自己写驱动之Linux设备驱动开发详解———设备驱动概述
之前就学习过Linux设备驱动开发详解,可是看得云山雾绕的,现在使用JZ2440开发板将近6个月左右,基础应该还不错,所以决定再重新认认真真地学习一遍Linux设备驱动开发详解。
例子:Linux系统下的LED驱动
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#include //POWER Manager
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LIGHT_ON 0
#define LIGHT_OFF 1
#define LIGHT_MAJOR 251
//设备结构体
struct light_dev
{
struct cdev cdev; //字符设备cdev 结构体,具体见注释1
unsigned char value; //LED亮时为1,熄灭时为0,用户可以读写此值
};
struct light_dev *light_devp;
int light_major = LIGHT_MAJOR;
void
light_on(
void
)
{
__gpio_set_pin(GPIO_LED_PIN);
}
void
light_off(
void
)
{
__gpio_clear_pin(GPIO_LED_PIN);
}
#
if
0
int
light_init(
void
)
{
__gpio_as_output(GPIO_LED_PIN);
}
#endif
//打开和关闭函数
static int light_open(struct inode * inode, struct file * filp)
{
struct light_dev *dev;
//获得设备结构体指针
dev = container_of(inode->i_cdev, struct light_dev, cdev); //见注释2
//让设备结构体作为设备的私有信息
filp->private_data = dev;
return 0;
}
static int light_release(struct inode * inode, struct file * filp)
{
return 0;
}
//写设备: 可以不需要
static ssize_t light_read(struct file * filp, char __user * buf, size_t nbytes, loff_t * ppos)
{
struct light_dev *dev= filp->private_data;
if(copy_to_user(buf, &dev->value, 1))
{
return -EFAULT;
}
return 1;
}
static ssize_t light_write(struct file * filp, const char __user * in, size_t size, loff_t * off)
{
struct light_dev *dev= filp->private_data;
if(copy_from_user(&(dev->value), in, 1))
{
return -EFAULT;
}
//根据写入的值点亮和熄灭LED
if(dev->value == 1)
light_on();
else
light_off();
return 1;
}
static long light_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct light_dev *dev= filp->private_data;
switch(cmd)
{
case LIGHT_ON:
dev->value =1;
light_on();
break;
case LIGHT_OFF:
dev->value =0;
light_off();
break;
default:
//不能支持的命令
return -ENOTTY;
}
return 0;
}
static struct file_operations light_fops=
{
.owner = THIS_MODULE,
.open = light_open,
.release = light_release,
.read = light_read,
.write = light_write,
.ioctl = light_ioctl,
};
//设置字符设备cdev 结构体
static void light_setup_cdev(struct light_dev *dev, int index)
{
int err, devno = MKDEV(light_major, index);
cdev_init(&dev->cdev, &light_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &light_fops;
err = cdev_add(&dev->cdev, devno, 1);
if(err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
static int light_init(void)
{
int result;
dev_t dev = MKDEV(light_major, 0);
//申请字符设备号
if(light_major)
result = register_chrdev_region(dev, 1, "LED");
else
{
result = alloc_chrdev_region(&dev, 0, 1, "LED");
light_major =MAJOR(dev);
}
if (result < 0)
return result;
//分配设备结构体的内存
light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL);
if(!light_devp) //分配失败
{
result = -ENOMEM;
goto fail_malloc;
}
memset(light_devp, 0 , sizeof(struct light_dev));
light_setup_cdev(light_devp, 0);
light_init();
return 0;
fail_malloc:
unregister_chrdev_region(dev, light_devp);
return result;
}
static void light_exit(void)
{
cdev_del(&light_devp->cdev); //删除字符设备结构体
kfree(light_devp); //释放在light_init中分配的内存
unregister_chrdev_region(MKDEV(light_major, 0), 1); //删除字符设备
}
module_init(light_init);
module_exit(light_exit);
MODULE_AUTHOR("DIAN");
MODULE_LICENSE("GPL");
**************************************************************************************************************************************************************************************************************************************
注释1:
struct cdev {
struct kobject kobj; //内嵌的kobject对象
struct module *owner; //所属模块
const struct file_operations *ops; //文件操作结构体
struct list_head list; //linux内核所维护的链表指针
dev_t dev; //设备号
unsigned int count;
};
其中,dev_t成员定义了设备号,为32位,其中高12位为主设备号,低20位为次设备号,使用下列宏可以从dev_t获得主、从设备号,见linux/kdev_t.h:
MAJOR(dev_t dev) MINOR(dev_t dev)
MKDEV(int major, int minor) ; 由主、从设备号生成dev_t
//用于初始化cdev成员,并建立cdev和file_operations之间的连接;
void cdev_init(struct cdev *, const struct file_operations *);
//用于动态申请一个cdev内存;
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
//动态的申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
//注销设备号
void unregister_chrdev_region(dev_t from, unsigned count)
//用于向系统添加一个cdev,完成字符设备的注册
int cdev_add(struct cdev *, dev_t, unsigned);
//用于从系统中删除一个cdev,完成字符设备的注销
void cdev_del(struct cdev *);
int cdev_index(struct inode *inode);
void cd_forget(struct inode *);
http://blog.csdn.net/ziyedianyuxiao/article/details/42753229
注释2:
本文出自:http://blog.csdn.net/hongchangfirst
关于container_of的用法,可参考http://blog.csdn.net/hongchangfirst/article/details/7076225。其实就是解决了”如何通过结构中的某个变量的地址获取结构本身的指针“这样的问题。container_of实现了根据一个结构体变量中的一个成员变量的指针来获取指向整个结构体变量的指针的功能。
首先container_of出现在linux/kernel.h中。定义如下:
[cpp] view
plaincopy
-
"font-size:18px;">
-
-
-
-
-
-
-
#define container_of(ptr, type, member) ({
-
const typeof( ((type *)0)->member ) *__mptr = (ptr);
-
(type *)( (char *)__mptr - offsetof(type,member) );})
typeof( ((type *)0)->member ) *__mptr 就是声明了一个指向其我们在看一下offset的定义,在linux/stddef.h中。定义如下:
[cpp] view
plaincopy
-
"font-size:18px;">#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
offset顾名思义就是获得该成员变量基于其包含体地址的偏移量。先分析一下这个宏:
1. ( (TYPE *)0 ) 将零转型为TYPE类型指针;
2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;
3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址;
4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。
有人可能感觉很不适应(TYPE *)0这种用法,把一个0转换成一个结构体指针是什么意思呢?其实就是声明了一个指向无物的指针,并告诉编译器这个指针式指向TYPE类型的,然后成员地址自然为偏移地址,因为成员地址-0还是成员地址。
最后把__mptr 强制类型转换为char*类型,保证指针相减时是以一个字节为单位进行相减的,保证了程序的正确性。
这样我们就可以利用container_of来根据一个成员变量的指针来获取指向整个结构体变量的指针,对于应用层程序而言,这种机制完全没有必要,但是对于设备驱动程序而言,运用container_of就很有必要了。
注释3:
http://blog.csdn.net/ziyedianyuxiao/article/details/42755019
注释4:
http://blog.csdn.net/ziyedianyuxiao/article/details/42755455