目录
0.前言
分享一句话:大事随心,小事随脑。
1.字符设备
Linux有三种驱动设备模型,字符设备、块设备、网络设备。
字符设备是面向字节流的。定义:是指只能一个字节一个字节的读写的设备,不能随机的读取设备中的某一段数据。
常见的字符设备:鼠标 键盘 串口 。。。
1.1字符设备驱动框架
init:
{
申请设备号(静态申请 动态申请)
创建字符设备
初始化字符设备
将设备号和字符设备关联
}
exit:
{
销毁字符设备
删除申请的设备号
}
设备号:是一个32bits位的无符号数,高12位是主设备号,低20位是次设备号。
主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务。
次设备号则用来标识具体且唯一的某个设备。
2.步骤
2.1申请设备号
我以动态申请为例。
函数:alloc_chrdev_region
查看内核代码。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
参数:
*dev:设备号指针
baseminor:子设备的第一个编号
count:子设备个数
*name:设备名称
返回值:成功为0
2.2创建字符设备
函数:cdev_alloc
(我用的是source insight看的内核代码)
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
无入参
返回值:指向字符设备结构体的指针
在linux内核中,struct cdev用来描述一个字符设备。再来看一下这个struct cdev。
struct cdev {
struct kobject kobj; //内嵌的内核对象
struct module *owner; //该字符设备所在的内核模块的对象指针
const struct file_operations *ops; //该结构描述了字符设备所能实现的所有方法
struct list_head list; //用来将已向内核注册的所有的字符设备形成链表
dev_t dev; //设备号,由主设备和次设备构成
unsigned int count; //隶属于同一个主设备号的次设备个数
};
所以我们要搞一个struct cdev指针接收,这是后话。
2.3初始化字符设备
函数:cdev_init
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
参数:
*cdev:被初始化的字符设备指针
*fops:字符设备操作函数指针集
无返回值
看下这个*fops:字符设备操作函数指针集。
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 (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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 *, loff_t, loff_t, 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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
里面大都是函数指针,指向你去定义的函数。当然函数格式和这里面的函数一样。
2.4将字符设备和设备号关联
函数:cdev_add
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
p->dev = dev;
p->count = count;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
参数:
*p:字符设备结构体指针
dev:设备号
count:子设备个数
返回值:成功为0
2.5exit
函数:cdev_del 销毁字符设备
unregister_chrdev_region 删除申请的设备号
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
void unregister_chrdev_region(dev_t from, unsigned count)
{
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
}
参数:
from:设备号
count:子设备个数
3.完整程序
/*动态 */
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define BUF_SIZE 100
int g_min=0; //子设备得第一个编号
dev_t g_DevNum=0; //存放设备号
char *g_DevName="hehe"; //设备名 这是注册函数所需要的参数
struct cdev *g_pCdev=NULL; //用来接收cdev_alloc()返回的内存空间
char g_Buf[BUF_SIZE]="Darling, you fly slowly";
int HelloOpen(struct inode *pNode,struct file *pFile)
{
printk("into hello open-----------\r\n");
printk("leave hello open-----------\r\n");
return 0;
}
int HelloClose(struct inode *pNode,struct file *pFile)
{
printk("into hello close---------\r\n");
printk("leave hello close------------\r\n");
return 0;
}
ssize_t HelloRead(struct file *pFile,char __user *buf,size_t count,loff_t *pOffset)
{
int ret=-1;
printk("into hello read---------------\r\n");
if(count>BUF_SIZE-1)
{
count=BUF_SIZE-1;
}
ret=copy_to_user(buf,g_Buf,count);
if(ret)
{
printk("copy to usr error!\r\n");
ret=-EFAULT;
return ret;
}
printk("copy to user ok!,g_Buf=%s\r\n",g_Buf);
printk("leave hello read-------------\r\n");
return count;
}
struct file_operations stFops= //这就是定义一个操作方法指针
{
.owner=THIS_MODULE, //就像C++ this指针
.open=HelloOpen, //指向HelloOpen函数
.read=HelloRead, //指向HelloRead函数
.release=HelloClose, //关闭
//想用哪些方法就写哪些
};
int HelloModule(void)
{
int ret=0;
printk("into hello Module--------------\r\n");
//1 register device number
ret=alloc_chrdev_region(&g_DevNum,g_min,1,g_DevName); //这是动态注册函数
//取地址设备号 子设备得第一个编号 子设备个数 设备名
if(0!=ret)
{
printk("register char device number error!\r\n");
return ret;
}
printk("1:register char device number ok!\r\n");
printk("major=%d minor=%d\r\n",MAJOR(g_DevNum),MINOR(g_DevNum));
//这两个宏定义就是得到 主设备号和 次设备号
// 2 创建字符设备
g_pCdev=cdev_alloc(); //无入参
if(NULL==g_pCdev) //判断一下
{
printk("alloc char device error!\r\n");
goto ALLOC_CDEV_ERR; //这是干什么 就是申请失败
//那自然那个设备号没啥用
//不是销毁那个接受的指针(本来就是指空)
//销毁的是设备号 跳转到ALLOC_CDEV_ERR
}
printk("2:alloc char device ok!\r\n");
//3 初始化设备
cdev_init(g_pCdev,&stFops); //第二个参数是一个方法指针 无返回值
printk("3:init char device ok!\r\n");
// 4 关联 创建的设备和设备号关联
ret=cdev_add(g_pCdev,g_DevNum,1); //还是用ret 可以明白
if(0!=ret)
{
printk("cdev add to kernel error!\r\n");
goto CDEV_ADD_ERROR; //关联失败 CDEV_ADD_ERROR
}
printk("4:add char device to kernel ok!\r\n");
printk("leave hello Module--------------\r\n");
return 0;
CDEV_ADD_ERROR:
cdev_del(g_pCdev); //关联失败 那创建的设备也没用 删除指针 就是删除那片空间
ALLOC_CDEV_ERR: //
unregister_chrdev_region(g_DevNum,1); //这就用来销毁的
return -1;
}
void HelloExit(void)
{
printk("into hello exit---------------\r\n");
cdev_del(g_pCdev);
//退出时去删除这字符设备 入参是指针
unregister_chrdev_region(g_DevNum,1); //这个注销和静态是一样的
printk("leave hello exit--------------\r\n");
}
module_init(HelloModule);
module_exit(HelloExit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("shixh");
MODULE_ALIAS("hehe");
MODULE_DESCRIPTION("giao");
顺便提下这两个函数:copy_to_user和copy_from_user
我们知道内核的数据用户是不能访问的,但是可以通过内核提供的函数。
内核到用户
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
*to:用户空间指针
*from:数据源--》内核空间指针
n:拷贝的字节数
返回值:成功为0
用户到内核
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
作用:将用户空间数据拷贝到内核空间
*to:内核buf指针
*from:用户空间数据源指针
n:拷贝的字节数
返回值:成功为0
4.测试程序
上面那些做完相当于什么,我们已经成功安装了一个字符设备驱动,剩下的等待一个对应的字符设备接入。所以搞一个测试程序。
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
int main()
{
int fd=open("/dev/nami",O_RDWR);
if(fd<0)
{
printf("open error!\r\n");
return -1;
}
printf("open ok!\r\n");
char buf[BUF_SIZE]={0};
read(fd,buf,BUF_SIZE-1);
printf("read data:%s\r\n",buf);
close(fd);
return 0;
}
5.总结
似乎也没什么是吧。
真正等到工作时也差不多。我贴出来一个平台驱动给大家看看。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
MODULE_LICENSE("GPL v2");
#define SI7006_GET_TEMP _IOR ('S',0x00,short)
#define SI7006_GET_HUM _IOR ('S',0x01,short)
struct Si7006_device{
dev_t devno;
struct cdev cdev;
struct class *cls;
struct device *dev;
struct i2c_client *client;
};
int Si7006_read_temperature(struct Si7006_device *si)
{
int ret;
int temp_code;
//int temperature;
struct i2c_msg msg[2];
unsigned char rx_buf[2];
struct i2c_client *client = si->client;
unsigned char tx_buf[] = {0xE3};
msg[0].addr = 0x40;
msg[0].flags = 0x0;
msg[0].len = sizeof(tx_buf)/sizeof(tx_buf[0]);
msg[0].buf = tx_buf;
msg[1].addr = 0x40;
msg[1].flags = 0x1;
msg[1].len = sizeof(rx_buf)/sizeof(rx_buf[0]);
msg[1].buf = rx_buf;
ret = i2c_transfer(client->adapter,msg,sizeof(msg)/sizeof(msg[0]));
if(ret < 0){
printk("Fail to i2c transfer\n");
return ret;
}
temp_code = rx_buf[0] << 8 | rx_buf[1];
//temperature = (175.72 * temp_code)/ 65536 - 46.85;
return temp_code;
}
int Si7006_read_humidity(struct Si7006_device *si)
{
int ret;
int hum_code;
//int humidity;
struct i2c_msg msg[2];
unsigned char rx_buf[2];
struct i2c_client *client = si->client;
unsigned char tx_buf[] = {0xE5};
msg[0].addr = 0x40;
msg[0].flags = 0x0;
msg[0].len = sizeof(tx_buf)/sizeof(tx_buf[0]);
msg[0].buf = tx_buf;
msg[1].addr = 0x40;
msg[1].flags = 0x1;
msg[1].len = sizeof(rx_buf)/sizeof(rx_buf[0]);
msg[1].buf = rx_buf;
ret = i2c_transfer(client->adapter,msg,sizeof(msg)/sizeof(msg[0]));
if(ret < 0){
printk("Fail to i2c transfer\n");
return ret;
}
hum_code = rx_buf[0] << 8 | rx_buf[1];
//humidity = (125 * hum_code)/ 65536 - 6;
return hum_code;
}
/* This function handles open for the character device */
static int Si7006_chrdev_open(struct inode *inode, struct file *file)
{
struct Si7006_device *si7006 = container_of(inode->i_cdev,struct Si7006_device,cdev);
file->private_data = si7006;
return 0;
}
static long Si7006_chrdev_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
int ret;
unsigned int temp;
unsigned int hum;
unsigned int id;
struct Si7006_device *si = file->private_data;
switch(cmd){
case SI7006_GET_TEMP:
temp = Si7006_read_temperature(si);
ret = copy_to_user((void __user *)arg,&temp,sizeof(temp));
if(ret){
printk("Fail to copy to user\n");
return -EFAULT;
}
break;
case SI7006_GET_HUM:
hum = Si7006_read_humidity(si);
ret = copy_to_user((void __user *)arg,&hum,sizeof(hum));
if(ret){
printk("Fail to copy to user\n");
return -EFAULT;
}
break;
default:
printk("unKnown cmd!\n");
break;
}
return 0;
}
static int Si7006_chrdev_release(struct inode *inode, struct file *file)
{
printk("si7006 chrdev release success!\n");
return 0;
}
/* File operations struct for character device */
static const struct file_operations Si7006_fops = {
.owner = THIS_MODULE,
.open = Si7006_chrdev_open,
.unlocked_ioctl = Si7006_chrdev_ioctl,
.release = Si7006_chrdev_release,
};
int register_Si7006_chrdev(struct Si7006_device *si)
{
int retval;
cdev_init(&si->cdev,&Si7006_fops);
retval = alloc_chrdev_region(&si->devno,0,1,"Si7006");
if(retval < 0){
printk("Fail to alloc chrdev region\n");
goto err_alloc_chrdev_region;
}
retval = cdev_add(&si->cdev,si->devno,1);
if(retval){
printk("Fail to cdev add\n");
goto err_cdev_add;
}
si->cls = class_create(THIS_MODULE, "Si7006");
if (IS_ERR(si->cls)){
printk("Fail to class create\n");
retval = PTR_ERR(si->cls);
goto err_class_create;
}
si->dev = device_create(si->cls,NULL,si->devno,NULL,"Si7006_device");
if (IS_ERR(si->dev)) {
printk("Fail to device create\n");
retval = PTR_ERR(si->dev);
goto err_device_create;
}
return 0;
err_device_create:
class_destroy(si->cls);
err_class_create:
cdev_del(&si->cdev);
err_cdev_add:
unregister_chrdev_region(si->devno,1);
err_alloc_chrdev_region:
return retval;
}
static int Si7006_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
int ret;
struct Si7006_device *si;
printk("Si7006 probe success\n");
si = devm_kmalloc(&client->dev,sizeof(*si),GFP_KERNEL);
if(!si){
printk("Fail to kmalloc\n");
return -ENOMEM;
}
i2c_set_clientdata(client,si);
si->client = client;
ret = register_Si7006_chrdev(si);
if(ret < 0){
printk("Fail to register Si7006 chrdev\n");
return ret;
}
return 0;
}
static int Si7006_remove(struct i2c_client *client)
{
struct Si7006_device *si = i2c_get_clientdata(client);
printk("Si7006 remove success\n");
device_destroy(si->cls,si->devno);
class_destroy(si->cls);
cdev_del(&si->cdev);
unregister_chrdev_region(si->devno,1);
return 0;
}
static struct i2c_device_id Si7006_id[] = {
{ "Si7006", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, Si7006_id);
#ifdef CONFIG_OF
static struct of_device_id dt_match[] = {
{ .compatible = "silabs,si7006", },
{},
};
MODULE_DEVICE_TABLE(of,dt_match);
#endif /* CONFIG_OF */
static struct i2c_driver Si7006_driver = {
.driver = {
.name = "Si7006",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(dt_match),
},
.probe = Si7006_probe,
.remove = Si7006_remove,
.id_table = Si7006_id,
};
module_i2c_driver(Si7006_driver);
祝大家身心愉悦!