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

目录

  • 博客说明
  • 开发环境
  • 1. Linux字符设备驱动的组成
    • 1.1 字符设备驱动模块加载与卸载函数
    • 1.2 字符设备驱动的file_operations 结构体中的成员函数
  • 2. 字符设备驱动——设备号注册卸载
    • 2.1 设备号注册
    • 2.2 设备号注销
  • 3. 字符设备驱动——文件操作
  • 参考资料
  • 示例代码

@(从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");

原文地址:https://www.cnblogs.com/multimicro/p/11625821.html

时间: 2024-09-30 02:05:31

从Linux内核LED驱动来理解字符设备驱动开发流程的相关文章

Linux内核分析(五)----字符设备驱动实现

原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷,我们都会以虚拟的设备为例进行学习,所以大家不必害怕没有硬件的问题. 今天我们会分析到以下内容: 1.      字符设备驱动基础 2.      简单字符设备驱动实现 3.      驱动测试 l  字符设备基础 1.       字符设备描述结构 在linux2.6内核中,使用cdev结构体描述一

Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质

原文:Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质 Linux内核分析(六) 昨天我们对字符设备进行了初步的了解,并且实现了简单的字符设备驱动,今天我们继续对字符设备的某些方法进行完善. 今天我们会分析到以下内容: 1.      字符设备控制方法实现 2.      揭秘系统调用本质 在昨天我们实现的字符设备中有open.read.write等方法,由于这些方法我们在以前编写应用程序的时候,相信大家已经有所涉及所以就没单独列出来分析,今天我们主要来分析一下我们以前接触

LCD驱动分析(一)字符设备驱动框架分析

LCD驱动也是字符设备驱动,也遵循字符设备驱动的流程: a. 分配主设备号 b. 构建file_operations结构体中的open,write,read...等函数 c. 调用register_chrdev()函数注册字符设备 d. 调用class_register()注册类 e. 调用device_create()创建设备,linux会在sysfs目录下自动创建字符设备. 以上的步骤同样适用于分析输入子系统,只不过上面的各个步骤可能分散在不同的文件与函数中完成. 1.linux/drive

linux内核cdev_init系列函数(字符设备的注册)

内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义: linux-2.6.22/include/linux/cdev.h struct cdev {    struct kobject kobj;          // 每个 cdev 都是一个 kobject    struct module *owner;       // 指向实现驱动的模块    const struct file_operations *ops;   // 操纵这个字符设备文件的方法    struct

linux驱动学习(1)——字符设备驱动开发

(一)驱动程序介绍 (a)Linux驱动程序学习 知识结构: 1. Linux驱动程序设计模式(40%) 2. 内核相关知识(30%) 3. 硬件相关知识(30%) (b)驱动分类: ①字符设备: 字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现 open, close,read和 write 系统调用. ②块设备: 在大部分的 Unix 系统, 块设备不能按字节处理数据,只能一次传送一个或多个长度是512字节( 或一个更大的 2 次幂的数 )的整块数据,而Lin

驱动学习之字符设备驱动的原理

1:嵌入式系统的整体工作原理 应用层->API->设备驱动->硬件 比如,在应用层,现在使用read函数去读取一个设备文件,这个read函数是属于应用层的,它不能直接读取设备文件,而是通过内核层的函数(其实就是和file_operations结构体中read这个函数指针相绑定的函数,这个函数才是真正操作硬件的函数)来实现读取文件, 2:file_operations结构体 (1)这个结构体里面存放的是一个驱动里面操作文件的各种函数指针,比如,现有一个驱动,它可以打开一个文件,那么这个驱动

linux4.10.8 内核移植(四)---字符设备驱动_led驱动程序

一.字符设备驱动程序介绍 app里面用 open.read.write等等函数出来操作底层硬件.驱动程序中也有对应的xxx_open等函数.怎么找到驱动程序中的函数依赖于驱动程序框架. 二.搭建驱动程序框架 2.1 初步框架 2.1.1 Makefile 2.1.2 jz2440_led.c 1 #include <linux/module.h> 2 #include <linux/of.h> 3 #include <linux/of_device.h> 4 #incl

Linux 设备驱动--- 阻塞型字符设备驱动 --- O_NONBLOCK --- 非阻塞标志【转】

转自:http://blog.csdn.net/yikai2009/article/details/8653697 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 阻塞 阻塞操作 非阻塞操作 阻塞方式-read- 实现 阻塞方式-write- 实现 非阻塞方式的读写操作 实例 --- 读阻塞的实现 实例 --- 按键驱动阻塞实现 1在 open 函数 查看看是 阻塞方式 还是 非阻塞方式 2在 read 函数中同样查看 3应用程序中 1以阻塞方式运行 2以非阻塞方式运行

linux 3.0.35下globalmem 字符设备驱动实现

1.Makefile KDIR=/home/xxx/s-linux-3.0.35 PWD:=$(shell pwd) # kernel modules obj-m := globalmem.o modules: make -C $(KDIR) M=$(PWD) modules clean: rm -rf *.o *.ko *.mod.c *.markesr *.order *.symvers .PHONY:modules clean 2.globalmem.c #include <linux/m