I need to connect the GPIO pins in the ARM machine emulated in QEMU to the GUI objects in application working on the host machine.
For example, the level on the output GPIO should be reflected by a color of a rectangle. The input GPIO should be connected to a button. When the button in GUI is pressed, the input GPIO should be read as zero (otherwise as one) etc. Of course the input GPIOs should be also capable of generating the interrupts.
In fact it would be perfect to connect the emulated pin to a pipe or socket so that a change of the state caused by QEMU would produce a message sent to the host, and the appropriate message sent by the host should trigger the appropriate change of the state of GPIO in QEMU (and possibly generate an interrupt).
I have created a few own peripherials for QEMU (e.g, https://github.com/wzab/qemu/blob/ster3/hw/misc/wzab_sysbus_enc1.c ) but implementation of such GPIO seems to be not trivial.
Up to now I have found that material: https://sudonull.com/post/80905-Virtual-GPIO-driver-with-QEMU-ivshmem-interrupt-controller-for-Linux but it uses relatively old QEMU. Additionally, the proposed solution is compatible only with the old sysfs-based method of handling GPIOs.
A newer solution based on the above concept is available in the https://github.com/maquefel/virtual_gpio_basic repository. However, it is not clear if it is libgpiod compatible.
Are there any existing solutions of that problem?
One possible solution
The application implementing the GUI could use msgpack ( https://msgpack.org/ ) protocol to communicate the QEMU via a socket (msgpack enables easy implementation of GUI in various languages including Python or Lua).
So whenever the QEMU changes the state of the pin, it sends a message contining two fields:
Direction: (In, Out)
State: (High, Low, High Impedance)
Whenever somebody changes the state of the pin in the GUI, similar message is sent to QEMU, but it should contain only one field:
State: (High, Low)
I assume that the logic that resolves collisions and generates the random state when somebody tries to read the not connected input should be implemented in the GUI application.
Is it a viable solution?
Another possible solution
In a version of QEMU modified by Xilinx I have found something that either maybe a solution, or at least provides means to find the solution.
These are the files with names starting with "remote-port" in the https://github.com/Xilinx/qemu/tree/master/include/hw and https://github.com/Xilinx/qemu/tree/master/hw/core directories.
Unfortunately, it seems that the Xilinx solution is aimed at cosimulation with System-C and can't be easily adapted for communication with the user GUI application.
I have managed to connect the GPIO to the GUI written in Python. The communication is currently established via POSIX message queues. I have modified the mpc8xxx.c model of GPIO available in QEMU 4.2.0, adding functions that receive the state of input lines and report the state of the output lines in messages.
I have modifed the MPC8XXXGPIOState adding the output message queue, the mutex and the receiving thread:
typedef struct MPC8XXXGPIOState {
SysBusDevice parent_obj;
MemoryRegion iomem;
qemu_irq irq;
qemu_irq out[32];
mqd_t mq;
QemuThread thread;
QemuMutex dat_lock;
uint32_t dir;
uint32_t odr;
uint32_t dat;
uint32_t ier;
uint32_t imr;
uint32_t icr;
} MPC8XXXGPIOState;
The changes of the pins are transmitted as structures:
typedef struct {
uint8_t magick[2];
uint8_t pin;
uint8_t state;
} gpio_msg;
The original procedure writing the data to the pin has been modified to report all modified bits via message queue:
static void mpc8xxx_write_data(MPC8XXXGPIOState *s, uint32_t new_data)
{
uint32_t old_data = s->dat;
uint32_t diff = old_data ^ new_data;
int i;
qemu_mutex_lock(&s->dat_lock);
for (i = 0; i < 32; i++) {
uint32_t mask = 0x80000000 >> i;
if (!(diff & mask)) {
continue;
}
if (s->dir & mask) {
gpio_msg msg;
msg.magick[0] = 0x69;
msg.magick[1] = 0x10;
msg.pin = i;
msg.state = (new_data & mask) ? 1 : 0;
/* Output */
qemu_set_irq(s->out[i], (new_data & mask) != 0);
/* Send the new value */
mq_send(s->mq,(const char *)&msg,sizeof(msg),0);
/* Update the bit in the dat field */
s->dat &= ~mask;
if ( new_data & mask ) s->dat |= mask;
}
}
qemu_mutex_unlock(&s->dat_lock);
}
Information about the pins modified by the GUI is received in a separate thread:
static void * remote_gpio_thread(void * arg)
{
//Here we receive the data from the queue
const int MSG_MAX = 8192;
char buf[MSG_MAX];
gpio_msg * mg = (gpio_msg *)&buf;
mqd_t mq = mq_open("/to_qemu",O_CREAT | O_RDONLY,S_IRUSR | S_IWUSR,NULL);
if(mq<0) {
perror("I can't open mq");
exit(1);
}
while(1) {
int res = mq_receive(mq,buf,MSG_MAX,NULL);
if(res<0) {
perror("I can't receive");
exit(1);
}
if(res != sizeof(gpio_msg)) continue;
if((int) mg->magick[0]*256+mg->magick[1] != REMOTE_GPIO_MAGICK) {
printf("Wrong message received");
}
if(mg->pin < 32) {
qemu_mutex_lock_iothread();
mpc8xxx_gpio_set_irq(arg,mg->pin,mg->state);
qemu_mutex_unlock_iothread();
}
}
}
The receiving thread is started in the modified instance initialization procedure:
static void mpc8xxx_gpio_initfn(Object *obj)
{
DeviceState *dev = DEVICE(obj);
MPC8XXXGPIOState *s = MPC8XXX_GPIO(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &mpc8xxx_gpio_ops,
s, "mpc8xxx_gpio", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
qdev_init_gpio_in(dev, mpc8xxx_gpio_set_irq, 32);
qdev_init_gpio_out(dev, s->out, 32);
qemu_mutex_init(&s->dat_lock);
s->mq = mq_open("/from_qemu",O_CREAT | O_WRONLY,S_IRUSR | S_IWUSR,NULL);
qemu_thread_create(&s->thread, "remote_gpio", remote_gpio_thread, s,
QEMU_THREAD_JOINABLE);
}
The minimalistic GUI is written in Python and GTK:
#!/usr/bin/python3
# Sources:
# https://lazka.github.io/pgi-docs
# https://python-gtk-3-tutorial.readthedocs.io/en/latest/button_widgets.html
# https://developer.gnome.org/gtk3/stable/
# Threads: https://wiki.gnome.org/Projects/PyGObject/Threading
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk
import threading
# Communication part
import struct
pipc_magick = 0x6910
import posix_ipc as pipc
mq_to_qemu = pipc.MessageQueue("/to_qemu",flags=pipc.O_CREAT, read=False, write=True)
mq_from_qemu = pipc.MessageQueue("/from_qemu",flags=pipc.O_CREAT, read=True, write=False)
def send_change(nof_pin, state):
s=struct.pack(">HBB",pipc_magick,nof_pin,state)
mq_to_qemu.send(s)
def recv_change(msg):
mg, pin, state = struct.unpack(">HBB",msg)
print("mg=",mg," pin=",pin," state=",state)
if mg != pipc_magick:
raise Exception("Wrong magick number in GPIO IPC message")
if state == 0:
s = 0
else:
s = 1
GLib.idle_add(MyLeds[pin-24].change_state,s)
def receiver():
while True:
msg = mq_from_qemu.receive()
recv_change(msg[0])
class MySwitch(Gtk.Switch):
def __init__(self,number):
super().__init__()
self.number = number
class MyButton(Gtk.Button):
def __init__(self,number):
super().__init__(label=str(number))
self.number = number
class MyLed(Gtk.Label):
color = Gdk.color_parse('gray')
rgba0 = Gdk.RGBA.from_color(color)
color = Gdk.color_parse('green')
rgba1 = Gdk.RGBA.from_color(color)
del color
def __init__(self, number):
super().__init__( label=str(number))
self.number = number
self.change_state(0)
def change_state(self,state):
if state == 1:
self.override_background_color(0,self.rgba1)
else:
self.override_background_color(0,self.rgba0)
MyLeds = []
class SwitchBoardWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Switch Demo")
self.set_border_width(10)
mainvbox = Gtk.Box(orientation = Gtk.Orientation.VERTICAL, spacing = 6)
self.add(mainvbox)
#Create the switches
label = Gtk.Label(label = "Stable switches: left 0, right 1")
mainvbox.pack_start(label,True,True,0)
hbox = Gtk.Box(spacing=6)
for i in range(0,12):
vbox = Gtk.Box(orientation = Gtk.Orientation.VERTICAL, spacing = 6)
label = Gtk.Label(label = str(i))
vbox.pack_start(label,True,True,0)
switch = MySwitch(i)
switch.connect("notify::active", self.on_switch_activated)
switch.set_active(False)
vbox.pack_start(switch,True,True,0)
hbox.pack_start(vbox, True, True, 0)
mainvbox.pack_start(hbox,True,True,0)
#Create the buttons
label = Gtk.Label(label = "Unstable buttons: pressed 0, released 1")
mainvbox.pack_start(label,True,True,0)
hbox = Gtk.Box(spacing=6)
for i in range(12,24):
button = MyButton(i)
button.connect("button-press-event", self.on_button_clicked,0)
button.connect("button-release-event", self.on_button_clicked,1)
hbox.pack_start(button,True,True,0)
mainvbox.pack_start(hbox,True,True,0)
#Create the LEDS
label = Gtk.Label(label = "LEDs")
mainvbox.pack_start(label,True,True,0)
hbox = Gtk.Box(spacing=6)
for i in range(24,32):
led = MyLed(i)
MyLeds.append(led)
hbox.pack_start(led,True,True,0)
mainvbox.pack_start(hbox,True,True,0)
def on_switch_activated(self, switch, gparam):
if switch.get_active():
state = 0
else:
state = 1
#MyLeds[switch.number].change_state(state)
send_change(switch.number,state)
print("Switch #"+str(switch.number)+" was turned", state)
return True
def on_button_clicked(self, button,gparam, state):
print("pressed!")
send_change(button.number,state)
print("Button #"+str(button.number)+" was turned", state)
return True
win = SwitchBoardWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
thread = threading.Thread(target=receiver)
thread.daemon = True
thread.start()
Gtk.main()
The full project integrating the modified MPC8XXX with emulated Vexpress A9 machine is available in the branch "gpio" of my repository https://github.com/wzab/BR_Internet_Radio