Subject: [PATCH] ARM: shmobile: ipmmu: Add basic PMB support

From: [email-masked] (Laurent Pinchart)
Date: Mon, 21 Jan 2013 14:12:27 +0100

Hi Damian,

Thank you for the patch.

On Friday 18 January 2013 15:35:10 Damian Hobson-Garcia wrote:
> The PMB can be used to remap 16, 64, 128 or 512 MiB pages from
> the 0x80000000-0xffffffff address range to anywhere in the
> 0x00000000-0x7fffffff range.

Isn't it 0x80000000 - 0xbfffffff to 0x00000000 - 0xffffffff ?

> It also has the ability to perform tiled-linear address translation,
> which can be used to access a memory buffer as a series of n x m tiles,
> useful for image encoding/decoding.
> Currently only the userspace API via character device is supported.

If I understand this correctly, you're allowing userspace to remap a virtual 
address block to a physical address block without performing any sanity check. 
Isn't that a major security issue ?

> All register access and hardware dependent functionality is
> provided by the IPMMU driver, which is shared with the IOMMU/TLB module.
> 
> Signed-off-by: Damian Hobson-Garcia <[email-masked]>
> ---
> This patch must be applied on top of the IPMMU patch series by
> Hideki EIRAKU. The code has been placed in the drivers/iommu directory for
> two reasons:
> 	1) The PMB also performs hardware address translation
> 	2) Since the PMB shares the same register address range as the
> 	   shmobile IOMMU, the two functions are accessed through the same
> 	   platform device, the driver for which is in drivers/iommu
> 
>  drivers/iommu/Kconfig          |   14 ++
>  drivers/iommu/Makefile         |    1 +
>  drivers/iommu/shmobile-ipmmu.c |  117 ++++++++++++-
>  drivers/iommu/shmobile-ipmmu.h |   28 +++-
>  drivers/iommu/shmobile-pmb.c   |  362 +++++++++++++++++++++++++++++++++++++
>  include/linux/ipmmu.h          |   29 ++++
>  6 files changed, 544 insertions(+), 7 deletions(-)
>  create mode 100644 drivers/iommu/shmobile-pmb.c
>  create mode 100644 include/linux/ipmmu.h
> 
> diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
> index d364494..44af0cb 100644
> --- a/drivers/iommu/Kconfig
> +++ b/drivers/iommu/Kconfig
> @@ -261,4 +261,18 @@ config SHMOBILE_IOMMU_L1SIZE
>  	default 256 if SHMOBILE_IOMMU_ADDRSIZE_64MB
>  	default 128 if SHMOBILE_IOMMU_ADDRSIZE_32MB
> 
> +config SHMOBILE_PMB
> +	bool "IPMMU PMB driver"
> +	default n
> +	select SHMOBILE_IPMMU
> +	help
> +	  This enables the PMB interface of the IPMMU hardware module.
> +	  The PMB can be used to remap 16, 64, 128 or 512 MiB pages from
> +	  the 0x80000000-0xffffffff address range to anywhere in the
> +	  0x00000000-0x7fffffff range.
> +	  PMB support can be used either with or without SHMOBILE IOMMU
> +	  support.
> +
> +	  If unsure, say N.
> +
>  endif # IOMMU_SUPPORT
> diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
> index ef0e520..618238b 100644
> --- a/drivers/iommu/Makefile
> +++ b/drivers/iommu/Makefile
> @@ -15,3 +15,4 @@ obj-$(CONFIG_TEGRA_IOMMU_SMMU) += tegra-smmu.o
>  obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o
>  obj-$(CONFIG_SHMOBILE_IOMMU) += shmobile-iommu.o
>  obj-$(CONFIG_SHMOBILE_IPMMU) += shmobile-ipmmu.o
> +obj-$(CONFIG_SHMOBILE_PMB) += shmobile-pmb.o
> diff --git a/drivers/iommu/shmobile-ipmmu.c b/drivers/iommu/shmobile-ipmmu.c
> index 8321f89..db2ae78 100644
> --- a/drivers/iommu/shmobile-ipmmu.c
> +++ b/drivers/iommu/shmobile-ipmmu.c
> @@ -14,15 +14,34 @@
>  #include <linux/slab.h>
>  #include <linux/platform_data/sh_ipmmu.h>
>  #include "shmobile-ipmmu.h"
> +#include <linux/ipmmu.h>
> 
> -#define IMCTR1 0x000
> -#define IMCTR2 0x004
> -#define IMASID 0x010
> -#define IMTTBR 0x014
> -#define IMTTBCR 0x018
> -
> +#define IMCTR1		0x00
>  #define IMCTR1_TLBEN (1 << 0)
>  #define IMCTR1_FLUSH (1 << 1)
> +#define IMCTR2		0x04
> +#define IMCTR2_PMBEN	0x01
> +#define IMASID		0x010
> +#define IMTTBR		0x014
> +#define IMTTBCR		0x018
> +#define IMPMBA_BASE	0x80
> +#define IMPBMA_V	(1 << 8)
> +#define IMPMBD_BASE	0xC0
> +#define IMPBMD_V	(1 << 8)
> +#define IMPBMD_SZ_16M	0x00
> +#define IMPBMD_SZ_64M	0x10
> +#define IMPBMD_SZ_128M	0x80
> +#define IMPBMD_SZ_512M	0x90
> +#define IMPBMD_BV	(1 << 9)
> +#define IMPBMD_TBM_MASK	(7 << 12)
> +#define IMPBMD_TBM_POS	12
> +#define IMPBMD_HBM_MASK	(7 << 16)
> +#define IMPBMD_HBM_POS	16
> +#define IMPBMD_VBM_MASK	(7 << 20)
> +#define IMPBMD_VBM_POS	20
> +
> +#define IMPBMA(x) (IMPMBA_BASE + 0x4*x)
> +#define IMPBMD(x) (IMPMBD_BASE + 0x4*x)
> 
>  static void ipmmu_reg_write(struct shmobile_ipmmu *ipmmu, unsigned long
> reg_off, unsigned long data)
> @@ -88,6 +107,91 @@ void ipmmu_tlb_set(struct shmobile_ipmmu *ipmmu,
> unsigned long phys, int size, mutex_unlock(&ipmmu->flush_lock);
>  }
> 
> +int ipmmu_pmb_enable(struct shmobile_ipmmu *ipmmu,
> +		      int enable)
> +{
> +	ipmmu_reg_write(ipmmu, IMCTR2, enable ? IMCTR2_PMBEN : 0);
> +	return 0;

This function could be void.

> +
> +}
> +int ipmmu_pmb_set_addr(struct shmobile_ipmmu *ipmmu,
> +		 int index,

The index could be an unsigned int.

> +		 unsigned long addr,
> +		 int enabled)

enabled could be a bool.

> +{
> +
> +	if (!enabled) {
> +		ipmmu_reg_write(ipmmu, IMPBMA(index), 0);
> +		return 0;
> +	}
> +
> +	ipmmu_reg_write(ipmmu, IMPBMA(index), addr |
> +		IMPBMD_V);
> +	return 0;

This function could be void.
> +

Please avoid extra blank lines.

> +}
> +
> +int ipmmu_pmb_set_data(struct shmobile_ipmmu *ipmmu,
> +		 int index,

index could be an unsigned int.

> +		 struct ipmmu_pmb_info *info,
> +		 struct pmb_tile_info *tile)

info and tile are not modified, you can use const struct ...

> +{
> +	int vbm, hbm, tbm;
> +	int w, h;
> +	unsigned long temp;

temp stores the value of a register, I think it would be better to use a type 
with an explicit length (u32 in this case). I would also rename temp to val, 
reg, regval or something similar.

> +
> +	if (!info || !info->enabled) {
> +		ipmmu_reg_write(ipmmu, IMPBMD(index), 0);
> +		return 0;
> +	}
> +
> +	temp = info->paddr;
> +
> +	switch (info->size_mb) {
> +	case 16:
> +		temp |= IMPBMD_SZ_16M;
> +		break;
> +	case 64:
> +		temp |= IMPBMD_SZ_64M;
> +		break;
> +	case 128:
> +		temp |= IMPBMD_SZ_128M;
> +		break;
> +	case 512:
> +		temp |= IMPBMD_SZ_512M;
> +		break;
> +	default:
> +		break;

Shouldn't you return an error here ?

> +	}
> +
> +	temp |= IMPBMD_V;
> +
> +	if (!tile || !tile->enabled) {
> +		ipmmu_reg_write(ipmmu, IMPBMD(index), temp);
> +		return 0;
> +	}
> +
> +	w = tile->tile_width;
> +	h = tile->tile_height;
> +
> +	if (w & (w - 1) || h & (h - 1))

What about using the IS_ALIGNED macro here ?

if (!IS_ALIGNED(tile->tile_width) || !IS_ALIGNED(tile->tile_height))

You could then get rid of the w and h variables.

Shouldn't you also check that the width and height are inside the valid range 
?

> +		return -EINVAL;
> +
> +	tbm = ilog2(tile->tile_width);
> +	vbm = ilog2(tile->tile_height) - 1;
> +	hbm = ilog2(tile->buffer_pitch) - tbm - 1;
> +	tbm -= 4;
> +
> +	temp |= (tbm << IMPBMD_TBM_POS) & IMPBMD_TBM_MASK;
> +	temp |= (vbm << IMPBMD_VBM_POS) & IMPBMD_VBM_MASK;
> +	temp |= (hbm << IMPBMD_HBM_POS) & IMPBMD_HBM_MASK;
> +	temp |= IMPBMD_BV;
> +	ipmmu_reg_write(ipmmu, IMPBMD(index),
> +			temp);

No need for a line split here.

> +	ipmmu_tlb_flush(ipmmu);
> +	return 0;
> +}
> +
>  static int ipmmu_probe(struct platform_device *pdev)
>  {
>  	struct shmobile_ipmmu *ipmmu;
> @@ -118,6 +222,7 @@ static int ipmmu_probe(struct platform_device *pdev)
>  	ipmmu_reg_write(ipmmu, IMCTR1, 0x0); /* disable TLB */
>  	ipmmu_reg_write(ipmmu, IMCTR2, 0x0); /* disable PMB */
>  	ipmmu_iommu_init(ipmmu);
> +	ipmmu->pmb_priv = ipmmu_pmb_init(ipmmu);
>  	return 0;
>  }
> 
> diff --git a/drivers/iommu/shmobile-ipmmu.h b/drivers/iommu/shmobile-ipmmu.h
> index 6270e7c..ecc4211 100644
> --- a/drivers/iommu/shmobile-ipmmu.h
> +++ b/drivers/iommu/shmobile-ipmmu.h
> @@ -11,6 +11,8 @@
>  #define __SHMOBILE_IPMMU_H__
> 
>  struct dma_iommu_mapping;
> +struct pmb_tile_info;
> +struct ipmmu_pmb_info;
> 
>  struct shmobile_ipmmu {
>  	struct device *dev;
> @@ -20,10 +22,12 @@ struct shmobile_ipmmu {
>  	struct dma_iommu_mapping *iommu_mapping;
>  	const char * const *dev_names;
>  	unsigned int num_dev_names;
> +	void *pmb_priv;
>  };
> 
> -#ifdef CONFIG_SHMOBILE_IPMMU_TLB
>  void ipmmu_tlb_flush(struct shmobile_ipmmu *ipmmu);
> +
> +#ifdef CONFIG_SHMOBILE_IPMMU_TLB
>  void ipmmu_tlb_set(struct shmobile_ipmmu *ipmmu, unsigned long phys, int
> size, int asid);
>  int ipmmu_iommu_init(struct shmobile_ipmmu *ipmmu);
> @@ -34,4 +38,26 @@ static int ipmmu_iommu_init(struct shmobile_ipmmu *ipmmu)
> }
>  #endif
> 
> +#ifdef CONFIG_SHMOBILE_PMB
> +/* Access functions used by PMB device */
> +/*void handle_free(struct device *dev, unsigned long handle, int size_mb);
> +unsigned long handle_alloc(struct device *dev, int size_mb);*/
> +int ipmmu_pmb_set_addr(struct shmobile_ipmmu *ipmmu,
> +		int index, unsigned long addr,
> +		int enabled);
> +int ipmmu_pmb_set_data(struct shmobile_ipmmu *ipmmu, int index,
> +		struct ipmmu_pmb_info *info,
> +		struct pmb_tile_info *tile);
> +int ipmmu_pmb_enable(struct shmobile_ipmmu *ipmmu, int index);
> +/* PMB initialization */
> +void *ipmmu_pmb_init(struct shmobile_ipmmu *ipmmu);
> +void ipmmu_pmb_deinit(void *pmb_priv);
> +#else
> +static inline void *ipmmu_pmb_init(struct device *dev)
> +{
> +	return NULL;
> +}
> +static inline void ipmmu_pmb_deinit(void *pmb_priv) { }
> +#endif
> +
>  #endif /* __SHMOBILE_IPMMU_H__ */
> diff --git a/drivers/iommu/shmobile-pmb.c b/drivers/iommu/shmobile-pmb.c
> new file mode 100644
> index 0000000..464af0b
> --- /dev/null
> +++ b/drivers/iommu/shmobile-pmb.c
> @@ -0,0 +1,362 @@
> +/*
> + * IPMMU-PMB driver
> + * Copyright (C) 2012 Damian Hobson-Garcia
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; version 2 of the License.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 
> USA + *
> + */
> +
> +#include <linux/platform_device.h>
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/cdev.h>
> +#include <linux/fs.h>
> +#include <linux/slab.h>
> +#include <linux/ipmmu.h>
> +#include "shmobile-ipmmu.h"
> +#include <linux/uaccess.h>

Please sort header files alphabetically, and move the #include "" at the end.

> +
> +#define PMB_DEVICE_NAME "pmb"
> +
> +#define PMB_NR 16
> +/* the smallest size that can be reserverd in the pmb */
> +#define PMB_GRANULARITY (16 << 20)
> +#define PMB_START_ADDR	0x80000000
> +#define PMB_SIZE	0x40000000
> +#define NUM_BITS(x)	((x) / PMB_GRANULARITY)
> +#define NR_BITMAPS	((NUM_BITS(PMB_SIZE) + BITS_PER_LONG - 1) \
> +				>> ilog2(BITS_PER_LONG))

Does ilog2(BITS_PER_LONG) resolve to a compile-time constant ?

> +
> +struct ipmmu_pmb_data {
> +	struct ipmmu_pmb_priv	*priv;
> +	struct ipmmu_pmb_info	info;
> +	struct pmb_tile_info	tile;
> +	unsigned long		handle;
> +	int			index;
> +};
> +
> +struct ipmmu_pmb_priv {
> +	struct shmobile_ipmmu	*ipmmu;
> +	struct device		*ipmmu_dev;
> +	struct cdev		cdev;
> +	struct class		*pmb_class;
> +	dev_t			pmb_dev;
> +	unsigned long		busy_pmbs;
> +	struct mutex		pmb_lock;
> +	struct ipmmu_pmb_data	pmbs[PMB_NR];
> +	struct mutex		alloc_lock;
> +	unsigned long		alloc_bitmaps[NR_BITMAPS];
> +	int			pmb_enabled;

bool could do here.

> +};
> +
> +static int valid_size(int size_mb)
> +{
> +	switch (size_mb) {
> +	case 16:
> +	case 64:
> +	case 128:
> +	case 512:
> +		return 1;
> +	}
> +	return 0;
> +
> +}
> +
> +static unsigned long alloc_handle(struct ipmmu_pmb_priv *priv,
> +				     int size_mb)
> +{
> +	int i;
> +	int idx;
> +	unsigned long tmp_bitmap;
> +	unsigned long alloc_mask;
> +	unsigned long align_mask;
> +	int alloc_bits;
> +
> +	if (!valid_size(size_mb))
> +		return -1;
> +
> +	alloc_bits = NUM_BITS(size_mb << 20);
> +	alloc_mask = alloc_bits < BITS_PER_LONG ?
> +				(1 << alloc_bits) - 1 : -1;
> +
> +
> +	align_mask = alloc_mask - 1;
> +	for (i = BITS_PER_LONG >> 1; i >= alloc_bits; i = i >> 1)
> +		align_mask = align_mask | (align_mask << i);
> +
> +	mutex_lock(&priv->alloc_lock);
> +	for (i = 0; i < NR_BITMAPS; i++) {
> +		tmp_bitmap = priv->alloc_bitmaps[i];
> +		tmp_bitmap |= align_mask;
> +		idx = 0;
> +		while (idx < BITS_PER_LONG) {
> +			idx = find_next_zero_bit(&tmp_bitmap, BITS_PER_LONG,
> +					idx);
> +			if (!((alloc_mask << idx) & priv->alloc_bitmaps[i]) ||
> +					(idx == BITS_PER_LONG))
> +				break;
> +			idx++;
> +		}
> +		if (idx < BITS_PER_LONG)
> +			break;
> +	}
> +	if (i == NR_BITMAPS) {
> +		mutex_unlock(&priv->alloc_lock);
> +		return 0;
> +	}
> +
> +	priv->alloc_bitmaps[i] |= (alloc_mask << idx);
> +	mutex_unlock(&priv->alloc_lock);
> +
> +	return PMB_START_ADDR + (i * BITS_PER_LONG + idx) * PMB_GRANULARITY;
> +}
> +
> +void free_handle(struct ipmmu_pmb_priv *priv,
> +			unsigned long handle,
> +			int size_mb)
> +{
> +	int idx;
> +	unsigned long offset;
> +	unsigned long alloc_bits;
> +	unsigned long alloc_mask;
> +
> +	alloc_bits = NUM_BITS(size_mb << 20);
> +	alloc_mask = alloc_bits < BITS_PER_LONG ?
> +				(1 << alloc_bits) - 1 : -1;
> +	offset = handle - PMB_START_ADDR;
> +	offset /= PMB_GRANULARITY;
> +	idx = offset & (BITS_PER_LONG - 1);
> +	offset = offset / BITS_PER_LONG;
> +	mutex_lock(&priv->alloc_lock);
> +	priv->alloc_bitmaps[offset] &= ~(alloc_mask << idx);
> +	mutex_unlock(&priv->alloc_lock);
> +}
> +
> +static int set_pmb(struct ipmmu_pmb_data *data,
> +	    struct ipmmu_pmb_info *info)
> +{
> +	struct ipmmu_pmb_priv *priv = data->priv;
> +	unsigned long data_mask;
> +	int err;
> +
> +	if (!info->enabled) {
> +		if (data->handle) {
> +			free_handle(priv, data->handle,
> +				data->info.size_mb);
> +			data->handle = 0;
> +		}
> +		data->info = *info;
> +		ipmmu_pmb_set_data(priv->ipmmu, data->index, NULL, NULL);
> +		ipmmu_pmb_set_addr(priv->ipmmu, data->index, 0, 0);
> +		ipmmu_tlb_flush(priv->ipmmu);
> +		return 0;
> +	}
> +
> +	if (data->info.enabled) {
> +		err = -EBUSY;
> +		goto err_out;
> +	}
> +
> +	data_mask = ~((info->size_mb) - 1);
> +
> +	if (info->paddr & ~(data_mask)) {
> +		err = -EINVAL;
> +		goto err_out;
> +	}
> +
> +	data->handle = alloc_handle(priv, info->size_mb);
> +
> +	if (!data->handle) {
> +		err = -ENOMEM;
> +		goto err_out;
> +	}
> +
> +	data->info = *info;
> +
> +	ipmmu_pmb_set_addr(priv->ipmmu, data->index, data->handle, 1);
> +	ipmmu_pmb_set_data(priv->ipmmu, data->index, &data->info,
> +		&data->tile);
> +
> +	if (!data->priv->pmb_enabled) {
> +		ipmmu_pmb_enable(priv->ipmmu, 1);
> +		data->priv->pmb_enabled = 1;
> +	}
> +
> +	ipmmu_tlb_flush(priv->ipmmu);
> +
> +	return 0;
> +
> +err_out:
> +	info->enabled = 0;
> +	return err;
> +}
> +
> +static int set_tile(struct ipmmu_pmb_data *data,
> +	    struct pmb_tile_info *tile)
> +{
> +	struct ipmmu_pmb_priv *priv = data->priv;
> +	data->tile = *tile;
> +	return ipmmu_pmb_set_data(priv->ipmmu, data->index, &data->info,
> +		&data->tile);
> +}
> +
> +static int ipmmu_pmb_open(struct inode *inode, struct file *filp)
> +{
> +	struct ipmmu_pmb_priv *priv;
> +	int idx;
> +	priv = container_of(inode->i_cdev, struct ipmmu_pmb_priv,
> +		cdev);
> +
> +	mutex_lock(&priv->pmb_lock);
> +	idx = find_first_zero_bit(&priv->busy_pmbs, PMB_NR);
> +	if (idx == PMB_NR)

You should unlock the mutex here.

> +		return -EBUSY;
> +
> +	__set_bit(idx, &priv->busy_pmbs);
> +	mutex_unlock(&priv->pmb_lock);
> +	priv->pmbs[idx].index = idx;
> +	priv->pmbs[idx].priv = priv;

You could set the index and priv fields for all PMBs at init time.

> +	filp->private_data = &priv->pmbs[idx];
> +	return 0;
> +}
> +
> +static int ipmmu_pmb_release(struct inode *inode, struct file *filp)
> +{
> +	struct ipmmu_pmb_data *pmb;
> +	pmb = filp->private_data;
> +	if (pmb->info.enabled) {
> +		pmb->info.enabled = 0;
> +		set_pmb(pmb, &pmb->info);
> +	}
> +
> +	mutex_lock(&pmb->priv->pmb_lock);
> +	__clear_bit(pmb->index, &pmb->priv->busy_pmbs);
> +	mutex_unlock(&pmb->priv->pmb_lock);
> +	return 0;
> +}
> +
> +static long ipmmu_pmb_ioctl(struct file *filp, unsigned int cmd_in,
> +		       unsigned long arg)
> +{
> +	struct ipmmu_pmb_data *pmb;
> +	struct ipmmu_pmb_info user_set;
> +	struct pmb_tile_info user_tile;
> +	long ret = -EINVAL;
> +	pmb = filp->private_data;
> +	switch (cmd_in) {
> +	case IPMMU_GET_PMB:
> +		ret = copy_to_user((char *)arg, &pmb->info, sizeof(pmb->info));
> +		break;
> +	case IPMMU_SET_PMB:
> +		ret = copy_from_user(&user_set, (char *)arg, sizeof(user_set));
> +		if (ret)
> +			break;
> +		ret = set_pmb(pmb, &user_set);
> +		if (!ret)
> +			pmb->info = user_set;
> +		break;
> +	case IPMMU_GET_PMB_HANDLE:
> +		ret = copy_to_user((char *)arg, &pmb->handle,
> +			sizeof(pmb->handle));
> +		break;
> +	case IPMMU_GET_PMB_TL:
> +		ret = copy_to_user((char *)arg, &pmb->tile, sizeof(pmb->tile));
> +		break;
> +	case IPMMU_SET_PMB_TL:
> +		ret = copy_from_user(&user_tile, (char *)arg,
> +			sizeof(user_tile));
> +		if (ret)
> +			break;
> +		ret = set_tile(pmb, &user_tile);
> +		if (!ret)
> +			pmb->tile = user_tile;
> +		break;
> +	}
> +	return ret;
> +}
> +
> +static const struct file_operations ipmmu_pmb_fops = {
> +	.owner = THIS_MODULE,
> +	.open = ipmmu_pmb_open,
> +	.release = ipmmu_pmb_release,
> +	.unlocked_ioctl = ipmmu_pmb_ioctl,
> +};
> +
> +void ipmmu_pmb_deinit(void *arg)
> +{
> +	struct ipmmu_pmb_priv *priv = arg;
> +
> +	if (!priv || IS_ERR(priv))
> +		return;
> +
> +	cdev_del(&priv->cdev);
> +	device_destroy(priv->pmb_class, priv->pmb_dev);
> +	unregister_chrdev_region(priv->pmb_dev, 1);
> +	class_destroy(priv->pmb_class);
> +	kfree(priv);
> +}
> +
> +void *ipmmu_pmb_init(struct shmobile_ipmmu *ipmmu)
> +{
> +	int err = -ENOENT;
> +	struct ipmmu_pmb_priv *priv;
> +
> +	priv = kzalloc(sizeof(struct ipmmu_pmb_priv), GFP_KERNEL);

Please use the devm_* allocation functions.

> +	if (!priv) {
> +		dev_err(ipmmu->dev, "cannot allocate device data\n");
> +		return ERR_PTR(-ENOMEM);
> +	}
> +
> +	priv->ipmmu = ipmmu;
> +	priv->ipmmu_dev = ipmmu->dev;
> +
> +	mutex_init(&priv->pmb_lock);
> +	mutex_init(&priv->alloc_lock);
> +
> +	priv->pmb_class = class_create(THIS_MODULE, "ipmmu-pmb");
> +	if (!priv->pmb_class)
> +		goto free_priv;
> +
> +	err = alloc_chrdev_region(&priv->pmb_dev, 0, 1, PMB_DEVICE_NAME);
> +	if (err) {
> +		dev_err(ipmmu->dev, "cannot allocate device num\n");
> +		goto destroy_class;
> +	}
> +
> +	if (!device_create(priv->pmb_class, ipmmu->dev, priv->pmb_dev, priv,
> +			"pmb"))
> +		goto unregister_region;
> +
> +	cdev_init(&priv->cdev, &ipmmu_pmb_fops);
> +	priv->cdev.owner = THIS_MODULE;
> +	priv->cdev.ops = &ipmmu_pmb_fops;
> +	err = cdev_add(&priv->cdev, priv->pmb_dev, 1);
> +	if (err) {
> +		dev_err(ipmmu->dev, "cannot add ipmmu_pmb device\n");
> +		goto destroy_device;
> +	}
> +
> +	return priv;
> +
> +destroy_device:
> +	device_destroy(priv->pmb_class, priv->pmb_dev);
> +unregister_region:
> +	unregister_chrdev_region(priv->pmb_dev, 1);
> +destroy_class:
> +	class_destroy(priv->pmb_class);
> +free_priv:
> +	kfree(priv);
> +	return ERR_PTR(err);
> +}
> diff --git a/include/linux/ipmmu.h b/include/linux/ipmmu.h
> new file mode 100644
> index 0000000..4d31122
> --- /dev/null
> +++ b/include/linux/ipmmu.h
> @@ -0,0 +1,29 @@
> +#ifndef __LINUX_IPMMU_PMB_H__
> +#define __LINUX_IPMMU_PMB_H__
> +
> +struct ipmmu_pmb_info {
> +	int		enabled;
> +	unsigned long	paddr;
> +	int             size_mb;
> +};
> +
> +struct pmb_tile_info {
> +	int             tile_width;
> +	int             tile_height;
> +	int             buffer_pitch;
> +	int             enabled;
> +};
> +
> +/* IOCTL commands. */
> +
> +#define IPMMU_SET_PMB		_IOW('S', 37, struct ipmmu_pmb_phys *)
> +#define IPMMU_GET_PMB		_IOR('S', 38, struct ipmmu_pmb_phys *)
> +#define IPMMU_GET_PMB_HANDLE	_IOR('S', 39, unsigned long *)
> +#define IPMMU_SET_PMB_TL	_IOW('S', 41, struct ipmmu_pmb_tile_info *)
> +#define IPMMU_GET_PMB_TL	_IOR('S', 42, struct ipmmu_pmb_tile_info *)
> +
> +#ifdef __kernel
> +
> +#endif /* __kernel */
> +
> +#endif /* __LINUX_IPMMU_PMB_H__ */
-- 
Regards,

Laurent Pinchart