Skip to content
Snippets Groups Projects
cmd_mtdparts.c 52.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * (C) Copyright 2002
     * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
     *
     * (C) Copyright 2002
     * Robert Schwebel, Pengutronix, <r.schwebel@pengutronix.de>
     *
     * (C) Copyright 2003
     * Kai-Uwe Bloem, Auerswald GmbH & Co KG, <linux-development@auerswald.de>
     *
     * (C) Copyright 2005
     * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
     *
     *   Added support for reading flash partition table from environment.
     *   Parsing routines are based on driver/mtd/cmdline.c from the linux 2.4
     *   kernel tree.
     *
    
     * (C) Copyright 2008
     * Harald Welte, OpenMoko, Inc., Harald Welte <laforge@openmoko.org>
     *
    
     *   $Id: cmdlinepart.c,v 1.17 2004/11/26 11:18:47 lavinen Exp $
     *   Copyright 2002 SYSGO Real-Time Solutions GmbH
     *
     * See file CREDITS for list of people who contributed to this
     * project.
     *
     * 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; either version 2 of
     * the License, or (at your option) any later version.
     *
     * 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., 59 Temple Place, Suite 330, Boston,
     * MA 02111-1307 USA
     */
    
    /*
     * Three environment variables are used by the parsing routines:
     *
     * 'partition' - keeps current partition identifier
     *
     * partition  := <part-id>
     * <part-id>  := <dev-id>,part_num
     *
     *
     * 'mtdids' - linux kernel mtd device id <-> u-boot device id mapping
     *
     * mtdids=<idmap>[,<idmap>,...]
     *
     * <idmap>    := <dev-id>=<mtd-id>
     * <dev-id>   := 'nand'|'nor'|'onenand'<dev-num>
     * <dev-num>  := mtd device number, 0...
     * <mtd-id>   := unique device tag used by linux kernel to find mtd device (mtd->name)
     *
     *
     * 'mtdparts' - partition list
     *
     * mtdparts=mtdparts=<mtd-def>[;<mtd-def>...]
     *
     * <mtd-def>  := <mtd-id>:<part-def>[,<part-def>...]
     * <mtd-id>   := unique device tag used by linux kernel to find mtd device (mtd->name)
     * <part-def> := <size>[@<offset>][<name>][<ro-flag>]
     * <size>     := standard linux memsize OR '-' to denote all remaining space
     * <offset>   := partition start offset within the device
     * <name>     := '(' NAME ')'
     * <ro-flag>  := when set to 'ro' makes partition read-only (not used, passed to kernel)
     *
     * Notes:
     * - each <mtd-id> used in mtdparts must albo exist in 'mtddis' mapping
     * - if the above variables are not set defaults for a given target are used
     *
     * Examples:
     *
     * 1 NOR Flash, with 1 single writable partition:
     * mtdids=nor0=edb7312-nor
     * mtdparts=mtdparts=edb7312-nor:-
     *
     * 1 NOR Flash with 2 partitions, 1 NAND with one
     * mtdids=nor0=edb7312-nor,nand0=edb7312-nand
     * mtdparts=mtdparts=edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)
     *
     */
    
    #include <common.h>
    #include <command.h>
    #include <malloc.h>
    
    #include <jffs2/load_kernel.h>
    
    #include <linux/list.h>
    #include <linux/ctype.h>
    
    #include <linux/err.h>
    #include <linux/mtd/mtd.h>
    
    
    #if defined(CONFIG_CMD_NAND)
    #include <linux/mtd/nand.h>
    #include <nand.h>
    #endif
    
    #if defined(CONFIG_CMD_ONENAND)
    #include <linux/mtd/onenand.h>
    #include <onenand_uboot.h>
    #endif
    
    /* special size referring to all the remaining space in a partition */
    #define SIZE_REMAINING		0xFFFFFFFF
    
    /* special offset value, it is used when not provided by user
     *
     * this value is used temporarily during parsing, later such offests
     * are recalculated */
    #define OFFSET_NOT_SPECIFIED	0xFFFFFFFF
    
    /* minimum partition size */
    #define MIN_PART_SIZE		4096
    
    /* this flag needs to be set in part_info struct mask_flags
     * field for read-only partitions */
    #define MTD_WRITEABLE_CMD		1
    
    /* default values for mtdids and mtdparts variables */
    #if defined(MTDIDS_DEFAULT)
    static const char *const mtdids_default = MTDIDS_DEFAULT;
    #else
    static const char *const mtdids_default = NULL;
    #endif
    
    #if defined(MTDPARTS_DEFAULT)
    static const char *const mtdparts_default = MTDPARTS_DEFAULT;
    #else
    static const char *const mtdparts_default = NULL;
    #endif
    
    /* copies of last seen 'mtdids', 'mtdparts' and 'partition' env variables */
    #define MTDIDS_MAXLEN		128
    #define MTDPARTS_MAXLEN		512
    #define PARTITION_MAXLEN	16
    static char last_ids[MTDIDS_MAXLEN];
    static char last_parts[MTDPARTS_MAXLEN];
    static char last_partition[PARTITION_MAXLEN];
    
    /* low level jffs2 cache cleaning routine */
    extern void jffs2_free_cache(struct part_info *part);
    
    /* mtdids mapping list, filled by parse_ids() */
    
    static struct list_head mtdids;
    
    
    /* device/partition list, parse_cmdline() parses into here */
    
    static struct list_head devices;
    
    
    /* current active device and partition number */
    
    struct mtd_device *current_mtd_dev = NULL;
    u8 current_mtd_partnum = 0;
    
    
    static struct part_info* mtd_part_info(struct mtd_device *dev, unsigned int part_num);
    
    /* command line only routines */
    static struct mtdids* id_find_by_mtd_id(const char *mtd_id, unsigned int mtd_id_len);
    static int device_del(struct mtd_device *dev);
    
    /**
     * Parses a string into a number.  The number stored at ptr is
     * potentially suffixed with K (for kilobytes, or 1024 bytes),
     * M (for megabytes, or 1048576 bytes), or G (for gigabytes, or
     * 1073741824).  If the number is suffixed with K, M, or G, then
     * the return value is the number multiplied by one kilobyte, one
     * megabyte, or one gigabyte, respectively.
     *
     * @param ptr where parse begins
     * @param retptr output pointer to next char after parse completes (output)
     * @return resulting unsigned int
     */
    static unsigned long memsize_parse (const char *const ptr, const char **retptr)
    {
    	unsigned long ret = simple_strtoul(ptr, (char **)retptr, 0);
    
    	switch (**retptr) {
    		case 'G':
    		case 'g':
    			ret <<= 10;
    		case 'M':
    		case 'm':
    			ret <<= 10;
    		case 'K':
    		case 'k':
    			ret <<= 10;
    			(*retptr)++;
    		default:
    			break;
    	}
    
    	return ret;
    }
    
    /**
     * Format string describing supplied size. This routine does the opposite job
     * to memsize_parse(). Size in bytes is converted to string and if possible
     * shortened by using k (kilobytes), m (megabytes) or g (gigabytes) suffix.
     *
     * Note, that this routine does not check for buffer overflow, it's the caller
     * who must assure enough space.
     *
     * @param buf output buffer
     * @param size size to be converted to string
     */
    static void memsize_format(char *buf, u32 size)
    {
    #define SIZE_GB ((u32)1024*1024*1024)
    #define SIZE_MB ((u32)1024*1024)
    #define SIZE_KB ((u32)1024)
    
    	if ((size % SIZE_GB) == 0)
    		sprintf(buf, "%ug", size/SIZE_GB);
    	else if ((size % SIZE_MB) == 0)
    		sprintf(buf, "%um", size/SIZE_MB);
    	else if (size % SIZE_KB == 0)
    		sprintf(buf, "%uk", size/SIZE_KB);
    	else
    		sprintf(buf, "%u", size);
    }
    
    /**
     * This routine does global indexing of all partitions. Resulting index for
     * current partition is saved in 'mtddevnum'. Current partition name in
     * 'mtddevname'.
     */
    static void index_partitions(void)
    {
    	char buf[16];
    	u16 mtddevnum;
    	struct part_info *part;
    	struct list_head *dentry;
    	struct mtd_device *dev;
    
    
    	debug("--- index partitions ---\n");
    
    		mtddevnum = 0;
    		list_for_each(dentry, &devices) {
    			dev = list_entry(dentry, struct mtd_device, link);
    
    			if (dev == current_mtd_dev) {
    				mtddevnum += current_mtd_partnum;
    
    				sprintf(buf, "%d", mtddevnum);
    				setenv("mtddevnum", buf);
    				break;
    			}
    			mtddevnum += dev->num_parts;
    		}
    
    
    		part = mtd_part_info(current_mtd_dev, current_mtd_partnum);
    
    		setenv("mtddevname", part->name);
    
    
    		debug("=> mtddevnum %d,\n=> mtddevname %s\n", mtddevnum, part->name);
    
    	} else {
    		setenv("mtddevnum", NULL);
    		setenv("mtddevname", NULL);
    
    
    		debug("=> mtddevnum NULL\n=> mtddevname NULL\n");
    
    	}
    }
    
    /**
     * Save current device and partition in environment variable 'partition'.
     */
    static void current_save(void)
    {
    	char buf[16];
    
    
    	debug("--- current_save ---\n");
    
    	if (current_mtd_dev) {
    		sprintf(buf, "%s%d,%d", MTD_DEV_TYPE(current_mtd_dev->id->type),
    					current_mtd_dev->id->num, current_mtd_partnum);
    
    
    		setenv("partition", buf);
    		strncpy(last_partition, buf, 16);
    
    
    		debug("=> partition %s\n", buf);
    
    	} else {
    		setenv("partition", NULL);
    		last_partition[0] = '\0';
    
    
    		debug("=> partition NULL\n");
    
    	}
    	index_partitions();
    }
    
    
    
    /**
     * Produce a mtd_info given a type and num.
     *
     * @param type mtd type
     * @param num mtd number
     * @param mtd a pointer to an mtd_info instance (output)
     * @return 0 if device is valid, 1 otherwise
     */
    static int get_mtd_info(u8 type, u8 num, struct mtd_info **mtd)
    {
    	char mtd_dev[16];
    
    	sprintf(mtd_dev, "%s%d", MTD_DEV_TYPE(type), num);
    	*mtd = get_mtd_device_nm(mtd_dev);
    	if (IS_ERR(*mtd)) {
    		printf("Device %s not found!\n", mtd_dev);
    		return 1;
    	}
    
    	return 0;
    }
    
    
     * Performs sanity check for supplied flash partition.
     * Table of existing MTD flash devices is searched and partition device
     * is located. Alignment with the granularity of nand erasesize is verified.
    
     *
     * @param id of the parent device
     * @param part partition to validate
     * @return 0 if partition is valid, 1 otherwise
     */
    
    static int part_validate_eraseblock(struct mtdids *id, struct part_info *part)
    
    	struct mtd_info *mtd = NULL;
    
    	if (get_mtd_info(id->type, id->num, &mtd))
    
    	part->sector_size = mtd->erasesize;
    
    	if (!mtd->numeraseregions) {
    		/*
    		 * Only one eraseregion (NAND, OneNAND or uniform NOR),
    		 * checking for alignment is easy here
    		 */
    		if ((unsigned long)part->offset % mtd->erasesize) {
    			printf("%s%d: partition (%s) start offset"
    			       "alignment incorrect\n",
    			       MTD_DEV_TYPE(id->type), id->num, part->name);
    			return 1;
    		}
    
    		if (part->size % mtd->erasesize) {
    			printf("%s%d: partition (%s) size alignment incorrect\n",
    			       MTD_DEV_TYPE(id->type), id->num, part->name);
    			return 1;
    		}
    	} else {
    		/*
    		 * Multiple eraseregions (non-uniform NOR),
    		 * checking for alignment is more complex here
    		 */
    
    		/* Check start alignment */
    		for (i = 0; i < mtd->numeraseregions; i++) {
    			start = mtd->eraseregions[i].offset;
    			for (j = 0; j < mtd->eraseregions[i].numblocks; j++) {
    				if (part->offset == start)
    					goto start_ok;
    				start += mtd->eraseregions[i].erasesize;
    			}
    		}
    
    
    		printf("%s%d: partition (%s) start offset alignment incorrect\n",
    
    		       MTD_DEV_TYPE(id->type), id->num, part->name);
    
    		/* Check end/size alignment */
    		for (i = 0; i < mtd->numeraseregions; i++) {
    			start = mtd->eraseregions[i].offset;
    			for (j = 0; j < mtd->eraseregions[i].numblocks; j++) {
    				if ((part->offset + part->size) == start)
    					goto end_ok;
    				start += mtd->eraseregions[i].erasesize;
    			}
    		}
    		/* Check last sector alignment */
    		if ((part->offset + part->size) == start)
    			goto end_ok;
    
    
    		printf("%s%d: partition (%s) size alignment incorrect\n",
    
    		       MTD_DEV_TYPE(id->type), id->num, part->name);
    
    	}
    
    	return 0;
    }
    
    
    /**
     * Performs sanity check for supplied partition. Offset and size are verified
     * to be within valid range. Partition type is checked and either
     * parts_validate_nor() or parts_validate_nand() is called with the argument
     * of part.
     *
     * @param id of the parent device
     * @param part partition to validate
     * @return 0 if partition is valid, 1 otherwise
     */
    static int part_validate(struct mtdids *id, struct part_info *part)
    {
    	if (part->size == SIZE_REMAINING)
    		part->size = id->size - part->offset;
    
    	if (part->offset > id->size) {
    		printf("%s: offset %08x beyond flash size %08x\n",
    				id->mtd_id, part->offset, id->size);
    		return 1;
    	}
    
    	if ((part->offset + part->size) <= part->offset) {
    		printf("%s%d: partition (%s) size too big\n",
    				MTD_DEV_TYPE(id->type), id->num, part->name);
    		return 1;
    	}
    
    	if (part->offset + part->size > id->size) {
    		printf("%s: partitioning exceeds flash size\n", id->mtd_id);
    		return 1;
    	}
    
    
    	/*
    	 * Now we need to check if the partition starts and ends on
    	 * sector (eraseblock) regions
    	 */
    	return part_validate_eraseblock(id, part);
    
    }
    
    /**
     * Delete selected partition from the partion list of the specified device.
     *
     * @param dev device to delete partition from
     * @param part partition to delete
     * @return 0 on success, 1 otherwise
     */
    static int part_del(struct mtd_device *dev, struct part_info *part)
    {
    	u8 current_save_needed = 0;
    
    	/* if there is only one partition, remove whole device */
    	if (dev->num_parts == 1)
    		return device_del(dev);
    
    	/* otherwise just delete this partition */
    
    
    		/* we are modyfing partitions for the current device,
    		 * update current */
    		struct part_info *curr_pi;
    
    		curr_pi = mtd_part_info(current_mtd_dev, current_mtd_partnum);
    
    
    		if (curr_pi) {
    			if (curr_pi == part) {
    				printf("current partition deleted, resetting current to 0\n");
    
    			} else if (part->offset <= curr_pi->offset) {
    
    			}
    			current_save_needed = 1;
    		}
    	}
    
    	list_del(&part->link);
    	free(part);
    	dev->num_parts--;
    
    	if (current_save_needed > 0)
    		current_save();
    	else
    		index_partitions();
    
    	return 0;
    }
    
    /**
     * Delete all partitions from parts head list, free memory.
     *
     * @param head list of partitions to delete
     */
    static void part_delall(struct list_head *head)
    {
    	struct list_head *entry, *n;
    	struct part_info *part_tmp;
    
    	/* clean tmp_list and free allocated memory */
    	list_for_each_safe(entry, n, head) {
    		part_tmp = list_entry(entry, struct part_info, link);
    
    		list_del(entry);
    		free(part_tmp);
    	}
    }
    
    /**
     * Add new partition to the supplied partition list. Make sure partitions are
     * sorted by offset in ascending order.
     *
     * @param head list this partition is to be added to
     * @param new partition to be added
     */
    static int part_sort_add(struct mtd_device *dev, struct part_info *part)
    {
    	struct list_head *entry;
    	struct part_info *new_pi, *curr_pi;
    
    	/* link partition to parrent dev */
    	part->dev = dev;
    
    	if (list_empty(&dev->parts)) {
    
    		debug("part_sort_add: list empty\n");
    
    		list_add(&part->link, &dev->parts);
    		dev->num_parts++;
    		index_partitions();
    		return 0;
    	}
    
    	new_pi = list_entry(&part->link, struct part_info, link);
    
    	/* get current partition info if we are updating current device */
    	curr_pi = NULL;
    
    	if (dev == current_mtd_dev)
    		curr_pi = mtd_part_info(current_mtd_dev, current_mtd_partnum);
    
    
    	list_for_each(entry, &dev->parts) {
    		struct part_info *pi;
    
    		pi = list_entry(entry, struct part_info, link);
    
    		/* be compliant with kernel cmdline, allow only one partition at offset zero */
    		if ((new_pi->offset == pi->offset) && (pi->offset == 0)) {
    			printf("cannot add second partition at offset 0\n");
    			return 1;
    		}
    
    		if (new_pi->offset <= pi->offset) {
    			list_add_tail(&part->link, entry);
    			dev->num_parts++;
    
    			if (curr_pi && (pi->offset <= curr_pi->offset)) {
    				/* we are modyfing partitions for the current
    				 * device, update current */
    
    				current_save();
    			} else {
    				index_partitions();
    			}
    			return 0;
    		}
    	}
    
    	list_add_tail(&part->link, &dev->parts);
    	dev->num_parts++;
    	index_partitions();
    	return 0;
    }
    
    /**
     * Add provided partition to the partition list of a given device.
     *
     * @param dev device to which partition is added
     * @param part partition to be added
     * @return 0 on success, 1 otherwise
     */
    static int part_add(struct mtd_device *dev, struct part_info *part)
    {
    	/* verify alignment and size */
    	if (part_validate(dev->id, part) != 0)
    		return 1;
    
    	/* partition is ok, add it to the list */
    	if (part_sort_add(dev, part) != 0)
    		return 1;
    
    	return 0;
    }
    
    /**
     * Parse one partition definition, allocate memory and return pointer to this
     * location in retpart.
     *
     * @param partdef pointer to the partition definition string i.e. <part-def>
     * @param ret output pointer to next char after parse completes (output)
     * @param retpart pointer to the allocated partition (output)
     * @return 0 on success, 1 otherwise
     */
    static int part_parse(const char *const partdef, const char **ret, struct part_info **retpart)
    {
    	struct part_info *part;
    	unsigned long size;
    	unsigned long offset;
    	const char *name;
    	int name_len;
    	unsigned int mask_flags;
    	const char *p;
    
    	p = partdef;
    	*retpart = NULL;
    	*ret = NULL;
    
    	/* fetch the partition size */
    	if (*p == '-') {
    		/* assign all remaining space to this partition */
    
    		debug("'-': remaining size assigned\n");
    
    		size = SIZE_REMAINING;
    		p++;
    	} else {
    		size = memsize_parse(p, &p);
    		if (size < MIN_PART_SIZE) {
    			printf("partition size too small (%lx)\n", size);
    			return 1;
    		}
    	}
    
    	/* check for offset */
    	offset = OFFSET_NOT_SPECIFIED;
    	if (*p == '@') {
    		p++;
    		offset = memsize_parse(p, &p);
    	}
    
    	/* now look for the name */
    	if (*p == '(') {
    		name = ++p;
    		if ((p = strchr(name, ')')) == NULL) {
    			printf("no closing ) found in partition name\n");
    			return 1;
    		}
    		name_len = p - name + 1;
    		if ((name_len - 1) == 0) {
    			printf("empty partition name\n");
    			return 1;
    		}
    		p++;
    	} else {
    		/* 0x00000000@0x00000000 */
    		name_len = 22;
    		name = NULL;
    	}
    
    	/* test for options */
    	mask_flags = 0;
    	if (strncmp(p, "ro", 2) == 0) {
    		mask_flags |= MTD_WRITEABLE_CMD;
    		p += 2;
    	}
    
    	/* check for next partition definition */
    	if (*p == ',') {
    		if (size == SIZE_REMAINING) {
    			*ret = NULL;
    			printf("no partitions allowed after a fill-up partition\n");
    			return 1;
    		}
    		*ret = ++p;
    	} else if ((*p == ';') || (*p == '\0')) {
    		*ret = p;
    	} else {
    		printf("unexpected character '%c' at the end of partition\n", *p);
    		*ret = NULL;
    		return 1;
    	}
    
    	/*  allocate memory */
    	part = (struct part_info *)malloc(sizeof(struct part_info) + name_len);
    	if (!part) {
    		printf("out of memory\n");
    		return 1;
    	}
    	memset(part, 0, sizeof(struct part_info) + name_len);
    	part->size = size;
    	part->offset = offset;
    	part->mask_flags = mask_flags;
    	part->name = (char *)(part + 1);
    
    	if (name) {
    		/* copy user provided name */
    		strncpy(part->name, name, name_len - 1);
    		part->auto_name = 0;
    	} else {
    		/* auto generated name in form of size@offset */
    		sprintf(part->name, "0x%08lx@0x%08lx", size, offset);
    		part->auto_name = 1;
    	}
    
    	part->name[name_len - 1] = '\0';
    	INIT_LIST_HEAD(&part->link);
    
    
    	debug("+ partition: name %-22s size 0x%08x offset 0x%08x mask flags %d\n",
    
    			part->name, part->size,
    			part->offset, part->mask_flags);
    
    	*retpart = part;
    	return 0;
    }
    
    /**
     * Check device number to be within valid range for given device type.
     *
    
     * @param type mtd type
     * @param num mtd number
     * @param size a pointer to the size of the mtd device (output)
    
     * @return 0 if device is valid, 1 otherwise
     */
    
    static int mtd_device_validate(u8 type, u8 num, u32 *size)
    
    	struct mtd_info *mtd = NULL;
    
    	if (get_mtd_info(type, num, &mtd))
    
    }
    
    /**
     * Delete all mtd devices from a supplied devices list, free memory allocated for
     * each device and delete all device partitions.
     *
     * @return 0 on success, 1 otherwise
     */
    static int device_delall(struct list_head *head)
    {
    	struct list_head *entry, *n;
    	struct mtd_device *dev_tmp;
    
    	/* clean devices list */
    	list_for_each_safe(entry, n, head) {
    		dev_tmp = list_entry(entry, struct mtd_device, link);
    		list_del(entry);
    		part_delall(&dev_tmp->parts);
    		free(dev_tmp);
    	}
    	INIT_LIST_HEAD(&devices);
    
    	return 0;
    }
    
    /**
     * If provided device exists it's partitions are deleted, device is removed
     * from device list and device memory is freed.
     *
     * @param dev device to be deleted
     * @return 0 on success, 1 otherwise
     */
    static int device_del(struct mtd_device *dev)
    {
    	part_delall(&dev->parts);
    	list_del(&dev->link);
    	free(dev);
    
    
    		/* we just deleted current device */
    		if (list_empty(&devices)) {
    
    		} else {
    			/* reset first partition from first dev from the
    			 * devices list as current */
    
    			current_mtd_dev = list_entry(devices.next, struct mtd_device, link);
    			current_mtd_partnum = 0;
    
    		}
    		current_save();
    		return 0;
    	}
    
    	index_partitions();
    	return 0;
    }
    
    /**
     * Search global device list and return pointer to the device of type and num
     * specified.
     *
     * @param type device type
     * @param num device number
     * @return NULL if requested device does not exist
     */
    
    struct mtd_device *device_find(u8 type, u8 num)
    
    {
    	struct list_head *entry;
    	struct mtd_device *dev_tmp;
    
    	list_for_each(entry, &devices) {
    		dev_tmp = list_entry(entry, struct mtd_device, link);
    
    		if ((dev_tmp->id->type == type) && (dev_tmp->id->num == num))
    			return dev_tmp;
    	}
    
    	return NULL;
    }
    
    /**
     * Add specified device to the global device list.
     *
     * @param dev device to be added
     */
    static void device_add(struct mtd_device *dev)
    {
    	u8 current_save_needed = 0;
    
    	if (list_empty(&devices)) {
    
    		current_mtd_dev = dev;
    		current_mtd_partnum = 0;
    
    		current_save_needed = 1;
    	}
    
    	list_add_tail(&dev->link, &devices);
    
    	if (current_save_needed > 0)
    		current_save();
    	else
    		index_partitions();
    }
    
    /**
     * Parse device type, name and mtd-id. If syntax is ok allocate memory and
     * return pointer to the device structure.
     *
     * @param mtd_dev pointer to the device definition string i.e. <mtd-dev>
     * @param ret output pointer to next char after parse completes (output)
     * @param retdev pointer to the allocated device (output)
     * @return 0 on success, 1 otherwise
     */
    static int device_parse(const char *const mtd_dev, const char **ret, struct mtd_device **retdev)
    {
    	struct mtd_device *dev;
    	struct part_info *part;
    	struct mtdids *id;
    	const char *mtd_id;
    	unsigned int mtd_id_len;
    
    	const char *p;
    	const char *pend;
    
    	LIST_HEAD(tmp_list);
    	struct list_head *entry, *n;
    	u16 num_parts;
    	u32 offset;
    	int err = 1;
    
    
    	debug("===device_parse===\n");
    
    	*retdev = NULL;
    
    
    	if (ret)
    		*ret = NULL;
    
    
    	/* fetch <mtd-id> */
    
    	mtd_id = p = mtd_dev;
    
    	if (!(p = strchr(mtd_id, ':'))) {
    		printf("no <mtd-id> identifier\n");
    		return 1;
    	}
    	mtd_id_len = p - mtd_id + 1;
    	p++;
    
    	/* verify if we have a valid device specified */
    	if ((id = id_find_by_mtd_id(mtd_id, mtd_id_len - 1)) == NULL) {
    		printf("invalid mtd device '%.*s'\n", mtd_id_len - 1, mtd_id);
    		return 1;
    	}
    
    
    #ifdef DEBUG
    	pend = strchr(p, ';');
    #endif
    
    	debug("dev type = %d (%s), dev num = %d, mtd-id = %s\n",
    
    			id->type, MTD_DEV_TYPE(id->type),
    			id->num, id->mtd_id);
    
    	debug("parsing partitions %.*s\n", (pend ? pend - p : strlen(p)), p);
    
    
    
    	/* parse partitions */
    	num_parts = 0;
    
    	offset = 0;
    	if ((dev = device_find(id->type, id->num)) != NULL) {
    		/* if device already exists start at the end of the last partition */
    		part = list_entry(dev->parts.prev, struct part_info, link);
    		offset = part->offset + part->size;
    	}
    
    	while (p && (*p != '\0') && (*p != ';')) {
    		err = 1;
    		if ((part_parse(p, &p, &part) != 0) || (!part))
    			break;
    
    		/* calculate offset when not specified */
    		if (part->offset == OFFSET_NOT_SPECIFIED)
    			part->offset = offset;
    		else
    			offset = part->offset;
    
    		/* verify alignment and size */
    		if (part_validate(id, part) != 0)
    			break;
    
    		offset += part->size;
    
    		/* partition is ok, add it to the list */
    		list_add_tail(&part->link, &tmp_list);
    		num_parts++;
    		err = 0;
    	}
    	if (err == 1) {
    		part_delall(&tmp_list);
    		return 1;
    	}
    
    	if (num_parts == 0) {
    		printf("no partitions for device %s%d (%s)\n",
    				MTD_DEV_TYPE(id->type), id->num, id->mtd_id);
    		return 1;
    	}
    
    
    	debug("\ntotal partitions: %d\n", num_parts);
    
    
    	/* check for next device presence */
    	if (p) {
    		if (*p == ';') {
    
    			if (ret)
    				*ret = ++p;
    
    		} else if (*p == '\0') {
    
    			if (ret)
    				*ret = p;
    
    		} else {
    			printf("unexpected character '%c' at the end of device\n", *p);
    
    			if (ret)
    				*ret = NULL;
    
    			return 1;
    		}
    	}
    
    	/* allocate memory for mtd_device structure */
    	if ((dev = (struct mtd_device *)malloc(sizeof(struct mtd_device))) == NULL) {
    		printf("out of memory\n");
    		return 1;
    	}
    	memset(dev, 0, sizeof(struct mtd_device));
    	dev->id = id;
    	dev->num_parts = 0; /* part_sort_add increments num_parts */
    	INIT_LIST_HEAD(&dev->parts);
    	INIT_LIST_HEAD(&dev->link);
    
    	/* move partitions from tmp_list to dev->parts */
    	list_for_each_safe(entry, n, &tmp_list) {
    		part = list_entry(entry, struct part_info, link);
    		list_del(entry);
    		if (part_sort_add(dev, part) != 0) {
    			device_del(dev);
    			return 1;
    		}
    	}
    
    	*retdev = dev;
    
    
    	return 0;
    }
    
    /**
     * Initialize global device list.
     *
     * @return 0 on success, 1 otherwise
     */
    static int mtd_devices_init(void)
    {
    	last_parts[0] = '\0';
    
    	current_save();
    
    	return device_delall(&devices);
    }
    
    /*
     * Search global mtdids list and find id of requested type and number.
     *
     * @return pointer to the id if it exists, NULL otherwise
     */
    static struct mtdids* id_find(u8 type, u8 num)
    {
    	struct list_head *entry;
    	struct mtdids *id;
    
    	list_for_each(entry, &mtdids) {
    		id = list_entry(entry, struct mtdids, link);
    
    		if ((id->type == type) && (id->num == num))
    			return id;
    	}
    
    	return NULL;