I did a little digging through the source code and concluded that my assumption about the gpio subsystem was correct—it's not implemented. The only thing it does implement is reading bootstrap. Obviously, this isn't enough for gpio to work in any form, so I tinkered with the code a bit. Here is a link to my implementation of the gpio subsystem
https://pastee.dev/p/uhW37KEF
I implemented the virtual registers from the "esp32 technical reference manual," section 4.12.1—the code can write data to and from the GPIO matrix registers, and they will retain their states. I added 40 qemu_irq input lines, which are responsible for emulating the GPIO lines.When signals arrive on these lines, virtual registers will be updated and edge-triggered interrupts will be generated. Level-triggered interrupts will also be generated, but they will work incorrectly—they will be cleared even if the signal remains active. Frankly, I'm just too lazy to implement the correct logic for their operation—I need a mental break from digging through the qemu source code and do something else. I didn't implement the GPIO output lines at all, but you can do that yourself if you need them. The logic is very simple: when the ESP firmware wants to set a certain GPIO level, it simply writes the required values to the appropriate registers. You simply look at the data written to the registers and modify the corresponding qemu_irq lines.
A few words about how to apply my code. First, completely replace the old files /hw/gpio/esp32_gpio.c and /include/hw/gpio/esp32_gpio.h. Next, in the file /hw/xtensa/esp32.c, find the function esp32_soc_realize. It contains these lines:
Code: Select all
qdev_realize(DEVICE(&s->gpio), &s->periph_bus, &error_fatal);
esp32_soc_add_periph_device(sys_mem, &s->gpio, DR_REG_GPIO_BASE);
Immediately after these lines, add the following code
Code: Select all
qdev_connect_gpio_out_named(
DEVICE( &s->gpio ),
ESP32_GPIO_IRQ_GPIO,
0,
qdev_get_gpio_in( DEVICE(&s->intmatrix), ETS_GPIO_INTR_SOURCE )
);
qdev_connect_gpio_out_named(
DEVICE( &s->gpio ),
ESP32_GPIO_NMI_IRQ_GPIO,
0,
qdev_get_gpio_in( DEVICE(&s->intmatrix), ETS_GPIO_NMI_SOURCE )
);
These two functions connect 2 interrupt lines "irq" and "nmi irq" from the gpio matrix to the interrupt matrix. It works like this: When you configure an interrupt on any GPIO, the GPIO matrix begins monitoring the GPIO states. When a GPIO interrupt occurs, the GPIO matrix sets the interrupt active level on the IRQ and NMI IRQ lines depending on the settings. The firmware then accesses the GPIO matrix, determines which pins the interrupt occurred on, and runs the appropriate interrupt handler.
Next, if you want to use the GPIO input, you should add code like this to esp32.c
Code: Select all
DeviceState* i2c_master = DEVICE(&s->i2c[0]);
I2CBus* i2c_bus = I2C_BUS( qdev_get_child_bus(i2c_master, "i2c") );
I2CSlave* bicPhoneKeyboard;
DeviceState* keyboardDeviceState;
qemu_irq gpioMatrixInGpio;
bicPhoneKeyboard = i2c_slave_create_simple( i2c_bus, "bic-phone-keyboard", 0x58 );
keyboardDeviceState = DEVICE( bicPhoneKeyboard );
gpioMatrixInGpio = qdev_get_gpio_in_named( DEVICE(&s->gpio), "gpio_in", 25 );
qdev_connect_gpio_out( keyboardDeviceState, 0, gpioMatrixInGpio );
I'm new to qemu programming, so I'm not sure exactly where to add it. I moved my code into a separate function and run it from the esp32_machine_init function immediately after the line esp32_machine_init_i2c(ss); This code, for example, connects my keyboard's output GPIO to input GPIO 25 on the ESP32 GPIO matrix. Further in my esp32 firmware code, I configure the interrupt for gpio25, and it works as usual.
I hope this information will be useful to someone and will help them achieve their goal.