从Linux内核LED驱动来理解字符设备驱动开发流程

发布时间:2023-06-07  

开发环境

环境说明 详细信息 备注信息
操作系统 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.2driverschars3c2440_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: %un", cmd);

    printk(DRV_NAME "tRecv arg: %lun", 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

 *

 *  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    /* Every Linux kernel module must include this head */

#include      /* Every Linux kernel module must include this head */

#include    /* printk() */

#include        /* struct fops */

#include     /* error codes */

#include      /* cdev_alloc()  */

#include    /* request_mem_region() */

#include

#include

#include

#include

#include

#include

#include

#include

#include


#include

#include

#include

#include

#include


#define DRV_NAME "s3c2440_leds"

#define DRV_AUTHOR "Alan Tian "

#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: %un", cmd);

    printk(DRV_NAME "tRecv arg: %lun", 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 %lun", 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;

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    发动机制动是什么_发动机制动是什么原理;发动机制动是什么意思?这个问题可能很多车主朋友都说不清楚它的含义,但却知道在什么情况下应该使用发动机制动。但不能正确理解发动机制动的意思,又是......
    ("__def_eth_init")));        可见,实现__def_eth_init的“alias  ”,bash命令里面有个alias,如果你用过就明白这是什么意思了,实现它,就可......
    来看看矢量网络分析仪S11和S21是什么意思。 R&S ZVL3台式矢量网络分析仪 矢量分析仪S11和S21是什么意思? 矢量分析仪测量S参数时,经常会谈到S11、S21等。这些是什么意思? 网络......
    你很绝望。” 有的对话让人想起贾登·史密斯(Jaden Smith): “一无所有是什么意思?” “就像万事俱备。” “万事俱备又是什么意思?” 有时还会阐述阴谋论: “谁是美国总统?” “巴拉克?奥巴......
    MCU 5V tolerant capability是什么意思?;以STM32F030为例,在datasheet中可以看到一句关于IO的表述:Up to 55 I/Os with 5V......
    参数(Scattering Parameters)常被简称为S参数。 二、矢量网络分析仪S11和S21是什么意思 矢量网络分析仪测量S参数时,经常会说到S11、S21等,这些是什么意思呢? 网络......
    FMEA是什么意思?FMEA在SMT行业的应用方法; ......
    情况当然也可以以此类推。后面我们的例程中会经常这样操作寄存器,并且还会涉及更多的编程技能。 思考: P1 &= 0xf2; P1 |= 0x23; P1 ^= 0x8; 这几句是什么意思......
    定时器中断是什么意思,定时器中断的工作原理;定时器中断是由单片机中的定时器溢出而申请的中断。51单片机中有两个定时器T0和T1。STM32中共有11个定时器。 一、定时器中断是什么意思 定时......
    万用表的单位(2023-01-05)
    )——千进制(注:uv不知道是什么意思,不好意思) 常用电流单位:1ka(千安)=1,000a(安培)、1a=1,000ma(毫安)=1,000,000μa(微安)——千进制 常用电阻单位:1mω......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>