字元裝置驅動筆記
一、簡介
在所有Linux裝置驅動中,字元裝置驅動最為基礎,本筆記将講解Linux字元裝置驅動的結構,并解釋其主要組成部分的程式設計方法。
二、主要結構體及API函數
- 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);/*設防裝置号*/
-
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;
}