linux 设备 major 253,ARM+LINUX字符型设备驱动编写框架

本文详细介绍了在ARM+LINUX环境下如何编写字符设备驱动程序,包括关键函数如module_init(), module_exit(), register_chrdev()等,以蜂鸣器和LED驱动为例,展示了从设备名设置、主设备号分配到驱动程序注册的完整流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ARM+LINUX字符型设备驱动编写框架

最近在ARM+LINUX嵌入式环境下学习字符型设备的驱动程序的编写,现整理如下:

字符型设备的编写其实还是有规律可循的,我自己总结了一个框图。

驱动程序框架:

0c789d34-4d9c-4374-8bb7-45552e3e5852.JPG

驱动程序里最重要的就是

module_init(XXX_init);和module_exit(XXX_exit);

第一句话是告诉内核编写的这段程序是驱动程序,也就是将驱动程序以模块的形式注册进内核。后一句就是用于将驱动程序卸载掉。

接着就是编写static int __init XXX_init(void)函数,其中最重要的就是注册字符型设备,给它分配一个主设备号ret=register_chrdev(XXX_MAJOR,DEVICE_NAME,&XXX_fops);这个函数的第一个参数是注册的主设备号,可以自己指定,我这里就是采用外部宏定义的方式,也可以让系统自动分配,将该参数写0即可。分配的主设备号范围是0~254.第二个参数是自己指定的设备名,也就是将驱动加入内核后添加在/dev下的设备名,这个名字很重要,应用程序在调用open()函数时也要用到这个设备名。第三个就是一个结构体指针,我们的任务就是要丰满它。

static struct file operations XXX_fops =

{

.ioctl       =     XXX_ioctl,

};

实际上file operations这个结构体的内容非常多如下,但是使用时并不用全部使用。

struct file_operations

{   struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

int (*readdir) (struct file *, void *, filldir_t);

unsigned int (*poll) (struct file *, struct poll_table_struct *);

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, struct dentry *, int datasync);

int (*aio_fsync) (struct kiocb *, int datasync);

int (*fasync) (int, struct file *, int); ...

};

可以看到里面主要的一项就是.ioctl   =     XXX_ioctl,我们对硬件的一些操作就是在XXX_ioctl这个函数里面来实现的。

这些就是一个字符型设备驱动的主要内容,把以上这些函数的丰满一下,就可以组成一个完整的驱动程序。下面举例说明。

这是一个蜂鸣器的驱动。开发板是友善之臂的mini2440。

下图是蜂鸣器部分的局部电路图,可以看到只要让GPB0口输出1就可以让蜂鸣器想起来。

b8010923-a682-4629-98bf-6d6c6cd86586.JPG

蜂鸣器的驱动程序:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define DEVICE_NAME     "beep"  //设备名beep

#define BEEP_MAJOR 253      //主设备号253

static int qq2440_beep_ioctl(

struct inode *inode,

struct file *file,

unsigned int cmd,

unsigned long arg)

{

s3c2410_gpio_setpin(S3C2410_GPB0, cmd);

}

static struct file_operations qq2440_beep_fops = {

.owner    =     THIS_MODULE,

.ioctl       =     qq2440_beep_ioctl,

};

static int __init qq2440_beep_module_init(void)

{

int ret;

int i;

ret = register_chrdev(BEEP_MAJOR, DEVICE_NAME, &qq2440_beep_fops);

if (ret < 0) {

printk(DEVICE_NAME " can't register major number\n");

return ret;

}

devfs_mk_cdev(MKDEV(BEEP_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);

s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);

s3c2410_gpio_setpin(S3C2410_GPB0, 0);

printk(DEVICE_NAME " initialized\n");

return 0;

}

static void __exit qq2440_beep_module_exit(void)

{

devfs_remove(DEVICE_NAME);

unregister_chrdev(BEEP_MAJOR, DEVICE_NAME);

printk(DEVICE_NAME " removed\n");

}

module_init(qq2440_beep_module_init);

module_exit(qq2440_beep_module_exit);

可以看到这里只是将我之前框图的函数丰满了一下。

1. module_init(qq2440_beep_module_init);

module_exit(qq2440_beep_module_exit);

2. static int __init qq2440_beep_module_init(void)

注册设备,初始化硬件。

3. static void __exit qq2440_beep_module_exit(void)

卸载设备的函数。

4.static struct file_operations qq2440_beep_fops =

编写file operations结构体。

5. tatic int qq2440_beep_ioctl

丰满ioctl函数。

注意一点,我的设备名是beep。

接着我们就可以把该驱动程序放在kernel-2.6.23/drivers/char的目录下。并打开Kconfig文件,添加

Config QQ2440_BEEP_MODULE  //驱动程序文件名

tristate”QQ2440_BEEP_MODULE”

depends on ARCH_S3C2410

help

qq2440_beep_module test

这样在内核根目录下make menuconfig后就可以把QQ2440_BEEP_MODULE显示在菜单上。把它前面的状态改为【M】,我们以模块的形式加载。

接着我们还需要修改Makefile文件。在里面添加

obj-$(CONFIG_QQ2440_BEEP_MODULE)+=qq2440_beep_module.o

然后我们在内核根目录下make modules时,就可以生成我们所需的qq2440_beep_module.ko文件了。把它放到开发板上,通过insmod加载,rmmod卸载。

下面是我的测试程序:

#include

#include

#include

#include

int main(int argc, char **argv)

{

int fd;

fd = open("/dev/beep", 0); //可以看到,open函数里调用了设备名,这也是驱动程序和

//应用程序的连接点

if (fd < 0) {

perror("cannt open device beep");

exit(1);

}

ioctl(fd,1,0);   //这里就是我们编写的ioctl函数的具体实现

close(fd);

return 0;

}

另外,再附上LED的驱动程序和测试程序,用于比较。

驱动程序:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define DEVICE_NAME     "leds"

#define LED_MAJOR 231

static unsigned long led_table [] = {

S3C2410_GPB5,

S3C2410_GPB6,

S3C2410_GPB7,

S3C2410_GPB8,

};

static unsigned int led_cfg_table [] = {

S3C2410_GPB5_OUTP,

S3C2410_GPB6_OUTP,

S3C2410_GPB7_OUTP,

S3C2410_GPB8_OUTP,

};

static int qq2440_leds_ioctl(

struct inode *inode,

struct file *file,

unsigned int cmd,

unsigned long arg)

{

switch(cmd) {

case 0:

case 1:

if (arg > 4) {

return -EINVAL;

}

s3c2410_gpio_setpin(led_table[arg], !cmd);

return 0;

default:

return -EINVAL;

}

}

static struct file_operations qq2440_leds_fops = {

.owner    =     THIS_MODULE,

.ioctl       =     qq2440_leds_ioctl,

};

static int __init qq2440_leds_init(void)

{

int ret;

int i;

ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &qq2440_leds_fops);

if (ret < 0) {

printk(DEVICE_NAME " can't register major number\n");

return ret;

}

devfs_mk_cdev(MKDEV(LED_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);

for (i = 0; i < 4; i++) {

s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);

s3c2410_gpio_setpin(led_table[i], 1);

}

printk(DEVICE_NAME " initialized\n");

return 0;

}

static void __exit qq2440_leds_exit(void)

{

devfs_remove(DEVICE_NAME);

unregister_chrdev(LED_MAJOR, DEVICE_NAME);

}

module_init(qq2440_leds_init);

module_exit(qq2440_leds_exit);

测试程序:

#include

#include

#include

#include

int main(int argc, char **argv)

{

int on;

int led_no;

int fd;

if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||

on < 0 || on > 1 || led_no < 0 || led_no > 3) {

fprintf(stderr, "Usage: leds led_no 0|1\n");

exit(1);

}

fd = open("/dev/leds0", 0);

if (fd < 0) {

perror("open device leds");

exit(1);

}

ioctl(fd, on, led_no);

close(fd);

return 0;

}

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值