3.字符設備驅動

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/songze_lee/article/details/73136843

linux設備分爲字符設備、塊設備和網絡設備三種,字符設備是最常見、最簡單的一種。字符設備的訪問是以字節流的形式來訪問設備的,換句話說,應用程序對它的讀取是以字節爲單位,而且要按照先後順序不能隨機讀取。串口是最常見的字符設備,它在進行收發數據時就是一個字節一個字節進行傳輸。

    cdev結構體

    struct cdev {

       structkobject kobj; /*內嵌的kobject對象*/

       structmodule *owner;    /*所屬模塊*/

       const structfile_operations *ops; /*文件操作結構體*/

       structlist_head list;  

       dev_t dev;        /*設備號*/

       unsigned intcount;

};

    cdev 結構體的 dev_t 成員定義了設備號,爲 32位,其中高 12 位爲主設備號,低20 位爲次設備號。使用下列宏可以從dev_t 獲得主設備號和次設備號。         MAJOR(dev_t dev)       MINOR(dev_t dev)

而使用下列宏則可以通過主設備號和設備號生成 dev_t

                MKDEV(int major, int minor)

Linux內核提供一組函數用於操作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,完成字符設備的註銷

 

file_operations 結構體

    structfile_operations

    {

       structmodule *owner;

       // 擁有該結構的模塊的指針,一般爲THIS_MODULES

       loff_t(*llseek)(structfile *, loff_t, int);

       // 用來修改文件當前的讀寫位置

       ssize_t(*read)(structfile *, char _ _user *, size_t, loff_t*);

       // 從設備中同步讀取數據

       ssize_t(*write)(structfile *, const char _ _user *, size_t,

       loff_t*);

       // 向設備發送數據

       unsignedint(*poll)(struct file *, struct poll_table_struct*);

       // 輪詢函數,判斷目前是否可以進行非阻塞的讀取或寫入

       int(*ioctl)(structinode *, struct file *, unsigned int, unsigned

       long);

       // 執行設備 I/O 控制命令

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

       // 用於請求將設備內存映射到進程地址空間

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

       // 打開

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

       ……

};

內核空間數據和用戶空間訪問函數

unsigned long __must_check copy_from_user(void *to, constvoid __user *from, unsigned long n)

用戶空間緩存區到內核空間的複製

to:目標地址,內核空間地址

from:源地址,用戶空間地址

n:拷貝數據的字節數

unsigned long __must_check copy_to_user(void __user *to,const void *from, unsigned long n)

內核空間到用戶空間緩存區的複製

to:目標地址,用戶空間地址

from:源地址,內核空間地址

n:拷貝數據的字節數

3.1早期註冊一個設備驅動程序的經典方式:

int register_chrdev(unsigned int major, const char *name,

           const struct file_operations *fops)

    major:設備的主設備號   0 內核會自動分配主設備號

    name:驅動程序的名詞(出現在/proc/devices)

    fops:file_operations結構

對register_chrdev的調用谷歌將爲給定的主設備號註冊0-255作爲次設備號,併爲每個設備建立一個對應默認cdev結構。

 

移除設備函數

void unregister_chrdev(unsigned int major, const char *name)

    major和name必須與傳遞給register_chrdev函數的值保持一致,否則該調用會失敗。

3.1.1 LED字符設備驅動示例

Led驅動:

/**
 *4LED 1亮 0滅
 *早期經典的字符設備註冊方法
 *register_chrdev
 *write read
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define LED_MAJOR 185

#define GPM4BASE 0x11000000
#define GPM4CON  0x2e0
#define GPM4DAT  0x2e4

static char *VGPM4BASE = NULL;

#define VGPM4CON *((u32 *)(VGPM4BASE+GPM4CON))
#define VGPM4DAT *((u32 *)(VGPM4BASE+GPM4DAT))


static void led_init(void)
{
	VGPM4CON &= ~0xffff;
	VGPM4CON |= 0x1111;/*設置輸出引腳*/

	VGPM4DAT |= 0x0f;/*led all off*/
}


static void led_stat(char *buf)
{
	u32 tmp;
	int i;
	
	tmp = VGPM4DAT;
	
	for(i = 0; i < 4; i++){
	
		if(tmp & (1<<i)){
			buf[i] = 0;
		}else{
			buf[i] = 1;
		}
	}
}

static void led_ctrl(int num, int stat)
{
	
	if(stat){
		VGPM4DAT &= ~(1 << (num - 1));
	}else{
		VGPM4DAT |= (1 << (num - 1));
	}
}

static int
exynos4412_led_open(struct inode *inodp, struct file *filp)
{
	return 0;
}

static ssize_t 
exynos4412_led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *fpos)
{
	char stat[4] = {0};
	
	led_stat(stat);	

	if(copy_to_user(buf,stat,sizeof(stat)))
	{
		return -EINVAL;
	}
	return sizeof(stat);
}

static ssize_t
exynos4412_led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *fpos)
{
	u8 cmd;
	u8 num,stat;
	if(copy_from_user(&cmd,buf,cnt)){//get_user(cmd,buf);
		return -EINVAL;
	}

	num = cmd >> 4;stat = cmd & 0xf;//cmd 高4位放led編號 低4位放led狀態

	if(( num < 1 ) || ( num > 4 ) ){
	        return -EINVAL;
	}
	if(( stat != 0 ) && ( stat != 1 )){
	        return -EINVAL;
	}
	
	led_ctrl(num, stat);
	
	return 1;

}
static int 
exynos4412_led_release(struct inode *inodp, struct file *filp)
{
	return 0;
}

/**字符驅動的核心
 *當應用程序操作設備文件時所調用的open read write等函數
 *最終會調用這個結構中對應的函數
 */
static struct file_operations exynos4412_led_fops = {
	.owner   = THIS_MODULE,/*內核使用這個字段以避免在模塊的操作正在被使用時卸載該模塊*/
	.open    = exynos4412_led_open,
	.read    = exynos4412_led_read,
	.write   = exynos4412_led_write,
	.release = exynos4412_led_release/*當file結構被釋放時,將調用這個操作*/
};

static int __init exynos4412_led_init(void)
{
	VGPM4BASE = ioremap(GPM4BASE,SZ_4K);
	/*內核中都是在虛擬內存中運行,通過ioremap IO映射*/
	if(NULL == VGPM4BASE){
		return -ENOMEM;
	}
	led_init();
	return register_chrdev(LED_MAJOR,"LED",&exynos4412_led_fops);
}
module_init(exynos4412_led_init);

static void __exit exynos4412_led_exit(void)
{
	unregister_chrdev(LED_MAJOR,"LED");
	printk("goodbye! led  driver");
}
module_exit(exynos4412_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Songze Lee");
MODULE_VERSION("verson 1.0");
MODULE_DESCRIPTION("char driver for led");

Makefile:

obj-m	:=led.o

OUR_KERNEL :=/ARM/linux-3.5-songze/

all:
	make -C $(OUR_KERNEL) M=$(shell pwd) modules
clean:
	make -C $(OUR_KERNEL) M=`pwd` clean

應用測試程序:

#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>

void pri_usage(char *info)
{
	printf("-------- usage: ------\n");
	printf("usage1: %s stat\n", info);
	printf("usage2: %s num on/off\n", info);
	printf("           num: <1~4>\n");
}

//a.out led stat
//./a.out led 1 on
int main(int argc, char **argv)
{
	int i;
	int ret;
	int num;
	char buf[4];
	int fd;

	unsigned char cmd = 0;

	if((argc !=  3) && (argc != 4)){
		pri_usage(argv[0]);
		exit(1);
	}

	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if(fd < 0){
		perror("open");
		exit(1);
	}

	if(argc == 3){
		if(!strncmp("stat", argv[2], 4)){
			ret = read(fd, buf, sizeof(buf));	
			if(ret != 4){
				perror("read");
				exit(1);
			}
			for(i = 0; i < sizeof(buf); i++){
				printf("led %d is %s\n", \
						i+1, buf[i]?"on":"off");
			}
		}else{
			pri_usage(argv[0]);
			exit(1);
		}
	}else{
		num = atoi(argv[2]);
		if(num >= 1 && num <= 4){
			cmd &= ~(0xf << 4);
			cmd = num << 4;
		}else{
			pri_usage(argv[0]);
			exit(1);
		}

		if(!strncmp("on", argv[3], 2)){
			cmd &= ~0xf;
			cmd |= 1;
		}else if(!strncmp("off", argv[3], 3)){
			cmd &= ~0xf;
		}else{
			pri_usage(argv[0]);
			exit(1);
		}
		ret = write(fd, &cmd,sizeof(cmd));
		if(ret != sizeof(cmd)){
			pri_usage(argv[0]);
			exit(1);
		}
	}

	return 0;
}

調試結果如下:

------------------------------PC機---------------------------------

[root@localhost led1]# arm-linux-gcc led_app.c -oled_test

[root@localhost led1]# make

make -C /ARM/linux-3.5-songze/M=/ARM/linux-3.5-songze/drivers/

songze_drivers/char/led/led1 modules

make[1]: Entering directory `/ARM/linux-3.5-songze'

  Building modules,stage 2.

  MODPOST 1modules

make[1]: Leaving directory `/ARM/linux-3.5-songze'

[root@localhost led1]# cp led_test /nfsroot

[root@localhost led1]# cp led.ko /nfsroot

--------------------------------ARM---------------------------------

[projct /]# insmod led.ko

[projct /]# cat /proc/devices

Character devices:

  1 mem

  2 pty

  3 ttyp

  4 /dev/vc/0

  4 tty

  4 ttyS

  5 /dev/tty

  5 /dev/console

  5 /dev/ptmx

  7 vcs

 10 misc

 13 input

 14 sound

 21 sg

 29 fb

 81 video4linux

 89 i2c

108 ppp

116 alsa

128 ptm

136 pts

180 usb

185 LED

188 ttyUSB

189 usb_device

204 ttySAC

216 rfcomm

251 ttyGS

252 watchdog

253 media

254 rtc

[projct /]# mknod led c 185 0

[projct /]# ls led -l

crw-r--r--    10        0         185,  0 Jan  5  2016 led

[projct /]# ./led_test

-------- usage: ------

usage1: ./led_test stat

usage2: ./led_test num on/off

           num:<1~4>

[projct /]# ./led_test led stat

led 1 is off

led 2 is off

led 3 is off

led 4 is off

[projct /]# ./led_test led 1 on

[projct /]# ./led_test led 3 on

[projct /]# ./led_test led stat

led 1 is on

led 2 is off

led 3 is on

led 4 is off

[projct /]# rmmod led

[ 3322.195000] goodbye! led  driver

rmmod: module 'led' not found


3.1.2 自動創建設備節點

       在上一個程序中通mknod創建字符設備,內核提供了一組函數來自動創建。如下所示:

1.class_create()創建類

 

#define class_create(owner, name)               \

({                                              \

    static structlock_class_key __key;     \

    __class_create(owner,name, &__key);    \

})

    宏class_create()用於動態創建設備的邏輯類,並完成部分字段的初始化,然後將其添加進linux內核系統中。

    此函數的執行效果就是在目錄/sys/class/下創建一個新的文件夾,此文件夾的名字爲此函數的第二個輸入參數,文件夾是空的。

 

宏輸入參數說明:

    宏owner是一個struct module結構體類型的指針,指向函數__class_create()即將創建的struct class類型對象的擁有者,

一般賦值爲THIS_MODULE,詳細查看:src/include/linux/module.h

       name是char類型的指針,代表即將創建的struct class變量的名字

 

返回參數:

    宏class_create()函數返回 struct class*的邏輯類

 

2.class_destroy()銷燬結構體類

void class_destroy(struct class *cls)

cls: 指向要被銷燬的結構體類,指針指向的必須時已經被創建的結構體

 

3.device_create()創建一個設備,並在sysfs中註冊

struct  device*device_create(struct class *class, struct device *parent,

                  dev_t devt, void *drvdata, constchar *fmt, ...)

 

函數功能:

        函數device_create()用於動態的建立邏輯設備,並對新的邏輯設備類進行相應初始化,將其與函數的第一個參數所代表的邏輯類關聯起來,然後將此邏輯設備加到linux內核系統的設備驅動程序模型中。函數能夠自動在/sys/devices/virtual目錄下創建新的邏輯設備目錄,在/dev目錄下創建於邏輯類對應的設備文件

參數說明:

   struct class *class:即將創建邏輯設備相關的邏輯類

   dev_t dev:設備號

   void *drvdata: void類型的指針,代表回調函數的輸入參數

   const char *fmt: 邏輯設備的設備名,即在目錄/sys/devices/virtual創建的邏輯設備目錄的目錄名。

 

4.函數device_unregister()移除設備

void  device_unregister(struct device *dev)


class_create() 和 device_create()關係

很多時候都是利用mknod命令手動創建設備節點,實際上Linux內核爲我們提供了一組函數,可以用來在模塊加載的時候自動在/dev目錄下創建相應設備節點,並在卸載模塊時刪除該節點,當然前提條件是用戶空間移植了udev。

內核中定義了structclass結構體,顧名思義,一個struct class結構體類型變量對應一個類,內核同時提供了class_create(…)函數,可以用它來創建一個類,這個類存放於sysfs下面,一旦創建好了這個類,再調用device_create(…)函數來在/dev目錄下創建相應的設備節點。這樣,加載模塊的時候,用戶空間中的udev會自動響應device_create(…)函數,去/sysfs下尋找對應的類從而創建設備節點。

    修改上一個程序部分代碼如下,insmod led.ko可在/dev/看見相應的設備節點

static struct class *led_class = NULL;
static struct device *led_device = NULL;

 static int __init exynos4412_led_init(void)
{
	int ret = 0;
	VGPM4BASE = ioremap(GPM4BASE,SZ_4K);
	if(NULL == VGPM4BASE){
		return -ENOMEM;
	}
	led_init();
	ret = register_chrdev(LED_MAJOR,"LED",&exynos4412_led_fops);

	/*自動創建設備文件節點*/
	/*1.創建LED類*/
	led_class =  class_create(THIS_MODULE,"LED");
	/*2.創建一個設備,並在sysfs中註冊*/
	led_device = device_create(led_class,NULL,MKDEV(LED_MAJOR,0),NULL,"led");

	return ret;
}
module_init(exynos4412_led_init);

static void __exit exynos4412_led_exit(void)
{
	unregister_chrdev(LED_MAJOR,"LED");
	/*卸載類下的設備*/
	device_unregister(led_device);
	/*卸載類*/
	class_destroy(led_class);
	/*解除映射*/
	iounmap(VGPM4BASE);
	printk("goodbye! led  driver\n");

3.2註冊設備和釋放設備號

靜態註冊設備:

int  register_chrdev_region(dev_t from, unsigned count, const char *name)

from:要分配設備編號範圍的起始值

count:連續設備編號的個數

name:設備名稱

動態註冊設備:

int  alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

              const char *name)

dev:僅用於輸出的參數,在成功完成調用後將保存已分配範圍的第一個編號

baseminor:要使用的被請求的第一個次設備號

count:連續設備編號的個數

name:設備名稱

註銷設備

void  unregister_chrdev_region(dev_t from, unsigned count)

from:要分配設備編號範圍的起始值

count:連續設備編號的個數

3.2.1 Led字符設備驅動示例

Led驅動:

/**
 *4LED 1亮 0滅
 *字符設備註冊方法
 *register_chrdev_region
 *write read
 *
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/cdev.h>

struct cdev cdev;
static dev_t devnum;
//static int major = 0;

static int LED_MAJOR = 0;

#define GPM4BASE 0x11000000
#define GPM4CON  0x2e0
#define GPM4DAT  0x2e4

static char *VGPM4BASE = NULL;

#define VGPM4CON *((u32 *)(VGPM4BASE+GPM4CON))
#define VGPM4DAT *((u32 *)(VGPM4BASE+GPM4DAT))


static void led_init(void)
{
	VGPM4CON &= ~0xffff;
	VGPM4CON |= 0x1111;/*設置輸出引腳*/

	VGPM4DAT |= 0x0f;/*led all off*/
}


static void led_stat(char *buf)
{
	u32 tmp;
	int i;
	
	tmp = VGPM4DAT;
	
	for(i = 0; i < 4; i++){
	
		if(tmp & (1<<i)){
			buf[i] = 0;
		}else{
			buf[i] = 1;
		}
	}
}

static void led_ctrl(int num, int stat)
{
	
	if(stat){
		VGPM4DAT &= ~(1 << (num - 1));
	}else{
		VGPM4DAT |= (1 << (num - 1));
	}
}

static int
exynos4412_led_open(struct inode *inodp, struct file *filp)
{
	return 0;
}

static ssize_t 
exynos4412_led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *fpos)
{
	char stat[4] = {0};
	
	led_stat(stat);	

	if(copy_to_user(buf,stat,sizeof(stat)))
	{
		return -EINVAL;
	}
	return sizeof(stat);
}

static ssize_t
exynos4412_led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *fpos)
{
	u8 cmd;
	u8 num,stat;
	if(copy_from_user(&cmd,buf,cnt)){//get_user(cmd,buf);
		return -EINVAL;
	}

	num = cmd >> 4;stat = cmd & 0xf;//cmd 高4位放led編號 低4位放led狀態

	if(( num < 1 ) || ( num > 4 ) ){
	        return -EINVAL;
	}
	if(( stat != 0 ) && ( stat != 1 )){
	        return -EINVAL;
	}
	
	led_ctrl(num, stat);
	
	return 1;

}
static int 
exynos4412_led_release(struct inode *inodp, struct file *filp)
{
	return 0;
}


/**字符驅動的核心
 *當應用程序操作設備文件時所調用的open read write等函數
 *最終會調用這個結構中對應的函數
 */
static struct file_operations exynos4412_led_fops = {
	.owner   = THIS_MODULE,/*內核使用這個字段以避免在模塊的操作正在被使用時卸載該模塊*/
	.open    = exynos4412_led_open,
	.read    = exynos4412_led_read,
	.write   = exynos4412_led_write,
	.release = exynos4412_led_release/*當file結構被釋放時,將調用這個操作*/
};

static int __init exynos4412_led_init(void)
{
	int ret = 0;
	
	VGPM4BASE = ioremap(GPM4BASE,SZ_4K);
	if(NULL == VGPM4BASE){
		return -ENOMEM;
	}
	led_init();
	/*申請設備號*/
	devnum = MKDEV(LED_MAJOR,0);
	/*註冊設備號*/
	if(LED_MAJOR){
		/*靜態註冊*/
		ret = register_chrdev_region(devnum,1,"LED");
	} else{
		/*動態註冊*/
		ret = alloc_chrdev_region(&devnum,0,1,"LED");
		LED_MAJOR = MAJOR(devnum);
		if(ret < 0) {
			printk("register_chrdev_region failed!");
			return ret;
		}
	}
	/*初始化 cdev*/
	cdev_init(&cdev, &exynos4412_led_fops);
	/*將cdev添加到系統中*/
	ret = cdev_add(&cdev, devnum, 1);
	if(ret < 0){
		return ret;
	}
	return 0;
}
module_init(exynos4412_led_init);

static void __exit exynos4412_led_exit(void)
{
	cdev_del(&cdev);
	unregister_chrdev_region(devnum, 1);

	printk("goodbye! led  driver");
}
module_exit(exynos4412_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Songze Lee");
MODULE_VERSION("verson 1.0");
MODULE_DESCRIPTION("char driver for led");

Makefile、用戶測試程序同上。

調試結果如下:

[projct /]# insmod led.ko

[projct /]# mknod led2 c 250 0   250爲自動分配的設備號可cat /proc/devices

[projct /]# ./led_test led2 stat

led 1 is off

led 2 is off

led 3 is off

led 4 is off

[projct /]# ./led_test led2 2 on

[projct /]# ./led_test led2 4 on

[projct /]# ./led_test led2 stat

led 1 is off

led 2 is on

led 3 is off

led 4 is on

[projct /]# rmmod led

[ 3520.355000] goodbye! led  driver

rmmod: module 'led' not found

3.3混雜設備

    Misc驅動是一些擁有着共同特性的簡單字符設備驅動。內核抽象出這些特性而形成了一些API(在文件drivers/char/misc.c中實現),以簡化這些設備驅動程序的初始化。所有misc設備被分配同一個主設備號MISC_MAJOR(10),但每次可以選擇一個單獨的次設備號。如果一個字符設備驅動要驅動多個設備,那麼它就不應給用misc設備來實現。

3.3.1 miscdevice 結構體

struct miscdevice {

    int minor;                              /* 次設備號*/

    const char*name;                       /*設備名稱*/

    const structfile_operations *fops;     /*文件操作結構體*/

    structlist_head list;                 /*misc_list的鏈表頭*/

    struct device*parent;                  /*父設備*/

    struct device*this_device;             /*當前設備*/

    const char*nodename;

    umode_t mode;

    };

3.3.2字符設備驅動和混雜設備驅動的使用區別

字符驅動程序完成初始化順序如下:

    1.register_chrdev_region()/register_chrdev_region()註冊分配主次設備號

    2.class_create()和device_create()創建/dev和/sys設備節點

    3.使用cdev_init()和cdev_add()將自身註冊爲字符設備驅動

字符驅動程序註銷順序如下:

    1.unregister_chrdev()和device_unregister()卸載類下的設備和類

    2.cdev_del(&cdev);刪除一個 cdev,完成字符設備的註銷

    3.unregister_chrdev_region()註銷字符設備

混雜設備只需調用misc_register()和misc_deregister()完成初始化和註銷設備。

static struct miscdevice miscdev = {

    .minor =MISC_DYNAMIC_MINOR,

    .name = "LED",

    .fops =&fops,

};

misc_register(&miscdev);

misc_deregister(&miscdev);

 

int misc_register(struct miscdevice * misc)

    misc_register()函數用於註冊一個混雜設備,其主設備號爲10,如果次設備號指定爲MISC_DYNAMIC_MINOR將由系統去指定一個次設備號,調用device_cr

eate創建設備節點。

misc_deregister(struct miscdevice * misc)

    misc_deregister()用於註銷這個混雜設備,其中調用了device_destroy

刪除設備節點。

    混雜設備就是主設備號爲10,所有的混雜設備形成一個鏈表,次設備號由註冊時指定,類名爲misc,自動創建設備節點的字符設備,是一種節約主設備號,爲驅動程序員簡化的字符設備驅動註冊方式。

3.3.3混雜設備實現機制

    misc_init()通過class_create(THIS_MODULE, "misc")在/sys/class/目錄下創建一個名爲misc,調用register_chrdev(MISC_MAJOR,"misc",&misc_fops)註冊設備,其中設備的主設備號爲MISC_MAJOR,爲10。設備名爲misc,misc_fops是操作函數的集合。通過subsys_initcall(misc_init)函數將misc作爲一個子系統被註冊到linux內核中。

    misc_register(&miscdev)中先初始化misc_list鏈表,遍歷misc_list鏈表,看這個次設備號是否已被使用如果次設備號已被佔有則退出。然後 判斷misc->minor== MISC_DYNAMIC_MINOR,如果相等,系統動態分配次設備號,MKDEV(MISC_MAJOR,misc->minor)計算出設備號,device_create(misc_class,misc->parent,dev, NULL, "%s", misc->name)在/dev下創建設備節點,list_add(&misc->list,&misc_list)將這個miscdevice添加到misc_list鏈中。(初始化和遍歷misc_list鏈表->匹配次設備號->找到一個沒有佔用的次設備號(如果需要動態分配的話)->計算設備號->創建設備節點->miscdevice結構體添加到misc_list鏈表中。)

    misc_deregister list_del(&misc->list)在misc_list鏈表中刪除miscdevice設備,device_destroy(misc_class, MKDEV(MISC_MAJOR,misc->min

or))刪除設備節點(從mist_list中刪miscdevice->刪除設備文件。)

 

3.3.4內核中的gpio口

    在Documentation/gpio.txt中有內核中gpio的使用詳細說明,下面先介紹比較常用的函數,如下:

使用 GPIO

對於一個GPIO,系統應該做的第一件事情就是通過 gpio_request() 函數聲明申請分配他

intgpio_request(unsigned gpio, const char *label)

設置GPIO的方向

    /*設置爲輸入或輸出, 成功返回 0 失敗返回負的錯誤代碼*/

    int gpio_direction_input(unsigned gpio);

    int gpio_direction_output(unsigned gpio, intvalue);

訪問自旋鎖安全的GPIO

    /* GPIO 讀取:返回零或非零 */

    int gpio_get_value(unsigned gpio);

    返回值是布爾值,零表示低電平,非零表示高電平

    /* GPIO 設置 */

    void gpio_set_value(unsigned gpio, intvalue);

釋放之前聲明的GPIO

    void gpio_free(unsigned gpio);

3.3.5混雜設備led驅動示例

    應用層調試程序,Makefile同上。

/**
 *混雜設備misc 
 *主設備號爲10的特殊的字符設備
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/miscdevice.h>

enum{
	ON,OFF
};

static int ledgpio[] = {
	EXYNOS4X12_GPM4(0),
	EXYNOS4X12_GPM4(1),
	EXYNOS4X12_GPM4(2),
	EXYNOS4X12_GPM4(3),
};

static void led_ctrl(int num, int stat)
{
	if(stat){
		 gpio_set_value(ledgpio[num - 1], ON);
	}else{
		gpio_set_value(ledgpio[num - 1], OFF);
	}
}

static void led_init(void)
{
	int i;

	for(i = 0; i < ARRAY_SIZE(ledgpio); i++){
		/*設置gpio方向爲輸出,電平爲1*/
		gpio_direction_output(ledgpio[i], OFF);
	}
}

static void led_stat(char *buf)
{
	int i;

	for(i = 0; i < ARRAY_SIZE(ledgpio); i++){
		/*讀取gpio端口的值*/
		if(gpio_get_value(ledgpio[i]) == ON){
			buf[i] = !ON;
		}else{
			buf[i] = !OFF;
		}
	}
}

static int exynos4412_led_open (struct inode *inodp, struct file *filp)
{

	return 0;
}

static ssize_t 
exynos4412_led_read (struct file *filp, char __user *buf, size_t cnt, loff_t *fpos)
{
	char stat[4] = {0};

	led_stat(stat);

	if(copy_to_user(buf, stat, sizeof(stat))){
		return -EINVAL;
	}

	return sizeof(stat);
}

static ssize_t 
exynos4412_led_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *fpos)
{
	u8 cmd;	
	u8 num, stat;

	get_user(cmd, buf);

	num = cmd >> 4; stat = cmd & 0xf;

	if(( num < 1 ) || ( num > 4 ) ){
		return -EINVAL;
	}

	if(( stat != 0 ) && ( stat != 1 )){
		return -EINVAL;
	}

	led_ctrl(num, stat);

	return 1;
}

static int     
exynos4412_led_release (struct inode *inodp, struct file *filp)
{
	return 0;
}

static struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = exynos4412_led_open,	
	.read = exynos4412_led_read,
	.write = exynos4412_led_write,
	.release = exynos4412_led_release,
};

static struct miscdevice miscdev = {
	.minor = MISC_DYNAMIC_MINOR,	/*動態分配次設備號*/
	.name = "LED",			/*設備名 在/dev/可見*/
	.fops = &fops, 			/*操作函數集*/
};

static void led_exit(void)
{
	int i;

	for(i = 0; i < ARRAY_SIZE(ledgpio); i++){
		/*設置gpio端口爲1*/
		gpio_set_value(ledgpio[i], OFF);
		/*釋放之前聲明申請的gpio*/
		gpio_free(ledgpio[i]);
	}	
}

static int __init exynos4412_led_init(void)
{
	int ret;
	int i;

	for(i = 0; i < ARRAY_SIZE(ledgpio); i++){
		/*申請分配gpio*/
		ret = gpio_request(ledgpio[i], "led");
		if(ret < 0){
			goto error0;
		}
	}
	
	led_init();
	
	ret = misc_register(&miscdev);

	if(ret < 0){
		led_exit();
		return ret;
	}

	return	0;

error0:
	while(i--){
		gpio_free(ledgpio[i]);
	}

	return ret;
}
module_init(exynos4412_led_init);

static void __exit exynos4412_led_exit(void)
{
	misc_deregister(&miscdev);
	led_exit();
} 
module_exit(exynos4412_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Songze Lee");
MODULE_VERSION("verson 1.0");
MODULE_DESCRIPTION("misc for led driver");
調試結果如下:

[projct /]# insmod led.ko

[projct /]# ./led_test /dev/led stat

led 1 is off

led 2 is off

led 3 is off

led 4 is off

[projct /]# ./led_test /dev/led 2 on

[projct /]# ./led_test /dev/led 4 on

[projct /]# ./led_test /dev/led stat

led 1 is off

led 2 is on

led 3 is off

led 4 is on

[projct /]# rmmod led

[ 3520.355000] goodbye! led  driver

rmmod: module 'led' not found


獲取源碼:git clone https://www.github.com/lisongze2016/Tiny4412_kernel.git


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章