linux内核led驱动开发,从Linux内核LED驱动来理解字符设备驱动开发流程
目錄
博客說明
開發(fā)環(huán)境
1. Linux字符設(shè)備驅(qū)動的組成
1.1 字符設(shè)備驅(qū)動模塊加載與卸載函數(shù)
1.2 字符設(shè)備驅(qū)動的file_operations 結(jié)構(gòu)體中的成員函數(shù)
2. 字符設(shè)備驅(qū)動——設(shè)備號注冊卸載
2.1 設(shè)備號注冊
2.2 設(shè)備號注銷
3. 字符設(shè)備驅(qū)動——文件操作
參考資料
示例代碼
@(從Linux內(nèi)核LED驅(qū)動來理解字符設(shè)備驅(qū)動開發(fā)流程)
博客說明
撰寫日期
2018.12.08
完稿日期
2019.10.06
最近維護(hù)
暫無
本文作者
multimicro
聯(lián)系方式
[email?protected]
GitHub
https://github.com/wifialan
本文地址
https://blog.csdn.net/multimicro/article/details/84898135
開發(fā)環(huán)境
環(huán)境說明
詳細(xì)信息
備注信息
操作系統(tǒng)
Ubunut 18.04.3 LTS
開發(fā)板
S3C2440(JZ2440-V3)
kernel版本
Linux-3.4.2
官網(wǎng)地址
busybox版本
busybox-1.22.1
官網(wǎng)地址
編譯器
arm-Linux-gcc-4.4.3
下載地址
編譯器路徑
/opt/FriendlyARM/toolschain/4.4.3/bin
絕對路徑
1. Linux字符設(shè)備驅(qū)動的組成
引自宋寶華《Linux設(shè)備驅(qū)動開發(fā)詳解--基于最新的Linux 4.0內(nèi)核》P138內(nèi)容:
在Linux中,字符設(shè)備驅(qū)動由如下幾個部分組成。
1. 字符設(shè)備驅(qū)動模塊加載與卸載函數(shù)
2. 字符設(shè)備驅(qū)動的file_operations 結(jié)構(gòu)體中的成員函數(shù)
這里先介紹一下字符設(shè)備的開發(fā)流程:字符設(shè)備驅(qū)動是通過設(shè)備號 與上位機程序連接。而上位機程序?qū)︱?qū)動的控制則是通過文件操作,即read、write、ioctl等完成。
ps.(對于linux系統(tǒng)而言,一切皆文件,驅(qū)動加載成功后,會在/proc/devices里面添加驅(qū)動節(jié)點號信息)
因此一個字符設(shè)備驅(qū)動應(yīng)包含1. 設(shè)備號的注冊、卸載和2. 文件操作兩個功能,注冊的設(shè)備號用于提供接口,而文件操作用于對驅(qū)動的操作。
字符設(shè)備驅(qū)動的結(jié)構(gòu)如下圖所示:
對于cdev_init函數(shù)中,建立file_operations之間的連接的疑問,看一下cdev_init的實現(xiàn)
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的綁定
下面從程序語言角度感性的認(rèn)識一下設(shè)備號的注冊、卸載函數(shù)原型,和文件操作函數(shù)原型。
1.1 字符設(shè)備驅(qū)動模塊加載與卸載函數(shù)
//加載函數(shù)
static int __init xxx_init(void)
{
... ...
}
//卸載函數(shù)
static int __exit xxx_exit(void)
{
... ...
}
1.2 字符設(shè)備驅(qū)動的file_operations 結(jié)構(gòu)體中的成員函數(shù)
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. 字符設(shè)備驅(qū)動——設(shè)備號注冊卸載
以我寫的字符設(shè)備驅(qū)動源代碼為例,路徑為Linux-3.4.2\drivers\char\s3c2440_leds.c,文章后附有完整的代碼
設(shè)備號的注冊由static int __init s3c2440_leds_init(void)完成
設(shè)備號的卸載由static int __init s3c2440_leds_exit(void)完成
首先分析設(shè)備號的注冊,然后分析卸載
2.1 設(shè)備號注冊
設(shè)備號分為主設(shè)備號和次設(shè)備號,若源代碼中定義了主設(shè)備號(次設(shè)備號一般為0),那么可以直接完成設(shè)備號的注冊,其流程為
注冊成功后,可通過cat /proc/devices命令查看設(shè)備號
2.2 設(shè)備號注銷
相比設(shè)備號的注冊,注銷流程就十分簡單:
3. 字符設(shè)備驅(qū)動——文件操作
當(dāng)上位機程序通過調(diào)用open函數(shù)打開(鏈接上)相應(yīng)的驅(qū)動程序后,open函數(shù)會返回一個==文件描述符==暫且記為fd,然后對該驅(qū)動的read、write、ioctl等操作都可以通過使用fd完成。簡單的字符設(shè)備驅(qū)動程序大多采用ioctl函數(shù)控制驅(qū)動程序,而這個ioctl函數(shù)本身也不難,其實現(xiàn)為:
static long s3c2440_leds_ioctl( struct file *file, unsigned int cmd, unsigned long arg )
函數(shù)中
第一個參數(shù):表示要操作的文件描述符
第二個參數(shù):表示傳遞的命令字
第三個參數(shù):表示傳遞的變量字
第二個參數(shù)和第三個參數(shù)的含義沒有硬性規(guī)定,傳遞的參數(shù)符合對應(yīng)的關(guān)鍵字限定類型即可
下面的給出示例參考
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設(shè)備驅(qū)動開發(fā)詳解–基于最新的Linux 4.0內(nèi)核》 第6章 字符設(shè)備驅(qū)動
示例代碼
/*
* 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;
/* 應(yīng)用程序執(zhí)行ioctl(fd, cmd, arg)時的第2個參數(shù) */
#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");
總結(jié)
以上是生活随笔為你收集整理的linux内核led驱动开发,从Linux内核LED驱动来理解字符设备驱动开发流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: CCD与CMOS摄像头哪个好
- 下一篇: SwitchyOmega使用教程
