天天看點

「Linux C 一站式程式設計」Linux 字元裝置驅動

字元裝置驅動結構

裝置分類

字元裝置

一個字元( char ) 裝置是一種可以當作一個位元組流來通路存取的裝置( 如同一個檔案 ); 一個字元驅動負責實作這種行為. 這樣的驅動常常至少實作 open, close, read, 和 write 系統調用. 文本控制台( /dev/console )和序列槽( /dev/ttyS0 及其類似的裝置 )就是兩個字元裝置的例子, 因為它們很好地展現了流的抽象. 字元裝置通過檔案系統結點來存取, 例如 /dev/tty1 和 /dev/lp0. 在一個字元裝置和一個普通檔案之間唯一有關的不同就是, 你經常可以在普通檔案中移來移去, 但是大部分字元裝置僅僅是資料通道, 你隻能順序存取.然而, 存在看起來象資料區的字元裝置, 你可以在裡面移來移去. 例如, frame grabber 經常這樣, 應用程式可以使用 mmap 或者 lseek 存取整個要求的圖像.

塊裝置

如同字元裝置, 塊裝置通過位于 /dev 目錄的檔案系統結點來存取. 一個塊裝置(例如一個磁盤)應該是可以駐有一個檔案系統的. 在大部分的 Unix 系統, 一個塊裝置隻能處理這樣的 I/O 操作, 傳送一個或多個長度經常是 512 位元組( 或一個更大的 2 的幂的數 )的整塊. Linux, 相反, 允許應用程式讀寫一個塊裝置象一個字元裝置一樣 – 它允許一次傳送任意數目的位元組. 結果就是, 塊和字元裝置的差別僅僅在核心在内部管理資料的方式上, 并且是以在核心/驅動的軟體接口上不同. 如同一個字元裝置, 每個塊裝置都通過一個檔案系統結點被存取的, 它們之間的差別對使用者是透明的. 塊驅動和字元驅動相比, 與核心的接口完全不同.

網絡接口

任何網絡事務都通過一個接口來進行, 就是說, 一個能夠與其他主機交換資料的裝置. 通常, 一個接口是一個硬體裝置, 但是它也可能是一個純粹的軟體裝置, 比如環回接口. 一個網絡接口負責發送和接收資料封包, 在核心網絡子系統的驅動下, 不必知道單個事務是如何映射到實際的被發送的封包上的. 很多網絡連接配接( 特别那些使用 TCP 的)是面向流的, 但是網絡裝置卻常常設計成處理封包的發送和接收. 一個網絡驅動對單個連接配接一無所知; 它隻處理封包.

字元裝置驅動結構

字元裝置是指發送或者接受資料按照字元方式進行,一般應用程式都通過裝置檔案來通路字元裝置。

字元裝置的驅動一般在 kernel-src/drivers/char 目錄下。常見的字元裝置有:滑鼠,控制台,聲霸卡,顯示裝置,touch panel,序列槽,并口等等。

我們可以在Linux源代碼目錄通過 make menuconfig來看到字元裝置。

字元裝置管理

我們都知道應用程式是通過裝置檔案來通路字元裝置的。那麼裝置檔案通過什麼标示來對應相關的驅動呢?這就是我們前面提到的裝置号。

因為應用程式要與字元裝置進行資料互動(read,write)。那麼驅動還要提供讀寫函數。 并且要求将讀寫函數與裝置号連接配接起來。這就是我們下面要講的應用和驅動的關聯。

應用和驅動關聯

在前面的課程我們談到了裝置檔案, 給出了裝置檔案和裝置号的概念。 這節課我們先看一個例子:

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

#define filename "/dev/akae_c"

int main (void)
{
	int fd;
	char buf[10];
	int ret;
	
	fd = open (filename,O_RDWR);
	
	if (fd < 0)
	{
	    printf ("Open %s file error,please use sudo !",filename);
	    return -1;
	}

	ret = read (fd,buf,10);
	if (ret != 10)
	{
	    printf ("NOTICE: read %s file,get %d Bytes\n",filename,ret);
	}
    
	ret = write (fd,buf,10);
	if (ret != 10)
	{
	    printf ("NOTICE: write %s file,get %d Bytes\n",filename,ret);
	}
	
	close (fd);
	
	return 0;
}

/* mychar_test.c */

#include <stdio.h>
#include <fcntl.h>

#include <sys/types.h>
#include <unistd.h>

int main(void)
{
	int fd;
	int local = 100;

	printf("hello, test char drv\n");
	printf("<main> &local = %p\n", &local);

	printf("current pid = %d\n", getpid());

	fd = open("mychar", O_RDWR); 
	printf("fd = %d\n", fd);

	if (fd < 0)
	{
		perror("open mychar failed!\n");
		return -1;
	}

	return 0;
}

/* mychar_drv.c */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/sched.h>

MODULE_LICENSE("GPL");

int mychar_open(struct inode * in, struct file * filp)
{
	int local = 100;

	printk("mychar open called!\n");

	printk("current pid = %d\n", current->pid);
	printk("current parent pid = %d\n", current->parent->pid);
	printk("current process name = %s\n", current->comm);
	printk("current open function = %p\n", mychar_open);

	printk("<driver> &local = %p\n", &local);
	printk("task struct size = %d\n", sizeof(*current));

//	while (1);

	printk("mychar open called finished!\n");
	
	return 0;
}

struct file_operations mychar_fops =
{
	.open = mychar_open,
};

static __init int mychar_init(void)
{
	int rc;

	printk("mychar init\n");

	// register mychar_drv 
	rc = register_chrdev(240, "this is my first char drv", &mychar_fops);

	printk("rc = %d\n", rc);

	return 0;
}


static __exit void mychar_exit(void)
{
	printk("mychar exit\n");

	// unregister mychar_drv 
	unregister_chrdev(240, "this is my first char drv");

	return;
}

module_init(mychar_init);
module_exit(mychar_exit);


/* Makefile */
CC = arm-linux-gcc

obj-m := mychar_drv.o
KDIR := /home/akaedu/teacher_li/linux-2.6.35.7/

all:
	make mychar_test
	make -C $(KDIR)	SUBDIRS=$(PWD) 	modules
	ls -l *.ko mychar_test

clean:
	-rm *.ko *.o *.order *.mod.c *.symvers
	-rm mychar_test           

從上例中我們可以看出:

* 應用程式可以通過打開裝置檔案通路裝置;
* 可以通過read, write函數通路裝置驅動;
* 裝置号是裝置的唯一重要标示;
* 多個裝置檔案可以對應到一個裝置号,反之則不行;
* 裝置檔案名可以根據需要命名,沒有硬性規定;
* 裝置驅動和裝置檔案之間,通過裝置節點的裝置号(id)關聯起來,和取名(name)無關,和存放位置(/dev)無關;           

主裝置号和次裝置号

主次裝置号

字元裝置通過檔案系統中的名字來存取. 那些名字稱為檔案系統的特殊檔案, 或者裝置檔案, 或者檔案系統的簡單結點; 慣例上它們位于 /dev 目錄. 字元驅動的特殊檔案由使用 ls -l 的輸出的第一列的”c”辨別. 塊裝置也出現在 /dev 中, 但是它們由”b”辨別. 本章集中在字元裝置, 但是下面的很多資訊也适用于塊裝置.

如果你發出 ls -l 指令, 你會看到在裝置檔案項中有 2 個數(由一個逗号分隔)在最後修改日期前面, 這裡通常是檔案長度出現的地方. 這些數字是給特殊裝置的主次裝置編号. 下面的清單顯示了一個典型系統上出現的幾個裝置. 它們的主編号是 1, 4, 7, 和 10, 而次編号是 1, 3, 5, 64, 65, 和 129.

crw-rw-rw- 1 root  root  1,  3 Apr 11  2002 null 
crw------- 1 root  root  10, 1 Apr 11  2002 psaux 
crw------- 1 root  root  4,  1 Oct 28 03:04 tty1 
crw-rw-rw- 1 root  tty   4, 64 Apr 11  2002 ttys0 
crw-rw---- 1 root  uucp  4, 65 Apr 11  2002 ttyS1 
crw--w---- 1 vcsa  tty   7,  1 Apr 11  2002 vcs1 
crw--w---- 1 vcsa  tty   7,129 Apr 11  2002 vcsa1 
crw-rw-rw- 1 root  root  1,  5 Apr 11  2002 zero             

傳統上, 主編号辨別裝置相連的驅動. 例如, /dev/null 和 /dev/zero 都由驅動 1 來管理, 而虛拟控制台和序列槽終端都由驅動 4 管理; 同樣, vcs1 和 vcsa1 裝置都由驅動 7 管理. 現代 Linux 核心允許多個驅動共享主編号, 但是你看到的大部分裝置仍然按照一個主編号一個驅動的原則來組織.

次編号被核心用來決定引用哪個裝置. 依據你的驅動是如何編寫的(如同我們下面見到的), 你可以從核心得到一個你的裝置的直接指針, 或者可以自己使用次編号作為本地裝置數組的索引. 不論哪個方法, 核心自己幾乎不知道次編号的任何事情, 除了它們指向你的驅動實作的裝置.

主裝置号是由 include/linux/major.h 定義的。 裝置号和裝置名可在核心源代碼的 Documentation/devices.txt 裡查到, mknod 可為這些指定的裝置建立節點,當然節點的位置不是一定要在/dev下,但是為了便于管理一般都是指定/dev。

mknod 指令

mknod - make block or character special files

mknod [OPTION]... NAME TYPE [MAJOR MINOR]  	
    option 有用的就是 -m 了  
    name   自定義  
    type   有 b 和 c 還有 p  
    主裝置号  
    次裝置号  	
	b	表示特殊檔案是面向塊的裝置(磁盤、軟碟或錄音帶)。
	c	表示特殊檔案是面向字元的裝置(其他裝置)。
	p	建立 FIFO(已命名的管道)。           

舉例

$ mknod mylcd c 29 0 (建立一個自己的lcd裝置,主裝置号為 29,等價于 /dev/fb0 )

實驗一下,如果用 cp 指令和 mv 指令分别對一個裝置檔案進行操作,會有什麼結果? (結論是 cp 不可用,mv 可以)

注冊裝置

注冊一個字元裝置的經典方法是使用:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);           

這裡, major 是感興趣的主編号, name 是驅動的名字(出現在 /proc/devices), fops 是預設的 file_operations 結構. 一個對 register_chrdev 的調用為給定的主編号, 并且為每一個建立一個預設的 cdev 結構.

如果你使用 register_chrdev, 從系統中去除你的裝置的正确的函數是:

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

major 和 name 必須和傳遞給 register_chrdev 的相同, 否則調用會失敗.

這兩個通用方法的聲明在 include/linux/fs.h 中,它們的實作主要展現在 fs.h 檔案的内聯函數和 fs/char_dev.c 中.

調用範例

struct file_operations xxx_fops = 
{
	.owner = THIS_MODULE,
	.open = xxx_open,
	.read = xxx_read,
	.write = xxx_write,
	.release = xxx_release,
};

xxx_init() 中注冊裝置
rc = register_chrdev(XXX_MAJOR, "akae", &xxx_fops);

xxx_release() 中解除安裝裝置
unregister_chrdev(XXX_MAJOR, "akae");           

目前已經注冊使用的裝置可以通過 cat /proc/devices 檔案得到:

Character devices:
 1 mem
 2 pty
 3 ttyp
 4 ttyS
 6 lp
 7 vcs
 10 misc
 13 input
 14 sound 
 21 sg
 180 usb

Block devices:
 2 fd
 8 sd
 11 sr
 65 sd
 66 sd            

裝置編号的内部表示

在核心中, dev_t 類型(在 linux/types.h 中定義)用來持有裝置編号 – 主次部分都包括. 對于 2.6.0 核心, dev_t 是 32 位的量, 12 位用作主編号, 20 位用作次編号. 你的代碼應當, 當然, 對于裝置編号的内部組織從不做任何假設; 相反, 應當利用在 linux/kdev_t.h 中的一套宏定義. 為獲得一個 dev_t 的主或者次編号, 使用:

MAJOR(dev_t dev); 
MINOR(dev_t dev);           

相反, 如果你有主次編号, 需要将其轉換為一個 dev_t, 使用:

MKDEV(int major, int minor); 


/* linux/fs.h */
struct inode {
	...
	dev_t                   i_rdev;
	...
};

/* linux/types.h */
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t		dev_t;

/* linux/kdev_t.h *?
#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))           

cdev結構體

在 Linux 2.6 核心中使用 cdev結構體描述字元裝置, cdev 結構體的定義在 include/linux/cdev.h 中,内容如下:

struct cdev  
{ 
	struct kobject kobj;   /*所屬子產品*/ 
	struct module *owner;    
	struct file_operations  /*檔案操作結構體*/ 
	struct list_head list; 
	dev_t dev;           /*裝置号*/ 
	unsigned int count; 
};            

Linux 2.6 核心提供了一組函數用于操作 cdev 結構體,它們的實作在 fs/char_dev.c 中,聲明如下所示:

void cdev_init(struct cdev *, struct file_operations *); 
struct cdev *cdev_alloc(void); 
void cdev_put(struct cdev *p); 
int cdev_add(struct cdev *, dev_t, unsigned); 
void cdev_del(struct cdev *);            

cdev_init()函數用于初始化 cdev 的成員,并建立 cdev 和 file_operations 之間的連接配接。

cdev_init()函數内部實作

void cdev_init(struct cdev *cdev, struct file_operations *fops) 
{ 
	memset(cdev, 0, sizeof *cdev); 
	INIT_LIST_HEAD(&cdev->list); 
	cdev->kobj.ktype = &ktype_cdev_default; 
	kobject_init(&cdev->kobj); 
	cdev->ops = fops;    /*将傳入的檔案操作結構體指針指派給cdev的ops*/ 
}             

cdev_alloc()函數用于動态申請一個cdev記憶體。

struct cdev *cdev_alloc(void) 
{ 
	struct  cdev  *p=kmalloc(sizeof(struct  cdev),GFP_KERNEL);  /*配置設定cdev的記憶體*/ 
	if (p) { 
		memset(p, 0, sizeof(struct cdev)); 
		p->kobj.ktype = &ktype_cdev_dynamic; 
		INIT_LIST_HEAD(&p->list); 
		kobject_init(&p->kobj); 
	} 
	return p; 
}            

配置設定和釋放裝置号

在 調用 cdev_add() 函數向系統注冊字元裝置之前 , 應首先調用 register_chrdev_region() 函數向系統申請裝置号。

相反地 ,在調用 cdev_del() 函數從系統登出字元裝置之後,應該調用 unregister_chrdev_region() 以釋放原先申請的裝置号,

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	p->dev = dev;
	p->count = count;
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

void cdev_del(struct cdev *p) 
{ 
	cdev_unmap(p->dev, p->count); 
	kobject_put(&p->kobj); 
}           

其中用到的申請區域的兩個函數的原型如下,它們的實作也在 fs/char_dev.c 中:

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

當 register_chrdev_region 傳回值為0,則表示申請成功。

如果 from 參數是0,代表要求系統自動配置設定一個可用的 major 并傳回這個 major 。

Examples

struct cdev uart_cdev;
dev_t uart_dev_no;

int uart_drv_init(void)
{
	printk("uart_drv init ok \n");

	//register_chrdev(UART_MAJOR, "myttyS3", &uart_drv_fops);

	// use cdev
	uart_dev_no = MKDEV(UART_MAJOR, 3);
	register_chrdev_region(uart_dev_no, 1, "myttyS3");
	cdev_init(&uart_cdev, &uart_drv_fops);
	cdev_add(&uart_cdev, uart_dev_no, 1);

	return 0;
}

void uart_drv_exit(void)
{
	printk("uart_drv exit ok \n");

	//unregister_chrdev(UART_MAJOR, "myttyS3");

	// use cdev 
	unregister_chrdev_region(uart_dev_no, 1);
	cdev_del(&uart_cdev);

	return;
}           

檔案操作和file結構

struct 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 *);
	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 *, 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 (*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 (*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);
	int (*setlease)(struct file *, long, struct file_lock **);
};           

這個結構, 定義在 linux/fs.h, 是一個函數指針的集合. 每個打開檔案(内部用一個 file 結構來代表, 稍後我們會檢視)與它自身的函數集合相關連( 通過包含一個稱為 f_op 的成員, 它指向一個 file_operations 結構). 這些操作大部分負責實作系統調用, 是以, 命名為 open, read, 等等. 我們可以認為檔案是一個”對象”并且其上的函數操作稱為它的”方法”, 使用面向對象程式設計的術語來表示一個對象聲明的用來操作對象的動作.

基本元素

file_operations的主要域:

struct module *owner:指向子產品自身。
open:打開裝置。
release:關閉裝置。
read:從裝置上讀資料。
write:向裝置上寫資料。
ioctl:操作裝置函數。
mmap:映射裝置空間到程序的位址空間。           

接口含義

struct module *owner            

第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的子產品的指針. 這個成員用來在它的操作還在被使用時阻止子產品被解除安裝. 幾乎所有時間中, 它被簡單初始化為 THIS_MODULE, 一個在 linux/module.h 中定義的宏.

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);            

用來從裝置中擷取資料. 在這個位置的一個空指針導緻 read 系統調用以 -EINVAL(“Invalid argument”) 失敗. 一個非負傳回值代表了成功讀取的位元組數( 傳回值是一個 “signed size” 類型, 常常是目标平台本地的整數類型).

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);            

發送資料給裝置. 如果 NULL, -EINVAL 傳回給調用 write 系統調用的程式. 如果非負, 傳回值代表成功寫的位元組數.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);            

ioctl 系統調用提供了發出裝置特定指令的方法(例如格式化軟碟的一個磁道, 這不是讀也不是寫). 另外, 幾個 ioctl 指令被核心識别而不必引用 fops 表. 如果裝置不提供 ioctl 方法, 對于任何未事先定義的請求(-ENOTTY, “裝置無這樣的 ioctl”), 系統調用傳回一個錯誤.

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

mmap 用來請求将裝置記憶體映射到程序的位址空間. 如果這個方法是 NULL, mmap 系統調用傳回 -ENODEV.

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

盡管這常常是對裝置檔案進行的第一個操作, 不要求驅動聲明一個對應的方法. 如果這個項是 NULL, 裝置打開一直成功, 但是你的驅動不會得到通知.

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

在檔案結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.

struct inode_operations

struct inode_operations {
	int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
	struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
	int (*link) (struct dentry *,struct inode *,struct dentry *);
	int (*unlink) (struct inode *,struct dentry *);
	int (*symlink) (struct inode *,struct dentry *,const char *);
	int (*mkdir) (struct inode *,struct dentry *,int);
	int (*rmdir) (struct inode *,struct dentry *);
	int (*mknod) (struct inode *,struct dentry *,int,dev_t);
	int (*rename) (struct inode *, struct dentry *,
			struct inode *, struct dentry *);
	int (*readlink) (struct dentry *, char __user *,int);
	void * (*follow_link) (struct dentry *, struct nameidata *);
	void (*put_link) (struct dentry *, struct nameidata *, void *);
	void (*truncate) (struct inode *);
	int (*permission) (struct inode *, int);
	int (*check_acl)(struct inode *, int);
	int (*setattr) (struct dentry *, struct iattr *);
	int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
	int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
	ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
	ssize_t (*listxattr) (struct dentry *, char *, size_t);
	int (*removexattr) (struct dentry *, const char *);
	void (*truncate_range)(struct inode *, loff_t, loff_t);
	long (*fallocate)(struct inode *inode, int mode, loff_t offset,
			  loff_t len);
	int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
		      u64 len);
};           

使用者空間和kernel空間的資料互傳

子產品在很多時候需要和使用者程式互動,其中包括資料傳輸。在子產品裡做傳輸的時候使用memcpy往往會出錯。

請看例子:

在使用者空間裡定義:
char user_buffer[100];

在核心子產品裡定義:
char kernel_buffer[100];

使用 memcpy(kernel_buffer,user_buffer,100);
往往不能成功;原因在于使用者空間的user_buffer,有可能是缺頁狀态。這時候kernel會出現異常。

我們需要使用更安全的函數 copy_to_user, copy_from_user。 它們的定義在 arch/arm/include/asm/uaccess.h 檔案中。

copy_to_user(user_buffer,kernel_buffer,100);

copy_from_user(kernel_buffer,user_buffer,100);

這裡還有一個函數:
put_user(k,u),get_user(k,u);	它們适合于每次通路char,int等單個資料類型;           

GPIO/UART 驅動代碼實作

led 驅動

#include <linux/module.h>	// module_init
#include <asm/io.h>		// ioremap
#include <linux/fs.h>		// file_operations
#include <asm/uaccess.h>	// copy_from_user

MODULE_LICENSE("GPL");

#define MA	240

volatile int * pled;

int led_drv_open(struct inode *inode, struct file *filp)
{
	int major, minor;

	major = MAJOR(inode->i_rdev);
	minor = MINOR(inode->i_rdev);

	printk("led drv open: major %d, minor %d\n", major, minor);

	return 0;
}

ssize_t led_drv_write(struct file *filp, const char __user * buf, size_t count, loff_t *f_pos)
{
	char kbuf[128];

	//buf[count] = '\0';
	printk("led drv write %d\n", count);

//	printk("buf = %s\n", buf);
//	printk("buf at %p\n", buf);
	printk("count = %d\n", count);

//	copy_from_user(kbuf, buf, count);
	*pled = buf[0];

//	printk("kbuf = %s\n", kbuf);
//	printk("kbuf at %p\n", kbuf);

	return count;
}

int led_drv_release(struct inode *inode, struct file *filp)
{
	printk("led drv release ok!\n");

	return 0;
}

struct file_operations led_fops = 
{
	.owner = THIS_MODULE,
	.open = led_drv_open,
	.write = led_drv_write,
	.release = led_drv_release,
};

static int led_drv_init(void)
{
	int rc;

	printk("led init \n");

	pled = ioremap(0xE0200284, 4);

	//*pled = 0;
	rc = register_chrdev(MA, "akae", &led_fops);
	if (rc < 0)
	{
		printk("register failed\n");
		return -1;
	}

	printk("register char ok %d!\n", rc);

	return 0;
}

static void led_drv_exit(void)
{
	printk("led exit \n");

	//*pled = 0xF;
	unregister_chrdev(MA, "akae");
	printk("unregister char ok!\n");

	return;
}

module_init(led_drv_init);
module_exit(led_drv_exit);           

課堂練習: 序列槽裝置驅動 (char device driver)

1 基本驅動功能實作

Makefile for kernel module
基本的核心子產品功能,uart_drv_init, uart_drv_exit, #include, License
序列槽的驅動接口:  uart_init, uart_putchar, uart_getchar (ioremap)
insmod uart_drv.ko 
	uart_drv_init -> write "hello" -> for + uart_putchar
	uart_drv_init -> read char -> write char -> echo           

example code

/* uart_drv.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>

MODULE_LICENSE("GPL");

struct uart_sfr
{
	int ulcon;
	int ucon;
	int ufcon;
	int umcon;
	int utrstat;
	int uerstat;
	int ufstat;
	int umstat;
	int utxh;
	int urxh;
	int ubrdiv;
	int udivslot;
};

typedef struct uart_sfr USFR;

static volatile USFR *puart;

void uart_init(void)
{
#if 0
	// see how linux set UART0 regs
	puart = ioremap(0xe2900000, sizeof(USFR));
	printk("reg ulcon = %x\n", puart->ulcon);
	printk("reg ucon = %x\n", puart->ucon);
	printk("reg ubrdiv = %x\n", puart->ubrdiv);
	printk("reg udivslot = %x\n", puart->udivslot);
#endif

	puart = ioremap(0xe2900c00, sizeof(USFR));
	puart->ulcon = 0x3;
	puart->ucon = 0x7c5;
	puart->ubrdiv = 0x23;
	puart->udivslot = 0x808;

	return;
}

int uart_putchar(char c)
{
	while ((puart->utrstat & (1<<2)) == 0)
		;

	puart->utxh = c;

	return 0;
}

int uart_drv_init(void)
{
	printk("uart_drv init ok \n");

	uart_init();

	uart_putchar('h');
	uart_putchar('e');
	uart_putchar('l');
	uart_putchar('l');
	uart_putchar('o');
	uart_putchar('\n');

	return 0;
}

void uart_drv_exit(void)
{
	printk("uart_drv exit ok \n");

	return;
}

module_init(uart_drv_init);
module_exit(uart_drv_exit);           

2 加入字元裝置驅動的接口

加入對于序列槽 UART3 的 open, release, read, write

open - 115200, 8N1 (init)
	read - return 1 (getchar())
	write - uart_putchar() 每次寫入1個位元組
	release - null           

加入注冊字元裝置和登出字元裝置

struct file_operations fops =
{
	...
};
init -> register_chrdev(245, "notmyttyS3", &f_ops)
exit -> unregister_chrdev(245, "notmyttyS3")           

建立裝置檔案的節點

mknod myttyS3 c 245 3           

測試字元裝置驅動

test write:		echo "hello" > myttyS3
test read:		cat myttyS3           

example code

/* uart_drv.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/cdev.h>

MODULE_LICENSE("GPL");

#define UART_MAJOR	245

struct uart_sfr
{
	int ulcon;
	int ucon;
	int ufcon;
	int umcon;
	int utrstat;
	int uerstat;
	int ufstat;
	int umstat;
	int utxh;
	int urxh;
	int ubrdiv;
	int udivslot;
};

typedef struct uart_sfr USFR;

static volatile USFR *puart;

#define ULCON0		(puart->ulcon)
#define UCON0		(puart->ucon)
#define UBRDIV0		(puart->ubrdiv)
#define UDIVSLOT0	(puart->udivslot)
#define UTRSTAT0	(puart->utrstat)
#define UTXH0		(puart->utxh)
#define URXH0		(puart->urxh)

void uart_init(void)
{
	// set UART SFRs
	ULCON0 = 0x3;
	UCON0 = 0x245;

	// 66Mhz / (115200*16) - 1 = 0x23
	// 66Mhz / (19200*16) - 1 = 0xD5
	UBRDIV0 = 0x23;
	UDIVSLOT0 = 0x808;

	return;
}

char uart_getchar(void)
{
	char c;

	// polling receive status: if buffer is full
	while ((UTRSTAT0 & (1<<0)) == 0)
		;
	
	c = URXH0;
		
	return c;
}

void uart_putchar(char c)
{
	// polling transmit status: if buffer is empty
	while ((UTRSTAT0 & (1<<2)) == 0)
		;
	
	UTXH0 = c;
	
	return;
}

int uart_drv_open(struct inode * inode, struct file * filp)
{
	printk("uart open\n");

	uart_init();

	uart_putchar('o');
	uart_putchar('p');
	uart_putchar('e');
	uart_putchar('n');
	uart_putchar('\n');

	return 0;
}

int uart_drv_release(struct inode * inode, struct file * filp)
{
	printk("uart release\n");

	uart_putchar('c');
	uart_putchar('l');
	uart_putchar('o');
	uart_putchar('s');
	uart_putchar('e');
	uart_putchar('\n');

	return 0;
}

int uart_drv_write(struct file * filp, const char __user * buf, size_t count, loff_t *f_pos)
{
	char c;

	printk("uart write %d bytes\n", count);

	c = *buf;

	uart_putchar(c);

	return 1;
}

int uart_drv_read(struct file * filp, char __user * buf, size_t count, loff_t *f_pos)
{
	char c;

	printk("uart read %d bytes\n", count);

	c = uart_getchar();

	*buf = c;

	return 1;
}

struct file_operations uart_drv_fops = 
{
    .owner = THIS_MODULE,
    .open = uart_drv_open,
    .release = uart_drv_release,
    .write = uart_drv_write,
    .read = uart_drv_read,
};

struct cdev uart_cdev;
dev_t uart_dev_no;

int uart_drv_init(void)
{
	printk("uart_drv init ok \n");

	//register_chrdev(UART_MAJOR, "myttyS3", &uart_drv_fops);

	// use cdev
	uart_dev_no = MKDEV(UART_MAJOR, 3);
	register_chrdev_region(uart_dev_no, 1, "myttyS3");
	cdev_init(&uart_cdev, &uart_drv_fops);
	cdev_add(&uart_cdev, uart_dev_no, 1);

	return 0;
}

void uart_drv_exit(void)
{
	printk("uart_drv exit ok \n");

	//unregister_chrdev(UART_MAJOR, "myttyS3");

	// use cdev 
	unregister_chrdev_region(uart_dev_no, 1);
	cdev_del(&uart_cdev);

	return;
}

module_init(uart_drv_init);
module_exit(uart_drv_exit);           

3 加入應用程式

引入系統調用, open, read, write, close
在 read 和 write 的基礎上,實作簡單的 shell 功能           

example code

/* test.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <stdio.h>
#include <unistd.h>

int fd;

char mygetchar(void)
{
	char c;

	read(fd, &c, 1);

	// echo
	write(fd, &c, 1);

	return c;
}

void myputchar(char c)
{
	write(fd, &c, 1);

	return;
}

void myputs(char * s)
{
	while (*s)
		myputchar(*s++);

	return;
}

void mygets(char * s)
{
	char c;

	while ((c = mygetchar()) != '\r')
		*s++ = c;

	*s = '\0';
	myputs("\r\n");

	return;
}

int main(void)
{
	fd = open("myttyS3", O_RDWR);

	while (1)
	{
		char buf[512];

		myputs("akaedu $ ");
		mygets(buf);
		myputs(buf);
		myputs("\r\n");

	#if 0
		char c;
		c = mygetchar();
		myputchar(c);
	#endif
	}

	close(fd);

	return 0;
}           

uart 驅動

#include <linux/module.h>
#include <linux/fs.h>	// register_chrdev
#include <asm/io.h>
//#include <asm/uaccess.h>

#define MA	243

MODULE_LICENSE("GPL");

struct uart_sfr
{
	int ulcon;
	int ucon;
	int ufcon;
	int umcon;
	int utrstat;
	int uerstat;
	int ufstat;
	int umstat;
	int utxh;
	int urxh;
	int ubrdiv;
	int udivslot,
};

typedef struct uart_sfr USFR;

static volatile USFR *puart;

//#define printk 		noprintk	

int noprintk(char * fmt, ...)
{
	return 0;
}

#define PRINT(x)	printk(#x " = 0x%x\n", x);

int uart_open(struct inode * inode, struct file * filp)
{
	int major = MAJOR(inode->i_rdev);
	int minor = MINOR(inode->i_rdev);
	int * p;
	int i;

	printk("uart open: major %d, minor %d\n", major, minor);

	puart = ioremap(0xe2900000, sizeof(USFR));
	p = (int *)puart;

	PRINT((int)p);	

	for (i = 0; i < sizeof(USFR)/4; i++)
	{
		PRINT(*p++);	
	}

	puart->ufcon = 0;

	return 0;
}

int uart_release(struct inode * inode, struct file * filp)
{
	printk("uart release\n");

	return 0;
}

int uart_putchar(char c)
{
	while ((puart->utrstat & (1<<2)) == 0)
		;

	puart->utxh = c;

	return 0;
}

int myputchar(char c)
{
	if (c == '\n')
		uart_putchar('\r');

	uart_putchar(c);

	return 0;
}

int uart_write(struct file * filp, const char __user * buf, size_t count, loff_t *f_pos)
{
	int i;

	printk("uart write\n");

	printk("buf = %c\n", buf[0]);
	printk("count = %d\n", count);


	for (i = 0; i < count; i++)
	{
		myputchar(buf[i]);
	}


	return count;
}


int uart_drv_ioctl(struct inode * inode, struct file * filp, unsigned int cmd, unsigned long arg)
{
	#define PRINTD(x)	printk(#x " = %d, <%s>\n", x, __FUNCTION__);

	PRINTD(cmd);
	PRINTD((int)arg);

#define UART_SET_BUAD_RATE  0
#define UART_ENABLE_FIFO    1

	switch (cmd)
	{   
	case UART_SET_BUAD_RATE:
		uart_set_baudrate(arg);
		break;
	}   

	return 0;
}


struct file_operations uart_fops = 
{
	.owner = THIS_MODULE,
	.open = uart_open,
	.release = uart_release,
	.write = uart_write,
	.ioctl = uart_ioctl,
};

int uart_init(void)
{
	int rc;

	rc = register_chrdev(MA, "myuart", &uart_fops);

	if (rc < 0)
	{
		printk("register chrdev failed! %d\n", rc);
		return -1;
	}

	printk("uart init ok \n");

	return 0;
}

void uart_exit(void)
{
	printk("uart exit ok \n");

	unregister_chrdev(MA, "myuart");

	return;
}


module_init(uart_init);
module_exit(uart_exit);