3535#if MICROPY_PY_GPIO_IRQ && !MICROPY_PY_THREAD
3636#error "GPIO IRQ support needs threading to work"
3737#endif
38+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION && !MICROPY_PY_THREAD
39+ #error "GPIO dynamic pin allocation needs threading to work"
40+ #endif
3841
3942#include <dirent.h>
4043#include <fcntl.h>
4851#include <signal.h>
4952#include <sys/epoll.h>
5053#endif
54+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
55+ #include <unistd.h>
56+ #include <sys/inotify.h>
57+ #endif
5158
5259#include <linux/gpio.h>
5360
@@ -84,6 +91,9 @@ typedef struct _gpio_port_t {
8491 mp_obj_t * pins ;
8592 int fd ;
8693 uint32_t lines ;
94+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
95+ int watch_descriptor ;
96+ #endif
8797} gpio_port_t ;
8898
8999#if MICROPY_PY_GPIO_IRQ
@@ -96,6 +106,16 @@ static volatile bool gpio_epoll_thread_stop = false;
96106
97107#endif
98108
109+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
110+
111+ static void * gpio_inotify_thread (void * arg );
112+
113+ static pthread_t gpio_inotify_thread_id = 0 ;
114+ static int gpio_inotify_fd = -1 ;
115+ static volatile bool gpio_inotify_thread_stop = false;
116+
117+ #endif
118+
99119static void gpio_ioctl (int fd , int call , void * payload ) {
100120 MP_THREAD_GIL_EXIT ();
101121 int result = ioctl (fd , call , payload );
@@ -380,6 +400,45 @@ static void gpio_epoll_remove_pin(mp_obj_t pin_in) {
380400
381401#endif
382402
403+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
404+
405+ static void gpio_inotify_init (void ) {
406+ MP_THREAD_GIL_EXIT ();
407+ gpio_inotify_fd = inotify_init1 (IN_CLOEXEC );
408+ MP_THREAD_GIL_ENTER ();
409+ if (gpio_inotify_fd < 0 ) {
410+ mp_raise_OSError (errno );
411+ }
412+
413+ MP_THREAD_GIL_EXIT ();
414+ int result = pthread_create (& gpio_inotify_thread_id , NULL , & gpio_inotify_thread , (void * )& gpio_inotify_thread_stop );
415+ MP_THREAD_GIL_ENTER ();
416+ if (result != 0 ) {
417+ mp_raise_OSError (errno );
418+ }
419+ }
420+
421+ static void gpio_inotify_deinit (void ) {
422+ gpio_inotify_thread_stop = true;
423+ MP_THREAD_GIL_EXIT ();
424+ close (gpio_inotify_fd );
425+ MP_THREAD_GIL_ENTER ();
426+ gpio_inotify_fd = -1 ;
427+ MP_THREAD_GIL_EXIT ();
428+ int result = pthread_cancel (gpio_inotify_thread_id );
429+ MP_THREAD_GIL_ENTER ();
430+ assert (result >= 0 && "pthread_cancel failed to cancel the inotify thread" );
431+ MP_THREAD_GIL_EXIT ();
432+ result = pthread_join (gpio_inotify_thread_id , NULL );
433+ MP_THREAD_GIL_ENTER ();
434+ assert (result >= 0 && "pthread_join failed to wait until the inotify exited" );
435+ (void )result ;
436+ gpio_inotify_thread_id = 0 ;
437+ m_del (struct inotify_event , MP_STATE_PORT (inotify_events ), MICROPY_PY_GPIO_ALLOCATION_QUEUE_SIZE );
438+ }
439+
440+ #endif
441+
383442// This expects to receive an already resolved port name!
384443static gpio_port_t * gpio_add_port (mp_obj_t path ) {
385444 mp_map_t * ports = & MP_STATE_PORT (ports );
@@ -396,6 +455,10 @@ static gpio_port_t *gpio_add_port(mp_obj_t path) {
396455 mp_raise_OSError (errno );
397456 }
398457
458+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
459+ int watch_descriptor = -1 ;
460+ #endif
461+
399462 gpio_port_t * port ;
400463 nlr_buf_t nlr ;
401464 if (nlr_push (& nlr ) == 0 ) {
@@ -417,12 +480,27 @@ static gpio_port_t *gpio_add_port(mp_obj_t path) {
417480 port -> fd = fd ;
418481 port -> lines = info .lines ;
419482
483+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
484+ // TODO: See if an inotify watch on the /dev entry is sufficient, otherwise
485+ // rebuild a sysfs path out of this.
486+ watch_descriptor = inotify_add_watch (gpio_inotify_fd , mp_obj_str_get_str (path ), IN_DELETE | IN_DELETE_SELF );
487+ if (watch_descriptor < 0 ) {
488+ mp_raise_OSError (errno );
489+ }
490+ port -> watch_descriptor = watch_descriptor ;
491+ #endif
492+
420493 slot = mp_map_lookup (ports , path , MP_MAP_LOOKUP_ADD_IF_NOT_FOUND );
421494 assert (slot && "No slot to fill was returned on port add" );
422495 slot -> value = MP_OBJ_FROM_PTR (port );
423496
424497 nlr_pop ();
425498 } else {
499+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
500+ if (watch_descriptor != -1 ) {
501+ inotify_rm_watch (gpio_inotify_fd , watch_descriptor );
502+ }
503+ #endif
426504 close (fd );
427505
428506 // Re-raise the exception.
@@ -1098,16 +1176,92 @@ static void *gpio_epoll_thread(void *arg) {
10981176}
10991177#endif
11001178
1179+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
1180+ static void * gpio_inotify_thread (void * arg ) {
1181+ // Keep a reference to the stop variable.
1182+ volatile bool * stop = (volatile bool * )arg ;
1183+
1184+ for (;;) {
1185+ ssize_t read_count = read (gpio_inotify_fd , MP_STATE_PORT (inotify_events ), MICROPY_PY_GPIO_ALLOCATION_QUEUE_SIZE * sizeof (struct inotify_event ));
1186+ if (* stop ) {
1187+ goto done ;
1188+ }
1189+
1190+ if (read_count < 0 ) {
1191+ // This is not correct but the code isn't in its final form yet.
1192+ continue ;
1193+ }
1194+
1195+ const char * current_pointer = MP_STATE_PORT (inotify_events );
1196+ for (;;) {
1197+ const struct inotify_event * event = (const struct inotify_event * )current_pointer ;
1198+ mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION ();
1199+ // Find which port is involved with the removal event.
1200+ mp_map_t * ports = & MP_STATE_PORT (ports );
1201+ if (ports -> table != NULL ) {
1202+ for (size_t port_index = 0 ; port_index < ports -> alloc ; port_index ++ ) {
1203+ if (!mp_map_slot_is_filled (ports , port_index )) {
1204+ continue ;
1205+ }
1206+ mp_map_elem_t slot = ports -> table [port_index ];
1207+ gpio_port_t * port = MP_OBJ_TO_PTR (slot .value );
1208+ if (port -> watch_descriptor != event -> wd ) {
1209+ continue ;
1210+ }
1211+ // This port was removed. Evict all pins so the last one
1212+ // will deallocate the port as well.
1213+ for (size_t pin = 0 ; pin < port -> lines ; pin ++ ) {
1214+ if (port -> pins [pin ] != MP_OBJ_NULL ) {
1215+ gpio_evict_pin (port -> pins [pin ]);
1216+ }
1217+ }
1218+ // Remove the watch.
1219+ inotify_rm_watch (gpio_inotify_fd , event -> wd );
1220+ break ;
1221+ }
1222+ }
1223+ MICROPY_END_ATOMIC_SECTION (atomic_state );
1224+ current_pointer += sizeof (struct inotify_event ) + event -> len ;
1225+ if (((ptrdiff_t )current_pointer - (ptrdiff_t )MP_STATE_PORT (inotify_events )) >= read_count ) {
1226+ break ;
1227+ }
1228+ }
1229+ }
1230+
1231+ done :
1232+ return NULL ;
1233+ }
1234+ #endif
1235+
11011236void mp_pin_init (void ) {
11021237 mp_map_init (& MP_STATE_PORT (ports ), 0 );
11031238
11041239 #if MICROPY_PY_GPIO_IRQ
11051240 MP_STATE_PORT (epoll_events ) = m_new (struct epoll_event , MICROPY_PY_GPIO_IRQ_QUEUE_SIZE );
1241+ #endif
1242+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
1243+ // The documentation suggests to align the buffer using the structure size
1244+ // as the alignment boundary, to improve performance. In this case we can
1245+ // make the trade-off to wait a millisecond or so later to know that a
1246+ // GPIO port went away. Even if something happens between the port
1247+ // disappearance and the inotify report, any ioctl on the port's pin file
1248+ // descriptors (or on the port's descriptor itself) would fail, and this
1249+ // should be accounted for anyway.
1250+ MP_STATE_PORT (inotify_events ) = m_new (struct inotify_event , MICROPY_PY_GPIO_ALLOCATION_QUEUE_SIZE );
1251+ #endif
1252+
1253+ #if MICROPY_PY_GPIO_IRQ
11061254 gpio_epoll_init ();
11071255 #endif
1256+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
1257+ gpio_inotify_init ();
1258+ #endif
11081259}
11091260
11101261void mp_pin_deinit (void ) {
1262+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
1263+ gpio_inotify_deinit ();
1264+ #endif
11111265 #if MICROPY_PY_GPIO_IRQ
11121266 gpio_epoll_deinit ();
11131267 #endif
@@ -1217,4 +1371,8 @@ MP_REGISTER_ROOT_POINTER(void *epoll_events);
12171371MP_REGISTER_ROOT_POINTER (mp_set_t epoll_pins );
12181372#endif
12191373
1374+ #if MICROPY_PY_GPIO_DYNAMIC_ALLOCATION
1375+ MP_REGISTER_ROOT_POINTER (void * inotify_events );
1376+ #endif
1377+
12201378#endif
0 commit comments