|
21 | 21 | bitstream_hash |
22 | 22 | ) |
23 | 23 | 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, |
26 | 28 | ) |
27 | 29 |
|
28 | 30 | import grpc |
@@ -184,6 +186,7 @@ def __init__(self, index=0, ip_addr=None, port=PYNQ_PORT, tag="remote{}"): |
184 | 186 | 'device': remote_device_pb2_grpc.RemoteDeviceStub(self.client.channel), |
185 | 187 | 'mmio': mmio_pb2_grpc.MmioStub(self.client.channel), |
186 | 188 | 'buffer': buffer_pb2_grpc.RemoteBufferStub(self.client.channel), |
| 189 | + 'gpio': gpio_pb2_grpc.GpioStub(self.client.channel), |
187 | 190 | } |
188 | 191 |
|
189 | 192 | self.arch = self.get_arch() |
@@ -515,48 +518,294 @@ def allocate(self, shape, dtype, cacheable=1, **kwargs): |
515 | 518 |
|
516 | 519 |
|
517 | 520 | 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. |
522 | 532 | """ |
523 | 533 |
|
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 | +
|
527 | 537 | Parameters |
528 | 538 | ---------- |
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 |
533 | 545 | """ |
| 546 | + if device is None: |
| 547 | + raise RuntimeError("RemoteGPIO requires a RemoteDevice instance") |
| 548 | + self.device = device |
534 | 549 | 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 |
537 | 557 |
|
538 | 558 | 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 |
540 | 574 |
|
541 | 575 | 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 | + ) |
543 | 610 |
|
544 | 611 | 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() |
546 | 620 |
|
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 | +
|
548 | 630 | """ |
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.") |
550 | 647 |
|
551 | | - # Add class methods to match the GPIO API |
552 | 648 | @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 |
555 | 659 | |
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 | +
|
557 | 720 | """ |
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 | + |
560 | 809 |
|
561 | 810 | class RemoteInterrupt: |
562 | 811 | """Remote Interrupt placeholder class |
|
0 commit comments