[MTD][NOR] adding physical address to point()

Greg Ungerer gerg at snapgear.com
Tue Mar 11 19:56:31 EDT 2008


Hi Jared,

Jared Hulbert wrote:
> [MTD][NOR] adding physical address to point()
> 
> Adding the ability to get a physical address from point() in addition
> to virtual address.  This physical address is required for XIP of
> userspace code from flash.
> 
> Signed-off-by: Jared Hulbert <jaredeh at gmail.com>
> Reviewed-by: Joern Engel <joern at logfs.org>
> Acked-by: Nicolas Pitre <nico at cam.org>

I have no problem with the change to uclinux.c.
Heres an acked-by for that change if you want:

Acked-by: Greg Ungerer <gerg at uclinux.org>

Regards
Greg


> CC'ing dwmw2 and owners of diff'ed files. Adding Nicolas as Acked-by.
> 
> I need this to do ANY legitimate XIP of
> userspace code from Flash.  I've written AXFS to do XIP of userspace
> code.  Working with the linux-mm guys we come up with a way to make
> mm/filemap_xip.c to what we need it to. To XIP Flash pages we need to
> do a vm_insert_pfn().  To get a pfn we need a physical address.
> virt_to_page() doesn't work for ioremap'ed kaddr's (like mtd->virt
> mtd->cached) and Linus told us "no sneaky page table walks and don't
> screw with virt_to_page()."   So we're stuck needing to get this
> physical address from the mtd driver.  This was the way we came up
> with to do that a while back.
> 
> 
> drivers/mtd/devices/mtdram.c      |   11 +++++++----
> drivers/mtd/devices/phram.c       |   13 +++++++------
> drivers/mtd/devices/pmc551.c      |   27 +++++++++++++++++----------
> drivers/mtd/devices/slram.c       |   15 ++++++++++-----
> drivers/mtd/maps/uclinux.c        |    6 ++++--
> drivers/mtd/mtdpart.c             |    8 ++++----
> fs/jffs2/erase.c                  |    7 ++++---
> fs/jffs2/readinode.c              |    9 +++++----
> fs/jffs2/scan.c                   |    7 ++++---
> include/linux/mtd/mtd.h           |    6 ++++--
> include/linux/mtd/pmc551.h        |    5 +++--
> drivers/mtd/chips/cfi_cmdset_0001.c |   14 ++++++++------
> 12 files changed, 77 insertions(+), 51 deletions(-)
> 
> diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c
> index 47794d2..d2105aa 100644
> --- a/drivers/mtd/chips/cfi_cmdset_0001.c
> +++ b/drivers/mtd/chips/cfi_cmdset_0001.c
> @@ -82,9 +82,8 @@ static struct mtd_info *cfi_intelext_setup (struct mtd_info *);
>  static int cfi_intelext_partition_fixup(struct mtd_info *, struct cfi_private **);
>  
>  static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len,
> -		     size_t *retlen, u_char **mtdbuf);
> -static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from,
> -			size_t len);
> +		     size_t *retlen, void **virt, resource_size_t *phys);
> +static void cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len);
>  
>  static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
>  static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
> @@ -1237,7 +1236,8 @@ static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t a
>  	return ret;
>  }
>  
> -static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf)
> +static int cfi_intelext_point(struct mtd_info *mtd, loff_t from, size_t len,
> +		size_t *retlen, void **virt, resource_size_t *phys)
>  {
>  	struct map_info *map = mtd->priv;
>  	struct cfi_private *cfi = map->fldrv_priv;
> @@ -1254,8 +1254,10 @@ static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, si
>  	chipnum = (from >> cfi->chipshift);
>  	ofs = from - (chipnum << cfi->chipshift);
>  
> -	*mtdbuf = (void *)map->virt + cfi->chips[chipnum].start + ofs;
> +	*virt = map->virt + cfi->chips[chipnum].start + ofs;
>  	*retlen = 0;
> +	if (phys)
> +		*phys = map->phys + cfi->chips[chipnum].start + ofs;
>  
>  	while (len) {
>  		unsigned long thislen;
> @@ -1288,7 +1290,7 @@ static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, si
>  	return 0;
>  }
>  
> -static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
> +static void cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
>  {
>  	struct map_info *map = mtd->priv;
>  	struct cfi_private *cfi = map->fldrv_priv;
> diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c
> index e427c82..fe39a9d 100644
> --- a/drivers/mtd/devices/mtdram.c
> +++ b/drivers/mtd/devices/mtdram.c
> @@ -47,18 +47,21 @@ static int ram_erase(struct mtd_info *mtd, struct erase_info *instr)
>  }
>  
>  static int ram_point(struct mtd_info *mtd, loff_t from, size_t len,
> -		size_t *retlen, u_char **mtdbuf)
> +		size_t *retlen, void **virt, resource_size_t *phys)
>  {
>  	if (from + len > mtd->size)
>  		return -EINVAL;
>  
> -	*mtdbuf = mtd->priv + from;
> +	/* can we return a physical address with this driver? */
> +	if (phys)
> +		return -EINVAL;
> +
> +	*virt = mtd->priv + from;
>  	*retlen = len;
>  	return 0;
>  }
>  
> -static void ram_unpoint(struct mtd_info *mtd, u_char * addr, loff_t from,
> -		size_t len)
> +static void ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
>  {
>  }
>  
> diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c
> index 180298b..2b4d739 100644
> --- a/drivers/mtd/devices/phram.c
> +++ b/drivers/mtd/devices/phram.c
> @@ -57,20 +57,21 @@ static int phram_erase(struct mtd_info *mtd, struct erase_info *instr)
>  }
>  
>  static int phram_point(struct mtd_info *mtd, loff_t from, size_t len,
> -		size_t *retlen, u_char **mtdbuf)
> +		size_t *retlen, void **virt, resource_size_t *phys)
>  {
> -	u_char *start = mtd->priv;
> -
>  	if (from + len > mtd->size)
>  		return -EINVAL;
>  
> -	*mtdbuf = start + from;
> +	/* can we return a physical address with this driver? */
> +	if (phys)
> +		return -EINVAL;
> +
> +	*virt = mtd->priv + from;
>  	*retlen = len;
>  	return 0;
>  }
>  
> -static void phram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from,
> -		size_t len)
> +static void phram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
>  {
>  }
>  
> diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c
> index 7060a08..bc99817 100644
> --- a/drivers/mtd/devices/pmc551.c
> +++ b/drivers/mtd/devices/pmc551.c
> @@ -134,7 +134,8 @@ static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
>  	eoff_lo = end & (priv->asize - 1);
>  	soff_lo = instr->addr & (priv->asize - 1);
>  
> -	pmc551_point(mtd, instr->addr, instr->len, &retlen, &ptr);
> +	pmc551_point(mtd, instr->addr, instr->len, &retlen,
> +		     (void **)&ptr, NULL);
>  
>  	if (soff_hi == eoff_hi || mtd->size == priv->asize) {
>  		/* The whole thing fits within one access, so just one shot
> @@ -154,7 +155,8 @@ static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
>  			}
>  			soff_hi += priv->asize;
>  			pmc551_point(mtd, (priv->base_map0 | soff_hi),
> -				     priv->asize, &retlen, &ptr);
> +				     priv->asize, &retlen,
> +				     (void **)&ptr, NULL);
>  		}
>  		memset(ptr, 0xff, eoff_lo);
>  	}
> @@ -170,7 +172,7 @@ static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
>  }
>  
>  static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
> -			size_t * retlen, u_char ** mtdbuf)
> +			size_t *retlen, void **virt, resource_size_t *phys)
>  {
>  	struct mypriv *priv = mtd->priv;
>  	u32 soff_hi;
> @@ -188,6 +190,10 @@ static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
>  		return -EINVAL;
>  	}
>  
> +	/* can we return a physical address with this driver? */
> +	if (phys)
> +		return -EINVAL;
> +
>  	soff_hi = from & ~(priv->asize - 1);
>  	soff_lo = from & (priv->asize - 1);
>  
> @@ -198,13 +204,12 @@ static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
>  		priv->curr_map0 = soff_hi;
>  	}
>  
> -	*mtdbuf = priv->start + soff_lo;
> +	*virt = priv->start + soff_lo;
>  	*retlen = len;
>  	return 0;
>  }
>  
> -static void pmc551_unpoint(struct mtd_info *mtd, u_char * addr, loff_t from,
> -			   size_t len)
> +static void pmc551_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
>  {
>  #ifdef CONFIG_MTD_PMC551_DEBUG
>  	printk(KERN_DEBUG "pmc551_unpoint()\n");
> @@ -242,7 +247,7 @@ static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len,
>  	soff_lo = from & (priv->asize - 1);
>  	eoff_lo = end & (priv->asize - 1);
>  
> -	pmc551_point(mtd, from, len, retlen, &ptr);
> +	pmc551_point(mtd, from, len, retlen, (void **)&ptr, NULL);
>  
>  	if (soff_hi == eoff_hi) {
>  		/* The whole thing fits within one access, so just one shot
> @@ -263,7 +268,8 @@ static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len,
>  				goto out;
>  			}
>  			soff_hi += priv->asize;
> -			pmc551_point(mtd, soff_hi, priv->asize, retlen, &ptr);
> +			pmc551_point(mtd, soff_hi, priv->asize, retlen,
> +				     (void **)&ptr, NULL);
>  		}
>  		memcpy(copyto, ptr, eoff_lo);
>  		copyto += eoff_lo;
> @@ -308,7 +314,7 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len,
>  	soff_lo = to & (priv->asize - 1);
>  	eoff_lo = end & (priv->asize - 1);
>  
> -	pmc551_point(mtd, to, len, retlen, &ptr);
> +	pmc551_point(mtd, to, len, retlen, (void **)&ptr, NULL);
>  
>  	if (soff_hi == eoff_hi) {
>  		/* The whole thing fits within one access, so just one shot
> @@ -329,7 +335,8 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len,
>  				goto out;
>  			}
>  			soff_hi += priv->asize;
> -			pmc551_point(mtd, soff_hi, priv->asize, retlen, &ptr);
> +			pmc551_point(mtd, soff_hi, priv->asize, retlen,
> +				     (void **)&ptr, NULL);
>  		}
>  		memcpy(ptr, copyfrom, eoff_lo);
>  		copyfrom += eoff_lo;
> diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c
> index d293add..cb86db7 100644
> --- a/drivers/mtd/devices/slram.c
> +++ b/drivers/mtd/devices/slram.c
> @@ -76,8 +76,9 @@ static char *map;
>  static slram_mtd_list_t *slram_mtdlist = NULL;
>  
>  static int slram_erase(struct mtd_info *, struct erase_info *);
> -static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, u_char **);
> -static void slram_unpoint(struct mtd_info *, u_char *, loff_t,	size_t);
> +static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, void **,
> +		resource_size_t *);
> +static void slram_unpoint(struct mtd_info *, loff_t, size_t);
>  static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);
>  static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
>  
> @@ -104,19 +105,23 @@ static int slram_erase(struct mtd_info *mtd, struct erase_info *instr)
>  }
>  
>  static int slram_point(struct mtd_info *mtd, loff_t from, size_t len,
> -		size_t *retlen, u_char **mtdbuf)
> +		size_t *retlen, void **virt, resource_size_t *phys)
>  {
>  	slram_priv_t *priv = mtd->priv;
>  
> +	/* can we return a physical address with this driver? */
> +	if (phys)
> +		return -EINVAL;
> +
>  	if (from + len > mtd->size)
>  		return -EINVAL;
>  
> -	*mtdbuf = priv->start + from;
> +	*virt = priv->start + from;
>  	*retlen = len;
>  	return(0);
>  }
>  
> -static void slram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
> +static void slram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
>  {
>  }
>  
> diff --git a/drivers/mtd/maps/uclinux.c b/drivers/mtd/maps/uclinux.c
> index 14ffb1a..c42f4b8 100644
> --- a/drivers/mtd/maps/uclinux.c
> +++ b/drivers/mtd/maps/uclinux.c
> @@ -40,10 +40,12 @@ struct mtd_partition uclinux_romfs[] = {
>  /****************************************************************************/
>  
>  int uclinux_point(struct mtd_info *mtd, loff_t from, size_t len,
> -	size_t *retlen, u_char **mtdbuf)
> +	size_t *retlen, void **virt, resource_size_t *phys)
>  {
>  	struct map_info *map = mtd->priv;
> -	*mtdbuf = (u_char *) (map->virt + ((int) from));
> +	*virt = map->virt + from;
> +	if (phys)
> +		*phys = map->phys + from;
>  	*retlen = len;
>  	return(0);
>  }
> diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
> index c66902d..07c7011 100644
> --- a/drivers/mtd/mtdpart.c
> +++ b/drivers/mtd/mtdpart.c
> @@ -68,7 +68,7 @@ static int part_read (struct mtd_info *mtd, loff_t from, size_t len,
>  }
>  
>  static int part_point (struct mtd_info *mtd, loff_t from, size_t len,
> -			size_t *retlen, u_char **buf)
> +			size_t *retlen, void **virt, resource_size_t *phys)
>  {
>  	struct mtd_part *part = PART(mtd);
>  	if (from >= mtd->size)
> @@ -76,14 +76,14 @@ static int part_point (struct mtd_info *mtd, loff_t from, size_t len,
>  	else if (from + len > mtd->size)
>  		len = mtd->size - from;
>  	return part->master->point (part->master, from + part->offset,
> -				    len, retlen, buf);
> +				    len, retlen, virt, phys);
>  }
>  
> -static void part_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
> +static void part_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
>  {
>  	struct mtd_part *part = PART(mtd);
>  
> -	part->master->unpoint (part->master, addr, from + part->offset, len);
> +	part->master->unpoint(part->master, from + part->offset, len);
>  }
>  
>  static int part_read_oob(struct mtd_info *mtd, loff_t from,
> diff --git a/fs/jffs2/erase.c b/fs/jffs2/erase.c
> index a1db918..6c2a046 100644
> --- a/fs/jffs2/erase.c
> +++ b/fs/jffs2/erase.c
> @@ -332,7 +332,8 @@ static int jffs2_block_check_erase(struct jffs2_sb_info *c, struct jffs2_erasebl
>  	if (c->mtd->point) {
>  		unsigned long *wordebuf;
>  
> -		ret = c->mtd->point(c->mtd, jeb->offset, c->sector_size, &retlen, (unsigned char **)&ebuf);
> +		ret = c->mtd->point(c->mtd, jeb->offset, c->sector_size,
> +				    &retlen, &ebuf, NULL);
>  		if (ret) {
>  			D1(printk(KERN_DEBUG "MTD point failed %d\n", ret));
>  			goto do_flash_read;
> @@ -340,7 +341,7 @@ static int jffs2_block_check_erase(struct jffs2_sb_info *c, struct jffs2_erasebl
>  		if (retlen < c->sector_size) {
>  			/* Don't muck about if it won't let us point to the whole erase sector */
>  			D1(printk(KERN_DEBUG "MTD point returned len too short: 0x%zx\n", retlen));
> -			c->mtd->unpoint(c->mtd, ebuf, jeb->offset, retlen);
> +			c->mtd->unpoint(c->mtd, jeb->offset, retlen);
>  			goto do_flash_read;
>  		}
>  		wordebuf = ebuf-sizeof(*wordebuf);
> @@ -349,7 +350,7 @@ static int jffs2_block_check_erase(struct jffs2_sb_info *c, struct jffs2_erasebl
>  		   if (*++wordebuf != ~0)
>  			   break;
>  		} while(--retlen);
> -		c->mtd->unpoint(c->mtd, ebuf, jeb->offset, c->sector_size);
> +		c->mtd->unpoint(c->mtd, jeb->offset, c->sector_size);
>  		if (retlen)
>  			printk(KERN_WARNING "Newly-erased block contained word 0x%lx at offset 0x%08tx\n",
>  			       *wordebuf, jeb->offset + c->sector_size-retlen*sizeof(*wordebuf));
> diff --git a/fs/jffs2/readinode.c b/fs/jffs2/readinode.c
> index e512a93..22acc12 100644
> --- a/fs/jffs2/readinode.c
> +++ b/fs/jffs2/readinode.c
> @@ -63,10 +63,11 @@ static int check_node_data(struct jffs2_sb_info *c, struct jffs2_tmp_dnode_info
>  	/* TODO: instead, incapsulate point() stuff to jffs2_flash_read(),
>  	 * adding and jffs2_flash_read_end() interface. */
>  	if (c->mtd->point) {
> -		err = c->mtd->point(c->mtd, ofs, len, &retlen, &buffer);
> +		err = c->mtd->point(c->mtd, ofs, len, &retlen,
> +				    (void **)&buffer, NULL);
>  		if (!err && retlen < len) {
>  			JFFS2_WARNING("MTD point returned len too short: %zu instead of %u.\n", retlen, tn->csize);
> -			c->mtd->unpoint(c->mtd, buffer, ofs, retlen);
> +			c->mtd->unpoint(c->mtd, ofs, retlen);
>  		} else if (err)
>  			JFFS2_WARNING("MTD point failed: error code %d.\n", err);
>  		else
> @@ -100,7 +101,7 @@ static int check_node_data(struct jffs2_sb_info *c, struct jffs2_tmp_dnode_info
>  		kfree(buffer);
>  #ifndef __ECOS
>  	else
> -		c->mtd->unpoint(c->mtd, buffer, ofs, len);
> +		c->mtd->unpoint(c->mtd, ofs, len);
>  #endif
>  
>  	if (crc != tn->data_crc) {
> @@ -136,7 +137,7 @@ free_out:
>  		kfree(buffer);
>  #ifndef __ECOS
>  	else
> -		c->mtd->unpoint(c->mtd, buffer, ofs, len);
> +		c->mtd->unpoint(c->mtd, ofs, len);
>  #endif
>  	return err;
>  }
> diff --git a/fs/jffs2/scan.c b/fs/jffs2/scan.c
> index 272872d..b1b3884 100644
> --- a/fs/jffs2/scan.c
> +++ b/fs/jffs2/scan.c
> @@ -97,11 +97,12 @@ int jffs2_scan_medium(struct jffs2_sb_info *c)
>  	size_t pointlen;
>  
>  	if (c->mtd->point) {
> -		ret = c->mtd->point (c->mtd, 0, c->mtd->size, &pointlen, &flashbuf);
> +		ret = c->mtd->point(c->mtd, 0, c->mtd->size, &pointlen,
> +				    (void **)&flashbuf, NULL);
>  		if (!ret && pointlen < c->mtd->size) {
>  			/* Don't muck about if it won't let us point to the whole flash */
>  			D1(printk(KERN_DEBUG "MTD point returned len too short: 0x%zx\n", pointlen));
> -			c->mtd->unpoint(c->mtd, flashbuf, 0, pointlen);
> +			c->mtd->unpoint(c->mtd, 0, pointlen);
>  			flashbuf = NULL;
>  		}
>  		if (ret)
> @@ -267,7 +268,7 @@ int jffs2_scan_medium(struct jffs2_sb_info *c)
>  		kfree(flashbuf);
>  #ifndef __ECOS
>  	else
> -		c->mtd->unpoint(c->mtd, flashbuf, 0, c->mtd->size);
> +		c->mtd->unpoint(c->mtd, 0, c->mtd->size);
>  #endif
>  	if (s)
>  		kfree(s);
> diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
> index 0a13bb3..245f909 100644
> --- a/include/linux/mtd/mtd.h
> +++ b/include/linux/mtd/mtd.h
> @@ -143,10 +143,12 @@ struct mtd_info {
>  	int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
>  
>  	/* This stuff for eXecute-In-Place */
> -	int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);
> +	/* phys is optional and may be set to NULL */
> +	int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
> +			size_t *retlen, void **virt, resource_size_t *phys);
>  
>  	/* We probably shouldn't allow XIP if the unpoint isn't a NULL */
> -	void (*unpoint) (struct mtd_info *mtd, u_char * addr, loff_t from, size_t len);
> +	void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
>  
>  
>  	int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
> diff --git a/include/linux/mtd/pmc551.h b/include/linux/mtd/pmc551.h
> index a7f6d20..5cc070c 100644
> --- a/include/linux/mtd/pmc551.h
> +++ b/include/linux/mtd/pmc551.h
> @@ -36,8 +36,9 @@ struct mypriv {
>   * Function Prototypes
>   */
>  static int pmc551_erase(struct mtd_info *, struct erase_info *);
> -static void pmc551_unpoint(struct mtd_info *, u_char *, loff_t, size_t);
> -static int pmc551_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf);
> +static void pmc551_unpoint(struct mtd_info *, loff_t, size_t);
> +static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
> +		size_t *retlen, void **virt, resource_size_t *phys);
>  static int pmc551_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);
>  static int pmc551_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
> 
> 
> 

-- 
------------------------------------------------------------------------
Greg Ungerer  --  Chief Software Dude       EMAIL:     gerg at snapgear.com
Secure Computing Corporation                PHONE:       +61 7 3435 2888
825 Stanley St,                             FAX:         +61 7 3891 3630
Woolloongabba, QLD, 4102, Australia         WEB: http://www.SnapGear.com



More information about the linux-mtd mailing list