zoukankan      html  css  js  c++  java
  • linux驱动开发(一)

    1:驱动开发环境

    要进行linux驱动开发我们首先要有linux内核的源码树,并且这个linux内核的源码树要和开发板中的内核源码树要一直;

    比如说我们开发板中用的是linux kernel内核版本为2.6.35.7,在我们ubuntu虚拟机上必须要有同样版本的源码树,

    我们再编译好驱动的的时候,使用modinfo XXX命令会打印出一个版本号,这个版本号是与使用的源码树版本有关,如果开发板中源码树中版本与

    modinfo的版本信息不一致使无法安装驱动的;

    我们开发板必须设置好nfs挂载;这些在根文件系统一章有详细的介绍;

    2:开发驱动常用的几个命令

    lsmod :list moduel 把我们机器上所有的驱动打印出来,

    insmod:安装驱动

    rmmod:删除驱动

    modinfo:打印驱动信息

    3:写linux驱动文件和裸机程序有很大的不同,虽然都是操作硬件设备,但是由于写裸机程序的时候是我们直接写代码操作硬件设备,这只有一个层次;

    而我们写驱动程序首先要让linux内核通过一定的接口对接,并且要在linux内核注册,应用程序还要通过内核跟应用程序的接口相关api来对接;

     4:驱动的编译模式是固定的,以后编译驱动的就是就按照这个模式来套即可,下面我们来分下一下驱动的编译规则:

    #ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
    #KERN_VER = $(shell uname -r)
    #KERN_DIR = /lib/modules/$(KERN_VER)/build
    
    
    # 开发板的linux内核的源码树目录
    KERN_DIR = /root/driver/kernel
    
    obj-m    += module_test.o
    
    all:
    make -C $(KERN_DIR) M=`pwd` modules
    
    cp:
    cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
    
    .PHONY: clean    
    clean:
    make -C $(KERN_DIR) M=`pwd` modules clean

    make -C $(KERN_DIR) M=`PWD` modules

    这句话代码的作用就是到 KERN_DIR这个文件夹中 make modules

    把当前目录赋值给M,M作为参数传到主目录的Makefile中,实际上是主目录的makefile中有目标modules,下面有一定的规则来编译驱动;

    #KERN_VER = $(shell uname -r)

    #KERN_DIR = /lib/modules/$(KERN_VER)/build

    我们在ubuntu中编译内核的时候用这两句代码,因为在ubuntu中为我们保留了一份linux内核的源码树,我们编译的时候直接调用那个源码树的主Makefile以及一些头文件、内核函数等;

    了解规则以后,我们设置好KERN_DIR、obj-m这两个变量以后直接make就可以了;

    经过编译会得到下面一些文件:

    下面我们可以使用

    lsmod命令来看一下我们ubuntu机器现有的一些驱动

    可以看到有很多的驱动,

    下面我们使用

    insmod XXX命令来安装驱动,在使用lsmod命令看一下实验现象

    可以看到我们刚才安装的驱动放在了第一个位置;

    使用modinfo来打印一下驱动信息

     modinfo xxx.ko

    这里注意vermagic 这个的1.8.0-41是你用的linux内核源码树的版本号,只有这个编译的版本号与运行的linux内核版本一致的时候,驱动程序才会被安装

     注意license:GPL linux内核开元项目的许可证一般都是GPL这里尽量设置为GPL,否则有些情况下会出现错误;

    下面使用

    rmmod xxx删除驱动;

    -------------------------------------------------------------------------------------

    5:下面我们分析一下驱动。C文件

    #include <linux/module.h>        // module_init  module_exit
    #include <linux/init.h>            // __init   __exit
    
    // 模块安装函数
    static int __init chrdev_init(void)
    {    
        printk(KERN_INFO "chrdev_init helloworld init
    ");
        //printk("<7>" "chrdev_init helloworld init
    ");
        //printk("<7> chrdev_init helloworld init
    ");
    
        return 0;
    }
    
    // 模块下载函数
    static void __exit chrdev_exit(void)
    {
        printk(KERN_INFO "chrdev_exit helloworld exit
    ");
    }
    
    
    module_init(chrdev_init);
    module_exit(chrdev_exit);
    
    // MODULE_xxx这种宏作用是用来添加模块描述信息
    MODULE_LICENSE("GPL");                // 描述模块的许可证
    MODULE_AUTHOR("aston");                // 描述模块的作者
    MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
    MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

    module_init宏的作用就是把insmod与module_init(XXX)中的XXX绑定起来,insmod命令实际上是执行的chrdev_init这个函数;

    我们写的这个chrdev_init函数只有一条打印信息;

    因为内核的printk函数是设置打印级别的,我们可以是用dmesg命令来查看打印信息

    如上面图,可见我们insmod的时候打印了 chrdev_init helloworld init这条信息,这里正是chrdev_init 这个函数中打印的信息;

    因为这里用到了很多内核函数、宏等,所以要包含这些函数、宏的头文件

    我们可以建立内核的man手册来查询这些函数;

    http://blog.sina.com.cn/s/blog_6642cd020101gtin.html

     下载的版本为linux-2.6.35.7建立内核man手册

    现在就可以man printk来查找linux内核函数了,但是这里注意到,好像还是没有相关的头文件包含

    可以参考,常用的内核函数的头文件包含,

    http://blog.csdn.net/guowenyan001/article/details/43342301

    最后办法就是把内核文件在SI中建立连接来查找;

    下面来看一下__init

    #define __init __section(.init.text) __cold notrace

    用来定义段属性的,把Chrdev_init函数定义为.init.text段,这个段在启动内核以后会内核会自动释放掉,以节省内存空间;

    采用2.6.35.7源码树编译在开发板上运行;

    KERN_DIR = /usr/bhc/kernel/linux-2.6.35.7/

    把KERN_DIR目录变量更改为我们安装的内核源码树目录即可

    make

    注意:一定要把源码树目录中主Makefile中ARCH、cross_compile变量的值更改了;

     开发板使用在zImage 也要是用这个内核来编译的zImage

    在开发板中同样使用

    lsmod

    insmod

    rmmod

    ----------------------------------------------------------------------------------------------------------

    6:应用层是如何调用驱动的

    在应用层进行应用编程的时候我们对底层设备的操作包括:open close write read 等操作;

    如fd = open("/dev/mouse", O_RDWR);

    这些操作都是通过内核给定的api接口来实现的,这些接口是通过file_operations这个结构体来实现的

    struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
    };

    这个结构体中包含了对硬件的所有操作;

    结构体中大多都是函数指针,这些函数指针指向真正的设备的操作函数;

    写好硬件真正的读写函数以后,在建立一个struct file_operations类型的结构体,把相应的操作对应真正的读写函数初始化以后,

    我们还需要把这个结构体向内核初始化,告诉内核,让内核知道我们建立了一个驱动,

    我们用register_chrdev这个函数来向内核注册:

    static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
    {
    return __register_chrdev(major, 0, 256, name, fops);

    }

    static inline void unregister_chrdev(unsigned int major, const char *name)
    {
    __unregister_chrdev(major, 0, 256, name);
    }

    这个函数来注销驱动

    注册的时候需要一个注册号major、name、以及建立好的struct file_operations结构体;

    内核中有一个结构体数组,这个数组中一共255个元素,我们写好的驱动注册的时候需要一个major主设备号,这个主设备号对应数组的下标,注册的时候设备命令以及file_operations的指针就放入了这个结构体数组对应的major下标的那个元素中;

    注册以后,内核知道有这个设备了,应用程序才可以通过内核来调用api来操作这个设备;

    应用是如何调用驱动呢?

    应用调用驱动是通过驱动设备文件来调用驱动的,我们首先要用mknod /dev/xxx c 主设备号 次设备号 命令来创建驱动设备文件,

    这样的话应用程序就可以通/dev/xxx这个设备驱动文件,获取对应的主设备号,内核在通过这个主设备号找到设备名称和file_operations这个结构体;

    可以看一下上面这个图:

    应用程序:通过/dev/xxx设备文件来找到这个主设备号,在通过系统api(open、close、read、write),执行对设备的操作;

    而在内核中linux内核通过主设备号找到file_operation这个结构图,在通过这个结构体找到真正的操作设备的函数;

    所以我们写驱动程序在应用层做的事情就是:

    使用mknod命令来建立设备驱动文件; 

    找到设备文件,调用api操作设备即可;

    在linux内核中要做的事情有:

    写好真正的设备操作函数,建立file_operation结构体,用register_chrdev函数来向内核注册;

    下面我们对每一个步骤做详细的操作分析:

    真正操作硬件设备的函数(以led为例)

    #include <linux/module.h> // module_init module_exit
    #include <linux/init.h>    // __init __exit
    #include <linux/fs.h>
    
    #define MYMAJOR 200
    #define MYNAME    "LED_DEVICE"
    
     //int (*open) (struct inode *, struct file *);
    
    //open函数的格式是上面的格式:
    
    static int led_dev_open(struct inode *inode, struct file *file)
    
    {
    
      printk(KERN_INFO "led_dev_open open
    ");
    
    }
    
    //release函数的原型是:int (*release) (struct inode *, struct file *);
    
    static int led_dev_close(struct inode *inode, struct file *file)
    
    {
    
      printk(KERN_INFO "led_dev_close close
    ");
    
    }
    
    static const struct file_operations led_dev_fops{
    
      .opne = led_dev_open,
    
      .release = led_dev_close,
    
    }
    
    static int __init leddev_init(void)
    
    {
    
      int ret = -1;
    
      printk(KERN_INFO "leddev_init");
    
      
    
      ret = register_chrdev(MYMAJOR, MYNAME, &led_dev_fops);
    
      if(ret) {
    
        printk(KERN_ERR "led devices rigister failed");
        retunt -EINVAL;
      }
    
      printk(KERN_INFO "led regist sucess");
    
      return 0;
    
    }
    
    static int __exit leddev_exit(void)
    
    {
    
      printfk(KERN_INFO "led device exit");
    
      unregister_chrdev(MYMAJOR, NAME);
    
    }


    module_init(leddev_init);
    module_exit(leddev_exit);

    
    

    // MODULE_xxx这种宏作用是用来添加模块描述信息
    MODULE_LICENSE("GPL"); // 描述模块的许可证
    MODULE_AUTHOR("bh

    c"); // 描述模块的作者
    MODULE_DESCRIPTION("led test"); // 描述模块的介绍信息
    MODULE_ALIAS("alias xxx"); // 描述模块的别名信息

    
    
    
     

    这样我们就可以去编译以后在开发板中使用insmod rmmod等命令来安装删除驱动了;

    在这里补充一问题,安装好驱动以后,主设备号可以在/proc/devices文件中查看,但是由于不同的设备主设备号占用的不一样,有时候需要系统来自动分配

    主设备号,这个如何实现呢:

    我们可以在register_chrdev函数的major变量传参0进去,因为这个函数的返回值为主设备号,所以我们定义一个全局变量来接受这个值即可

    static int mymajor;

    //注册的时候

    mymajor = register_chrdev(0, MYNAME, &ded_dev_fops);

    //释放的时候

    unregister_chrdev(mymajor, MYNAME);

    这样即可;

    -----------------------------------------------------------------------------------------------------------------

     下面介绍api的write read函数:

    read函数的函数原型是下面:

    ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)

    write函数的函数原型:

    static ssize_t ab3550_bank_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)

    write函数的函数原型是这样的

    这里注意下:应用层面的内存buf与内核中的buf数据不能直接交换的,不能用memcpy函数复制;

    要使用

    copy_form_user

    copy_to_user两个函数;

    static inline long copy_to_user(void __user *to, const void *from, unsigned long n);

    static inline long copy_from_user(void *to, const void __user * from, unsigned long n)

    两个函数的原型为上:

    app程序

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    #define FILE "/dev/led_device"
    
    char ubuf[100];
    
    int main(void)
    {
        int fd = -1;
        int ret = -1;
        
        //打开led设备
        fd = open(FILE, O_RDWR);
        if(fd < 0) {
            printf("/dev/led_device open failed
    ");
            return -1;
        }
        printf("/dev/led_device open success
    ");
        
        //写led设备
        ret = write(fd, "led_blink_bbb", 13);
        if(ret < 0) {
            printf("write error
    ");
        }
        printf("write success...
    ");
        
        //读led设备
        ret =  read(fd, ubuf, 100);
        printf("read is %s
    ", ubuf);    
        
        //关闭led设备
        close(fd);
        
    }

    驱动程序相关代码

    #include <linux/module.h>        // module_init  module_exit
    #include <linux/init.h>            // __init   __exit
    #include <linux/fs.h>
    #include <asm/uaccess.h>
    
    #define MYMAJOR        200
    #define MYNAME        "LED_DEVICE"
    
    static char kbuf[100];
    static int mymojor;
    
    static int led_dev_open(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "led_dev open
    ");
        
        return 0;
    }
    
    static int led_dev_release(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "led_dev close
    ");
        
        return 0;
    }
    
    ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
        int ret = -1;
            
        ret = copy_to_user(buf, kbuf, sizeof(kbuf));
        if(ret) {
            printk(KERN_ERR "kernel led read error
    ");
        }
        printk(KERN_INFO "led device read success
    ");
    }
    
    static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
    {
        int ret = -1;
        
        ret = copy_from_user(kbuf, user_buf, count);
        if(ret) {
            printk(KERN_ERR "kernel led write error
    ");
            return -EINVAL;
        }
        printk(KERN_INFO "led device write success
    ");
        
        return 0;
    }
    
    static const struct file_operations led_dev_fops = {
        .open = led_dev_open,
        .write = led_dev_write,
        .read = led_dev_read,    
        .release = led_dev_release,
        .owner = THIS_MODULE,
    };
    
    
    
    
    
    // 模块安装函数
    static int __init leddev_init(void)
    {    
        
        printk(KERN_INFO "led_device init
    ");
        //printk("<7>" "chrdev_init helloworld init
    ");
        //printk("<7> chrdev_init helloworld init
    ");
        
        //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
        mymojor = register_chrdev(0, MYNAME, &led_dev_fops);
        
        if(!mymojor)
        {
            printk(KERN_ERR "led_device failed
    ");
            
            return -EINVAL;
        }
        
        printk(KERN_INFO "leddev_dev regist success
    ");
        
        return 0;
    }
    
    // 模块下载函数
    static void __exit leddev_exit(void)
    {
        printk(KERN_INFO "leddev_dev  exit
    ");
        
        //注销led设备驱动
        unregister_chrdev(mymojor, MYNAME);
        
        printk(KERN_INFO "leddev_dev  unregist success
    ");
    
        
    }
    
    
    module_init(leddev_init);
    module_exit(leddev_exit);
    
    // MODULE_xxx这种宏作用是用来添加模块描述信息
    MODULE_LICENSE("GPL");                // 描述模块的许可证
    MODULE_AUTHOR("bhc");                // 描述模块的作者
    MODULE_DESCRIPTION("led test");    // 描述模块的介绍信息
    MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

    编译以后在开发板上测试。。。。。

    -----------------------------------------------------------------------------------------------------

    下面我们的低层驱动开始真正的操作硬件了:

    在操作硬件的时候,我们会用到硬件的香瓜寄存器,因为我们之前在逻辑程序中使用的物理地址来直接写的,而我们在开发板上移植好内核以后

    我们的内核程序就是运行在虚拟地址上了,所以我们要看一下我们的linux内核中虚拟地址跟物理地址是如何映射的,三星在移植的内核的时候,把硬件相关

    的寄存器,建立了三个映射表文件,让我们来查找这些物理地址对应的虚拟地址:

    首先来看一下静态虚拟地址映射:

    分别为

    arch/arm/plat-samsung/plat/map-base.h

    看一下这个文件中的内容:

    #define S3C_VA_IRQ    S3C_ADDR(0x00000000)    /* irq controller(s) */
    #define S3C_VA_SYS    S3C_ADDR(0x00100000)    /* system control */
    #define S3C_VA_MEM    S3C_ADDR(0x00200000)    /* memory control */
    #define S3C_VA_TIMER    S3C_ADDR(0x00300000)    /* timer block */
    #define S3C_VA_WATCHDOG    S3C_ADDR(0x00400000)    /* watchdog */
    #define S3C_VA_OTG    S3C_ADDR(0x00E00000)    /* OTG */
    #define S3C_VA_OTGSFR    S3C_ADDR(0x00F00000)    /* OTG PHY */
    #define S3C_VA_UART    S3C_ADDR(0x01000000)    /* UART */

    因为cpu的相关模块的寄存器地址都是分块的,如终端相关寄存器,被分配在一起,如mem内存相关寄存器,map-base.h中把各个模块先关的虚拟基地址罗列了出来;

    arch/arm/plat-s5p/include/plat/map-s5p.h

    #define S5P_VA_CHIPID        S3C_ADDR(0x00700000)
    #define S5P_VA_GPIO        S3C_ADDR(0x00500000)
    #define S5P_VA_SYSTIMER        S3C_ADDR(0x01200000)
    #define S5P_VA_SROMC        S3C_ADDR(0x01100000)
    #define S5P_VA_AUDSS        S3C_ADDR(0X01600000)
    
    #define S5P_VA_UART0        (S3C_VA_UART + 0x0)
    #define S5P_VA_UART1        (S3C_VA_UART + 0x400)
    #define S5P_VA_UART2        (S3C_VA_UART + 0x800)
    #define S5P_VA_UART3        (S3C_VA_UART + 0xC00)
    
    #define S3C_UART_OFFSET        (0x400)
    
    #define VA_VIC(x)        (S3C_VA_IRQ + ((x) * 0x10000))
    #define VA_VIC0            VA_VIC(0)
    #define VA_VIC1            VA_VIC(1)
    #define VA_VIC2            VA_VIC(2)
    #define VA_VIC3            VA_VIC(3)

    这个文件中三星工程师把要用得到的模块的基地址又细化了,如GPIO UART0-3 SROM VIC中断,如我们要添加新的模块的话,可以在这个文件中,定义相关模块寄存器

    的基地址;

    arch/arm/mach-s5pv210/include/mach/regs-gpio.h

    arch/arm/mach-s5pv210/include/mach/gpio-bank.h

    可以看到在gpio-bank.h文件中,定义了每个寄存器的虚拟地址;我们在操作相关寄存器的时候直接用这里定义好的宏就可以;

    如何遇到一个新的开发板如何找虚拟内存映射表呢,一般都是在arch/arm/plat或者 arch/arm/mach 等目录,一般是文件名都是map-文件;

     下面开始我们真正的应用程序通过驱动来控制led硬件;

    驱动模块

    #include <linux/module.h>        // module_init  module_exit
    #include <linux/init.h>            // __init   __exit
    #include <linux/fs.h>
    #include <asm/uaccess.h>
    #include <plat/map-base.h>
    #include <plat/map-s5p.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio-bank.h>
    
    #include <linux/string.h>
    
    #define MYMAJOR        200
    #define MYNAME        "LED_DEVICE"
    
    #define GPJ0CON        S5PV210_GPJ0CON
    #define GPJ0DAT        S5PV210_GPJ0DAT
    
    #define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
    #define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)
    
    static char kbuf[100];
    static int mymojor;
    
    static int led_dev_open(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "led_dev open
    ");
        
        return 0;
    }
    
    static int led_dev_release(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "led_dev close
    ");
        
        return 0;
    }
    
    ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
        int ret = -1;
            
        ret = copy_to_user(buf, kbuf, sizeof(kbuf));
        if(ret) {
            printk(KERN_ERR "kernel led read error
    ");
        }
        printk(KERN_INFO "led device read success
    ");
    }
    
    static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
    {
        int ret = -1;
        
        //首先把kbuf清零
        memset(kbuf, 0, sizeof(kbuf));
        
        ret = copy_from_user(kbuf, user_buf, count);
        if(ret) {
            printk(KERN_ERR "kernel led write error
    ");
            return -EINVAL;
        }
        
        printk(KERN_INFO "led device write success
    ");
        
        if (kbuf[0] == '1') {
            rGPJ0CON = 0x11111111;
            rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
        }
        
        if (kbuf[0] == '0') {
            rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
        }
        
        return 0;
    }
    
    static const struct file_operations led_dev_fops = {
        .open = led_dev_open,
        .write = led_dev_write,
        .read = led_dev_read,    
        .release = led_dev_release,
        .owner = THIS_MODULE,
    };
    
    
    
    
    
    // 模块安装函数
    static int __init leddev_init(void)
    {    
        
        printk(KERN_INFO "led_device init
    ");
        //printk("<7>" "chrdev_init helloworld init
    ");
        //printk("<7> chrdev_init helloworld init
    ");
        
        //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
        mymojor = register_chrdev(0, MYNAME, &led_dev_fops);
        
        if(!mymojor)
        {
            printk(KERN_ERR "led_device failed
    ");
            
            return -EINVAL;
        }
        
        printk(KERN_INFO "leddev_dev regist success
    ");
        
        return 0;
    }
    
    // 模块下载函数
    static void __exit leddev_exit(void)
    {
        printk(KERN_INFO "leddev_dev  exit
    ");
        
        //注销led设备驱动
        unregister_chrdev(mymojor, MYNAME);
        
        printk(KERN_INFO "leddev_dev  unregist success
    ");
    
        
    }
    
    
    module_init(leddev_init);
    module_exit(leddev_exit);
    
    // MODULE_xxx这种宏作用是用来添加模块描述信息
    MODULE_LICENSE("GPL");                // 描述模块的许可证
    MODULE_AUTHOR("bhc");                // 描述模块的作者
    MODULE_DESCRIPTION("led test");    // 描述模块的介绍信息
    MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

    app文件

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    
    
    #define FILE "/dev/led_device"
    
    char ubuf[100];
    
    int main(void)
    {
        int fd = -1;
        int ret = -1;
        
        //打开led设备
        fd = open(FILE, O_RDWR);
        if(fd < 0) {
            printf("/dev/led_device open failed
    ");
            return -1;
        }
        
        printf("please input on | off | quit.
    ");        
        
        while (1) {
            memset(ubuf, 0, sizeof(ubuf));
            scanf("%s", ubuf);
            if (!strcmp(ubuf, "on")) {
                write(fd , "1", 1);
            }
            if (!strcmp(ubuf, "off")) {
                write(fd, "0", 1);
            }
            if (!strcmp(ubuf, "quit")) {
                break;
            }
        }    
        
        //读led设备
        //ret =  read(fd, ubuf, 100);
        //printf("read is %s
    ", ubuf);    
        
        //关闭led设备
        close(fd);
        
    }

    使用动态虚拟地址

    动态虚拟地址:当我们要使用这个寄存器的物理地址的时候,不用事先建立好的页表,而是给物理地址动态的分配一个虚拟地址,操作的时候直接使用这个动态分配的虚拟地址

    操作物理地址即可,使用完以后取消映射即可;

    使用动态虚拟地址映射首先:

    1:建立映射

    使用request_mem_region向内核申请虚拟地址空间;

    #define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)

    request_mem_region实际上是一个宏,真正调用的是 __request_region这个函数;

    request_mem_region宏需要三个参数:start:启示的物理地址,n长度,name 

    申请成功则返回0;

    ioremap

     #define ioremap(cookie,size) __arm_ioremap(cookie, size, MT_DEVICE)

     也是一个宏,调用的是内核函数__arm_ioremap

     这个宏需要两个参数起始物理地址以及 长度;

    2:使用完以后我们首先要消除映射

    取消映射iounmap宏

    #define iounmap(cookie) __iounmap(cookie)

     接受一个参数,起始物理地址;

    然后在消除分配的虚拟地址

    使用release_mem_region

     #define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))

     这个宏只需要两个参数即可一个是起始物理地址,一个长度;

    下面看具体代码:我们只修改驱动代码,应用层代码不进行修改了。。。

    #include <linux/module.h>        // module_init  module_exit
    #include <linux/init.h>            // __init   __exit
    #include <linux/fs.h>
    #include <asm/uaccess.h>
    #include <plat/map-base.h>
    #include <plat/map-s5p.h>
    #include <mach/regs-gpio.h>
    #include <mach/gpio-bank.h>
    #include <linux/ioport.h>
    #include <linux/string.h>
    #include <asm/io.h>
    
    #define MYMAJOR            200
    #define MYNAME            "LED_DEVICE"
    
    #define GPJ0_PA_base        0xE0200240        
    #define GPJ0CON_PA_OFFSET    0x0
    
    
    unsigned int *pGPJ0CON;
    unsigned int *pGPJ0DAT;
    
    static char kbuf[100];
    static int mymojor;
    
    static int led_dev_open(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "led_dev open
    ");
        
        return 0;
    }
    
    static int led_dev_release(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "led_dev close
    ");
        
        return 0;
    }
    
    ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
        int ret = -1;
            
        ret = copy_to_user(buf, kbuf, sizeof(kbuf));
        if(ret) {
            printk(KERN_ERR "kernel led read error
    ");
        }
        printk(KERN_INFO "led device read success
    ");
    }
    
    static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
    {
        int ret = -1;
        
        //首先把kbuf清零
        memset(kbuf, 0, sizeof(kbuf));
        
        ret = copy_from_user(kbuf, user_buf, count);
        if(ret) {
            printk(KERN_ERR "kernel led write error
    ");
            return -EINVAL;
        }
        
        printk(KERN_INFO "led device write success
    ");
        
        if (kbuf[0] == '1') {
            *pGPJ0CON = 0x11111111;
            *(pGPJ0CON + 1) = ((0<<3) | (0<<4) | (0<<5));
        }
        
        if (kbuf[0] == '0') {
            *(pGPJ0CON + 1) = ((1<<3) | (1<<4) | (1<<5));
        }
        
        return 0;
    }
    
    static const struct file_operations led_dev_fops = {
        .open = led_dev_open,
        .write = led_dev_write,
        .read = led_dev_read,    
        .release = led_dev_release,
        .owner = THIS_MODULE,
    };
    
    
    // 模块安装函数
    static int __init leddev_init(void)
    {    
        
        printk(KERN_INFO "led_device init
    ");
        //printk("<7>" "chrdev_init helloworld init
    ");
        //printk("<7> chrdev_init helloworld init
    ");
        
        //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
        mymojor = register_chrdev(0, MYNAME, &led_dev_fops);
        
        if(!mymojor)
        {
            printk(KERN_ERR "led_device failed
    ");
            
            return -EINVAL;
        }
        
        printk(KERN_INFO "leddev_dev regist success
    ");
        
        if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) {
            return -EINVAL;
        }
        
        pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4);
            
        return 0;
    }
    
    // 模块下载函数
    static void __exit leddev_exit(void)
    {
        printk(KERN_INFO "leddev_dev  exit
    ");
        
        //注销led设备驱动
        unregister_chrdev(mymojor, MYNAME);
        
        iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET);
        
        release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8);
        
        printk(KERN_INFO "leddev_dev  unregist success
    ");
    
        
    }
    
    
    module_init(leddev_init);
    module_exit(leddev_exit);
    
    // MODULE_xxx这种宏作用是用来添加模块描述信息
    MODULE_LICENSE("GPL");                // 描述模块的许可证
    MODULE_AUTHOR("bhc");                // 描述模块的作者
    MODULE_DESCRIPTION("led test");    // 描述模块的介绍信息
    MODULE_ALIAS("alias xxx");            // 描述模块的别名信息
  • 相关阅读:
    Redis——发布/订阅
    Redis——任务队列
    GOF设计模式——Builder模式
    GOF设计模式——Prototype模式
    GOF设计模式——Singleton模式
    shell 脚本中的数学计算表达
    shell $'somestring'
    shell if-elif-elif-fi
    vim 使用
    疑问:为什么要使用href=”javascript:void(0);”?
  • 原文地址:https://www.cnblogs.com/biaohc/p/6575074.html
Copyright © 2011-2022 走看看