Android学习之pmem driver

updated (2012/04/12):请注意,多方资料显示PMEM已经逐渐被抛弃,各个OEM厂商相对的都有各自的新的应对措施。
请参看:
http://lwn.net/Articles/480055/, 说明了总体情况,会由ION memory manager取代(android 4.0中已经引入了)。老的版本中各家厂商分别使用自己的一些实现:Nvidia(NVMAP), TI(CMEM),Qualcomm(PMEM),但是4.0之后,都在往ION迁移。

http://lwn.net/Articles/394665/, qualcomm对于PMEM的一些牢骚:)

后面我会补充一下分析ION的文章《Android学习之ION memory manager》及《玩转pandaboard之TI的ION》


这个东西是用来为user space和kernel driver之间预留连续的共享内存而存在的.  它作为platform driver来实现.

首先,看看它是如何被定义的:

Brian Swetland是这样描述它的

The pmem driver is used to manage large (1-16+MB) physically contiguous
regions of memory shared between userspace and kernel drivers (dsp, gpu,
etc).  It was written specifically to deal with hardware limitations of
the MSM7201A, but could be used for other chipsets as well.  For now,
you're safe to turn it off on x86.

从现在比较流行的几个SoC厂商的内核中来看, 目前qualcom/samsung/tegra都有利用它, 而TI则没有使用它.相关的信息可以自己从以下对应的git库中的Kconfig中看到.

$ git clone https://android.googlesource.com/kernel/msm.git
$ git clone https://android.googlesource.com/kernel/omap.git
$ git clone https://android.googlesource.com/kernel/samsung.git
$ git clone https://android.googlesource.com/kernel/tegra.git
另外, 插一句题外话, 大家如果感兴趣如何为android device编译烧录自己定制的kernel, 请参照< 如何制作android nexus one内核>

接下来, 就让我们看看它的具体实现及一些应用场景(以nexus one为例)

实现逻辑

android的这些驱动代码中的注释还是相当好的, 只要有些耐心, 看懂它们都不难. (但是,这个驱动的代码注释中有些简单的拼写错误,有瑕疵)

几个关键数据结构:

它们的相关成员代码中的注释都相当清晰明了, 这里就不多敷述了.

struct pmem_info, pmem的管理信息

其中有一个miscdevice的变量, 它使用来跟用户交互的接口, 每个pmem是一个platform device, 而对应的各个region就是由misc device来标识的.

也包括相关的region的使用信息及一个读写信号量,需要注意的是注释中强调了它与pmem_data中的那个读写信号量的使用场景, 如果需要同时获得两者,需要按照以下顺序:down(pmem_data->sem) => down(bitmap_sem)

struct pmem_data, 物理内存的描述信息

其中包含vm_area_struct, task_struct等标识该pmep region是如果被user所map到相应的用户进程信息

struct pmem_region, pmem_data的一部分,提供了pmem更好的灵活性

struct android_pmem_platform_data, pmem platform driver对应的管理信息

关键的函数:

pmem_setup, 

pmem中初始化给用户使用的连续物理内存的函数. 相关使用的物理内存由各个厂商的platform相关设备的初始化来完成, 并由此来初始化上面所说的android_pmem_platform_data.

比如, 而qualcomm的nexus one的,就在arch/arm/mach-msm/board-mahimahi.c中,
static struct android_pmem_platform_data mdp_pmem_pdata = {
    .name       = "pmem",
    .start      = MSM_PMEM_MDP_BASE,
    .size       = MSM_PMEM_MDP_SIZE,
    .no_allocator   = 0,
    .cached     = 1,
};

static struct android_pmem_platform_data android_pmem_adsp_pdata = {
    .name       = "pmem_adsp",
    .start      = MSM_PMEM_ADSP_BASE,
    .size       = MSM_PMEM_ADSP_SIZE,
    .no_allocator   = 0,
    .cached     = 1,
};

static struct android_pmem_platform_data android_pmem_camera_pdata = {
    .name       = "pmem_camera",
    .start      = MSM_PMEM_CAMERA_BASE,
    .size       = MSM_PMEM_CAMERA_SIZE,
    .no_allocator   = 1,
    .cached     = 1,
};
static struct platform_device android_pmem_mdp_device = {
    .name       = "android_pmem",
    .id     = 0,
    .dev        = {
        .platform_data = &mdp_pmem_pdata
    },
};

static struct platform_device android_pmem_adsp_device = {
    .name       = "android_pmem",
    .id     = 1,
    .dev        = {
        .platform_data = &android_pmem_adsp_pdata,
    },
};

static struct platform_device android_pmem_camera_device = {
    .name       = "android_pmem",
    .id     = 2,
    .dev        = {
        .platform_data = &android_pmem_camera_pdata,
    },
};

其中还有一个关于对ioremap的使用, 通过它来初始化各个region,这里也就最清晰的表示了如何把pmem与硬件的特素I/O地址联系起来.对于ioremap一系列的接口的介绍强烈建议大家回顾lld3中的相关章节. 同时, 还有一个帮助理解io map的讨论也值得看看.

所以各个可供pmem的物理地址大小都在初始化阶段就完成了, 相关的大小范围参看对应的arch/arm/mach-msm/board-mahimahi.h中的定义.

其余部分就是通常的一些域的初始化了,没什么特别的,这里就略过.

pmem_open, pmem_free, pmem_mmap, pmem_release就是通过misc设备暴露给用户使用的几个接口, 当然对应的也有相关的ioctl.

关键的还是pmem_allocate, 用来基于一个简单的buddy体系管理region及pmem, 每次的分配和回收都会符合buddy的概念.
同时,关于pmem_mmap这个函数也是关键之一, 代码结构还是挺清晰的, 主要抓住如何把用户传来的vma转化为受管理的pmem这条主线,具体再看pmem_mmap及其相关的辅助函数还是很容易理解的. 这里我也就简单略过:)

应用场景

搜索一下Cyanogenmod中的hardware/目录下, 就可以知道哪些厂商的lib用了pmem,比如, qualcomm的libgralloc(android的mali GPU)实现的接口.

qcom/media/mm-video/qdsp6/vdec/src/pmem.c
msm7k/libgralloc-qsd8k/gralloc.cpp, gpu.cpp, ...
比如看一下
qcom/media/mm-video/qdsp6/vdec/src/
下的关于video decode的实现代码, 很容易就找到了使用上面提到的ioctl的代码,它起到包装的作用

int pmem_alloc(struct pmem *pmem, unsigned sz)
{
...
   struct pmem_region region;

#if USE_SMI
   pmem->fd = open("/dev/pmem_gpu0", O_RDWR);
#elif defined(USE_PMEM_ADSP_CACHED)
   pmem->fd = open("/dev/pmem_adsp", O_RDWR);
#else
   //uncached behavior
   pmem->fd = open("/dev/pmem_adsp", O_RDWR|O_SYNC);
#endif

   if (pmem->fd < 0) {
      perror("cannot open pmem device");
      return -1;
   }
   //sz = (sz + 4095) & (~4095);
   pmem->size = sz;
   pmem->data = mmap(NULL, sz, PROT_READ | PROT_WRITE,
           MAP_SHARED, pmem->fd, 0);

 ....
这个包装函数会被video deocde的处理函数调用, 这是因为大部分的SoC都会有独立的DSP来处理相关音视频编解码, 这里的视频解码用的就是adsp这个dsp设备, 而基本上所有的dsp设备在处理各种数据的时候,都要求传递给它的地址要是物理上连续的,所以这里用pmem就相当符合要求,这也是为什么android要设计出pmem这样一个驱动. 通常情况如果要分配连续的物理内存需要使用kernel中的一套其它接口, 用起来会比较麻烦.而且因为SoC的各种实现不同,对于连续物理内存都有不少限制或者特殊要求, 有了pmem及相应的SoC platform支持, 对于中间件来说,使用起来更加友好些.

Vdec_ReturnType vdec_allocate_input_buffer(unsigned int size,
                  Vdec_BufferInfo * buf, int is_pmem)
{
   ...
   if (is_pmem) {
      struct pmem pmem_data;

      pmem_data.fd = -1;
      pmem_data.size = (size + page_size - 1) & (~(page_size - 1));;

      QTV_MSG_PRIO(QTVDIAG_GENERAL, QTVDIAG_PRIO_HIGH,
              "Allocating input buffer from pmem\n");
      if (-1 == pmem_alloc(&pmem_data, pmem_data.size)) {
         QTV_MSG_PRIO(QTVDIAG_GENERAL, QTVDIAG_PRIO_ERROR,
                 "pmem allocation failed\n");
         return VDEC_EFAILED;
      }
      QTV_MSG_PRIO1(QTVDIAG_GENERAL, QTVDIAG_PRIO_HIGH,
               "Allocated input buffer from pmem size %d\n",
               pmem_data.size);

      buf->base = (byte *) pmem_data.data;
      buf->pmem_id = pmem_data.fd;
      buf->bufferSize = pmem_data.size;
      buf->pmem_offset = 0;
    ... 

参考资料:

      http://hilbert-space.de/?p=14, 这篇文章很不错,对TI的CMEM做了阐述,后面我会在pandaboard上尝试以下CMEM,并做一个关于CMEM的分析文章。希望有兴趣的朋友也去看看<玩转pandaboard之多媒体上的那些事--TI CMEM>

你可能感兴趣的:(android,struct,git,buffer,input,Allocation)