Chinaunix首页 | 论坛 | 博客
  • 博客访问: 437231
  • 博文数量: 71
  • 博客积分: 26
  • 博客等级: 民兵
  • 技术积分: 1246
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-23 14:46
个人简介

linux --- 一切皆文件

文章分类

全部博文(71)

文章存档

2021年(1)

2019年(2)

2018年(4)

2017年(7)

2016年(11)

2015年(1)

2014年(2)

2013年(33)

2012年(10)

分类: LINUX

2013-10-10 23:08:29

 通过数据的传输方式,可以大致的分为三类驱动:
        字符设备驱动:字节流
        快设备驱动 :数据块 硬盘块叫block 512B,flash的块叫page 4K
        网络设备驱动:数据包


首先学习的是字符设备驱动,


字符设备的大体调用流程:


    上层应用程序通过open,read,write等函数对/dev/xxx文件进行操作
    ---------/dev/xxx--------------------- 其实这个文件是通过设备号与底层驱动进行关联的
            ls -l /dev/xxxx   
    结果为  c 10 0 表示这个设备节点关联的是字符设备,并且设备的主设备好是10,次设备号是0
                调用
    -------sys_call----------------------
            调用
    ----------vfs---------------------------
    通过设备号在vfs中找到对应的处理函数


设备号:
设备号是cdev_t类型的一个标号,前12位表示的是主设备号,后20位表示的是次设备号,但是现在主次设备最多支持255个。
    major = MAJOR(cdev_t id); 获取主设备号
    minor = MINOR(cdev_t id); 获取次设备号
    cdev_t id = MKDEV(int major,int minor);根据主次设备号计算dev_id
以上是在我们知道主次设备好的情况下可以进行:
    dev_t base_id;
    base_id = MKDEV(major, 0);
    register_chrdev_region(base_id, DEV_NUM, "char_test");将设备号进行注册,放止重复使用
如果不清楚主次设备号,可以通过系统来分配:
    alloc_chrdev_region(&base_id, 0, DEV_NUM, "char_test");让内核来回填base_id
    major = MAJOR(base_id);来获取主设备号
这样设备的编号就已经申请好了,然后就可以在应用层进行mknode:
    mknode /dev/led_01 c 10 0
这里的主次设备号要和驱动中注册的相同,应为驱动和应用层通过这id进行匹配对应的读写函数。
**在设备模型里,可以通过netlink来发送请求给udev让应用层的udev来动态创建节点。


字符驱动核心数据结构:
struct file_operations mem_fops{
    .owner = THIS_MODULE, //结构体中有这个成员的一定要赋值成THIS_MODULE,放止在模块运行时被卸载
    .read = my_read, //自己些的读函数,应用层读写结点文件,调用的就是对饮节点的read函数
    .write = my_write, //与上面的类似
    .open = my_open, //与上面的类似
    .release = my_relese, //与上面的类似
    .unlocked_ioctl = mem_ioctl, //与上面的类似
};
这个结构体在注册设备的时候,cdev会有成员指向这个结构体,当用户调用open函数的时候,
内核会为这次open创建一个文件描述符,并且指向一个file结构体,这个file结构体里面也有成员指向file_operations,
这样的话,打开一次之后,再对这个描述符操作的时候就可以直接调用函数了,不用在过vfs了
总结file_operations两处使用 :
    1:file_operations在注册的时候会和cdev进行帮定,方便应用层通过dev_id找到执行函数,
    在建立file结构体的时候会将file结中的f_ops执行file_operations
    2:当open文件后,系统产生file结构体中的成员也会指向file_operations,如果要执行read操作的话,可以直接执行
---------------------------------------------------------
struct file filp{
    .f_ops = &mem_f_ops,
    .private_data = 私有结构体,将来可以之间通过file进行访问
};
私有结构体里面有cdev,而inode里也有cdev这个成员,可以在open的时候,
通过inode里的cdev找到私有结构体存放在.private_data成员里。
---------------------------------------------------------
struct inode node{
    .i_rdev = base_id, //这里存放设备号
    .i_cdev = &cdev, //这个cdev在注册的时候是指向私有结构体里的cdev的,所以可以通过cdev找到私有数据
};


然后就是字符驱动的注册了:
首先定义一个私有结构体,存放一些私有的信息:


struct cdev {
    struct kobject kobj;
    struct module *owner; //THIS_MODULE
    const struct file_operations *ops; //初始化的时候会把ops指向file_operations
    struct list_head list;
    //会把这个结构体放在一个链表上,通过dev_id进行识别,并且给然对应的file中的f_op指向这里的ops,也就是我们自己些的file_operations
    dev_t dev; //设备ID
    unsigned int count;
};
struct mem_priv {
    char *start, *end;
    int buf_len;
    int wp; /* write position */
    dev_t dev_id; //存放设备ID
    struct cdev mem_cdev; //存放cdev
};


static struct mem_priv *devs[MEM_NUM];


struct mem_priv *dev;
dev = (struct mem_priv *)kzalloc(sizeof(*dev),
GFP_KERNEL);//分配私有结构体空间,这个结构中有cdev,也就是贯穿始终的cdev


dev->dev_id = MKDEV(major, i+10);//设置devid


cdev_init(&dev->mem_cdev, &mem_fops); //将mem_cdev->ops 指向自己的file_operations
dev->mem_cdev.owner = THIS_MODULE;
cdev_add(&dev->mem_cdev, dev->dev_id, 1); //将设备id赋值给mem_cdev->dev_id,并且添加到链表上
*******到此为止,字符驱动驱动的注册搞定了**********
//完成open函数,
int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_priv *tmp = container_of(inode->i_cdev, struct mem_priv, mem_cdev);
    //因为inode->i_cdev指向的就是私有结构体的mem_cdev,所以通过container_of找到对应的私有结构体
    filp->private_data = tmp; //将这个私有结构存放到file的private_data中,以后在read,write的时候就可以直接使用了
}


ssize_t mem_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct mem_priv *dev = filp->private_data; //获取到私有数据
……………………
}
在用户态和内核态之间数据拷贝使用的是:
copy_to_user() copy_from_user(),如果参数表记为__user则说明是用户态的内存
内核态分配空间使用kzalloc(size,gfp_t )
把硬件寄存器映射成虚拟地址:ioremap(),iounmap()
GFP_KERNEL可能休眠,分配物理连续内存GFP_ATOMIC不会休眠可能会失败,使用紧急内存


思路整理如下:
*****************************************************************************
1:在私有结构体中存放 struct cdev结构体是一个技巧,用于之后找到私有结构体
2:私有结构体里的cdev会和file_operactions和dev_id结合在一起,并且把这个dev放在一个链表上
形成如下结构:
private_struct1 private_struct2
head------- cdev -----链表---> cdev
因为cdev中有id所以很轻松可以找到对应节点的cdev
3:当要open一个设备节点,返回一个fd的时候,会有两个结构体被创建,一个是file,一个是inode
file结构体中的f_op会指向cdev中的ops,这样对这个fd进行读写操作的时候就可以直接使用了,不用再通过VFS了
在执行open函数的时候,函数会传入inode和file两个结构体,inode结构体里有cdev结构,通过这个结构可以找到私有结构体
然后将filp->private_data指向私有解体,这样file结构中就有了操作行为fops和数据privatedata,为之后的read和write等操作作准备




最后就是ioctl,
.unlocked_ioctl = mem_ioctl, //应用层调用ioctl,对应于驱动的.unlocked_ioctl
ioctl号:
    2 |     14     |     8     |     8     |
     |          |              |          |->ioctl号,1、2、3、4
     |         |                 |->类型,#define MEM_TYPE 'Z'
     |          |->参数大小(一般传递结构体指针)
     |->对于内核的写入或者写出方向,内核中说的读写都是从用户态看的


#define MEM_TYPE    'Z'
#define MEM_RESET   _IO(MEM_TYPE, 1)  //ioctl号1没有参数
#define MEM_RESIZE  _IOW(MEM_TYPE, 2, int) //ioctl号2带int类型的参数,从用户态到内核态写
#define MEM_RESIZE  _IOR(MEM_TYPE, 3, int) //ioctl号3带参数int类型的参数,从用户态读取内核态的数据


long mem_ioctl(struct file *filp, unsigned int req, unsigned long arg)
{
req为ioctl请求号
_IOC_TYPE(req) //从请求号中获取请求类型


_IOC_NR(req)  //获取请求号,进行switch case操作
}


用户程序:
#define MEM_TYPE    'Z'
#define MEM_RESET   _IO(MEM_TYPE, 1)
#define MEM_RESIZE  _IOW(MEM_TYPE, 2, int)
fd = open("/dev/abc0", O_RDWR);
ret = ioctl(fd, MEM_RESIZE, 0x10000);这样使用就好了

*******对寄存器的操作*******
#define GPIO_BASE   0x7F008800
#define GPIO_SIZE   0x1000 
#define GPMCON      0x20
#define GPMDAT      0x24



static void __iomem *vir_base;   //__iomem表示访问内存
vir_base = ioremap(GPIO_BASE, GPIO_SIZE);    //将物理地址映射称虚拟地址
if (!vir_base) {
    printk("Cannot ioremap\n");
    return -EIO;
}

/* 8位寄存器 */
char value;
value = readb(vir_base + offset);
writeb(value, (vir_base+offset));

/* 16位寄存器 */  
short value;
value = readw(vir_base+offset);
writew(value, (vir_base+offset));


/* 32位寄存器 */  
int value;
value = readl(vir_base+offset);
writel(value, (vir_base+offset));


/* 64位寄存器 */
u64 value;
value = readq(vir_base+offset);
writeq(value, (vir_base+offset));


//如果不再访问寄存器,应该取消映射
iounmap(vir_base);



LED的例子.rar

阅读(1368) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~