如何在Android平台上使用PMEM及注册Platform设备

Android中PMEM驱动程序是物理内存的驱动程序,可用于分配物理内存。PMEM在camera和video系统中频繁使用。下面,简单记录一下PMEM的使用方法。另外,由于PMEM设备做为Platform设备存在,所以我们将对Platform设备做以简单描述。

一、PMEM的使用

使用PMEM需要包含如下几个头文件:

#include <sys/ioctl.h>

#include <binder/MemoryHeapBase.h>

#include <binder/MemoryHeapPmem.h>

#include <linux/android_pmem.h>

 

定义如下几个数据结构:

#define PMEM_DEV "/dev/pmem0" //PMEM设备的路径

#define kBufferCount 3 //申请的buffer数目

sp<MemoryBase> mBuffers[kBufferCount];//存储PMEM buffer的数组

int mBuffersPhys[kBufferCount];//存储PMEM buffer的物理地址

int8 *mBuffersVirt[kBufferCount]; //存储PMEM buffer的逻辑地址

sp<MemoryHeapBase> masterHeap;

sp<MemoryHeapPmem>  mPreviewHeap;

 

下面,我们分配3个大小为mPreviewFrameSize的buffer,同时获取每个buffer的物理地址和逻辑地址,将3个buffer放入数组mBuffers中。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Int mem_size = kBufferCount  * mPreviewFrameSize; 
 
masterHeap = new
 
             MemoryHeapBase(PMEM_DEV,mem_size,MemoryHeapBase::NO_CACHING);
 
     mPreviewHeap = new MemoryHeapPmem(masterHeap,MemoryHeapBase::NO_CACHING);
 
         if (mPreviewHeap->getHeapID() >= 0) {
 
             mPreviewHeap->slap();
 
             masterHeap.clear();
 
             struct pmem_region region;
 
            int fd_pmem = 0;
 
           fd_pmem = mPreviewHeap->getHeapID();
 
       ::ioctl(fd_pmem,PMEM_GET_PHYS,&region); //获取物理地址
 
       for ( int i = 0; i  < kBufferCount; i++){
 
         mBuffersPhys[i] = region.offset + i * mPreviewFrameSize;  
 
mBuffersVirt[i] = (int8 *)mPreviewHeap->getBase() + i * mPreviewFrameSize;
 
         mBuffers[i] = new MemoryBase(mPreviewHeap,  i * mPreviewFrameSize, mPreviewFrameSize);
 
         ssize_t offset;
 
         size_t size;
 
         mBuffers[i]->getMemory(&offset, &size);
 
         LOGD( "Preview buffer %d: offset: %d, size: %d." , i, offset, size);
 
       }
 
      }
 
   else LOGE( "Camera preview heap  error: could not create master heap!" );

mPreviewFrameSize:一帧的大小,即byte数;

MemoryHeapBase::NO_CACHING:表示该区域不会被cache;

::ioctl(fd_pmem,PMEM_GET_PHYS,&region);获取被分配的区域对应的物理地址;

mBuffersPhys[i] = region.offset + i * mPreviewFrameSize;获取每个buffer对应的物理地址;

mBuffersVirt[i] = (int8 *)mPreviewHeap->getBase() + i * mPreviewFrameSize;获取每个buffer对应的逻辑地址;

mBuffers[i] = new MemoryBase(mPreviewHeap,  i * mPreviewFrameSize, mPreviewFrameSize);被分配区域对应的每个buffer的信息;

mBuffers[i]->getMemory(&offset, &size);获取每个buffer的offset和大小;

由于将PMEM做为Platform设备,下面将对Platform设备做以简单描述。

 

二、Platform设备

在Linux 2.6的设备驱动模型中,我们关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2 C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver。

Linux platform. driver机制和传统的device driver 机制(通过driver_register函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform. device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性(这些标准接口是安全的)。

下面,我们介绍几个重要的数据结构。
platform_device结构体用来描述设备的名称、资源信息等。该结构被定义在

/kernel/include/linux/platform_device.h中。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct platform_device {
 
    const char * name;    //设备名
 
    int     id;           //设备编号
 
    struct device dev; 
 
    u32     num_resources;  //设备使用资源的数目
 
    struct resource  * resource;  //设备使用资源
 
};

下面来看一下platform_device结构体中最重要的一个成员struct resource * resource。struct resource被定义在include/linux/ioport.h中,定义原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct resource {
 
     resource_size_t start;  //资源起始地址
 
     resource_size_t end;    //资源结束地址
 
     const char *name;    
 
     unsigned long flags;    //资源类型
 
     struct resource *parent, *sibling, *child;
 
};

注:struct resource结构中我们通常关心start、end和flags这3个字段,分别标明资源的开始值、结束值和类型,flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。start、end的含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,譬如说某设备占据了2个内存区域,则可以定义2个IORESOURCE_MEM资源。

下面,我们以PMEM设备为例,对Platform设备的注册流程做以描述。

 

三、注册PMEM设备

这里我们除了描述PMEM设备,还将注册一个拥有memory空间和IRQ资源的示例设备example_device。

对于example_device,定义如下结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static struct resource example_resources[] = {
 
     [0] = {
 
         .start  = 0xC0000000,
 
         .end    = 0xC0020000,
 
         .flags  = IORESOURCE_MEM,
 
     },
 
     [1] = {
 
         .start  = 30,
 
         .end    = 30,
 
         .flags  = IORESOURCE_IRQ,
 
     },
 
};
 
static struct platform_device example_device = {
 
     .name           = "example" ,
 
     .id             = 0,
 
     .num_resources  = ARRAY_SIZE(example_resources),
 
     .resource       = example_resources,
 
};
example_device设备拥有IORESOURCE_MEM和IORESOURCE_IRQ两种资源,其IORESOURCE_MEM的起始地址为0xC0000000,结束地址为0xC0020000,IORESOURCE_IRQ的中断号为30。

对于PMEM设备,我们先要介绍一下结构体android_pmem_platform_data。它被定义在文件/kernel/include/linux/android_pmem.h中。其定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct android_pmem_platform_data
{
 
     const char * name;
 
     /* starting physical address of memory region */
 
     unsigned long start;
 
     /* size of memory region */
 
     unsigned long size;
 
     /* set to indicate the region should not be managed with an allocator */
 
     unsigned no_allocator;
 
     /* set to indicate maps of this region should be cached, if a mix of
 
      * cached and uncached is desired, set this and open the device with
 
      * O_SYNC to get an uncached region */
 
     unsigned cached;
 
     /* The MSM7k has bits to enable a write buffer in the bus controller*/
 
     unsigned buffered;
 
};
我们为PMEM设备定义如下结构体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static struct android_pmem_platform_data android_pmem0_pdata = {
 
        .name = "pmem0" ,
 
        .start = PMEM_0_BASE,
 
        .size = PMEM_0_SIZE,
 
        .no_allocator = 0,
 
        .cached = 1,
 
};
 
static struct android_pmem_platform_data android_pmem1_pdata = {
 
        .name = "pmem1" ,
 
        .start = PMEM_1_BASE,
 
        .size = PMEM_1_SIZE,
 
        .no_allocator = 0,
 
        .cached = 1,
 
};
 
struct platform_device android_pmem0_device = {
 
        .name = "android_pmem" ,
 
        .id = 0,
 
        .dev = { .platform_data = &android_pmem0_pdata },
 
};
 
struct platform_device android_pmem1_device = {
 
        .name = "android_pmem" ,
 
        .id = 1,
 
        .dev = { .platform_data = &android_pmem1_pdata },
 
};
然后将这几个设备结构体放置到一个platform_device的数组中,
1
2
3
4
5
6
7
8
9
static struct platform_device *devices[] __initdata = {
 
     &example_device,
 
     &android_pmem0_device,
 
     &android_pmem1_device,
 
};
最后通过调用函数platform_add_devices()向系统中添加这些设备。
1
2
3
4
5
6
7
8
9
10
static void __init androidphone_init( void )
{
 
     ……
 
     platform_add_devices(devices, ARRAY_SIZE(devices));
 
     ……
 
}

函数platform_add_devices()内部调用platform_device_register( )进行设备注册。要注意的是,这里的platform_device设备的注册过程必须在相应设备驱动加载之前被调用,即执行platform_driver_register()之前,原因是驱动注册时需要匹配内核中所有已注册的设备名。

函数platform_add_devices()定义在文件/kernel/driver/base/platform.c中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 
  * platform_add_devices - add a numbers of platform devices
 
  * @devs: array of platform devices to add
 
  * @num: number of platform devices in array
 
  */
 
int platform_add_devices( struct platform_device **devs, int num)
 
{
 
     int i, ret = 0;
 
  
 
     for (i = 0; i < num; i++) {
 
         ret = platform_device_register(devs[i]);
 
         if (ret) {
 
             while (--i >= 0)
 
                 platform_device_unregister(devs[i]);
 
             break ;
 
         }
 
     }
 
  
 
     return ret;
 
}
 
EXPORT_SYMBOL_GPL(platform_add_devices);

最后,需要说明的结构体是platform_driver,它的原型定义,在

/kernel/include/linux/platform_device.h中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct platform_driver {
 
     int (*probe)( struct platform_device *);
 
     int (*remove)( struct platform_device *);
 
     void (*shutdown)( struct platform_device *);
 
     int (*suspend)( struct platform_device *, pm_message_t state);
 
     int (*resume)( struct platform_device *);
 
     struct device_driver driver;
 
     struct platform_device_id *id_table;
 
};

内核提供的platform_driver结构体的注册函数为platform_driver_register(),其原型定义在/kernel/driver/base/platform.c文件中,具体实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 
  * platform_driver_register
 
  * @drv: platform driver structure
 
  */
 
int platform_driver_register( struct platform_driver *drv)
 
{
 
     drv->driver.bus = &platform_bus_type;
 
     if (drv->probe)
 
         drv->driver.probe = platform_drv_probe;
 
     if (drv->remove)
 
         drv->driver.remove = platform_drv_remove;
 
     if (drv->shutdown)
 
         drv->driver.shutdown = platform_drv_shutdown;
 
  
 
     return driver_register(&drv->driver);
 
}
 
EXPORT_SYMBOL_GPL(platform_driver_register);
如果想深入了解Platform机制,可以参考下面的文章:

Linux Platform驱动程序框架解析

http://www.linuxidc.com/Linux/2011-01/31291.htm

Linux内核驱动的的platform机制

http://intq.blog.163.com/blog/static/671231452010124112546491/

原文链接

你可能感兴趣的:(如何在Android平台上使用PMEM及注册Platform设备)