如何通过主机上运行的 virtio GUI 在 QEMU 模拟虚拟机中使用 GPIO 进行连接?

问题描述 投票:0回答:1

前段时间,我问了一个类似的问题,“如何将 QEMU 模拟机中的 GPIO 连接到主机中的对象?” 经过一些工作,我找到了一个不完美但令人满意的解决方案

但是,现在我们有了支持 GPIO 的 virtio,最好使用该解决方案而不是修改过的 mpc8xxx 驱动程序。 之前的方案并不完美且难以维护(我只移植到了Buildroot 2021.02并停止了进一步的维护)。

不幸的是,我没有看到任何可以用来将 GUI 连接到 QEMU 中模拟的机器的主机端 virtio-user-gpio 实现。

是否有任何库(最好带有 Python 绑定)可以帮助完成此任务? 我应该从头开始,为 GPIO 设备实现

规范中定义的 virtio 协议吗?

qemu gpio virtio
1个回答
0
投票
终于,经过近一年的时间,我找到了我的问题的答案。 事实上,正确的解决方案基于

vhost-user-gpio 的 Rust 实现。我已经创建了我自己的fork,解决方案在分支gpio-python

我修改了

MockGpioDevice 的实现,使其使用简单的 HTTP 传输连接到 JSON RPC 服务器:

use jsonrpc::Client; use jsonrpc::simple_http::{self, SimpleHttpTransport}; use serde_json::json; use serde_json::value::to_raw_value; fn client() -> std::result::Result<Client,simple_http::Error> { let url = "http://127.0.0.1:8001"; let t = SimpleHttpTransport::builder() .url(url)? .build(); Ok(Client::with_transport(t)) } fn call(cli : &Client, fun : &str, param : serde_json::Value ) -> serde_json::Value { let raw_value = Some(to_raw_value(&param).unwrap()); let request = cli.build_request(fun, raw_value.as_deref()); let response = cli.send_request(request).expect("send_request failed"); let resp2 : serde_json::Value = serde_json::from_str((*response.result.unwrap()).get()).unwrap(); return resp2; }
处理 GPIO 状态变化的各个函数执行 RPC 调用。例如GPIO引脚的

实现如下:

fn value(&self, gpio: u16) -> Result<u8> { if self.value_result.is_err() { return self.value_result; } let resp = call(&self.rpc_client,"value",json!([gpio])); println!("{:?}",resp); let val : u8 = resp[1].as_u64().unwrap().try_into().unwrap() ; return Ok(val); } fn set_value(&self, gpio: u16, value: u32) -> Result<()> { info!( "gpio {} set value to {}", self.gpio_names[gpio as usize], value ); if self.set_value_result.is_err() { return self.set_value_result; } let resp = call(&self.rpc_client,"set_value",json!([gpio,value])); println!("{:?}",resp); return Ok(()); }

JSON RPC 服务器是使用tinyrpc 在Python 中实现的。它连接到改编自 我的旧解决方案Gtk GUI

对应的

readwrite引脚实现非常简单。

@dispatcher.public def value(n): return ("OK",gpios[n].val) @dispatcher.public def set_value(n,v): gpios[n].val = v rpc_server.change_handler(n,v) return ("OK")
字段

val

由GUI中的
send_change函数修改:

def send_change(nof_pin, state): do_irq = (rpc.gpios[nof_pin].val != state) rpc.gpios[nof_pin].val = state if do_irq: with rpc.gpios[nof_pin].wait_both: rpc.gpios[nof_pin].wait_both.notify_all() if state == 1: with rpc.gpios[nof_pin].wait_rise: rpc.gpios[nof_pin].wait_rise.notify_all() if state == 0: with rpc.gpios[nof_pin].wait_fall: rpc.gpios[nof_pin].wait_fall.notify_all()
该函数还支持使用 

wait_for_interrupt 函数处理 GPIO 生成的中断。

def wait_for_interrupt(n): if gpios[n].irq_type == 1: # RISING with gpios[n].wait_rise: gpios[n].wait_rise.wait() if gpios[n].irq_type == 2: # FALLING with gpios[n].wait_fall: gpios[n].wait_fall.wait() if gpios[n].irq_type == 3: # BOTH with gpios[n].wait_both: gpios[n].wait_both.wait() return ("OK",1)
vhost-device-gpio中对应的

function如下:

fn wait_for_interrupt(&self, gpio: u16) -> Result<bool> { if self.wait_for_irq_result.is_err() { return self.wait_for_irq_result; } let resp = call(&self.rpc_client,"wait_for_interrupt",json!([gpio])); println!("{:?}",resp); let val : bool = resp[1].as_u64().unwrap() > 0; return Ok(val); }
为了进行测试,我首先通过在安装了以下软件的虚拟环境中运行 

python3 gui3.py

 来启动 GUI:
tinyrpc
gevent
werkzeug
pgi
。
然后我启动了vhost-device-gpio:

LD_LIBRARY_PATH=/home/emb/libgpiod-2.1/lib/.libs/ ./vhost-device-gpio -s /tmp/gpio.sock -l s1
必须设置 LD_LIBRARY_PATH,因为在我的 Debian/测试机器中,使用了旧的 libgpiod。因此,我不得不在

/home/emb/libgpiod-2.1

中编译新版本。当然,
vhost-device-gpio
也需要专门打造:

export PATH_TO_LIBGPIOD=/home/emb/libgpiod-2.1 export SYSTEM_DEPS_LIBGPIOD_NO_PKG_CONFIG=1 export SYSTEM_DEPS_LIBGPIOD_SEARCH_NATIVE="${PATH_TO_LIBGPIOD}/lib/.libs/" export SYSTEM_DEPS_LIBGPIOD_LIB=gpiod export SYSTEM_DEPS_LIBGPIOD_INCLUDE="${PATH_TO_LIBGPIOD}/include/" cargo build --features "mock_gpio"
当 GUI 和 vhost-device-gpio 启动时,我可以使用连接到我的模拟 GPIO 的来宾操作系统来运行 QEMU。为此,我使用了为 

qemu-aarch64-virt

 平台构建的 Linux 和 Buildroot 2023.11.1。

当然,我必须在 Linux 内核配置中启用

CONFIG_GPIO_VIRTIO=m

。要模拟 GPIO 中断,必须对 QEMU 进行修补,如
此处所述。

仿真机启动后,就可以加载驱动程序了:

modprobe gpio-virtio

。然后可以使用 
gpiomon 0 12
 测试中断,使用 
gpioget 0 4
 读取引脚或使用 
gpioset 0 24
 写入引脚(当然你可以更改引脚编号)。

所描述的解决方案只是概念的证明。错误检测和处理几乎不存在。此外,当客户等待中断时停止仿真可能很困难。不过,我希望这可能是进一步发展的良好起点。

© www.soinside.com 2019 - 2024. All rights reserved.