您的位置 首页 知识

IO端口映射和IO内存映射(详解S3C24XX_GPIO驱动)

刚刚我们实现了linux系统内存的分配,读写,释放功能,下面,我们一鼓作气将IO端口映射及IO内存映射搞定,加油!(一)地址的概念1)物理地…

刚刚咱们完结了linux体系内存的分配,读写,开释功用,下面,咱们趁热打铁将IO端口映射IO内存映射搞定,加油!

(一)地址的概念

1)物理地址:CPU地址总线传来的地址,由硬件电路操控其详细意义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址线上。
物理地址空间,一部分给物理RAM(内存)用,一部分给总线用,这是由硬件规划来决议的,因而在32 bits地址线的x86处理器中,物理地址空间是2的32次方,即4GB,但物理RAM一般不能上到4GB,因为还有一部分要给总线用(总线上还挂着其他许多设备)。在PC机中,一般是把低端物理地址给RAM用,高端物理地址给总线用。
2)总线地址:总线的地址线或在地址周期上发生的信号。外设运用的是总线地址,CPU运用的是物理地址。
物理地址与总线地址之间的关系由体系的规划决议的。在x86渠道上,物理地址便是总线地址,这是因为它们同享相同的地址空间——这句话有点难了解,详见下面的”独立编址”。在其他渠道上,或许需求转化/映射。比方:CPU需求拜访物理地址是0xfa的单元,那么在x86渠道上,会发生一个PCI总线上对0xfa地址的拜访。因为物理地址和总线地址相同,所以凭眼睛看是不能确认这个地址是用在哪儿的,它或许在内存中,或许是某个卡上的存储单元,乃至或许这个地址上没有对应的存储器。

3)虚拟地址:现代操作体系遍及选用虚拟内存办理(Virtual Memory Management)机制,这需求MMU(Memory Management Unit)的支撑。MMU通常是CPU的一部分,假如处理器没有MMU,或许有MMU但没有启用,CPU履行单元宣布的内存地址将直接传到芯片引脚上,被内存芯片(物理内存)接纳,这称为物理地址(Physical Address),假如处理器启用了MMU,CPU履行单元宣布的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也便是将虚拟地址映射成物理地址。
Linux中,进程的4GB(虚拟)内存分为用户空间、内核空间。用户空间散布为0~3GB(即PAGE_OFFSET,在0X86中它等于0xC0),剩余的1G为内核空间。程序员只能运用虚拟地址。体系中每个进程有各自的私有用户空间(0~3G),这个空间对体系中的其他进程是不行见的。
CPU宣布取指令恳求时的地址是当时上下文的虚拟地址,MMU再从页表中找到这个虚拟地址的物理地址,完结取指。相同读取数据的也是虚拟地址,比方mov ax, var.编译时var便是一个虚拟地址,也是经过MMU从也表中来找到物理地址,再发生总线时序,完结取数据的。

(二)编址办法
1)外设都是经过读写设备上的寄存器来进行的,外设寄存器也称为”I/O端口”,而IO端口有两种编址办法:独立编址和一致编制。

一致编址:外设接口中的IO寄存器(即IO端口)与主存单元相同看待,每个端口占用一个存储单元的地址,将主存的一部分划出来用作IO地址空间,如,在PDP-11中,把最高的4K主存作为IO设备寄存器地址。端口占用了存储器的地址空间,使存储量容量减小。
一致编址也称为”I/O内存”办法,外设寄存器坐落”内存空间”(许多外设有自己的内存、缓冲区,外设的寄存器和内存总称”I/O空间”)。
如,Samsung的S3C2440,是32位ARM处理器,它的4GB地址空间被外设、RAM等分割:
0x8 1 LED 8*8点阵的地址
0x4800 0 ~ 0x6 0 SFR(特别暂存器)地址空间
0x3800 1002键盘地址
0x3 0 ~ 0x3400 0 SDRAM空间
0x2 0020 ~ 0x2 002e IDE
0x1900 0300 CS8900

独立编址(独自编址):IO地址与存储地址分隔独立编址,I/0端口地址不占用存储空间的地址规模,这样,在体系中就存在了另一种与存储地址无关的IO地址,CPU也有必要具有专用与输入输出操作的IO指令(IN、OUT等)和操控逻辑。独立编址下,地址总线上过来一个地址,设备不知道是给IO端口的、仍是给存储器的,所以处理器经过MEMR/MEMW和IOR/IOW两组操控信号来完结对I/O端口和存储器的不同寻址。如,intel 80×86就选用独自编址,CPU内存和I/O是一同编址的,便是说内存一部分的地址和I/O地址是堆叠的。
独立编址也称为”I/O端口”办法,外设寄存器坐落”I/O(地址)空间”。
关于x86架构来说,经过IN/OUT指令拜访。PC架构一共有65536个8bit的I/O端口,组成64K个I/O地址空间,编号从0~0xFFFF,有16位,80×86用低16位地址线A0-A15来寻址。接连两个8bit的端口能够组成一个16bit的端口,接连4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如I/O地址空间为64K,一个32bit的CPU物理地址空间是4G。如,在Intel 8086+Redhat9.0下用”more /proc/ioports”可看到:

0-001f : dma1
0020-003f : pic1
0040-005f : timer
0060-006f : keyboard
0070-007f : rtc
0080-008f : dma page reg
00a0-00bf : pic2
00c0-00df : dma2
00f0-00ff : fpu
0170-0177 : ide1
……

不过Intel x86渠道一般运用了名为内存映射(MMIO)的技能,该技能是PCI标准的一部分,IO设备端口被映射到内存空间,映射后,CPU拜访IO端口就好像拜访内存相同。看Intel TA 719文档给出的x86/x64体系典型内存地址分配表:
体系资源占用

BIOS 1M

本地APIC 4K

芯片组保存 2M

IO APIC 4K

PCI设备 256M

PCI Express设备 256M

PCI设备(可选) 256M

显现帧缓存 16M

TSEG 1M

关于某一既定的体系,它要么是独立编址、要么是一致编址,详细选用哪一种则取决于CPU的体系结构。 如,PowerPC、m68k等选用一致编址,而X86等则选用独立编址,存在IO空间的概念。现在,大多数嵌入式微操控器如ARM、PowerPC等并 不供给I/O空间,仅有内存空间,可直接用地址、指针拜访。但关于Linux内核而言,它或许用于不同的CPU,所以它有必要都要考虑这两种办法,所以它采 用一种新的办法,将根据I/O映射办法的或内存映射办法的I/O端口通称为”I/O区域”(I/O region),不管你选用哪种办法,都要先恳求IO区域:request_resource(),结束时开释 它:release_resource()。

2)对外设的拜访

1、拜访I/O内存的流程是:request_mem_region()->ioremap()->ioread8()/iowrite8()->iounmap()->release_mem_region()。
前面说过,IO内存是一致编址下的概念,关于一致编址,IO地址空间是物理主存的一部分,关于编程而言,咱们只能操作虚拟内存,所以,拜访的第一步便是要把设备所在的物理地址映射到虚拟地址,Linux2.6下用ioremap():
void *ioremap(unsigned long offset, unsigned long size);
然后,咱们能够直接经过指针来拜访这些地址,可是也能够用Linux内核的一组函数来读写:
ioread8(), iowrite16(), ioread8_rep(), iowrite8_rep()……

2、拜访I/O端口
拜访IO端口有2种途径:I/O映射办法(I/O-mapped)、内存映射办法(Memory-mapped)。前一种途径不映射到内存空间,直接运用intb()/outb()之类的函数来读写IO端口;后一种MMIO是先把IO端口映射到IO内存(”内存空间”),再运用拜访IO内存的函数来拜访IO端口。
void ioport_map(unsigned long port, unsigned int count);
经过这个函数,能够把port开端的count个接连的IO端口映射为一段”内存空间”,然后就能够在其回来的地址是像拜访IO内存相同拜访这些IO端口。

(三)Linux下的IO端口和IO内存
CPU对外设端口物理地址的编址办法有两种:一种是IO映射办法,另一种是内存映射办法。
 Linux将根据IO映射办法的和内存映射办法的IO端口总称为IO区域(IO region)。
  IO region依然是一种IO资源,因而它依然能够用resource结构类型来描绘。

  Linux办理IO region:

  1) request_region()

  把一个给定区间的IO端口分配给一个IO设备。

  2) check_region()

  查看一个给定区间的IO端口是否闲暇,或许其间一些是否现已分配给某个IO设备。

  3) release_region()

  开释从前分配给一个IO设备的给定区间的IO端口。

  Linux中能够经过以下辅佐函数来拜访IO端口:

  inb(),inw(),inl(),outb(),outw(),outl()

  ”b””w””l”别离代表8位,16位,32位。

 对IO内存资源的拜访

  1) request_mem_region()

  恳求分配指定的IO内存资源。

  2) check_mem_region()

  查看指定的IO内存资源是否已被占用。

  3) release_mem_region()

  开释指定的IO内存资源。

  其间传给函数的start address参数是内存区的物理地址(以上函数参数表已省掉)。

  驱动开发人员能够将内存映射办法的IO端口和外设内存一致看作是IO内存资源。

  ioremap()用来将IO资源的物理地址映射到内核虚地址空间(3GB – 4GB)中,参数addr是指向内核虚地址的指针。

  Linux中能够经过以下辅佐函数来拜访IO内存资源:

  readb(),readw(),readl(),writeb(),writew(),writel()。

  Linux在kernel/resource.c文件中界说了全局变量ioport_resource和iomem_resource,来别离描绘根据IO映射办法的整个IO端口空间和根据内存映射办法的IO内存资源空间(包含IO端口和外设内存)。

1)关于IO与内存空间:
在X86处理器中存在着I/O空间的概念,I/O空间是相关于内存空间而言的,它经过特定的指令in、out来拜访。端口号标识了外设的寄存器地址。Intel语法的in、out指令格局为:
IN累加器, {端口号│DX}
OUT {端口号│DX},累加器
现在,大多数嵌入式微操控器如ARM、PowerPC等中并不供给I/O空间,而仅存在内存空间。内存空间能够直接经过地址、指针来拜访,程序和程序运转中运用的变量和其他数据都存在于内存空间中。
即便是在X86处理器中,尽管供给了I/O空间,假如由咱们自己规划电路板,外设依然能够只挂接在内存空间。此刻,CPU能够像拜访一个内存单元那样拜访外设I/O端口,而不需求建立专门的I/O指令。因而,内存空间是有必要的,而I/O空间是可选的。

(2)inb和outb:

在Linux设备驱动中,宜运用Linux内核供给的函数来拜访定坐落I/O空间的端口,这些函数包含:
·读写字节端口(8位宽)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
·读写字端口(16位宽)
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
·读写长字端口(32位宽)
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
·读写一串字节
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
· insb()从端口port开端读count个字节端口,并将读取成果写入addr指向的内存;outsb()将addr指向的内存的count个字节接连地写入port开端的端口。
·读写一串字
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
·读写一串长字
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
上述各函数中I/O端口号port的类型高度依赖于详细的硬件渠道,因而,仅仅写出了unsigned。

以上知识点摘自:http://www.embeddedlinux.org.cn/html/yingjianqudong/201304/20-2556.html

这儿我不献丑了,因为上面的现已写的很好了。

(四)Linux下的IO端口和IO内存

因为我自己对IMX257的IO内存分配不太了解,从前询问过许多周建功公司那些所谓的技能人员,每个人答复的答案都是,”你好,咱们供给的材料就仅仅官网上的光盘材料了哦”,每次碰到这种话,我真的很无语,我只能说,好吧,只能说是我智商太低了,那些材料太深邃了。

好了,废话也不多说了,这次,我就针对S3C24XX的gpio驱动程序来解说io端口的分配。

1. 界说一些寄存器(数据寄存器,装备寄存器)

上图中,gpio_va便是咱们GPIO的基址,是用来确认GPFCON装备寄存器和GPFDAT数据寄存器的指针地址的

,其初始化咱们在init函数中会解说。

2.在init函数中确认gpio基址

前面咱们寄存器的正常效果的条件便是gpio_va这个基址,所以咱们在init函数中分配IO端口

如图所示,将gpio的地址0x56这个io端口分配为gpio_va,这样也就确认了,前面的装备寄存器和数据寄存器。

3.在open函数中装备GPIO

单片机程序相同,运用GPIO都要先装备GPIO,原理相同:

给GPIO引脚赋初值,今后对GPIO引脚的操作,直接用这个寄存器赋值就能够了

4.在exit函数中开释IO端口

运用完GPIO引脚后,要将咱们恳求的IO端口开释掉。

总结一下,很简单,恳求,装备,开释

恳求IO端口à装备GPIO端口为输出(入)à给数据寄存器赋值(读取)à开释IO端口

附上驱动程序:


1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 12 #define DEVICE_NAME     "leds"  13 /* 加载形式后,履行”cat /proc/devices”指令看到的设备称号 */14 #define LED_MAJOR       231     /* 主设备号 */15 16 static struct class *leds_class;17 static struct class_device    *leds_class_devs[4];18 19 /* bit0<=>D10, 0:亮, 1:灭 20  *  bit1<=>D11, 0:亮, 1:灭 21  *  bit2<=>D12, 0:亮, 1:灭 22  */ 23 static char leds_status = 0x0;  24 static DECLARE_MUTEX(leds_lock); // 界说赋值25 26 //static int minor;27 static unsigned long gpio_va;  //gpio基址28 #define GPIO_OFT(x) ((x) - 0x56)29 //GPIO 装备寄存器30 #define GPFCON  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56050)))31 //GPIO 数据寄存器32 #define GPFDAT  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56054)))33 /* 应用程序对设备文件/dev/leds履行open(...)时,34  * 就会调用s3c24xx_leds_open函数35  */36 static int s3c24xx_leds_open(struct inode *inode, struct file *file)37 {38     int minor = MINOR(inode->i_rdev); //MINOR(inode->i_cdev);39     switch(minor)40     {41         case 0: /* /dev/leds */42         {43             // 装备3引脚为输出44             //s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);45             GPFCON &= ~(0x3<<(4*2));46             GPFCON = (1<<(4*2));47             48             //s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);49             GPFCON &= ~(0x3<<(5*2));50             GPFCON = (1<<(5*2));51 52             //s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);53             GPFCON &= ~(0x3<<(6*2));54             GPFCON = (1<<(6*2));55 56             // 都输出057             //s3c2410_gpio_setpin(S3C2410_GPF4, 0);58             GPFDAT &= ~(1<<4);59             60             //s3c2410_gpio_setpin(S3C2410_GPF5, 0);61             GPFDAT &= ~(1<<5);62             //s3c2410_gpio_setpin(S3C2410_GPF6, 0);63             GPFDAT &= ~(1<<6);64 65             down(&leds_lock);66             leds_status = 0x0;67             up(&leds_lock);68                 69             break;70         }71 72         case 1: /* /dev/led1 */73         {74             s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP);75             s3c2410_gpio_setpin(S3C2410_GPF4, 0);76             77             down(&leds_lock);78             leds_status &= ~(1<<0);79             up(&leds_lock);80             81             break;82         }83 84         case 2: /* /dev/led2 */85         {86             s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP);87             s3c2410_gpio_setpin(S3C2410_GPF5, 0);88             leds_status &= ~(1<<1);89             break;90         }91 92         case 3: /* /dev/led3 */93         {94             s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP);95             s3c2410_gpio_setpin(S3C2410_GPF6, 0);96 97             down(&leds_lock);98             leds_status &= ~(1<<2);99             up(&leds_lock);100             101             break;102         }103     }    104     return 0;105 }106 static int s3c24xx_leds_read(struct file *filp, char __user *buff, 107                                          size_t count, loff_t *offp)108 {109     int minor = MINOR(filp->f_dentry->d_inode->i_rdev);110     char val;112     switch (minor)113     {114         case 0: /* /dev/leds */115         {116             copy_to_user(buff, (const void *)&leds_status, 1);117             break;118         }119         case 1: /* /dev/led1 */120         {121             down(&leds_lock);122             val = leds_status & 0x1;123             up(&leds_lock);124             copy_to_user(buff, (const void *)&val, 1);125             break;126         }127         case 2: /* /dev/led2 */128         {129             down(&leds_lock);130             val = (leds_status>>1) & 0x1;131             up(&leds_lock);132             copy_to_user(buff, (const void *)&val, 1);133             break;134         }135         case 3: /* /dev/led3 */136         {137             down(&leds_lock);138             val = (leds_status>>2) & 0x1;139             up(&leds_lock);140             copy_to_user(buff, (const void *)&val, 1);141             break;142         }143     }144     return 1;145 }146 static ssize_t s3c24xx_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)147 {148     //int minor = MINOR(inode->i_rdev); //MINOR(inode->i_cdev);149     int minor = MINOR(file->f_dentry->d_inode->i_rdev);150     char val;151     copy_from_user(&val, buf, 1);152     switch (minor)153     {154         case 0: /* /dev/leds */155         {            156             s3c2410_gpio_setpin(S3C2410_GPF4, (val & 0x1));157             s3c2410_gpio_setpin(S3C2410_GPF5, (val & 0x1));158             s3c2410_gpio_setpin(S3C2410_GPF6, (val & 0x1));159             down(&leds_lock);160             leds_status = val;161             up(&leds_lock);162             break;163         }164         case 1: /* /dev/led1 */165         {166             s3c2410_gpio_setpin(S3C2410_GPF4, val);167             if (val == 0)168             {169                 down(&leds_lock);170                 leds_status &= ~(1<<0);171                 up(&leds_lock);172             }173             else174             {175                 down(&leds_lock);176                 leds_status = (1<<0);                177                 up(&leds_lock);178             }179             break;180         }181         case 2: /* /dev/led2 */182         {183             s3c2410_gpio_setpin(S3C2410_GPF5, val);184             if (val == 0)185             {186                 down(&leds_lock);187                 leds_status &= ~(1<<1);188                 up(&leds_lock);189             }190             else191             {192                 down(&leds_lock);193                 leds_status = (1<<1);                194                 up(&leds_lock);195             }196             break;197         }198         case 3: /* /dev/led3 */199         {200             s3c2410_gpio_setpin(S3C2410_GPF6, val);201             if (val == 0)202             {203                 down(&leds_lock);204                 leds_status &= ~(1<<2);205                 up(&leds_lock);206             }207             else208             {209                 down(&leds_lock);210                 leds_status = (1<<2);                211                 up(&leds_lock);212             }213             break;214         }215     }216     return 1;217 }218 /* 这个结构是字符设备驱动程序的中心219  * 当应用程序操作设备文件时所调用的open、read、write等函数,220  * 终究会调用这个结构中指定的对应函数221  */static struct file_operations s3c24xx_leds_fops = {223     .owner  =   THIS_MODULE,    /* 这是一个宏,面向编译模块时主动创立的__this_module变量 */224     .open   =   s3c24xx_leds_open,     225     .read    =    s3c24xx_leds_read,       226     .write    =    s3c24xx_leds_write,       227 };228 229 /*230  * 履行insmod指令时就会调用这个函数 231  */232 static int __init s3c24xx_leds_init(void)233 {234     int ret;235     int minor = 0;236 237     gpio_va = ioremap(0x56, 0x100);238     if (!gpio_va) {239         return -EIO;240     }241     /* 注册字符设备242      * 参数为主设备号、设备姓名、file_operations结构;243      * 这样,主设备号就和详细的file_operations结构联系起来了,244      * 操作主设备为LED_MAJOR的设备文件时,就会调用s3c24xx_leds_fops中的相关成员函数245      * LED_MAJOR能够设为0,表明由内核主动分配主设备号246      */247     ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);248     if (ret < 0) {249       printk(DEVICE_NAME " cant register major number\n");250       return ret;251     }252     leds_class = class_create(THIS_MODULE, "leds");253     if (IS_ERR(leds_class))254         return PTR_ERR(leds_class);255     256     leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds");257     258     for (minor = 1; minor < 4; minor++)259     {260         leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);261         if (unlikely(IS_ERR(leds_class_devs[minor])))262             return PTR_ERR(leds_class_devs[minor]);263     }264         265     printk(DEVICE_NAME " initialized\n");266     return 0;267 }268 269 /*270  * 履行rmmod指令时就会调用这个函数 271  */272 static void __exit s3c24xx_leds_exit(void)273 {274     int minor;275     /* 卸载驱动程序 */276     unregister_chrdev(LED_MAJOR, DEVICE_NAME);277 278     for (minor = 0; minor < 4; minor++)279     {280         class_device_unregister(leds_class_devs[minor]);281     }282     class_destroy(leds_class);283     //开释IO端口284     iounmap(gpio_va);285 }286 287 /* 这两行指定驱动程序的初始化函数和卸载函数 */288 module_init(s3c24xx_leds_init);289 module_exit(s3c24xx_leds_exit);290 291 /* 描绘驱动程序的一些信息,不是有必要的 */292 MODULE_LICENSE("GPL");

View Code

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/zhishi/277497.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部