linux 驱动笔记(三)

来源:互联网 发布:java的编译命令是哪个 编辑:程序博客网 时间:2024/05/16 04:44

第四章 IO内存

1 什么是IO内存

在嵌入式是平台上,系统内存(DDR2,512MB)和外设(GPIO ADC LCD ...)是统一编址的,是在同一地址空间内的。即SFR的地址和系统内存的地址是在同一地址空间上。

这样我们访问外设的方法和访问系统内存的方法是一样--->都是通过地址来访问。

 

[root@GEC210 /]# cat /proc/iomem

30000000-3fffffff : System RAM     0个通道的系统内存DDR2

3008e000-307d4fff : Kernel text

307d6000-30881337 : Kernel data

40000000-4fffffff : System RAM     1个通道的系统内存DDR2

88001000-88001fff : dm9000  

88001000-88001fff : dm9000

8800400c-8800500b : dm9000

8800400c-8800500b : dm9000

b0e00000-b0efffff : s5pv210-nand

b0e00000-b0efffff : s5pv210-nand

e0900000-e0901000 : s3c-pl330.1

e0900000-e0901000 : s3c-pl330

e0a00000-e0a01000 : s3c-pl330.2

  e0a00000-e0a01000 : s3c-pl330

e1300000-e13000ff : s3c64xx-spi.0

  e1300000-e13000ff : s3c64xx-spi

e1400000-e14000ff : s3c64xx-spi.1

  e1400000-e14000ff : s3c64xx-spi

e1700000-e1700fff : s3c-adc

e1800000-e1800fff : s3c2440-i2c.0

  e1800000-e1800fff : s3c2440-i2c

e1a00000-e1a00fff : s3c2440-i2c.2

  e1a00000-e1a00fff : s3c2440-i2c

e1b00000-e1b00fff : s5p-cec

  e1b00000-e1b00fff : s5p-cec

e2700000-e27fffff : s3c2410-wdt

e2800000-e28000ff : smdkc110-rtc

  e2800000-e28000ff : smdkc110-rtc

e2900000-e2900100 : s5pv210-uart.0

  e2900000-e29000ff : s5pv210-uart

e2900400-e2900500 : s5pv210-uart.1

  e2900400-e29004ff : s5pv210-uart

e2900800-e2900900 : s5pv210-uart.2

  e2900800-e29008ff : s5pv210-uart

e2900c00-e2900d00 : s5pv210-uart.3

  e2900c00-e2900cff : s5pv210-uart

eb000000-eb000fff : s3c-sdhci.0

  eb000000-eb000fff : mmc0

eb100000-eb100fff : s3c-sdhci.1

  eb100000-eb100fff : mmc1

eb200000-eb200fff : s3c-sdhci.2

  eb200000-eb200fff : mmc2

eb300000-eb300fff : s3c-sdhci.3

  eb300000-eb300fff : mmc3

ec000000-ec0fffff : s3c-usbgadget

ec200000-ec2fffff : s5p-ehci

  ec200000-ec2fffff : ehci_hcd

ec300000-ec3fffff : s5p-ohci

  ec300000-ec3fffff : ohci_hcd

eee30000-eee300ff : s3c64xx-iis.0

  eee30000-eee300ff : s3c64xx-i2s

f1700000-f17fffff : s3c-mfc

  f1700000-f17fffff : s3c-mfc

f8000000-f80fffff : s3cfb

  f8000000-f80fffff : s3cfb

f9000000-f90fffff : s5p-tvout

  f9000000-f90fffff : s5p-tvout

f9100000-f91fffff : s5p-tvout

  f9100000-f91fffff : s5p-tvout

f9200000-f92fffff : s5p-tvout

  f9200000-f92fffff : s5p-tvout

fa000000-fa0fffff : s3c-g2d

  fa000000-fa0fffff : s3c-g2d

fa100000-fa1fffff : s5p-tvout

  fa100000-fa1fffff : s5p-tvout

fa200000-fa201000 : s3c-pl330.0

  fa200000-fa201000 : s3c-pl330

fa600000-fa6fffff : s3c-csis

  fa600000-fa6fffff : s3c-csis

fa900000-fa9003ff : s5p-tvout

  fa900000-fa9003ff : s5p-tvout

fab00000-fab00fff : s3c2440-i2c.1

  fab00000-fab00fff : s3c2440-i2c

fb200000-fb2fffff : s3c-fimc.0

  fb200000-fb2fffff : s3c-fimc

fb300000-fb3fffff : s3c-fimc.1

  fb300000-fb3fffff : s3c-fimc

fb400000-fb4fffff : s3c-fimc.2

  fb400000-fb4fffff : s3c-fimc

fb600000-fb6fffff : s3c-jpg

  fb600000-fb6fffff : s3c-jpg

 

都是物理地址。

 

2 IO内存的使用方法

linux 不能直接访问物理地址

2.1 申请物理内存区

物理内存 ---> 一段SFR的物理地址,例如:我们想控制LED1~LED4,需要控制GPJ2CONGPJ2DAT两个寄存器, 这个寄存器的物理地址范围:0xe0200280~0xe200287

通过寄存器访问硬件的第一步骤就是申请物理内存区,将这个物理内存区作为一个资源resource,存放在内核中,注意,资源只能申请一次,这也是对资源的保护。

2.1.1申请物理内存区的函数

#include <linux/ioport.h>

struct resource * request_mem_region(resource_size_t start,resource_size_t n, const char *name)//(物理内存开始地址,  物理内存地址大小,物理内存名字)

 

例:申请GPJ2CONGPJ2DAT两个寄存器的物理内存区

static struct resource *  gec210_led_res; //定义一个物理内存区的资源

gec210_led_res = request_mem_region(0xe0200280,8,"GPJ2_LED");  //(物理内存开始地址,  地址大小-每个寄存器4两个是8,物理内存名字)cat /proc/iomem

if(gec210_led_res == NULL)

{

return -EBUSY; //资源在忙,申请不了

}

 

2.1.2释放物理内存区

void release_mem_region(resource_size_t start,resource_size_t n)//(物理内存开始地址,物理内存地址大小)

 

例:

release_mem_region(0xe0200280,8)

 

2.2 IO内存的动态映射

我们查找ARM的处理器使用手册,查看到的寄存器都是物理地址,linux系统不能直接使用物理地址。使用IO内存的动态映射由物理地址得到它对应的虚拟地址,然后我们就可以直接使用虚拟地址了。

注意:IO内存的动态映射与MMU是一个反向的过程。IO内存的动态映射实际上是改写MMU的页表的过程。MMU通过虚拟地址来查找这个页表,然后找到页表中的物理地址,这样完成了虚拟地址到物理地址的转换。

 

2.2.1 IO内存的动态映射函数

void __iomem *ioremap(phys_addr_t start, unsigned long size) //(物理内存作动态映射的开始地址,动态映射的物理内存区的大小)

 

例:我们想得到GPJ2CONGPJ2DAT的虚拟地址

 

static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针

static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针

 

gpj2con_va = ioremap(0xe0200280,8); //返回虚拟地址的开始值(首地址)

if(gpj2con_va == NULL)

{

return -EFAULT;

}

gpj2dat_va = gpj2con_va + 1; //不是4, int类型指针偏移一个单位为4字节

 

2.2.2取消IO内存的动态映射

void iounmap(void *addr)

参数说明:

void *add --- ioremap映射后的虚拟地址。

 

我们访问虚拟地址的方法和访问物理地址的方法是一样的。

 

3 虚拟地址的使用方法

 

常用的函数:

readl(addr)

writel(b,addr)

 

__raw_readl(addr)

__raw_writel(u32 b, volatile void __iomem *addr)

 

GPJ2_0~3设置成输出,输出高电平。

static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针

static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针

*gpj2con_va &= ~0xffff;

*gpj2con_va |= 0x1111;

 

*gpj2dat_va |= 0xf;

 

风格-->裸机的编程风格,不是linux的风格。

 

linux的风格:

writel( (readl(gpj2con_va) & ~0xffff)|0x1111,gpj2con_va );

 

writel( (readl(gpj2dat_va) |0xf,gpj2dat_va );

 

 

访问虚拟地址的函数:

 

#define readl(addr) __le32_to_cpu(__raw_readl(addr))

static inline u32 __raw_readl(const volatile void __iomem *addr)

{

return *(const volatile u32 __force *) addr;

}

 

#define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)

static inline void __raw_writel(u32 b, volatile void __iomem *addr)

{

*(volatile u32 __force *) addr = b;

}

 

 

A代码一

1. Filename: led_drv.c

 

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/uaccess.h>

#include <linux/ioport.h>

#include <linux/io.h>

 

//1)定义一个字符设备

static struct cdev led_drv;

 

static unsigned int led_major = 100; //0-->动态分配,>0-->静态注册

static unsigned int led_minor = 0;

static dev_t led_drv_num;

 

static struct resource *  gec210_led_res;

static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针

static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针

 

 

 //3)定义文件操作集,并初始化

static int gec210_led_open (struct inode *inode, struct file *filp)

{

*gpj2con_va &= ~0xffff; //16位清0

*gpj2con_va |= 0x1111; //16位为0x1111,四个IO口就配置成输出,四个位控制一个引脚

 

*gpj2dat_va |= 0xf;  //打开驱动,四个灯灭,高电平,一个位对应一个引脚

printk("openning the driver of led\n");

return 0;

}

 

//char buf[2],两个字节数据   buf[1]灯的状态:1--on灯亮,0-->off灯灭  (自定义)

//               buf[0] 哪一个led1/2/3/4 ,实际控制的位为 第0/1/2/3

ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) //应用程序向驱动程序写数据,用此数据控制灯亮灭,

{

int ret;

char kbuf[2]; //两个字节 ,第一字节buf[1]灯的状态,第二字节,buf[0]哪一个led

if(len != 2)

return -EINVAL;

ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据放到kbuf

if(ret!= 0)

return -EFAULT;

if( (kbuf[0]<1) || (kbuf[0]>4) )  //灯数量,有四个灯,因此不能小于1 ,大于4

return -EINVAL;

if(kbuf[1]==1) //灯亮,

*gpj2dat_va &= ~(1<<(kbuf[0]-1));  //buf[0] 哪一个led1/2/3/4 ,实际控制的位为 第0/1/2/3

else if(kbuf[1]==0) //灯灭

*gpj2dat_va |= (1<<(kbuf[0]-1));

else

return -EINVAL;

return len;

}

 

static int gec210_led_release(struct inode *inode, struct file *filp)

{

*gpj2dat_va |= 0xf; //关闭驱动,四个灯灭,高电平,一个位对应一个引脚

printk("closing the driver of led\n");

return 0;

}

         

static struct file_operations gec210_led_fops = {

.owner = THIS_MODULE,

.open  = gec210_led_open,

.write = gec210_led_write,

.release = gec210_led_release,

};

 

static int __init gec210_led_init(void) //驱动的初始化及安装函数

{

int ret;

//2)申请/注册设备号

if(led_major == 0){

ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");

}

else{

led_drv_num = MKDEV(led_major,led_minor);

ret = register_chrdev_region(led_drv_num,  1, "gec210_leds");

}

if(ret < 0){

printk("led_drv_num is error \n");

return ret;

}

 

//4)初始化cdev

cdev_init(&led_drv,  &gec210_led_fops);

 

//5)cdev加入kernel

ret = cdev_add(&led_drv,led_drv_num, 1 );

if(ret < 0){

printk("cdev add error\n");

goto failed_cdev_add;

}

 

//6 申请物理内存区,作为一个资源

gec210_led_res = request_mem_region(0xe0200280,8,"GPJ2_LED"); //cat /proc/iomem

if(gec210_led_res == NULL)

{

printk("requst mem region error\n");

ret = -EBUSY;

goto failed_request_mem_region;

}

//7 io内存动态映射

gpj2con_va = ioremap(0xe0200280,8);

if(gpj2con_va == NULL)

{

printk("ioremap error\n");

ret = -EFAULT;

goto failed_ioremap;

}

gpj2dat_va = gpj2con_va + 1; //不是4

printk("gpj2con_va=%pgpj2dat_va=%p\n", gpj2con_va,gpj2dat_va);

return 0;

failed_ioremap:

release_mem_region(0xe0200280,8);

failed_request_mem_region:

cdev_del(&led_drv);

failed_cdev_add:

unregister_chrdev_region(led_drv_num,  1);

return ret;

}

 

static void __exit gec210_led_exit(void) //驱动卸载函数

{

unregister_chrdev_region(led_drv_num,  1);

cdev_del(&led_drv);

release_mem_region(0xe0200280,8);

printk("good bye gec210\n");

}

 

module_init(gec210_led_init); //驱动的入口

module_exit(gec210_led_exit); //驱动的出口

 

//内核模块的描述

MODULE_AUTHOR("bobeyfeng@163.com");

MODULE_DESCRIPTION("the first demo of module");

MODULE_LICENSE("GPL"); //符合GPL协议

MODULE_VERSION("V1.0");

 

//-------------------------------

2. Filename: test.c

#include <stdio.h>

#include <fcntl.h>

int main(void)

{

int fd;

int ret;

char buf[2]; //写两个字节数据

//"/dev/led_drv" ---linux驱动的设备文件节点(node

fd = open("/dev/led_drv", O_WRONLY);

if(fd <0)

{

perror("open led_drv:");

return -1;

}

while(1)

{

buf[1] = 1;buf[0]=3;  //led3 on 第三个灯亮

ret = write(fd,buf,sizeof(buf)); //把数据写到驱动程序里

if(ret < 0)

{

perror("write led_drv: ");

return -1;

}

sleep(1);

buf[1] = 0;buf[0]=3; //led3 off 第三个灯灭

ret = write(fd,buf,sizeof(buf));

if(ret < 0)

{

perror("write led_drv: ");

return -1;

}

sleep(1);

}

close(fd);

return 0;

}

//-------------------------------

3. Filename: Makefile

obj-m += led_drv.o

#KERNELDIR := /lib/modules/$(shell uname -r)/build

KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110

PWD:=$(shell pwd)

 

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

rm -rf *.o *.mod.c *.mod.o *.ko

 

 

 

 

 

 

 

 

 

 

 

 

第五章 自动生成设备文件

1 字符设备驱动的设计流程

1.1 定义一个字符设备--->cdev

1.2 申请一个设备号 --->10--主设备号, 131--次设备号,设备号10<<20+130。动态申请or静态注册

1.3 定义文件操作集并初始化--->file_operations给应用程序提供一个标准的接口

1.4 字符设备的初始化

1.5 将字符设备加入内核

1.6 申请SFR的物理内存区,作为一个资源

1.7 IO内存的动态映射,得到了虚拟地址,访问虚拟地址。

 

//自动生成设备文件:不用mknod

1.8 创建字符设备的class

1.9 创建属于该classdevice

 

2 如何创建classdevice

#include <linux/device.h>

2.1 class的创建

/**

 * class_create - create a struct class structure

 * @owner: pointer to the module that is to "own" this struct class

 * @name: pointer to a string for the name of this class.

 *

 * This is used to create a struct class pointer that can then be used

 * in calls to device_create().

 *

 * Returns &struct class pointer on success, or ERR_PTR() on error.

 *

 * Note, the pointer created here is to be destroyed when finished by

 * making a call to class_destroy().

 */

原型

struct class * class_create(struct module *owner, const char *name)

参数说明:

struct module *owner ---class的所有者,THIS_MODULE

const char *name --- class的名字。 /sys/class/

返回值:

成功,返回创建好的class的指针

失败,ERR_PTR()确定失败的原因

例:

static struct class *gec210_led_class;

gec210_led_class = class_create(THIS_MODULE, "led_class");

if(gec210_led_class == NULL)

{

return -EBUSY;

}

 

2.2销毁一个class

void class_destroy(struct class *cls);

2.3 device的创建

原型

struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt)

参数说明:

struct class *cls --->创建的device输入哪个class

struct device *parent --->device的父设备,一般都为NULL

dev_t devt   --->设备号

void *drvdata --->device的数据,一般也为NULL

const char *fmt --->device的名字,也就是设备文件的名字

返回值:

创建好的device

 

2.4销毁device函数:

void device_destroy(struct class *cls, dev_t devt);

 

3 为什么有classdevice就可以自动生成设备文件?

使用的一个自动生成设备文件的工具---mdev

# which mdev

/sbin/mdev    ---> busybox生成的

 

mdev工具会根据/sys路径下的class找到device,然后再根据device创建设备文件。

 

#vi /etc/init.d/rcS

 

echo /sbin/mdev > /proc/sys/kernel/hotplug

mdev -s

 

/sbin/mdev工具写入到/proc/sys/kernel/hotplughotplug热插拔的过程,向内核中安装驱动和卸载驱动都是一个热插拔过程。

在我们安装驱动和卸载驱动的时候,就会立即调用mdev工具。安装驱动的时候生成设备文件,卸载驱动的时候,设备文件会自动删掉。

 

A代码一

1. Filename: led_drv.c

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/uaccess.h>

#include <linux/ioport.h>

#include <linux/io.h>

#include <linux/device.h>

 

//1)定义一个字符设备cdev

static struct cdev led_drv;

 

static unsigned int led_major = 0; //0-->动态分配,>0-->静态注册

static unsigned int led_minor = 0;

static dev_t led_drv_num;

 

static struct resource *  gec210_led_res;

static unsigned int *gpj2con_va; //0xe0200280对应的虚拟地址指针

static unsigned int *gpj2dat_va; //0xe0200284对应的虚拟地址指针

 

static struct class *gec210_led_class;

static struct device *gec210_led_device;

 

 

 //3)定义文件操作集,并初始化

//char buf[2],buf[1]灯的状态:1--on0-->off

//            buf[0]哪一个led1/2/3/4

ssize_t gec210_led_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)

{

int ret;

char kbuf[2];

if(len != 2)

return -EINVAL;

ret = copy_from_user(kbuf,buf,len); //从用户空间拷贝数据

if(ret!= 0)

return -EFAULT;

if( (kbuf[0]<1) || (kbuf[0]>4) )

return -EINVAL;

if(kbuf[1]==1)

*gpj2dat_va &= ~(1<<(kbuf[0]-1));

else if(kbuf[1]==0)

*gpj2dat_va |= (1<<(kbuf[0]-1));

else

return -EINVAL;

return len;

}

 

  

static struct file_operations gec210_led_fops = {

.owner = THIS_MODULE,

.write = gec210_led_write,

};

 

static int __init gec210_led_init(void) //驱动的初始化及安装函数

{

int ret;

//2)申请/注册设备号

if(led_major == 0){

ret = alloc_chrdev_region(&led_drv_num, led_minor, 1, "gec210_leds");

}

else{

led_drv_num = MKDEV(led_major,led_minor);

ret = register_chrdev_region(led_drv_num,  1, "gec210_leds");

}

if(ret < 0){

printk("led_drv_num is error \n");

return ret;

}

 

//4)初始化cdev

cdev_init(&led_drv,  &gec210_led_fops);

 

//5)cdev加入kernel

ret = cdev_add(&led_drv,led_drv_num, 1 );

if(ret < 0){

printk("cdev add error\n");

goto failed_cdev_add;

}

 

//6)申请物理内存区,作为一个资源

gec210_led_res = request_mem_region(0xe0200280,8,"GPJ2_LED"); //cat /proc/iomem

if(gec210_led_res == NULL)

{

printk("requst mem region error\n");

ret = -EBUSY;

goto failed_request_mem_region;

}

//7)io内存动态映射

gpj2con_va = ioremap(0xe0200280,8);

if(gpj2con_va == NULL)

{

printk("ioremap error\n");

ret = -EFAULT;

goto failed_ioremap;

}

gpj2dat_va = gpj2con_va + 1; //不是4

printk("gpj2con_va=%pgpj2dat_va=%p\n", gpj2con_va,gpj2dat_va);

//8)创建class

gec210_led_class = class_create(THIS_MODULE, "led_class");

if(gec210_led_class == NULL)

{

printk("class create error\n");

ret = -EBUSY;

goto failed_class_create;

}

//9)创建device

gec210_led_device = device_create(gec210_led_class,NULL,

                  led_drv_num,NULL,"led_drv"); // /dev/led_drv

if(gec210_led_device == NULL)

{

printk("class device error\n");

ret = -EBUSY;

goto failed_device_create;

}

//led1~4,初始状态是灭的

*gpj2con_va &= ~0xffff;

*gpj2con_va |= 0x1111;

 

*gpj2dat_va |= 0xf;

return 0;

failed_device_create:

class_destroy(gec210_led_class);

failed_class_create:

iounmap(gpj2con_va);

failed_ioremap:

release_mem_region(0xe0200280,8);

failed_request_mem_region:

cdev_del(&led_drv);

failed_cdev_add:

unregister_chrdev_region(led_drv_num,  1);

return ret;

}

 

static void __exit gec210_led_exit(void) //驱动卸载函数

{

unregister_chrdev_region(led_drv_num,  1);

cdev_del(&led_drv);

release_mem_region(0xe0200280,8);

iounmap(gpj2con_va);

device_destroy(gec210_led_class,led_drv_num);

class_destroy(gec210_led_class);

printk("good bye gec210\n");

}

 

module_init(gec210_led_init); //驱动的入口

module_exit(gec210_led_exit); //驱动的出口

 

//内核模块的描述

MODULE_AUTHOR("bobeyfeng@163.com");

MODULE_DESCRIPTION("the first demo of module");

MODULE_LICENSE("GPL"); //符合GPL协议

MODULE_VERSION("V1.0");

//-------------------------------------------------------------------------

2. Filename: test

#include <stdio.h>

#include <fcntl.h>

int main(void)

{

int fd;

int ret;

char buf[2];

//"/dev/led_drv" ---linux驱动的设备文件节点(node

fd = open("/dev/led_drv", O_WRONLY);

if(fd <0)

{

perror("open led_drv:");

return -1;

}

while(1)

{

buf[1] = 1;buf[0]=3; //led3 on

ret = write(fd,buf,sizeof(buf));

if(ret < 0)

{

perror("write led_drv: ");

return -1;

}

sleep(1);

buf[1] = 0;buf[0]=3; //led3 on

ret = write(fd,buf,sizeof(buf));

if(ret < 0)

{

perror("write led_drv: ");

return -1;

}

sleep(1);

}

close(fd);

return 0;

}

//-------------------------------------------------------

3. Filename: Makefile

obj-m += led_drv.o

#KERNELDIR := /lib/modules/$(shell uname -r)/build

KERNELDIR := /home/gec/linux-2.6.35.7-gec-v3.0-gt110

PWD:=$(shell pwd)

 

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

rm -rf *.o *.mod.c *.mod.o *.ko

 

 

0 0