如何把字符设备抽象成文件?
open()函数,在文件系统中找到指定文件的操作接口,绑定到进程
task_srtuct->files_struct->fd_array[]->file_operations
思路
把底层寄存器配置操作放在文件操作接口里面,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件来设置底层寄存器
基本接口实现
- 查原理图,数据手册,确定底层需要配置的寄存器
- 类似裸机开发
- 实现一个文件的底层操作接口,这是文件的基本特征
struct file_operations //fops
/include/linux/fs.h
几乎所有成员都是函数指针,用来实现文件的具体操作
驱动层原理
把file_operations文件操作接口注册到内核,内核通过主次设备号来登记记录它
- 构造驱动基本对象:struct cdev,里面记录具体的file_operations
cdev_init()
- 两个hash表
- chrdevs:登记设备号
__register_chrdev_region()
- cdev_map->probe:保存驱动基本对象struct cdev
cdev_add()
文件系统层原理
mknod指令+主从设备号
- 构建一个新的设备文件
- 通过主次设备号在cdev_map中找到cdev->file_operations
- 把cdev->file_operations绑定到新的设备文件中
到这里,应用程序就可以使用open()、write()、read()等函数来控制设备文件了
/*
* RTC client/driver for the Maxim/Dallas SD2405 Real-Time Clock over I2C
*
* Based on code by Randy Vinson <[email protected]>,
* which was based on the m41t00.c by Mark Greer <[email protected]>.
*
* Copyright (C) 2014 Rose Technology
* Copyright (C) 2006-2007 Freescale Semiconductor
*
* 2005 (c) MontaVista Software, Inc. This file is licensed under
* the terms of the GNU General Public License version 2. This program
* is licensed "as is" without any warranty of any kind, whether express
* or implied.
*/
/*
* It would be more efficient to use i2c msgs/i2c_transfer directly but, as
* recommened in .../Documentation/i2c/writing-clients section
* "Sending and receiving", using SMBus level communication is preferred.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/pm.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/reboot.h>
#include <linux/watchdog.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/kthread.h>
#define DEBUG
#include <linux/device.h>
#undef DEBUG
#include <linux/proc_fs.h>
#include <linux/delay.h>
#define DEV_NAME "test_char_dev"
#define DEV_CNT (1)
#define BUFF_SIZE 128
//设备类
struct class* chr_test_class = NULL;
//设备节点
struct device* chr_test_device = NULL;
//数据缓冲区
static char vbuf[BUFF_SIZE] = {0};
static int chr_dev_open(struct inode *inode, struct file *filp)
{
printk("OPEN!!!\n");
printk("f_pos:%d\r\n",filp->f_pos);
return 0;
}
static int chr_dev_release(struct inode *inode, struct file *filp)
{
mdelay(2000);
printk("RELEASE!!\n");
return 0;
}
ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
printk("write!!\r\n");
unsigned long p = *ppos;
int ret;
int tmp = count;
if (p > BUFF_SIZE)
return 0;
if (tmp > BUFF_SIZE - p)
tmp = BUFF_SIZE - p;
ret = copy_from_user(vbuf, buf, tmp);
*ppos += tmp;
printk("now buf:%s tmp:%d f_pos:%d\n",vbuf,tmp,filp->f_pos);
return tmp;
}
ssize_t chr_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
printk("read!!\r\n");
unsigned long p = *ppos;
int ret;
int tmp = count;
if (p > BUFF_SIZE)
return 0;
if (tmp > BUFF_SIZE - p)
tmp = BUFF_SIZE - p;
ret = copy_to_user(buf, vbuf + p, tmp);
*ppos += tmp;
printk("now buf:%s p:%d\n",vbuf,p);
return tmp;
}
loff_t chr_dev_llseek(struct file *filp, loff_t off, int whence)
{
struct device* dev = filp->private_data;
loff_t newpos;
printk("whence:%d off:%d\r\n",whence,off);
switch (whence)
{
case SEEK_SET:
newpos = off;
break;
case SEEK_CUR:
newpos = filp->f_pos + off;
break;
case SEEK_END:
newpos = BUFF_SIZE + off;
break;
default:
return -EINVAL;
}
if(newpos < 0)
{
return -EINVAL;
}
filp->f_pos = newpos;
return newpos;
}
static struct file_operations chr_dev_fops = {
.owner = THIS_MODULE,
.open = chr_dev_open,
.release = chr_dev_release,
.write = chr_dev_write,
.read = chr_dev_read,
.llseek = chr_dev_llseek,
};
//定义设备号
static dev_t devno;
//定义字符设备结构体 char_dev
static struct cdev chr_dev;
static int __init chrdev_init(void)
{
int ret = 0;
printk("chrdev_init\n");
//第一步
//采用动态分配的方法,获取设备编号,次设备号为0
//设备名称为 test_char_dev ,可通过命令 cat /proc/devices查看
//DEV_CNT为1,当前只申请一个设备编号
ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0)
{
printk("fail to alloc devno\n");
goto alloc_err;
}
//第二步
//关联字符设备结构体cdev与文件操作结构体file_operations
cdev_init(&chr_dev, &chr_dev_fops);
//第三步
//添加设备至cdev_map散列表
ret = cdev_add(&chr_dev, devno, DEV_CNT);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
//创建设备类
if(chr_test_class == NULL)
chr_test_class = class_create(THIS_MODULE, DEV_NAME);
//创建设备节点
chr_test_device = device_create(chr_test_class, NULL, devno, NULL, DEV_NAME);
return 0;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(devno, DEV_CNT);
alloc_err:
return ret;
}
module_init(chrdev_init);
static void __exit chrdev_exit(void)
{
printk("chrdev exit\n");
unregister_chrdev_region(devno, DEV_CNT);
cdev_del(&chr_dev);
//删除设备节点
device_destroy(chr_test_class, devno);
//销毁设备类
class_destroy(chr_test_class);
}
module_exit(chrdev_exit);
static const struct of_device_id chardev_test_dt_ids[] = {
{ .compatible = "me, chardev_test" },
{}
};
MODULE_DEVICE_TABLE(of, chardev_test_dt_ids);
MODULE_AUTHOR("reisen");
MODULE_LICENSE("GPL");