To avoid spamming whoever is connected to the chardev any time a pin state changes, we'll provide an allowlist so the transmitter only transmits on state changes the user cares about.
The allowlist is a qdev property that takes in an array of pin numbers to pay attention to, and maps it to a relative pin number on a controller, assuming each controller has 32-bits of pins. If no allowlist is specified, we transmit on any pin update. Signed-off-by: Joe Komlodi <koml...@google.com> --- hw/gpio/google_gpio_transmitter.c | 122 ++++++++++++++++++++++ include/hw/gpio/google_gpio_transmitter.h | 20 ++++ 2 files changed, 142 insertions(+) diff --git a/hw/gpio/google_gpio_transmitter.c b/hw/gpio/google_gpio_transmitter.c index 3429121ccb..b0331e8f03 100644 --- a/hw/gpio/google_gpio_transmitter.c +++ b/hw/gpio/google_gpio_transmitter.c @@ -19,6 +19,7 @@ #include "qemu/osdep.h" +#include "migration/vmstate.h" #include "hw/gpio/google_gpio_transmitter.h" #include "hw/qdev-properties-system.h" #include "hw/sysbus.h" @@ -28,11 +29,36 @@ #define PACKET_REVISION 0x01 +static bool google_gpio_tx_check_allowlist(GoogleGPIOTXState *s, + uint32_t controller, uint32_t gpios) +{ + /* If the user didn't give us a list, allow everything */ + if (!s->gpio_state_by_ctlr) { + return true; + } + + GPIOCtlrState *gs = g_hash_table_lookup(s->gpio_state_by_ctlr, &controller); + + if (!gs) { + return false; + } + + bool updated = (gs->gpios & gs->allowed) != (gpios & gs->allowed); + /* Update the new state */ + gs->gpios = gpios; + + return updated; +} + void google_gpio_tx_transmit(GoogleGPIOTXState *s, uint8_t controller, uint32_t gpios) { uint8_t packet[6]; + if (!google_gpio_tx_check_allowlist(s, controller, gpios)) { + return; + } + packet[0] = PACKET_REVISION; packet[1] = controller; memcpy(&packet[2], &gpios, sizeof(gpios)); @@ -91,18 +117,112 @@ static int google_gpio_tx_can_receive(void *opaque) return 1; } +void google_gpio_tx_state_init(GoogleGPIOTXState *s, uint8_t controller, + uint32_t gpios) +{ + if (!s->gpio_state_by_ctlr) { + return; + } + + GPIOCtlrState *gs = g_hash_table_lookup(s->gpio_state_by_ctlr, &controller); + if (gs) { + gs->gpios = gpios; + } +} + +void google_gpio_tx_allowlist_qdev_init(GoogleGPIOTXState *s, + const uint32_t *allowed_pins, + size_t num) +{ + size_t i; + char propname[64]; + + qdev_prop_set_uint32(DEVICE(s), "len-gpio-allowlist", num); + + for (i = 0; i < num; i++) { + snprintf(propname, sizeof(propname), "gpio-allowlist[%zu]", i); + qdev_prop_set_uint32(DEVICE(s), propname, allowed_pins[i]); + } +} + +static void google_gpio_tx_allowlist_init(GoogleGPIOTXState *s) +{ + size_t i; + GPIOCtlrState *gs; + + if (!s->gpio_allowlist) { + return; + } + + s->gpio_state_by_ctlr = g_hash_table_new_full(g_int_hash, g_int_equal, + g_free, g_free); + + for (i = 0; i < s->gpio_allowlist_sz; i++) { + uint32_t controller = s->gpio_allowlist[i] / 32; + uint32_t pin = (1 << (s->gpio_allowlist[i] % 32)); + + gs = g_hash_table_lookup(s->gpio_state_by_ctlr, &controller); + if (gs) { + gs->allowed |= pin; + } else { + gs = g_malloc0(sizeof(*gs)); + gs->allowed |= pin; + /* + * The hash table relies on a pointer to be the key, so the pointer + * containing the controller num must remain unchanged. + * Because of that, just allocate a new key with the controller num. + */ + uint32_t *ctlr = g_memdup(&controller, sizeof(controller)); + g_hash_table_insert(s->gpio_state_by_ctlr, ctlr, gs); + } + } +} + static void google_gpio_tx_realize(DeviceState *dev, Error **errp) { GoogleGPIOTXState *s = GOOGLE_GPIO_TX(dev); + google_gpio_tx_allowlist_init(s); + qemu_chr_fe_set_handlers(&s->chr, google_gpio_tx_can_receive, google_gpio_tx_receive, google_gpio_tx_event, NULL, OBJECT(s), NULL, true); } +static void google_gpio_tx_finalize(Object *obj) +{ + GoogleGPIOTXState *s = GOOGLE_GPIO_TX(obj); + + g_hash_table_destroy(s->gpio_state_by_ctlr); + g_free(s->gpio_allowlist); +} + +static int google_gpio_tx_post_load(void *opaque, int version_id) +{ + GoogleGPIOTXState *s = GOOGLE_GPIO_TX(opaque); + + google_gpio_tx_allowlist_init(s); + return 0; +} + +static const VMStateDescription vmstate_google_gpio_tx = { + .name = "gpio_transmitter", + .version_id = 1, + .minimum_version_id = 1, + .post_load = google_gpio_tx_post_load, + .fields = (VMStateField[]) { + VMSTATE_VARRAY_UINT32(gpio_allowlist, GoogleGPIOTXState, + gpio_allowlist_sz, 0, vmstate_info_uint32, + uint32_t), + VMSTATE_END_OF_LIST() + } +}; + static Property google_gpio_properties[] = { DEFINE_PROP_CHR("gpio-chardev", GoogleGPIOTXState, chr), + DEFINE_PROP_ARRAY("gpio-allowlist", GoogleGPIOTXState, gpio_allowlist_sz, + gpio_allowlist, qdev_prop_uint32, uint32_t), DEFINE_PROP_END_OF_LIST(), }; @@ -112,6 +232,7 @@ static void google_gpio_tx_class_init(ObjectClass *klass, void *data) dc->desc = "Google GPIO Controller Transmitter"; dc->realize = google_gpio_tx_realize; + dc->vmsd = &vmstate_google_gpio_tx; device_class_set_props(dc, google_gpio_properties); } @@ -120,6 +241,7 @@ static const TypeInfo google_gpio_tx_types[] = { .name = TYPE_GOOGLE_GPIO_TRANSMITTER, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(GoogleGPIOTXState), + .instance_finalize = google_gpio_tx_finalize, .class_init = google_gpio_tx_class_init, }, }; diff --git a/include/hw/gpio/google_gpio_transmitter.h b/include/hw/gpio/google_gpio_transmitter.h index fa7d7b3b77..ddc3561372 100644 --- a/include/hw/gpio/google_gpio_transmitter.h +++ b/include/hw/gpio/google_gpio_transmitter.h @@ -34,13 +34,33 @@ typedef enum { GPIOTXCODE_UNKNOWN_VERSION = 0xe1, } GoogleGPIOTXCode; +typedef struct { + uint32_t gpios; + uint32_t allowed; +} GPIOCtlrState; + typedef struct { SysBusDevice parent; + GHashTable *gpio_state_by_ctlr; + uint32_t *gpio_allowlist; + uint32_t gpio_allowlist_sz; + CharBackend chr; } GoogleGPIOTXState; void google_gpio_tx_transmit(GoogleGPIOTXState *s, uint8_t controller, uint32_t gpios); +/* + * If using an allowlist, this function should be called by the GPIO controller + * to set an initial state of the controller's GPIO pins. + * Otherwise all pins will be assumed to have an initial state of 0. + */ +void google_gpio_tx_state_init(GoogleGPIOTXState *s, uint8_t controller, + uint32_t gpios); +/* Helper function to be called to initialize the allowlist qdev properties */ +void google_gpio_tx_allowlist_qdev_init(GoogleGPIOTXState *s, + const uint32_t *allowed_pins, + size_t num); #endif /* GOOGLE_GPIO_TRANSMITTER_H */ -- 2.34.1.173.g76aa8bc2d0-goog