/********************************************************************* PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved. See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage. Authors: Frederik Van Slycken *********************************************************************/ #include "pico_protocol.h" #include "pico_hotplug_detection.h" #include "pico_tree.h" #include "pico_device.h" struct pico_hotplug_device { struct pico_device *dev; int prev_state; struct pico_tree callbacks; struct pico_tree init_callbacks; /* functions we still need to call for initialization */ }; static uint32_t timer_id = 0; static int pico_hotplug_dev_cmp(void *ka, void *kb) { struct pico_hotplug_device *a = ka, *b = kb; if (a->dev->hash < b->dev->hash) return -1; if (a->dev->hash > b->dev->hash) return 1; return 0; } static int callback_compare(void *ka, void *kb) { if (ka < kb) return -1; if (ka > kb) return 1; return 0; } static PICO_TREE_DECLARE(Hotplug_device_tree, pico_hotplug_dev_cmp); static void initial_callbacks(struct pico_hotplug_device *hpdev, int event) { struct pico_tree_node *cb_node = NULL, *cb_safe = NULL; void (*cb)(struct pico_device *dev, int event); pico_tree_foreach_safe(cb_node, &(hpdev->init_callbacks), cb_safe) { cb = cb_node->keyValue; cb(hpdev->dev, event); pico_tree_delete(&hpdev->init_callbacks, cb); } } static void execute_callbacks(struct pico_hotplug_device *hpdev, int new_state, int event) { struct pico_tree_node *cb_node = NULL, *cb_safe = NULL; void (*cb)(struct pico_device *dev, int event); if (new_state != hpdev->prev_state) { /* we don't know if one of the callbacks might deregister, so be safe */ pico_tree_foreach_safe(cb_node, &(hpdev->callbacks), cb_safe) { cb = cb_node->keyValue; cb(hpdev->dev, event); } hpdev->prev_state = new_state; } } static void timer_cb(__attribute__((unused)) pico_time t, __attribute__((unused)) void*v) { struct pico_tree_node *node = NULL, *safe = NULL; int new_state, event; struct pico_hotplug_device *hpdev = NULL; /* we don't know if one of the callbacks might deregister, so be safe */ pico_tree_foreach_safe(node, &Hotplug_device_tree, safe) { hpdev = node->keyValue; new_state = hpdev->dev->link_state(hpdev->dev); if (new_state == 1) { event = PICO_HOTPLUG_EVENT_UP; } else { event = PICO_HOTPLUG_EVENT_DOWN; } initial_callbacks(hpdev, event); execute_callbacks(hpdev, new_state, event); } timer_id = pico_timer_add(PICO_HOTPLUG_INTERVAL, &timer_cb, NULL); if (timer_id == 0) { dbg("HOTPLUG: Failed to start timer\n"); } } static int ensure_hotplug_timer(void) { if (timer_id == 0) { timer_id = pico_timer_add(PICO_HOTPLUG_INTERVAL, &timer_cb, NULL); if (timer_id == 0) { dbg("HOTPLUG: Failed to start timer\n"); return -1; } } return 0; } static void disable_hotplug_timer(void) { if (timer_id != 0) { pico_timer_cancel(timer_id); timer_id = 0; } } int pico_hotplug_register(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)) { struct pico_hotplug_device *hotplug_dev; struct pico_hotplug_device search = { .dev = dev }; /* If it does not have a link_state, */ /* the device does not support hotplug detection */ if (dev->link_state == NULL) { pico_err = PICO_ERR_EPROTONOSUPPORT; return -1; } hotplug_dev = (struct pico_hotplug_device*)pico_tree_findKey(&Hotplug_device_tree, &search); if (!hotplug_dev ) { hotplug_dev = PICO_ZALLOC(sizeof(struct pico_hotplug_device)); if (!hotplug_dev) { pico_err = PICO_ERR_ENOMEM; return -1; } hotplug_dev->dev = dev; hotplug_dev->prev_state = dev->link_state(hotplug_dev->dev); hotplug_dev->callbacks.root = &LEAF; hotplug_dev->callbacks.compare = &callback_compare; hotplug_dev->init_callbacks.root = &LEAF; hotplug_dev->init_callbacks.compare = &callback_compare; if (pico_tree_insert(&Hotplug_device_tree, hotplug_dev)) { PICO_FREE(hotplug_dev); return -1; } } if (pico_tree_insert(&(hotplug_dev->callbacks), cb) == &LEAF) { PICO_FREE(hotplug_dev); return -1; } if (pico_tree_insert(&(hotplug_dev->init_callbacks), cb) == &LEAF) { pico_tree_delete(&(hotplug_dev->callbacks), cb); PICO_FREE(hotplug_dev); return -1; } if (ensure_hotplug_timer() < 0) { pico_hotplug_deregister((struct pico_device *)hotplug_dev, cb); return -1; } return 0; } int pico_hotplug_deregister(struct pico_device *dev, void (*cb)(struct pico_device *dev, int event)) { struct pico_hotplug_device*hotplug_dev; struct pico_hotplug_device search = { .dev = dev }; hotplug_dev = (struct pico_hotplug_device*)pico_tree_findKey(&Hotplug_device_tree, &search); if (!hotplug_dev) /* wasn't registered */ return 0; pico_tree_delete(&hotplug_dev->callbacks, cb); pico_tree_delete(&hotplug_dev->init_callbacks, cb); if (pico_tree_empty(&hotplug_dev->callbacks)) { pico_tree_delete(&Hotplug_device_tree, hotplug_dev); PICO_FREE(hotplug_dev); } if (pico_tree_empty(&Hotplug_device_tree)) { disable_hotplug_timer(); } return 0; }