一只鱼 さんのプロフィールEE小站フォトブログリストその他 ![]() | ヘルプ |
|
5月30日 Linux 2.6字符设备驱动程序样例写这些东西还真是花时间啊,继续昨天的内容。
我写驱动的时候总希望能找到一个样例参考一下,可惜网上的例子基本找不到。还好友善之臂的文档里有些例子,但是说的很不详细,要是直接输入会有很多的编译错误。我的这个例子是一个控制LED的例子,用Linux就控制LED,当然是相当的弱智的哈哈。我用的是S3C2410,LED连接在GPB7~10上,灌电流方式驱动,IO配置寄存器GPBCON的物理地址0x56000010,IO数据寄存器GPBDAT的物理地址0x56000014。程序中的几个关键点,在我昨天的BLOG中有叙述。
首先编写一个叫做leds_test.c的文件,内容如下:
#include <linux/config.h>
#include <linux/fs.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/init.h> //Please configure your kernel first to use the following headers, because the directory "asm" is a short cut to your arch's "asm" directory.
//So do the headers in the "hardware" sub directory.
#include <asm/io.h> //This header is for ioremap(), iounmap(). #include <asm/uaccess.h> //This header is for get_user(), put_user(). #define NAME "led_test"
MODULE_AUTHOR("Lu Xianzi <cosine@126.com>"); //This line and the following 4 lines can be omitted.
MODULE_DESCRIPTION("LED Test Driver"); MODULE_LICENSE("GPL"); module_param(major, int, 0);
MODULE_PARM_DESC(major, "Major device number"); static int major = 231; //Define device major
unsigned long * pREG; //Definition of register base. static ssize_t led_test_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
{ char buf[256]; size_t i; for (i = 0; i < len && i < 254; i++)
if (get_user(buf[i], data + i)) return -EFAULT; buf[i] = '\0'; printk("LED Test - write: user_data %s\n", buf); return (len < 255 ? len : 255); } static ssize_t led_test_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
{ char rbuf[4]; size_t i; long tmp; tmp = * (volatile unsigned long *)(pREG + 1); rbuf[0] = tmp % 256; rbuf[1] = (tmp >> 8) % 256; rbuf[2] = (tmp >> 16) % 256; rbuf[3] = (tmp >> 24) % 256; if (len > 4) return 0; for (i = 0; i < len && i < 4; i++) if (put_user(rbuf[i], buf + i)) return -EFAULT; printk("LED Test - read\n"); return (len < 4 ? len : 4); } static int led_test_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{ unsigned long tmp; printk("LED Test - ioctl: param %u %lu\n", cmd, arg);
switch (cmd) { case 0: case 1: if (arg > 3) return -EINVAL; if (!cmd) * (volatile unsigned long *)(pREG + 1) |= (0x80 << arg); else * (volatile unsigned long *)(pREG + 1) &= ~(0x80 << arg); tmp = * (volatile unsigned long *)pREG; printk("GPBCON = 0x%lx\n", tmp); tmp = * (volatile unsigned long *)(pREG + 1); printk("GPBDAT = 0x%lx\n", tmp); break; default: return -EINVAL; } return 1; } static int led_test_open(struct inode *inode, struct file *file)
{ unsigned m = iminor(inode); if (m > 63) return -EINVAL; printk("LED Test driver opened!\n"); return nonseekable_open(inode, file); } static int led_test_release(struct inode *inode, struct file *file)
{ printk("LED Test driver released!\n"); return 0; } static struct file_operations led_test_fops = { .owner = THIS_MODULE, .ioctl = led_test_ioctl, .write = led_test_write, .read = led_test_read, .open = led_test_open, .release = led_test_release, }; static int __init led_test_init(void)
{ int ret; unsigned long tmp; printk("LXZ LED Test Driver.\n"); ret = register_chrdev(major, NAME, &led_test_fops);
if (ret < 0) { printk("Unable to register character device!\n"); return ret; } pREG = ioremap(0x56000010, 0x20); printk("Virtual addr base = 0x%lx\n", (unsigned long)pREG); tmp = * (volatile unsigned long *)pREG; printk("GPBCON = 0x%lx\n", tmp); tmp = * (volatile unsigned long *)(pREG + 1); printk("GPBDAT = 0x%lx\n", tmp); printk("Seting LED Test Driver...\n"); * (volatile unsigned long *)pREG = 0x155555; * (volatile unsigned long *)(pREG + 1) = 0xfff; tmp = * (volatile unsigned long *)pREG; printk("GPBCON = 0x%lx\n", tmp); tmp = * (volatile unsigned long *)(pREG + 1); printk("GPBDAT = 0x%lx\n", tmp); printk("LED Test Driver initiated.\n"); return 0; } static void __exit led_test_cleanup(void)
{ int ret; iounmap(pREG); ret = unregister_chrdev(major, NAME); if (ret < 0) printk("Unable to register character device!\n"); else
printk("LED Test Driver unloaded!"); } module_init(led_test_init);
module_exit(led_test_cleanup); 在驱动程序的目录下建立一个名为“Makefile”的文件,其内容只有一行: obj-m := leds_test.o
编译之,我的linux内核存在/home/lxz/linux-2.6.11.7,所以编译命令为
make -k -C /home/lxz/linux-2.6.11.7 SUBDIRS=$PWD modules
编译后生成几个文件,其中leds_test.ko是我们需要的驱动模块。
然后在另外一个目录中编写一个叫做leds.c的文件,其内容如下
#include <stdio.h>
#include <fcntl.h> int main(int argc, char **argv) { int fd; int on, led_no; char buf[256] = {"1234567890"}; unsigned long tmp; if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2], "%d", &on) != 1 || on < 0 || on > 1 || led_no < 0 || led_no >3) { fprintf(stderr, "Usage: leds led_no 0|1\n"); exit(1); } fd = open("/dev/leds", O_RDWR); if (fd < 0) { perror("open device leds"); exit(1); } ioctl(fd, on, led_no); write(fd, buf, 10); read(fd, buf, 4); tmp = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); printf("User program read: GPBDAT = 0x%lx\n", tmp); close(fd); return 0; }
编译之,输入 arm-linux-gcc -o leds leds.c
然后把生成的leds_test.ko和leds这2个文件拷贝到你的文件系统中,如/home下,启动Linux。之后的过程如下:
/ # cd /home
/home # insmod leds_test.ko Lxz LED Test Driver. Virtual addr base = 0xc485e010 GPBCON = 0x44555 GPBDAT = 0x540 Seting LED Test Driver... GPBCON = 0x155555 GPBDAT = 0x7ff LED Test Driver initiated. /home # mknod /dev/leds c 231 0 /home # ./leds 0 1 LED Test driver opened! LED Test - ioctl: param 1 0 GPBCON = 0x155555 GPBDAT = 0x77f LED Test - write: user_data 1234567890 LED Test - read User proLED Test driver released! gram read: GPBDAT = 0x77f /home # 这里有个非常有趣的事情,你会发现内核的printk函数比客户程序的printf函数打印时出现一些混乱,我想应该是因为Linux不是一个实时系统,内核和用户程序分时执行的结果。
如果要卸载驱动模块,如下:
/ # rmmod leds_test
LED Test Driver unloaded!
/ #
5月29日 Linux字符设备驱动(转载)这篇文章描述了在Linux 2.4下,如何建立一个虚拟的设备,对初学者来说很有帮助。原文地址:http://dev.yesky.com/186/2623186.shtml
Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open ()、close ()、read ()、write () 等。
Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。 下面我们来假设一个非常简单的虚拟字符设备:这个设备中只有一个4个字节的全局变量int global_var,而这个设备的名字叫做"gobalvar"。对"gobalvar"设备的读写等操作即是对其中全局变量global_var的操作。 驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备:
其中,register_chrdev函数中的参数MAJOR_NUM为主设备号,"gobalvar"为设备名,gobalvar_fops为包含基本函数入口点的结构体,类型为file_operations。当gobalvar模块被加载时,gobalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。 与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的"反函数" unregister_chrdev():
随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其中的一部分。对于字符设备来说,要提供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()、llseek()、poll()等。 open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open () 函数:
其中参数inode为设备特殊文件的inode (索引结点) 结构的指针,参数file是指向这一设备的文件结构的指针。open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode-> i - rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误) 等; release()函数 当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数:
release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。 read()函数 当对设备特殊文件进行read() 系统调用时,将调用驱动程序read() 函数:
用来从设备中读取数据。当该函数指针被赋为NULL 值时,将导致read 系统调用出错并返回-EINVAL("Invalid argument,非法参数")。函数返回非负值表示成功读取的字节数(返回值为"signed size"数据类型,通常就是目标平台上的固有整数类型)。 globalvar_read函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:
write( ) 函数 当设备特殊文件进行write () 系统调用时,将调用驱动程序的write () 函数:
向设备发送数据。如果没有这个函数,write 系统调用会向调用程序返回一个-EINVAL。如果返回值非负,则表示成功写入的字节数。 globalvar_write函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:
ioctl() 函数 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:
unsigned int参数为设备驱动程序要执行的命令的代码,由用户自定义,unsigned long参数为相应的命令提供参数,类型可以是整型、指针等。如果设备不提供ioctl 入口点,则对于任何内核未预先定义的请求,ioctl 系统调用将返回错误(-ENOTTY,"No such ioctl fordevice,该设备无此ioctl 命令")。如果该设备方法返回一个非负值,那么该值会被返回给调用程序以表示调用成功。 llseek()函数 该函数用来修改文件的当前读写位置,并将新位置作为(正的)返回值返回,原型为:
poll()函数 poll 方法是poll 和select 这两个系统调用的后端实现,用来查询设备是否可读或可写,或是否处于某种特殊状态,原型为:
设备"gobalvar"的驱动程序的这些函数应分别命名为gobalvar_open、gobalvar_ release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此设备"gobalvar"的基本入口点结构变量gobalvar_fops 赋值如下:
上述代码中对gobalvar_fops的初始化方法并不是标准C所支持的,属于GNU扩展语法。 完整的globalvar.c文件源代码如下:
运行:
编译代码,运行:
加载globalvar模块,再运行:
发现其中多出了"254 globalvar"一行,如下图: 接着我们可以运行:
创建设备节点,用户进程通过/dev/globalvar这个路径就可以访问到这个全局变量虚拟设备了。我们写一个用户态的程序globalvartest.c来验证上述设备:
编译上述文件:
运行
可以发现"globalvar"设备可以正确的读写。 Linux 2.6 内核下字符设备(Character Device)驱动编写概述做人要厚道,转载请注明。有人摘录我BLOG中的话当作自己说的。我认为只要能找出出处的摘录,都会注明来源,以方便阅读的人做进一步的搜索。 花了4天的时间基本整明白了怎么写一个字符设备的驱动,呵呵,我也不知道原理,紧紧是从网上找到了很多文章,加以综合,搞出一个不明原理的HOWTO,趁我的大脑还没有变成浆糊,把这些写出来。内核移植和文件系统构建部分先暂缓。 向大家推荐一个网站,http://www.linuxforum.net/index.php,上面的版主很热心,而且之前的文章很多,可以给大家很大的帮助。 什么是字符设备?我也搞不清楚,哈哈,快要毕业了,速成的结果。目前我弄明白的是:字符设备是以字节为单位来读写的,与字符设备相对应的,块设备是以块为单位来读写的。例如我在总线上扩展的FPGA,它的控制字映射到总线上,每次读写一个字(16位)。 在Linux下和无操作系统情况下,对总线上地址的访问是不同的,Linux提供的内存虚拟内存机制使用户程序无法直接接触到物理内存——这就需要驱动程序这个桥梁。我们先从用户的角度出发,看看怎么使用设备。做人要厚道,下面这段文字来自友善之臂的文档(这个文档可以在他们的主页上下到,http://www.arm9.com.cn/product_more.asp?id=1185)
目前我的理解是驱动程序所要做的,是将硬件设备(通常是物理地址)与文件操作相关联。当然,我觉得这句话不太全面,以后我学明白原理以后再回来改,FIXME。 驱动程序需要完成的工作有:初始化设备、管理设备、提供文件读写操作的接口、处理设备出现的错误等。 那么驱动程序怎么才能被内核使用呢?或者说怎么样才能把驱动程序加入我的系统中呢?有两种方式:一是把驱动程序直接编译进内核;二是使用模块加载的方式。我采用的是第二种。第一种的细节问题我没有怎么研究,大致上就是写好驱动程序之后,放到内核代码的目录里,修改Makefile文件,与内核一同编译。可以参考《Linux 字符设备驱动程序的设计》,潘俊强、刘莉,杭州应用工程技术学院学报,第12卷第4期,2000年12月。我在百度上搜到这个文章而且下载的,应该还能搜到。 先说下我调试模块的方法,在MTD中留个USER分区,然后用VIVI将USER分区的映像用串口下载到NAND Flash,启动内核,挂载USER分区。如果把驱动程序模块和使用驱动程序模块的测试程序都放在根文件系统里,每次都下载实在比较费事。当然,如果你的板子已经能在Linux下访问网络,那太好了,nfs也好,tftp也好,速度就更快了。 下面开始Step by Step:
在编写内核驱动程序的时候,需要注意的有这几点(建议你看完下一篇文章之后再回来看这下面的东西):
5月25日 回到Linux —— 一些ARM Linux开发琐事集锦经过这么久,终于回到我写这个Blog的原因上了,我又开始搞ARM Linux了。
很后悔之前没有把很多问题写清楚,1年多了,我也忘得差不多了。幸好由于是虚拟机开发,Linux开发环境和我过去在Linux下写的代码在经历了几次系统重装后还健在,这个星期花了3~4天的时间基本上把之前的东西捡起来了。今天主要记录我遗漏的导致这个星期花很多时间整的内容。 首先是Source Navigator的安装,我想在本本上也来个Linux,安装Source Navigator的时候就遇到了问题。安装说明里如是说,解压之后:
mkdir snbuild
cd snbuild ../sourcenav-5.20/configure --prefix=/opt/sourcenav make (become root) make install 我在错误的地方(解压之后的文件夹里)建立了snbuild,网上搜了下还有不少人和我犯同样错误,这个会导致说某个文件No rule to make的错误。 写个提示符带目录名的: /home/lxz # tar xvzf sourcenav-5.2b2 -C ./ /home/lxz # mkdir snbuild /home/lxz # cd snbuild /home/lxz/snbuild # ../sourcenav-5.2b2/configure --prefix=/opt/sourcenav /home/lxz/snbuild # make (become root) /home/lxz/snbuild # make install 还有个错误,说什么“OSF*)”处Unexpected token )之类的,这个好像是Linux各种包安装的问题,另外路径错误好像也会有这个提示,我把Suse 10.1全部安装之后就没有这个问题了。 第二个,U-Boot网络下载内核、文件系统使用NFS,Windows下可以安装Omni NFS 5.2。这个软件的共享版支持2个用户连接,用来下载内核是足够了。另外使用Omni NFS的时候请把Windows防火墙禁用。
第三,之前我遇到的无法加载根文件系统或者说Busybox无法运行的问题已经找到原因了,有这几个方面共同作用:
1.S3C2410 NAND读写的ECC问题,必须取消ECC,或者使用Bootloader将NAND中的文件系统拷贝到RAM中做ramdisk 2.Busybox工作需要/lib下的共享库文件 3.Busybox静态库(即不使用/lib下的库文件)编译方式有问题 4.根文件系统中必须有“/dev”和“/mnt”,否则无法挂载console 目前我使用我的开发板自带的库文件,以及编译后的Busybox 1.00,成功加载root文件系统(cramfs格式,ramdisk)。解释下,ramdisk、bon、mtdblock是一个类别的东西,cramfs、jffs、yaffs、ext2是一个类别的东西,例如你可以在bon上用cramfs,也可以在ramdisk里用cramfs。 第四,mtdblock和bon的关系,目前我的理解是他们都是一种分区方式,当然需要相应的驱动,在vivi中用bon命令可以对NAND进行分区,如bon part add 0 192k 2m,bon命令分区过的NAND需要重新烧写vivi,而mtdblock分区的改变则不用。另外,bon和mtdblock信息是可以同时存在的。
第五,U-Boot的NAND WRITE命令在烧写NAND时不会自动擦除NAND Sector(vivi 会自动擦除的),需要手动进行,否则写入数据就不对了。
第六,ARM交叉编译器必须解压缩到/usr/local/arm,否则会提示编译器安装错误。
时隔一年,再次搞ARM Linux,发现能百度或者Google到的关于遇到的问题的中文文章还是这么少,甚至还有1997年的。希望我的这些琐事可以给你带来些帮助。 5月15日 发现TI 2407A的一个BUG今天调串口程序,发现我的2407串口程序居然在有定时器中断时无法正常工作。进而研究发现TI的一个小BUG。赶快写下来,不然过几天我又忘记了。
我试过的中断优先级如下:
INT2 --- T3PINT
INT5 --- SCIRX,SCITX
和
INT1 --- SCIRX,SCITX
INT2 --- T3PINT
以及
INT1 --- SCIRX
INT2 --- T3PINT
INT5 --- SCITX
都出现一个问题——SCITX的中断有可能无法进入。而且如果SCIRX和SCITX处于同一个优先级,整个这个级别的中断(如INT1或INT5)都无法进入;这样造成的后果是SCIRX中断也无法进入,进而触发SCIRXST中的RX Err和OE位,使得SCI模块需要重启才能正常工作。
于是分析2407的中断结构,发现出现问题时外设中断请求PIRQ1中的SCITX请求位为1,按TI的Datasheet这个请求是发生中断时被内核自动清除的,而且正常情况下也是如此。于是只好人为在SCITX中断程序里向PIACKR1里的SCITX位写1来响应中断,且最好在中断程序的首尾都加上对这个位的操作。
哈哈,原理很抽象啊,说说这个BUG的表象吧,如果你遇到:发送串口数据后,同一优先级的中断,如CAN,SPI,SCI发送,PDPINTA/B,ADCINT(高优先模式),XINT1/2等这些中断无法响应,就是这个BUG造成的。
当然,这可能只是一批芯片的问题,我遇到问题的芯片的DINR = 0x0521,芯片型号批号TMS320LF2407APGEA CA-66A3TKW G4。
解决方法举例(不是100%的解决):
void interrupt INT?_handler()
{
PIACKR1 = 0x0200;
// 你的中断服务代码
PIACKR1 = 0x0200;
}
当然别忘了清除相应的IFR位。
这样做仍然有非常低的概率出现中断无法响应的情况,请注意!以我的串口程序为例,以115200波特率传输,T3PR为20ms,目前测试到结果是,传输字节数大约500kb就会出一次错。 |
|
|