QEMU学习(二):LED设备仿真及驱动开发

在仿真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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值