diff mbox series

[RFC,2/6] hw/gpio/gpio_transmitter: Add allowlist

Message ID 20211216015417.1234812-3-komlodi@google.com (mailing list archive)
State New, archived
Headers show
Series Introduce GPIO transmitter and connect to NPCM7xx | expand

Commit Message

Joe Komlodi Dec. 16, 2021, 1:54 a.m. UTC
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 <komlodi@google.com>
---
 hw/gpio/google_gpio_transmitter.c         | 122 ++++++++++++++++++++++
 include/hw/gpio/google_gpio_transmitter.h |  20 ++++
 2 files changed, 142 insertions(+)
diff mbox series

Patch

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 */