Linux驱动开发-简单例子

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 
#include 
#include 
#include 

#define IOCTL_LED_ON    0
#define IOCTL_LED_OFF   1

void usage(char *exename)
{
    printf("Usage:\n");
    printf("    %s  \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

你可能感兴趣的:(笔记-嵌入式-Linux韦东山)