Sunday, December 21, 2014

ION Allocator

Each hardware vendor(Soc Manufacturer) has its own memory manager. For ex. Qualcomm has PMEM, NVIDIA has NVRAM, TI has CMEM. From ICS onwards google made it mandatory for all OEMs to use the ION memory allocator provided by google to maintain uniformity.


ION is a memory pool manager and also enables its clients to share buffers.

ION manages one or more memory pools, some of which are set aside at boot time to combat fragmentation or to serve special hardware needs. GPUs, display controllers, and cameras are some of the hardware blocks that may have special memory requirements. ION presents its memory pools as ION heaps. Each type of Android device can be provisioned with a different set of ION heaps according to the memory requirements of the device. The provider of an ION heap must implement the following set of callbacks:


struct ion_heap_ops {
	int (*allocate) (struct ion_heap *heap,
			 struct ion_buffer *buffer, unsigned long len,
			 unsigned long align, unsigned long flags);
	void (*free) (struct ion_buffer *buffer);
	int (*phys) (struct ion_heap *heap, struct ion_buffer *buffer,
		     ion_phys_addr_t *addr, size_t *len);
	struct scatterlist *(*map_dma) (struct ion_heap *heap,
			 struct ion_buffer *buffer);
	void (*unmap_dma) (struct ion_heap *heap, 
	         struct ion_buffer *buffer);
	void * (*map_kernel) (struct ion_heap *heap, 
	         struct ion_buffer *buffer);
	void (*unmap_kernel) (struct ion_heap *heap, 
	         struct ion_buffer *buffer);
	int (*map_user) (struct ion_heap *heap, struct ion_buffer *buffer,
			 struct vm_area_struct *vma);
   };



1. allocate() and free() obtain or release an ion_buffer object from the heap.
2. phys() will return the physical address and length of the buffer, but only for physically-contiguous buffers. If the heap does not provide physically contiguous buffers, it does not have   to provide this callback.
3. The map_dma() and unmap_dma() callbacks cause the buffer to be prepared (or unprepared)  for DMA.
4. The map_kernel() and unmap_kernel() callbacks map (or unmap) the physical memory into  the kernel virtual address space.
5. map_user() will map the memory to user space. There is no unmap_user() because the mapping is represented as a file descriptor in user space. The closing of that file descriptor will  
cause the memory to be unmapped from the calling process.


By default the ION driver provides three heaps and more heaps can be added depending upon the use.

   ION_HEAP_TYPE_SYSTEM:        memory allocated via vmalloc_user().
   ION_HEAP_TYPE_SYSTEM_CONTIG: memory allocated via kzalloc.
   ION_HEAP_TYPE_CARVEOUT:  carveout memory is physically contiguous and set aside at boot.



User space program must have access to /dec/ion then only it can be allocated memory by ION.A call to open("/dev/ion", O_RDONLY) returns a file descriptor as a handle representing an ION client. Writable memory  can be allocated with an O_RDONLY open. There can be no more than one client per user process. To allocate a buffer, the client needs to fill in all the fields except the handle field in this data structure:
   struct ion_allocation_data {
        size_t len;
        size_t align;
        unsigned int flags;
        struct ion_handle *handle;
   }



The first three fields are the input parameters and the fourth field is the O/P parameter.
In the flag field we can give priorities from where we wish to allocate the ion memory fromFor eg. the flags of ION_HEAP_TYPE_CONTIG | ION_HEAP_TYPE_CARVEOUT indicate the intention to allocate fromION_HEAP_TYPE_CARVEOUT with fallback to ION_HEAP_TYPE_CONTIG.

Userspace clients interact with the ION allocator using ioctls.

To allocate a buffer, the client makes this call:
   int ioctl(int client_fd, ION_IOC_ALLOC, struct ion_allocation_data *allocation_data)
This call returns a buffer represented by ion_handle which is not a CPU-accessible buffer pointer. The handle can only be used to obtain a file descriptor for buffer sharing as follows:


int ioctl(int client_fd, ION_IOC_SHARE, struct ion_fd_data *fd_data);
Here client_fd is the file descriptor corresponding to /dev/ion, and fd_data is a data structure with an input handle field and an output fd field, as defined below:
   struct ion_fd_data {
        struct ion_handle *handle;
        int fd;
   }


The fd field is the file descriptor that can be passed around for sharing. On Android devices the BINDER IPC mechanism may be used to send fd to another process for sharing


To obtain the shared buffer, the second user process must obtain a client handle first via the open("/dev/ion", O_RDONLY) system call. ION tracks its user space clients by the PID of the process (specifically, the PID of the thread that is the "group leader" in the process). Repeating the open("/dev/ion", O_RDONLY) call in the same process will get back another file descriptor corresponding to the same client structure in the kernel.

After getting the fd, the second client calls mmap to map the buffer in its address space.

To free the buffer, the second client needs to undo the effect of mmap() with a call to munmap(), and the first client needs to close the file descriptor it obtained via ION_IOC_SHARE, and call ION_IOC_FREE as follows:
     int ioctl(int client_fd, ION_IOC_FREE, struct ion_handle_data *handle_data);
Here ion_handle_data holds the handle as shown below:
     struct ion_handle_data {
	     struct ion_handle *handle;
     }

The ION_IOC_FREE command causes the handle's reference counter to be decremented by one. When this reference counter reaches zero, the ion_handle object gets destroyed and the affected ION bookkeeping data structure is updated.

ION_IOC_ALLOC : allocate memory. Not yet CPU accessible
 ION_IOC_FREE  : free memory
 ION_IOC_MAP   : get a file descriptor to mmap
 ION_IOC_SHARE : creates a file descriptor to use to share an allocation
 ION_IOC_IMPORT : imports a shared file descriptor
 ION_IOC_CUSTOM : architecture specific ioctl. this is used to implement platform specific functionality.


How to share the buffer allocated in userspace with the kernel space?


The user process typically allocates the buffer from ION, obtains a file descriptor using the ION_IOC_SHARE command, then passes the file desciptor to a kernel driver. The kernel driver calls ion_import_fd() which converts the file descriptor to an ion_handle object, as shown below:

struct ion_handle *ion_import_fd(struct ion_client *client, int fd_from_user);

The ion_handle object is the driver's client-local reference to the shared buffer. The ion_import_fd() call looks up the physical address of the buffer to see whether the client has obtained a handle to the same buffer before, and if it has, this call simply increments the reference counter of the existing handle.


Some hardware blocks can only operate on physically-contiguous buffers with physical addresses, so affected drivers need to convert ion_handle to a physical buffer via this call:
   int ion_phys(struct ion_client *client, struct ion_handle *handle,
	       ion_phys_addr_t *addr, size_t *len)
Needless to say, if the buffer is not physically contiguous, this call will fail.
The ION Allocator exports heap information through debugfs. Heap information is keyed after the PID and can be dumped like:
 cat /sys/kernel/debug/ion/ion-heap-1
 cat /sys/kernel/debug/ion/PID






No comments:

Post a Comment