欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux驱动开发-简单例子

程序员文章站 2022-06-03 22:57:12
...

1、软件系统分为:应用程序、库、操作系统(内核)、驱动程序,开发人员专注某一层,了解邻层的接口。如,应用程序调用库函数open,库根据open传入的参数执行swi指令引起CPU异常进入内核。内核的异常处理函数根据参数找到相应驱动程序。内核与驱动程序没有界限,因为驱动程序最终是要编进内核。驱动程序从不主动运行。在有MMU的系统中,应用程序处于用户空间,驱动程序处于内核空间。

2、Linux外设分为:字符设备(读写以字节方式进行)、块设备(数据读写以块方式,数据有格式)、网络接口(数据读写是大小不固定的块)。

3、Linux设备驱动程序开发步骤
(1)初始化驱动程序,如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序。
(2)设计要实现的操作函数,如open等。
(3)实现中断服务
(4)编译驱动程序到内核,如果动态编译为模块,则用insmod rmmod命令进行加载和卸载。加载:调用模块的初始化函数,向内核注册驱动程序。卸载:调用模块清除函数。
(5)测试

4、应用程序使用统一的接口函数(系统调用)调用硬件启动程序。字符设备驱动程序的函数集合在 include/linux/fs.h 的file_operations 结构中

//为驱动函数规定统一以文件操作的接口,如open、read ....
//当应用程序使用open函数打开某个设备,就会调用 file_operations 中的open函数。从这个角度,编写字符设备驱动程序就是为具体硬件的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);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*dir_notify)(struct file *filp, unsigned long arg);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};

5、安装驱动程序时调用初始化函数,把驱动出现的file_operations 结构和主设备号向内核注册。内核为字符设备保存一个数组,主设备号为数组标号。加载即在对于标号填入对应设备file_operations 地址,卸载相反。

//对于字符设备使用如下函数进行注册
int register_chrdev(unsigned int major, consr char *name, struct file_operations *fops);

//之后当应用程序操作设备时,Linux系统就会根据设备文件类型、主设备号找到file_operations 

简单的驱动程序编写(不涉及中断、select机制、fasync异步通知机制)
(1)编写驱动初始化函数
(2)构造file_operations 中成员函数

6、LED驱动程序分析
(1)初始化,指定加载和卸载函数

//执行 insmod s3c24xx_leds 就会调用该函数
static int __init s3c24xx_leds_init(void)
{
    int ret;

    /* 注册字符设备驱动程序
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0,表示由内核自动分配主设备号
     */
    ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }

    printk(DEVICE_NAME " initialized\n");
    return 0;
}

 // 执行”rmmod s3c24xx_leds.ko”命令时就会调用这个函数 
static void __exit s3c24xx_leds_exit(void)
{
    /* 卸载驱动程序 */
    unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
//要是不适用这2行就要把加载和卸载函数名改为init_module和cleanup_module
module_init(s3c24xx_leds_init);
module_exit(s3c24xx_leds_exit);

(2)file_operations 结构

static struct file_operations s3c24xx_leds_fops = 
{
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   s3c24xx_leds_open,     
    .ioctl  =   s3c24xx_leds_ioctl,
};

(3)应用程序对设备文件/dev/leds执行open和ioclt时会调用的函数

// 应用程序对设备文件/dev/leds执行open(...)时,就会调用s3c24xx_leds_open函数
 //open即设置LED引脚为输出
static int s3c24xx_leds_open(struct inode *inode, struct file *file)
{
    int i; 
    for (i = 0; i < 4; i++) 
    {
        // 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能,该函数在内核中实现
        s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
    }
    return 0;
}

/* 应用程序对设备文件/dev/leds执行ioclt(...)时,
 * 就会调用s3c24xx_leds_ioctl函数
 */
 //根据命令,实现LED的开和关,open返回inode和file给ioctl
static int s3c24xx_leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    if (arg > 4) {
        return -EINVAL;
    }

    switch(cmd) {
    case IOCTL_LED_ON:
        // 设置指定引脚的输出电平为0,该函数在内核中实现
        s3c2410_gpio_setpin(led_table[arg], 0);
        return 0;

    case IOCTL_LED_OFF:
        // 设置指定引脚的输出电平为1,该函数在内核中实现
        s3c2410_gpio_setpin(led_table[arg], 1);
        return 0;

    default:
        return -EINVAL;
    }
}

7、驱动编译,编译在PC上,PC上有未编译的linux目录
cat /proc/devices 查看已有设备
(1)在drivers\char下加入文件s3c24xx_leds.c;
(2)在drivers\char\Makefile中加一行

obj-m += s3c24xx_leds.o

(3)在内核根目录下执行make modules生成drivers\chars\s3c24xx_leds.ko
(4)把s3c24xx_leds.ko(以网络传输等方式)放到开发板文件系统/lib/modules/2.6.22.6/下
(5)这是就可以使用insmod s3c24xx_leds 和rmmod s3c24xx_leds命令进行加载和卸载。

8、测试程序(应用程序)
(1)在PC上编译生成可执行文件,把它放到开发板文件系统/user/bin目录下
(2)在开发板文件系统中建立设备文件

mknod /dev/leds c 231 0

(3)用命令测试

led_test 1 on
led_test 2 off

应用程序的open和ioctl等系统调用,他们的参数和驱动程序中相应函数的参数不是一一对应的,经过了内核文件层的转换。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define IOCTL_LED_ON    0
#define IOCTL_LED_OFF   1

void usage(char *exename)
{
    printf("Usage:\n");
    printf("    %s <led_no> <on/off>\n", exename);
    printf("    led_no = 1, 2, 3 or 4\n");
}

int main(int argc, char **argv)
{
    unsigned int led_no;
    int fd = -1;

    if (argc != 3)
        goto err;

    fd = open("/dev/leds", 0);  // 打开设备,根据/dev/leds提取设备类型、主设备号,根据这些可以找到对应file_operations 
    if (fd < 0) {
        printf("Can't open /dev/leds\n");
        return -1;
    }

    led_no = strtoul(argv[1], 0, 0) - 1;    // 操作哪个LED?
    if (led_no > 3)
        goto err;

    if (!strcmp(argv[2], "on")) {
        ioctl(fd, IOCTL_LED_ON, led_no);    // 点亮它
    } else if (!strcmp(argv[2], "off")) {
        ioctl(fd, IOCTL_LED_OFF, led_no);   // 熄灭它
    } else {
        goto err;
    }

    close(fd);
    return 0;

err:
    if (fd > 0) 
        close(fd);
    usage(argv[0]);
    return -1;
}

9、Makefile,把开发版根文件挂载到PC,PC直接编译无需下载

KERN_DIR = /work/linux-2.6//内核目录

all:    
    make -C $(KERN_DIR ) M = 'pwd' module
    // -C表示到KERN_DIR 目录下执行Makefile
boj-m += s3c24xx_led.o