在仿真led之前,先来了解一下QEMU源码结构及GPIO仿真原理。
QEMU源码目录
我们只罗列出涉及的少许文件,由此可以看出,我们要仿真的设备文件都放在hw目录下,一般来说一个.c 文件会有一个.h 文件,它们的目录类似。
比如 hw/gpio/imx_gpio.c 对应的头文件为 include/hw/gpio/imx_gpio.h。

QEMU设备仿真原理
一个板子上有很多硬件:IMX6ULL、LED、按键、LCD、触摸屏、网卡等等。
IMX6ULL 这类芯片被称为 SoC(System on Chip),它里面也有很多部件,比如 CPU、GPIO、
SD 控制器、中断控制器等等。这些硬件,或是部件,各有不同。怎么描述它们?
首先,我们要把芯片真实设备地址添加上去,在设备地址的基础上每一个都使用一个 TypeInfo 结构体来描述。当我们驱动访问这个设备地址时,仿佛是与真实的设备通信。
/*\qemu\hw\arm\fsl-imx6ul.c*/
enum FslIMX6ULMemoryMap {
FSL_IMX6UL_MMDC_ADDR = 0x80000000,
FSL_IMX6UL_MMDC_SIZE = 2 * 1024 * 1024 * 1024UL,
FSL_IMX6UL_QSPI1_MEM_ADDR = 0x60000000,
FSL_IMX6UL_EIM_ALIAS_ADDR = 0x58000000,
FSL_IMX6UL_EIM_CS_ADDR = 0x50000000,
FSL_IMX6UL_AES_ENCRYPT_ADDR = 0x10000000,
FSL_IMX6UL_QSPI1_RX_ADDR = 0x0C000000,
/* 100ask IOMUXC_SNVS */
FSL_IMX6UL_IOMUXC_SNVS = 0x02290000,
/* AIPS-2 */
FSL_IMX6UL_GPIO5_ADDR = 0x020AC000,
FSL_IMX6UL_GPIO4_ADDR = 0x020A8000,
FSL_IMX6UL_GPIO3_ADDR = 0x020A4000,
FSL_IMX6UL_GPIO2_ADDR = 0x020A0000,
FSL_IMX6UL_GPIO1_ADDR = 0x0209C000,
FSL_IMX6UL_GPT1_ADDR = 0x02098000,
};
/*
* GPIOs 1 to 5
*/
for (i = 0; i < FSL_IMX6UL_NUM_GPIOS; i++) {
snprintf(name, NAME_SIZE, "gpio%d", i);
sysbus_init_child_obj(obj, name, &s->gpio[i], sizeof(s->gpio[i]),
TYPE_IMX_GPIO);
/*
* GPIO
*/
for (i = 0; i < FSL_IMX6UL_NUM_GPIOS; i++) {
static const hwaddr FSL_IMX6UL_GPIOn_ADDR[FSL_IMX6UL_NUM_GPIOS] = {
FSL_IMX6UL_GPIO1_ADDR,
FSL_IMX6UL_GPIO2_ADDR,
FSL_IMX6UL_GPIO3_ADDR,
FSL_IMX6UL_GPIO4_ADDR,
FSL_IMX6UL_GPIO5_ADDR,
};
static const int FSL_IMX6UL_GPIOn_LOW_IRQ[FSL_IMX6UL_NUM_GPIOS] = {
FSL_IMX6UL_GPIO1_LOW_IRQ,
FSL_IMX6UL_GPIO2_LOW_IRQ,
FSL_IMX6UL_GPIO3_LOW_IRQ,
FSL_IMX6UL_GPIO4_LOW_IRQ,
FSL_IMX6UL_GPIO5_LOW_IRQ,
};
static const int FSL_IMX6UL_GPIOn_HIGH_IRQ[FSL_IMX6UL_NUM_GPIOS] = {
FSL_IMX6UL_GPIO1_HIGH_IRQ,
FSL_IMX6UL_GPIO2_HIGH_IRQ,
FSL_IMX6UL_GPIO3_HIGH_IRQ,
FSL_IMX6UL_GPIO4_HIGH_IRQ,
FSL_IMX6UL_GPIO5_HIGH_IRQ,
};
object_property_set_bool(OBJECT(&s->gpio[i]), true, "realized",
&error_abort);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0,
FSL_IMX6UL_GPIOn_ADDR[i]);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0,
qdev_get_gpio_in(DEVICE(&s->a7mpcore),
FSL_IMX6UL_GPIOn_LOW_IRQ[i]));
sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 1,
qdev_get_gpio_in(DEVICE(&s->a7mpcore),
FSL_IMX6UL_GPIOn_HIGH_IRQ[i]));
}
我们以最基础的GPIO为例,找到hw/gpio/imx_gpio.c,可以看到最后几行
static void imx_gpio_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = imx_gpio_realize;
dc->reset = imx_gpio_reset;
dc->props = imx_gpio_properties;
dc->vmsd = &vmstate_imx_gpio;
dc->desc = "i.MX GPIO controller";
}
static const TypeInfo imx_gpio_info = {
.name = TYPE_IMX_GPIO,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(IMXGPIOState),
.class_init = imx_gpio_class_init,
};
static void imx_gpio_register_types(void)
{
type_register_static(&imx_gpio_info);
}
type_init(imx_gpio_register_types)
我们从后向前分析,
24行函数type_init(),字面意思是把类型初始化,有点像驱动里的module_init(),他是把要仿真的模块注册进设备链表中,这个设备链表会在main之前初始化。
19行是把我们设备结构体注册成一个类型或是模块。
12行是创建一个我们要仿真的设备信息,然后填充结构体包含:设备名,父类,设备大小,设备类初始化。
1行是把此设备类进行初始化,包含:实现方法,设备重置,特性设置,版本设置,设备描述。
其中在imx_gpio_realize里注册并初始化的就是我们要仿真的GPIO真正的操作了,
memory_region_init_io(&s->iomem, OBJECT(s), &imx_gpio_ops, s,
TYPE_IMX_GPIO, IMX_GPIO_MEM_SIZE);
memory_region_init_io用来为IO在GPIO设备基地址上申请内存空间,并指定ops,当发生读写操作时会调用read,write来完成。可以看出imx_gpio_ops包含了GPIO的读写操作。
static const MemoryRegionOps imx_gpio_ops = {
.read = imx_gpio_read,
.write = imx_gpio_write,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
.endianness = DEVICE_NATIVE_ENDIAN,
};
其在写函数中,DR数据寄存器接收到了控制GPIO高低电平的值。
static void imx_gpio_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
IMXGPIOState *s = IMX_GPIO(opaque);
// info_report("test (%s, value = 0x%" PRIx32 ")\n", imx_gpio_reg_name(offset),
// (uint32_t)value);
DPRINTF("(%s, value = 0x%" PRIx32 ")\n", imx_gpio_reg_name(offset),
(uint32_t)value);
switch (offset) {
case DR_ADDR:
s->dr = value;
imx_gpio_set_all_output_lines(s);
imx_gpio_info_update(opaque, offset, value, size);
break;
......
}
其imx_gpio_info_update就是设置更新GPIO状态信息的,接着就进入今天的主题!
led的仿真
在这个函数中,把获得到的IO值写入imx6ul_board结构体中,这个结构体又是在100ask_qemu_fb.h里申请并初始化的。
static void imx_gpio_info_update (void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
IMXGPIOState *s = IMX_GPIO(opaque);
SysBusDevice *dev = SYS_BUS_DEVICE(s);
int group;
int pin;
int i;
...
for (i = 0; i < imx6ul_board->led_cnt; i++)
{
if (group == IMX6UL_GPIO_GROUP(imx6ul_board->leds[i].pin))
{
pin = IMX6UL_GPIO_PIN(imx6ul_board->leds[i].pin);
if (value & (1<<pin))
imx6ul_board->leds[i].on = 0;
else
imx6ul_board->leds[i].on = 1;
imx6ul_board->leds[i].need_ui_update = 1;
}
}
}
static void ask100fb_update(void *opaque)
{
ASK100FbState *s = ASK100FB(opaque);
SysBusDevice *sbd;
DisplaySurface *surface = qemu_console_surface(s->con);
static int inited = 0;
int dest_width;
int src_width;
int first = 0;
int last = 0;
int fb_x, fb_y;
src_width = s->fb_xres * s->fb_bpp / 8;
dest_width = s->board_xres * surface_bits_per_pixel(surface) / 8;
sbd = SYS_BUS_DEVICE(s);
if (inited)
{
imx_gpio_ui_update(opaque); // 判断led是否需要更新
if (!s->fb_base_phys)
return;
//if (s->invalidate) {
framebuffer_update_memory_section(&s->fbsection, sysbus_address_space(sbd), s->fb_base_phys,
s->fb_yres, src_width);
//}
framebuffer_update_display(surface, &s->fbsection, s->fb_xres, s->fb_yres,
src_width, dest_width, 0, 1, ask100fb_draw_line_src16,
s, &first, &last);
fb_x = imx6ul_board_descs[selected_board].lcd.x;
fb_y = imx6ul_board_descs[selected_board].lcd.y;
dpy_gfx_update(s->con, fb_x, fb_y, s->fb_xres, s->fb_yres);
//}
}
else
{
//dpy_gfx_update_image(s->con, "/home/book/board.bmp", 0, 0, s->board_xres, s->board_yres);
framebuffer_update_region(surface, &board_mem_pixels, 0, 0, s->board_xres, s->board_yres);
dpy_gfx_update(s->con, 0, 0, s->board_xres, s->board_yres);
imx_gpio_ui_init();
inited = 1;
}
s->invalidate = 0;
}
static void imx_gpio_ui_update(void *opaque)
{
ASK100FbState *s = ASK100FB(opaque);
DisplaySurface *surface = qemu_console_surface(s->con);
int i;
imx6ul_board_desc *brd = &imx6ul_board_descs[selected_board];
int need_update = 0;
led_desc *led;
for (i = 0; i < brd->led_cnt; i++)
{
led = &brd->leds[i];
if (led->need_ui_update) // 如果需要更新led,则更新led的UI显示
{
led->need_ui_update = 0;
need_update = 1;
if (led->on)
framebuffer_update_region(surface, &led_on_pixels, led->x, led->y, led->w, led->h);
else
framebuffer_update_region(surface, &led_off_pixels, led->x, led->y, led->w, led->h);
}
}
if (need_update)
{
dpy_gfx_update(s->con, 0, 0, s->board_xres, s->board_yres);
}
}
typedef struct imx6ul_board_desc {
const char *name;
const char *board_picture;
lcd_desc lcd;
int led_cnt;
led_desc leds[4];
}imx6ul_board_desc;
static imx6ul_board_desc imx6ul_board_descs[3] = {
{
.name = "100ask",
.board_picture = "board_100ask.bmp",
.lcd = {
.x = 60,
.y = 309,
.w = 500,
.h = 300,
},
.led_cnt = 4,
.leds = {
[0] = {
.led_on_picture = "led_on_100ask.bmp",
.led_off_picture = "led_off_100ask.bmp",
.pin = IMX6UL_GPIO(1, 3),
.x = 292,
.y = 181,
.w = 19,
.h = 42,
},
...
}
其中初始化函数中的led_on_100ask.bmp和led_off_100ask.bmp就是我们UI上点灯,灭灯的图片。
至此qemu中led的模拟告一段落,接下来实现开关led的驱动和应用。
led驱动
设备树配置
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status="okay";
};
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
}
return 0;
}
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int __init led_init(void)
{
int ret = 0;
/* 设置LED所使用的GPIO */
/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
printk("gpioled node not find!\r\n");
return -EINVAL;
} else {
printk("gpioled node find!\r\n");
}
/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
printk("led-gpio num = %d\r\n", gpioled.led_gpio);
/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (gpioled.major) { /* 定义了设备号 */
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
}
printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
/* 2、初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
/* 3、添加一个cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
/* 4、创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class)) {
return PTR_ERR(gpioled.class);
}
/* 5、创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device)) {
return PTR_ERR(gpioled.device);
}
return 0;
}
交叉编译出led.ko,dtb设备树文件。
led应用层操作代码,编译出ledtest可执行文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
* ./ledtest /dev/led0 on
* ./ledtest /dev/led0 off
*/
int main(int argc, char **argv)
{
int fd;
char status;
/* 1. 判断参数 */
if (argc != 3)
{
printf("Usage: %s <dev> <on | off>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
/* 3. 写文件 */
if (0 == strcmp(argv[2], "on"))
{
status = 1;
write(fd, &status, 1);
}
else
{
status = 0;
write(fd, &status, 1);
}
close(fd);
return 0;
}
最后开启qemu仿真
$ ./qemu-imx6ull-gui.sh
通过nfs挂在,拷贝上面的得到的dtb,led.ko,ledtest到qemu仿真的板子上。
[root@qemu_imx6ul:~]# mount -t nfs -o nolock,vers=3 10.0.2.2:/home/book/nfs_rootfs /mnt
开关led,最后可以看到最左边的灯已经被点亮。
[root@qemu_imx6ul:~]# cd led_driver_qemu/
[root@qemu_imx6ul:~/led_driver_qemu]# insmod led.ko
[root@qemu_imx6ul:~/led_driver_qemu]# ./ledtest /dev/led0 off
[root@qemu_imx6ul:~/led_driver_qemu]# ./ledtest /dev/led0 on
