前提条件是工作目录在/usr/local/src/下。编写de0_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</usr/local/src/uClinux-dist/linux-2.6.x/include/asm-nios2nommu/io.h>
#include</usr/local/src/uClinux-dist/linux-2.6.x/include/asm-nios2nommu/page.h>
#include</usr/local/src/uClinux-dist/linux-2.6.x/include/asm-nios2nommu/uaccess.h>
#include<linux/sched.h>
#include<linux/fs.h>
#include<linux/proc_fs.h>
#include</usr/local/src/uClinux-dist/linux-2.6.x/include/linux/sysctl.h>
//#include</usr/local/src/uClinux-dist/linux-2.6.x/include/asm-nios2nommu/nios2.h>
#include </usr/local/src/uClinux-dist/linux-2.6.x/include/nios2_system.h>
#define de0_led_major 120 //major number 120
static char de0_led_name[]="de0_led";
static int de0_led_open(struct inode* inode,struct file* file)
{
printk("open the de0_led device,major:%d,minor:%d\n",MAJOR(inode->i_rdev),MINOR(inode->i_rdev));
try_module_get(THIS_MODULE);
return 0;
}
static int de0_led_release(struct inode*inode,struct file*file)
{
printk("release the de0_led device!\n");
module_put(THIS_MODULE);
return 0;
}
static ssize_t de0_led_read(struct file *filp,char __user *buff,size_t len,loff_t *ppos)//32
{
printk("read de0_led device\n");
int bytes_read=0;
bytes_read=na_led->np_piodata;//36
printk("bytes_read=%d\n",bytes_read);
if(copy_to_user(buff,(char*)&bytes_read,sizeof(int)))
{
return -EFAULT;
}
return len;
}
static ssize_t de0_led_write(struct file*file,const char __user*buff,size_t len,loff_t*off)
{
unsigned int data_write=0;
if(copy_from_user((char*)&data_write,buff,sizeof(int)))//47
return -EFAULT;
na_led->np_piodata=data_write;
return len;
}
static struct file_operations de0_led_fops={
owner:THIS_MODULE,
open:de0_led_open,
release:de0_led_release,
read:de0_led_read,
write:de0_led_write//57
};
static int __init led_init(void)
{
printk("Init the module!\n");
int err;
if((err=register_chrdev(de0_led_major,de0_led_name,&de0_led_fops))<0)
{
printk("register_chrdev failed!error code=%d\n",err);
return err;
}
return 0;
}
static void __exit led_exit(void)
{
printk("exit module!!\n");
unregister_chrdev(de0_led_major,de0_led_name);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
将编写好的驱动文件放到linux-2.6/drivers/char下,修改该文件夹下的Kconfig和Makefile文件。
Kconfig中添加:
config DE0_LED
tristate”Nios led support”
help
de0_led driver
在Makefile中添加:
obj-$(CONFIG_DE0_LED) +=de0_led.o
然后运行make menuonfig,对内核进行配置。为了让操作系统支持动态加载,必须在Linux Kernel Configuration中选中Loadable module support,然后再进入Linux Kernel Configuration的Device Drivers选项,找到Character Devices,进入,找到新加入的Nios led support,选择为M,可动态加载。然后保存退出。
用户程序的处理跟hello world一样,现在src文件夹下用交叉工具链编译,将生成的可执行文件复制到romfs/bin下。
接下来就是编译内核了,make。
这里总结一下写字符型驱动程序的步骤:
驱动程序编写完成后,要用用户程序验证其是否正常工作。用户程序中不会再出现硬件寄存器,而是打开文件,读写文件,关闭文件等操作。用户把所有设备都当做文件来处理。Led驱动的测试程序如下:
#include <stdio.h>
//#include <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 i=0;
unsigned char buff[]={0xfe,0xfe,0xfe,0xfe};
if((fd=open("/dev/de0_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("write error");
exit(1);
}
mydelay(100000);
i++;
buff[4]=i;
}
}
运行uclinux系统后,运行Altera Nios II EDS 11.0,与系统通信。可加载模块在uclinux下的路径为:/lib/modules/2.6.19-uc1/kernel/drivers/char/
进入到这个目录下,运行insmod de0_led.ko安装内核模块
然后建立设备节点,运行mknod /dev/de0_led c 120 0
其中120是主设备号,0是次设备号,c代表char字符设备。
成功注册设备并建立设备节点后,返回到根目录:cd /
运行用户程序,输入命令:led_app
可以看到LED灯变亮,证明驱动正常工作。
后记:用户测试程序的功能是死循环,灯应该闪烁,但是实际效果并不是闪烁,而是一直亮。后来仔细检查了驱动程序,发现了问题,将设备写函数改为下面的行驶后,LED就会按照用户程序规定的那样闪烁了。
static ssize_t de0_led_write(struct file*file,const char __user*buff,size_t len,loff_t*off)
{
char data_write[4]={0};
if(copy_from_user(data_write,buff,sizeof(int)))//47
return -EFAULT;
na_led->np_piodata=(data_write[0]|(data_write[1]<<8)|(data_write[2]<<16)|(data_write[3]<<24));
return len;
}
先前的驱动程序是照搬别人的,那个驱动有问题,强制将int变量的地址转化为char*型,是会出现问题的,如果int变量的地址值大于8位,强制转化为char*后,只保留了后8位地址值,前面的都没了,像这个地址写入数据,不会反应到LED上。而且还有可能会使系统程序崩溃。Uclinux系统是没有内存保护的,就是说用户一不小心可能访问了系统的内存空间,修改了系统的代码,内存都是不管的,所以编写程序时要格外小心。