一个字符( 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-gccobj-m := mychar_drv.oKDIR := /home/akaedu/teacher_li/linux-2.6.35.7/all:make mychar_testmake -C $(KDIR)SUBDIRS=$(PWD) modulesls -l *.ko mychar_testclean:-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 usbBlock 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_tdev_t;/* linux/kdev_t.h *?#define MINORBITS20#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 。
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 cdevuart_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_operationsstruct 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_userMODULE_LICENSE("GPL");#define MA240volatile 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_putcharuart_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 regspuart = 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);#endifpuart = 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, 8电脑N1 (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" > myttyS3test 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_MAJOR245struct 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 SFRsULCON0 = 0x3;UCON0 = 0x245;// 66Mhz / (115200*16) - 1 = 0x23// 66Mhz / (19200*16) - 1 = 0xD5UBRDIV0 = 0x23;UDIVSLOT0 = 0x808;return;}char uart_getchar(void){char c;// polling receive status: if buffer is fullwhile ((UTRSTAT0 & (1<<0)) == 0);c = URXH0;return c;}void uart_putchar(char c){// polling transmit status: if buffer is emptywhile ((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 cdevuart_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);// echowrite(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 0char 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 MA243MODULE_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 noprintkint 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 1switch (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);
电脑