diff options
Diffstat (limited to 'ubi-utils/src/libpfiflash')
| -rw-r--r-- | ubi-utils/src/libpfiflash/pfiflash.c | 617 | 
1 files changed, 617 insertions, 0 deletions
| diff --git a/ubi-utils/src/libpfiflash/pfiflash.c b/ubi-utils/src/libpfiflash/pfiflash.c new file mode 100644 index 0000000..0859a22 --- /dev/null +++ b/ubi-utils/src/libpfiflash/pfiflash.c @@ -0,0 +1,617 @@ +/* + * Copyright (c) International Business Machines Corp., 2006 + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file pfiflash.c + * + * @author Oliver Lohmann <oliloh@de.ibm.com> + * + * @brief This library is provides an interface to the pfiflash utility. + * + * <oliloh@de.ibm.com> Wed Mar 15 11:39:19 CET 2006 Initial creation. + * + * @TODO Comare data before writing it. This implies that the volume + * parameters are compared first: size, alignment, name, type, ..., + * this is the same, compare the data. Volume deletion is deffered + * until the difference has been found out. + */ + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#define __USE_GNU +#include <string.h> + +#include <libubi.h> +#include <pfiflash.h> + +#include <mtd/ubi-user.h>	/* FIXME Is this ok here!!?? */ + +#include "ubimirror.h" +#include "error.h" +#include "reader.h" +#include "example_ubi.h" +#include "bootenv.h" + +static const char copyright [] __attribute__((unused)) = +	"Copyright (c) International Business Machines Corp., 2006"; + +#define EBUF(fmt...) do {			\ +		snprintf(err_buf, err_buf_size, fmt);	\ +	} while (0) + +static pdd_func_t pdd_funcs[PDD_HANDLING_NUM]  = +	{ +		&bootenv_pdd_keep, +		&bootenv_pdd_merge, +		&bootenv_pdd_overwrite +	}; +/**< An array of PDD function pointers indexed by the algorithm. */ + + +typedef enum ubi_update_process_t { +	UBI_REMOVE = 0, +	UBI_WRITE, +} ubi_update_process_t; + +static int +skip_raw_sections(FILE* pfi, list_t pfi_raws, +		  char* err_buf, size_t err_buf_size) +{ +	int rc = 0; + +	void *i; +	list_t ptr; +	size_t j, skip_size; + +	if (is_empty(pfi_raws)) +		return 0; + +	foreach(i, ptr, pfi_raws) { +		skip_size = ((pfi_raw_t)i)->data_size; +		for(j = 0;  j < skip_size; j++) { +			fgetc(pfi); +			if (ferror(pfi)) { +				EBUF("Cannot skip raw section in PFI."); +				rc = -EIO; +				goto err; +			} +		} +	} + err: +	return rc; +} + +/** + * @brief Wraps the ubi_mkvol functions and implements a hook for the bootenv + *	  update. + * @param devno UBI device number. + * @param s Current seqnum. + * @param u Information about the UBI volume from the PFI. + * @param err_buf An error buffer. + * @param err_buf_size The size of the error buffer. + * @return 0 On Sucess. + * @return else Error. + */ +static int +my_ubi_mkvol(int devno, int s, pfi_ubi_t u, char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	int type; +	ubi_lib_t ulib = NULL; + +	log_msg("%s(vol_id=%d, size=%d, data_size=%d, type=%d, " +		"alig=%d, nlen=%d, name=%s)", __func__, +		u->ids[s], u->size, u->data_size, u->type, u->alignment, +		strnlen(u->names[s], PFI_UBI_VOL_NAME_LEN), u->names[s]); + +	rc = ubi_open(&ulib); +	if (rc != 0) { +		goto err; +	} + +	switch (u->type) { +	case pfi_ubi_static: +		type = UBI_STATIC_VOLUME; break; +	case pfi_ubi_dynamic: +		type = UBI_DYNAMIC_VOLUME; break; +	default: +		type = UBI_DYNAMIC_VOLUME; +	} + +	rc = ubi_mkvol(ulib, devno, u->ids[s], type, u->size, u->alignment, +		       u->names[s]); +	if (rc != 0) { +		EBUF("Cannot create volume: %d", u->ids[s]); +		goto err; +	} + + err: +	if (ulib != NULL) +		ubi_close(&ulib); +	return rc; +} + +/** + * @brief A wrapper around the UBI library function ubi_rmvol. + * @param devno UBI device number. + * @param s Current seqnum. + * @param u Information about the UBI volume from the PFI. + * @param err_buf An error buffer. + * @param err_buf_size The size of the error buffer. + * + * If the volume does not exist, the function will return success. + */ +static int +my_ubi_rmvol(int devno, uint32_t id, char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	ubi_lib_t ulib = NULL; +	int fd; + +	log_msg("%s(id=%d)", __func__, id); + +	rc = ubi_open(&ulib); +	if (rc != 0) +		goto err; + +	/** +	 * Truncate if it exist or not. +	 */ +	fd = ubi_vol_open(ulib, devno, id, O_RDWR); +	if (fd == -1) +		return 0;	/* not existent, return */ + +	rc = ubi_vol_update(fd, 0); +	if (rc < 0) { +		fprintf(stderr, "update failed rc=%d errno=%d\n", rc, errno); +		ubi_vol_close(fd); +		goto err;	/* if EBUSY than empty device, continue */ +	} +	ubi_vol_close(fd); + +	rc = ubi_rmvol(ulib, devno, id); +	if (rc != 0) { +		/* @TODO Define a ubi_rmvol return value which says +		 * sth like EUBI_NOSUCHDEV. In this case, a failed +		 * operation is acceptable. Everything else has to be +		 * classified as real error. But talk to Andreas Arnez +		 * before defining something odd... +		 */ +		/* if ((errno == EINVAL) || (errno == ENODEV)) +		   return 0; */ /* currently it is EINVAL or ENODEV */ + +		dbg_msg("Remove UBI volume %d returned with error: %d " +			"errno=%d", id, rc, errno); +		goto err; +	} + err: +	if (ulib != NULL) +		ubi_close(&ulib); +	return rc; +} + +static int +read_bootenv_volume(int devno, uint32_t id, bootenv_t bootenv_old, +		    char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	ubi_lib_t ulib = NULL; +	FILE* fp_in = NULL; + +	rc = ubi_open(&ulib); +	if (rc) +		return rc; + +	fp_in = ubi_vol_fopen_read(ulib, devno, id); +	if (!fp_in) { +		EBUF("Cannot open bootenv volume"); +		rc = -EIO; +		goto err; +	} + +	log_msg("%s reading old bootenvs", __func__); + +	/* Save old bootenvs for reference */ +	rc = bootenv_read(fp_in, bootenv_old, BOOTENV_MAXSIZE); +	if (rc) +		EBUF("Cannot read bootenv_old"); + err: +	if (fp_in) +		fclose(fp_in); +	if (ulib) +		ubi_close(&ulib); +	return rc; +} + +static int +write_bootenv_volume(int devno, uint32_t id, bootenv_t bootenv_old, +		     pdd_func_t pdd_f, +		     FILE* fp_in, /* new pdd data contained in pfi */ +		     size_t fp_in_size,	/* data size of new pdd data in pfi */ +		     char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	int warnings = 0; +	ubi_lib_t ulib = NULL; +	bootenv_t bootenv_new = NULL; +	bootenv_t bootenv_res = NULL; +	size_t update_size = 0; +	FILE *fp_out = NULL; + +	log_msg("%s(id=%d, fp_in=%p)", __func__, id, fp_in); + +	/* Workflow: +	 * 1. Apply PDD operation and get the size of the returning +	 *    bootenv_res section. Without the correct size it wouldn't +	 *    be possible to call UBI update vol. +	 * 2. Call UBI update vol +	 * 3. Get FILE* to vol dev +	 * 4. Write to FILE* +	 */ + +	rc = ubi_open(&ulib); +	if (rc != 0) { +		goto err; +	} + +	rc = bootenv_create(&bootenv_new); +	if (rc != 0) +		goto err; +	rc = bootenv_create(&bootenv_res); +	if (rc != 0) +		goto err; + +	rc = bootenv_read(fp_in, bootenv_new, fp_in_size); +	if (rc != 0) +		goto err; + +	rc = pdd_f(bootenv_old, bootenv_new, &bootenv_res, &warnings, +		   err_buf, err_buf_size); +	if (rc != 0) +		goto err; +	if (warnings) { +		/* @TODO Do sth with the warning */ +		dbg_msg("A warning in the PDD operation occured: %d", +			warnings); +	} +	log_msg("... (2)"); + +	rc = bootenv_size(bootenv_res, &update_size); +	if (rc != 0) +		goto err; + +	fp_out = ubi_vol_fopen_update(ulib, devno, id, update_size); +	if (fp_out == NULL) +		goto err; + +	rc = bootenv_write(fp_out, bootenv_res); +	if (rc != 0) { +		EBUF("Write operation on ubi%d_%d failed.", devno, id); +		rc = -EIO; +		goto err; +	} + + err: +	if (ulib != NULL) +		ubi_close(&ulib); +	if (bootenv_new != NULL) +		bootenv_destroy(&bootenv_new); +	if (bootenv_res != NULL) +		bootenv_destroy(&bootenv_res); +	if (fp_out) +		fclose(fp_out); +	return rc; +} + +static int +write_normal_volume(int devno, uint32_t id, size_t update_size, FILE* fp_in, +		    char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	ubi_lib_t ulib = NULL; +	FILE* fp_out = NULL; +	int c; +	size_t i; + +	log_msg("%s(id=%d, update_size=%d fp_in=%p)", +		__func__, id, update_size, fp_in); + +	rc = ubi_open(&ulib); +	if (rc) +		return rc; + +	fp_out = ubi_vol_fopen_update(ulib, devno, id, update_size); +	if (fp_out == NULL) { +		rc = -1; +		goto err; +	} + +	log_msg("starting the update ... "); /* FIXME DBG */ +	for (i = 0; i < update_size; i++) { +		c = getc(fp_in); +		if (c == EOF && ferror(fp_in)) { +			rc = -EIO; +			goto err; +		} +		if (putc(c, fp_out) == EOF) { +			rc = -EIO; +			goto err; +		} +		/*  FIXME DBG */ +		/* if ((i & 0xFFF) == 0xFFF) log_msg("."); */ +	} +	/* log_msg("\n"); */		/*  FIXME DBG */ + err: +	if (fp_out) +		fclose(fp_out); +	if (ulib) +		ubi_close(&ulib); +	return rc; +} + + +/** + * @brief ... + * @precondition	The PFI file contains at least one ubi_id entry. + *			This is assured by the PFI read process. + * @postcondition	The used seqnum number is set in the UBI PFI + *			header list. + *			The UBI volumes specified by seqnum are processed. + */ +static int +process_ubi_volumes(FILE* pfi, int seqnum, list_t pfi_ubis, +		    bootenv_t bootenv_old, pdd_func_t pdd_f, +		    ubi_update_process_t ubi_update_process, +		    char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	pfi_ubi_t u; +	list_t ptr; + +	foreach(u, ptr, pfi_ubis) { +		int s = seqnum; +		if (seqnum > (u->ids_size - 1)) { +			s = 0; /* per default use the first */ +		} +		u->curr_seqnum = s; + +		switch (ubi_update_process) { +		case UBI_REMOVE: +			if ((u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_1) || +			    (u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_2)) { +				rc =read_bootenv_volume(EXAMPLE_UBI_DEVICE, +							u->ids[s], +							bootenv_old, err_buf, +							err_buf_size); +				if (rc != 0) +					goto err; +				} +			rc = my_ubi_rmvol(EXAMPLE_UBI_DEVICE,  u->ids[s], +					  err_buf, err_buf_size); +			if (rc != 0) +				goto err; +			break; +		case UBI_WRITE: +			rc = my_ubi_mkvol(EXAMPLE_UBI_DEVICE, s, u, +					  err_buf, err_buf_size); +			if (rc != 0) +				goto err; +			if ((u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_1) || +			    (u->ids[s] == EXAMPLE_BOOTENV_VOL_ID_2)) { +				rc = write_bootenv_volume(EXAMPLE_UBI_DEVICE, +							  u->ids[s], +							  bootenv_old, pdd_f, +							  pfi, +							  u->data_size, +							  err_buf, +							  err_buf_size); +			} +			else { +				rc = write_normal_volume(EXAMPLE_UBI_DEVICE, +							 u->ids[s], +							 u->data_size, pfi, +							 err_buf, +							 err_buf_size); +			} +			if (rc != 0) +				goto err; +			break; +		default: +			EBUF("Invoked unknown UBI operation."); +			rc = -1; +			goto err; + +		} +		if (rc != 0) { +			goto err; +		} +	} + err: +	return rc; + +} + +static int +erase_unmapped_ubi_volumes(int devno, list_t pfi_ubis, +			   char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	list_t ptr; +	pfi_ubi_t u; +	size_t i; +	uint8_t ubi_volumes[PFI_UBI_MAX_VOLUMES]; + +	for (i = 0; i < PFI_UBI_MAX_VOLUMES; i++) { +		ubi_volumes[i] = 1; +	} + +	foreach(u, ptr, pfi_ubis) { +		/* iterate over each vol_id */ +		for(i = 0; i < u->ids_size; i++) { +			if (u->ids[i] > PFI_UBI_MAX_VOLUMES) { +				EBUF("PFI file contains an invalid " +				     "volume id: %d", u->ids[i]); +				goto err; +			} +			/* remove from removal list */ +			ubi_volumes[u->ids[i]] = 0; +		} +	} + +	for (i = 0; i < PFI_UBI_MAX_VOLUMES; i++) { +		if (ubi_volumes[i]) { +			rc = my_ubi_rmvol(devno, i, err_buf, err_buf_size); +			if (rc != 0) +				goto err; +		} +	} + err: +	return rc; +} + +static int +mirror_ubi_volumes(uint32_t devno, list_t pfi_ubis, +		   char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	list_t ptr; +	uint32_t j; +	pfi_ubi_t i; +	ubi_lib_t  ulib = NULL; + +	log_msg("%s(...)", __func__); + +	rc = ubi_open(&ulib); +	if (rc != 0) +		goto err; + +	/** +	 * Execute all mirror operations on redundant groups. +	 * Create a volume within a redundant group if it does +	 * not exist already (this is a precondition of +	 * ubimirror). +	 */ +	foreach(i, ptr, pfi_ubis) { +		for(j = 0; j < i->ids_size; j++) { +			/* skip self-match */ +			if (i->ids[j] == i->ids[i->curr_seqnum]) +				continue; + +			rc = my_ubi_rmvol(devno, i->ids[j], err_buf, +					  err_buf_size); +			if (rc != 0) +				goto err; + +			rc = my_ubi_mkvol(devno, j, i, err_buf, err_buf_size); +			if (rc != 0) +				goto err; +		} +	} + +	foreach(i, ptr, pfi_ubis) { +		rc = ubimirror(devno, i->curr_seqnum, i->ids, +			       i->ids_size, err_buf, err_buf_size); +		if (rc != 0) +			goto err; +	} + + + err: +	if (ulib != NULL) +		ubi_close(&ulib); +	return rc; +} + +int +pfiflash(FILE* pfi, int complete, int seqnum, pdd_handling_t pdd_handling, +	 char *err_buf, size_t err_buf_size) +{ +	int rc = 0; +	pdd_func_t pdd_f = NULL; + +	if (pfi == NULL) +		return -EINVAL; + +	/** +	 * If the user didnt specify a seqnum we start per default +	 * with the index 0 +	 */ +	int curr_seqnum = seqnum < 0 ? 0 : seqnum; + +	list_t pfi_raws   = mk_empty(); /* list of raw sections from a pfi */ +	list_t pfi_ubis   = mk_empty(); /* list of ubi sections from a pfi */ + +	bootenv_t bootenv; +	rc = bootenv_create(&bootenv); +	if (rc != 0) { +		EBUF("Cannot create bootenv variable"); +	} + +	rc = read_pfi_headers(&pfi_raws, &pfi_ubis, pfi, +			      err_buf, err_buf_size); +	if (rc != 0) { +		EBUF("Cannot read PFI headers."); +		goto err; +	} + +	/* @TODO: If you want to implement an IPL update - start here. */ +	rc = skip_raw_sections(pfi, pfi_raws, err_buf, err_buf_size); +	if (rc != 0) { +		goto err; +	} + +	if (complete) { +		rc = erase_unmapped_ubi_volumes(EXAMPLE_UBI_DEVICE, pfi_ubis, +						err_buf, err_buf_size); +		if (rc != 0) { +			EBUF("Cannot delete unmapped UBI volumes."); +			goto err; +		} +	} + +	if ((pdd_handling >= 0) && (pdd_handling < PDD_HANDLING_NUM)) { +		pdd_f = pdd_funcs[pdd_handling]; +	} +	else { +		EBUF("Used unknown PDD handling algorithm (pdd_handling)"); +	} + +	rc = process_ubi_volumes(pfi, curr_seqnum, pfi_ubis, bootenv, pdd_f, +				 UBI_REMOVE, err_buf, err_buf_size); +	if  (rc != 0) { +		goto err; +	} +	rc = process_ubi_volumes(pfi, curr_seqnum, pfi_ubis, bootenv, pdd_f, +				 UBI_WRITE, err_buf, err_buf_size); +	if  (rc != 0) { +		goto err; +	} +	if (seqnum < 0) { /* mirror redundant pairs */ +		rc = mirror_ubi_volumes(EXAMPLE_UBI_DEVICE, pfi_ubis, +					err_buf, err_buf_size); +		if (rc != 0) +			goto err; +	} + + err: +	pfi_raws = remove_all((free_func_t)&free_pfi_raw, pfi_raws); +	pfi_ubis = remove_all((free_func_t)&free_pfi_ubi, pfi_ubis); +	bootenv_destroy(&bootenv); +	return rc; +} | 
