從Linux內核LED驅動來理解字符設備驅動開發流程

博客說明

撰寫日期 2018.12.08
完稿日期 2019.10.06
最近維護 暫無
本文作者 multimicro
聯繫方式 [email protected]
GitHub https://github.com/wifialan
本文地址 https://blog.csdn.net/multimicro/article/details/84898135

開發環境

環境說明 詳細信息 備註信息
操作系統 Ubunut 18.04.3 LTS
開發板 S3C2440(JZ2440-V3)
kernel版本 linux-3.4.2 官網地址
busybox版本 busybox-1.22.1 官網地址
編譯器 arm-linux-gcc-4.4.3 下載地址
編譯器路徑 /opt/FriendlyARM/toolschain/4.4.3/bin 絕對路徑

1. Linux字符設備驅動的組成

引自宋寶華《Linux設備驅動開發詳解–基於最新的Linux 4.0內核》P138內容:


在Linux中,字符設備驅動由如下幾個部分組成。
1. 字符設備驅動模塊加載與卸載函數
2. 字符設備驅動的file_operations 結構體中的成員函數


這裏先介紹一下字符設備的開發流程:字符設備驅動是通過設備號與上位機程序連接。而上位機程序對驅動的控制則是通過文件操作,即read、write、ioctl等完成。

  • ps.(對於Linux系統而言,一切皆文件,驅動加載成功後,會在/proc/devices裏面添加驅動節點號信息)

因此一個字符設備驅動應包含1. 設備號的註冊、卸載2. 文件操作兩個功能,註冊的設備號用於提供接口,而文件操作用於對驅動的操作。

字符設備驅動的結構如下圖所示:
在這裏插入圖片描述
對於cdev_init函數中,建立file_operations之間的連接的疑問,看一下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->ops = fops;完成了在cdev中的file_operations的綁定
下面從程序語言角度感性的認識一下設備號的註冊、卸載函數原型,和文件操作函數原型。

1.1 字符設備驅動模塊加載與卸載函數

//加載函數
static int __init xxx_init(void)
{
		... ...
}
//卸載函數
static int __exit xxx_exit(void)
{
		... ...
}

1.2 字符設備驅動的file_operations 結構體中的成員函數

static const struct file_operations xxx_fileops = {
    .owner  = THIS_MODULE,
    .write  = xxx_write,
    .read   = xxx_read,
    .open   = xxx_open,
    .unlocked_ioctl  = xxx_ioctl,
    ... ...
};

static int xxx_open( struct inode *inodes, struct file *filp )
{
		... ...
}
static long xxx_ioctl( struct file  *file, unsigned int cmd, unsigned long arg )
{
		... ...
}
static ssize_t xxx_write( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
{
		... ...
}
static ssize_t xxx_read( struct file *filp, const char __user *buffer, size_t size, loff_t *f_pos )
{
		... ...
}

2. 字符設備驅動——設備號註冊卸載

以我寫的字符設備驅動源代碼爲例,路徑爲linux-3.4.2\drivers\char\s3c2440_leds.c,文章後附有完整的代碼
設備號的註冊由static int __init s3c2440_leds_init(void)完成
設備號的卸載由static int __init s3c2440_leds_exit(void)完成
首先分析設備號的註冊,然後分析卸載

2.1 設備號註冊

設備號分爲主設備號和次設備號,若源代碼中定義了主設備號(次設備號一般爲0),那麼可以直接完成設備號的註冊,其流程爲
在這裏插入圖片描述
註冊成功後,可通過cat /proc/devices命令查看設備號
在這裏插入圖片描述

2.2 設備號註銷

相比設備號的註冊,註銷流程就十分簡單:
在這裏插入圖片描述

3. 字符設備驅動——文件操作

上位機程序首先要調用open函數打開此驅動,具體方法就是,打開該設備號對應的文件,一般而言,該設備號文件在/dev/文件夾下,驅動在內核中註冊成功後會在/proc/devices中包含設備號信息,但/dev/文件夾內並沒有創建該設備號對應的文件,因此需要手動創建該設備號文件,命令爲:

mknod /dev/leds c 230 0

表示在/dev文件夾下創建名爲leds的字符設備文件,其主設備號爲230,次設備號爲0。
字符設備文件名可以另取,但設備號一定要對應/proc/devices裏面的設備號。
在這裏插入圖片描述
在這裏插入圖片描述

然後通過fd = open("/dev/leds",0);完成設備驅動的打開

當上位機程序通過調用open函數打開(鏈接上)相應的驅動程序後,open函數會返回一個文件描述符暫且記爲fd,然後對該驅動的read、write、ioctl等操作都可以通過使用fd完成。簡單的字符設備驅動程序大多采用ioctl函數控制驅動程序,而這個ioctl函數本身也不難,其實現爲:

static long s3c2440_leds_ioctl( struct file  *file, unsigned int cmd, unsigned long arg )

函數中
第一個參數:表示要操作的文件描述符
第二個參數:表示傳遞的命令字
第三個參數:表示傳遞的變量字
第二個參數和第三個參數的含義沒有硬性規定,傳遞的參數符合對應的關鍵字限定類型即可

下面的給出示例參考

static long s3c2440_leds_ioctl( struct file  *file, unsigned int cmd, unsigned long arg )
{
    printk(DRV_NAME "\tRecv cmd: %u\n", cmd);
    printk(DRV_NAME "\tRecv arg: %lu\n", arg);
    //IO operations function.
    if(arg > 4) {
        return -EINVAL;
    }

    switch (cmd) {
        case IOCTL_LED_ON:			//#define IOCTL_LED_ON 1
            s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 0);//Set pin
            printk("Open LED %lu ",arg);
        return 0;

        case IOCTL_LED_OFF:			//#define IOCTL_LED_OFF 0
            s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 1);
            printk("Close LED %lu ",arg);
        return 0;

        default:
            return -EINVAL;
    }
}

參考資料

1. 宋寶華《Linux設備驅動開發詳解–基於最新的Linux 4.0內核》 第6章 字符設備驅動
2. Jonathan Corbet《linux設備驅動程序第三版》 P50-P51

示例代碼


/*
 *  Driver for S3C2440 base board.
 *
 *  Copyright (C) 2019  Alan NWPU <[email protected]>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *  MULTIBEANS, NPU Youyi West Ave, Beilin District, Xi'an, China.
 */

#include <linux/module.h>   /* Every Linux kernel module must include this head */
#include <linux/init.h>     /* Every Linux kernel module must include this head */
#include <linux/kernel.h>   /* printk() */
#include <linux/fs.h>       /* struct fops */
#include <linux/errno.h>    /* error codes */
#include <linux/cdev.h>     /* cdev_alloc()  */
#include <linux/ioport.h>   /* request_mem_region() */
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#include <asm/irq.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <mach/hardware.h>
#include <plat/gpio-cfg.h>

#define DRV_NAME "s3c2440_leds"
#define DRV_AUTHOR "Alan Tian <[email protected]>"
#define DRV_DESC "S3C2440 LED Pin Driver"

#define S3C2440_LED_SIZE 0x1000

#define S3C2440_LED_MAJOR 230
#define S3C2440_LED_MINOR 0

static int major = S3C2440_LED_MAJOR;
static int minor = S3C2440_LED_MINOR;

/* 應用程序執行ioctl(fd, cmd, arg)時的第2個參數 */
#define IOCTL_LED_ON    0
#define IOCTL_LED_OFF   1

static int s3c2440_leds_open( struct inode *inodes, struct file *filp );
static long s3c2440_leds_ioctl( struct file  *file, unsigned int cmd, unsigned long arg );
static ssize_t s3c2440_leds_write( struct file *filp, const char __user *buffer, \
                                     size_t size, loff_t *f_pos );
static ssize_t s3c2440_leds_read( struct file *filp, const char __user *buffer, \
                                     size_t size, loff_t *f_pos );

struct s3c2440_leds_dev_t
{
    struct cdev cdev;
    unsigned char mem[S3C2440_LED_SIZE];
} *s3c2440_leds_dev;

//Step 2: Add file operations
static const struct file_operations s3c2440_leds_fileops = {
    .owner  = THIS_MODULE,
    .write  = s3c2440_leds_write,
    .read   = s3c2440_leds_read,
    .open   = s3c2440_leds_open,
    .unlocked_ioctl  = s3c2440_leds_ioctl,
};

static int s3c2440_leds_open( struct inode *inodes, struct file *filp )
{
    //int ret;

    filp->private_data = s3c2440_leds_dev;
    printk(DRV_NAME"\tS3C2440 open function...\n");

    return 0;
}

static long s3c2440_leds_ioctl( struct file  *file, unsigned int cmd, unsigned long arg )
{
    printk(DRV_NAME "\tRecv cmd: %u\n", cmd);
    printk(DRV_NAME "\tRecv arg: %lu\n", arg);
    //IO operations function.
    if(arg > 4) {
        return -EINVAL;
    }

    switch (cmd) {
        case IOCTL_LED_ON:
            s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 0);//Set pin
            printk("Open LED %lu ",arg);
        return 0;

        case IOCTL_LED_OFF:
            s3c2410_gpio_setpin(S3C2410_GPF(arg+3), 1);
            printk("Close LED %lu ",arg);
        return 0;

        default:
            return -EINVAL;
    }
}


static ssize_t s3c2440_leds_write( struct file *filp, const char __user *buffer, \
                                     size_t size, loff_t *f_pos )
{
    unsigned long p = *f_pos;
    unsigned int count = size;
    int ret = 0;
    struct s3c2440_leds_dev_t *dev = filp->private_data;

    if(p >= S3C2440_LED_SIZE)
        return 0;
    if(count > S3C2440_LED_SIZE - p)
        count = S3C2440_LED_SIZE - p;

    memset(dev->mem, 0, S3C2440_LED_SIZE);

    if(copy_from_user(dev->mem + p, buffer, count)) {
        ret = -EFAULT;
    } else {
        *f_pos += count;
        ret = count;
        printk(KERN_INFO "writter %u bytes(s) from %lu\n", count, p);
    }

    return ret;
    
}

static ssize_t s3c2440_leds_read( struct file *filp, const char __user *buffer, \
                                     size_t size, loff_t *f_pos )
{
    unsigned long p = *f_pos;
    unsigned int count = size;
    int ret = 0;
    struct s3c2440_leds_dev_t *dev = filp->private_data;
    
    if(p >= S3C2440_LED_SIZE)
        return 0;
    if(count > S3C2440_LED_SIZE - p)
        count = S3C2440_LED_SIZE - p;
    if(copy_to_user(buffer, dev->mem + p, count)) {
        ret = -EFAULT;
    } else {
        *f_pos += count;
        ret = count;
        printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
    }

    return ret;
}

static int __init s3c2440_leds_init(void)
{
    int ret,err;
    dev_t devid;
    
    if(major) {
        devid = MKDEV(major, 0);
        ret = register_chrdev_region(devid, 1, DRV_NAME);
        printk("Origin Creat node %d\n",major);
    } else {
        ret = alloc_chrdev_region(&devid, 0, 1, DRV_NAME);
        major = MAJOR(devid);
        printk("Arrage1 Creat node %d\n",major);
    }
    if(ret < 0) {
        printk(DRV_NAME "\ts3c2440 new device failed\n");
        //goto fail_malloc;
        return ret;
    }
    s3c2440_leds_dev = kzalloc(sizeof(struct s3c2440_leds_dev_t), GFP_KERNEL);
    if(!s3c2440_leds_dev) {
        ret = -ENOMEM;
        goto fail_malloc;
    }
    printk("success init leds\n");
    
    cdev_init(&s3c2440_leds_dev->cdev, &s3c2440_leds_fileops);
    err = cdev_add(&s3c2440_leds_dev->cdev, devid, 1);
    if(err)
        printk(KERN_NOTICE "Error %d adding s2c2440_leds %d",err, 1);
    return 0;
    
fail_malloc:
    unregister_chrdev_region(devid, 1);
    return ret;
}

static void __exit s3c2440_leds_exit(void)
{
    printk("Starting delet node %d\n",major);
    cdev_del(&s3c2440_leds_dev->cdev);
    kfree(s3c2440_leds_dev);
    unregister_chrdev_region(MKDEV(major, minor), 1);
    printk("Delete node %d\n",major);
}

module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);

MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章