精华内容
下载资源
问答
  • 主要介绍了linux 驱动编写虚拟字符设备编写实例详解的相关资料,需要的朋友可以参考下
  • 需要不断编写新的驱动程序以支持硬件, 通过虚拟字符设备驱动程序的编写,来说明 Linux 系统中字符设备驱动程序的工作 原理。首先介绍了 Linux 系统中设备驱动程序的基本结构, 以及字符设备驱动程序应提供的入口点, ...
  • 文档+代码
  • 学会编写字符设备驱动程序 实验设备PC 机 实验原理Linux 函数系统调用是应用程序和操作系统内核之间的接口而 设备驱动程序是内核和硬件设备之间的接口设备驱动程序屏蔽硬件 细节且设备被映射成特殊的文件进行处理每...
  • linux驱动编写虚拟字符设备编写

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

    也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                   


    【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】


        昨天我们说了一些简单模块编写方法,但是终归没有涉及到设备的编写内容,今天我们就可以了解一下相关方面的内容,并且用一个实例来说明在linux上面设备是如何编写的。虽然我不是专门做linux驱动的,却也经常收到一些朋友们的来信。在信件中,很多做驱动的朋友对自己的工作不是很满意,认为自己的工作就是把代码拷贝来拷贝去,或者说是改来改去,没有什么技术含量。有这种想法的朋友不在少数,我想这主要还是因为他们对自己的工作缺少了解导致。如果有可能,我们可以问问自己这样几个问题:


        (1)我真的搞懂设备的开发驱动流程了吗?我是否可以从0开始,编写一个独立的驱动代码呢?

        (2)我真的了解设备的初始化、关闭、运行的流程吗?

        (3)当前的设备驱动流程是否合理,有没有可以改进的地方?

        (4)对于内核开发中涉及的api调用,我自己是否真正了解、是否明白它们在使用上有什么区别?

        (5)如果我要驱动的设备只是在一个前后台系统中运行,在没有框架帮助的情况下,我是否有信心把它启动和运行起来?


        当然,上面的内容只是我个人的想法,也不一定都正确。但是,知其然,更要知其所以然,熟悉了当前开发流程的优缺点才能真正掌握和了解驱动开发的本质。这听上去有些玄乎,其实也很简单,就是要有一种刨根问底、不断改进的精神,这样才能做好自己的工作。因为我们是在pc linux上学习驱动的,因此暂时没有真实的外接设备可以使用,但是这丝毫不影响我们学习的热情。通过定时器、进程,我们可以仿真出真实设备的各种需求,所以对于系统来说,它是无所谓真设备、假设备的,基本的处理流程对它来说都是一样的。只要大家一步一步做下去,肯定可以了解linux驱动设备的开发工程的。


        下面,为了说明问题,我们可以编写一段简单的char设备驱动代码,文件名为char.c,

    #include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/cdev.h>static struct cdev chr_dev;static dev_t ndev;static int chr_open(struct inode* nd, struct file* filp)int major ; int minor;  major = MAJOR(nd->i_rdev); minor = MINOR(nd->i_rdev);  printk("chr_open, major = %d, minor = %d\n", major, minor); return 0;}static ssize_t chr_read(struct file* filp, char __user* u, size_t sz, loff_t* off){ printk("chr_read process!\n"); return 0;}struct file_operations chr_ops = { .owner = THIS_MODULE, .open = chr_open, .read = chr_read};static int demo_init(void)int ret;  cdev_init(&chr_dev, &chr_ops); ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); if(ret < 0 ) {  return ret; }  printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev)); ret = cdev_add(&chr_dev, ndev, 1); if(ret < 0) {  return ret; }  return 0;}static void demo_exit(void){ printk("demo_exit process!\n"); cdev_del(&chr_dev); unregister_chrdev_region(ndev, 1);}module_init(demo_init);module_exit(demo_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("feixiaoxing@163.com");MODULE_DESCRIPTION("A simple device example!");
        在module_init中的函数是模块加载时处理的函数,而模块卸载的函数则是在module_exit中。每一个设备都要对应一个基本的设备数据,当然为了使得这个设备注册在整个系统当中,我们还需要分配一个设备节点,alloc_chrdev_region就完成这样一个功能。等到cdev_add的时候,整个设备注册的过程就全部完成了,就是这么简单。当然为了编写这个文件,我们还需要编写一个Makefile文件,
    ifneq ($(KERNELRELEASE),)obj-m := char.oelsePWD  := $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall: $(MAKE) -C $(KDIR) M=$(PWD) modulesclean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.*  Module.*endif
        这个Makefile文件和我们之前编写的makefile基本上没有区别,唯一的区别就是文件名称改成了char.o,仅此而已。为了编写模块,我们直接输入make即可。这时候,char.ko文件就可以生成了。然后,模块需要被注册在系统当中,insmod char.ko是少不了的。如果此时,我们还不确信是否模块已经加入到系统当中,完全可以通过输入lsmod | grep char进行查找和验证。为了创建设备节点,我们需要知道设备为我们创建的major、minor数值是多少,所以dmesg | tail 查找一下数值。在我hp的机器上,这两个数值分别是249和0,所以下面可以利用它们直接创建设备节点了,输入mknod /dev/chr_dev c 249 0即可,此时可以输入ls /dev/chr_dev验证一下。那么,按照这种方法,真的可以访问这个虚拟设备了吗,我们可以编写一段简单的代码验证一下,
    #include <stdio.h>#include <fcntl.h>#include <unistd.h>#define CHAR_DEV_NAME "/dev/chr_dev"int main()int ret; int fd; char buf[32]; fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY); if(fd < 0) {  printf("open failed!\n");  return -1; }  read(fd, buf, 32); close(fd);  return 0;}

        代码的内容非常简单,就是利用CHAR_DEV_NAME直接打开设备,读写设备。当然。首先还是需要对这个文件进行编译,文件名为test.c,输入gcc test.c -o test,其次就是运行这个文件,直接输入./test即可。如果没有问题的话,那么说明我们的代码是ok的,但是我们还是没有看到任何内容。没关系,我们还是通过dmesg这个命令查看内核中是否存在相关的打印内容,直接输入dmesg | tail即可。此时如果没有意外的话,我们就可以看到之前在chr_open和chr_read中留下的printk打印,这说明我们的代码完全是ok的。


        上面的代码只是一段小例子,真实的内容要比这复杂一下。不过既然我们都已经入门了,那么后面的内容其实也没有什么好怕的了。最后有两个事情补充一下:(1)如果大家在创建节点后想删除设备节点,直接rm -rf /dev/chr_dev即可;(2)上面这段代码的原型来自于《深入linux设备驱动程序内核机制》这本书,稍作修改,如果大家对内核机制的内容感兴趣,可以参考这本书的内容。



               

    给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

    这里写图片描述
    展开全文
  • 虚拟字符驱动设备

    2015-11-23 14:43:36
    编写一个完整的字符设备驱动程序。由于这类驱动程序适合于大多数简单的硬件设备,我们首先实现一个字符设备驱动程序。Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合
  • 昨天我们说了一些简单模块编写方法,但是终归没有涉及到设备编写内容,我们可以了解一下相关方面的内容,并且用一个实例来说明在Linux上面设备是如何编写的。虽然我不是专门做Linux驱动的,却也经常收到一些朋友们...
  • 字符设备驱动

    2013-01-11 23:24:07
    简单的字符设备驱动程序,包含文件打开关闭,读写,位置控制等等。
  • 2.字符设备驱动开发基础(一个虚拟字符设备驱动开发流程) 字符设备驱动编写,主要工作就是驱动对应的open close read write函数的编写说白了,就是 对file_operations结构体的成员变量的实现; 在linux内核代码中...

    一、字符设备驱动框架

    具体的对应关系见上一篇文章,这里只对需要实现的部分进行说明
    2.字符设备驱动开发基础(一个虚拟的字符设备驱动开发流程)
    字符设备驱动编写,主要工作就是驱动对应的open close read write函数的编写说白了,就是
    file_operations结构体的成员变量的实现;
    在linux内核代码中,file_operations的定义在/include/linux/fs.h这在个.h文件中定义了很多对于linux文件很重要的概念,包括inode、file_operations等等;

    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

    1.1 使用vscode作为linux下编辑器的设置

    在当前目录下创建.vscode文件夹,其下创建如下设置当前目录的头文件搜索路径(注意选择自己的内核源码路径)
    c_cpp_properties.json

    {
        "configurations": [
            {
                "name": "Linux",
                "includePath": [
                    "${workspaceFolder}/**",
                    "/home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", 
                    "/home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", 
                    "/home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
                ],
                "defines": [],
                "compilerPath": "/usr/bin/clang",
                "cStandard": "c11",
                "cppStandard": "c++17",
                "intelliSenseMode": "clang-x64"
            }
        ],
        "version": 4
    }
    

    1.2 驱动模块加载卸载测试(仅含有模块注册和卸载函数的驱动测试,具体框架在1.4中)

    内容:编写一个仅含有驱动的注册和卸载函数的.c文件,将其编译成驱动模块后加载到内核,观察是否加载成功
    目的:验证环境搭建是否存在问题;
    printk()的头文件:#include <linux/kernel.h>
    chrdevbase.c

    #include <linux/module.h>
    #include <linux/kernel.h>
    
    static int __init chrdevbase_init(void)
    {
        printk("chrdevbase_init\r\n");
        return 0;
    }
    static void __exit chrdevbase_exit(void)
    {
        printk("chrdevbase_exit\r\n");
    }
    /**
     * 模块入口与出口
     */
    
    module_init(chrdevbase_init);
    module_exit(chrdevbase_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("QJY");
    

    1.3 Makefile

    KERNELDIR := /home/qjy/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
    
    CURRENT_PATH := $(shell pwd)
    
    obj-m := chrdevbase.o
    
    build: kernel_modules
    
    kernel_modules:
    	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
    clean:
    	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
    

    编译之后的目录为:(编译——拷贝到ubuntu nfs下的rootfs中)
    在这里插入图片描述

    开发板中加载模块:
    在这里插入图片描述

    1.4 字符设备驱动框架搭建与函数实现

    下图所示是本实验使用的字符设备驱动框架,并不完善;
    在这里插入图片描述

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/uaccess.h>  // copy from user
    
    #define CHRDEVBASE_MAJOR    200             // 主设备号
    #define CHRDEVBASE_NAME     "chrdevbase"    // 名字
    
    static char readbuf[100];   // 读缓冲区
    static char writebuf[100];  // 写缓冲区
    static char kernel_data[] = {"kernel data!"};
    
    
    static int chrdevbase_open(struct inode *inode, struct file *filp)
    {
        printk("chrdevbase_open\r\n");
        return 0;
    }
    
    static int chrdevbase_close(struct inode *inode, struct file *filp)
    {
        printk("chrdevbase_release\r\n");
        return 0;
    }
    
    static ssize_t chrdevbase_read(struct file *filp, __user char *buf, 
                                    size_t cnt, loff_t *ppos)
    {
        int retval = 0;
        printk("chrdevbase_reade\r\n");
        // 从内核中读取数据
        memcpy(readbuf, kernel_data, sizeof(kernel_data));
        retval = copy_to_user(buf, readbuf, cnt);
        if(retval == 0) {
            printk("kernel data sended! OK\r\n");
        }else {
            printk("kernel data sended failed!\r\n");
        }
    
        return 0;
    }
    
    static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, 
                                    size_t cnt, loff_t *ppos)
    {
        int retval = 0;
        printk("chrdevbase_write\r\n");
        // 从用户空间向内核空间写数据
        copy_from_user(writebuf, buf, cnt);
        if(retval == 0) {
            printk("kernel reveived OK\r\n");
        }else {
            printk("kernel reveived failed!\r\n");
        }
        return 0;
    }
    
    
    /* 字符设备操作集合*/
    static struct file_operations chrdevbase_fops={
        .owner = THIS_MODULE,
        .open  = chrdevbase_open,
        .release = chrdevbase_close,
        .read = chrdevbase_read,
        .write = chrdevbase_write, 
    };
    
    
    
    static int __init chrdevbase_init(void)
    {   
        int ret = 0;
        printk("chrdevbase_init\r\n");
    
        /* 注册字符设备*/
        ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, 
                        &chrdevbase_fops);
        if(ret < 0) {
            printk("chrdevbase init failed!\r\n");
        }
        
        return 0;
    }
    
    static void __exit chrdevbase_exit(void)
    {
        printk("chrdevbase_exit\r\n");
        /* 注销字符设备*/
        unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    }
    
    /**
     * 模块入口与出口
     */
    
    module_init(chrdevbase_init);
    module_exit(chrdevbase_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("QJY");
    

    二、驱动模块的加载和卸载

    驱动的加载有两种方式:

    1. 直接写进内核文件:编译内核后内核(zImage)与驱动融为一体
    2. 编译成.ko模块文件,使用modprobe指令加载模块;(我们使用这种)

    第二种方法的好处在于,方便调试,可以随时加载和卸载模块,而不是重新编译整个内核文件;

    2.1 开发板的准备工作(alpha-i.mx6ull)

    1. 启动:uboot代码编译后,烧写在sd卡中,开发板从sd卡启动
    2. 内核zImage和设备树文件.dtb:使用tftp从ubuntu中读取;
    3. rootfs根文件系统在ubuntu中,使用nfs挂载;
    4. 设置uboot变量 bootargs 和 bootcmd完成2、3的设置;

    下面是设置之后的uboot环境变量

    => print
    baudrate=115200
    board_name=EVK
    board_rev=14X14
    boot_fdt=try
    bootargs=console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.5.103:/home/qjy/linux/nfs/rootfs ip=192.168.5.111:192.168.5.103:192.168.5.1:255.255.255.0::eth0:off
    bootcmd=tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000
    bootcmd_mfg=run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
    bootdelay=3
    bootscript=echo Running bootscript from mmc ...; source
    console=ttymxc0
    ethact=FEC1
    ethaddr=00:04:9f:04:d2:35
    ethprime=FEC
    fdt_addr=0x83000000
    fdt_file=imx6ull-alientek-emmc.dtb
    fdt_high=0xffffffff
    fileaddr=83000000
    filesize=8d32
    findfdt=if test $fdt_file = undefined; then if test $board_name = EVK && test $board_rev = 9X9; then setenv fdt_file imx6ull-9x9-evk.dtb; fi; if test $board_name = EVK && test $board_rev = 14X14; then setenv fdt_file imx6ull-14x14-evk.dtb; fi; if test $fdt_file = undefined; then echo WARNING: Could not determine dtb to use; fi; fi;
    gatewayip=192.168.5.1
    image=zImage
    initrd_addr=0x83800000
    initrd_high=0xffffffff
    ip_dyn=yes
    ipaddr=192.168.5.111
    loadaddr=0x80800000
    loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
    loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
    loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
    mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc g_mass_storage.stall=0 g_mass_storage.removable=1 g_mass_storage.file=/fat g_mass_storage.ro=1 g_mass_storage.idVendor=0x066F g_mass_storage.idProduct=0x37FF g_mass_storage.iSerialNumber="" clk_ignore_unused
    mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
    mmcautodetect=yes
    mmcboot=echo Booting from mmc ...; run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
    mmcdev=0
    mmcpart=1
    mmcroot=/dev/mmcblk0p2 rootwait rw
    netargs=setenv bootargs console=${console},${baudrate} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp
    netboot=echo Booting from net ...; run netargs; if test ${ip_dyn} = yes; then setenv get_cmd dhcp; else setenv get_cmd tftp; fi; ${get_cmd} ${image}; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if ${get_cmd} ${fdt_addr} ${fdt_file}; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
    netmask=255.255.255.0
    panel=TFT7016
    script=boot.scr
    serverip=192.168.5.103
    

    如果重新配置,需要更新的环境变量为:bootcmd、bootargs、ipaddr、ethaddr、gatewayip、netmask、serverip;

    2.2 驱动模块的加载卸载

    将编译出来的.ko模块放到根文件系统中;
    使用depmod指令分析模块依赖心,为使用modprobe准备;
    使用modprobe / insmod指令完成对模块的加载;
    使用modprobe -r / rmmod指令完成对模块的卸载;
    使用lsmod查看已经加载的模块;
    如果使用modprobe出现问题,见
    Linux驱动加载问题“.ko模块无法加载modprobe: module ‘xxx.ko’ not found”解决方法
    Linux depmod命令用于分析可载入模块的相依性。
    depmod(depend module)可检测模块的相依性,供modprobe在安装模块时使用。

    3. 应用程序的编写

    /**
     * 本文件对应驱动模块CharDevBase的测试APP
     * argv[1] : filename:要进行互动的驱动设备名称(通过/dev/
     * argv[2]  : 1:表示从驱动中读取数据操作;   2:表示向驱动中写数据操作
     */
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define READ_SIZE   50
    #define WRITE_SIZE  50
    
    int main(int argc,char* argv[])
    {
        int fd;
        int read_ret = 0, write_ret = 0;
        char read_buf[100];     // 用户空间读取数据缓冲区
        char write_buf[100];    // 用户空间写入数据缓冲区
        char write_data[] = "Im the written data, I come from UserSapce!\n";
        memcpy(write_buf,write_data, sizeof(write_data));
        if(argc != 3){
            printf("参数不足!\n");
            return -1;
        }
    
        fd = open(argv[1], O_RDWR);
        if(fd < 0){
            printf("Now in userSpace: open error!\n");
            return errno;
        }
    
        if(atoi(argv[2]) == 1){// 读操作
            if((read_ret = read(fd, read_buf,READ_SIZE)) == -1){
                printf("Now in userSpace: reading error~\n");
                return errno;
            }else{
                printf("Now in userSpace: the received data: %s\n", read_buf);
            }
        }
        
        if(atoi(argv[2]) == 2){// 写操作
            if((write_ret = write(fd, write_buf,WRITE_SIZE)) == -1){
                printf("Now in userSpace: reading error~\n");
                return errno;
            }
        }
        usleep(200);
        if(-1 == close(fd)){
            printf("Now in userSpace: close error!\n");
            return errno;
        }
        return 0;
    }
    

    3.1编译应用程序并将.ko与App文件拷贝到rootfs中

    注意,驱动模块的编译方法在之前的makefile中

    arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
    sudo cp ./*.ko ./chrdevbaseApp ../../../nfs/rootfs/lib/modules/4.1.15
    

    4. 测试

    4.1 首先在系统中创建设备结点

    4.2 测试驱动程序

    在这里插入图片描述

    展开全文
  • 字符设备驱动代码

    2017-10-18 15:48:25
    本资源主要是在linux平台下开发的一个虚拟字符驱动程序,利用C语言编写,文档内有详细运行过程及截图,可以学习和参考一下。
  • 字符设备驱动模型 -------- 创建普通字符设备驱动模型 --------- ①定义一个字符设备------&amp;amp;gt;struct cdev ②定义并初始化一个文件操作集------&amp;amp;gt;struct file_operations ③为字符设备...

    字符设备驱动模型

    -------- 创建普通字符设备驱动模型 ---------
    ①定义一个字符设备------>struct cdev
    ②定义并初始化一个文件操作集------>struct file_operations
    ③为字符设备申请设备号
    ③初始化字符设备------>cdev_init(struct cdev*, struct file_operations*);
    ④把字符设备加入Linux内核------>add_cdev(struct cdev*, 设备号, 次设备的数量);
    
    -------- 自动创建设备文件 ---------
    ⑥创建class------>class_create(THIS_MODULE, 类名);
    ⑦创建device
    
    -------- 得到虚拟地址,通过虚拟地址访问寄存器 ------
    ⑧申请物理内存区------>request_mem_region(内存区起始物理地址, 内存区大小, 内存区名);
    ⑨IO内存动态映射------>ioremap(映射内存区起始物理地址, 映射内存区大小);
    
    //led_drv.c(GEC6818开发板-->led灯驱动例子)
    //测试程序:test.c (在最下面)
    //头文件来源于linux内核源码
    #include <linux/kernel.h>/* Needed for KERN_INFO */
    #include <linux/module.h>/* Needed by all modules */
    #include <linux/cdev.h>/*Needed for cdev*/
    #include <linux/fs.h>/*Needed for file_operations*/
    #include <linux/device.h>/*Needed for create device file*/
    #include <linux/errno.h>/*Needed for error node*/
    #include <linux/uaccess.h>/*Needed for copy from user or copy to user*/
    #include <linux/io.h>/*Needed for ioremap*/
    
    //1.定义一个字符设备
    static struct cdev led_cdev;
    
    //2.给字符设备申请设备号(在gec6818_led_init()中申请)
    static unsigned int led_major; //主设备号
    static unsigned int led_minor; //次设备号
    static dev_t led_dev_num;  //设备号
    /*(设备号的前12位是主设备号,后20位是次设备号)*/
    
    //定义类、设备、内存资源指针
    static struct class *led_class;
    static struct device *led_device;
    static struct resource *led_res;
    
    //定义寄存器指针
    static void __iomem *GPIOC_BASE;//让编译器compiler忽略对本指针的检测
    static void __iomem *GPIOCOUT;
    static void __iomem *GPIOCOUTENB;
    static void __iomem *GPIOCALTFN0;
    static void __iomem *GPIOCALTFN1;
    
    static void __iomem *GPIOE_BASE;
    static void __iomem *GPIOEOUT;
    static void __iomem *GPIOEOUTENB;
    static void __iomem *GPIOEALTFN0;
    
    //定义IOCTL命令-->使用方法:ioctl(int fd, unsigned int cmd, unsigned int led_number);
    #define GEC6818_LED_MAGIC 'L'
    #define GEC6818_LED_ON  _IOW(GEC6818_LED_MAGIC, 1, unsigned int)
    #define GEC6818_LED_OFF     _IOW(GEC6818_LED_MAGIC, 2, unsigned int)
    
    static int gec6818_led_open(struct inode *inode, struct file *filp)
    {
        printk("led driver is openning\n");
        return 0;
    }
    
    static int gec6818_led_release(struct inode *inode, struct file *filp)
    {
        *(unsigned int *)GPIOCOUT |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
        *(unsigned int *)GPIOEOUT |= (1<<13);
        printk("led driver is closing\n");
        return 0;
    }
    
    static long gec6818_led_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
        if(_IOC_TYPE(cmd) != GEC6818_LED_MAGIC) return -EINVAL;
        if(_IOC_NR(cmd) != 2 && _IOC_NR(cmd) != 1) return -ENOIOCTLCMD;
    
        switch(cmd)
        {
            case GEC6818_LED_OFF:
                switch(arg)
                {
                    case 7  : *(unsigned int *)GPIOEOUT |= (1<<13);break;
                    case 8  : *(unsigned int *)GPIOCOUT |= (1<<17);break;
                    case 9  : *(unsigned int *)GPIOCOUT |= (1<<8);  break;
                    case 10: *(unsigned int *)GPIOCOUT |= (1<<7);   break;
                    case 11: *(unsigned int *)GPIOCOUT |= (1<<12);break;
                    default : return -EINVAL;
                }
                break;
    
            case GEC6818_LED_ON:
                switch(arg)
                {
                    case 7  : *(unsigned int *)GPIOEOUT &= ~(1<<13);   break;
                    case 8  : *(unsigned int *)GPIOCOUT &= ~(1<<17);   break;
                    case 9  : *(unsigned int *)GPIOCOUT &= ~(1<<8);    break;
                    case 10: *(unsigned int *)GPIOCOUT &= ~(1<<7); break;
                    case 11: *(unsigned int *)GPIOCOUT &= ~(1<<12);    break;
                    default : return -EINVAL;
                }
        }
        return 0;
    }
    
    //3.定义一个文件操作集并初始化
    static const struct file_operations gec6818_led_fops = {
        .owner = THIS_MODULE,
        // .write = gec6818_led_write,
        .unlocked_ioctl = gec6818_led_unlocked_ioctl,
        .open = gec6818_led_open,
        .release = gec6818_led_release,
    };
    
    
    //驱动的安装函数
    static int __init gec6818_led_init(void)
    {
    
        //2.申请设备号(如果主设备号设置为0 则由系统动态分配)
        int ret;
        led_major = 0;
        led_minor = 0;
        if (led_major == 0)
        {
            ret = alloc_chrdev_region(&led_dev_num, led_minor, 1, "led_driver");
        }
        else
        {
            led_dev_num = MKDEV(led_major, led_minor); //把主次设备号转换成设备号
            ret = register_chrdev_region(led_dev_num, 1, "led_driver");
        }
        if (ret < 0) //出错时,跳转到出错处理
        {
            printk("can not get led device number\n");
            goto err_dev_num;
        }
    
    
        //4.初始化字符设备cdev(把文件操作集与字符设备关联起来)
        cdev_init(&led_cdev, &gec6818_led_fops);
    
    
        //5.把初始化好的字符设备cdev加入内核
        ret = cdev_add(&led_cdev, led_dev_num, 1);
        if (ret < 0)
        {
            printk("cdev add error\n");
            goto err_cdev_add;//出错时,跳转到出错处理
        }
    
        //6.驱动程序自动创建设备文件(操作系统必须有mdev或udev)
    
            //6.1 创建class
        led_class = class_create(THIS_MODULE, "led_class");
        if (IS_ERR(led_class)) //判断指针是否有效
        {
            ret = PTR_ERR(led_class); //用指针求得错误码,并赋值给ret
            printk("led driver class create error\n");
            goto err_create_class;
        }
    
            //6.2 创建device
        led_device = device_create(led_class, NULL, led_dev_num, NULL, "led_drv");
        if (IS_ERR(led_device)) //判断指针是否有效
        {
            ret = PTR_ERR(led_device);//用指针求得错误码,并赋值给ret
            printk("led driver device create error\n");
            goto err_create_device;
        }
    
    
        //7.申请物理内存区(用于实现内存资源的互斥访问,不申请亦可)
        led_res = request_mem_region(0xC001C000, 0x1000, "GPIOC_MEM");
        if (led_res == NULL)
        {
            printk("request memory error\n");
            ret = -EBUSY;
            goto err_request_mem;
        }
    
    /************************************************************************************/
    
        //8.IO内存的动态映射
        GPIOC_BASE = ioremap(0xC001C000, 0x1000);
        if (GPIOC_BASE == NULL)
        {
            printk("ioremap GPIOC_BASE error\n");
            ret = -EFAULT;
            goto err_ioremap_C;
        }
    
        GPIOE_BASE = ioremap(0xC001E000, 0x1000);
        if (GPIOE_BASE == NULL)
        {
            printk("ioremap GPIOE_BASE error\n");
            ret = -EFAULT;
            goto err_ioremap_E;
        }
    
    
        //通过映射的基地址计算各寄存器的地址
        GPIOCOUT        = GPIOC_BASE + 0X00;
        GPIOCOUTENB = GPIOC_BASE + 0X04;
        GPIOCALTFN0 = GPIOC_BASE + 0X20;
        GPIOCALTFN1     = GPIOC_BASE + 0X24;
    
        GPIOEOUT        = GPIOE_BASE + 0X00;
        GPIOEOUTENB = GPIOE_BASE + 0X04;
        GPIOEALTFN0 = GPIOE_BASE + 0X20;
    
        //LED的初始化并关闭全部LED
        *(unsigned int *)GPIOCOUTENB |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
        *(unsigned int *)GPIOCALTFN0    &= ~((3<<14) | (3<<16) | (3<<24));
        *(unsigned int *)GPIOCALTFN0    |= ((1<<14) | (1<<16) | (1<<24));
        *(unsigned int *)GPIOCALTFN1    &= ~(3<<2);
        *(unsigned int *)GPIOCALTFN1    |= (1<<2);
        *(unsigned int *)GPIOCOUT       |= ((1<<17) | (1<<8) | (1<<7) | (1<<12));
    
        *(unsigned int *)GPIOEOUTENB |= (1<<13);
        *(unsigned int *)GPIOEALTFN0 &= ~(3<<26);
        *(unsigned int *)GPIOEOUT |= (1<<13);
    
        //驱动安装完成,并输出相关信息
        printk(KERN_INFO "gec6818_led_init\n");
        printk(KERN_INFO "device_name : led_drv\n");
        printk(KERN_INFO "led_major : %d\n", MAJOR(led_dev_num));
        printk(KERN_INFO "led_minor : %d\n", MINOR(led_dev_num));
        // printk("gec6818_led_init\n"); //缺省优先级的printk将使用系统默认优先级
    
        return 0;
    
    
        //出错处理
        err_ioremap_E:
            iounmap(GPIOC_BASE);
        err_ioremap_C:
            release_mem_region(0xC001C000, 0x1000);//释放已经申请的物理内存区
        err_request_mem:
            device_destroy(led_class, led_dev_num);//删除已经创建的设备文件
        err_create_device:
            class_destroy(led_class);//删除已经创建的类led_class
        err_create_class:
            cdev_del(&led_cdev);//从内核删除已经成功添加的字符设备
        err_cdev_add:
            unregister_chrdev_region(led_dev_num, 1); //释放已经申请的设备号
        err_dev_num:
            return ret;//出错时返回错误码
    }
    
    //驱动的卸载函数
    static void __exit gec6818_led_exit(void)
    {
        iounmap(GPIOE_BASE);
        iounmap(GPIOC_BASE);//解除内存地址映射
        release_mem_region(0xC001C000, 0x1000);
        device_destroy(led_class, led_dev_num);
        class_destroy(led_class);
        cdev_del(&led_cdev);
        unregister_chrdev_region(led_dev_num, 1);
    
        printk(KERN_INFO "gec6818_led_exit\n"); 
    }
    
    
    //module的入口和出口函数
    //#insmod led_drv.ko --->module_init()--->驱动程序的安装函数gec6818_led_init()
    module_init(gec6818_led_init);//入口函数
    //#rmmod led_drv.ko --->module_exit()--->驱动程序的卸载函数gec6818_led_exit()
    module_exit(gec6818_led_exit);//出口函数
    
    
    //module的描述,不是必需的。#modinfo led_drv.ko
    MODULE_AUTHOR("chery_cwf@163.com");
    MODULE_DESCRIPTION("led driver for GEC6818");
    MODULE_LICENSE("GPL"); //符合GPL协议
    MODULE_VERSION("V1.0");
    

    分步详解:

    ①定义字符设备管理结构体变量(具体的结构体定义在内核头文件<linux/cdev.h>)
    

    这里写图片描述

    ②定义并初始化一个文件操作集(用于驱动与应用程序的交互)
        1)文件操作集结构体定义在内核头文件<linux/fs.h>
    

    这里写图片描述

        2)选择有需要的接口函数进行初始化
    

    这里写图片描述

        3)定义文件操作集中初始化的接口函数
    

    这里写图片描述
    【函数具体实现见:led_drv.c】

    ③为字符设备申请设备号(包括主设备号、次设备号和设备号)
    

    这里写图片描述

    ※ 申请设备号的两种方法:动态申请(由系统自动分配)和 手动指定设备号。
    

    这里写图片描述

    ④初始化字符设备(把cdev与文件操作集关联起来)
    

    这里写图片描述

    ⑤把初始化好的字符设备加入Linux内核
    

    这里写图片描述

    ⑥创建设备文件 \ 设备节点
        1)手动创建设备文件节点
    ~:mknod 设备类型 设备文件名 主设备号 次设备号
    例:[ root@GEC6818 / ]#mknod /dev/led_drv c 100 0
    ※ 可以使用#cat /proc/devices  查看创建是否成功
    
    2)自动创建设备文件节点(系统中必须有medv或udev工具)
    mdev是udev的简化版,这个工具可以根据驱动的class和device来创建设备文件节点。
    

    这里写图片描述

    ⑦申请物理内存区(类似于互斥锁功能)
    

    这里写图片描述

    ⑧IO内存动态映射(把物理地址映射到操作系统可操作的虚拟地址)
    

    这里写图片描述

    ⑨按照实际要求配置寄存器的值
            【具体参考:led_drv.c】
    

    ⑩出错处理(用goto语句实现)
    每一部都设置出错处理,出错时释放已经申请的资源。
    这里写图片描述

    【做到这里,驱动安装步骤结束】

    XI 定义驱动的卸载函数(按顺序释放驱动安装时申请的资源)
    这里写图片描述

    XII 驱动程序的说明与描述(不是必须的)
    这里写图片描述

    ※ 如果MODULE_LICENSE 不写,内核会报警告【信息:污染内核】。

    //test.c --> led_drv.ko 适用的 led灯测试程序
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
     #include <unistd.h>
    #include <sys/ioctl.h>
    
    #define GEC6818_LED_MAGIC 'L'
    #define GEC6818_LED_ON  _IOW(GEC6818_LED_MAGIC, 1, unsigned int)
    #define GEC6818_LED_OFF     _IOW(GEC6818_LED_MAGIC, 2, unsigned int)
    
    
    int main(void)
    {
        int fd;
        long ret;
        unsigned int num = 7;
        fd = open("/dev/led_drv", O_WRONLY);
        if(fd < 0)
        {
            perror("open led driver");
            return -1;      
        }
        while(1)
        {
            ret = ioctl(fd, GEC6818_LED_ON, num);
            sleep(1);
    
            printf("num = %d\n", num);
            num += 1;
            if (num > 11)
            {
                num = 7;
                break;
            }
        }
    
        while(1)
        {
            ret = ioctl(fd, GEC6818_LED_OFF, num);
            sleep(1);
    
            printf("num down = %d\n", num);
            num += 1;
            if (num > 11)
            {
                break;
            }
        }   
    
        close(fd);
    
        return 0;
    }
    
    展开全文
  • 嵌入式字符设备驱动编写步骤

    千次阅读 2019-03-09 20:42:58
    目录 编写步骤: 设备号的申请注册注销: 通用函数(静态动态申请都可以): 静态申请: 动态申请: ...设备号的注销: ...设备节点的创建与销毁: ...手动创建设备节点: ...*1、编写驱动模块的基本框架h...

    目录

    编写步骤: 

    设备号的申请注册注销:

    通用函数(静态动态申请都可以):

    静态申请:

    动态申请:

    设备号的注销:

    设备节点的创建与销毁:

    手动创建设备节点:

    自动创建设备节点:

    设备节点的销毁:

    IO资源的映射与配置:

    读写函数编写,操作IO口,比如点灯:

    应用实验程序的编写

    驱动示例代码:


    编写步骤: 

    /*编写步骤:
    *
    *1、编写驱动模块的基本框架  https://blog.csdn.net/shenlong1356/article/details/88367429
    *2、注册注销设备号
    *3、创建设备节点
    *4、编写file_operations 结构体成员 open write .....
    *5、点灯LED1 GPF4  查出寄存器地址 进行物理地址到虚拟地址的映射 ioremap iounmap 
    */

    设备号的申请注册注销:

    通用函数(静态动态申请都可以)

    register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)

    major  :确定一个主设备号,如果major=0,则会自动分配设备号

    name  : 设备名

    fops    : 构造一个file_operations结构体, 然后放在chrdevs数组中

        #define ledmajor 250
    	
        
        const struct file_operations led_fops = {
    	.open = led_open ,
    	.read = led_read ,
    	.write = led_write ,
    	.release = led_close ,
    
    };
    
    
        int ret;
    	
    	ret = register_chrdev(ledmajor , "led_test" , &led_fops) ;  //注册设备号

    静态申请:

         int register_chrdev_region(dev_t from, unsigned count, const char *name);

    /* 参数:
        dev_t from - 要申请的设备号(起始)
        unsigned count - 要申请的设备号数量
        const char *name - 设备名
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/

     

    动态申请:

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
    /* 参数:
        dev_t *dev - 用于保存分配到的第一个设备号(起始)
        unsigned baseminor - 起始次设备号
        unsigned count - 要分配设备号的数量
        const char *name - 设备名
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/

     

    设备号的注销:

    unregister_chrdev(unsigned int major, const char * name);

    void unregister_chrdev_region(dev_t from, unsigned count);
    /* 参数:
        dev_t from - 要释放的第一个设备号(起始)
        unsigned count - 要释放的次设备号数量 */

     

    设备节点的创建与销毁:

    手动创建设备节点

    1. 使用mknod手工创建:mknod filename type major minor

    自动创建设备节点

    利用udev(mdev)来实现设备文件的自动创建

    struct class *class_create(struct module *owner, const char *name);
    /*
      功能:在/sys/class目录下创建一个目录,目录名是name指定的
      参数:
        struct module *owner - THIS_MODULE
        const char *name - 设备名
      返回值:
        成功:class指针
        失败: - bool IS_ERR(const void *ptr)  判断是否出错
             long PTR_ERR(const void *ptr)  转换错误码
    */

     

    struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
    /* 
      功能:
        在class指针指向的目录下再创建一个目录,目录名由const char *fmt, ...指出、并导出设备信息(dev_t)
      参数:
        struct class *cls - class指针
        struct device *parent - 父对象,NULL
        dev_t devt - 设备号
        void *drvdata - 驱动私有数据
        const char *fmt, ... - fmt是目录名字符串格式,...就是不定参数
      返回值:
        成功 - device指针
        失败 - bool IS_ERR(const void *ptr)  判断是否出错
           long PTR_ERR(const void *ptr)   转换错误码
    */

     

     

        static  struct class *ledclass ;
        static  struct device *led_dev;
    
    
    	ledclass = class_create( THIS_MODULE , "led_class");  
    	device_create(ledclass , NULL , MKDEV(250 , 0), "led_device") ; //创建设备节点
    
    

    设备节点的销毁:

    void class_destroy(struct class *cls);
    /*
      功能:删除class指针指向的目录
      参数:
        struct class *cls - class指针
    */

     

    void device_destroy(struct class *cls, dev_t devt);
    /*
      功能:删除device_create创建的目录
      参数:
        struct class *cls - class指针
        dev_t devt - 设备号
    */

     

    	device_destroy(ledclass,  MKDEV(250 , 0));    //注意二者的顺序
    
    	class_destroy(ledclass);

    IO资源的映射与配置:

        #define GPFCON 0x56000050   //控制寄存器物理地址
        #define GPFDAT 0x56000054   //数据寄存器
        #define GPF_SIZE	8
    
        volatile unsigned long *gpfconvir;    //虚拟地址
        volatile unsigned long *gpfdatvir;
    
    
    
    
        gpfconvir = (volatile unsigned long *)ioremap(GPFCON, 16); //物理地址到虚拟地址映射
    	gpfdatvir = gpfconvir + 1;
    
    	*gpfconvir &= ~(3<<8);   //对8 9 bit 清零 也就是 GPF4
    	*gpfconvir |= (1<<8) ;	 //配置为输出
    

     

    读写函数编写,操作IO口,比如点灯:

    unsigned long copy_to_user(void *to, const void *from, unsigned long n)
    to:目标地址(用户空间)
    from:源地址(内核空间)
    n:将要拷贝数据的字节数
    返回:成功返回0,失败返回没有拷贝成功的数据字节数

    一般在read函数中调用,传递数据给用户(用户读数据)

     

    unsigned long copy_from_user(void *to, const void *from, unsigned long n);
    to:目标地址(内核空间)
    from:源地址(用户空间)
    n:将要拷贝数据的字节数
    返回:成功返回0,失败返回没有拷贝成功的数据字节数

    一般在write函数中调用接受来自用户的数据(用户写数据)

    ssize_t led_read (struct file *filp, char __user *buf , size_t count, loff_t *fops)
    {
    	int value = 110 ;
    	int ret;
    
       ret = copy_to_user(buf, &value, count);
    	if(ret > 0)
    	{
    		printk("read filed\n");
    		return -EFAULT;
    	}
    
    	return 0 ;
    
    }
    
    /*操作灯函数*/
    ssize_t led_write (struct file *filp, const char __user * buf, size_t count, loff_t *fops)
    {
    
    	int value ;
    	int ret ;
    
    	ret = copy_from_user(&value, buf, count);
    	if(ret > 0)
    	{
    		printk("write filed!\n");
    		return -EFAULT;
    
    	}
    
    	if(value > 0)
    	{
    		*gpfdatvir |= (1<<4);		//灭灯
    	}
    	else
    	{
    		*gpfdatvir &= ~(1<<4);   //亮灯
    	}
    	
    	
    	return 0;
    
    }
    

    应用实验程序的编写

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
    
    	int fd;
    	int value = 0;
    
    	fd = open("/dev/led_device", O_RDWR); //打开对应的设备节点led_device
    
    	if(fd < 0)
    	{
    		perror("open\n");	
    
    		exit(1);
    	}
    
    
    	//控制灯的亮灭  实现灯的闪烁
    	
    	while(1)
    	{
    		value = 0;
    		write(fd, &value, 4);
    		sleep(1);
    
    		value = 1;
    		write(fd, &value, 4);
    		sleep(1);
    		
    	}
    
    	close(fd);
    
    	return 0 ;
    
    }
    
    
    

    驱动示例代码:


    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/cdev.h>    //字符设备头文件
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <asm/uaccess.h>
    #include <asm/io.h>
    
    
    
    
    /*编写步骤:
    *
    *1、编写驱动模块的基本框架
    *2、注册注销设备号
    *3、创建设备节点
    *4、编写file_operations 结构体成员 open write .....
    *5、点灯LED1 GPF4  查出寄存器地址 进行物理地址到虚拟地址的映射 ioremap iounmap 
    */
    
    #define ledmajor 250
    static  struct class *ledclass ;
    static  struct device *led_dev;
    
    #define GPFCON 0x56000050   //控制寄存器物理地址
    #define GPFDAT 0x56000054   //数据寄存器
    #define GPF_SIZE	8
    
    volatile unsigned long *gpfconvir;    //虚拟地址
    volatile unsigned long *gpfdatvir;
    
    
    ssize_t led_read (struct file *filp, char __user *buf , size_t count, loff_t *fops)
    {
    	int value = 110 ;
    	int ret;
    
       ret = copy_to_user(buf, &value, count);
    	if(ret > 0)
    	{
    		printk("read filed\n");
    		return -EFAULT;
    	}
    
    	return 0 ;
    
    }
    
    /*操作灯函数*/
    ssize_t led_write (struct file *filp, const char __user * buf, size_t count, loff_t *fops)
    {
    
    	int value ;
    	int ret ;
    
    	ret = copy_from_user(&value, buf, count);
    	if(ret > 0)
    	{
    		printk("write filed!\n");
    		return -EFAULT;
    
    	}
    
    	if(value > 0)
    	{
    		*gpfdatvir |= (1<<4);		//灭灯
    	}
    	else
    	{
    		*gpfdatvir &= ~(1<<4);   //亮灯
    	}
    	
    	
    	return 0;
    
    }
    
    int led_open (struct inode *inod, struct file *filp)
    {
    //	printk("open ok\n");
    
    	return 0;
    
    }
    
    int led_close(struct inode *inod, struct file *filp)
    {
    //	printk("close ok\n");
    
    	return 0;
    }
    
    
    const struct file_operations led_fops = {
    	.open = led_open ,
    	.read = led_read ,
    	.write = led_write ,
    	.release = led_close ,
    
    };
    
    static int __init led_drv_init(void)
    {
    
    
    	int ret;
    	
    	ret = register_chrdev(ledmajor , "led_test" , &led_fops) ;  //注册设备号
    
    	if (ret == 0)
    		{
    			printk("register ok!\n" );
    
    	}
    	else
    		{
    
    		printk("register filed!\n");
    		return -1;
    	}
    
    	gpfconvir = (volatile unsigned long *)ioremap(GPFCON, 16); //物理地址到虚拟地址映射
    	gpfdatvir = gpfconvir + 1;
    
    	*gpfconvir &= ~(3<<8);   //对8 9 bit 清零 也就是 GPF4
    	*gpfconvir |= (1<<8) ;	 //配置为输出
    
    	ledclass = class_create( THIS_MODULE , "led_class");  
    	device_create(ledclass , NULL , MKDEV(250 , 0), "led_device") ; //创建设备节点
    	
    	return 0;
    	
    }
    
    static void __exit led_drv_exit(void)
    {
    
    	iounmap(gpfconvir);     //释放io资源
    	
    	device_destroy(ledclass,  MKDEV(250 , 0));    //注意三者的顺序
    
    	class_destroy(ledclass);
    
    	unregister_chrdev(ledmajor , "led_test") ;    //注销设备号
    
    }
    
    
    module_init(led_drv_init);
    module_exit(led_drv_exit);
    MODULE_LICENSE("GPL");
    

     

    错误总结:

    寄存器数据类型要正确,寄存器地址映射要正确;

     

     

     

     

     

    展开全文
  • linux内核编译以及字符设备驱动程序的编写 ...虚拟内存设备globalmem驱动实现,编写程序,然后将生成的驱动模块插入到驱动之中,接着编写测试程序,对设备globalmem进行测试。(Copyright © http://blog.csdn.net/s_...
  • 3 字符设备驱动编写(含代码和makefile) 4 GTK编写系统监视器,可以监测系统很多方面(含代码和makefile) 5 虚拟文件系统(实现的比较简单)(含代码和makefile) 说明:本内容实现自己添加设备驱动,含有代码...
  • 字符设备作为linux内核三大驱动设备之一,主要完成字节的读写操作,常见的应用有鼠标、键盘等,结构体形式如下所示:struct cdev{struct kobject kobj;struct module *owner;//所说模块struct file_operations *ops;...
  • 编写驱动的第一步仍是看原理图:  可以看到,该蜂鸣器由 GPD0_0 来控制 ,查手册可知该I/O口由Time0 来控制,找到相应的寄存器: a -- I/O口寄存器及地址  GPD0CON 0x114000a0 b -- Time0 寄存器及地址  基地址...
  • ubuntu 添加字符设备驱动程序

    千次阅读 2018-02-28 11:19:13
    ubuntu设备驱动是由装载模块的方式进行的 2.6.x核心模块在http://www.ibm.com/developerworks/cn/linux/l-module26/上可以下载按照定义创建驱动文件创建c文件 Drive.c#include "linux/kernel.h" #include ...
  • linux字符设备驱动

    2012-04-27 21:53:01
    1)编写一个简单的字符设备驱动程序,该字符设备包括打开、读、写、I/O控制与释放五个基本操作。 2)编写一个测试程序,测试字符设备驱动程序的正确性。 3)要求在实验报告中列出Linux内核的版本与内核加载的过程
  • VMware下创建字符设备,并编写驱动进行读写操作。亲自测试可用。
  • 字符设备驱动模型

    千次阅读 2016-05-08 21:48:23
    字符驱动编程模型: 1. 设备描述结构cdev  1.1 结构定义  1.2 设备号  1.3 设备操作集 在Linux系统中,设备的类型非常繁多,如:字符设备,块设备,网络接口设备,USB设备,PCI设备,平台设备,混杂设备……,而...
  • 02、字符设备驱动

    千次阅读 2021-09-26 14:09:08
    02、字符设备驱动 一、cdev结构体   在Linux内核中,使用cdev结构体描述一个字符设备,cdev结构体的定义在<linux/cdev.h>头文件中声明如下: struct cdev { struct kobject kobj; struct module *owner; ...
  • 二.字符设备驱动基础

    千次阅读 2019-09-29 15:37:40
    开启驱动开发之路 二.最简单的模块源码分析1 2.1、常用的模块操作命令 三.最简单的模块源码分析2 3.1、模块卸载 3.2、模块中常用宏 四.最简单的模块源码分析3 4.1、printk函数详解 4.2、关于驱动模块中的...
  • (一)驱动程序介绍 (a)Linux驱动程序学习 ... 字符设备是一种按字节来访问的设备字符驱动则负责驱动字符设备,这样的驱动通常实现 open, close,read和 write 系统调用。 ②块设备:  在大部分的 Unix...
  • //支持字符设备的头文件 #include <linux/semaphore.h> //支持信号量的头文件 #define FREG_DEVICE_NODE_NAME "freg" #define FREG_DEVICE_FILE_NAME "freg" #define FREG_DEVICE_PROC_NAME "freg" #define ...
  • 现在,我们来编写自己第一个字符设备驱动 —— 点亮LED。 硬件平台:Exynos4412(FS4412) 编写驱动分下面几步: a -- 查看原理图、数据手册,了解设备的操作方法; b -- 在内核中找到相近的驱动程序,以它为模板...
  • 字符设备是Linux系统三大类设备之一(字符设备、块设备...该字符设备通过在内核中创建一段内存空间,并将这段空间作为字符设备读写访问的目标地址,来实现Linux内核字符设备驱动与应用程序的通信。 1. 操作系统 Li...
  • 在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动。本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存。 下面...
  • 字符设备驱动代码完整分析

    千次阅读 2016-08-03 11:15:39
    1.编译、安装驱动程序 linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此编译、安装驱动程序实质是编译、安装内核模块memdev.c #include #include #include #include #include int

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,129
精华内容 16,451
关键字:

编写虚拟字符设备驱动