@@ -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,
},
};
@@ -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 */
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(+)