我需要将在QEMU中仿真的ARM机器中的GPIO引脚连接到在主机上工作的应用程序中的GUI对象。
例如,输出GPIO上的电平应由矩形颜色反映。输入GPIO应该连接到按钮。当按下GUI中的按钮时,输入GPIO应该读为零(否则读为1),等等。当然,输入GPIO也应该能够产生中断。
实际上,将仿真引脚连接到管道或插座是理想的,这样,由QEMU引起的状态更改将产生一条消息发送给主机,并且主机发送的相应消息应触发相应的更改QEMU中GPIO状态的状态(并可能产生中断)。
我为QEMU创建了一些自己的外围设备(例如https://github.com/wzab/qemu/blob/ster3/hw/misc/wzab_sysbus_enc1.c,但是这种GPIO的实现似乎并不简单。
[到目前为止,我发现该材料:https://sudonull.com/post/80905-Virtual-GPIO-driver-with-QEMU-ivshmem-interrupt-controller-for-Linux,但它使用了相对较旧的QEMU。此外,建议的解决方案仅与处理GPIO的基于sysfs的旧方法兼容。
基于上述概念的较新解决方案可在https://github.com/maquefel/virtual_gpio_basic存储库中找到。但是,尚不清楚它是否与libgpiod兼容。
是否存在该问题的现有解决方案?
一种可能的解决方案
实现GUI的应用程序可以使用msgpack(https://msgpack.org/)协议通过套接字来传递QEMU(通过msgpack,可以轻松地以各种语言(包括Python或Lua)来实现GUI)。
因此,只要QEMU更改了引脚的状态,它都会发送一条连续两个字段的消息:
Direction: (In, Out)
State: (High, Low, High Impedance)
[只要有人在GUI中更改引脚的状态,类似的消息就会发送到QEMU,但它应该只包含一个字段:
State: (High, Low)
我假设应该在GUI应用程序中实现解决冲突并在有人尝试读取未连接的输入时生成随机状态的逻辑。
这是可行的解决方案吗?
另一个可能的解决方案
在Xilinx修改的QEMU版本中,我发现了可能是解决方案,或者至少提供了找到解决方案的方法。
这些是https://github.com/Xilinx/qemu/tree/master/include/hw和https://github.com/Xilinx/qemu/tree/master/hw/core目录中名称以“ remote-port”开头的文件。
不幸的是,Xilinx解决方案似乎旨在与System-C协同仿真,因此无法轻松地与用户GUI应用程序进行通信。
我设法将GPIO连接到用Python编写的GUI。当前通过POSIX消息队列建立通信。我修改了QEMU 4.2.0中可用的GPIO的mpc8xxx.c模型,添加了接收输入行状态并在消息中报告输出行状态的函数。
我修改了MPC8XXXGPIOState,添加了输出消息队列,互斥锁和接收线程:
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;
引脚的变化作为结构传输:
typedef struct {
uint8_t magick[2];
uint8_t pin;
uint8_t state;
} gpio_msg;
将数据写入引脚的原始过程已被修改以通过消息队列报告所有修改的位:
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);
}
关于GUI修改的引脚的信息在单独的线程中接收:
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,0x660,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();
}
}
}
接收线程在修改后的实例初始化过程中启动:
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,0x660,NULL);
qemu_thread_create(&s->thread, "remote_gpio", remote_gpio_thread, s,
QEMU_THREAD_JOINABLE);
}
简约的GUI用Python和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()
在我的存储库https://github.com/wzab/BR_Internet_Radio的分支“ gpio”中可以找到将修改后的MPC8XXX与仿真的Vexpress A9计算机集成在一起的完整项目,>