本节知识:
硬件访问知识点:
1.I/O空间和内存空间的概念:I/O空间一般只有64K,内存空间有4G,他俩是分开的总线。
切记ARM只有内存空间,一般只有X86才有I/O空间。
2.I/O端口:当一个寄存器或内存位于I/O空间时候,称其为I/O端口。
3.I/O内存:当一个寄存器或内存位于内存空间时候,称其为I/O内存。
4.操作I/O端口的流程:第一种是 在设备驱动加载和open的时候申请I/O端口资源request_region()------------->>在操作驱动的过程中进行inb(),outb()等操作--------------->>release_region()释放资源。
这种方式没有从I/O空间映射到内存空间。
第二种是 request_region()------------->>ioport_map()------------->>ioread8,iowrite8等------------->>ioport_unmap()------------->>release_region()
这种方式映射到了内存空间。
5.操作I/O内存的流程:申请I/O内存request_mem_region()------------->>ioremap()把物理内存和内核态的虚拟内存建立联系------------->>ioread8,iowrite8等------------->>iounmap()断开映射------------>>release_mem_region()释放资源。详细见宋保华linu设备驱动第11章。
重要函数:
1.struct resource *request_region(unsigned long first,unsigned long n,const char *name).I/O端口申请资源的函数
2.inb,outb .I/O端口访问读写函数
3.release_region().I/O端口释放资源函数
4.struct resource *request_mem_region(unsigned long start,unsigned long len,char *name)申请I/O内存的函数,start为起始物理地址,len为映射物理地址的长度,name是resource内核中设备资源中的资源名。这个函数实际是把你要申请的设备资源添加到内核中的设备资源列表中,避免资源被同时使用。这个函数仅仅是检测这物理资源能否被使用,被申请。如果不成功返回NULL,成功返回一个resource设备资源结构体。结构体中的内容基本就是函数参数。
5.void *ioremap(unsigned long phys_add,unsigned long size) 映射物理地址到内核空间的虚拟地址的函数。
首先次函数有一个特别要注意的东西,其实我也不是很懂了,就是在返回值的控制上一般都是GPIOM_VA_BASE = (volatile unsigned long )ioremap(led_resource.start, led_resource.start-led_resource.end); 把返回值变成一个long整形,然后对这个long整形进行移动加减,再要赋值的时候再把它转换成指针(*(volatile unsigned long *)GPIOM_VA_BASE)&=~0xffff; 如果直接返回指针一般会出现oops报错的,原因未明,一定要注意。start是起始的物理地址,size是映射的长度。
6.unsigned ioread(void *addr) void iowrite(u8 value,void *addr)对I/O内存读写函数
7.void iounmap(void *addr)断开映射,addr是返回的内核虚拟地址,iounmap((void*)GPIOM_VA_BASE);
8.release_mem_region(led_resource.start,led_resource.end-led_resource.start);I/O内存释放资源函数
混杂设备驱动知识点:
1.混杂设备驱动其实属于字符设备驱动,只是多了一层封装而已。混杂设备的主设备号都是10,只有一个设备名,但是每个都有自己的次设备号和设备节点。
2.混杂设备创建的几步:a.先填写混杂设备的结构miscdevice
struct miscdevice{
int minor; //次设备号 把这个值给0 内核会自动分配设备号的 否则容易冲突
const char* name; //设备节点名称
const struct file_operation *fops;//文件操作
struct list_head list:
struct device *parent;
strcut device *this_device;
}
b.填写file_operation结构
c.然后在init函数中加入注册混杂设备函数int misc_register(struct miscdevice *misc)
这个函数自动帮你创建设备节点
d.在exit函数中加入释放设备的函数misc_deregister(&misc);
3.混杂设备和简单字符设备的区别,我觉得就是混杂设备比较简单,方便,所有用作了led的驱动练习了。
4.设备资源struct resource 的结构:struct resource{
resource_size_t start;//资源的起始物理地址
resource_size_t end;//设备资源结束地址
const char* name;//资源名 和 request_mem_region函数中的name是一样的
unsigned long flags;//资源类型 mem,io,irq
struct resource *parent ,*sibling,*child;//资源链表
}
5.谢伟老师在视频里面说了说:对于驱动的学习应该总结他们的流程,一般都是注册这个设备到内核, 再通过file *operation进行操作,最后释放。
本节代码:
dev_led.c:
/**************************************************************************
文件名: dev_led.c
日期: 2013/06/08
头文件: led.h
功能: 混杂设备驱动通过ioctl控制led
环境: Redhat企业版5 内核版本2.6.36
作者: Hao
流程: 只需注册misc_register() 会自动注册,申请,创建设备节点,是字符驱动的封装
***************************************************************************/
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include "dev_led.h"
int kernel_num=1991;//用一个全局变量吧 要不不能把先写入的数据保存下来
volatile unsigned long GPIOM_VA_BASE;//定义一个全局变量 保存ioremap映射的地址
#define GPIOM_CON_VA GPIOM_VA_BASE
#define GPIOM_DAT_VA (GPIOM_VA_BASE+0x4)
#define GPIOM_PUD_VA (GPIOM_VA_BASE+0x8)
#define GPIOM_PA_BASE 0x7f008820
MODULE_AUTHOR("Hao");
MODULE_LICENSE("GPL");
/**********************这个是设备资源 跟混杂设备驱动没什么关系 可有可无的*********************************************/
struct resource led_resource = {
.name = "led io-mem", //设备资源的名字
.start = GPIOM_PA_BASE,
.end = GPIOM_PA_BASE + 0xc,
.flags = IORESOURCE_MEM,
};
/**************************************************************************
函数名: ok6410_led_setup
函数功能: ioremap映射 gpio初始化
函数参数: 无
函数返回值: 无
***************************************************************************/
static void ok6410_led_setup(void)
{
unsigned long temp;
request_mem_region(led_resource.start,(led_resource.end-led_resource.start),led_resource.name);//申请i/o内存 设备资源的名字
//其实我觉得用上面那个资源的结构体意义不打 因为request_mem_region就是在跟系统申请这个资源 等价于了把上面的那个资源结构体拷贝到了内核中的设备资源链表
GPIOM_VA_BASE = (volatile unsigned long )ioremap(led_resource.start, led_resource.end-led_resource.start);//
/****************************可以直接对地址进行操作***************************************/
/*
(*(volatile unsigned long *)GPIOM_VA_BASE)&=~0xffff;
(*(volatile unsigned long *)GPIOM_VA_BASE)|=0x1|(0x1<<4)|(0x1<<8)|(0x1<<12);
temp=0;
(*(volatile unsigned long*)GPIOM_DAT_VA)=temp; //默认所有灯都亮
*/
/*******************也可以用函数api进行操作 貌似这个方式更加安全***************************/
temp&=~0xffff;
temp|=0x1|(0x1<<4)|(0x1<<8)|(0x1<<12);
writel(temp, GPIOM_CON_VA);
temp|=0xf;
temp=0;
writel(temp, GPIOM_DAT_VA);
}
/**************************************************************************
函数名: ok6410_led_release
函数功能: iounmap撤销映射 release_mem_region撤销申请
函数参数: 无
函数返回值: 无
***************************************************************************/
static void ok6410_led_release(void)
{
iounmap((void*)GPIOM_VA_BASE);
release_mem_region(led_resource.start,led_resource.end-led_resource.start);
}
/**************************************************************************
函数名: memdev_ioctl
函数功能: ioctl实现函数 命令实习函数
函数参数: 无
函数返回值: 返回ret为正常执行 返回-EINVAL命令号不正确
***************************************************************************/
static long memdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret=0;
int err=0;
int kernel_num=1991;
//char kernel_buf[20]="hello kernel!!!";
/*先判断命令号是否正确*/
if (_IOC_TYPE(cmd) != CMD_KTYPE) //获得命令的type类型是否正确
return -EINVAL;
if (_IOC_NR(cmd) > LED_KCMD) //获得命令的num类型 是否小于命令个数
return -EINVAL;
/*获命令的数据传输方向 根据各自的方向判断*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));/*此函数是根据
内核空间写的 是用来判断 arg应用程序传来的用户空间 是否有效的 所以对于用户空间来说是写*/
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));//对于用户空间来说是读 成功返回1 失败返回0
if (err)
return -EFAULT;
/*实现CMD的用法*/
switch(cmd)
{
case LEDR_KCMD:
ret=__put_user(kernel_num, (int *)arg); //把内核中的int型数据读入用户空间 unsigned long arg就是一个地址值 kernel->arg
break;
case LEDW_KCMD:
ret=__get_user(kernel_num, (int *)arg); //arg->kernel_num 把用户空间的数据传递给kernel_num
printk(KERN_EMERG "WRITE_KCMD is in kernel!!! kernel_num:%d \n",kernel_num);
if(1==kernel_num)
{
writel(0x0, GPIOM_DAT_VA);//将4个led全部点亮
}
if(0==kernel_num)
{
writel(0x1f, GPIOM_DAT_VA);//将4个led全部熄灭
}
break;
default:
return -EINVAL;
break;
}
}
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
int mem_open(struct inode *inode,struct file *filp)
{
return 0;
}
static const struct file_operations mem_fops = //定义此字符设备的file_operations
{ //这里是对结构体整体赋值的方式
.owner = THIS_MODULE, //函数名都可以自己定义 都是函数指针
.open = mem_open,
.release = mem_release,
.unlocked_ioctl=memdev_ioctl,
};
static struct miscdevice misc = {
.minor = 0,//设置为0 系统自动分配次设备号
.name = "misc_led", //我觉得这个是设备节点的名字 就是/dev路径下的文件的名字
.fops = &mem_fops, //文件操作
};
static int led_init(void)
{
int ret;
ok6410_led_setup();
ret = misc_register(&misc);
return ret;
}
static int led_exit(void)
{
ok6410_led_release();
misc_deregister(&misc);
return 0;
}
module_init(led_init);
module_exit(led_exit);
注意:在对led控制的函数中,可以用指针也可以用函数做,原理一样的。
dev_led.h
/**************************************************************************
文件名: dev_led.h
日期: 2013/06/08
头文件: led.h
功能: 混杂设备驱动的头文件
环境: Redhat企业版5 内核版本2.6.36
作者: Hao
***************************************************************************/
#ifndef _LED_H_
#define _LED_H_
#include <linux/ioctl.h>
#define CMD_KTYPE 'k' //定义命令幻数 也叫命令类型
#define LEDR_KCMD _IOR(CMD_KTYPE,1,int) //定义读方向的命令
#define LEDW_KCMD _IOW(CMD_KTYPE,2,int) //定义写方向的命令
#define LED_KCMD 2 //命令个数 后面判断命令是否有效 用的
#endif /* _MEMDEV_H_ */
app_led.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include "dev_led.h"
int main(int argc, char *argv[])
{
int fd=0;
printf("\n%d\n",*argv[1]);
unsigned int arg=(unsigned int)(*argv[1]-'0');
char buf[40]="WRITE_STR_KCMD is in kernel";
if(-1==(fd=open("/dev/misc_led",O_RDWR))) //设备节点名称为memdev0
{
printf("Open Dev Mem0 Error!\n");
_exit(EXIT_FAILURE);
}
printf("begin WRITE_KCMD!!!\n"); //写入一个int型arg
ioctl(fd,LEDW_KCMD,&arg);
close(fd);
return 0;
}
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := dev_led.o
else
KDIR := /guoqian/linux/linux-2.6.36
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers modul*
endif