天天看點

字元裝置驅動筆記字元裝置驅動筆記

字元裝置驅動筆記

一、簡介

在所有Linux裝置驅動中,字元裝置驅動最為基礎,本筆記将講解Linux字元裝置驅動的結構,并解釋其主要組成部分的程式設計方法。

二、主要結構體及API函數

  1. cdev結構
struct cdev {  
	struct kobject kobj;/*内嵌的kobject對象*/  
	struct module *owner;/*所屬子產品*/  
	const struct file_operations *ops;/*檔案操作結構體*/  
	struct list_head list;  
	dev_t dev;/*裝置号*/  
	unsigned int count;  
};
           

cdev結構體的dev_t成員定義了裝置号,為32位,其中12bit為主裝置号,20bit為次裝置号。使用如下宏可以從dev_t獲得主裝置号和次裝置号。

MAJOR(dev_t dev)
MINOR(dev_t dev)
           

使用如下宏可以用主裝置号和次裝置号生成裝置号

MKDEV(int major, int minor)  
           

核心提供了一組函數用于操作cdev結構體:

void cdev_init(struct cdev *,struct file_operations *);/*初始化cdev成員,建立cdev和file_operations的連接配接*/  
struct cdev *cdev_alloc(void);/*動态申請一個cdev記憶體*/  
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);  /*向系統添加一個cdev*/  
void cdev_del(struct cdev *);/*删除系統中的一個cdev*/  
           

裝置号的申請和釋放

int register_chrdev_region(dev_t from, unsigned count, const char *name);/*用于已知起始裝置号的情況下申請裝置号*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);/*用于未知起始裝置号的情況下申請未被占用的裝置号*/
void unregister_chrdev_region(dev_t from, unsigned count);/*設防裝置号*/
           
  1. file_operations結構

    cdev結構體的一個重要成員file_operations定義了字元裝置驅動提供給檔案系統的接口函數。

struct file_operations {
	struct module *owner;/*擁有該結構的子產品的指針,一般為THIS_MODULES*/  
	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);/*僅用于讀取目錄,對于裝置檔案,該字段為NULL*/  
	unsigned int (*poll) (struct file *, struct poll_table_struct *);/*輪詢函數,判斷目前是否可以進行非阻塞的讀取或寫入*/  
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);/*執行裝置IO控制指令*/  
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);/*不使用BLK的檔案系統,将使用此函數指針代替ioctl*/  
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);/*在64位的系統上,32位的ioctl調用,将使用此函數指針代替*/  
	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);/*異步fsync*/  
	int (*fasync) (int, struct file *, int);/*通知裝置FASYNC标志發生變化*/  
	int (*lock) (struct file *, int, struct file_lock *);  
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);/*通常為NULL*/  
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);/*在目前程序位址空間找到一個未映射的記憶體段*/  
	int (*check_flags)(int);/*允許子產品檢查傳遞給fcntl(F_SETEL...)調用的标志*/  
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);/*由VFS調用,将管道資料粘貼到檔案*/  
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);/*由VFS調用,将檔案資料粘貼到管道*/  
	int (*setlease)(struct file *, long, struct file_lock **);
};  
           

三、驅動代碼及分析

如下是一個最簡單的字元裝置驅動,四個LED燈的控制,可以作為字元裝置驅動的模闆。

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <mach/regs-clock.h>
#include <plat/regs-timer.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>

static int led_major = 0;     /* 主裝置号 */
static struct cdev LedDevs;

/* 應用程式執行ioctl(fd, cmd, arg)時的第2個參數 */
#define LED_MAGIC 'k'
#define IOCTL_LED_ON _IOW (LED_MAGIC, 1, int)
#define IOCTL_LED_OFF _IOW (LED_MAGIC, 2, int)
#define IOCTL_LED_RUN _IOW (LED_MAGIC, 3, int)
#define IOCTL_LED_SHINE _IOW (LED_MAGIC, 4, int)
#define IOCTL_LED_ALLON _IOW (LED_MAGIC, 5, int)
#define IOCTL_LED_ALLOFF _IOW (LED_MAGIC, 6, int)

/* 用來指定LED所用的GPIO引腳 */
static unsigned long led_table [] = {
    S3C2410_GPB(5),
    S3C2410_GPB(6),
    S3C2410_GPB(7),
    S3C2410_GPB(8),
};

/* 應用程式對裝置檔案/dev/led執行open(...)時,
 * 就會調用s3c24xx_leds_open函數
 */
static int s3c2440_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], S3C2410_GPIO_OUTPUT);
    }
    return 0;
}

//LEDS all light on
void leds_all_on()
{
    int i;
    for (i=0; i<4; i++) {
        s3c2410_gpio_setpin(led_table[i], 0);
    }
}

//LEDs all light off
void leds_all_off()
{
    int i;
    for (i=0; i<4; i++) {
        s3c2410_gpio_setpin(led_table[i], 1);
    }
}

/* 應用程式對裝置檔案/dev/leds執行ioctl(...)時,
 * 就會調用s3c24xx_leds_ioctl函數
 */
static int s3c2440_leds_ioctl(struct inode *inode, 
								struct file *file, 
								unsigned int cmd, 
								unsigned long arg)
{
    unsigned int data;

    if (__get_user(data, (unsigned int __user *)arg)) 
        return -EFAULT;

    switch(cmd) {
        case IOCTL_LED_ON:
            // 設定指定引腳的輸出電平為0
            s3c2410_gpio_setpin(led_table[data], 0);
            return 0;

        case IOCTL_LED_OFF:
            // 設定指定引腳的輸出電平為1
            s3c2410_gpio_setpin(led_table[data], 1);
            return 0;
            
        case IOCTL_LED_RUN:
            // 跑馬燈
            {
               int i,j;
                leds_all_off();            
                //printk("IOCTL_LED_RUN");
                for (i=0;i<data;i++)
                    for (j=0;j<4;j++) {
                        s3c2410_gpio_setpin(led_table[j], 0);
                        mdelay(400); //delay 400ms
                        s3c2410_gpio_setpin(led_table[j], 1);
                        mdelay(400); //delay 400ms
                    }  
                return 0;
             }
          
        case IOCTL_LED_SHINE:
            // LED 閃爍
            {
                int i,j;
                leds_all_off();
                printk("IOCTL_LED_SHINE\n");
                for (i=0;i<data;i++) {
                    for (j=0;j<4;j++)
                        s3c2410_gpio_setpin(led_table[j], 0);
                    mdelay(400); //delay 400ms
                    for (j=0;j<4;j++)
                        s3c2410_gpio_setpin(led_table[j], 1);
                    mdelay(400);
                }
                return 0;
           }
        case IOCTL_LED_ALLON:
            // 設定指定引腳的輸出電平為0
            leds_all_on();
            return 0;
        case IOCTL_LED_ALLOFF:
            // 設定指定引腳的輸出電平為1
            leds_all_off();
            return 0;

        default:
            return -EINVAL;
    }
}

/* 這個結構是字元裝置驅動程式的核心
 * 當應用程式操作裝置檔案時所調用的open、read、write等函數,
 * 最終會調用這個結構中指定的對應函數
 */
static struct file_operations s3c2440_leds_fops = {
    .owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯子產品時自動建立的__this_module變量 */
    .open   =   s3c2440_leds_open,     
    .ioctl  =   s3c2440_leds_ioctl,
};

/*
 * Set up the cdev structure for a device.
 */
static void led_setup_cdev(struct cdev *dev, int minor,
		struct file_operations *fops)
{
	int err, devno = MKDEV(led_major, minor);
    
	cdev_init(dev, fops);
	dev->owner = THIS_MODULE;
	dev->ops = fops;
	err = cdev_add (dev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk (KERN_NOTICE "Error %d adding Led%d", err, minor);
}

/*
 * 執行“insmod s3c24xx_leds.ko”指令時就會調用這個函數
 */
static int __init s3c2440_leds_init(void)
{
	int result;
	dev_t dev = MKDEV(led_major, 0);
	char dev_name[]="led";  /* 加載模式後,執行”cat /proc/devices”指令看到的裝置名稱 */

	/* Figure out our device number. */
	if (led_major)
		result = register_chrdev_region(dev, 1, dev_name);
	else {
		result = alloc_chrdev_region(&dev, 0, 1, dev_name);
		led_major = MAJOR(dev);
	}
	if (result < 0) {
		printk(KERN_WARNING "leds: unable to get major %d\n", led_major);
		return result;
	}
	if (led_major == 0)
		led_major = result;

	/* Now set up cdev. */
	led_setup_cdev(&LedDevs, 0, &s3c2440_leds_fops);
	printk("Led device installed, with major %d\n", led_major);
	printk("The device name is: %s\n", dev_name);
	return 0;
}

/*
 * 執行”rmmod s3c24xx_leds”指令時就會調用這個函數 
 */
static void __exit s3c2440_leds_exit(void)
{
    /* 解除安裝驅動程式 */
	cdev_del(&LedDevs);
	unregister_chrdev_region(MKDEV(led_major, 0), 1);
	printk("Led device uninstalled\n");
}

/* 這兩行指定驅動程式的初始化函數和解除安裝函數 */
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);

/* 描述驅動程式的一些資訊,不是必須的 */
MODULE_AUTHOR("eurphan");             					// 驅動程式的作者
MODULE_DESCRIPTION("s3c2440 LED Driver");  			 // 一些描述資訊
MODULE_LICENSE("Dual BSD/GPL");                        // 遵循的協定
           

四、測試應用程式

寫好的驅動怎麼測試呢,這就需要編寫應用程式來測試了

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

#define LED_MAGIC 'k'
#define IOCTL_LED_ON _IOW (LED_MAGIC, 1, int)
#define IOCTL_LED_OFF _IOW (LED_MAGIC, 2, int)
#define IOCTL_LED_RUN _IOW (LED_MAGIC, 3, int)
#define IOCTL_LED_SHINE _IOW (LED_MAGIC, 4, int)
#define IOCTL_LED_ALLON _IOW (LED_MAGIC, 5, int)
#define IOCTL_LED_ALLOFF _IOW (LED_MAGIC, 6, int)

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;
        unsigned int count=10;
    
	if (argc > 3 || argc == 1)
		goto err;
        
	fd = open("/dev/led", 0);  // 打開裝置
	if (fd < 0) {
		printf("Can't open /dev/leds\n");
		return -1;	
	}	
		
	if (argc == 2) {
		if (!strcmp(argv[1], "on")) {
			ioctl(fd, IOCTL_LED_ALLON, &count);    // 點亮它
		} else if (!strcmp(argv[1], "off")) {
			ioctl(fd, IOCTL_LED_ALLOFF, &count);   // 熄滅它
		} else if (!strcmp(argv[1], "run")) {
			ioctl(fd, IOCTL_LED_RUN, &count);   //運作跑馬燈
                } else if (!strcmp(argv[1], "shine")) {
			ioctl(fd, IOCTL_LED_SHINE, &count);   //閃爍
		} else {
			goto err;
		}
	}
		
	if (argc == 3) {
		led_no = strtoul(argv[1], NULL, 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;
}