续“uclinux在niosII上的移植——添加应用程序、驱动程序(3)”。
第三部分仅做了一个没有实现实际功能的驱程,下面增加驱动程序的功能,访问板子上的硬件。
目的:访问sopc中的组件的pio_switch和red_led(在sopc builder中搭建起来的),通过添加驱动程序的方法,实现读取板子上switch的值,点亮相应的红色led灯。
/////////////////////////////////////////////////////////////////////////////
- 可以将pio_switch与red_led结合起来,看成一个设备sw_led
读sw_led设备时,从switch_pio读取
写sw_led设备时,写入led_red
- 虽然物理上不是一个设备,但驱程可以屏蔽硬件细节,统一提供一个简单访问接口
//////////////////////////////////////////////////////////////////////////////
将sw_led作为字符设备实现,在驱程源文件中填充file_operations,向系统注册成为字符设备调用register_chrdev。
在nios2-linux/linux-2.6/include/linux/fs.h中定义了file_operations结构体,结构体中定义了大量的函数接口。在本驱动程序中指实现open()、release()、read()、write().
注册字符设备函数register_chrdev()在nios2-linux/linux-2.6/fs/char_dev.c中。
驱程源码sw_led.c:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/version.h> #include <linux/init.h> #include <linux/types.h> #include <linux/string.h> #include <linux/slab.h> #include <asm-nios2/io.h> #include <asm-nios2/page.h> #include <asm-nios2/uaccess.h> #include <linux/sched.h> #include <linux/fs.h> #include <linux/proc_fs.h> #include <linux/sysctl.h> #include <asm-nios2/nios2.h> #define sw_led_MAJOR 125 //注册设备号为125 static char sw_led_name[]="sw_led"; static int sw_led_open(struct inode* inode,struct file * file) { printk("Enter sw_led_open, major :%d , minor : %d \n",MAJOR(inode->i_rdev),MINOR(inode->i_rdev)); return 0; } static int sw_led_release(struct inode* inode,struct file * file) { printk("Enter sw_led_release\n"); return 0; } static ssize_t sw_led_read (struct file * file, char __user * buff, size_t len, loff_t * off) { printk("Enter sw_led_read\n"); unsigned int sw_data; sw_data=na_pio_switch->np_piodata; printk("sw_data=%d\n",sw_data); if(copy_to_user(buff,(char *)&sw_data,sizeof(int))) return -EFAULT; // na_led_red->np_piodata=sw_data; // outl(sw_data,&(na_led_red->np_piodata)); return len; } static ssize_t sw_led_write (struct file * file, const char __user * buff, size_t len, loff_t * off) { unsigned int led_data; led_data=0; if(copy_from_user((char *)&led_data,buff,sizeof(int))) return -EFAULT; printk("sw_led_write got %d from user App\n",led_data); na_pio_red_led->np_piodata=led_data; return len; } static struct file_operations sw_led_fops={ owner:THIS_MODULE, open:sw_led_open, release:sw_led_release, read:sw_led_read, write:sw_led_write }; static int __init enter_module(void) { printk("<1>Enter enter_module\n"); int err; if((err=register_chrdev(sw_led_MAJOR,sw_led_name,&sw_led_fops))<0) { printk("register_chrdev fail!,error code = %d\n",err); return err; } return 0; } static void __exit exit_module(void) { printk("<0>Enter exit_module,Exiting...\n"); // int res; // res= unregister_chrdev(sw_led_MAJOR,sw_led_name); printk("unregister_chrdev ,result code\n"); } module_init(enter_module); module_exit(exit_module); MODULE_AUTHOR("dxzhang@ustc.edu"); MODULE_DESCRIPTION("Driver for sw_led"); MODULE_LICENSE("GPL");
源码放在nios2-linux/linux-2.6/drivers/char下,编译该目录下Kconfig和Makefile文件,和第(3)部分方法一样。
Kconfig中添加:
config SW_LED
tristate “Nios switch and led support”
help
This driver supports the nios2 switches and leds.
Makefile中添加:
obj-$(CONFIG_SW_LED) +=sw_led.o
测试程序myhello.c:
#include <stdio.h> #include <http://www.cnblogs.com/linux-2.6.x/include/asm/nios2.h> #include <string.h> #include <malloc.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <sys/ioctl.h> void mydelay(int count) { int i; int j; j=0; for(i=0;i<count;i++) { j=j+i; } } int main() { int fd; int count; char buff[4]; if((fd=open("/dev/sw_led",O_RDWR))==-1) { perror("open eror"); exit(1); } while(1) { if(count=read(fd,(char *)buff,4)!=4) { perror("read error"); exit(1); } mydelay(100000); if(count=write(fd,(char *)buff,4)!=4) { perror("read error"); exit(1); } mydelay(100000); } }
注:其中open()函数的定义,表头文件为#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h>
open("/dev/sw_led",O_RDWR): 以可读可写的方式打开设备文件/dev/sw_led
read(fd,(char *)buff,4) :设备文件的内容读到buff[4]中,即读取switch_pio
write(fd,(char *)buff,4) :buff[4]的内容写到设备文件,即写red_led
测试程序放在nios2-linux/uClinux-dist/user/myhello下,按照前面添加应用程序相同的方法修改user下的Kconfig和Makefile。
配置内核和应用程序。下载,测试。
uClinux启动之后,进入模块sw_led所在目录( romfs/lib/modules/2.6.30/kernel/drivers/char/ )
加载模块: insmod sw_led.ko
创建设备节点: mknod /dev/sw_led c 125 0 //c表示为字符设备;125和0为major/minor number
运行应用程序访问外设:myhello
////////////////////////////////////////////////////////////////////////////////
uClinux_Nios2_Devicedrivers_and_Testapplication_Documentation.pdf:
character devices are accessed through device files, usually located in /dev. The major number tells you which driver handles which device file. The minor number is used only by the driver itself to differentiate which device it’s operating on, just in case the driver handles more than one device (discussed later). When the system was installed, all of those device files were created by the mknod command. You don’t have to put your device files into /dev, but it’s done by convention. Linux put his device files in /dev, and so should you. However, when creating a device file for testing purposes, it’s probably OK to place it in your working directory where you compile the kernel module. Just be sure to put it in the right place when you’re done writing the device driver
////////////////////////////////////////////////////////////////////////////////
测试结果:
测试成功后,可以加入启动脚本中:
在uClinux-dist/vendors/Altera/nios2/rc文件中加入:modprobe sw_led
在uClinux-dist/vendors/Altera/nios2/romfs_list中加入:nod /dev/sw_led 666 0 0 c 125 0
重新编译,下载测试。/dev下已经有了sw_led设备文件。
现在可以直接运行myhello。
关于设备驱动可读文档uClinux_Nios2_Devicedrivers_and_Testapplication_Documentation.pdf
后记:若myhello.c程序为:
#include <stdio.h> #include <http://www.cnblogs.com/linux-2.6.x/include/asm/nios2.h> int main() { while(1) { *na_pio_red_led = *na_pio_switch; } return 0; }
则程序不能执行,使用nios2-linux-objdump工具反汇编elf格式的myhello.gdb文件,可以看到依次读取了pio_switch的四个域(np_piodata、np_piodirection、np_piointerruptmask、np_pioedgecapture),写到pio-red_led相对应的四个域中
但若将myhello.c另作修改:
#include <stdio.h> #include <http://www.cnblogs.com/linux-2.6.x/include/asm/nios2.h> int main() { while(1) { na_pio_red_led->np_piodata = na_pio_switch->np_piodata; } return 0; }
则程序可以成功执行,拨动相应switch,可以点亮相应led。
其中,不明白的问题:
(1)为什么读取四个域写到相应四个域中,测试不成功,而只能进行数据域的读取?
(2)两种测试成功的myhello.c,在内部执行的时候有什么不同?