diff options
Diffstat (limited to 'arch/powerpc/kernel/nvram_64.c')
| -rw-r--r-- | arch/powerpc/kernel/nvram_64.c | 537 | 
1 files changed, 206 insertions, 331 deletions
diff --git a/arch/powerpc/kernel/nvram_64.c b/arch/powerpc/kernel/nvram_64.c index 9cf197f01e9..28b898e6818 100644 --- a/arch/powerpc/kernel/nvram_64.c +++ b/arch/powerpc/kernel/nvram_64.c @@ -34,15 +34,26 @@  #undef DEBUG_NVRAM -static struct nvram_partition * nvram_part; -static long nvram_error_log_index = -1; -static long nvram_error_log_size = 0; +#define NVRAM_HEADER_LEN	sizeof(struct nvram_header) +#define NVRAM_BLOCK_LEN		NVRAM_HEADER_LEN + +/* If change this size, then change the size of NVNAME_LEN */ +struct nvram_header { +	unsigned char signature; +	unsigned char checksum; +	unsigned short length; +	/* Terminating null required only for names < 12 chars. */ +	char name[12]; +}; -struct err_log_info { -	int error_type; -	unsigned int seq_num; +struct nvram_partition { +	struct list_head partition; +	struct nvram_header header; +	unsigned int index;  }; +static LIST_HEAD(nvram_partitions); +  static loff_t dev_nvram_llseek(struct file *file, loff_t offset, int origin)  {  	int size; @@ -73,22 +84,30 @@ static ssize_t dev_nvram_read(struct file *file, char __user *buf,  	char *tmp = NULL;  	ssize_t size; -	ret = -ENODEV; -	if (!ppc_md.nvram_size) +	if (!ppc_md.nvram_size) { +		ret = -ENODEV;  		goto out; +	} -	ret = 0;  	size = ppc_md.nvram_size(); -	if (*ppos >= size || size < 0) +	if (size < 0) { +		ret = size;  		goto out; +	} + +	if (*ppos >= size) { +		ret = 0; +		goto out; +	}  	count = min_t(size_t, count, size - *ppos);  	count = min(count, PAGE_SIZE); -	ret = -ENOMEM;  	tmp = kmalloc(count, GFP_KERNEL); -	if (!tmp) +	if (!tmp) { +		ret = -ENOMEM;  		goto out; +	}  	ret = ppc_md.nvram_read(tmp, count, ppos);  	if (ret <= 0) @@ -186,14 +205,12 @@ static struct miscdevice nvram_dev = {  #ifdef DEBUG_NVRAM  static void __init nvram_print_partitions(char * label)  { -	struct list_head * p;  	struct nvram_partition * tmp_part;  	printk(KERN_WARNING "--------%s---------\n", label);  	printk(KERN_WARNING "indx\t\tsig\tchks\tlen\tname\n"); -	list_for_each(p, &nvram_part->partition) { -		tmp_part = list_entry(p, struct nvram_partition, partition); -		printk(KERN_WARNING "%4d    \t%02x\t%02x\t%d\t%s\n", +	list_for_each_entry(tmp_part, &nvram_partitions, partition) { +		printk(KERN_WARNING "%4d    \t%02x\t%02x\t%d\t%12.12s\n",  		       tmp_part->index, tmp_part->header.signature,  		       tmp_part->header.checksum, tmp_part->header.length,  		       tmp_part->header.name); @@ -206,9 +223,13 @@ static int __init nvram_write_header(struct nvram_partition * part)  {  	loff_t tmp_index;  	int rc; -	 +	struct nvram_header phead; + +	memcpy(&phead, &part->header, NVRAM_HEADER_LEN); +	phead.length = cpu_to_be16(phead.length); +  	tmp_index = part->index; -	rc = ppc_md.nvram_write((char *)&part->header, NVRAM_HEADER_LEN, &tmp_index);  +	rc = ppc_md.nvram_write((char *)&phead, NVRAM_HEADER_LEN, &tmp_index);  	return rc;  } @@ -228,95 +249,136 @@ static unsigned char __init nvram_checksum(struct nvram_header *p)  	return c_sum;  } -static int __init nvram_remove_os_partition(void) +/* + * Per the criteria passed via nvram_remove_partition(), should this + * partition be removed?  1=remove, 0=keep + */ +static int nvram_can_remove_partition(struct nvram_partition *part, +		const char *name, int sig, const char *exceptions[]) +{ +	if (part->header.signature != sig) +		return 0; +	if (name) { +		if (strncmp(name, part->header.name, 12)) +			return 0; +	} else if (exceptions) { +		const char **except; +		for (except = exceptions; *except; except++) { +			if (!strncmp(*except, part->header.name, 12)) +				return 0; +		} +	} +	return 1; +} + +/** + * nvram_remove_partition - Remove one or more partitions in nvram + * @name: name of the partition to remove, or NULL for a + *        signature only match + * @sig: signature of the partition(s) to remove + * @exceptions: When removing all partitions with a matching signature, + *        leave these alone. + */ + +int __init nvram_remove_partition(const char *name, int sig, +						const char *exceptions[])  { -	struct list_head *i; -	struct list_head *j; -	struct nvram_partition * part; -	struct nvram_partition * cur_part; +	struct nvram_partition *part, *prev, *tmp;  	int rc; -	list_for_each(i, &nvram_part->partition) { -		part = list_entry(i, struct nvram_partition, partition); -		if (part->header.signature != NVRAM_SIG_OS) +	list_for_each_entry(part, &nvram_partitions, partition) { +		if (!nvram_can_remove_partition(part, name, sig, exceptions))  			continue; -		 -		/* Make os partition a free partition */ + +		/* Make partition a free partition */  		part->header.signature = NVRAM_SIG_FREE; -		sprintf(part->header.name, "wwwwwwwwwwww"); +		strncpy(part->header.name, "wwwwwwwwwwww", 12);  		part->header.checksum = nvram_checksum(&part->header); - -		/* Merge contiguous free partitions backwards */ -		list_for_each_prev(j, &part->partition) { -			cur_part = list_entry(j, struct nvram_partition, partition); -			if (cur_part == nvram_part || cur_part->header.signature != NVRAM_SIG_FREE) { -				break; -			} -			 -			part->header.length += cur_part->header.length; -			part->header.checksum = nvram_checksum(&part->header); -			part->index = cur_part->index; - -			list_del(&cur_part->partition); -			kfree(cur_part); -			j = &part->partition; /* fixup our loop */ -		} -		 -		/* Merge contiguous free partitions forwards */ -		list_for_each(j, &part->partition) { -			cur_part = list_entry(j, struct nvram_partition, partition); -			if (cur_part == nvram_part || cur_part->header.signature != NVRAM_SIG_FREE) { -				break; -			} - -			part->header.length += cur_part->header.length; -			part->header.checksum = nvram_checksum(&part->header); - -			list_del(&cur_part->partition); -			kfree(cur_part); -			j = &part->partition; /* fixup our loop */ -		} -		  		rc = nvram_write_header(part);  		if (rc <= 0) { -			printk(KERN_ERR "nvram_remove_os_partition: nvram_write failed (%d)\n", rc); +			printk(KERN_ERR "nvram_remove_partition: nvram_write failed (%d)\n", rc);  			return rc;  		} +	} +	/* Merge contiguous ones */ +	prev = NULL; +	list_for_each_entry_safe(part, tmp, &nvram_partitions, partition) { +		if (part->header.signature != NVRAM_SIG_FREE) { +			prev = NULL; +			continue; +		} +		if (prev) { +			prev->header.length += part->header.length; +			prev->header.checksum = nvram_checksum(&part->header); +			rc = nvram_write_header(part); +			if (rc <= 0) { +				printk(KERN_ERR "nvram_remove_partition: nvram_write failed (%d)\n", rc); +				return rc; +			} +			list_del(&part->partition); +			kfree(part); +		} else +			prev = part;  	}  	return 0;  } -/* nvram_create_os_partition +/** + * nvram_create_partition - Create a partition in nvram + * @name: name of the partition to create + * @sig: signature of the partition to create + * @req_size: size of data to allocate in bytes + * @min_size: minimum acceptable size (0 means req_size)   * - * Create a OS linux partition to buffer error logs. - * Will create a partition starting at the first free - * space found if space has enough room. + * Returns a negative error code or a positive nvram index + * of the beginning of the data area of the newly created + * partition. If you provided a min_size smaller than req_size + * you need to query for the actual size yourself after the + * call using nvram_partition_get_size().   */ -static int __init nvram_create_os_partition(void) +loff_t __init nvram_create_partition(const char *name, int sig, +				     int req_size, int min_size)  {  	struct nvram_partition *part;  	struct nvram_partition *new_part;  	struct nvram_partition *free_part = NULL; -	int seq_init[2] = { 0, 0 }; +	static char nv_init_vals[16];  	loff_t tmp_index;  	long size = 0;  	int rc; -	 + +	/* Convert sizes from bytes to blocks */ +	req_size = _ALIGN_UP(req_size, NVRAM_BLOCK_LEN) / NVRAM_BLOCK_LEN; +	min_size = _ALIGN_UP(min_size, NVRAM_BLOCK_LEN) / NVRAM_BLOCK_LEN; + +	/* If no minimum size specified, make it the same as the +	 * requested size +	 */ +	if (min_size == 0) +		min_size = req_size; +	if (min_size > req_size) +		return -EINVAL; + +	/* Now add one block to each for the header */ +	req_size += 1; +	min_size += 1; +  	/* Find a free partition that will give us the maximum needed size   	   If can't find one that will give us the minimum size needed */ -	list_for_each_entry(part, &nvram_part->partition, partition) { +	list_for_each_entry(part, &nvram_partitions, partition) {  		if (part->header.signature != NVRAM_SIG_FREE)  			continue; -		if (part->header.length >= NVRAM_MAX_REQ) { -			size = NVRAM_MAX_REQ; +		if (part->header.length >= req_size) { +			size = req_size;  			free_part = part;  			break;  		} -		if (!size && part->header.length >= NVRAM_MIN_REQ) { -			size = NVRAM_MIN_REQ; +		if (part->header.length > size && +		    part->header.length >= min_size) { +			size = part->header.length;  			free_part = part;  		}  	} @@ -326,136 +388,95 @@ static int __init nvram_create_os_partition(void)  	/* Create our OS partition */  	new_part = kmalloc(sizeof(*new_part), GFP_KERNEL);  	if (!new_part) { -		printk(KERN_ERR "nvram_create_os_partition: kmalloc failed\n"); +		pr_err("nvram_create_os_partition: kmalloc failed\n");  		return -ENOMEM;  	}  	new_part->index = free_part->index; -	new_part->header.signature = NVRAM_SIG_OS; +	new_part->header.signature = sig;  	new_part->header.length = size; -	strcpy(new_part->header.name, "ppc64,linux"); +	strncpy(new_part->header.name, name, 12);  	new_part->header.checksum = nvram_checksum(&new_part->header);  	rc = nvram_write_header(new_part);  	if (rc <= 0) { -		printk(KERN_ERR "nvram_create_os_partition: nvram_write_header " -				"failed (%d)\n", rc); -		return rc; -	} - -	/* make sure and initialize to zero the sequence number and the error -	   type logged */ -	tmp_index = new_part->index + NVRAM_HEADER_LEN; -	rc = ppc_md.nvram_write((char *)&seq_init, sizeof(seq_init), &tmp_index); -	if (rc <= 0) { -		printk(KERN_ERR "nvram_create_os_partition: nvram_write " +		pr_err("nvram_create_os_partition: nvram_write_header "  		       "failed (%d)\n", rc);  		return rc;  	} -	 -	nvram_error_log_index = new_part->index + NVRAM_HEADER_LEN; -	nvram_error_log_size = ((part->header.length - 1) * -				NVRAM_BLOCK_LEN) - sizeof(struct err_log_info); -	  	list_add_tail(&new_part->partition, &free_part->partition); -	if (free_part->header.length <= size) { +	/* Adjust or remove the partition we stole the space from */ +	if (free_part->header.length > size) { +		free_part->index += size * NVRAM_BLOCK_LEN; +		free_part->header.length -= size; +		free_part->header.checksum = nvram_checksum(&free_part->header); +		rc = nvram_write_header(free_part); +		if (rc <= 0) { +			pr_err("nvram_create_os_partition: nvram_write_header " +			       "failed (%d)\n", rc); +			return rc; +		} +	} else {  		list_del(&free_part->partition);  		kfree(free_part); -		return 0;  	}  -	/* Adjust the partition we stole the space from */ -	free_part->index += size * NVRAM_BLOCK_LEN; -	free_part->header.length -= size; -	free_part->header.checksum = nvram_checksum(&free_part->header); -	 -	rc = nvram_write_header(free_part); -	if (rc <= 0) { -		printk(KERN_ERR "nvram_create_os_partition: nvram_write_header " -		       "failed (%d)\n", rc); -		return rc; +	/* Clear the new partition */ +	for (tmp_index = new_part->index + NVRAM_HEADER_LEN; +	     tmp_index <  ((size - 1) * NVRAM_BLOCK_LEN); +	     tmp_index += NVRAM_BLOCK_LEN) { +		rc = ppc_md.nvram_write(nv_init_vals, NVRAM_BLOCK_LEN, &tmp_index); +		if (rc <= 0) { +			pr_err("nvram_create_partition: nvram_write failed (%d)\n", rc); +			return rc; +		}  	} - -	return 0; +	 +	return new_part->index + NVRAM_HEADER_LEN;  } - -/* nvram_setup_partition - * - * This will setup the partition we need for buffering the - * error logs and cleanup partitions if needed. - * - * The general strategy is the following: - * 1.) If there is ppc64,linux partition large enough then use it. - * 2.) If there is not a ppc64,linux partition large enough, search - * for a free partition that is large enough. - * 3.) If there is not a free partition large enough remove  - * _all_ OS partitions and consolidate the space. - * 4.) Will first try getting a chunk that will satisfy the maximum - * error log size (NVRAM_MAX_REQ). - * 5.) If the max chunk cannot be allocated then try finding a chunk - * that will satisfy the minum needed (NVRAM_MIN_REQ). +/** + * nvram_get_partition_size - Get the data size of an nvram partition + * @data_index: This is the offset of the start of the data of + *              the partition. The same value that is returned by + *              nvram_create_partition().   */ -static int __init nvram_setup_partition(void) +int nvram_get_partition_size(loff_t data_index)  { -	struct list_head * p; -	struct nvram_partition * part; -	int rc; - -	/* For now, we don't do any of this on pmac, until I -	 * have figured out if it's worth killing some unused stuffs -	 * in our nvram, as Apple defined partitions use pretty much -	 * all of the space -	 */ -	if (machine_is(powermac)) -		return -ENOSPC; - -	/* see if we have an OS partition that meets our needs. -	   will try getting the max we need.  If not we'll delete -	   partitions and try again. */ -	list_for_each(p, &nvram_part->partition) { -		part = list_entry(p, struct nvram_partition, partition); -		if (part->header.signature != NVRAM_SIG_OS) -			continue; +	struct nvram_partition *part; +	 +	list_for_each_entry(part, &nvram_partitions, partition) { +		if (part->index + NVRAM_HEADER_LEN == data_index) +			return (part->header.length - 1) * NVRAM_BLOCK_LEN; +	} +	return -1; +} -		if (strcmp(part->header.name, "ppc64,linux")) -			continue; -		if (part->header.length >= NVRAM_MIN_REQ) { -			/* found our partition */ -			nvram_error_log_index = part->index + NVRAM_HEADER_LEN; -			nvram_error_log_size = ((part->header.length - 1) * -						NVRAM_BLOCK_LEN) - sizeof(struct err_log_info); -			return 0; +/** + * nvram_find_partition - Find an nvram partition by signature and name + * @name: Name of the partition or NULL for any name + * @sig: Signature to test against + * @out_size: if non-NULL, returns the size of the data part of the partition + */ +loff_t nvram_find_partition(const char *name, int sig, int *out_size) +{ +	struct nvram_partition *p; + +	list_for_each_entry(p, &nvram_partitions, partition) { +		if (p->header.signature == sig && +		    (!name || !strncmp(p->header.name, name, 12))) { +			if (out_size) +				*out_size = (p->header.length - 1) * +					NVRAM_BLOCK_LEN; +			return p->index + NVRAM_HEADER_LEN;  		}  	} -	 -	/* try creating a partition with the free space we have */ -	rc = nvram_create_os_partition(); -	if (!rc) { -		return 0; -	} -		 -	/* need to free up some space */ -	rc = nvram_remove_os_partition(); -	if (rc) { -		return rc; -	} -	 -	/* create a partition in this new space */ -	rc = nvram_create_os_partition(); -	if (rc) { -		printk(KERN_ERR "nvram_create_os_partition: Could not find a " -		       "NVRAM partition large enough\n"); -		return rc; -	} -	  	return 0;  } - -static int __init nvram_scan_partitions(void) +int __init nvram_scan_partitions(void)  {  	loff_t cur_index = 0;  	struct nvram_header phead; @@ -465,7 +486,7 @@ static int __init nvram_scan_partitions(void)  	int total_size;  	int err; -	if (ppc_md.nvram_size == NULL) +	if (ppc_md.nvram_size == NULL || ppc_md.nvram_size() <= 0)  		return -ENODEV;  	total_size = ppc_md.nvram_size(); @@ -488,6 +509,8 @@ static int __init nvram_scan_partitions(void)  		memcpy(&phead, header, NVRAM_HEADER_LEN); +		phead.length = be16_to_cpu(phead.length); +  		err = 0;  		c_sum = nvram_checksum(&phead);  		if (c_sum != phead.checksum) { @@ -502,8 +525,7 @@ static int __init nvram_scan_partitions(void)  			       "detected: 0-length partition\n");  			goto out;  		} -		tmp_part = (struct nvram_partition *) -			kmalloc(sizeof(struct nvram_partition), GFP_KERNEL); +		tmp_part = kmalloc(sizeof(struct nvram_partition), GFP_KERNEL);  		err = -ENOMEM;  		if (!tmp_part) {  			printk(KERN_ERR "nvram_scan_partitions: kmalloc failed\n"); @@ -512,12 +534,16 @@ static int __init nvram_scan_partitions(void)  		memcpy(&tmp_part->header, &phead, NVRAM_HEADER_LEN);  		tmp_part->index = cur_index; -		list_add_tail(&tmp_part->partition, &nvram_part->partition); +		list_add_tail(&tmp_part->partition, &nvram_partitions);  		cur_index += phead.length * NVRAM_BLOCK_LEN;  	}  	err = 0; +#ifdef DEBUG_NVRAM +	nvram_print_partitions("NVRAM Partitions"); +#endif +   out:  	kfree(header);  	return err; @@ -525,9 +551,10 @@ static int __init nvram_scan_partitions(void)  static int __init nvram_init(void)  { -	int error;  	int rc; +	BUILD_BUG_ON(NVRAM_BLOCK_LEN != 16); +  	if (ppc_md.nvram_size == NULL || ppc_md.nvram_size() <= 0)  		return  -ENODEV; @@ -537,29 +564,6 @@ static int __init nvram_init(void)  		return rc;  	} -  	/* initialize our anchor for the nvram partition list */ -  	nvram_part = kmalloc(sizeof(struct nvram_partition), GFP_KERNEL); -  	if (!nvram_part) { -  		printk(KERN_ERR "nvram_init: Failed kmalloc\n"); -  		return -ENOMEM; -  	} -  	INIT_LIST_HEAD(&nvram_part->partition); -   -  	/* Get all the NVRAM partitions */ -  	error = nvram_scan_partitions(); -  	if (error) { -  		printk(KERN_ERR "nvram_init: Failed nvram_scan_partitions\n"); -  		return error; -  	} -  		 -  	if(nvram_setup_partition())  -  		printk(KERN_WARNING "nvram_init: Could not find nvram partition" -  		       " for nvram buffered error logging.\n"); -   -#ifdef DEBUG_NVRAM -	nvram_print_partitions("NVRAM Partitions"); -#endif -    	return rc;  } @@ -568,135 +572,6 @@ void __exit nvram_cleanup(void)          misc_deregister( &nvram_dev );  } - -#ifdef CONFIG_PPC_PSERIES - -/* nvram_write_error_log - * - * We need to buffer the error logs into nvram to ensure that we have - * the failure information to decode.  If we have a severe error there - * is no way to guarantee that the OS or the machine is in a state to - * get back to user land and write the error to disk.  For example if - * the SCSI device driver causes a Machine Check by writing to a bad - * IO address, there is no way of guaranteeing that the device driver - * is in any state that is would also be able to write the error data - * captured to disk, thus we buffer it in NVRAM for analysis on the - * next boot. - * - * In NVRAM the partition containing the error log buffer will looks like: - * Header (in bytes): - * +-----------+----------+--------+------------+------------------+ - * | signature | checksum | length | name       | data             | - * |0          |1         |2      3|4         15|16        length-1| - * +-----------+----------+--------+------------+------------------+ - * - * The 'data' section would look like (in bytes): - * +--------------+------------+-----------------------------------+ - * | event_logged | sequence # | error log                         | - * |0            3|4          7|8            nvram_error_log_size-1| - * +--------------+------------+-----------------------------------+ - * - * event_logged: 0 if event has not been logged to syslog, 1 if it has - * sequence #: The unique sequence # for each event. (until it wraps) - * error log: The error log from event_scan - */ -int nvram_write_error_log(char * buff, int length, -                          unsigned int err_type, unsigned int error_log_cnt) -{ -	int rc; -	loff_t tmp_index; -	struct err_log_info info; -	 -	if (nvram_error_log_index == -1) { -		return -ESPIPE; -	} - -	if (length > nvram_error_log_size) { -		length = nvram_error_log_size; -	} - -	info.error_type = err_type; -	info.seq_num = error_log_cnt; - -	tmp_index = nvram_error_log_index; - -	rc = ppc_md.nvram_write((char *)&info, sizeof(struct err_log_info), &tmp_index); -	if (rc <= 0) { -		printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc); -		return rc; -	} - -	rc = ppc_md.nvram_write(buff, length, &tmp_index); -	if (rc <= 0) { -		printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc); -		return rc; -	} -	 -	return 0; -} - -/* nvram_read_error_log - * - * Reads nvram for error log for at most 'length' - */ -int nvram_read_error_log(char * buff, int length, -                         unsigned int * err_type, unsigned int * error_log_cnt) -{ -	int rc; -	loff_t tmp_index; -	struct err_log_info info; -	 -	if (nvram_error_log_index == -1) -		return -1; - -	if (length > nvram_error_log_size) -		length = nvram_error_log_size; - -	tmp_index = nvram_error_log_index; - -	rc = ppc_md.nvram_read((char *)&info, sizeof(struct err_log_info), &tmp_index); -	if (rc <= 0) { -		printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc); -		return rc; -	} - -	rc = ppc_md.nvram_read(buff, length, &tmp_index); -	if (rc <= 0) { -		printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc); -		return rc; -	} - -	*error_log_cnt = info.seq_num; -	*err_type = info.error_type; - -	return 0; -} - -/* This doesn't actually zero anything, but it sets the event_logged - * word to tell that this event is safely in syslog. - */ -int nvram_clear_error_log(void) -{ -	loff_t tmp_index; -	int clear_word = ERR_FLAG_ALREADY_LOGGED; -	int rc; - -	if (nvram_error_log_index == -1) -		return -1; - -	tmp_index = nvram_error_log_index; -	 -	rc = ppc_md.nvram_write((char *)&clear_word, sizeof(int), &tmp_index); -	if (rc <= 0) { -		printk(KERN_ERR "nvram_clear_error_log: Failed nvram_write (%d)\n", rc); -		return rc; -	} - -	return 0; -} - -#endif /* CONFIG_PPC_PSERIES */ -  module_init(nvram_init);  module_exit(nvram_cleanup);  MODULE_LICENSE("GPL");  | 
