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

这篇具有很好参考价值的文章主要介绍了QEMU学习(二):LED设备仿真及驱动开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在仿真led之前,先来了解一下QEMU源码结构及GPIO仿真原理。

QEMU源码目录

我们只罗列出涉及的少许文件,由此可以看出,我们要仿真的设备文件都放在hw目录下,一般来说一个.c 文件会有一个.h 文件,它们的目录类似。

比如 hw/gpio/imx_gpio.c 对应的头文件为 include/hw/gpio/imx_gpio.h。

qemu 驱动开发,arm开发,linux,软件构建,Powered by 金山文档

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

qemu 驱动开发,arm开发,linux,软件构建,Powered by 金山文档

我们以最基础的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,最后可以看到最左边的灯已经被点亮。文章来源地址https://www.toymoban.com/news/detail-697420.html


[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
qemu 驱动开发,arm开发,linux,软件构建,Powered by 金山文档

到了这里,关于QEMU学习(二):LED设备仿真及驱动开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Qemu虚拟arm开发板驱动开发详解(一)——驱动基本架构

            此前在《WSL2下Ubuntu22.04使用Qemu搭建虚拟Vexpress-A9开发板》系列文章中,我们已建立好Linux最小系统的运行环境,并将其成功移植到了由Qemu模拟的arm32开发板上。接下来将介绍如何基于上述环境进行驱动开发。         本节主要带各位读者了解Linux内核驱动的基本架

    2024年02月05日
    浏览(34)
  • QEMU搭建arm虚拟机开发环境

    使用git指令切换到对应的分支上,我这里使用的是stable-4.0的分支 git checkout -b stable-4.0 remotes/origin/stable-4.0 在工程的根目录下执行 ./configure --target-list=aarch64-linux-user,aarch64-softmmu --enable-virtfs --enable-debug 然后执行make,视情况是否要执行make install

    2024年01月25日
    浏览(28)
  • Linux 之搭建 arm 的 qemu 模拟器

    2024年02月03日
    浏览(35)
  • VSCode+GDB+Qemu调试ARM64 linux内核

    俗话说,工欲善其事 必先利其器。linux kernel是一个非常复杂的系统,初学者会很难入门。 如果有一个方便的调试环境,学习效率至少能有5-10倍的提升。 为了学习linux内核,通常有这两个需要 可以摆脱硬件,方便的编译和运行linux 可以使用图形化的工具来调试linux 笔者使用

    2024年02月08日
    浏览(31)
  • ARM Linux 调试 -QEMU启动 Uboot/Kernel/Rootfs

    懒人方式: 直接去方锐/qemu克隆项目,执行script目录的脚本即可 1. build_env.sh安装环境 2. build_rootfs.sh 生成rootfs 3. build_kernel.sh编译kernel 4. qemu_run.sh开始调试 2.1busybox代码的下载编译 Busybox下载地址:https://busybox.net/downloads/ Download 1.36.0 Busybox 默认会安装到 ./_install 目录下 制作ro

    2024年02月02日
    浏览(39)
  • ubuntu22上使用qemu-system-arm调试linux

    qemu是用软件模拟硬件解析指令运行的软件,可以模拟arm、arm64、x86等,对于调试linux 内核机制很方便,不用额外购买开发板。由于linux上有对qemu的加速引擎,支持程度更高,且网络上教程居多,所以这里使用virtualbox+ubuntu22虚拟机,在ubuntu上运行qemu进行模拟。 virtualbox安装:

    2024年01月25日
    浏览(36)
  • 利用WSL2搭建Qemu仿真Vexpress-a9开发环境

    最近想熟悉下Linux开发方面的知识,由于不想安装个虚拟机,便想着利用windows自身带的linux子系统,跑qemu模拟ARM vexpress-a9开发板,过程是逐渐摸索的,参考了网上不少文章,算是做下总结吧! 本身电脑是多年前的win10 64位,性能更不上,不想安装太多软件,WSL2可以在Mircosof

    2024年02月06日
    浏览(29)
  • Linux下LED设备驱动开发(LED灯实现闪烁)

    前面我们介绍了Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统等,大家看这篇文章之前需要提前知道的基础都在这篇文章中: Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍 有部分函数没有涉及到的最后会讲解

    2024年02月17日
    浏览(34)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(22)
  • Linux 利用 qemu-system-aarch64 实现 x86 机器安装 arm64 的操作系统

    qemu-system-aarch64 启动的虚拟机,无法受到 kvm 的管理,也无法在后台运行 终端一旦断开了,虚拟机就被关掉了,想再次进入目前没找到方法,都会回到安装操作系统的步骤 可能是 centos 有某些特殊性吧,暂时没太多时间研究,后期有时间了再看看有没有解决的方案 qemu-5.2.0 和

    2024年02月06日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包