首页 > 解决方案 > 如何将 QEMU 模拟机器中的 GPIO 连接到主机中的对象?

问题描述

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

例如,输出 GPIO 上的电平应该由矩形的颜色来反映。输入 GPIO 应连接到按钮。当按下 GUI 中的按钮时,输入 GPIO 应被读取为零(否则为一)等。当然输入 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。此外,建议的解决方案仅与旧的基于 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 中更改 pin 的状态时,都会向 QEMU 发送类似的消息,但它应该只包含一个字段:

State: (High, Low)

我假设当有人试图读取未连接的输入时解决冲突并生成随机状态的逻辑应该在 GUI 应用程序中实现。

这是一个可行的解决方案吗?

另一种可能的解决方案

在 Xilinx 修改的 QEMU 版本中,我发现了一些可能是解决方案的东西,或者至少提供了找到解决方案的方法。

这些是https://github.com/Xilinx/qemu/tree/master/include/hwhttps://github.com/Xilinx/qemu/tree/master中名称以“remote-port”开头的文件/hw/核心目录。

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

标签: emulationqemugpio

解决方案


我已经设法将 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,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”中找到


推荐阅读