Chinaunix首页 | 论坛 | 博客
  • 博客访问: 17297
  • 博文数量: 3
  • 博客积分: 267
  • 博客等级: 二等列兵
  • 技术积分: 115
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-04 17:06
文章分类
文章存档

2012年(3)

我的朋友

分类: LINUX

2012-09-05 09:43:04

本文从以前的百度空间转过来,全当是资料备份。

以下内容有部分是参考网上的文章。

几乎每一种外设都是通过读写设备上的寄存器来进行的。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。

在驱动程序编写过程中,很少会注意到IO Port和IO Mem的区别。虽然使用一些不符合规范的代码可以达到最终目的,这是极其不推荐使用的。下面就是i386和arm的区别。

1.        CPUi386架构的情况

在i386系列的处理中,内存和外部IO是独立编址,也是独立寻址的。MEM的内存空间是32位可以寻址到4G,IO空间是16位可以寻址到64K。

在Linux内核中,访问i386外设上的IO Port必须通过IO Port的寻址方式。而访问IO Mem就比较罗嗦,外部MEM不能和主存一样访问,虽然大小上不相上下,可是外部MEM是没有在系统中注册的。访问外部IO MEM必须通过remap映射到内核的MEM空间后才能访问。

为了达到接口的同一性,内核提供了IO Port到IO Mem的映射函数。映射后IO Port就可以看作是IO Mem,按照IO Mem的访问方式即可。

2.        CPUARMPPC架构的情况

在这一类的嵌入式处理器中,IO Port的寻址方式是采用内存映射,也就是IO bus就是Mem bus。系统的寻址能力如果是32位,IO Port+Mem(包括IO Mem)可以达到4G。

访问arm这类IO Port时,我们也可以用IO Port专用寻址方式。至于在对IO Port寻址时,内核是具体如何完成的,这个在内核移植时就已经完成。在这种架构的处理器中,仍然保持对IO Port的支持,完全是i386架构遗留下来的问题,在此不多讨论。而访问IO Mem的方式和i386一致。

注意:linux内核给我提供了完全对IO Port和IO Mem的支持,然而具体去看看driver目录下的驱动程序,很少按照这个规范去组织IO Port和IO Mem资源。对这二者访问最关键问题就是地址的定位,在C语言中,使用volatile就可以实现。很多的代码访问IO Port中的寄存器时,就使用volatile关键字,虽然功能可以实现,我们还是不推荐使用。就像最简单的延时莫过于while,可是在多任务的系统中是坚决避免的!

 

CPU对外设端口物理地址的编址方式有两种:一种是IO映射方式,另一种是内存映射方式。

  Linux将基于IO映射方式的和内存映射方式的IO端口统称为IO区域(IO region)。

  IO region仍然是一种IO资源,因此它仍然可以用resource结构类型来描述。

  Linux管理IO region:

  1) request_region()

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

void request_region(unsigned long from, unsigned long num, const char *name)
这个函数用来申请一块输入输出区域。如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的io口。
  参数1:io端口的基地址。
  参数2:io端口占用的范围。
  参数3:使用这段io地址的设备名。
  在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。

request_region()用于内核为驱动“分配”端口,这里分配的意思是,记录该端口已经被某个进程使用,要是其它进程试图访问它,就会产生“忙”错误。所以目的在于实现资源的互斥访问。 反之, 如果一个资源只被一个进程访问,不会导致资源的争用,这时request_region()是可选的。

  2) check_region()

  检查一个给定区间的IO端口是否空闲,或者其中一些是否已经分配给某个IO设备。

      check_region(int io_port, int off_set)
  这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
  参数1:io端口的基地址,
  参数2:io端口占用的范围。
  返回值:0 没有占用, 非0,已经被占用。

  3) release_region()

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

void release_region(unsigned long from, unsigned long num)
这个函数用来释放一块输入输出区域。参数与前面的request_region()是对应的。

  4)对IO port的访问:

      Linux中可以通过以下辅助函数来访问IO端口:

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

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

 

 

对IO内存资源的访问:

Linux在头文件include/linux/ioport.h中,定义了三个对I/O内存资源进行操作的宏:  

  1) request_mem_region()

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

#define request_mem_region(start,n,name)
  __request_region(&iomem_resource, (start), (n), (name))

  2) check_mem_region()

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

:#define check_mem_region(start,n)
__check_region(&iomem_resource, (start), (n))


  3) release_mem_region()

  释放指定的IO内存资源。

#define release_mem_region(start,n)
__release_region(&iomem_resource, (start), (n))

其中,参数start是I/O内存资源的起始物理地址(是CPU的RAM物理地址空间中的物理地址),参数n指定I/O内存资源的大小。


  驱动开发人员可以将内存映射方式的IO端口和外设内存统一看作是IO内存资源。

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

    void *ioremap(unsigned long phys_addr, unsigned long size)

  入口: phys_addr:要映射的起始的IO地址;

  size:要映射的空间的大小;

  flags:要映射的IO空间的和权限有关的标志;

  phys_addr:是要映射的物理地址,

  size:是要映射的长度,

  S3C2410的long是32位而非64位。

  功能:将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问;  

       ioremap是内核提供的用来映射外设寄存器到主存的函数,我们要映射的地址已经从pci_dev中读了出来(上一步),这样就水到渠成的成功映射了而不会和其他地址有冲突。映射完了有什么效果呢,我举个例子,比如某个网卡有100 个寄存器,他们都是连在一块的,位置是固定的,假如每个寄存器占4个字节,那么一共400个字节的空间被映射到内存成功后,ioaddr就是这段地址的开头(注意ioaddr是虚拟地址,而mmio_start是物理地址,它是BIOS得到的,肯定是物理地址,而保护模式下CPU不认物理地址,只认虚拟地址),ioaddr+0就是第一个寄存器的地址,ioaddr+4就是第二个寄存器地址(每个寄存器占4个字节),以此类推,我们就能够在内存中访问到所有的寄存器进而操控他们了。     

     5)iounmap函数用于取消ioremap()所做的映射

   void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
  6)IO MEM的访问

      Linux中可以通过以下辅助函数来访问IO内存资源:

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

  Linux在kernel/resource.c文件中定义了全局变量ioport_resource和iomem_resource,来分别描述基于IO映射方式的整个IO端口空间和基于内存映射方式的IO内存资源空间(包括IO端口和外设内存)。

 

下面转载儒雅的一个程序:

#include  
#include
#include   /* for ioremap and iounmap */


static int __init hello_init(void)
{
    void        * v_addr = NULL;
    unsigned long p_addr = 0xfe000000;
    unsigned long size   = 0x1000;

    /* 将物理地址映射为虚拟地址 */
    v_addr = ioremap(p_addr, size);
    if (NULL == v_addr)
    {
        printk("ioremap 0x%lx error\n", p_addr);
    }
    else
    {
        /* 使用虚拟地址访问物理空间,访问结束后,释放本次映射 */
        printk("*(unsigned int *)v_addr = 0x%x\n",
                *(unsigned int *)v_addr);
        iounmap(v_addr);
    }

    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_ALERT "Goodbye world 1.\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zengxiaolong ");
MODULE_DESCRIPTION("A sample driver");
MODULE_SUPPORTED_DEVICE("testdevice");

阅读(782) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:转帖:为arm linux 2.6.24 添加系统调用

给主人留下些什么吧!~~