天天看点

在LINUX中实现一个字符设备如何把字符设备抽象成文件?

如何把字符设备抽象成文件?

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");      

继续阅读