驱动内核模块编写步骤
一、模块编写
1.模块组成(以下为一个模板实例)
一个内核模块一共由四部分组成:头文件,实现函数,注册功能以及模块描述:
实例文件(该文件完成了最简单的led灯的驱动):vim mymod.c
//头文件:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include "mymod.h"
#define MA 500
#define MI 0
#define GPX2CON 0x11000c40
#define GPX2DAT 0x11000c44
static dev_t devno = 0;
static int devNum = 1;
static char* devName = "myDev";
static struct cdev mydev;
static char kbuf[32]={0};
static int flag = 0;
static char* gpx2con = NULL;
static char* gpx2dat = NULL;
static const struct file_operations myops={
.open = myOpen,
.release = myRelease,
.read = myRead,
.write = myWrite,
.unlocked_ioctl = myIoctl
};
//实现函数(至少拥有mymod_init和mymod_exit两个函数):
int myOpen(struct inode *pi,struct file *pf)
{
printk("open OK\n");
return 0;
}
int myRelease(struct inode *pi,struct file *pf)
{
printk("release OK\n");
return 0;
}
ssize_t myRead(struct file *pf,char __user *ubuf,size_t len,loff_t *poff)
{
int ret = -1;
ret = copy_to_user(ubuf,kbuf,len);
if(ret != 0)
return -1;
return len;
}
ssize_t myWrite(struct file *pf, const char __user *ubuf, size_t len, loff_t *poff)
{
int ret = -1;
ret = copy_from_user(kbuf,ubuf,len);
if(ret != 0)
return -1;
return len;
}
long myIoctl(struct file *pf,unsigned int cmd,unsigned long arg)
{
if(cmd == LED_ON)
{
writel(readl(gpx2dat) | (0x1<<7) ,gpx2dat);
printk("led on\n");
}
else if(cmd == LED_OFF)
{
writel(readl(gpx2dat) & (~0x1<<7) ,gpx2dat);
printk("led off\n");
}
return 0;
}
static int mymod_init(void)
{
int ret = -1;
//内核相关的部分
//1.注册
devno = MKDEV(MA,MI);
ret = register_chrdev_region(devno,devNum,devName);
if(ret != 0)
return ret;
//2.初始化
cdev_init(&mydev,&myops);
//3.添加到系统
ret = cdev_add(&mydev,devno,devNum);
if(0!=ret)
{
unregister_chrdev_region(devno,devNum);
return ret;
}
//硬件相关的部分
//1.将实际的物理地址映射为虚拟地址
gpx2con = ioremap(GPX2CON,4);
gpx2dat = ioremap(GPX2DAT,4);
//2.对硬件进行初始化操作
writel((readl(gpx2con) & ~(0xf<<28)) | (0x1<<28),gpx2con);
writel(readl(gpx2dat) & ~(0x1<<7) ,gpx2dat);
printk("mod init OK\n");
return 0;
}
static void mymod_exit(void)
{
//内核部分
//1.删除设备
cdev_del(&mydev);
//2.注销设备
unregister_chrdev_region(devno,devNum);
//硬件部分
//1.复位(不是必须操作)
writel(readl(gpx2dat) & ~(0xf<<7), gpx2dat);
//2.地址回收(必须操作)
iounmap(gpx2con);
iounmap(gpx2dat);
printk("mod exit OK\n");
return;
}
//注册功能:
module_init(mymod_init);
module_exit(mymod_exit);
//模块信息描述:
MODULE_LICENSE("GPL");//该模块信息最为重要,表示免费开源."BSD"表示部分开源
实例头文件:vim mymod.h
#ifndef _MYMOD_H
#define _MYMOD_H
#define LED_ON _IOW('L',0,int)
#define LED_OFF _IOW('L',1,int)
#endif
测试文件1(不涉及硬件):vim main.c
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pthread.h>
static char rbuf[32] = {0};
static char wbuf[32] = "123456";
void func1(int *fd)
{
int newfd = *fd;
while(1)
{
write(newfd,wbuf,sizeof(wbuf));
write(newfd,"abcdef",sizeof(wbuf));
}
}
int main()
{
int fd = open("/dev/mydev",O_RDWR);
if(fd<0)
return -1;
pthread_t thread1;
pthread_create(&thread1,NULL,(void *)func1,&fd);
pthread_detach(thread1);
while(1)
{
read(fd,rbuf,16);
printf("rbuf %s\n",rbuf);
}
close(fd);
return 0;
}
测试文件2:(涉及led灯)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "mymod.h"
#include <pthread.h>
int main()
{
int fd = open("/dev/mydev",O_RDWR);
if(fd<0)
return -1;
while(1)
{
ioctl(fd,LED_ON);
sleep(1);
ioctl(fd,LED_OFF);
sleep(1);
}
close(fd);
}
说明:驱动文件主要分为与内核相接的接口和与硬件相接两个部分,最主要的就是驱动文件中的init和exit函数其书写格式和实例代码一致,唯一可能不一样的地方在于,针对每一种硬件驱动的硬件接口部分需要结合芯片手册进行设计。
二、模块编译
制定一个Makefile文件:(内核文件必须为板子上移植的内核)
#module templet
ifeq($(KERNELRELEASE),)
KERNELDIR ?= /home/farsight/linux-3.14
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
cp -af *.ko /home/farsight/arm
u:
arm-linux-gcc main.c -lpthread
cp -af a.out /home/farsight/arm
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean
else
obj -m := mymod.o
endif
解释说明:
KERNELDIR ?= /home/farsight/linux-3.14(这个路径必须是板子上跑起来的内核路径)
/home/farsight/arm(这个路径必须是nfs注册的路径,具体查看命令cat /etc/exports
)
mymod.o(该文件名必须和自己写的驱动文件同名,也就是说你写的驱动文件必须为mymod.c)
三、模块移植
编译生成之后我们可以在板子的文件系统上面看见一个mymod.ko的文件
然后我们可以插入驱动文件:
insmod mymod.ko
注册字符设备:
mknod /dev/mydev c 500 0(/dev/mydev必须和测试文件中的打开文件命名相同)
查看驱动文件:
lsmod
删除驱动文件:
rmmod mymod
错误提示:
解决方法:
mkdir /lib/modules
mkdir /lib/modules/3.14.0