Virtual Gpio Emulation

Virtual GPIO emulation

The solution exists and is available since kernel 4.10 as GPIO mockup driver.

Some references for you:

  • https://github.com/torvalds/linux/blob/v4.10/tools/testing/selftests/gpio/gpio-mockup.sh
  • https://github.com/torvalds/linux/blob/master/drivers/gpio/gpio-mockup.c

How to setup virtual Gpio loopback

If there is no real hardware available to you, there is no such possibility without hacking a real kernel driver. Luckily in the Linux kernel we have gpio-mockup (and new coming gpio-sim) which is used for testing GPIO library and APIs.

The main idea is that. You create a virtual GPIO chip:

% modprobe gpio-mockup gpio_mockup_ranges=-1,3 gpio_mockup_named_lines

% gpiodetect
...
gpiochip2 [gpio-mockup-A] (3 lines)

% gpioinfo 2
gpiochip2 - 3 lines:
line 0: "gpio-mockup-A-0" unused input active-high
line 1: "gpio-mockup-A-1" unused input active-high
line 2: "gpio-mockup-A-2" unused input active-high

Through DebugFS you may see the lines as being on the hardware side, i.e. whatever you write there, the Linux stack will consider as a state of hardware:

% mount -t debugfs none /sys/kernel/debug

% ls -l /sys/kernel/debug/gpio-mockup/gpiochip2/
total 0
--w------- 1 root root 0 Jan 29 01:10 0
--w------- 1 root root 0 Jan 29 01:10 1
--w------- 1 root root 0 Jan 29 01:10 2

(Note write-only attribute there)

Now demo part

Check first the current state of a pin (let's choose pin 2 of that virtual GPIO controller):

% gpioget 2 2
0

Let's assume the hardware state of the pin has been changed from 0 to 1:

% echo 1 > /sys/kernel/debug/gpio-mockup/gpiochip2/2

Check the state of the pin again:

% gpioget 2 2
1

For the output part it's a bit complicated, i.e. either you need deprecated sysfs interface to the GPIO chip or you have to run a thread or fork a process from your code to keep the context state correct. With gpioget and gpioset it's currently impossible because the driver restores the state immediately after device node (of the GPIO controller) is closed.

Overall I think you got the idea and maybe you even don't need an output GPIO line.

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

I have managed to connect the GPIO to the GUI written in Python.
The communication is currently established via POSIX message queues.
I have modified the mpc8xxx.c model of GPIO available in QEMU 4.2.0, adding functions that receive the state of input lines and report the state of the output lines in messages.

I have modifed the MPC8XXXGPIOState adding the output message queue, the mutex and the receiving thread:


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;

The changes of the pins are transmitted as structures:

typedef struct {
uint8_t magick[2];
uint8_t pin;
uint8_t state;
} gpio_msg;

The original procedure writing the data to the pin has been modified to report all modified bits via message queue:

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

Information about the pins modified by the GUI is received in a separate thread:

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

The receiving thread is started in the modified instance initialization procedure:

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

The minimalistic GUI is written in Python and 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()

The full project integrating the modified MPC8XXX with emulated Vexpress A9 machine is available in the branch "gpio" of my repository https://github.com/wzab/BR_Internet_Radio

Is there a Raspberry Pi hardware emulator that supports custom ISOs?

For most of the complex hardware (i.e. CPU, GPU, RAM, network cards, disks and so on), qemu has you covered, there also seem to be a --machine raspi3b flag that's supposed to be close to a Raspberry Pi 3B but I can't find any information about what is included exactly.

If you want to emulate some other raspi, you can use qemu-system-aarch64 --machine help to list all 64bits ARM devices that can be emulated by qemu (note that as of writing this, qemu has no support for the raspi4).

If the emulation offered by qemu doesn't include some hardware you want, a lot of things can be emulated using dummy kernel modules such as the GPIO mockup driver as explained here and the mac80211_hwsim module. It's probably not as close to the hardware as you'd like though, if you prefer a more hands on approach you could create qemu "hardware" to be a perfect replica of the pi.

How to emulate the Raspberry Pi 2 on QEMU?

If you're comfortable building qemu, you can find support for pi2 system emulation here: https://github.com/0xabu/qemu. It's not particularly speedy, and the device emulations are incomplete, but you can resize the RAM and framebuffer.

There are brief instructions for booting Raspbian at the end of https://github.com/0xabu/qemu/wiki

Is there an emulator of MSP430 chip that works without the actual chip and integrates with Code Composer Studio?

This emulator is pretty awesome, once you can get it running. Note that it does claim GDB support, which likely means you can get a pure eclipse CDT C project & CDT-GDB-HW-Debugging session up and running against it (making sure to compile with the msp430's tool chain, of course).

http://opencores.org/project,openmsp430

As far as a simulator, the answer is truly 'no'. I would like to be wrong on that... But consider for a moment the number of variants of the msp430, the peripherals, and so on. Not sure if any company can justify that kind of cost!

Especially when launchpad/etc are so cheap and fast.



Related Topics



Leave a reply



Submit