From 59da9fb94287b11527b2e96c2fdd6befb3a79882 Mon Sep 17 00:00:00 2001 From: BiffoBear Date: Fri, 17 Feb 2023 00:46:22 +0300 Subject: [PATCH 1/2] Added socket reservation system to WIZNET5K. --- adafruit_wiznet5k/adafruit_wiznet5k.py | 69 +++++++++++++++----- adafruit_wiznet5k/adafruit_wiznet5k_debug.py | 49 ++++++++++++++ 2 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 adafruit_wiznet5k/adafruit_wiznet5k_debug.py diff --git a/adafruit_wiznet5k/adafruit_wiznet5k.py b/adafruit_wiznet5k/adafruit_wiznet5k.py index bf6a122..8212de5 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k.py @@ -45,11 +45,13 @@ from random import randint import time +import gc from micropython import const from adafruit_bus_device.spi_device import SPIDevice import adafruit_wiznet5k.adafruit_wiznet5k_dhcp as dhcp import adafruit_wiznet5k.adafruit_wiznet5k_dns as dns +from adafruit_wiznet5k.adafruit_wiznet5k_debug import debug_msg # Wiznet5k Registers _REG_MR = const(0x0000) # Mode @@ -144,6 +146,8 @@ class WIZNET5K: # pylint: disable=too-many-public-methods, too-many-instance-at _UDP_MODE = const(0x02) _TLS_MODE = const(0x03) # This is NOT currently implemented + _sockets_reserved = [] + # pylint: disable=too-many-arguments def __init__( self, @@ -722,26 +726,61 @@ def _send_socket_cmd(self, socket: int, cmd: int) -> None: if self._debug: print("waiting for sncr to clear...") - def get_socket(self) -> int: - """Request, allocate and return a socket from the W5k chip. + def get_socket(self, *, reserve_socket=False) -> int: + """ + Request, allocate and return a socket from the W5k chip. + + Cycle through the sockets to find the first available one. If the called with + reserve_socket=True, update the list of reserved sockets (intended to be used with + socket.socket()). Note that reserved sockets must be released by calling + cancel_reservation() once they are no longer needed. + + If all sockets are reserved, no sockets are available for DNS calls, etc. Therefore, + one socket cannot be reserved. Since socket 0 is the only socket that is capable of + operating in MacRAW mode, it is the non-reservable socket. - Cycle through the sockets to find the first available one, if any. + :param bool reserve_socket: Whether to reserve the socket. - :return int: The first available socket. Returns 0xFF if no sockets are free. + :returns int: The first available socket. + + :raises RuntimeError: If no socket is available. """ - if self._debug: - print("*** Get socket") + debug_msg("*** Get socket.", self._debug) + # Prefer socket zero for none reserved calls as it cannot be reserved. + if not reserve_socket and self.socket_status(0)[0] == SNSR_SOCK_CLOSED: + debug_msg("Allocated socket # 0", self._debug) + return 0 + # Then check the other sockets. - sock = _SOCKET_INVALID - for _sock in range(self.max_sockets): - status = self.socket_status(_sock)[0] - if status == SNSR_SOCK_CLOSED: - sock = _sock - break + # Call garbage collection to encourage socket.__del__() be called to on any + # destroyed instances. Not at all guaranteed to work! + gc.collect() + debug_msg( + "Reserved sockets: {}".format(WIZNET5K._sockets_reserved), self._debug + ) - if self._debug: - print("Allocated socket #{}".format(sock)) - return sock + for socket_number, reserved in enumerate(WIZNET5K._sockets_reserved, start=1): + if ( + not reserved + and self.socket_status(socket_number)[0] == SNSR_SOCK_CLOSED + ): + if reserve_socket: + WIZNET5K._sockets_reserved[socket_number - 1] = True + debug_msg( + "Allocated socket # {}.".format(socket_number), + self._debug, + ) + return socket_number + raise RuntimeError("Out of sockets.") + + @staticmethod + def release_socket(socket_number): + """ + Update the socket reservation list when a socket is no longer reserved. + + :param int socket_number: The socket to release. + """ + WIZNET5K._sockets_reserved[socket_number] = False def socket_listen( self, socket_num: int, port: int, conn_mode: int = _SNMR_TCP diff --git a/adafruit_wiznet5k/adafruit_wiznet5k_debug.py b/adafruit_wiznet5k/adafruit_wiznet5k_debug.py new file mode 100644 index 0000000..9fb02c6 --- /dev/null +++ b/adafruit_wiznet5k/adafruit_wiznet5k_debug.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: 2023 Martin Stephens +# +# SPDX-License-Identifier: MIT + +"""Makes a debug message function available to all modules.""" +try: + from typing import TYPE_CHECKING, Union + + if TYPE_CHECKING: + from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K +except ImportError: + pass + +import gc + + +def debug_msg( + message: Union[Exception, str, bytes, bytearray], debugging: bool +) -> None: + """ + Helper function to print debugging messages. If the message is a bytes type + object, create a hexdump. + + :param Union[Exception, str, bytes, bytearray] message: The message to print. + :param bool debugging: Only print if debugging is True. + """ + if debugging: + if isinstance(message, (bytes, bytearray)): + message = _hexdump(message) + print(message) + del message + gc.collect() + + +def _hexdump(src: bytes): + """ + Create a 16 column hexdump of a bytes object. + + :param bytes src: The bytes object to hexdump. + + :returns str: The hexdump. + """ + result = [] + for i in range(0, len(src), 16): + chunk = src[i : i + 16] + hexa = " ".join(("{:02x}".format(x) for x in chunk)) + text = "".join((chr(x) if 0x20 <= x < 0x7F else "." for x in chunk)) + result.append("{:04x} {:<48} {}".format(i, hexa, text)) + return "\n".join(result) From 3d96b1cc42b98da9e0e382194861fe7e90e403d3 Mon Sep 17 00:00:00 2001 From: BiffoBear Date: Fri, 17 Feb 2023 01:13:29 +0300 Subject: [PATCH 2/2] Added release_socket() called to socket.close(), __exit__() and __del__ plus bug fixes. --- adafruit_wiznet5k/adafruit_wiznet5k.py | 9 ++++++++- adafruit_wiznet5k/adafruit_wiznet5k_socket.py | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/adafruit_wiznet5k/adafruit_wiznet5k.py b/adafruit_wiznet5k/adafruit_wiznet5k.py index 8212de5..b4c3408 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k.py @@ -193,6 +193,13 @@ def __init__( self._ch_base_msb = 0 if self._w5xxx_init() != 1: raise RuntimeError("Failed to initialize WIZnet module.") + if self._chip_type == "w5100s": + WIZNET5K._sockets_reserved = [False] * (_W5100_MAX_SOCK_NUM - 1) + elif self._chip_type == "w5500": + WIZNET5K._sockets_reserved = [False] * (_W5200_W5500_MAX_SOCK_NUM - 1) + else: + raise RuntimeError("Unrecognized chip type.") + # Set MAC address self.mac_address = mac self.src_port = 0 @@ -780,7 +787,7 @@ def release_socket(socket_number): :param int socket_number: The socket to release. """ - WIZNET5K._sockets_reserved[socket_number] = False + WIZNET5K._sockets_reserved[socket_number - 1] = False def socket_listen( self, socket_num: int, port: int, conn_mode: int = _SNMR_TCP diff --git a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py index 0378d03..611e66d 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py @@ -231,14 +231,18 @@ def __init__( self._timeout = _default_socket_timeout self._listen_port = None - self._socknum = _the_interface.get_socket() + self._socknum = _the_interface.get_socket(reserve_socket=True) if self._socknum == _SOCKET_INVALID: raise RuntimeError("Failed to allocate socket.") + def __del__(self): + _the_interface.release_socket(self._socknum) + def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb) -> None: + _the_interface.release_socket(self._socknum) if self._sock_type == SOCK_STREAM: self._disconnect() stamp = time.monotonic() @@ -601,6 +605,7 @@ def close(self) -> None: Mark the socket closed. Once that happens, all future operations on the socket object will fail. The remote end will receive no more data. """ + _the_interface.release_socket(self._socknum) _the_interface.socket_close(self._socknum) def _available(self) -> int: