linux设备驱动 (0)

一、 linux中设备驱动的类型
字符设备 GPIO、EINT、ADC、WDT、LCD、CAMERA、JPEG......
块设备 nand flash、SD卡、硬盘
网络设备 :有线网卡、无线网卡
===================================================================================

二、字符设备驱动的描述
1、cdev
cdev 是一个描述一个 字符设备的结构体 ,我们要设计一个字符设备,就必须定义一个 cdev的结构体,然后对cdev结构进行初始化,初始化结束后, cdev注册到内核 ,这样,内核中就添加了一个字符设备驱动。
 
#include <linux/cdev.h>
 
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
 
1)struct kobject kobj;
    linux在做字符设备管理的时候,使用的驱动模型,利用kobject生成/sys下的驱动信息。
 
2)struct module *owner;
     这个字符设备输入那一个 module, 一般设备为 THIS_MODULE
 
 
3)const struct file_operations *ops;
     文件操作集 ,给应用程序提供 API接口,应用程序通过系统调用,来访问file_operations中的接口函数。
 
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_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);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};
 
以上的接口函数,是应用程序中,文件 IO的系统调用接口。
open、read、write、lseek、ioctl、mmap、close
 
 
提示 1:
     文件 IO函数原型中的参数,与文件操作集中对应函数的参数是否一致???
      是一个系统调用过程,中间是经过 vfs和linux内核,所以参数是不一致的,与函数调用不同。
 
提示 2:
    ssize_t是什么类型?
typedef __kernel_ssize_t ssize_t;
 
#ifndef __kernel_size_t
#if __BITS_PER_LONG != 64
typedef unsigned int __kernel_size_t;
typedef int __kernel_ssize_t;
typedef int __kernel_ptrdiff_t;
#else
typedef unsigned long __kernel_size_t;
typedef long __kernel_ssize_t;
typedef long __kernel_ptrdiff_t;
#endif
#endif
 
4) struct list_head list;
     内核双向链表,向内核中注册一个字符设备时,链表增加一项
 
5) dev_t dev;
设备号
 
6)unsigned int count;
次设备号的数量
 
 
-----------------------------------------------------------------------------------------------------------
2、设备号
 
1) 设备号 的定义
dev_t dev;
 
linux内核中,每一个设备驱动都有一个设备号,设备号有 主设备号和次设备号 组成
主设备号:描述设备驱动的具体应用类型
次设备号:描述该具体应用类型下的一个应用实例
 
 
例如: 4个uart的驱动
[root@YueQian /]# ls /dev/s3c2410_serial* -l
crw-rw----    1 root     root      204,  64 Jan  2 11:21 /dev/s3c2410_serial0
crw-rw----    1 root     root      204,  65 Jan  2 11:21 /dev/s3c2410_serial1
crw-rw----    1 root     root      204,  66 Jan  2 11:21 /dev/s3c2410_serial2
crw-rw----    1 root     root      204,  67 Jan  2 11:21 /dev/s3c2410_serial3
 
例如: nand flash的5个分区
[root@YueQian /]# ls /dev/mtdblock* -l
brw-rw----    1 root     root       31,   0 Jan  2 11:21 /dev/mtdblock0
brw-rw----    1 root     root       31,   1 Jan  2 11:21 /dev/mtdblock1
brw-rw----    1 root     root       31,   2 Jan  2 11:21 /dev/mtdblock2
brw-rw----    1 root     root       31,   3 Jan  2 11:21 /dev/mtdblock3
brw-rw----    1 root     root       31,   4 Jan  2 11:21 /dev/mtdblock4
 
typedef unsigned int __u32;
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
 
设备号 是一个 无符号 32位的整型值 ,其中 12bits是主设备号,低20bits是次设备号
 
 
-----------------------------------------------------------------------------------------------------------
2)主次设备号之间的函数
 
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)  //0x000 fffff
 
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))  //MINORBITS = 20
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
 
 
-----------------------------------------------------------------------------------------------------------
3)如何得到一个设备号
我们设置一个设备驱动,需要从内核中,申请一个设备号。
1) 静态注册设备号 :我们来指定设备号,不要使用系统已经使用的设备号
/**
 * register_chrdev_region() - register a range of device numbers
 * @from: the first in the desired range of device numbers; must include
 *        the major number.
 * @count: the number of consecutive device numbers required
 * @name: the name of the device or driver.
 *
 * Return value is zero on success, a negative error code on failure.
 */
 
int register_chrdev_region(dev_t from, unsigned count, const char *name)
 
参数说明:
    dev_t from:注册的一个设备号的开始值。如果是gec210 uart,from=(204<<20 + 64)
    unsigned count: 设备实例的数量,即次设备 号的数目。如果是 gec210 uart,count=4
    const char *name:字符设备的名称。如果是gec210 uart,name=s3c2410_serial
返回值:
     注册成功,返回 0
     注册失败,返回一个负数的错误码
 
 
2) 动态分配设备号 :让系统自动分配空闲的设备号
/**
 * alloc_chrdev_region() - register a range of char device numbers
 * @dev: output parameter for first assigned number
 * @baseminor: first of the requested range of minor numbers
 * @count: the number of minor numbers required
 * @name: the name of the associated device or driver
 *
 * Allocates a range of char device numbers.  The major number will be
 * chosen dynamically, and returned (along with the first minor number)
 * in @dev.  Returns zero or a negative error code.
 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
 
参数说明:
    dev_t *dev:动态分配后的设备号。
    unsigned baseminor:我们使用的第一个次设备号,次设备号的开始值,如果是gec210 uart,baseminor=64
    unsigned count: 设备实例的数量,即次设备 号的数目。如果是 gec210 uart,count=4
    const char *name:字符设备的名称。如果是gec210 uart,name=s3c2410_serial
返回值:
     注册成功,返回 0
     注册失败,返回一个负数的错误码
 
===================================================================================
三、字符设备驱动的设计思路
 
1、 定义一个字符设备
struct cdev chrdev_test;
------------------------------------------------------------------------------------
 
2、 定义一个文件操作集,并对文件操作集进行初始化
struct file_operations  chdev_fops = {
.open = test_open,
.read = test_read,
.write = test_write,
.ioctl = test_ioctl,
.release = test_release,
};
------------------------------------------------------------------------------------
 
3、 字符设备的初始化
/**
 * cdev_init() - initialize a cdev structure
 * @cdev: the structure to initialize
 * @fops: the file_operations for this device
 *
 * Initializes @cdev, remembering @fops, making it ready to add to the
 * system with cdev_add().
 */
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:
    struct cdev *cdev:定义的字符设备
    const struct file_operations *fops:已经完成定义和初始化的一个文件操作集
------------------------------------------------------------------------------------
 
4、申请设备号
        dev_t ndev;//设备号
        int TestMajor = 0;//主设备号
int TestMinor = 0;//次设备号
char drv_name[]="chrtest1";
int ret;
 
if(TestMajor)
{
ndev=MKDEV(TestMajor,TestMinor);//由主次设备号,得到设备号
ret=register_chrdev_region(ndev,1,drv_name);//注册字符设备编号
}
else
{
ret=alloc_chrdev_region(&ndev,TestMinor,1,drv_name);//动态分配一个字符设备号,&ndev存放返回的设备号
TestMajor=MAJOR(ndev);
}
if(ret<0)
{
printk(KERN_WARNING"cannot get major %d \n",TestMajor);
goto fail_chrdev_region; //出错处理 goto
}
 
------------------------------------------------------------------------------------
5、 将字符设备加入到内核
 
/**
 * cdev_add() - add a char device to the system
 * @p: the cdev structure for the device
 * @dev: the first device number for which this device is responsible
 * @count: the number of consecutive minor numbers corresponding to this
 *         device
 *
 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately.  A negative error code is returned on failure.
 */
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:
struct cdev *p :定义并初始化的字符设备cdev
dev_t dev:已经申请到的设备号
unsigned count:次设备的数量
返回值:
     注册成功,返回 0
     注册失败,返回一个负数的错误码
 
 
------------------------------------------------------------------------------------
6、 注销一个设备号
/**
 * unregister_chrdev_region() - return a range of device numbers
 * @from: the first in the range of numbers to unregister
 * @count: the number of device numbers to unregister
 *
 * This function will unregister a range of @count device numbers,
 * starting with @from.  The caller should normally be the one who
 * allocated those numbers in the first place...
 */
void unregister_chrdev_region(dev_t from, unsigned count)
参数说明:
    dev_t from:注册的一个设备号的开始值。如果是gec210 uart,from=(204<<20 + 64)
    unsigned count: 设备实例的数量,即次设备 号的数目。如果是 gec210 uart,count=4
 
------------------------------------------------------------------------------------
7、 注销一个字符设备
/**
 * cdev_del() - remove a cdev from the system
 * @p: the cdev structure to be removed
 *
 * cdev_del() removes @p from the system, possibly freeing the structure
 * itself.
 */
void cdev_del(struct cdev *p)
 
 
 
===================================================================================
四、用户空间与内核空间交互数据的函数
 
1、 将用户空间的数据拷贝到内核空间
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
 
 
2、 将内核空间的数据拷贝到用户空间
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
 
返回值:如果数据拷贝成功,返回是 0;如果 数据拷贝失败,返回是尚未拷贝的字节数。
 
 
提示 1:
__must_check 必须检查该函数的返回值,如果不检查,会有警告。
 
提示 2:
inline --内联函数
 
===================================================================================
五、驱动程序的设计
 
demo1
 
===================================================================================
六、驱动的调试
写一个应用程序,利用应用程序调试驱动程序。
1、安装驱动
[root@YueQian /test]# insmod demo1.ko  
[24273.778642]
[24273.779733] this is the first demo of driver,  inserting successful !
 
 
2、查看内核模块
[root@YueQian /test]# lsmod
demo1 2589 0 - Live 0xbf000000
 
3、查看字符设备的主设备号和设备名称
char drv_name[ ]="chrtest";
int TestMajor = 0;//主设备号
int TestMinor = 0;//次设备号
 
[root@YueQian /test]# cat /proc/devices  
Character devices:
250 chrtest   --->主设备号和设备名称
 
4、 手动创建设备文件 (后面讲如何自动创建设备文件)
[root@YueQian /test]# mknod /dev/chr_test1 c 250 0
 
5、查看设备文件
[root@YueQian /test]# ls /dev/chr_test1 -l
crw-r--r--    1 root     root      250,   0 Jan  2 18:14 /dev/chr_test1
 
6、执行应用程序
 
 
7、 驱动的卸载
[root@YueQian /]# rmmod demo1
[25293.152108] the driver is exiting
 
 
 
===================================================================================
七、字符设备驱动的老方法
老的方法更简洁,更直观;新的方法比较灵活。
 
1、注册字符设备
 
static inline int register_chrdev(unsigned int major, const char *name,
  const struct file_operations *fops)
参数说明:
    unsigned int major  主设备号,如果major=0,系统会自动分配一个主设备号,如果major!=0,静态注册主设备号。
    const char *name  设备名称
    const struct file_operations *fops 文件操作集。
 
返回值:
    1)如果unsigned int major 是大于0的值,静态注册字符设备:
注册成功,返回 0
     注册失败,返回一个负数的错误码
2)如果unsigned int major 是等于0的值,动态注册字符设备
分配成功,返回分配后的主设备号
     注册失败,返回一个负数的错误码
 
将设备号申请、字符设备的初始化、字符设备的注册使用一个函数实现。
 
2、注销字符设备
static inline void unregister_chrdev(unsigned int major, const char *name)
 
 
 
 
 
 
 
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值