旧接口注册LED字符驱动设备(静态映射)

#include <linux/init.h>            // __init   __exit
#include <linux/module.h>      // module_init  module_exit

#include <linux/fs.h>          //file_operations

#include <asm/uaccess.h>          //copy_from_user  copy_to_user

#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>

#include <asm/string.h> 

#define rGPJ0CON  *((volatile unsigned int *)S5PV210_GPJ0CON)
#define rGPJ0DAT  *((volatile unsigned int *)S5PV210_GPJ0DAT)

static int led_open(struct inode *inode, struct file *file);
ssize_t led_read(struct file *file, char __user *user, size_t count, loff_t *loff);
ssize_t led_write(struct file *file, const char __user *user, size_t count, loff_t *loff);
static int led_release(struct inode *inode, struct file *file);

static int led_major = -1;
static char kbuf[100] = {0};
static const struct file_operations led_fops = {
    .owner        = THIS_MODULE,
    .open        = led_open,
    .read       = led_read,
    .write      = led_write,
    .release    = led_release,
};

int led_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_open successful\n");
    return 0;
}

ssize_t led_read(struct file *file, char __user *user, size_t ucount, loff_t *loff)
{
    printk(KERN_INFO "led_read successful\n");
    if (copy_to_user(user,kbuf , ucount))
    {
        printk(KERN_INFO "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user successful\n");
    return strlen(kbuf);
}

ssize_t led_write(struct file *file, const char __user *user, size_t ucount, loff_t *loff)
{
    printk(KERN_INFO "led_write successful\n");
    memset(kbuf,0,sizeof(kbuf));
    if (copy_from_user(kbuf, user, ucount))
    {
        printk(KERN_INFO "copy_from_user fail\n");
        return -EINVAL;
    }
    
    if(!strcmp(kbuf,"on"))
    {
        rGPJ0CON &=0xff000fff;
        rGPJ0CON |=0x00111000;
        rGPJ0DAT &=~((0x01<<3)|(0x01<<4)|(0x01<<5));
    }
    else if(!strcmp(kbuf,"off"))
    {
        rGPJ0CON &=0xff000fff;
        rGPJ0CON |=0x00111000;
        rGPJ0DAT |=((0x01<<3)|(0x01<<4)|(0x01<<5));
    }
    return ucount;
    printk(KERN_INFO "copy_from_user successful\n");
}

int led_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_release successful\n");
    return 0;    
}

// 模块安装函数
static int __init chrdev_init(void)
{    
    printk(KERN_INFO "chrdev_init successful\n");
    
    if ((led_major = register_chrdev (0, "led_dev", &led_fops)) < 0)
    {
        printk(KERN_WARNING "led_module.c: Failed to register character device.");
        return -EINVAL;
    }
    return 0;
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    
    unregister_chrdev(led_major,"led_dev");
    printk(KERN_INFO "chrdev_exit successful\n");

}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("Musk <qq:739112417>");                // 描述模块的作者
MODULE_DESCRIPTION("led driver");    // 描述模块的介绍信息
MODULE_ALIAS("led test driver");            // 描述模块的别名信息
View Code

    上述程序是led非常简陋,手动注册的字符驱动代码。

    此部分我从小往上分析。

    一. 模块部分

        1.1. 模块相关分析参考上篇文章

    二.驱动设备注册

        2.1. 认识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 **);
};
View Code

         linux应用层使用file_operations结构访问驱动程序的函数,这个结构的每一个成员的名字都对应着一个调用。       

        2.2. 注册&卸载字符驱动函数

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);
}
View Code

        2.2.1. 上述两个函数一般是成对出现的,使用注册了,在不使用时可以卸载。

        2.2.2. 需要注意的,register_chrdev注册时,注册的设备子设备号为0,@major参数对应需要注册的设备的主设备号,当此参数为0时表示由内核分配主设备号。

        2.2.3. 在内核管理设备时,使用数组管理所有设备(1~255),其中主设备号对应数组的下标。

        2.2.4. 使用cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动)

    三. 内核空间与用户空间数据交互

        3.1. 由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成用户空间到内核空间的复制,函数copy_from_user()完成内核空间到用户空间的复制

        3.2. 认识copy_to_user函数(函数在arch/arm/include/asm/uaccess.h)

static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
    if (access_ok(VERIFY_WRITE, to, n))
        n = __copy_to_user(to, from, n);
    return n;
}
View Code

            3.2.1. 此函数表示从内核空间拷贝n个字节到用户空间。             

            3.2.2. 先看函数的三个参数:*to是内核空间的指针,*from是用户空间指针,n表示从内核空间拷贝到用户空间数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数

        3.3. 认识copy_from_user函数(函数在arch/arm/include/asm/uaccess.h)

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
    if (access_ok(VERIFY_READ, from, n))
        n = __copy_from_user(to, from, n);
    else /* security hole - plug it */
        memset(to, 0, n);
    return n;
}
View Code

            3.3.1. 此函数表示从用户空间拷贝n个字节到内核空间。

            3.3.2. 先看函数的三个参数:*to是内核空间的指针,*from是用户空间指针,n表示从用户空间拷贝内核空间到数据的字节数。如果成功执行拷贝操作,则返回0,否则返回还没有完成拷贝的字节数

    四. 设备节点(设备文件)的手动创建

        4.1. 关于设备节点(设备文件)

            4.1.1. 在Linux中,所有的设备访问都是通过文件的方式,一般的数据文件程序普通文件,设备节点称为设备文件

            4.1.2. 对应字符类设备文件的关键信息是:设备号 = 主设备号 + 次设备号,使用ls -l去查看设备文件,就可以得到这个设备文件对应的主次设备号。

        4.2. 创建设备节点

            4.2.1. 使用mknod创建设备文件:mknod /dev/xxx c 主设备号 次设备号,其中xxx:表示设备节点名称(应用层可以使用open打开此文件)

    五. 驱动中操作硬件

        5.1. 虚拟地址

            5.1.1. 由于内核层使用虚拟地址,无法字节使用物理地址操作寄存器。

            5.1.2. 内核中物理地址映射分为:动态和静态

            5.1.3. 静态映射方法的特点:

                a.  在内核启动时建立静态映射表,在内核关机时销毁,中间一直有效,优点是执行效率高,缺点是始终占用虚拟地址空间,空间利用率低。

                b. 不同版本内核静态映射表位置,文件名可能不同

                c. 不同的SOC静态映射表位置,文件名可能不同

                d. 所谓的映射表其实是头文件中的宏定义

            5.1.4. 动态映射方法的特点:

                a. 驱动程序根据需要随时动态建立使用和销毁映射,映射是短期临时的。类似c语言中的malloc分配内存。

                b. 优点是按需使用地址空间,空间利用率高。缺点是每次使用前后都要去建立和销毁映射,操作繁琐

            5.1.5. 虚拟地址到物理地址的映射可以多对1,但是不能1对多

        5.2. 驱动中操作寄存器

            5.2.1. 使用寄存器对应的虚拟地址直接读取。

            5.2.2. 操作相关文件

                5.2.2.1.  主映射表位于:arch/arm/plat-s5p/include/plat/map-s5p.h

                        a. CPU在安排寄存器地址时不是随意乱序分布的,而是按照模块去区分的。每一个模块内部的很多个寄存器的地址是连续的。所以内核在定义寄存器地址时都是先找到基地址,然后再用基地址+偏移量来寻找具体的一个寄存器。

                        b. map-s5p.h中定义的就是要用到的几个模块的寄存器基地址。

                        c. map-s5p.h中定义的是模块的寄存器基地址的虚拟地址。

                5.2.2.2. 虚拟地址基地址定义在:arch/arm/plat-samsung/include/plat/map-base.h

                        a. #define S3C_ADDR_BASE(0xFD000000)// 三星移植时确定的静态映射表的基地址,表中的所有虚拟地址都是以这个地址+偏移量来指定的

                5.2.2.3. GPIO相关的主映射表位于:arch/arm/mach-s5pv210/include/mach/regs-gpio.h

                        a. 表中是GPIO的各个端口的基地址的定义

                5.2.2.4. GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h

         六. 应用层调用内核

            6.1. 相关代码如下               

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <string.h>

#define DEVFILE "/dev/led_dev"

int main(void)
{
    char buf[100] = {0};
    int fd = -1;
    if((fd =open(DEVFILE, O_RDWR))<0)
    {
        perror("open");
        return -1;   
    }
    printf("open successful fd = %d\n",fd);
    if(write(fd, "on", strlen("on"))<0)
    {
        perror("write");
        return -1;
    }   
    sleep(3);
    memset(buf,0,sizeof(buf));
    if(read(fd, buf, 3)<0)
    {
        perror("read");
        return -1;
    }
    printf("read data = %s\n",buf);
    
    
    if(write(fd, "off", strlen("off"))<0)
    {
        perror("write");
        return -1;
    }   
    sleep(3);
    memset(buf,0,sizeof(buf));
    if(read(fd, buf, 4)<0)
    {
        perror("read");
        return -1;
    }
    printf("read data = %s\n",buf);
    
    close(fd);
    return 0;
}
View Code

 

posted @ 2019-01-04 09:44  三七鸽  阅读(284)  评论(0编辑  收藏  举报