天天看點

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

繼續閱讀