Как подключить GPIO в QEMU-эмулированной машине к объекту в хосте? - PullRequest
0 голосов
/ 19 марта 2020

Мне нужно подключить выводы GPIO на компьютере ARM, эмулированном в QEMU, к объектам GUI в приложении, работающем на главном компьютере.

Например, уровень на выходе GPIO должен отражаться как цвет прямоугольника. Вход GPIO должен быть подключен к кнопке. При нажатии кнопки в GUI входной GPIO должен читаться как ноль (иначе как единица) и c. Конечно, вход GP IOs также должен быть способен генерировать прерывания.

На самом деле было бы идеально подключить эмулируемый вывод к трубе или сокету, чтобы изменение состояния, вызванное QEMU, создать сообщение, отправленное на хост, и соответствующее сообщение, отправленное хостом, должно вызвать соответствующее изменение состояния GPIO в QEMU (и, возможно, сгенерировать прерывание).

Я создал несколько собственных периферийных устройств для 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 способом обработки GP IOs.

Более новое решение, основанное на вышеупомянутой концепции, доступно в репозитории 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)

Я предполагаю, что logi c, который разрешает коллизии и генерирует случайное состояние, когда кто-то пытается прочитать несвязанный вход, должен быть реализован в GUI приложение.

Это жизнеспособное решение?

Другое возможное решение

В версии QEMU, модифицированной Xilinx I нашли что-то, что может быть решением или, по крайней мере, предоставляет средства для поиска решения.

Это файлы с именами, начинающимися с «remote-port» в https://github.com/Xilinx/qemu/tree/master/include/hw и https://github.com/Xilinx/qemu/tree/master/hw/core каталогов.

К сожалению, кажется, что решение Xilinx нацелено на симуляцию с System- C и не может быть легко адаптировано для связи с пользователем GUI применение.

1 Ответ

0 голосов
/ 22 марта 2020

Мне удалось подключить GPIO к GUI, записанному в Python. В настоящее время связь устанавливается через очереди сообщений POSIX. Я изменил модель GPIO mpc8xxx. c, доступную в QEMU 4.2.0, добавив функции, которые получают состояние входных линий и сообщают о состоянии выходных линий в сообщениях.

Я модифицировал 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);
}

Минимализм c 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 с эмулированной машиной V express A9 доступен в ветке "gpio" моего репозитория https://github.com/wzab/BR_Internet_Radio

...