如何将 QEMU 仿真机中的 GPIO 连接到主机中的对象?

How to connect GPIO in QEMU-emulated machine to an object in host?

我需要将 QEMU 中模拟的 ARM 机器中的 GPIO 引脚连接到主机上运行的应用程序中的 GUI 对象。

例如,输出GPIO上的电平应该通过矩形的颜色来反映。输入 GPIO 应该连接到一个按钮。当按下 GUI 中的按钮时,输入 GPIO 应读取为零(否则为一)等。 当然,输入 GPIO 也应该能够产生中断。

事实上,将模拟的 pin 连接到管道或套接字将是完美的,这样 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。此外,建议的解决方案仅与旧的基于 sysfs 的处理 GPIO 的方法兼容。

https://github.com/maquefel/virtual_gpio_basic 存储库中提供了基于上述概念的更新解决方案。不过不清楚是否兼容libgpiod

这个问题有现成的解决方案吗?

一种可能的解决方案

实现 GUI 的应用程序可以使用 msgpack ( https://msgpack.org/ ) 协议通过套接字与 QEMU 通信 (msgpack 可以轻松实现各种语言的 GUI,包括 Python 或 Lua)。

因此,每当 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 and https://github.com/Xilinx/qemu/tree/master/hw/core 目录中名称以 "remote-port" 开头的文件。

不幸的是,Xilinx 解决方案似乎旨在与 System-C 进行协同仿真,无法轻松适应与用户 GUI 应用程序的通信。

我已经成功地将 GPIO 连接到 Python 中编写的 GUI。 当前通过 POSIX 消息队列建立通信。 我修改了 QEMU 4.2.0 中可用的 mpc8xxx.c GPIO 模型,添加了接收输入线状态并在消息中报告输出线状态的函数。

我修改了 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;

将数据写入 pin 的原始程序已修改为通过消息队列报告所有修改的位:

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,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();
        }
    }
}

接收线程在修改后的实例初始化过程中启动:

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);
}

简约的 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()

将修改后的 MPC8XXX 与模拟 Vexpress A9 机器集成的完整项目可在我的存储库 https://github.com/wzab/BR_Internet_Radio

的分支 "gpio" 中找到