Skip to content

Commit cf7b8d7

Browse files
authored
Adding remote GPIO functionality (#1528)
1 parent a4b3f93 commit cf7b8d7

File tree

9 files changed

+1397
-37
lines changed

9 files changed

+1397
-37
lines changed

pynq/pl_server/remote_device.py

Lines changed: 275 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
bitstream_hash
2222
)
2323
from pynq.remote import (
24-
remote_device_pb2_grpc, mmio_pb2_grpc, buffer_pb2_grpc,
25-
remote_device_pb2, mmio_pb2, buffer_pb2,
24+
remote_device_pb2_grpc, remote_device_pb2,
25+
mmio_pb2_grpc, mmio_pb2,
26+
buffer_pb2_grpc, buffer_pb2,
27+
gpio_pb2_grpc, gpio_pb2,
2628
)
2729

2830
import grpc
@@ -184,6 +186,7 @@ def __init__(self, index=0, ip_addr=None, port=PYNQ_PORT, tag="remote{}"):
184186
'device': remote_device_pb2_grpc.RemoteDeviceStub(self.client.channel),
185187
'mmio': mmio_pb2_grpc.MmioStub(self.client.channel),
186188
'buffer': buffer_pb2_grpc.RemoteBufferStub(self.client.channel),
189+
'gpio': gpio_pb2_grpc.GpioStub(self.client.channel),
187190
}
188191

189192
self.arch = self.get_arch()
@@ -515,48 +518,294 @@ def allocate(self, shape, dtype, cacheable=1, **kwargs):
515518

516519

517520
class RemoteGPIO:
518-
"""Remote GPIO placeholder class
519-
520-
Placeholder implementation for GPIO operations on remote devices.
521-
GPIO functionality is not yet implemented for remote PYNQ devices.
521+
"""Internal Helper class to wrap Linux's GPIO Sysfs API.
522+
523+
This GPIO class does not handle PL I/O without the use of
524+
device tree overlays.
525+
526+
Attributes
527+
----------
528+
index : int
529+
The index of the GPIO, starting from the GPIO base.
530+
direction : str
531+
Input/output direction of the GPIO.
522532
"""
523533

524-
def __init__(self, gpio_index=None, direction=None):
525-
"""Initialize RemoteGPIO object
526-
534+
def __init__(self, gpio_index, direction, device=None):
535+
"""Return a new RemoteGPIO object.
536+
527537
Parameters
528538
----------
529-
gpio_index : int, optional
530-
GPIO pin index number
531-
direction : str, optional
532-
GPIO direction ('in' or 'out')
539+
gpio_index : int
540+
The index of the RemoteGPIO using Linux's GPIO Sysfs API.
541+
direction : 'str'
542+
Input/output direction of the GPIO.
543+
device : RemoteDevice
544+
Device object for RemoteGPIO operations
533545
"""
546+
if device is None:
547+
raise RuntimeError("RemoteGPIO requires a RemoteDevice instance")
548+
self.device = device
534549
self.gpio_index = gpio_index
535-
self.direction = direction
536-
warnings.warn("GPIO operations are not yet implemented for remote devices")
550+
self._direction = direction
551+
self._stub = device._stub['gpio']
552+
553+
response = self._stub.get_gpio(
554+
gpio_pb2.GetGpioRequest(index=gpio_index, direction=direction)
555+
)
556+
self._gpio_id = response.gpio_id
537557

538558
def read(self):
539-
raise RuntimeError("GPIO operations are not yet implemented for remote devices")
559+
"""The method to read a value from the GPIO.
560+
561+
Returns
562+
-------
563+
int
564+
An integer read from the GPIO
565+
566+
"""
567+
if self.direction != 'in':
568+
raise AttributeError("Cannot read from GPIO output.")
569+
570+
response = self._stub.read(
571+
gpio_pb2.GpioReadRequest(gpio_id=self._gpio_id)
572+
)
573+
return response.value
540574

541575
def write(self, value):
542-
raise RuntimeError("GPIO operations are not yet implemented for remote devices")
576+
"""The method to write a value into the GPIO.
577+
578+
Parameters
579+
----------
580+
value : int
581+
An integer value, either 0 or 1
582+
583+
Returns
584+
-------
585+
None
586+
587+
"""
588+
if self.direction != 'out':
589+
raise AttributeError("Cannot write to GPIO input.")
590+
591+
if value not in (0, 1):
592+
raise ValueError("Can only write integer 0 or 1.")
593+
594+
response = self._stub.write(
595+
gpio_pb2.GpioWriteRequest(gpio_id=self._gpio_id, value=value)
596+
)
597+
return
598+
599+
def unexport(self):
600+
"""The method to unexport the GPIO using Linux's GPIO Sysfs API.
601+
602+
Returns
603+
-------
604+
None
605+
606+
"""
607+
response = self._stub.unexport(
608+
gpio_pb2.GpioUnexportRequest(gpio_id=self._gpio_id)
609+
)
543610

544611
def release(self):
545-
"""Release GPIO resources
612+
"""The method to release the GPIO.
613+
614+
Returns
615+
-------
616+
None
617+
618+
"""
619+
self.unexport()
546620

547-
No-op for remote GPIO placeholder
621+
def is_exported(self):
622+
"""The method to check if a GPIO is still exported using
623+
Linux's GPIO Sysfs API.
624+
625+
Returns
626+
-------
627+
bool
628+
True if the GPIO is still loaded.
629+
548630
"""
549-
pass
631+
response = self._stub.is_exported(
632+
gpio_pb2.GpioIsExportedRequest(gpio_id=self._gpio_id)
633+
)
634+
return bool(response.is_exported)
635+
636+
@property
637+
def index(self):
638+
return self.gpio_index
639+
640+
@property
641+
def direction(self):
642+
return self._direction
643+
644+
@property
645+
def path(self):
646+
warnings.warn("This property is not implemented for remote devices.")
550647

551-
# Add class methods to match the GPIO API
552648
@staticmethod
553-
def get_gpio_pin(gpio_user_index, target_label=None):
554-
"""Get GPIO pin by user index
649+
def get_gpio_base_path(target_label=None, device=None):
650+
"""This method returns the path to the Remote GPIO base using Linux's
651+
GPIO Sysfs API. This path relates to the target device, not the
652+
host machine.
653+
654+
This is a static method. To use:
655+
656+
>>> from pynq import GPIO
657+
658+
>>> from pynq.pl_server import RemoteDevice
555659
556-
Placeholder method to prevent attribute errors in remote device context.
660+
>>> device = RemoteDevice(ip_addr='192.168.2.99')
661+
662+
>>> gpio = GPIO.get_gpio_base_path(device=device)
663+
664+
Parameters
665+
----------
666+
target_label : str
667+
The label of the GPIO driver to look for, as defined in a
668+
device tree entry.
669+
device : RemoteDevice
670+
Remote device object for RemoteGPIO operations
671+
672+
Returns
673+
-------
674+
str
675+
The path to the Remote GPIO base.
676+
677+
"""
678+
if device is None:
679+
raise RuntimeError("get_gpio_base_path requires a RemoteDevice instance.")
680+
stub = device._stub['gpio']
681+
682+
response = stub.get_gpio_base_path(
683+
gpio_pb2.GetGpioBasePathRequest(target_label=target_label)
684+
)
685+
if response.base_path == "":
686+
return None
687+
return response.base_path
688+
689+
@staticmethod
690+
def get_gpio_base(target_label=None, device=None):
691+
"""This method returns the GPIO base using Linux's GPIO Sysfs API.
692+
693+
This is a static method. To use:
694+
695+
>>> from pynq import GPIO
696+
697+
>>> from pynq.pl_server import RemoteDevice
698+
699+
>>> device = RemoteDevice(ip_addr='192.168.2.99')
700+
701+
>>> gpio = GPIO.get_gpio_base(device)
702+
703+
Note
704+
----
705+
For path '/sys/class/gpio/gpiochip138/', this method returns 138.
706+
707+
Parameters
708+
----------
709+
target_label : str
710+
The label of the GPIO driver to look for, as defined in a
711+
device tree entry.
712+
device : RemoteDevice
713+
Remote device object for RemoteGPIO operations
714+
715+
Returns
716+
-------
717+
int
718+
The GPIO index of the base.
719+
557720
"""
558-
warnings.warn("GPIO operations are not yet implemented for remote devices")
559-
return gpio_user_index # Just return the index to prevent errors
721+
if device is None:
722+
raise RuntimeError("get_gpio_base requires a RemoteDevice instance.")
723+
724+
base_path = RemoteGPIO.get_gpio_base_path(target_label=target_label, device=device)
725+
if base_path is not None:
726+
return int(''.join(x for x in base_path if x.isdigit()))
727+
728+
@staticmethod
729+
def get_gpio_pin(gpio_user_index, target_label=None, device=None):
730+
"""This method returns a GPIO instance for PS GPIO pins.
731+
732+
Users only need to specify an index starting from 0; this static
733+
method will map this index to the correct Linux GPIO pin number.
734+
735+
Note
736+
----
737+
The GPIO pin number can be calculated using:
738+
GPIO pin number = GPIO base + GPIO offset + user index
739+
e.g. The GPIO base is 138, and pin 54 is the base GPIO offset.
740+
Then the Linux GPIO pin would be (138 + 54 + 0) = 192.
741+
742+
Parameters
743+
----------
744+
gpio_user_index : int
745+
The index specified by users, starting from 0.
746+
target_label : str
747+
The label of the GPIO driver to look for, as defined in a
748+
device tree entry.
749+
750+
Returns
751+
-------
752+
int
753+
The Linux Sysfs GPIO pin number.
754+
755+
"""
756+
if device is None:
757+
raise RuntimeError("get_gpio_pin requires a RemoteDevice instance.")
758+
759+
if target_label is not None:
760+
GPIO_OFFSET = 0
761+
else:
762+
if device.arch == "aarch64":
763+
GPIO_OFFSET = 78
764+
else:
765+
GPIO_OFFSET = 54
766+
return (RemoteGPIO.get_gpio_base(target_label=target_label, device=device) +
767+
GPIO_OFFSET +
768+
gpio_user_index)
769+
770+
@staticmethod
771+
def get_gpio_npins(target_label=None, device=None):
772+
"""This method returns the number of GPIO pins for the GPIO base
773+
using Linux's GPIO Sysfs API.
774+
775+
This is a static method. To use:
776+
777+
>>> from pynq import GPIO
778+
779+
>>> from pynq.pl_server import RemoteDevice
780+
781+
>>> device = RemoteDevice(ip_addr='192.168.2.99')
782+
783+
>>> gpio = GPIO.get_gpio_npins()
784+
785+
Parameters
786+
----------
787+
target_label : str
788+
The label of the GPIO driver to look for, as defined in a
789+
device tree entry.
790+
device : RemoteDevice
791+
Remote device object for RemoteGPIO operations
792+
793+
Returns
794+
-------
795+
int
796+
The number of GPIO pins for the GPIO base.
797+
798+
"""
799+
if device is None:
800+
raise RuntimeError("get_gpio_npins requires a RemoteDevice instance.")
801+
802+
stub = device._stub['gpio']
803+
response = stub.get_gpio_npins(
804+
gpio_pb2.GetGpioNPinsRequest(
805+
target_label=target_label)
806+
)
807+
return response.npins
808+
560809

561810
class RemoteInterrupt:
562811
"""Remote Interrupt placeholder class

pynq/remote/gpio_pb2.py

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)