linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* RE: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-05-19 12:03 Abhay_Salunke
  2005-05-19 14:42 ` Greg KH
  0 siblings, 1 reply; 28+ messages in thread
From: Abhay_Salunke @ 2005-05-19 12:03 UTC (permalink / raw)
  To: greg; +Cc: linux-kernel, akpm, Matt_Domsch, adobriyan

> -----Original Message-----
> From: Greg KH [mailto:greg@kroah.com]
> Sent: Wednesday, May 18, 2005 10:33 PM
> To: Salunke, Abhay
> Cc: linux-kernel@vger.kernel.org; Andrew Morton; Domsch, Matt; Alexey
> Dobriyan
> Subject: Re: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new
Dell
> BIOS update driver
> 
> On Wed, May 18, 2005 at 01:13:42PM -0500, Abhay Salunke wrote:
> > This is a resubmit of the patch after incorporating all the inputs
> > from revieweres. This also has a fix where the packets were leaked
in
> > the function create_packet line#227.
> 
> You did not address the issues I had with your use of binary sysfs
files
> for all file types.  Please fix that up.
> 
> Also, what's wrong with using the existing firmware interface in the
> kernel?
> 
I am working on it; just wanted to address all the cosmetic issues
first.

Thanks,
Abhay 


^ permalink raw reply	[flat|nested] 28+ messages in thread
* [patch 2.6.12-rc3]dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-07-20 23:50 Abhay Salunke
  0 siblings, 0 replies; 28+ messages in thread
From: Abhay Salunke @ 2005-07-20 23:50 UTC (permalink / raw)
  To: linux-kernel, akpm, abhay_salunke; +Cc: greg

Resending dell_rbu driver after making a few more improvements and also 
using the new request_firmware_nowait kernel API sent in the firmware_class.c 
patch.

This patch has been tested on i386 and x86-64 systems along with the 
firmware_class.c patch and it works fine.

Signed-off-by: Abhay Salunke <Abhay_Salunke@dell.com>

Thanks
Abhay
diff -uprN linux-2.6.11.11.orig/Documentation/dell_rbu.txt linux-2.6.11.11.new/Documentation/dell_rbu.txt
--- linux-2.6.11.11.orig/Documentation/dell_rbu.txt	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.11.new/Documentation/dell_rbu.txt	2005-07-14 14:59:10.000000000 -0500
@@ -0,0 +1,74 @@
+Purpose:
+Demonstrate the usage of the new open sourced rbu (Remote BIOS Update) driver
+for updating BIOS images on Dell servers and desktops.
+
+Scope:
+This document discusses the functionality of the rbu driver only.
+It does not cover the support needed from aplications to enable the BIOS to
+update itself with the image downloaded in to the memory.
+
+Overview:
+This driver works with Dell OpenManage or Dell Update Packages for updating 
+the BIOS on Dell servers (starting from servers sold since 1999), desktops 
+and notebooks (starting from those sold in 2005). 
+Please go to  http://support.dell.com register and you can find info on
+OpenManage and Dell Update packages (DUP).
+
+Dell_RBU driver supports BIOS update using the monilothic image and packetized
+image methods. In case of moniolithic the driver allocates a contiguous chunk
+of physical pages having the BIOS image. In case of packetized the app
+using the driver breaks the image in to packets of fixed sizes and the driver
+would place each packet in contiguous physical memory. The driver also
+maintains a link list of packets for reading them back.
+If the dell_rbu driver is unloaded all the allocated memory is freed.
+
+The rbu driver needs to have an application which will inform the BIOS to
+enable the update in the next system reboot.
+
+The user should not unload the rbu driver after downloading the BIOS image
+or updating.
+
+The driver load creates the following directories under the /sys file system.
+/sys/class/firmware/dell_rbu/loading 
+/sys/class/firmware/dell_rbu/data
+/sys/devices/platform/dell_rbu/image_type
+/sys/devices/platform/dell_rbu/data
+
+The driver supports two types of update mechanism; monolithic and packetized.
+These update mechanism depends upon the BIOS currently running on the system.
+Most of the Dell systems support a monolithic update where the BIOS image is 
+copied to a single contiguous block of physical memory. 
+In case of packet mechanism the single memory can be broken in smaller chuks
+of contiguous memory and the BIOS image is scattered in these packets.
+
+By default the driver uses monolithic memory for the update type. This can be
+changed to contiguous during the driver load time by specifying the load
+parameter image_type=packet.  This can also be changed later as below
+echo "packet" > /sys/devices/platform/dell_rbu/image_type 
+
+Do the steps below to download the BIOS image.
+1) echo 1 > /sys/class/firmware/dell_rbu/loading
+2) cp bios_image.hdr /sys/class/firmware/dell_rbu/data
+3) echo 0 > /sys/class/firmware/dell_rbu/loading
+
+The /sys/class/firmware/dell_rbu/ entries will remain till the following is
+done. 
+echo -1 > /sys/class/firmware/dell_rbu/loading
+
+Until this step is completed the drivr cannot be unloaded.
+
+Also the driver provides /sys/devices/platform/dell_rbu/data readonly file to
+read back the image downloaded. This is useful in case of packet update 
+mechanism where the above steps 1,2,3 will repeated for every packet. 
+By reading the /sys/devices/platform/dell_rbu/data file all packet data 
+downloaded can be verified in a single file. 
+The packets are arranged in this file one after the other in a FIFO order.
+
+NOTE:
+This driver requires a patch for firmware_class.c which has the addition
+of request_firmware_nowait_nohotplug function to wortk
+Also after updating the BIOS image an user mdoe application neeeds to execute
+code which message the BIOS update request to the BIOS. So on the next reboot
+the BIOS knows about the new image downloaded and it updates it self.
+Also don't unload the rbu drive if the image has to be updated.
+
diff -uprN linux-2.6.11.11.orig/drivers/firmware/dell_rbu.c linux-2.6.11.11.new/drivers/firmware/dell_rbu.c
--- linux-2.6.11.11.orig/drivers/firmware/dell_rbu.c	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.11.new/drivers/firmware/dell_rbu.c	2005-07-20 18:30:51.000000000 -0500
@@ -0,0 +1,656 @@
+/*
+ * dell_rbu.c
+ * Bios Update driver for Dell systems
+ * Author: Dell Inc
+ *         Abhay Salunke <abhay_salunke@dell.com>
+ *
+ * Copyright (C) 2005 Dell Inc.
+ *
+ * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by 
+ * creating entries in the /sys file systems on Linux 2.6 and higher 
+ * kernels. The driver supports two mechanism to update the BIOS namely 
+ * contiguous and packetized. Both these methods still require having some
+ * application to set the CMOS bit indicating the BIOS to update itself 
+ * after a reboot.
+ *
+ * Contiguous method:
+ * This driver writes the incoming data in a monolithic image by allocating 
+ * contiguous physical pages large enough to accommodate the incoming BIOS 
+ * image size.  
+ *
+ * Packetized method:
+ * The driver writes the incoming packet image by allocating a new packet 
+ * on every time the packet data is written. This driver requires an 
+ * application to break the BIOS image in to fixed sized packet chunks.
+ *
+ * See Documentation/dell_rbu.txt for more info.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * 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.
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/dma-mapping.h>
+
+MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
+MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+#define BIOS_SCAN_LIMIT 0xffffffff
+#define MAX_IMAGE_LENGTH 16
+static struct _rbu_data {
+	void *image_update_buffer;
+	unsigned long image_update_buffer_size;
+	unsigned long bios_image_size;
+	int image_update_ordernum;
+	int dma_alloc;
+	spinlock_t lock;
+	unsigned long packet_read_count;
+	unsigned long packet_write_count;
+	unsigned long num_packets;
+	unsigned long packetsize;
+} rbu_data;
+
+static char image_type[MAX_IMAGE_LENGTH] = "mono";
+module_param_string(image_type, image_type, sizeof (image_type), 0);
+MODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet");
+
+struct packet_data {
+	struct list_head list;
+	size_t length;
+	void *data;
+	int ordernum;
+};
+
+static struct packet_data packet_data_head;
+
+static struct platform_device *rbu_device;
+static int context;
+static dma_addr_t dell_rbu_dmaaddr;
+
+static void
+init_packet_head(void)
+{
+	INIT_LIST_HEAD(&packet_data_head.list);
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+static int
+fill_last_packet(void *data, size_t length)
+{
+	struct list_head *ptemp_list;
+	struct packet_data *packet = NULL;
+	int packet_count = 0;
+
+	pr_debug("fill_last_packet: entry \n");
+
+	if (!rbu_data.num_packets) {
+		pr_debug("fill_last_packet: num_packets=0\n");
+		return -ENOMEM;
+	}
+
+	packet_count = rbu_data.num_packets;
+
+	ptemp_list = (&packet_data_head.list)->prev;
+
+	packet = list_entry(ptemp_list, struct packet_data, list);
+
+	if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {
+		pr_debug("dell_rbu:%s: packet size data "
+			"overrun\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	pr_debug("fill_last_packet : buffer = %p\n", packet->data);
+
+	memcpy((packet->data + rbu_data.packet_write_count), data, length);
+
+	if ((rbu_data.packet_write_count + length) == rbu_data.packetsize) {
+		/*
+		 * this was the last data chunk in the packet
+		 * so reinitialize the packet data counter to zero
+		 */
+		rbu_data.packet_write_count = 0;
+	} else
+		rbu_data.packet_write_count += length;
+
+	pr_debug("fill_last_packet: exit \n");
+	return 0;
+}
+
+static int
+create_packet(size_t length)
+{
+	struct packet_data *newpacket;
+	int ordernum = 0;
+
+	pr_debug("create_packet: entry \n");
+
+	if (!rbu_data.packetsize) {
+		pr_debug("create_packet: packetsize not specified\n");
+		return -EINVAL;
+	}
+
+	newpacket = kmalloc(sizeof (struct packet_data), GFP_KERNEL);
+	if (!newpacket) {
+		printk(KERN_WARNING
+			"dell_rbu:%s: failed to allocate new "
+			"packet\n", __FUNCTION__);
+		return -ENOMEM;
+	}
+
+	ordernum = get_order(length);
+	/*
+	 * there is no upper limit on memory 
+	 * address for packetized mechanism 
+	 */
+	newpacket->data = (unsigned char *) __get_free_pages(GFP_KERNEL,
+		ordernum);
+
+	pr_debug("create_packet: newpacket %p\n", newpacket->data);
+
+	if (!newpacket->data) {
+		printk(KERN_WARNING
+			"dell_rbu:%s: failed to allocate new "
+			"packet\n", __FUNCTION__);
+		kfree(newpacket);
+		return -ENOMEM;
+	}
+
+	newpacket->ordernum = ordernum;
+	++rbu_data.num_packets;
+	/*
+	 * initialize the newly created packet headers 
+	 */
+	INIT_LIST_HEAD(&newpacket->list);
+	list_add_tail(&newpacket->list, &packet_data_head.list);
+	/*
+	 * packets have fixed size 
+	 */
+	newpacket->length = rbu_data.packetsize;
+
+	pr_debug("create_packet: exit \n");
+
+	return 0;
+}
+
+static int
+packetize_data(void *data, size_t length)
+{
+	int rc = 0;
+
+	if (!rbu_data.packet_write_count) {
+		if ((rc = create_packet(length)))
+			return rc;
+	}
+	if ((rc = fill_last_packet(data, length)))
+		return rc;
+
+	return rc;
+}
+
+static int
+do_packet_read(char *data, struct list_head *ptemp_list,
+	int length, int bytes_read, int *list_read_count)
+{
+	void *ptemp_buf;
+	struct packet_data *newpacket = NULL;
+	int bytes_copied = 0;
+	int j = 0;
+
+	newpacket = list_entry(ptemp_list, struct packet_data, list);
+	*list_read_count += newpacket->length;
+
+	if (*list_read_count > bytes_read) {
+		/* point to the start of unread data */
+		j = newpacket->length - (*list_read_count - bytes_read);
+		/* point to the offset in the packet buffer */
+		ptemp_buf = (u8 *) newpacket->data + j;
+		/* 
+		 * check if there is enough room in 
+		 * * the incoming buffer 
+		 */
+		if (length > (*list_read_count - bytes_read))
+			/* 
+			 * copy what ever is there in this 
+			 * packet and move on 
+			 */
+			bytes_copied = (*list_read_count - bytes_read);
+		else
+			/* copy the remaining */
+			bytes_copied = length;
+		memcpy(data, ptemp_buf, bytes_copied);
+	}
+	return bytes_copied;
+}
+
+static int
+packet_read_list(char *data, size_t * pread_length)
+{
+	struct list_head *ptemp_list;
+	int temp_count = 0;
+	int bytes_copied = 0;
+	int bytes_read = 0;
+	int remaining_bytes = 0;
+	char *pdest = data;
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets)
+		return -ENOMEM;
+
+	remaining_bytes = *pread_length;
+	bytes_read = rbu_data.packet_read_count;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while (!list_empty(ptemp_list)) {
+		bytes_copied = do_packet_read(pdest, ptemp_list,
+			remaining_bytes, bytes_read, &temp_count);
+		remaining_bytes -= bytes_copied;
+		bytes_read += bytes_copied;
+		pdest += bytes_copied;
+		/*
+		 * check if we reached end of buffer before reaching the
+		 * last packet
+		 */
+		if (remaining_bytes == 0)
+			break;
+
+		ptemp_list = ptemp_list->next;
+	}
+	/*finally set the bytes read */
+	*pread_length = bytes_read - rbu_data.packet_read_count;
+	rbu_data.packet_read_count = bytes_read;
+	return 0;
+}
+
+static void
+packet_empty_list(void)
+{
+	struct list_head *ptemp_list;
+	struct list_head *pnext_list;
+	struct packet_data *newpacket;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while (!list_empty(ptemp_list)) {
+		newpacket =
+			list_entry(ptemp_list, struct packet_data, list);
+		pnext_list = ptemp_list->next;
+		list_del(ptemp_list);
+		ptemp_list = pnext_list;
+		/*
+		 * zero out the RBU packet memory before freeing 
+		 * to make sure there are no stale RBU packets left in memory
+		 */
+		memset(newpacket->data, 0, rbu_data.packetsize);
+		free_pages((unsigned long) newpacket->data,
+			newpacket->ordernum);
+		kfree(newpacket);
+	}
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+/*
+ * img_update_free: Frees the buffer allocated for storing BIOS image
+ * Always called with lock held and returned with lock held 
+ */
+static void
+img_update_free(void)
+{
+	if (!rbu_data.image_update_buffer)
+		return;
+	/*
+	 * zero out this buffer before freeing it to get rid of any stale
+	 * BIOS image copied in memory.
+	 */
+	memset(rbu_data.image_update_buffer, 0,
+		rbu_data.image_update_buffer_size);
+	if (rbu_data.dma_alloc == 1)
+		dma_free_coherent(NULL, rbu_data.bios_image_size,
+			rbu_data.image_update_buffer, dell_rbu_dmaaddr);
+	else
+		free_pages((unsigned long) rbu_data.image_update_buffer,
+			rbu_data.image_update_ordernum);
+
+	/*
+	 * Re-initialize the rbu_data variables after a free 
+	 */
+	rbu_data.image_update_ordernum = -1;
+	rbu_data.image_update_buffer = NULL;
+	rbu_data.image_update_buffer_size = 0;
+	rbu_data.bios_image_size = 0;
+	rbu_data.dma_alloc = 0;
+}
+
+/*
+ * img_update_realloc: This function allocates the contiguous pages to
+ * accommodate the requested size of data. The memory address and size
+ * values are stored globally and on every call to this function the new
+ * size is checked to see if more data is required than the existing size. 
+ * If true the previous memory is freed and new allocation is done to
+ * accommodate the new size. If the incoming size is less then than the
+ * already allocated size, then that memory is reused. This function is
+ * called with lock held and returns with lock held. 
+ */
+static int
+img_update_realloc(unsigned long size)
+{
+	unsigned char *image_update_buffer = NULL;
+	unsigned long rc;
+	unsigned long img_buf_phys_addr;
+	int ordernum;
+	int dma_alloc = 0; 
+
+	/*
+	 * check if the buffer of sufficient size has been 
+	 * already allocated 
+	 */
+	if (rbu_data.image_update_buffer_size >= size) {
+		/*
+		 * check for corruption 
+		 */
+		if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
+			printk(KERN_ERR "dell_rbu:%s: corruption "
+				"check failed\n", __FUNCTION__);
+			return -EINVAL;
+		}
+		/*
+		 * we have a valid pre-allocated buffer with 
+		 * sufficient size 
+		 */
+		return 0;
+	}
+
+	/*
+	 * free any previously allocated buffer 
+	 */
+	img_update_free();
+	
+	spin_unlock(&rbu_data.lock);
+	
+	ordernum = get_order(size);
+	image_update_buffer =
+		(unsigned char *) __get_free_pages(GFP_KERNEL,
+		ordernum);
+
+	img_buf_phys_addr =
+		(unsigned long) virt_to_phys(image_update_buffer);
+	
+	if (img_buf_phys_addr > BIOS_SCAN_LIMIT) {
+		free_pages((unsigned long) image_update_buffer,
+			ordernum);
+		ordernum = -1;
+		image_update_buffer = dma_alloc_coherent(NULL, size,
+			&dell_rbu_dmaaddr, GFP_KERNEL);
+		dma_alloc = 1;
+	}
+	
+	spin_lock(&rbu_data.lock);
+	
+	if (image_update_buffer != NULL) {
+		rbu_data.image_update_buffer = image_update_buffer;
+		rbu_data.image_update_buffer_size = size;
+		rbu_data.bios_image_size =
+			rbu_data.image_update_buffer_size;
+		rbu_data.image_update_ordernum = ordernum;
+		rbu_data.dma_alloc = dma_alloc;
+		rc = 0;
+	} else {
+		pr_debug("Not enough memory for image update:"
+			"size = %ld\n", size);
+		rc = -ENOMEM;
+	}
+
+	return rc;
+}
+
+static ssize_t
+read_packet_data(char *buffer, loff_t pos, size_t count)
+{
+	int retval;
+	size_t bytes_left;
+	size_t data_length;
+	char *ptempBuf = buffer;
+	unsigned long imagesize;
+
+	/* check to see if we have something to return */
+	if (rbu_data.num_packets == 0) {
+		pr_debug("read_packet_data: no packets written\n");
+		retval = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+
+	imagesize = rbu_data.num_packets * rbu_data.packetsize;
+
+	if (pos > imagesize) {
+		retval = 0;
+		printk(KERN_WARNING "dell_rbu:read_packet_data: "
+			"data underrun\n");
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = imagesize - pos;
+	data_length = min(bytes_left, count);
+
+	if ((retval = packet_read_list(ptempBuf, &data_length)) < 0)
+		goto read_rbu_data_exit;
+
+	if ((pos + count) > imagesize) {
+		rbu_data.packet_read_count = 0;
+		/* this was the last copy */
+		retval = bytes_left;
+	} else
+		retval = count;
+
+read_rbu_data_exit:
+	return retval;
+}
+
+static ssize_t
+read_rbu_mono_data(char *buffer, loff_t pos, size_t count)
+{
+	unsigned char *ptemp = NULL;
+	size_t bytes_left = 0;
+	size_t data_length = 0;
+	ssize_t ret_count = 0;
+
+	/* check to see if we have something to return */
+	if ((rbu_data.image_update_buffer == NULL) ||
+		(rbu_data.bios_image_size == 0)) {
+		pr_debug("read_rbu_data_mono: image_update_buffer %p ,"
+			"bios_image_size %lu\n",
+			rbu_data.image_update_buffer,
+			rbu_data.bios_image_size);
+		ret_count = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+
+	if (pos > rbu_data.bios_image_size) {
+		ret_count = 0;
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = rbu_data.bios_image_size - pos;
+	data_length = min(bytes_left, count);
+
+	ptemp = rbu_data.image_update_buffer;
+	memcpy(buffer, (ptemp + pos), data_length);
+
+	if ((pos + count) > rbu_data.bios_image_size)
+		/* this was the last copy */
+		ret_count = bytes_left;
+	else
+		ret_count = count;
+read_rbu_data_exit:
+	return ret_count;
+}
+
+static ssize_t
+read_rbu_data(struct kobject *kobj, char *buffer, loff_t pos, size_t count)
+{
+	ssize_t ret_count = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	if (!strcmp(image_type, "mono"))
+		ret_count = read_rbu_mono_data(buffer, pos, count);
+	else if (!strcmp(image_type, "packet"))
+		ret_count = read_packet_data(buffer, pos, count);
+	else
+		pr_debug("read_rbu_data: invalid image type specified\n");
+
+	spin_unlock(&rbu_data.lock);
+	return ret_count;
+}
+
+static ssize_t
+rbu_show_image_type(struct platform_device *rbu_dev, char *buf)
+{
+	unsigned int size = 0;
+	size = sprintf(buf, "%s\n", image_type);
+	return size;
+}
+
+static ssize_t
+rbu_store_image_type(struct platform_device *rbu_dev,
+	const char *buf, size_t count)
+{
+	int rc = count;
+	spin_lock(&rbu_data.lock);
+
+	if (strlen(buf) < MAX_IMAGE_LENGTH) {
+		if (strstr(buf, "mono") || strstr(buf, "packet"))
+			sscanf(buf, "%s", image_type);
+	}
+	else
+		printk(KERN_WARNING "dell_rbu: image_type is invalid"
+			"max chars = %d\n", MAX_IMAGE_LENGTH);
+
+	/* we must free all previous allocations */
+	packet_empty_list();
+	img_update_free();
+
+	spin_unlock(&rbu_data.lock);
+	return rc;
+}
+
+struct rbu_attribute {
+	struct attribute attr;
+	ssize_t(*show) (struct platform_device * rbu_dev, char *buf);
+	ssize_t(*store) (struct platform_device * rbu_dev,
+		const char *buf, size_t count);
+};
+
+#define RBU_DEVICE_ATTR(_name, _mode, _show, _store ) \
+struct rbu_attribute rbu_attr_##_name = {       \
+	.attr = {.name = __stringify(_name),  \
+			.mode= _mode, .owner= THIS_MODULE},\
+	.show = _show,                                \
+	.store = _store,                                \
+};
+
+static RBU_DEVICE_ATTR(image_type, 0644, rbu_show_image_type,
+	rbu_store_image_type);
+
+static struct bin_attribute rbu_data_attr = {
+	.attr = {.name = "data", .owner = THIS_MODULE, .mode = 0444},
+	.read = read_rbu_data,
+};
+
+static void
+callbackfn_rbu(const struct firmware *fw, void *context)
+{
+	int rc = 0;
+
+	if (!fw || !fw->size)
+		return;
+
+	spin_lock(&rbu_data.lock);
+	if (!strcmp(image_type, "mono")) {
+		if (!img_update_realloc(fw->size))
+			memcpy(rbu_data.image_update_buffer,
+				fw->data, fw->size);
+	} else if (!strcmp(image_type, "packet")) {
+		if (!rbu_data.packetsize)
+			rbu_data.packetsize = fw->size;
+		else if (rbu_data.packetsize != fw->size) {
+			packet_empty_list();
+			rbu_data.packetsize = fw->size;
+		}
+		packetize_data(fw->data, fw->size);
+	} else
+		pr_debug("invalid image type specified.\n");
+	spin_unlock(&rbu_data.lock);
+
+	rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG,
+		"dell_rbu", &rbu_device->dev, &context, callbackfn_rbu);
+	if (rc)
+		printk(KERN_ERR
+			"dell_rbu:%s request_firmware_nowait failed"
+			" %d\n", __FUNCTION__, rc);
+}
+
+static int __init
+dcdrbu_init(void)
+{
+	int rc = 0;
+	spin_lock_init(&rbu_data.lock);
+
+	init_packet_head();
+	rbu_device =
+		platform_device_register_simple("dell_rbu", -1, NULL, 0);
+	if (!rbu_device) {
+		printk(KERN_ERR
+			"dell_rbu:%s:platform_device_register_simple "
+			"failed\n", __FUNCTION__);
+		return -EIO;
+	}
+
+	sysfs_create_file(&rbu_device->dev.kobj,
+		&rbu_attr_image_type.attr);
+
+	sysfs_create_bin_file(&rbu_device->dev.kobj, &rbu_data_attr);
+
+	rc = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG,
+		"dell_rbu", &rbu_device->dev, &context, callbackfn_rbu);
+	if (rc)
+		printk(KERN_ERR "dell_rbu:%s:request_firmware_nowait"
+			" failed %d\n", __FUNCTION__, rc);
+
+	return rc;
+
+}
+
+static __exit void
+dcdrbu_exit(void)
+{
+	spin_lock(&rbu_data.lock);
+	packet_empty_list();
+	img_update_free();
+	spin_unlock(&rbu_data.lock);
+	platform_device_unregister(rbu_device);
+}
+
+module_exit(dcdrbu_exit);
+module_init(dcdrbu_init);
diff -uprN linux-2.6.11.11.orig/drivers/firmware/Kconfig linux-2.6.11.11.new/drivers/firmware/Kconfig
--- linux-2.6.11.11.orig/drivers/firmware/Kconfig	2005-06-14 21:00:10.000000000 -0500
+++ linux-2.6.11.11.new/drivers/firmware/Kconfig	2005-07-14 15:43:11.000000000 -0500
@@ -58,4 +58,15 @@ config EFI_PCDP
 
 	  See <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
 
+config DELL_RBU
+	tristate "BIOS update support for DELL systems via sysfs"
+	default m
+	select FW_LOADER
+	help
+	 Say Y if you want to have the option of updating the BIOS for your
+	 DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) 
+	 supporting application to comunicate with the BIOS regarding the new 
+	 image for the image update to take effect.
+	 See <file:Documentation/dell_rbu.txt> for more details on the driver.
+
 endmenu
diff -uprN linux-2.6.11.11.orig/drivers/firmware/Makefile linux-2.6.11.11.new/drivers/firmware/Makefile
--- linux-2.6.11.11.orig/drivers/firmware/Makefile	2005-06-14 21:01:11.000000000 -0500
+++ linux-2.6.11.11.new/drivers/firmware/Makefile	2005-07-14 14:51:54.000000000 -0500
@@ -4,3 +4,4 @@
 obj-$(CONFIG_EDD)             	+= edd.o
 obj-$(CONFIG_EFI_VARS)		+= efivars.o
 obj-$(CONFIG_EFI_PCDP)		+= pcdp.o
+obj-$(CONFIG_DELL_RBU)          += dell_rbu.o

^ permalink raw reply	[flat|nested] 28+ messages in thread
* [patch 2.6.12-rc3]dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-07-09  1:07 Abhay Salunke
  0 siblings, 0 replies; 28+ messages in thread
From: Abhay Salunke @ 2005-07-09  1:07 UTC (permalink / raw)
  To: linux-kernel, akpm, abhay_salunke; +Cc: greg

>Wrong Subject:  :(
Oops sorry subject line corrected.

>> +struct platform_device *rbu_device;
>> +int context;

>These should not be global variables.
made them static

>You also have some functions that are global, please fix them.
fixed

Signed-off-by: Abhay Salunke <Abhay_Salunke@dell.com>

Thanks
Abhay
diff -uprN linux-2.6.11.11.orig/Documentation/dell_rbu.txt linux-2.6.11.11.new/Documentation/dell_rbu.txt
--- linux-2.6.11.11.orig/Documentation/dell_rbu.txt	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.11.new/Documentation/dell_rbu.txt	2005-06-30 15:41:28.000000000 -0500
@@ -0,0 +1,72 @@
+Purpose:
+Demonstrate the usage of the new open sourced rbu (Remote BIOS Update) driver
+for updating BIOS images on Dell servers and desktops.
+
+Scope:
+This document discusses the functionality of the rbu driver only.
+It does not cover the support needed from aplications to enable the BIOS to
+update itself with the image downloaded in to the memory.
+
+Overview:
+This driver enables userspace applications to update the BIOS on Dell servers
+(starting from servers sold since 1999), desktops and notebooks (starting
+from those sold in 2005).
+
+The driver supports BIOS update using the monilothic image and packetized
+image methods. In case of moniolithic the driver allocates a contiguous chunk
+of physical pages having the BIOS image. In case of packetized the app
+using the driver breaks the image in to packets of fixed sizes and the driver
+would place each packet in contiguous physical memory. The driver also
+maintains a link list of packets for reading them back.
+If the dell_rbu driver is unloaded all the allocated memory is freed.
+
+The rbu driver needs to have an application which will inform the BIOS to
+enable the update in the next system reboot.
+
+The user should not unload the rbu driver after downloading the BIOS image
+or updating.
+
+The driver load creates the following directories under the /sys file system.
+/sys/class/firmware/dell_rbu/loading 
+/sys/class/firmware/dell_rbu/data
+/sys/devices/platform/dell_rbu/image_type
+/sys/devices/platform/dell_rbu/data
+
+The driver supports two types of update mechanism; monolithic and packetized.
+These update mechanism depends upon the BIOS currently running on the system.
+Most of the Dell systems support a monolithic update where the BIOS image is 
+copied to a single contiguous block of physical memory. 
+In case of packet mechanism the single memory can be broken in smaller chuks
+of contiguous memory and the BIOS image is scattered in these packets.
+
+By default the driver uses monolithic memory for the update type. This can be
+changed to contiguous during the driver load time by specifying the load
+parameter image_type=packet.  This can also be changed later as below
+echo "packet" > /sys/devices/platform/dell_rbu/image_type 
+
+Do the steps below to download the BIOS image.
+1) echo 1 > /sys/class/firmware/dell_rbu/loading
+2) cp bios_image.hdr /sys/class/firmware/dell_rbu/data
+3) echo 0 > /sys/class/firmware/dell_rbu/loading
+
+The /sys/class/firmware/dell_rbu/ entries will remain till the following is
+done. 
+echo -1 > /sys/class/firmware/dell_rbu/loading
+
+Until this step is completed the drivr cannot be unloaded.
+
+Also the driver provides /sys/devices/platform/dell_rbu/data readonly file to
+read back the image downloaded. This is useful in case of packet update 
+mechanism where the above steps 1,2,3 will repeated for every packet. 
+By reading the /sys/devices/platform/dell_rbu/data file all packet data 
+downloaded can be verified in a single file. 
+The packets are arranged in this file one after the other in a FIFO order.
+
+NOTE:
+This driver requires a patch for firmware_class.c which has the addition
+of request_firmware_nowait_nohotplug function to wortk
+Also after updating the BIOS image an user mdoe application neeeds to execute
+code which message the BIOS update request to the BIOS. So on the next reboot
+the BIOS knows about the new image downloaded and it updates it self.
+Also don't unload the rbu drive if the image has to be updated.
+
diff -uprN linux-2.6.11.11.orig/drivers/firmware/dell_rbu.c linux-2.6.11.11.new/drivers/firmware/dell_rbu.c
--- linux-2.6.11.11.orig/drivers/firmware/dell_rbu.c	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.11.new/drivers/firmware/dell_rbu.c	2005-07-08 19:59:41.000000000 -0500
@@ -0,0 +1,627 @@
+/*
+ * dell_rbu.c
+ * Bios Update driver for Dell systems
+ * Author: Dell Inc
+ *         Abhay Salunke <abhay_salunke@dell.com>
+ *
+ * Copyright (C) 2005 Dell Inc.
+ *
+ * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by 
+ * creating entries in the /sys file systems on Linux 2.6 and higher 
+ * kernels. The driver supports two mechanism to update the BIOS namely 
+ * contiguous and packetized. Both these methods still require having some
+ * application to set the CMOS bit indicating the BIOS to update itself 
+ * after a reboot.
+ *
+ * Contiguous method:
+ * This driver writes the incoming data in a monolithic image by allocating 
+ * contiguous physical pages large enough to accommodate the incoming BIOS 
+ * image size.  
+ *
+ * Packetized method:
+ * The driver writes the incoming packet image by allocating a new packet 
+ * on every time the packet data is written. This driver requires an 
+ * application to break the BIOS image in to fixed sized packet chunks.
+ *
+ * See Documentation/dell_rbu.txt for more info.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * 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.
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+#include <linux/dma-mapping.h>
+
+MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
+MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+#define BIOS_SCAN_LIMIT 0xffffffff
+#define MAX_IMAGE_LENGTH 16
+static struct _rbu_data {
+	void *image_update_buffer;
+	unsigned long image_update_buffer_size;
+	unsigned long bios_image_size;
+	spinlock_t lock;
+	unsigned long packet_read_count;
+	unsigned long packet_write_count;
+	unsigned long num_packets;
+	unsigned long packetsize;
+} rbu_data;
+
+static char image_type[MAX_IMAGE_LENGTH] = "mono";
+module_param_string(image_type, image_type, sizeof (image_type), 0);
+MODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet");
+
+struct packet_data {
+	struct list_head list;
+	size_t length;
+	void *data;
+	int ordernum;
+};
+
+static struct packet_data packet_data_head;
+
+static struct platform_device *rbu_device;
+static int context;
+static dma_addr_t dell_rbu_dmaaddr;
+
+static void
+init_packet_head(void)
+{
+	INIT_LIST_HEAD(&packet_data_head.list);
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+static int
+fill_last_packet(void *data, size_t length)
+{
+	struct list_head *ptemp_list;
+	struct packet_data *packet = NULL;
+	int packet_count = 0;
+
+	pr_debug("fill_last_packet: entry \n");
+
+	if (!rbu_data.num_packets) {
+		pr_debug("fill_last_packet: num_packets=0\n");
+		return -ENOMEM;
+	}
+
+	packet_count = rbu_data.num_packets;
+
+	ptemp_list = (&packet_data_head.list)->prev;
+
+	packet = list_entry(ptemp_list, struct packet_data, list);
+
+	if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {
+		pr_debug("dell_rbu:%s: packet size data "
+			"overrun\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	pr_debug("fill_last_packet : buffer = %p\n", packet->data);
+
+	memcpy((packet->data + rbu_data.packet_write_count), data, length);
+
+	if ((rbu_data.packet_write_count + length) == rbu_data.packetsize) {
+		/*
+		 * this was the last data chunk in the packet
+		 * so reinitialize the packet data counter to zero
+		 */
+		rbu_data.packet_write_count = 0;
+	} else
+		rbu_data.packet_write_count += length;
+
+	pr_debug("fill_last_packet: exit \n");
+	return 0;
+}
+
+static int
+create_packet(size_t length)
+{
+	struct packet_data *newpacket;
+	int ordernum = 0;
+
+	pr_debug("create_packet: entry \n");
+
+	if (!rbu_data.packetsize) {
+		pr_debug("create_packet: packetsize not specified\n");
+		return -EINVAL;
+	}
+
+	newpacket = kmalloc(sizeof (struct packet_data), GFP_KERNEL);
+	if (!newpacket) {
+		printk(KERN_WARNING
+			"dell_rbu:%s: failed to allocate new "
+			"packet\n", __FUNCTION__);
+		return -ENOMEM;
+	}
+
+	ordernum = get_order(length);
+	/*
+	 * there is no upper limit on memory 
+	 * address for packetized mechanism 
+	 */
+	newpacket->data = (unsigned char *) __get_free_pages(GFP_KERNEL,
+		ordernum);
+
+	pr_debug("create_packet: newpacket %p\n", newpacket->data);
+
+	if (!newpacket->data) {
+		printk(KERN_WARNING
+			"dell_rbu:%s: failed to allocate new "
+			"packet\n", __FUNCTION__);
+		kfree(newpacket);
+		return -ENOMEM;
+	}
+
+	newpacket->ordernum = ordernum;
+	++rbu_data.num_packets;
+	/*
+	 * initialize the newly created packet headers 
+	 */
+	INIT_LIST_HEAD(&newpacket->list);
+	list_add_tail(&newpacket->list, &packet_data_head.list);
+	/*
+	 * packets have fixed size 
+	 */
+	newpacket->length = rbu_data.packetsize;
+
+	pr_debug("create_packet: exit \n");
+
+	return 0;
+}
+
+static int
+packetize_data(void *data, size_t length)
+{
+	int rc = 0;
+
+	if (!rbu_data.packet_write_count) {
+		if ((rc = create_packet(length)))
+			return rc;
+	}
+	if ((rc = fill_last_packet(data, length)))
+		return rc;
+
+	return rc;
+}
+
+static int
+do_packet_read(char *data, struct list_head *ptemp_list,
+	int length, int bytes_read, int *list_read_count)
+{
+	void *ptemp_buf;
+	struct packet_data *newpacket = NULL;
+	int bytes_copied = 0;
+	int j = 0;
+
+	newpacket = list_entry(ptemp_list, struct packet_data, list);
+	*list_read_count += newpacket->length;
+
+	if (*list_read_count > bytes_read) {
+		/* point to the start of unread data */
+		j = newpacket->length - (*list_read_count - bytes_read);
+		/* point to the offset in the packet buffer */
+		ptemp_buf = (u8 *) newpacket->data + j;
+		/* 
+		 * check if there is enough room in 
+		 * * the incoming buffer 
+		 */
+		if (length > (*list_read_count - bytes_read))
+			/* 
+			 * copy what ever is there in this 
+			 * packet and move on 
+			 */
+			bytes_copied = (*list_read_count - bytes_read);
+		else
+			/* copy the remaining */
+			bytes_copied = length;
+		memcpy(data, ptemp_buf, bytes_copied);
+	}
+	return bytes_copied;
+}
+
+static int
+packet_read_list(char *data, size_t * pread_length)
+{
+	struct list_head *ptemp_list;
+	int temp_count = 0;
+	int bytes_copied = 0;
+	int bytes_read = 0;
+	int remaining_bytes = 0;
+	char *pdest = data;
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets)
+		return -ENOMEM;
+
+	remaining_bytes = *pread_length;
+	bytes_read = rbu_data.packet_read_count;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while (!list_empty(ptemp_list)) {
+		bytes_copied = do_packet_read(pdest, ptemp_list,
+			remaining_bytes, bytes_read, &temp_count);
+		remaining_bytes -= bytes_copied;
+		bytes_read += bytes_copied;
+		pdest += bytes_copied;
+		/*
+		 * check if we reached end of buffer before reaching the
+		 * last packet
+		 */
+		if (remaining_bytes == 0)
+			break;
+
+		ptemp_list = ptemp_list->next;
+	}
+	/*finally set the bytes read */
+	*pread_length = bytes_read - rbu_data.packet_read_count;
+	rbu_data.packet_read_count = bytes_read;
+	return 0;
+}
+
+static void
+packet_empty_list(void)
+{
+	struct list_head *ptemp_list;
+	struct list_head *pnext_list;
+	struct packet_data *newpacket;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while (!list_empty(ptemp_list)) {
+		newpacket =
+			list_entry(ptemp_list, struct packet_data, list);
+		pnext_list = ptemp_list->next;
+		list_del(ptemp_list);
+		ptemp_list = pnext_list;
+		/*
+		 * zero out the RBU packet memory before freeing 
+		 * to make sure there are no stale RBU packets left in memory
+		 */
+		memset(newpacket->data, 0, rbu_data.packetsize);
+		free_pages((unsigned long) newpacket->data,
+			newpacket->ordernum);
+		kfree(newpacket);
+	}
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+/*
+ * img_update_free: Frees the buffer allocated for storing BIOS image
+ * Always called with lock held and returned with lock held 
+ */
+static void
+img_update_free(void)
+{
+	if (!rbu_data.image_update_buffer)
+		return;
+	/*
+	 * zero out this buffer before freeing it to get rid of any stale
+	 * BIOS image copied in memory.
+	 */
+	memset(rbu_data.image_update_buffer, 0,
+		rbu_data.image_update_buffer_size);
+	dma_free_coherent(NULL, rbu_data.bios_image_size,
+		rbu_data.image_update_buffer, dell_rbu_dmaaddr);
+
+	/*
+	 * Re-initialize the rbu_data variables after a free 
+	 */
+	rbu_data.image_update_buffer = NULL;
+	rbu_data.image_update_buffer_size = 0;
+	rbu_data.bios_image_size = 0;
+}
+
+/*
+ * img_update_realloc: This function allocates the contiguous pages to
+ * accommodate the requested size of data. The memory address and size
+ * values are stored globally and on every call to this function the new
+ * size is checked to see if more data is required than the existing size. 
+ * If true the previous memory is freed and new allocation is done to
+ * accommodate the new size. If the incoming size is less then than the
+ * already allocated size, then that memory is reused. This function is
+ * called with lock held and returns with lock held. 
+ */
+static int
+img_update_realloc(unsigned long size)
+{
+	unsigned char *image_update_buffer = NULL;
+	unsigned long rc;
+
+	/*
+	 * check if the buffer of sufficient size has been 
+	 * already allocated 
+	 */
+	if (rbu_data.image_update_buffer_size >= size) {
+		/*
+		 * check for corruption 
+		 */
+		if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
+			printk(KERN_ERR "dell_rbu:%s: corruption "
+				"check failed\n", __FUNCTION__);
+			return -EINVAL;
+		}
+		/*
+		 * we have a valid pre-allocated buffer with 
+		 * sufficient size 
+		 */
+		return 0;
+	}
+
+	/*
+	 * free any previously allocated buffer 
+	 */
+	img_update_free();
+
+	spin_unlock(&rbu_data.lock);
+
+	image_update_buffer = dma_alloc_coherent(NULL, size,
+		&dell_rbu_dmaaddr, GFP_KERNEL);
+
+	spin_lock(&rbu_data.lock);
+
+	if (image_update_buffer != NULL) {
+		rbu_data.image_update_buffer = image_update_buffer;
+		rbu_data.image_update_buffer_size = size;
+		rbu_data.bios_image_size =
+			rbu_data.image_update_buffer_size;
+		rc = 0;
+	} else {
+		pr_debug("Not enough memory for image update:"
+			"size = %ld\n", size);
+		rc = -ENOMEM;
+	}
+
+	return rc;
+}
+
+static ssize_t
+read_packet_data(char *buffer, loff_t pos, size_t count)
+{
+	int retval;
+	size_t bytes_left;
+	size_t data_length;
+	char *ptempBuf = buffer;
+	unsigned long imagesize;
+
+	/* check to see if we have something to return */
+	if (rbu_data.num_packets == 0) {
+		pr_debug("read_packet_data: no packets written\n");
+		retval = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+
+	imagesize = rbu_data.num_packets * rbu_data.packetsize;
+
+	if (pos > imagesize) {
+		retval = 0;
+		printk(KERN_WARNING "dell_rbu:read_packet_data: "
+			"data underrun\n");
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = imagesize - pos;
+	data_length = min(bytes_left, count);
+
+	if ((retval = packet_read_list(ptempBuf, &data_length)) < 0)
+		goto read_rbu_data_exit;
+
+	if ((pos + count) > imagesize) {
+		rbu_data.packet_read_count = 0;
+		/* this was the last copy */
+		retval = bytes_left;
+	} else
+		retval = count;
+
+      read_rbu_data_exit:
+	return retval;
+}
+
+static ssize_t
+read_rbu_mono_data(char *buffer, loff_t pos, size_t count)
+{
+	unsigned char *ptemp = NULL;
+	size_t bytes_left = 0;
+	size_t data_length = 0;
+	ssize_t ret_count = 0;
+
+	/* check to see if we have something to return */
+	if ((rbu_data.image_update_buffer == NULL) ||
+		(rbu_data.bios_image_size == 0)) {
+		pr_debug("read_rbu_data_mono: image_update_buffer %p ,"
+			"bios_image_size %lu\n",
+			rbu_data.image_update_buffer,
+			rbu_data.bios_image_size);
+		ret_count = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+
+	if (pos > rbu_data.bios_image_size) {
+		ret_count = 0;
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = rbu_data.bios_image_size - pos;
+	data_length = min(bytes_left, count);
+
+	ptemp = rbu_data.image_update_buffer;
+	memcpy(buffer, (ptemp + pos), data_length);
+
+	if ((pos + count) > rbu_data.bios_image_size)
+		/* this was the last copy */
+		ret_count = bytes_left;
+	else
+		ret_count = count;
+read_rbu_data_exit:
+	return ret_count;
+}
+
+static ssize_t
+read_rbu_data(struct kobject *kobj, char *buffer, loff_t pos, size_t count)
+{
+	ssize_t ret_count = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	if (!strcmp(image_type, "mono"))
+		ret_count = read_rbu_mono_data(buffer, pos, count);
+	else if (!strcmp(image_type, "packet"))
+		ret_count = read_packet_data(buffer, pos, count);
+	else
+		pr_debug("read_rbu_data: invalid image type specified\n");
+
+	spin_unlock(&rbu_data.lock);
+	return ret_count;
+}
+
+static ssize_t
+rbu_show_image_type(struct platform_device *rbu_dev, char *buf)
+{
+	unsigned int size = 0;
+	size = sprintf(buf, "%s\n", image_type);
+	return size;
+}
+
+static ssize_t
+rbu_store_image_type(struct platform_device *rbu_dev,
+	const char *buf, size_t count)
+{
+	int rc = count;
+	spin_lock(&rbu_data.lock);
+
+	if (strlen(buf) < MAX_IMAGE_LENGTH)
+		sscanf(buf, "%s", image_type);
+	else
+		printk(KERN_WARNING "dell_rbu: image_type is invalid"
+			"max chars = %d\n", MAX_IMAGE_LENGTH);
+
+	/* we must free all previous allocations */
+	packet_empty_list();
+	img_update_free();
+
+	spin_unlock(&rbu_data.lock);
+	return rc;
+}
+
+struct rbu_attribute {
+	struct attribute attr;
+	 ssize_t(*show) (struct platform_device * rbu_dev, char *buf);
+	 ssize_t(*store) (struct platform_device * rbu_dev,
+		const char *buf, size_t count);
+};
+
+#define RBU_DEVICE_ATTR(_name,_mode,_show,_store ) \
+struct rbu_attribute rbu_attr_##_name = {       \
+	.attr ={.name= __stringify(_name), .mode= _mode, .owner= THIS_MODULE},\
+	.show = _show,                                \
+	.store = _store,                                \
+};
+
+static RBU_DEVICE_ATTR(image_type, 0644, rbu_show_image_type,
+	rbu_store_image_type);
+
+static struct bin_attribute rbu_data_attr = {
+	.attr = {.name = "data",.owner = THIS_MODULE,.mode = 0444},
+	.read = read_rbu_data,
+};
+
+static void
+callbackfn_rbu(const struct firmware *fw, void *context)
+{
+	int rc = 0;
+
+	if (!fw || !fw->size)
+		return;
+
+	spin_lock(&rbu_data.lock);
+	if (!strcmp(image_type, "mono")) {
+		if (!img_update_realloc(fw->size))
+			memcpy(rbu_data.image_update_buffer,
+				fw->data, fw->size);
+	} else if (!strcmp(image_type, "packet")) {
+		if (!rbu_data.packetsize)
+			rbu_data.packetsize = fw->size;
+		else if (rbu_data.packetsize != fw->size) {
+			packet_empty_list();
+			rbu_data.packetsize = fw->size;
+		}
+		packetize_data(fw->data, fw->size);
+	} else
+		pr_debug("invalid image type specified.\n");
+	spin_unlock(&rbu_data.lock);
+
+	rc = request_firmware_nowait_nohotplug(THIS_MODULE,
+		"dell_rbu", &rbu_device->dev, &context, callbackfn_rbu);
+	if (rc)
+		printk(KERN_ERR
+			"dell_rbu:%s request_firmware_nowait failed"
+			" %d\n", __FUNCTION__, rc);
+}
+
+static int __init
+dcdrbu_init(void)
+{
+	int rc = 0;
+	spin_lock_init(&rbu_data.lock);
+
+	init_packet_head();
+
+	rbu_device =
+		platform_device_register_simple("dell_rbu", -1, NULL, 0);
+	if (!rbu_device) {
+		printk(KERN_ERR
+			"dell_rbu:%s:platform_device_register_simple "
+			"failed\n", __FUNCTION__);
+		return -EIO;
+	}
+
+	sysfs_create_file(&rbu_device->dev.kobj,
+		&rbu_attr_image_type.attr);
+
+	sysfs_create_bin_file(&rbu_device->dev.kobj, &rbu_data_attr);
+
+	rc = request_firmware_nowait_nohotplug(THIS_MODULE,
+		"dell_rbu", &rbu_device->dev, &context, callbackfn_rbu);
+	if (rc)
+		printk(KERN_ERR "dell_rbu:%s:request_firmware_nowait"
+			" failed %d\n", __FUNCTION__, rc);
+
+	return rc;
+
+}
+
+static __exit void
+dcdrbu_exit(void)
+{
+	spin_lock(&rbu_data.lock);
+	packet_empty_list();
+	img_update_free();
+	spin_unlock(&rbu_data.lock);
+	platform_device_unregister(rbu_device);
+}
+
+module_exit(dcdrbu_exit);
+module_init(dcdrbu_init);
diff -uprN linux-2.6.11.11.orig/drivers/firmware/Kconfig linux-2.6.11.11.new/drivers/firmware/Kconfig
--- linux-2.6.11.11.orig/drivers/firmware/Kconfig	2005-06-14 21:00:10.000000000 -0500
+++ linux-2.6.11.11.new/drivers/firmware/Kconfig	2005-06-30 15:37:43.000000000 -0500
@@ -58,4 +58,15 @@ config EFI_PCDP
 
 	  See <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
 
+config DELL_RBU
+	tristate "BIOS update support for DELL systems via sysfs"
+	default y
+	select FW_LOADER
+	help
+	 Say Y if you want to have the option of updating the BIOS for your
+	 DELL system. Note you need a supporting application to comunicate
+	 with the BIOS regardin the new image for the image update to
+	 take effect.
+	 See <file:Documentation/dell_rbu.txt> for more details on the driver.
+
 endmenu
diff -uprN linux-2.6.11.11.orig/drivers/firmware/Makefile linux-2.6.11.11.new/drivers/firmware/Makefile
--- linux-2.6.11.11.orig/drivers/firmware/Makefile	2005-06-14 21:01:11.000000000 -0500
+++ linux-2.6.11.11.new/drivers/firmware/Makefile	2005-06-30 15:37:35.000000000 -0500
@@ -4,3 +4,4 @@
 obj-$(CONFIG_EDD)             	+= edd.o
 obj-$(CONFIG_EFI_VARS)		+= efivars.o
 obj-$(CONFIG_EFI_PCDP)		+= pcdp.o
+obj-$(CONFIG_DELL_RBU)          += dell_rbu.o

^ permalink raw reply	[flat|nested] 28+ messages in thread
* RE: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-06-17 14:55 Abhay_Salunke
  2005-06-17 15:29 ` Greg KH
  0 siblings, 1 reply; 28+ messages in thread
From: Abhay_Salunke @ 2005-06-17 14:55 UTC (permalink / raw)
  To: greg; +Cc: linux-kernel, akpm, dmitry.torokhov, Matt_Domsch



> -----Original Message-----
> From: Greg KH [mailto:greg@kroah.com]
> Sent: Thursday, June 16, 2005 1:52 PM
> To: Salunke, Abhay
> Cc: linux-kernel@vger.kernel.org; Andrew Morton; Dmitry Torokhov;
Domsch,
> Matt
> Subject: Re: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new
Dell
> BIOS update driver
> 
> On Wed, Jun 15, 2005 at 12:59:46PM -0500, Abhay Salunke wrote:
> > +static struct device rbu_device_mono;
> > +static struct device rbu_device_packet;
> > +static struct device rbu_device_cancel;
> 
> You should never create a struct device on the stack.  Lots of bad
> things can happen (including not having a release function for them.)
> 
they are not declared inside any function; can they be on stack?
> Why not just point to the cpu device, or some other platform or system
> device?
> 
Not sure what these devices are for and didn't want to mess with them.> 

Thanks
Abhay

^ permalink raw reply	[flat|nested] 28+ messages in thread
* [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-06-15 17:59 Abhay Salunke
  2005-06-16 18:52 ` Greg KH
  2005-06-20  0:36 ` Andrew Morton
  0 siblings, 2 replies; 28+ messages in thread
From: Abhay Salunke @ 2005-06-15 17:59 UTC (permalink / raw)
  To: linux-kernel, Andrew Morton, Dmitry Torokhov
  Cc: abhay_salunke, matt_domsch, Greg KH

This patch uses request_firmware_nowait without having to modify any firmware_class.c code.

By making a contribution to this project, I certify that:
The contribution was created in whole or in part by me and I have the
right to submit it under the open source license indicated in the file.
Resubmitting after cleaning up spaces/tabs etc...

Signed-off-by: Abhay Salunke <Abhay_Salunke@dell.com>

Thanks,
Abhay Salunke
Software Engineer.
DELL Inc

diff -uprN /usr/src/linux-2.6.11.11.orig/Documentation/dell_rbu.txt /usr/src/linux-2.6.11.11/Documentation/dell_rbu.txt
--- /usr/src/linux-2.6.11.11.orig/Documentation/dell_rbu.txt	1969-12-31 18:00:00.000000000 -0600
+++ /usr/src/linux-2.6.11.11/Documentation/dell_rbu.txt	2005-06-14 21:02:09.000000000 -0500
@@ -0,0 +1,62 @@
+Purpose:
+Demonstrate the usage of the new open sourced rbu (Remote BIOS Update) driver 
+for updating BIOS images on Dell hardware.
+
+Scope:
+This document discusses the functionality of the rbu driver only. 
+It does not cover the mechanism related to flipping of the CMOS bit to tell 
+the BIOS for reflashing it self.
+
+Overview:
+The rbu driver is designed to be running on 2.6 kernel. 
+This driver is a open sourced code with one rbu.c (total lines ~500) file.
+The driver supports BIOS update using the monilothic image and packetized
+image methods. In case of moniolithic the driver allocates a contiguous chunk
+of physical pages having the BIOS image. In case of packetized the app
+using the driver breaks the image in to packets of fixed sizes and the driver
+woudl place each packet in contiguous physical memory. The driver also
+maintains a link list of packets if for reading them back.
+If the dell_rbu driver is unloaded all the allocated memory is freed.
+
+The rbu driver needs to have an aplication which flips the CMOS bit to enable 
+the BIOS update after a reboot.
+
+The user should not unload the rbu driver after downloading the BIOS image for updating.
+
+The driver load creates the following directories under the /sys file system.
+/sys/class/firmware/dell_rbu_mono
+/sys/class/firmware/dell_rbu_packet
+/sys/class/firmware/dell_rbu_cancel
+
+each of these directories have the files loading and data.
+
+Always before starting the BIOS update do 
+1> capture value of /sys/class/firmware/timeout 
+2> echo 0 > /sys/class/firmware/timeout
+before exiting the BIOS update script restore the timeout value.
+
+Steps for updating a monolithic image
+1> echo 1 > /sys/class/firmware/dell_rbu_mono/loading
+2> cp image_file /sys/class/firmware/dell_rbu_mono/data
+3> echo 0 > /sys/class/firmware/dell_rbu_mono/loading
+
+Steps for updating a packet image
+1> echo 1 > /sys/class/firmware/dell_rbu_packet/loading
+2> cp image_file /sys/class/firmware/dell_rbu_packet/data
+3> echo 0 > /sys/class/firmware/dell_rbu_packet/loading
+
+The uploaded image can be freed without unloading the driver as follows.
+echo "-1" > /sys/class/firmware/dell_rbu_cancel/loading
+
+
+The user can overwrite the data file with a new image. 
+The user has to make sure that the new image size is less than or equal to the 
+image size copied to the rbudatasize file. 
+If the new image is grater than the allocated size then only the allocated size gets copied the rest will not.
+
+NOTE:
+Afte updating the BIOS image the CMOS bit has to be set by some application to enable the update.
+Also dont unload the rbu drive if the image has to be updated.
+
+
+
diff -uprN /usr/src/linux-2.6.11.11.orig/drivers/firmware/dell_rbu.c /usr/src/linux-2.6.11.11/drivers/firmware/dell_rbu.c
--- /usr/src/linux-2.6.11.11.orig/drivers/firmware/dell_rbu.c	1969-12-31 18:00:00.000000000 -0600
+++ /usr/src/linux-2.6.11.11/drivers/firmware/dell_rbu.c	2005-06-15 15:05:13.928810384 -0500
@@ -0,0 +1,487 @@
+/*
+ * dell_rbu.c
+ * Bios Update driver for Dell systems
+ * Author: Dell Inc
+ *	   Abhay Salunke <abhay_salunke@dell.com>
+ *
+ * Copyright (C) 2005 Dell Inc.
+ *
+ * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by 
+ * creating entries in the /sys file systems on Linux 2.6 and higher 
+ * kernels. The driver supports two mechanism to update the BIOS namely 
+ * contiguous and packetized. Both these methods still require having some
+ * application to set the CMOS bit indicating the BIOS to update itself 
+ * after a reboot.
+ *
+ * Contiguous method:
+ * This driver writes the incoming data in a monolithic image by allocating 
+ * contiguous physical pages large enough to accommodate the incoming BIOS 
+ * image size.  
+ *
+ * Packetized method:
+ * The driver writes the incoming packet image by allocating a new packet 
+ * on every time the packet data is written. This driver requires an 
+ * application to break the BIOS image in to fixed sized packet chunks.
+ *
+ * See Documentation/dell_rbu.txt for more info.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * 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.
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+
+MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
+MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+#define BIOS_SCAN_LIMIT 0xffffffff
+
+static struct _rbu_data {
+	void *image_update_buffer;
+	unsigned long image_update_buffer_size;
+	unsigned long bios_image_size;
+	unsigned long image_update_order_number;
+	spinlock_t lock;
+	unsigned long packet_read_count;
+	unsigned long packet_write_count;
+	unsigned long num_packets;
+	unsigned long packetsize;
+} rbu_data;
+
+struct packet_data {
+	struct list_head list;
+	size_t length;
+	void *data;
+	int ordernum;
+};
+
+static struct packet_data packet_data_head;
+
+static void init_packet_head(void)
+{
+	INIT_LIST_HEAD(&packet_data_head.list);
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+static struct device rbu_device_mono;
+static struct device rbu_device_packet;
+static struct device rbu_device_cancel;
+
+static int fill_last_packet(void *data, size_t length)
+{
+	struct list_head *ptemp_list;
+	struct packet_data *ppacket = NULL;
+	int packet_count = 0;
+
+	pr_debug("fill_last_packet: entry \n");
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets) {
+		pr_debug("fill_last_packet: num_packets=0\n");
+		return -ENOMEM;
+	}
+
+	packet_count = rbu_data.num_packets;
+
+	ptemp_list = (&packet_data_head.list)->next;
+
+	while (--packet_count)
+		ptemp_list = ptemp_list->next;
+
+	ppacket = list_entry(ptemp_list, struct packet_data, list);
+
+	if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {
+		printk(KERN_WARNING "fill_last_packet: packet size data "
+		       "overrun\n");
+		return -ENOMEM;
+	}
+
+	pr_debug("fill_last_packet : buffer = %p\n", ppacket->data);
+
+	/* copy the incoming data in to the new buffer */
+	memcpy((ppacket->data + rbu_data.packet_write_count), data, length);
+
+	if ((rbu_data.packet_write_count + length) == rbu_data.packetsize) {
+		/*
+		   this was the last data chunk in the packet
+		   so reinitialize the packet data counter to zero
+		 */
+		rbu_data.packet_write_count = 0;
+	} else
+		rbu_data.packet_write_count += length;
+
+	pr_debug("fill_last_packet: exit \n");
+	return 0;
+}
+
+/*
+ get_free_pages_limited:
+ This is a helper function which allocates free pages based on an upper limit.
+ On x86_64 or 64 bit arch the memory allocation goes above 4GB space which is
+ not addressable by the BIOS. This function tries to get allocation below the
+ limit (4GB) address. It first tries to allocate memory normally using the
+ GFP_KERNEL argument if the incoming limit is non-zero and if the returned
+ physical memory address exceeds the upper limit, the allocated pages are 
+ freed and the memory is reallocated using the GFP_DMA argument.
+*/
+static void *get_free_pages_limited(unsigned long size, int *ordernum,
+				    unsigned long limit)
+{
+	unsigned long img_buf_phys_addr;
+	void *pbuf = NULL;
+
+	*ordernum = get_order(size);
+	/*
+	   Check if we are not getting a very large file.
+	   This can happen as a user error in entering the file size
+	 */
+	if (*ordernum == BITS_PER_LONG) {
+		pr_debug("get_free_pages_limited: Incoming size is"
+			 " very large\n");
+		return NULL;
+	}
+
+	/* try allocating a new buffer to fit the request */
+	pbuf = (unsigned char *)__get_free_pages(GFP_KERNEL, *ordernum);
+
+	if (pbuf) {
+		/* check if the image is with in limits */
+		img_buf_phys_addr = (unsigned long)virt_to_phys(pbuf);
+
+		if (!limit && ((img_buf_phys_addr + size) > limit)) {
+			pr_debug("Got memory above 4GB range, free this "
+				 "and try with DMA memory\n");
+			/*
+			   free this memory as we need it with in 
+			   4GB range 
+			 */
+			free_pages((unsigned long)pbuf, *ordernum);
+			/*
+			   Try allocating a new buffer from the 
+			   GFP_DMA range as it is with in 16MB range.
+			 */
+			pbuf = (unsigned char *)__get_free_pages(GFP_DMA,
+								 *ordernum);
+			if (!pbuf)
+				pr_debug("Failed to get memory "
+					 "of size %ld "
+					 "using GFP_DMA\n", size);
+		}
+	}
+	return pbuf;
+}
+
+static int create_packet(size_t length)
+{
+	struct packet_data *newpacket;
+	int ordernum = 0;
+
+	pr_debug("create_packet: entry \n");
+
+	if (!rbu_data.packetsize) {
+		pr_debug("create_packet: packetsize not specified\n");
+		return -EINVAL;
+	}
+
+	newpacket = kmalloc(sizeof(struct packet_data), GFP_KERNEL);
+	if (!newpacket) {
+		printk(KERN_WARNING "create_packet: failed to allocate new "
+		       "packet\n");
+		return -ENOMEM;
+	}
+
+	/* there is no upper limit on memory address for packetized mechanism */
+	newpacket->data = get_free_pages_limited(rbu_data.packetsize,
+						 &ordernum, 0);
+	pr_debug("create_packet: newpacket %p\n", newpacket->data);
+
+	if (!newpacket->data) {
+		printk(KERN_WARNING "create_packet: failed to allocate new "
+		       "packet\n");
+		kfree(newpacket);
+		return -ENOMEM;
+	}
+
+	newpacket->ordernum = ordernum;
+	++rbu_data.num_packets;
+	/* initialize the newly created packet headers */
+	INIT_LIST_HEAD(&newpacket->list);
+	list_add_tail(&newpacket->list, &packet_data_head.list);
+	/* packets have fixed size */
+	newpacket->length = rbu_data.packetsize;
+
+	pr_debug("create_packet: exit \n");
+
+	return 0;
+}
+
+static int packetize_data(void *data, size_t length)
+{
+	int rc = 0;
+
+	if (!rbu_data.packet_write_count) {
+		if ((rc = create_packet(length)))
+			return rc;
+	}
+	/* fill data in to the packet */
+	if ((rc = fill_last_packet(data, length)))
+		return rc;
+
+	return rc;
+}
+
+static void packet_empty_list(void)
+{
+	struct list_head *ptemp_list;
+	struct list_head *pnext_list;
+	struct packet_data *newpacket;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while (!list_empty(ptemp_list)) {
+		newpacket = list_entry(ptemp_list, struct packet_data, list);
+		pnext_list = ptemp_list->next;
+		list_del(ptemp_list);
+		ptemp_list = pnext_list;
+		/*
+		   zero out the RBU packet memory before freeing 
+		   to make sure there are no stale RBU packets left in memory
+		 */
+		memset(newpacket->data, 0, rbu_data.packetsize);
+		free_pages((unsigned long)newpacket->data, newpacket->ordernum);
+		kfree(newpacket);
+	}
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+/*
+ img_update_free:
+ Frees the buffer allocated for storing BIOS image
+ Always called with lock held and returned with lock held
+*/
+static void img_update_free(void)
+{
+	if (!rbu_data.image_update_buffer)
+		return;
+	/*
+	   zero out this buffer before freeing it to get rid of any stale
+	   BIOS image copied in memory.
+	 */
+	memset(rbu_data.image_update_buffer, 0,
+	       rbu_data.image_update_buffer_size);
+	free_pages((unsigned long)rbu_data.image_update_buffer,
+		   rbu_data.image_update_order_number);
+	/* Re-initialize the rbu_data variables after a free */
+	rbu_data.image_update_buffer = NULL;
+	rbu_data.image_update_buffer_size = 0;
+	rbu_data.bios_image_size = 0;
+}
+
+/*
+ img_update_realloc:
+ This function allocates the contiguous pages to accommodate the requested
+ size of data. The memory address and size values are stored globally and
+ on every call to this function the new size is checked to see if more
+ data is required than the existing size. If true the previous memory is
+ freed and new allocation is done to accommodate the new size. If the 
+ incoming size is less then than the already allocated size, then that
+ memory is reused.
+ This function is called with lock held and returns with lock held.
+*/
+static int img_update_realloc(unsigned long size)
+{
+	unsigned char *image_update_buffer = NULL;
+	unsigned long rc;
+	int ordernum = 0;
+
+	/* 
+	   check if the buffer of sufficient size has been 
+	   already allocated 
+	 */
+	if (rbu_data.image_update_buffer_size >= size) {
+		/* check for corruption */
+		if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
+			printk(KERN_ERR "img_update_realloc: "
+			       "corruption check failed\n");
+			return -EINVAL;
+		}
+		/* 
+		   we have a valid pre-allocated buffer with 
+		   sufficient size 
+		 */
+		return 0;
+	}
+
+	/* free any previously allocated buffer */
+	img_update_free();
+
+	/*
+	   This has already been called as locked so we can now unlock
+	   and proceed to calling get_free_pages_limited as this function
+	   can sleep
+	 */
+	spin_unlock(&rbu_data.lock);
+
+	image_update_buffer = (unsigned char *)get_free_pages_limited(size,
+								      &ordernum,
+								      BIOS_SCAN_LIMIT);
+
+	/* acquire the spinlock again */
+	spin_lock(&rbu_data.lock);
+
+	if (image_update_buffer != NULL) {
+		rbu_data.image_update_buffer = image_update_buffer;
+		rbu_data.image_update_buffer_size = PAGE_SIZE << ordernum;
+		rbu_data.image_update_order_number = ordernum;
+		memset(rbu_data.image_update_buffer, 0,
+		       rbu_data.image_update_buffer_size);
+		rc = 0;
+	} else {
+		pr_debug("Not enough memory for image update:order"
+			 " number = %d,size = %ld\n", ordernum, size);
+		rc = -ENOMEM;
+	}
+
+	return rc;
+}				/* img_update_realloc */
+
+int context;
+
+void callbackfn_mono(const struct firmware *fw, void *context);
+void callbackfn_packet(const struct firmware *fw, void *context);
+void callbackfn_cancel(const struct firmware *fw, void *context);
+
+void callbackfn_cancel(const struct firmware *fw, void *context)
+{
+	int rc;
+	if (!fw) {
+		spin_lock(&rbu_data.lock);
+		packet_empty_list();
+		img_update_free();
+		spin_unlock(&rbu_data.lock);
+	}
+	if ((rc = request_firmware_nowait(THIS_MODULE,
+					  "dell_rbu_cancel", &rbu_device_cancel,
+					  &context, callbackfn_mono)))
+		printk(KERN_ERR "%s rbu_cancel request_firmware_nowait"
+		       " failed %d\n", __FUNCTION__, rc);
+
+}
+
+void callbackfn_packet(const struct firmware *fw, void *context)
+{
+	int rc;
+	if (fw) {
+		spin_lock(&rbu_data.lock);
+		if (!rbu_data.packetsize)
+			rbu_data.packetsize = fw->size;
+		else if (rbu_data.packetsize != fw->size) {
+			packet_empty_list();
+			rbu_data.packetsize = fw->size;
+		}
+		packetize_data(fw->data, fw->size);
+		spin_unlock(&rbu_data.lock);
+	}
+	if ((rc = request_firmware_nowait(THIS_MODULE,
+					  "dell_rbu_packet", &rbu_device_packet,
+					  &context, callbackfn_packet)))
+		printk(KERN_ERR "%s rbu_packet request_firmware_nowait"
+		       " failed %d\n", __FUNCTION__, rc);
+
+}
+
+void callbackfn_mono(const struct firmware *fw, void *context)
+{
+	int rc;
+	if (fw) {
+		spin_lock(&rbu_data.lock);
+		if (!img_update_realloc(fw->size))
+			memcpy(rbu_data.image_update_buffer,
+			       fw->data, fw->size);
+		spin_unlock(&rbu_data.lock);
+	}
+	if ((rc = request_firmware_nowait(THIS_MODULE,
+					  "dell_rbu_mono_", &rbu_device_mono,
+					  &context, callbackfn_mono)))
+		printk(KERN_ERR "%s rbu_mono request_firmware_nowait"
+		       " failed %d\n", __FUNCTION__, rc);
+
+}
+
+static int __init dcdrbu_init(void)
+{
+	int rc = 0;
+	spin_lock_init(&rbu_data.lock);
+
+	init_packet_head();
+
+	device_initialize(&rbu_device_mono);
+	device_initialize(&rbu_device_packet);
+	device_initialize(&rbu_device_cancel);
+
+	strncpy(rbu_device_mono.bus_id, "dell_rbu_mono", BUS_ID_SIZE);
+	strncpy(rbu_device_packet.bus_id, "dell_rbu_packet", BUS_ID_SIZE);
+	strncpy(rbu_device_cancel.bus_id, "dell_rbu_cancel", BUS_ID_SIZE);
+
+	kobject_set_name(&rbu_device_mono.kobj, "%s", rbu_device_mono.bus_id);
+	kobject_set_name(&rbu_device_packet.kobj, "%s",
+			 rbu_device_packet.bus_id);
+	kobject_set_name(&rbu_device_cancel.kobj, "%s",
+			 rbu_device_cancel.bus_id);
+
+	rc = request_firmware_nowait(THIS_MODULE,
+				     "dell_rbu_mono", &rbu_device_mono,
+				     &context, callbackfn_mono);
+	if (rc)
+		printk(KERN_ERR "%s rbu_mono request_firmware_nowait"
+		       " failed %d\n", __FUNCTION__, rc);
+
+	rc = request_firmware_nowait(THIS_MODULE,
+				     "dell_rbu_packet", &rbu_device_packet,
+				     &context, callbackfn_packet);
+	if (rc)
+		printk(KERN_ERR "%s rbu_packet request_firmware_nowait"
+		       " failed %d\n", __FUNCTION__, rc);
+
+	rc = request_firmware_nowait(THIS_MODULE,
+				     "dell_rbu_cancel", &rbu_device_cancel,
+				     &context, callbackfn_cancel);
+	if (rc)
+		printk(KERN_ERR "%s rbu_cancel request_firmware_nowait"
+		       " failed %d\n", __FUNCTION__, rc);
+
+	return rc;
+}
+
+static __exit void dcdrbu_exit(void)
+{
+	spin_lock(&rbu_data.lock);
+	packet_empty_list();
+	img_update_free();
+	spin_unlock(&rbu_data.lock);
+}
+
+module_exit(dcdrbu_exit);
+module_init(dcdrbu_init);
diff -uprN /usr/src/linux-2.6.11.11.orig/drivers/firmware/Kconfig /usr/src/linux-2.6.11.11/drivers/firmware/Kconfig
--- /usr/src/linux-2.6.11.11.orig/drivers/firmware/Kconfig	2005-06-14 21:00:10.000000000 -0500
+++ /usr/src/linux-2.6.11.11/drivers/firmware/Kconfig	2005-06-14 20:59:56.000000000 -0500
@@ -58,4 +58,15 @@ config EFI_PCDP
 
 	  See <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
 
+config DELL_RBU
+	tristate "BIOS update support for DELL systems via sysfs"
+	default y
+	select FW_LOADER
+	help
+	 Say Y if you want to have the option of updating the BIOS for your
+	 DELL system. Note you need a supporting application to comunicate
+	 with the BIOS regardin the new image for the image update to
+	 take effect.
+	See <file:Documentation/dell_rbu.txt> for more details on the driver.
+
 endmenu
diff -uprN /usr/src/linux-2.6.11.11.orig/drivers/firmware/Makefile /usr/src/linux-2.6.11.11/drivers/firmware/Makefile
--- /usr/src/linux-2.6.11.11.orig/drivers/firmware/Makefile	2005-06-14 21:01:11.000000000 -0500
+++ /usr/src/linux-2.6.11.11/drivers/firmware/Makefile	2005-06-14 21:00:47.000000000 -0500
@@ -4,3 +4,4 @@
 obj-$(CONFIG_EDD)             	+= edd.o
 obj-$(CONFIG_EFI_VARS)		+= efivars.o
 obj-$(CONFIG_EFI_PCDP)		+= pcdp.o
+obj-$(CONFIG_DELL_RBU)          += dell_rbu.o

^ permalink raw reply	[flat|nested] 28+ messages in thread
* [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-06-02 23:26 Abhay Salunke
  2005-06-02 23:58 ` Marcel Holtmann
  0 siblings, 1 reply; 28+ messages in thread
From: Abhay Salunke @ 2005-06-02 23:26 UTC (permalink / raw)
  To: linux-kernel, Andrew Morton; +Cc: abhay_salunke, matt_domsch, Greg KH, marcel

Resubmitting after cleaning up spaces/tabs etc...

By making a contribution to this project, I certify that:
The contribution was created in whole or in part by me and I have the
right to submit it under the open source license indicated in the file.
Resubmitting after cleaning up spaces/tabs etc...

Signed-off-by: Abhay Salunke <Abhay_Salunke@dell.com>

Thanks,
Abhay Salunke
Software Engineer.
DELL Inc

diff -uprN linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt linux-2.6.11.8/Documentation/DELL_RBU.txt
--- linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/Documentation/DELL_RBU.txt	2005-06-02 13:23:36.000000000 -0500
@@ -0,0 +1,70 @@
+Purpose:
+Demonstrate the usage of the DELL_RBU (DELL Remote BIOS Update) driver
+for updating BIOS images on Dell hardware.
+
+Scope:
+This document discusses the functionality of the DELL_RBU driver. 
+This driver is required by BIOS update applications shipped by DELL for updating
+BIOS on DELL servers and client systems. 
+
+Overview:
+The rbu driver is designed to be running on 2.6 kernel. 
+This driver is one single dell_rbu.c file (approx 800 lines total).
+This driver utilizes the hotplug interface for downloading the BIOS update image 
+in the contiguous or packetized memory depending upon the update type.
+The BIOS then scans the memory to find the image and will then update itself. 
+There are basically two different mechanisms for writing the BIOS image in to 
+contiguous memory 
+1> By writing the image to one monolithic chunk of contiguous physical memory.
+2> By writing image in to smaller packet chunks of contiguous physical memory.
+The update mechanism is determined by the update application based on the 
+particular system type.
+
+Update mechanism using single physical chunk of memory:
+The rbu driver on its load time created the following entries in sysfs
+/sys/firmware/dell_rbu/monolithic/mono_name
+/sys/firmware/dell_rbu/monolithic/mono_size
+/sys/firmware/dell_rbu/packetized/packet_name
+/sys/firmware/dell_rbu/packetized/packet_size
+
+Steps to update the BIOS image:
+
+1> Copy the image file in to /lib/firmware 
+2> echo the image name in to /sys/firmware/dell_rbu/xxxx/xxxx_name
+
+This will generate a hotplug event and the data form the image file is
+transferred to the memory. 
+Here xxxx stands for the type of BIOS update mechanism chosen by choosing
+monolitich the image is copied to contiguous physical pages and by choosing
+the mechanism as packetized the image is treated as one single packet and the
+packet size is set to the first packet image size. If a new image packet of
+different size form the previous is copied then all the previous packets are
+freed and this packet's size is treated as new packet size.
+
+On a driver unload all the allocated memory is freed.
+The user should not unload the driver after downloading the new BIOS image  
+if it wants to update BIOS with that image.
+The user can overwrite the previously loaded monolithich image by echoing a 
+new file name string to /sys/firmware/dell_rbu/monolithic/mono_name. Make sure 
+the file is present in /lib/firmware.  If the image size is more than
+previous image then the previous image is freed and the new alocation is made. 
+
+The user can know of a successful allocation by readind the size files.
+cat /sys/firmware/dell_rbu/monolithic/mono_size
+
+Update using smaller chunks (packets) of contiguous memory:
+The disadvantage of contiguous allocation is that it may not be always possible
+to get that size of contiuguous chunk of avaliable physical pages as in most 
+Linux systems the memory gets fragmented immideately after a reboot.
+The update using smaller chunks fixes this issue; it also requires the BIOS on 
+the system to support this feature; the update application needs to query this 
+with the BIOS on the system before using this technique. 
+
+
+NOTE:
+Afte updating the BIOS image the appplication needs to communicate with the BIOS 
+for enabling the update on the next reboot. The application can then choose to 
+reboot the system imideately or not reboot the system and leave up to the user 
+to do a reboot.
+
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c linux-2.6.11.8/drivers/firmware/dell_rbu.c
--- linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/drivers/firmware/dell_rbu.c	2005-06-02 18:18:24.035903088 -0500
@@ -0,0 +1,680 @@
+/*
+ * dell_rbu.c
+ * Bios Update driver for Dell systems
+ * Author: Dell Inc
+ *	   Abhay Salunke <abhay_salunke@dell.com>
+ *
+ * Copyright (C) 2005 Dell Inc.
+ *
+ * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by creating 
+ * entries in the /sys file systems on Linux 2.6 and higher kernels.
+ * It uses the hotplug interface for getting the image in to memory.
+ * The driver supports two mechanism to update the BIOS namely contiguous and 
+ * packetized. Both these methods still require having some application to set
+ * the CMOS bit indicating the BIOS to update itself after a reboot.
+ * In both the methods the image file name needs to be specified for hotplugging
+ * the image file. 
+ *
+ * Contiguous method:
+ * This driver writes the incoming data in a monolithic image by allocating 
+ * contiguous physical pages large enough to accommodate the incoming BIOS image 
+ * size.  
+ *
+ * Packetized method:
+ * The driver writes the incoming packet image by allocating a new packet on 
+ * every time the packet image name is written. 
+ * This driver requires an application to break the BIOS image in to fixed sized 
+ * packet chunks and each packet is written as a hotplug image.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * 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.
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/firmware.h>
+
+MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
+MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+#define BIOS_SCAN_LIMIT 0xffffffff
+#define MONOLITHIC (1)
+#define PACKETIZED (2)
+
+static struct _rbu_data {
+        void *image_update_buffer;
+        unsigned long image_update_buffer_size;
+        unsigned long bios_image_size;
+        unsigned long image_update_order_number;
+        spinlock_t lock;
+        unsigned long packet_read_count;
+        unsigned long packet_write_count;
+        unsigned long num_packets;
+        unsigned long packetsize;
+        char mono_image[256];
+        char packet_image[256];
+} rbu_data;
+
+struct packet_data{
+	struct list_head list;
+	size_t length;
+	void *data;
+	int ordernum;
+};
+
+static struct packet_data packet_data_head;
+
+static void init_packet_head(void)
+{
+	INIT_LIST_HEAD(&packet_data_head.list);
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+struct rbu_download_device {
+	int type;
+	struct kobject kobj;
+};
+
+static struct rbu_download_device *rbu_download_mono;
+
+static struct rbu_download_device *rbu_download_packet;
+
+static struct device rbu_device;
+
+static int fill_last_packet(void *data, size_t length)
+{
+	struct list_head *ptemp_list;
+	struct packet_data *ppacket = NULL;
+	int packet_count = 0;
+
+	pr_debug("fill_last_packet: entry \n");
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets) {
+		pr_debug("fill_last_packet: num_packets=0\n");
+		return -ENOMEM;
+	}
+
+	packet_count = rbu_data.num_packets;
+
+	ptemp_list = (&packet_data_head.list)->next;
+
+	while(--packet_count) {
+		ptemp_list = ptemp_list->next;
+	}
+
+	ppacket = list_entry(ptemp_list,struct packet_data, list);
+
+	if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {
+		printk(KERN_WARNING "fill_last_packet: packet size data "
+			"overrun\n");
+		return -ENOMEM;
+	}
+
+	pr_debug("fill_last_packet : buffer = %p\n", ppacket->data);
+
+	/* copy the incoming data in to the new buffer */
+	memcpy((ppacket->data + rbu_data.packet_write_count),
+			data, length);
+
+	if ((rbu_data.packet_write_count + length) == rbu_data.packetsize) {
+		/*
+		 this was the last data chunk in the packet
+		 so reinitialize the packet data counter to zero
+		*/
+		rbu_data.packet_write_count = 0;
+	} else
+		rbu_data.packet_write_count += length;
+	
+	pr_debug("fill_last_packet: exit \n");
+	return 0;
+}
+
+/*
+ get_free_pages_limited:
+ This is a helper function which allocates free pages based on an upper limit.
+ On x86_64 or 64 bit arch the memory allocation goes above 4GB space which is
+ not addressable by the BIOS. This function tries to get allocation below the
+ limit (4GB) address. It first tries to allocate memory normally using the
+ GFP_KERNEL argument if the incoming limit is non-zero and if the returned
+ physical memory address exceeds the upper limit, the allocated pages are freed
+ and the memory is reallocated using the GFP_DMA argument.
+*/
+static void *get_free_pages_limited(unsigned long size,
+                                    int *ordernum,
+                                    unsigned long limit)
+{
+	unsigned long img_buf_phys_addr;
+	void *pbuf = NULL;
+
+	*ordernum = get_order(size);
+	/*
+	 Check if we are not getting a very large file.
+	 This can happen as a user error in entering the file size
+	*/
+	if (*ordernum == BITS_PER_LONG) {
+		pr_debug("get_free_pages_limited: Incoming size is"
+			" very large\n");
+		return NULL;
+	}
+
+	/* try allocating a new buffer to fit the request */
+	pbuf =(unsigned char *)__get_free_pages(GFP_KERNEL, *ordernum);
+
+	if (pbuf != NULL) {
+		/* check if the image is with in limits */
+		img_buf_phys_addr = (unsigned long)virt_to_phys(pbuf);
+
+		if ((limit != 0) && ((img_buf_phys_addr + size) > limit)) {
+			pr_debug("Got memory above 4GB range, free this "
+				"and try with DMA memory\n");
+			/*
+			  free this memory as we need it with in 
+			 4GB range 
+			*/
+			free_pages ((unsigned long)pbuf, *ordernum);
+			/*
+			 Try allocating a new buffer from the 
+			 GFP_DMA range as it is with in 16MB range.
+			*/
+			pbuf =(unsigned char *)__get_free_pages(GFP_DMA,
+				*ordernum);
+			if (pbuf == NULL)
+				pr_debug("Failed to get memory "
+					"of size %ld "
+					"using GFP_DMA\n", size);
+		}
+	}
+	return pbuf;
+}
+
+static int create_packet(size_t length)
+{
+	struct packet_data *newpacket;
+	int ordernum = 0;
+
+	pr_debug("create_packet: entry \n");
+
+	if (rbu_data.packetsize == 0 ) {
+		pr_debug("create_packet: packetsize not specified\n");
+		return -EINVAL;
+	}
+
+	newpacket = kmalloc(sizeof(struct packet_data) ,GFP_KERNEL);
+	if(newpacket == NULL) {
+		printk(KERN_WARNING "create_packet: failed to allocate new "
+			"packet\n");
+		return -ENOMEM;
+	}
+
+	/* there is no upper limit on memory address for packetized mechanism*/
+	newpacket->data = get_free_pages_limited(rbu_data.packetsize,
+							&ordernum, 0);
+	pr_debug("create_packet: newpacket %p\n", newpacket->data);
+
+	if(newpacket->data == NULL) {
+		printk(KERN_WARNING "create_packet: failed to allocate new "
+			"packet\n");
+		kfree(newpacket);
+		return -ENOMEM;
+	}
+
+	newpacket->ordernum = ordernum;
+	++rbu_data.num_packets;
+	/* initialize the newly created packet headers */
+	INIT_LIST_HEAD(&newpacket->list);
+	list_add_tail(&newpacket->list, &packet_data_head.list);
+	/* packets have fixed size*/
+	newpacket->length = rbu_data.packetsize;
+
+	pr_debug("create_packet: exit \n");
+
+	return 0;
+}
+
+static int packetize_data(void *data, size_t length)
+{
+	int rc = 0;
+
+	if (rbu_data.packet_write_count == 0) {
+		if ((rc = create_packet(length)) != 0 )
+			return rc;
+	}
+	/* fill data in to the packet */
+	if ((rc = fill_last_packet(data, length)) != 0)
+		return rc;
+
+	return rc;
+}
+
+static void packet_empty_list(void)
+{
+	struct list_head *ptemp_list;
+	struct list_head *pnext_list;
+	struct packet_data *newpacket;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while(!list_empty(ptemp_list)) {
+		newpacket = list_entry(ptemp_list, struct packet_data, list);
+		pnext_list = ptemp_list->next;
+		list_del(ptemp_list);
+		ptemp_list = pnext_list;
+		/*
+		 zero out the RBU packet memory before freeing to make sure
+		 there are no stale RBU packets left in memory
+		*/
+		memset(newpacket->data, 0, rbu_data.packetsize);
+		free_pages((unsigned long)newpacket->data, newpacket->ordernum);
+		kfree(newpacket);
+	}
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+/*
+ img_update_free:
+ Frees the buffer allocated for storing BIOS image
+ Always called with lock held and returned with lock held
+*/
+static void img_update_free( void)
+{
+	if (rbu_data.image_update_buffer == NULL)
+		return;
+	/*
+	 zero out this buffer before freeing it to get rid of any stale
+	 BIOS image copied in memory.
+	*/
+	memset(rbu_data.image_update_buffer, 0,
+		rbu_data.image_update_buffer_size);
+	free_pages((unsigned long)rbu_data.image_update_buffer,
+		rbu_data.image_update_order_number);
+	/* Re-initialize the rbu_data variables after a free */
+	rbu_data.image_update_buffer = NULL;
+	rbu_data.image_update_buffer_size = 0;
+	rbu_data.bios_image_size = 0;
+}
+
+/*
+ img_update_realloc:
+ This function allocates the contiguous pages to accommodate the requested
+ size of data. The memory address and size values are stored globally and
+ on every call to this function the new size is checked to see if more
+ data is required than the existing size. If true the previous memory is
+ freed and new allocation is done to accommodate the new size. If the 
+ incoming size is less then than the already allocated size, then that
+ memory is reused.
+ This function is called with lock held and returns with lock held.
+*/
+static int img_update_realloc(unsigned long size)
+{
+	unsigned char *image_update_buffer = NULL;
+	unsigned long rc;
+	int ordernum =0;
+
+	/* check if the buffer of sufficient size has been already allocated */
+	if (rbu_data.image_update_buffer_size >= size) {
+		/* check for corruption */
+		if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
+			printk(KERN_ERR "img_update_realloc: "
+				"corruption check failed\n");
+			return -EINVAL;
+		}
+		/* we have a valid pre-allocated buffer with sufficient size */
+		return 0;
+	}
+
+	/* free any previously allocated buffer */
+	img_update_free();
+	
+	/*
+	 This has already been called as locked so we can now unlock
+	 and proceed to calling get_free_pages_limited as this function
+	 can sleep
+	*/
+	spin_unlock(&rbu_data.lock);
+
+	image_update_buffer = (unsigned char *)get_free_pages_limited(size,
+		&ordernum,
+		BIOS_SCAN_LIMIT);
+
+	/* acquire the spinlock again */
+	spin_lock(&rbu_data.lock);
+
+	if (image_update_buffer != NULL) {
+		rbu_data.image_update_buffer = image_update_buffer;
+		rbu_data.image_update_buffer_size = PAGE_SIZE << ordernum;
+		rbu_data.image_update_order_number = ordernum;
+		memset(rbu_data.image_update_buffer,0,
+			rbu_data.image_update_buffer_size);
+		rc = 0;
+	} else {
+		pr_debug("Not enough memory for image update:order"
+			" number = %d,size = %ld\n",ordernum, size);
+		rc = -ENOMEM;
+	}
+
+	return rc;
+} /* img_update_realloc */
+
+/*
+ rbu_store_image_size:
+ This is primarily for cancelling any RBU updates.
+*/
+static ssize_t rbu_store_image_size (struct rbu_download_device *rbu_dev,
+		const char *buf, size_t count, int type)
+{
+	int size = 0;
+	
+	sscanf(buf, "%d",&size);	
+	if (size != 0)
+		return -EINVAL;
+
+	spin_lock(&rbu_data.lock);
+	if (type == MONOLITHIC )
+		img_update_free();
+	else 
+		packet_empty_list();
+
+	spin_unlock(&rbu_data.lock);
+	return count; 
+}
+
+static ssize_t rbu_show_image_size (struct rbu_download_device *rbu_dev, 
+		char *buf, int type)
+{
+	unsigned int size = 0;
+	if ( type == MONOLITHIC )
+		size = sprintf(buf, "%lu\n",  rbu_data.bios_image_size);
+	else
+		size = sprintf(buf, "%lu\n", rbu_data.packetsize);
+	return size;
+}
+
+static ssize_t rbu_show_image_name (struct rbu_download_device *rbu_dev, 
+		char *buf, int type)
+{
+	unsigned int size = 0;
+	char *image;
+        
+	if (type == MONOLITHIC ) 
+		image = rbu_data.mono_image;
+	else
+		image = rbu_data.packet_image;
+	size = sprintf(buf, "%s\n", image);
+	return size;
+}
+
+static ssize_t rbu_store_image_name (struct rbu_download_device *rbu_dev, 
+		const char *buf, size_t count, int type)
+{
+	int rc = count;
+	const struct firmware *fw_entry;
+	char *image_name = NULL;
+
+	spin_lock(&rbu_data.lock);
+
+	if (strlen(buf) >= 256 ) {
+		spin_unlock(&rbu_data.lock);
+		rc = -ENOMEM;
+	}
+
+	if (type == MONOLITHIC ) {
+		sscanf(buf, "%s",rbu_data.mono_image);
+		image_name = rbu_data.mono_image;
+	} else {
+		sscanf(buf, "%s",rbu_data.packet_image);
+		image_name = rbu_data.packet_image;
+	}
+
+	spin_unlock(&rbu_data.lock);	
+
+	rc = request_firmware(&fw_entry, image_name,
+		&rbu_device);
+	if (rc) {
+		printk(KERN_ERR "rbu_store_image_name: " 
+			"Firmware not available %d\n", rc);
+		return rc;
+	}
+		
+	pr_debug("rbu_store_image_name: request_firmware is successful "
+		"fw->size = %lu\n", fw_entry->size);
+
+	spin_lock(&rbu_data.lock);
+	if (type == MONOLITHIC ) {
+		rbu_data.bios_image_size = fw_entry->size;
+		if (fw_entry->size == 0 )
+			img_update_free();
+		else {	
+			rc = img_update_realloc(fw_entry->size);
+				if (rc == 0) {
+					memcpy(rbu_data.image_update_buffer, 
+						fw_entry->data, 
+						fw_entry->size);
+					rc = count;
+				}
+			}
+	} else {
+		/* 
+		  if a new packet is entered free all previsou 
+		  packets and start over.
+		 */
+		if ( rbu_data.packetsize != fw_entry->size )
+			packet_empty_list();
+
+		rbu_data.packetsize = fw_entry->size;
+		rc = packetize_data(fw_entry->data, fw_entry->size);
+		if ( rc == 0 )
+			rc = count;
+	}
+	spin_unlock(&rbu_data.lock);
+	release_firmware(fw_entry);
+
+	return rc;
+}
+
+/* no default attributes yet. */
+static struct attribute * def_attrs[] = { NULL, };
+
+struct rbu_attribute {
+	struct attribute attr;
+	ssize_t (*show) (struct rbu_download_device *rbu_dev,
+			char *buf, int type);
+	ssize_t (*store)(struct rbu_download_device *rbu_dev, 
+			const char *buf , size_t count, int type);
+	int type;
+};
+
+#define RBU_DEVICE_ATTR(_name,_mode,_show,_store, _type) \
+struct rbu_attribute rbu_attr_##_name = {       \
+	.attr ={.name= __stringify(_name), .mode= _mode, .owner= THIS_MODULE},\
+	.show = _show,                                \
+	.store = _store,                                \
+	.type = _type,	 \
+};
+
+#define to_rbu_attr(_attr) container_of(_attr,struct rbu_attribute,attr)
+#define to_rbu_download_device(obj)  \
+	container_of(obj,struct rbu_download_device,kobj)
+
+
+static RBU_DEVICE_ATTR(mono_name, 0644, rbu_show_image_name, 
+			rbu_store_image_name, MONOLITHIC);
+
+static RBU_DEVICE_ATTR(mono_size, 0644, rbu_show_image_size, 
+			rbu_store_image_size, MONOLITHIC);
+
+static RBU_DEVICE_ATTR(packet_name, 0644, rbu_show_image_name,
+			rbu_store_image_name, PACKETIZED);
+
+static RBU_DEVICE_ATTR(packet_size, 0644, rbu_show_image_size,
+			rbu_store_image_size, PACKETIZED);
+
+static  ssize_t rbu_attr_store (struct kobject *kobj,
+				struct attribute *attr,
+				const char *buf, 
+				size_t count) 
+{
+	struct rbu_download_device *rbu_dev = to_rbu_download_device(kobj);
+	struct rbu_attribute *rbu_attr = to_rbu_attr(attr);
+	ssize_t rc = count;
+
+	pr_debug("rbu_attr_store: entry type = %d\n", rbu_attr->type);
+	
+	if (rbu_attr->store)
+		rc = rbu_attr->store(rbu_dev, buf, count, rbu_attr->type);
+	return rc;
+}
+
+static ssize_t rbu_attr_show (struct kobject * kobj, 
+			      struct attribute *attr, 
+			      char *buf)
+{
+	struct rbu_download_device *rbu_dev = to_rbu_download_device(kobj);
+	struct rbu_attribute *rbu_attr = to_rbu_attr(attr);
+	ssize_t rc = 0;
+	pr_debug("rbu_attr_show: entry type = %d\n", rbu_attr->type);
+	if (rbu_attr->show)
+		rc = rbu_attr->show(rbu_dev, buf, rbu_attr->type);
+	return rc;
+}
+
+static struct sysfs_ops rbu_attr_ops = { 
+	.show = rbu_attr_show,
+	.store = rbu_attr_store,
+};
+
+static struct kobj_type ktype_dell_rbu = { 
+	.sysfs_ops	= &rbu_attr_ops,
+	.default_attrs	= def_attrs,
+};
+static decl_subsys(dell_rbu,&ktype_dell_rbu,NULL);
+
+static int rbu_download_device_register(struct rbu_download_device *rbu_dev, 
+					int type)
+{
+	int rc = 0;
+	if (!rbu_dev)
+		return 1;
+        memset(rbu_dev, 0, sizeof (*rbu_dev));
+	if (type == MONOLITHIC)
+		kobject_set_name(&rbu_dev->kobj, "monolithic");
+	else 
+		kobject_set_name(&rbu_dev->kobj, "packetized");
+	kobj_set_kset_s(rbu_dev,dell_rbu_subsys);
+	rc = kobject_register(&rbu_dev->kobj);
+	if (!rc) {
+		if (type == MONOLITHIC ) {
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_mono_name.attr);
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_mono_size.attr);
+		} else {
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_packet_name.attr);
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_packet_size.attr);
+		}
+	} else 
+		pr_debug("rbu_download_device_register: "
+			"kobject_register %d \n", rc);
+	pr_debug("rbu_download_device_register: rbu_dev addr %p\n", rbu_dev);
+	return rc;
+}
+
+static struct rbu_download_device *create_rbu_download_entry(int type) 
+{
+	struct rbu_download_device *rbu_dev = NULL;
+	rbu_dev = kmalloc( sizeof(struct rbu_download_device), 
+		GFP_KERNEL);
+	if (!rbu_dev) {
+		printk(KERN_ERR "create_rbu_download_entry: kmalloc failed\n");
+		return NULL;
+	}
+	rbu_dev->type = type;
+	if (rbu_download_device_register(rbu_dev, type)) {
+		pr_debug("create_rbu_download_entry: "
+			"rbu_download_device_register failed \n");
+		kfree(rbu_dev);
+        }
+
+	pr_debug("create_rbu_download_entry: rbu_dev %p\n", rbu_dev);	
+	return rbu_dev;
+}
+
+static void remove_rbu_download_entry(struct rbu_download_device *rbu_dev, 
+				       int type) 
+{
+	pr_debug("remove_rbu_download_entry: rbu_dev ptr %p \n", rbu_dev);	
+	if (rbu_dev != NULL) {
+		kobject_unregister(&rbu_dev->kobj);
+		kfree(rbu_dev);
+	}
+}
+
+static int __init dcdrbu_init(void)
+{
+	int rc = 0;
+	spin_lock_init(&rbu_data.lock);
+	
+	init_packet_head();
+	
+	device_initialize(&rbu_device);
+
+	rc = firmware_register(&dell_rbu_subsys);
+	if (rc < 0) {
+		printk(KERN_WARNING "dcdrbu_init: firmware_register"
+			" dell_rbu failed\n");
+		return rc;
+        }
+	
+	rbu_download_mono = create_rbu_download_entry (MONOLITHIC);
+	if (rbu_download_mono == NULL) {	
+		firmware_unregister(&dell_rbu_subsys);
+		return -ENOMEM;
+	}
+	
+	rbu_download_packet=  create_rbu_download_entry (PACKETIZED);
+	if (rbu_download_packet == NULL) {	
+		remove_rbu_download_entry(rbu_download_mono, MONOLITHIC);
+		firmware_unregister(&dell_rbu_subsys);
+		return -ENOMEM;
+	}
+	strncpy(rbu_device.bus_id,"firmware", BUS_ID_SIZE);
+	return rc;
+}
+
+static __exit void dcdrbu_exit( void)
+{
+	spin_lock(&rbu_data.lock);
+	packet_empty_list();
+	img_update_free();
+	spin_unlock(&rbu_data.lock);
+	remove_rbu_download_entry(rbu_download_packet, PACKETIZED);
+	remove_rbu_download_entry(rbu_download_mono, MONOLITHIC);
+	firmware_unregister(&dell_rbu_subsys);
+}
+
+module_exit(dcdrbu_exit);
+module_init(dcdrbu_init);
+
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Kconfig linux-2.6.11.8/drivers/firmware/Kconfig
--- linux-2.6.11.8.ORIG/drivers/firmware/Kconfig	2005-05-13 12:07:58.000000000 -0500
+++ linux-2.6.11.8/drivers/firmware/Kconfig	2005-06-02 17:01:29.072484168 -0500
@@ -58,4 +58,17 @@ config EFI_PCDP
 
 	  See <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
 
+config DELL_RBU
+        tristate "BIOS update support for DELL systems via sysfs"
+        default n
+	select FW_LOADER
+        help
+          Say Y if you want to have the option of updating the BIOS for your
+	  DELL system. Note you need a supporting application to comunicate 
+	  with the BIOS regardin the new image for the image update to 
+	  take effect.
+
+	  See <file:Documentation/DELL_RBU.txt> for more details on the driver.
+
+
 endmenu
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Makefile linux-2.6.11.8/drivers/firmware/Makefile
--- linux-2.6.11.8.ORIG/drivers/firmware/Makefile	2005-05-13 12:08:12.000000000 -0500
+++ linux-2.6.11.8/drivers/firmware/Makefile	2005-05-09 15:15:16.000000000 -0500
@@ -4,3 +4,4 @@
 obj-$(CONFIG_EDD)             	+= edd.o
 obj-$(CONFIG_EFI_VARS)		+= efivars.o
 obj-$(CONFIG_EFI_PCDP)		+= pcdp.o
+obj-$(CONFIG_DELL_RBU)		+= dell_rbu.o

^ permalink raw reply	[flat|nested] 28+ messages in thread
* [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-06-02 18:36 Abhay Salunke
  2005-06-02 21:44 ` Marcel Holtmann
  0 siblings, 1 reply; 28+ messages in thread
From: Abhay Salunke @ 2005-06-02 18:36 UTC (permalink / raw)
  To: linux-kernel, Andrew Morton; +Cc: abhay_salunke, matt_domsch, Greg KH

This is a resubmit of the patch after incorporating all the inputs from revieweres. 
This has the hotplug firmware interface as suggested by many. 
Currently it does not suport reading back the data; I am workingon it and will add 
that feature as new patch.

By making a contribution to this project, I certify that:
The contribution was created in whole or in part by me and I have the 
right to submit it under the open source license indicated in the file.

Signed-off-by: Abhay Salunke <Abhay_Salunke@dell.com>

Thanks,
Abhay Salunke
Software Engineer.
DELL Inc

diff -uprN linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt linux-2.6.11.8/Documentation/DELL_RBU.txt
--- linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/Documentation/DELL_RBU.txt	2005-06-02 13:23:36.358562176 -0500
@@ -0,0 +1,70 @@
+Purpose:
+Demonstrate the usage of the DELL_RBU (DELL Remote BIOS Update) driver
+for updating BIOS images on Dell hardware.
+
+Scope:
+This document discusses the functionality of the DELL_RBU driver. 
+This driver is required by BIOS update applications shipped by DELL for updating
+BIOS on DELL servers and client systems. 
+
+Overview:
+The rbu driver is designed to be running on 2.6 kernel. 
+This driver is one single dell_rbu.c file (approx 800 lines total).
+This driver utilizes the hotplug interface for downloading the BIOS update image 
+in the contiguous or packetized memory depending upon the update type.
+The BIOS then scans the memory to find the image and will then update itself. 
+There are basically two different mechanisms for writing the BIOS image in to 
+contiguous memory 
+1> By writing the image to one monolithic chunk of contiguous physical memory.
+2> By writing image in to smaller packet chunks of contiguous physical memory.
+The update mechanism is determined by the update application based on the 
+particular system type.
+
+Update mechanism using single physical chunk of memory:
+The rbu driver on its load time created the following entries in sysfs
+/sys/firmware/dell_rbu/monolithic/mono_name
+/sys/firmware/dell_rbu/monolithic/mono_size
+/sys/firmware/dell_rbu/packetized/packet_name
+/sys/firmware/dell_rbu/packetized/packet_size
+
+Steps to update the BIOS image:
+
+1> Copy the image file in to /lib/firmware 
+2> echo the image name in to /sys/firmware/dell_rbu/xxxx/xxxx_name
+
+This will generate a hotplug event and the data form the image file is
+transferred to the memory. 
+Here xxxx stands for the type of BIOS update mechanism chosen by choosing
+monolitich the image is copied to contiguous physical pages and by choosing
+the mechanism as packetized the image is treated as one single packet and the
+packet size is set to the first packet image size. If a new image packet of
+different size form the previous is copied then all the previous packets are
+freed and this packet's size is treated as new packet size.
+
+On a driver unload all the allocated memory is freed.
+The user should not unload the driver after downloading the new BIOS image  
+if it wants to update BIOS with that image.
+The user can overwrite the previously loaded monolithich image by echoing a 
+new file name string to /sys/firmware/dell_rbu/monolithic/mono_name. Make sure 
+the file is present in /lib/firmware.  If the image size is more than
+previous image then the previous image is freed and the new alocation is made. 
+
+The user can know of a successful allocation by readind the size files.
+cat /sys/firmware/dell_rbu/monolithic/mono_size
+
+Update using smaller chunks (packets) of contiguous memory:
+The disadvantage of contiguous allocation is that it may not be always possible
+to get that size of contiuguous chunk of avaliable physical pages as in most 
+Linux systems the memory gets fragmented immideately after a reboot.
+The update using smaller chunks fixes this issue; it also requires the BIOS on 
+the system to support this feature; the update application needs to query this 
+with the BIOS on the system before using this technique. 
+
+
+NOTE:
+Afte updating the BIOS image the appplication needs to communicate with the BIOS 
+for enabling the update on the next reboot. The application can then choose to 
+reboot the system imideately or not reboot the system and leave up to the user 
+to do a reboot.
+
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c linux-2.6.11.8/drivers/firmware/dell_rbu.c
--- linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/drivers/firmware/dell_rbu.c	2005-06-02 13:28:48.821060656 -0500
@@ -0,0 +1,700 @@
+/*
+ * dell_rbu.c
+ * Bios Update driver for Dell systems
+ * Author: Dell Inc
+ *	   Abhay Salunke <abhay_salunke@dell.com>
+ *
+ * Copyright (C) 2005 Dell Inc.
+ *
+ * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by creating 
+ * entries in the /sys file systems on Linux 2.6 and higher kernels.
+ * It uses the hotplug interface for getting the image in to memory.
+ * The driver supports two mechanism to update the BIOS namely contiguous and 
+ * packetized. Both these methods still require to have some application to set
+ * the  CMOS bit indicating the BIOS to update itself after a reboot.
+ * In both the methods the image file name needs to be specified for hotplugging
+ * the image file. 
+ *
+ * Contiguous method:
+ * This driver writes the incmoing data in a monolithic image by allocating 
+ * contiguos physical pages large enough to accomodate the incoming BIOS image 
+ * size.  
+ *
+ * Packetized method:
+ * The driver writes the incoming packet image by allocating a new packet on 
+ * every time the packet image name is written. 
+ * This driver requires an application to break the BIOS image in to fixed sized 
+ * packet chunks and each packet is written as a hotplug image.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * 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.
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include "linux/firmware.h"
+#define BIOS_SCAN_LIMIT 0xffffffff
+MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
+MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+#define MONOLITHIC (1)
+#define PACKETIZED (10)
+static struct _rbu_data {
+        void *image_update_buffer;
+        unsigned long image_update_buffer_size;
+        unsigned long bios_image_size;
+        unsigned long image_update_order_number;
+        spinlock_t lock;
+        unsigned long packet_read_count;
+        unsigned long packet_write_count;
+        unsigned long num_packets;
+        unsigned long packetsize;
+        char mono_image[256];
+        char packet_image[256];
+} rbu_data;
+
+struct packet_data{
+	struct list_head list;
+	size_t length;
+	void *data;
+	int ordernum;
+};
+
+static struct packet_data packet_data_head;
+
+
+static void init_packet_head(void)
+{
+	INIT_LIST_HEAD(&packet_data_head.list);
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+struct rbu_download_device {
+	int type;
+	struct kobject kobj;
+};
+
+static struct rbu_download_device *rbu_download_mono;
+
+static struct rbu_download_device *rbu_download_packet;
+
+static struct device rbu_device;
+
+static int fill_last_packet(void *data, size_t length)
+{
+        struct list_head *ptemp_list;
+        struct packet_data *ppacket = NULL;
+        int packet_count = 0;
+
+        pr_debug("fill_last_packet: entry \n");
+
+        /* check if we have any packets */
+        if (0 == rbu_data.num_packets) {
+                pr_debug("fill_last_packet: num_packets=0\n");
+                return -ENOMEM;
+        }
+
+        packet_count = rbu_data.num_packets;
+
+        ptemp_list = (&packet_data_head.list)->next;
+
+        while(--packet_count) {
+                ptemp_list = ptemp_list->next;
+        }
+
+        ppacket = list_entry(ptemp_list,struct packet_data, list);
+
+        if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {
+                printk(KERN_WARNING "fill_last_packet: packet size data "
+                                "overrun\n");
+                return -ENOMEM;
+        }
+
+        pr_debug("fill_last_packet : buffer = %p\n", ppacket->data);
+
+        /* copy the incoming data in to the new buffer */
+        memcpy((ppacket->data + rbu_data.packet_write_count),
+                data, length);
+
+        if ((rbu_data.packet_write_count + length) == rbu_data.packetsize) {
+                /*
+                 this was the last data chunk in the packet
+                 so reinitialize the packet data counter to zero
+                */
+                rbu_data.packet_write_count = 0;
+        } else {
+                rbu_data.packet_write_count += length;
+        }
+        pr_debug("fill_last_packet: exit \n");
+        return 0;
+}
+
+/*
+ get_free_pages_limited:
+ This is a helper function which allocates free pages based on an upper limit.
+ On x86_64 or 64 bit arch the memory allocation goes above 4GB space which is
+ not addressable by the BIOS. This function tries to get allocation below the
+ limit (4GB) address. It first tries to allocate memory normally using the
+ GFP_KERNEL argument if the incoming limit is non-zero and if the returned
+ physical memory address exceeds the upper limit, the allocated pages are freed
+ and the memory is reallocated using the GFP_DMA argument.
+*/
+static void *get_free_pages_limited(unsigned long size,
+                                    int *ordernum,
+                                    unsigned long limit)
+{
+        unsigned long img_buf_phys_addr;
+        void *pbuf = NULL;
+
+        *ordernum = get_order(size);
+        /*
+         Check if we are not getting a very large file.
+         This can happen as a user error in entering the file size
+        */
+        if (*ordernum == BITS_PER_LONG) {
+                pr_debug("get_free_pages_limited: Incoming size is"
+                        " very large\n");
+                return NULL;
+        }
+
+        /* try allocating a new buffer to fit the request */
+        pbuf =(unsigned char *)__get_free_pages(GFP_KERNEL, *ordernum);
+
+        if (pbuf != NULL) {
+                /* check if the image is with in limits */
+                img_buf_phys_addr = (unsigned long)virt_to_phys(pbuf);
+
+                if ((limit != 0) && ((img_buf_phys_addr + size) > limit)) {
+                        pr_debug("Got memory above 4GB range, free this "
+                                "and try with DMA memory\n");
+                        /* free this memory as we need it with in 4GB range */
+                        free_pages ((unsigned long)pbuf, *ordernum);
+                        /*
+                         Try allocating a new buffer from the GFP_DMA range
+                         as it is with in 16MB range.
+                        */
+                        pbuf =(unsigned char *)__get_free_pages(GFP_DMA,
+                                                *ordernum);
+                        if (pbuf == NULL)
+                                pr_debug("Failed to get memory of size %ld "
+                                        "using GFP_DMA\n", size);
+                }
+        }
+        return pbuf;
+}
+
+static int create_packet(size_t length)
+{
+        struct packet_data *newpacket;
+        int ordernum = 0;
+
+        pr_debug("create_packet: entry \n");
+
+        if (rbu_data.packetsize == 0 ) {
+                pr_debug("create_packet: packetsize not specified\n");
+                return -EINVAL;
+        }
+
+        newpacket = kmalloc(sizeof(struct packet_data) ,GFP_KERNEL);
+        if(newpacket == NULL) {
+                printk(KERN_WARNING "create_packet: failed to allocate new "
+                        "packet\n");
+                return -ENOMEM;
+        }
+
+        /* there is no upper limit on memory address for packetized mechanism*/
+        newpacket->data = get_free_pages_limited(rbu_data.packetsize,
+                                &ordernum, 0);
+        pr_debug("create_packet: newpacket %p\n", newpacket->data);
+
+        if(newpacket->data == NULL) {
+                printk(KERN_WARNING "create_packet: failed to allocate new "
+                        "packet\n");
+                kfree(newpacket);
+                return -ENOMEM;
+        }
+
+        newpacket->ordernum = ordernum;
+        ++rbu_data.num_packets;
+        /* initialize the newly created packet headers */
+        INIT_LIST_HEAD(&newpacket->list);
+        list_add_tail(&newpacket->list, &packet_data_head.list);
+        /* packets have fixed size*/
+        newpacket->length = rbu_data.packetsize;
+
+        pr_debug("create_packet: exit \n");
+
+        return 0;
+}
+
+static int packetize_data(void *data, size_t length)
+{
+        int rc = 0;
+
+        if (rbu_data.packet_write_count == 0) {
+                if ((rc = create_packet(length)) != 0 )
+                        return rc;
+        }
+        /* fill data in to the packet */
+        if ((rc = fill_last_packet(data, length)) != 0)
+                return rc;
+
+        return rc;
+}
+
+/*
+ do_packet_read :
+ This is a helper function which reads the packet data of the
+ current list.
+ data: is the incoming buffer
+ ptemp_list: points to the incoming list item
+ length: is the length of the free space in the buffer.
+ bytes_read: is the total number of bytes read already from
+ the packet list
+ list_read_count: is the counter to keep track of the number
+ of bytes read out of each packet.
+*/
+int do_packet_read(char *data,
+                   struct list_head *ptemp_list,
+                   int length,
+                   int bytes_read,
+                   int *list_read_count)
+{
+        void *ptemp_buf;
+        struct packet_data *newpacket = NULL;
+        int bytes_copied = 0;
+        int j = 0;
+
+        newpacket = list_entry(ptemp_list,struct packet_data, list);
+        *list_read_count += newpacket->length;
+
+        if (*list_read_count > bytes_read) {
+                /* point to the start of unread data */
+                j = newpacket->length - (*list_read_count - bytes_read);
+                /* point to the offset in the packet buffer*/
+                ptemp_buf = (u8 *)newpacket->data + j;
+                /* check if there is enough room in the incoming buffer*/
+                if (length > (*list_read_count - bytes_read))
+                        /* copy what ever is there in this packet and move on*/
+                        bytes_copied = (*list_read_count - bytes_read);
+                else
+                        /* copy the remaining */
+                        bytes_copied = length;
+                memcpy(data, ptemp_buf, bytes_copied);
+        }
+        return bytes_copied;
+}
+
+static void packet_empty_list(void)
+{
+        struct list_head *ptemp_list;
+        struct list_head *pnext_list;
+        struct packet_data *newpacket;
+
+        ptemp_list = (&packet_data_head.list)->next;
+        while(!list_empty(ptemp_list)) {
+                newpacket = list_entry(ptemp_list, struct packet_data, list);
+                pnext_list = ptemp_list->next;
+                list_del(ptemp_list);
+                ptemp_list = pnext_list;
+                /*
+                 zero out the RBU packet memory before freeing to make sure
+                 there are no stale RBU packets left in memory
+                */
+                memset(newpacket->data, 0, rbu_data.packetsize);
+                free_pages((unsigned long)newpacket->data, newpacket->ordernum);
+                kfree(newpacket);
+        }
+        rbu_data.packet_write_count = 0;
+        rbu_data.packet_read_count = 0;
+        rbu_data.num_packets = 0;
+}
+
+/*
+ img_update_free:
+ Frees the buffer allocated for storing BIOS image
+ Always called with lock held and returned with lock held
+*/
+static void img_update_free( void)
+{
+        if (rbu_data.image_update_buffer == NULL)
+                return;
+
+        /*
+         zero out this buffer before freeing it to get rid of any stale
+         BIOS image copied in memory.
+        */
+        memset(rbu_data.image_update_buffer, 0,
+                rbu_data.image_update_buffer_size);
+        free_pages((unsigned long)rbu_data.image_update_buffer,
+                rbu_data.image_update_order_number);
+        /* Re-initialize the rbu_data variables after a free */
+        rbu_data.image_update_buffer = NULL;
+        rbu_data.image_update_buffer_size = 0;
+        rbu_data.bios_image_size = 0;
+}
+
+/*
+ img_update_realloc:
+ This function allocates the contiguous pages to accomodate the requested
+ size of data. The memory address and size values are stored globally and
+ on every call to this function the new size is checked to see if more
+ data is required than the existing size. If true the previous memory is freed
+ and new allocation is done to accomodate the new size. If the incoming size is
+ less then than the already allocated size, then that  memory is reused.
+ This function is called with lock held and returna with lock held.
+*/
+static int img_update_realloc(unsigned long size)
+{
+        unsigned char *image_update_buffer = NULL;
+        unsigned long rc;
+        int ordernum =0;
+
+        /* check if the buffer of sufficient size has been already allocated */
+	if (rbu_data.image_update_buffer_size >= size) {
+        	/* check for corruption */
+                if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
+                        printk(KERN_ERR "img_update_realloc: corruption check "
+                                "failed\n");
+                        return -EINVAL;
+                }
+                /* we have a valid pre-allocated buffer with sufficient size */
+                return 0;
+    	}
+
+        /* free any previously allocated buffer */
+        img_update_free();
+
+        /*
+         This has already been called as locked so we can now unlock
+         and proceed to calling get_free_pages_limited as this function
+         can sleep
+        */
+        spin_unlock(&rbu_data.lock);
+
+        image_update_buffer = (unsigned char *)get_free_pages_limited(size,
+                &ordernum,
+                BIOS_SCAN_LIMIT);
+
+        /* acquire the spinlock again */
+        spin_lock(&rbu_data.lock);
+
+        if (image_update_buffer != NULL) {
+                rbu_data.image_update_buffer = image_update_buffer;
+                rbu_data.image_update_buffer_size = PAGE_SIZE << ordernum;
+                rbu_data.image_update_order_number = ordernum;
+                memset(rbu_data.image_update_buffer,0,
+                        rbu_data.image_update_buffer_size);
+                rc = 0;
+        } else {
+                pr_debug("Not enough memory for image update:order number = %d"
+                        ",size = %ld\n",ordernum, size);
+                rc = -ENOMEM;
+        }
+
+        return rc;
+} /* img_update_realloc */
+
+static ssize_t dummy_store (struct rbu_download_device *rbu_dev,
+                     const char *buf, size_t count, int type)
+{
+	/* do nothing */
+	return count; 
+}
+
+static ssize_t rbu_show_image_size (struct rbu_download_device *rbu_dev, 
+				    char *buf, int type)
+{
+        unsigned int size = 0;
+	if ( type == MONOLITHIC )
+		size = sprintf(buf, "%lu\n",  rbu_data.bios_image_size);
+	else
+		size = sprintf(buf, "%lu\n", rbu_data.packetsize);
+        return size;
+}
+
+static ssize_t rbu_show_image_name (struct rbu_download_device *rbu_dev, 
+				    char *buf, int type)
+{
+        unsigned int size = 0;
+        char *image;
+        
+	if (type == MONOLITHIC ) 
+		image = rbu_data.mono_image;
+	else
+		image = rbu_data.packet_image;
+
+	size = sprintf(buf, "%s\n", image);
+        return size;
+}
+
+static ssize_t rbu_store_image_name (struct rbu_download_device *rbu_dev, 
+				     const char *buf, size_t count, int type)
+{
+        int rc = count;
+        const struct firmware *fw_entry;
+	char *image_name = NULL;
+
+        spin_lock(&rbu_data.lock);
+
+        if (strlen(buf) < 256 ) {
+		if (type == MONOLITHIC ){
+                	sscanf(buf, "%s",rbu_data.mono_image);
+			image_name = rbu_data.mono_image;
+		} else {
+                	sscanf(buf, "%s",rbu_data.packet_image);
+			image_name = rbu_data.packet_image;
+		}
+
+		spin_unlock(&rbu_data.lock);	
+
+                rc = request_firmware(&fw_entry, image_name,
+                                &rbu_device);
+		if (rc) {
+                        printk(KERN_ERR "rbu_store_image_name: " 
+				"Firmware not available %d\n", rc);
+			return rc;
+		}
+			
+		pr_debug("rbu_store_image_name: "
+			"request_firmware is successful  "
+			"fw->size = %lu\n", fw_entry->size);
+
+		spin_lock(&rbu_data.lock);
+		if (type == MONOLITHIC ){
+			rbu_data.bios_image_size = fw_entry->size;
+			rc = img_update_realloc(fw_entry->size);
+			if (rc == 0) {
+				memcpy(rbu_data.image_update_buffer, 
+					fw_entry->data, fw_entry->size);
+				rc = count;
+			}
+		} else {
+			/* if a new packet is entered free all previsou 
+			   packets and start over.
+			 */
+			if ( rbu_data.packetsize != fw_entry->size )
+				packet_empty_list();
+
+			rbu_data.packetsize = fw_entry->size;
+			rc = packetize_data(fw_entry->data, fw_entry->size);
+			if ( rc == 0 )
+				rc = count;
+		}
+		spin_unlock(&rbu_data.lock);
+		release_firmware(fw_entry);
+        } else {
+		spin_unlock(&rbu_data.lock);
+		rc = -ENOMEM;
+	}
+
+        return rc;
+}
+
+/* no default attributes yet. */
+static struct attribute * def_attrs[] = { NULL, };
+
+struct rbu_attribute {
+	struct attribute attr;
+	ssize_t (*show) (struct rbu_download_device *rbu_dev,
+		char *buf, int type);
+	ssize_t (*store)(struct rbu_download_device *rbu_dev, 
+		const char *buf , size_t count, int type);
+	int type;
+};
+
+#define RBU_DEVICE_ATTR(_name,_mode,_show,_store, _type) \
+struct rbu_attribute rbu_attr_##_name = {       \
+	.attr ={.name= __stringify(_name), .mode= _mode, .owner= THIS_MODULE},\
+	.show = _show,                                \
+	.store = _store,                                \
+	.type = _type,	 \
+};
+
+#define to_rbu_attr(_attr) container_of(_attr,struct rbu_attribute,attr)
+#define to_rbu_download_device(obj)  \
+		container_of(obj,struct rbu_download_device,kobj)
+
+
+static RBU_DEVICE_ATTR(mono_name, 0644, rbu_show_image_name, 
+				rbu_store_image_name, MONOLITHIC);
+
+static RBU_DEVICE_ATTR(mono_size, 0444, rbu_show_image_size, 
+				dummy_store, MONOLITHIC);
+
+static RBU_DEVICE_ATTR(packet_name, 0644, rbu_show_image_name,
+                                rbu_store_image_name, PACKETIZED);
+
+static RBU_DEVICE_ATTR(packet_size, 0444, rbu_show_image_size,
+                                dummy_store, PACKETIZED);
+
+static  ssize_t rbu_attr_store (struct kobject *kobj,
+				struct attribute *attr,
+				const char *buf, 
+				size_t count) 
+{
+	struct rbu_download_device *rbu_dev = to_rbu_download_device(kobj);
+	struct rbu_attribute *rbu_attr = to_rbu_attr(attr);
+	ssize_t rc = count;
+
+	pr_debug("rbu_attr_store: entry type = %d\n", rbu_attr->type);
+	
+	if (rbu_attr->store)
+		rc = rbu_attr->store(rbu_dev, buf, count, rbu_attr->type);
+
+	return rc;
+}
+
+static ssize_t rbu_attr_show (struct kobject * kobj, 
+			      struct attribute *attr, 
+			      char *buf)
+{
+	struct rbu_download_device *rbu_dev = to_rbu_download_device(kobj);
+	struct rbu_attribute *rbu_attr = to_rbu_attr(attr);
+	ssize_t rc = 0;
+
+	pr_debug("rbu_attr_show: entry type = %d\n", rbu_attr->type);
+
+	if (rbu_attr->show)
+		rc = rbu_attr->show(rbu_dev, buf, rbu_attr->type);
+	return rc;
+}
+
+static struct sysfs_ops rbu_attr_ops = { 
+	.show = rbu_attr_show,
+	.store = rbu_attr_store,
+};
+
+static struct kobj_type ktype_dell_rbu = { 
+	.sysfs_ops	= &rbu_attr_ops,
+	.default_attrs	= def_attrs,
+};
+static decl_subsys(dell_rbu,&ktype_dell_rbu,NULL);
+
+static int rbu_download_device_register(struct rbu_download_device *rbu_dev, 
+					int type)
+{
+        int rc = 0;
+        if (!rbu_dev)
+                return 1;
+        memset(rbu_dev, 0, sizeof (*rbu_dev));
+	if (type == MONOLITHIC)
+        	kobject_set_name(&rbu_dev->kobj, "monolithic");
+	else 
+        	kobject_set_name(&rbu_dev->kobj, "packetized");
+        kobj_set_kset_s(rbu_dev,dell_rbu_subsys);
+        rc = kobject_register(&rbu_dev->kobj);
+        if (!rc) {
+		if (type == MONOLITHIC ) {
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_mono_name.attr);
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_mono_size.attr);
+		} else {
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_packet_name.attr);
+			sysfs_create_file(&rbu_dev->kobj,
+				&rbu_attr_packet_size.attr);
+		}
+	} else 
+		pr_debug("rbu_download_device_register: "
+			"kobject_register %d \n", rc);
+	pr_debug("rbu_download_device_register: rbu_dev addr %p\n", rbu_dev);
+        return rc;
+}
+
+static struct rbu_download_device *create_rbu_download_entry(int type) 
+{
+	struct rbu_download_device *rbu_dev = NULL;
+	rbu_dev = kmalloc( sizeof(struct rbu_download_device), 
+		GFP_KERNEL);
+	if (!rbu_dev) {
+		printk(KERN_ERR "create_rbu_download_entry: kmalloc failed\n");
+		return NULL;
+	}
+	rbu_dev->type = type;
+        if (rbu_download_device_register(rbu_dev, type))  {
+		pr_debug("create_rbu_download_entry: "
+			"rbu_download_device_register failed \n");
+		kfree(rbu_dev);
+        }
+
+	pr_debug("create_rbu_download_entry: rbu_dev %p\n", rbu_dev);	
+	return rbu_dev;
+}
+
+static void remove_rbu_download_entry(struct rbu_download_device *rbu_dev, 
+				       int type) 
+{
+	pr_debug("remove_rbu_download_entry: rbu_dev ptr %p \n", rbu_dev);	
+	if (rbu_dev != NULL) {
+		kobject_unregister(&rbu_dev->kobj);
+		kfree(rbu_dev);
+	}
+	
+}
+
+static int __init dcdrbu_init(void)
+{
+        int rc = 0;
+	spin_lock_init(&rbu_data.lock);
+	
+	init_packet_head();
+	
+        device_initialize(&rbu_device);
+
+	rc = firmware_register(&dell_rbu_subsys);
+        if (rc < 0) {
+                printk(KERN_WARNING "dcdrbu_init: firmware_register"
+			" dell_rbu failed\n");
+                return rc;
+        }
+	
+	rbu_download_mono = create_rbu_download_entry (MONOLITHIC);
+	if (rbu_download_mono == NULL) {	
+		firmware_unregister(&dell_rbu_subsys);
+		return -ENOMEM;
+	}
+	
+	rbu_download_packet=  create_rbu_download_entry (PACKETIZED);
+	if (rbu_download_packet == NULL) {	
+		remove_rbu_download_entry(rbu_download_mono, MONOLITHIC);
+		firmware_unregister(&dell_rbu_subsys);
+		return -ENOMEM;
+	}
+        strncpy(rbu_device.bus_id,"firmware", BUS_ID_SIZE);
+        return rc;
+}
+
+static __exit void dcdrbu_exit( void)
+{
+	spin_lock(&rbu_data.lock);
+        packet_empty_list();
+        img_update_free();
+        spin_unlock(&rbu_data.lock);
+	remove_rbu_download_entry(rbu_download_packet, PACKETIZED);
+	remove_rbu_download_entry(rbu_download_mono, MONOLITHIC);
+        firmware_unregister(&dell_rbu_subsys);
+}
+
+module_exit(dcdrbu_exit);
+module_init(dcdrbu_init);
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Kconfig linux-2.6.11.8/drivers/firmware/Kconfig
--- linux-2.6.11.8.ORIG/drivers/firmware/Kconfig	2005-05-13 12:07:58.000000000 -0500
+++ linux-2.6.11.8/drivers/firmware/Kconfig	2005-05-13 12:07:00.000000000 -0500
@@ -58,4 +58,16 @@ config EFI_PCDP
 
 	  See <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
 
+config DELL_RBU
+        tristate "BIOS update support for DELL systems via sysfs"
+        default n
+        help
+          Say Y if you want to have the option of updating the BIOS for your
+	  DELL system. Note you need a supporting application to comunicate 
+	  with the BIOS regardign the new image for the image update to 
+	  take effect.
+
+	  See <file:Documentation/DELL_RBU.txt> for more details on the driver.
+
+
 endmenu
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Makefile linux-2.6.11.8/drivers/firmware/Makefile
--- linux-2.6.11.8.ORIG/drivers/firmware/Makefile	2005-05-13 12:08:12.000000000 -0500
+++ linux-2.6.11.8/drivers/firmware/Makefile	2005-05-09 15:15:16.000000000 -0500
@@ -4,3 +4,4 @@
 obj-$(CONFIG_EDD)             	+= edd.o
 obj-$(CONFIG_EFI_VARS)		+= efivars.o
 obj-$(CONFIG_EFI_PCDP)		+= pcdp.o
+obj-$(CONFIG_DELL_RBU)		+= dell_rbu.o

^ permalink raw reply	[flat|nested] 28+ messages in thread
* RE: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-05-26 18:43 Abhay_Salunke
  0 siblings, 0 replies; 28+ messages in thread
From: Abhay_Salunke @ 2005-05-26 18:43 UTC (permalink / raw)
  To: Matt_Domsch; +Cc: greg, linux-kernel, ranty

Matt,
I may be wrong; but I think request_firmware(&fw, NAME , device)  will
create an entry /sys/firmware/NAME/(loading,data).
You can simply download the firmware by doing 
echo 1 > /sys/firmware/NAME/loading
cat rbu.image > /sys/firmware/NAME/data.
After this is done request_firmware returns and the image can be
actually 
copied to the device using fw->data and fw-size. 
Once this is complete the fw can be released.
Not sure why do I need a firmware image file in /lib/firmware?
Thanks,
Abhay

> -----Original Message-----
> From: Domsch, Matt
> Sent: Thursday, May 26, 2005 11:56 AM
> To: Salunke, Abhay
> Cc: greg@kroah.com; linux-kernel@vger.kernel.org; ranty@debian.org
> Subject: Re: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new
Dell
> BIOS update driver
> 
> On Thu, May 26, 2005 at 11:37:44AM -0500, Abhay_Salunke@Dell.com
wrote:
> > > -----Original Message-----
> > > From: Greg KH [mailto:greg@kroah.com]
> > > Sent: Monday, May 23, 2005 10:48 AM
> > > To: Salunke, Abhay
> > > Cc: linux-kernel@vger.kernel.org; akpm@osdl.org; Domsch, Matt
> > > Subject: Re: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for
new
> > Dell
> > > BIOS update driver
> > >
> > > On Mon, May 23, 2005 at 09:52:05AM -0500, Abhay_Salunke@Dell.com
> > wrote:
> > > > Greg,
> > > > >
> > > > > Also, what's wrong with using the existing firmware interface
in
> > the
> > > > > kernel?
> > > > request_firmware requires the $FIRMWARE env to be populated with
the
> > > > firmware image name or the firmware image name needs to be
hardcoded
> > > > within  the call to request_firmware. Since the user is free to
> > change
> > > > the BIOS update image at will, it may not be possible if we use
> > > > $FIRMWARE also I am not sure if this env variable might be
> > conflicting
> > > > to some other driver.
> > >
> > > As others have already stated, this doesn't really matter.  Make
it
> > > "dell_bios_update", if any device names their firmware that, well,
> > > that's their problem...
> >
> > OK, I have been trying to use request_firmware but it always fails
with
> > return code -2. This is the code snippet below, any thoughts?
> 
> -2 is -ENOENT, "No such file or directory".
> It's looking for a file called /lib/firmware/dell_rbu_type, and not
> finding it.  That probably isn't the name of the file you want it to
> look for.
> 
> Thanks,
> Matt
> 
> --
> Matt Domsch
> Software Architect
> Dell Linux Solutions linux.dell.com & www.dell.com/linux
> Linux on Dell mailing lists @ http://lists.us.dell.com


^ permalink raw reply	[flat|nested] 28+ messages in thread
* RE: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-05-26 16:37 Abhay_Salunke
  2005-05-26 16:56 ` Matt Domsch
  2005-05-26 20:37 ` Greg KH
  0 siblings, 2 replies; 28+ messages in thread
From: Abhay_Salunke @ 2005-05-26 16:37 UTC (permalink / raw)
  To: greg; +Cc: linux-kernel, ranty

> -----Original Message-----
> From: Greg KH [mailto:greg@kroah.com]
> Sent: Monday, May 23, 2005 10:48 AM
> To: Salunke, Abhay
> Cc: linux-kernel@vger.kernel.org; akpm@osdl.org; Domsch, Matt
> Subject: Re: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new
Dell
> BIOS update driver
> 
> On Mon, May 23, 2005 at 09:52:05AM -0500, Abhay_Salunke@Dell.com
wrote:
> > Greg,
> > >
> > > Also, what's wrong with using the existing firmware interface in
the
> > > kernel?
> > request_firmware requires the $FIRMWARE env to be populated with the
> > firmware image name or the firmware image name needs to be hardcoded
> > within  the call to request_firmware. Since the user is free to
change
> > the BIOS update image at will, it may not be possible if we use
> > $FIRMWARE also I am not sure if this env variable might be
conflicting
> > to some other driver.
> 
> As others have already stated, this doesn't really matter.  Make it
> "dell_bios_update", if any device names their firmware that, well,
> that's their problem...

OK, I have been trying to use request_firmware but it always fails with
return code -2. This is the code snippet below, any thoughts?

static struct device rbu_device_type;

static struct device rbu_device;

static int __init dcdrbu_init(void)
{
        int rc = 0;
        const struct firmware *fw;

        device_initialize(&rbu_device_type);
        device_initialize(&rbu_device);

        strncpy(rbu_device.bus_id,"dell_rbu.bin", BUS_ID_SIZE);
        strncpy(rbu_device_type.bus_id,"dell_rbu1.bin", BUS_ID_SIZE);

        rc = request_firmware(&fw, "dell_rbu_type", &rbu_device_type);

        if (rc) {
                printk(KERN_ERR "dcdrbu_init: Firmware 1 missing "
                        "%d\n", rc);
                return -EIO;
        }

        release_firmware(fw);

        rc = request_firmware(&fw, "dell_rbu_data", &rbu_device);
        if (rc) {
                printk(KERN_ERR "dcdrbu_init: Firmware 2 missing "
                        "%d\n", rc);
                return -EIO;
        }

        release_firmware(fw);

        return rc;
}

static __exit void dcdrbu_exit( void)
{
}

module_exit(dcdrbu_exit);
module_init(dcdrbu_init);

^ permalink raw reply	[flat|nested] 28+ messages in thread
* RE: [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-05-23 14:52 Abhay_Salunke
  2005-05-23 14:58 ` Arjan van de Ven
                   ` (2 more replies)
  0 siblings, 3 replies; 28+ messages in thread
From: Abhay_Salunke @ 2005-05-23 14:52 UTC (permalink / raw)
  To: greg; +Cc: linux-kernel, akpm, Matt_Domsch

Greg,
> 
> Also, what's wrong with using the existing firmware interface in the
> kernel?
request_firmware requires the $FIRMWARE env to be populated with the
firmware image name or the firmware image name needs to be hardcoded
within  the call to request_firmware. Since the user is free to change
the BIOS update image at will, it may not be possible if we use
$FIRMWARE also I am not sure if this env variable might be conflicting
to some other driver.

Thanks
Abhay

^ permalink raw reply	[flat|nested] 28+ messages in thread
* [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-05-18 18:13 Abhay Salunke
  2005-05-19  3:32 ` Greg KH
  0 siblings, 1 reply; 28+ messages in thread
From: Abhay Salunke @ 2005-05-18 18:13 UTC (permalink / raw)
  To: linux-kernel, Andrew Morton; +Cc: abhay_salunke, matt_domsch, Alexey Dobriyan

This is a resubmit of the patch after incorporating all the inputs from revieweres. This also has a fix where the packets were leaked in the function create_packet line#227.

By making a contribution to this project, I certify that:
The contribution was created in whole or in part by me and I have the 
right to submit it under the open source license indicated in the file.

Signed-off-by: Abhay Salunke <Abhay_Salunke@dell.com>

Thanks,
Abhay Salunke
Software Engineer.
DELL Inc

diff -uprN linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt linux-2.6.11.8/Documentation/DELL_RBU.txt
--- linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/Documentation/DELL_RBU.txt	2005-05-11 13:22:45.000000000 -0500
@@ -0,0 +1,92 @@
+Purpose:
+Demonstrate the usage of the DELL_RBU (DELL Remote BIOS Update) driver
+for updating BIOS images on Dell hardware.
+
+Scope:
+This document discusses the functionality of the DELL_RBU driver. 
+This driver is required by BIOS update applications shipped by DELL for updating
+BIOS on DELL servers and client systems. 
+
+Overview:
+The rbu driver is designed to be running on 2.6 kernel. 
+This driver is one single dell_rbu.c file (approx 800 lines total).
+The BIOS update is done by writing the new BIOS image in to contiguous physical
+memory addressable by the BIOS. The user application indicates the BIOS regarding 
+the update of a fresh image. The BIOS then scans the memory to find the image and 
+it will then update itself. There are basically two different mechanisms for 
+writing the BIOS image in to contiguous memory 
+1> By writing the image to one single shunk of contiguous physical memory.
+2> By writing image in to smaller chunks of contiguous physical memory.
+The update mechanism is determined by the update application based on the 
+particular system type.
+
+Update mechanism using single physical chunk of memory:
+The rbu driver on its load time created the following entries in sysfs
+/sys/firmware/rbu/rbudatasize
+/sys/firmware/rbu/rbudata
+
+Steps to update the BIOS image:
+
+1> Set the incoming BIOS image size in the /sys/firmware/rbudatasize file.
+
+ e.g. echo XXXXXX > /sys/firmware/rbudatasize 
+NOTE: the size specified is always in decimal.
+
+you can also read back the image size by doing
+cat /sys/firmware/rbudatasize
+
+2> Download the BIOS image by copying the image file to /sys/firmware/rbudata 
+file.
+e.g. cat image.hdr > /sys/firmware/rbudata
+
+you can also read back the image using 
+cat /sys/firmware/rbu/rbudata
+This is usually helpful in verifying the image downloaded.
+
+Step#1 results in the driver allocating contiguous physical memory  of the size
+echoed in to rbudatasize. The subsequent writes to rbudata as described in
+step #2 results in the image getting written to the allocated contiguous physical 
+pages. Repeating step #2 will overwrite the previous data in rbudata file.
+
+On a driver unload the allocated memory is freed and the rbudatasize file reads 0.
+The user should not unload the driver after downloading the new BIOS image for 
+if it wants to update BIOS with that image.
+
+The user can overwrite the rbudata file with a new image. The user has to make 
+sure that the new image size is less than or equal to the image size copied to 
+the rbudatasize file. 
+If the new image is grater than the allocated size then only the allocated size
+gets copied the rest will not.
+
+The user can also free the previous BIOS image as follows
+echo 0 > /sys/firmware/rbu/rbudatasize
+
+If the user tries to set the BIOS image size there is a possiblity that the 
+system may not have enough contiguous physical memory for upadtes, thus the 
+image allocation will fail. The user the needs to verify this by reading back 
+the rbudatasize which will be set to 0.
+
+Update using smaller chunks (packets) of contiguous memory:
+The disadvantage of contiguous allocation is that it may not be always possible
+to get that size of contiuguous chunk of avaliable physical pages as in most 
+Linux systems the memory gets fragmented immideately after a reboot.
+The update using smaller chunks fixes this issue; it also requires the BIOS on 
+the system to support this feature; the update application needs to query this 
+with the BIOS on the system before using this technique. 
+
+The appplication breaks the BIOS image in to small packets; before starting the 
+update using this technique, the application sets the packetdatasize as follows
+
+echo XXXXXX > /sys/firmware/packetdatasize
+Any writes to /sys/firmware/packetdata results in allocation of contiguous 
+physical memory of packetdatasize and the data is written to that meomry.
+Writing 0 to packetdatasize results in freeing of all packets.
+Unloading the driver will also result in freeing up of the allocated packets.
+
+NOTE:
+Afte updating the BIOS image the appplication needs to communicate with the BIOS 
+for enabling the update on the next reboot. The application can then choose to 
+reboot the system imideately or not reboot the system and leave up to the user 
+to do a reboot.
+
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c linux-2.6.11.8/drivers/firmware/dell_rbu.c
--- linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/drivers/firmware/dell_rbu.c	2005-05-18 13:00:28.102529160 -0500
@@ -0,0 +1,853 @@
+/*
+ * dell_rbu.c
+ * Bios Update driver for Dell systems
+ * Author: Dell Inc
+ *	   Abhay Salunke <abhay_salunke@dell.com>
+ *
+ * Copyright (C) 2004 Dell Inc.
+ *
+ * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by creating 
+ * entries in the /sys file systems on Linux 2.6 and higher kernels.
+ * The driver supports two mechanism to update the BIOS namely contiguous and 
+ * packetized. Both these methods still require to have some application to set
+ * the  CMOS bit indicating the BIOS to update itself after a reboot.
+ * 
+ * Contiguous method:
+ * This driver tries to allocates contiguos physical pages large enough 
+ * to accomodate the BIOS image size specified by the user. The user 
+ * supplied BIOS image is then copied in to the allocated contiguous pages.
+ *
+ * Packetized method:
+ * In case of packetized the driver provides entries in the /sys file systems 
+ * as packetdatasize and packetdata. This driver requires an application to 
+ * break the BIOS image in to fixed sized packet chunks and each packet is 
+ * written to the packetdata entry. The packetdatasize needs to be set once 
+ * and is fixed for all the packets.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * 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.
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/firmware.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+
+#define BIOS_SCAN_LIMIT 0xffffffff
+MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
+MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.7");
+
+static struct _rbu_data {
+	void *image_update_buffer;
+	unsigned long image_update_buffer_size;
+	unsigned long bios_image_size;
+	unsigned long image_update_order_number;
+	spinlock_t lock;
+	unsigned long packet_read_count;
+	unsigned long packet_write_count;
+	unsigned long num_packets;
+	unsigned long packetsize;
+} rbu_data;
+
+struct packet_data{
+	struct list_head list;
+	size_t length;
+	void *data;
+	int ordernum;
+};
+
+
+static struct packet_data packet_data_head;
+
+/* no default attributes yet. */
+static struct attribute * def_attrs[] = { NULL, };
+
+/* don't use show and store attribute functions */
+static struct sysfs_ops rbu_attr_ops = { };
+
+static struct kobj_type ktype_rbu = { 
+	.sysfs_ops	= &rbu_attr_ops,
+	.default_attrs	= def_attrs,
+};
+
+static decl_subsys(rbu,&ktype_rbu,NULL);
+
+static void init_packet_head(void)
+{
+	INIT_LIST_HEAD(&packet_data_head.list);
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+static int fill_last_packet(void *data, size_t length)
+{
+	struct list_head *ptemp_list;
+	struct packet_data *ppacket = NULL;
+	int packet_count = 0;
+	
+	pr_debug("fill_last_packet: entry \n");
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets) {
+		pr_debug("fill_last_packet: num_packets=0\n");
+		return -ENOMEM;
+	}
+
+	packet_count = rbu_data.num_packets;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	
+	while(--packet_count) {
+		ptemp_list = ptemp_list->next;
+	}
+
+	ppacket = list_entry(ptemp_list,struct packet_data, list);
+
+	if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {
+		printk(KERN_WARNING "fill_last_packet: packet size data "
+				"overrun\n");
+		return -ENOMEM;
+	}
+	
+	pr_debug("fill_last_packet : buffer = %p\n", ppacket->data);
+
+	/* copy the incoming data in to the new buffer */
+	memcpy((ppacket->data + rbu_data.packet_write_count), 
+		data, length);
+	
+	if ((rbu_data.packet_write_count + length) == rbu_data.packetsize) {
+		/* 
+		 this was the last data chunk in the packet 
+		 so reinitialize the packet data counter to zero 
+		*/
+		rbu_data.packet_write_count = 0;
+	} else {
+		rbu_data.packet_write_count += length;
+	}
+	pr_debug("fill_last_packet: exit \n");
+	return 0;
+}
+
+/*
+ get_free_pages_limited:
+ This is a helper function which allocates free pages based on an upper limit.
+ On x86_64 or 64 bit arch the memory allocation goes above 4GB space which is 
+ not addressable by the BIOS. This function tries to get allocation below the 
+ limit (4GB) address. It first tries to allocate memory normally using the 
+ GFP_KERNEL argument if the incoming limit is non-zero and if the returned 
+ physical memory address exceeds the upper limit, the allocated pages are freed 
+ and the memory is reallocated using the GFP_DMA argument.
+*/
+static void *get_free_pages_limited(unsigned long size,
+                                    int *ordernum,
+				    unsigned long limit)
+{
+	unsigned long img_buf_phys_addr;
+	void *pbuf = NULL;
+
+	*ordernum = get_order(size);
+	/* 
+         Check if we are not getting a very large file. 
+	 This can happen as a user error in entering the file size 
+        */
+	if (*ordernum == BITS_PER_LONG) {
+		pr_debug("get_free_pages_limited: Incoming size is"
+			" very large\n");
+		return NULL;
+	}
+	
+	/* try allocating a new buffer to fit the request */
+	pbuf =(unsigned char *)__get_free_pages(GFP_KERNEL, *ordernum);
+
+	if (pbuf != NULL) {
+        	/* check if the image is with in limits */
+		img_buf_phys_addr = (unsigned long)virt_to_phys(pbuf);
+		
+		if ((limit != 0) && ((img_buf_phys_addr + size) > limit)) {
+			pr_debug("Got memory above 4GB range, free this "
+				"and try with DMA memory\n");
+			/* free this memory as we need it with in 4GB range */
+			free_pages ((unsigned long)pbuf, *ordernum);
+			/* 
+                         Try allocating a new buffer from the GFP_DMA range 
+			 as it is with in 16MB range.
+			*/
+			pbuf =(unsigned char *)__get_free_pages(GFP_DMA, 
+						*ordernum);
+			if (pbuf == NULL)
+				pr_debug("Failed to get memory of size %ld "
+					"using GFP_DMA\n", size);
+		}
+	}
+	return pbuf;
+}
+
+static int create_packet(size_t length)
+{
+	struct packet_data *newpacket;
+	int ordernum = 0;
+
+	pr_debug("create_packet: entry \n");
+
+	if (rbu_data.packetsize == 0 ) {
+		pr_debug("create_packet: packetsize not specified\n");
+		return -EINVAL;
+	}
+
+	newpacket = kmalloc(sizeof(struct packet_data) ,GFP_KERNEL);
+	if(newpacket == NULL) {
+		printk(KERN_WARNING "create_packet: failed to allocate new "
+			"packet\n");
+		return -ENOMEM;
+	}
+
+	/* there is no upper limit on memory address for packetized mechanism*/
+	newpacket->data = get_free_pages_limited(rbu_data.packetsize,
+				&ordernum, 0);
+	pr_debug("create_packet: newpacket %p\n", newpacket->data);
+		
+	if(newpacket->data == NULL) {
+		printk(KERN_WARNING "create_packet: failed to allocate new "
+			"packet\n");
+		kfree(newpacket);
+		return -ENOMEM;
+	}
+
+	newpacket->ordernum = ordernum;
+	++rbu_data.num_packets;
+	/* initialize the newly created packet headers */
+	INIT_LIST_HEAD(&newpacket->list);
+	list_add_tail(&newpacket->list, &packet_data_head.list);
+	/* packets have fixed size*/
+	newpacket->length = rbu_data.packetsize;
+	
+	pr_debug("create_packet: exit \n");
+
+	return 0;
+}
+
+
+static int packetize_data(void *data, size_t length) 
+{
+	int rc = 0;
+
+	pr_debug("packetize_data : entry\n");
+	if (rbu_data.packet_write_count == 0) {
+		if ((rc = create_packet(length)) != 0 )
+			return rc;
+	}
+	/* fill data in to the packet */	
+	if ((rc = fill_last_packet(data, length)) != 0)
+		return rc;
+	
+	pr_debug("packetize_data : exit\n");
+	return rc;
+}
+
+
+/*
+ do_packet_read :
+ This is a helper function which reads the packet data of the 
+ current list.
+ data: is the incoming buffer
+ ptemp_list: points to the incoming list item
+ length: is the length of the free space in the buffer.
+ bytes_read: is the total number of bytes read already from 
+ the packet list
+ list_read_count: is the counter to keep track of the number 
+ of bytes read out of each packet.
+*/
+int do_packet_read(char *data, 
+		   struct list_head *ptemp_list, 
+		   int length,
+		   int bytes_read, 
+		   int *list_read_count)
+{
+	void *ptemp_buf;
+	struct packet_data *newpacket = NULL;
+	int bytes_copied = 0;
+	int j = 0;
+
+	newpacket = list_entry(ptemp_list,struct packet_data, list);
+	*list_read_count += newpacket->length;
+
+	if (*list_read_count > bytes_read) {
+		/* point to the start of unread data */
+		j = newpacket->length - (*list_read_count - bytes_read);
+		/* point to the offset in the packet buffer*/
+		ptemp_buf = (u8 *)newpacket->data + j;
+		/* check if there is enough room in the	incoming buffer*/
+		if (length > (*list_read_count - bytes_read)) 
+			/* copy what ever is there in this packet and move on*/
+			bytes_copied = (*list_read_count - bytes_read);
+		else 
+			/* copy the remaining */
+			bytes_copied = length;
+		memcpy(data, ptemp_buf, bytes_copied);
+	} 
+	return bytes_copied;
+}
+
+/*
+ packet_read_list:
+ This function reads the data out of the packet link list.
+ It will read data from multiple packets depending upon the 
+ size of the incoming buffer.
+ data: is the incoming buffer pointer
+ *pread_length: is the length of the incoming buffer. At return 
+ this value is adjusted to the actual size of the data read.
+*/
+static int packet_read_list(char *data, size_t *pread_length)
+{
+	struct list_head *ptemp_list;
+	int temp_count = 0;
+	int bytes_copied = 0;
+	int bytes_read = 0;
+	int remaining_bytes =0;
+	char *pdest = data;
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets)
+		return -ENOMEM;
+
+	remaining_bytes = *pread_length;
+	bytes_read = rbu_data.packet_read_count;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while(!list_empty(ptemp_list)) {
+		bytes_copied = do_packet_read(pdest, ptemp_list, 
+			remaining_bytes, bytes_read, &temp_count);
+		remaining_bytes -= bytes_copied;
+		bytes_read += bytes_copied;
+		pdest += bytes_copied;
+		/* 
+		 check if we reached end of buffer before reaching the 
+		 last packet
+		*/
+		if (remaining_bytes == 0)
+			break;
+
+		ptemp_list = ptemp_list->next;
+	}
+	/*finally set the bytes read */
+	*pread_length = bytes_read - rbu_data.packet_read_count;
+	rbu_data.packet_read_count = bytes_read;
+	return 0;
+}
+
+static void packet_empty_list(void)
+{
+	struct list_head *ptemp_list;
+	struct list_head *pnext_list;
+	struct packet_data *newpacket;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while(!list_empty(ptemp_list)) {
+		newpacket = list_entry(ptemp_list, struct packet_data, list);
+		pnext_list = ptemp_list->next;
+		list_del(ptemp_list);
+		ptemp_list = pnext_list;
+		/* 
+		 zero out the RBU packet memory before freeing to make sure
+		 there are no stale RBU packets left in memory 
+		*/
+		memset(newpacket->data, 0, rbu_data.packetsize);
+		free_pages((unsigned long)newpacket->data, newpacket->ordernum);
+		kfree(newpacket);
+	}
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+}
+
+
+/*
+ img_update_free:
+ Frees the buffer allocated for storing BIOS image
+ Always called with lock held and returned with lock held
+*/
+static void img_update_free( void)
+{
+	if (rbu_data.image_update_buffer == NULL)
+		return;
+	
+	/* 
+	 zero out this buffer before freeing it to get rid of any stale 
+	 BIOS image copied in memory.
+	*/
+	memset(rbu_data.image_update_buffer, 0, 
+		rbu_data.image_update_buffer_size);
+ 	free_pages((unsigned long)rbu_data.image_update_buffer, 
+		rbu_data.image_update_order_number);
+	/* Re-initialize the rbu_data variables after a free */
+	rbu_data.image_update_buffer = NULL;
+	rbu_data.image_update_buffer_size = 0;
+	rbu_data.bios_image_size = 0;
+}
+
+/*
+ img_update_realloc:
+ This function allocates the contiguous pages to accomodate the requested
+ size of data. The memory address and size values are stored globally and 
+ on every call to this function the new size is checked to see if more 
+ data is required than the existing size. If true the previous memory is freed
+ and new allocation is done to accomodate the new size. If the incoming size is 
+ less then than the already allocated size, then that  memory is reused.
+ This function is called with lock held and returna with lock held.
+*/
+static int img_update_realloc(unsigned long size)
+{
+	unsigned char *image_update_buffer = NULL;
+	unsigned long rc;
+	int ordernum =0;
+
+
+	/* check if the buffer of sufficient size has been already allocated */
+    if (rbu_data.image_update_buffer_size >= size) {
+		/* check for corruption */
+		if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
+			pr_debug("img_update_realloc: corruption check "
+				"failed\n");
+			return -EINVAL;
+		}
+		/* we have a valid pre-allocated buffer with sufficient size */
+		return 0;
+    }
+
+	/* free any previously allocated buffer */
+	img_update_free();
+
+	/* 
+	 This has already been called as locked so we can now unlock 
+	 and proceed to calling get_free_pages_limited as this function
+	 can sleep
+	*/
+	spin_unlock(&rbu_data.lock);
+
+	image_update_buffer = (unsigned char *)get_free_pages_limited(size,
+		&ordernum,
+		BIOS_SCAN_LIMIT);
+	
+	/* acquire the spinlock again */
+	spin_lock(&rbu_data.lock);
+
+	if (image_update_buffer != NULL) {
+		rbu_data.image_update_buffer = image_update_buffer;
+		rbu_data.image_update_buffer_size = PAGE_SIZE << ordernum;
+		rbu_data.image_update_order_number = ordernum;
+		memset(rbu_data.image_update_buffer,0, 
+			rbu_data.image_update_buffer_size);
+		pr_debug("img_update_realloc: success\n");
+		rc = 0; 
+	} else {
+		pr_debug("Not enough memory for image update:order number = %d"
+			",size = %ld\n",ordernum, size);
+		rc = -ENOMEM;
+	}
+
+	return rc;
+} /* img_update_realloc */
+
+
+/*
+ read_packet_data_size:
+ Returns the size of an RBU packet; if no packets present returns 0
+*/
+static ssize_t read_packet_data_size(struct kobject *kobj, 
+				     char *buffer,
+   				     loff_t pos, 
+				     size_t count)
+{
+	unsigned int size = 0;
+	if (pos == 0)
+		size = sprintf(buffer, "%lu\n", rbu_data.packetsize);
+	return size;
+} 
+
+/*
+ write_packet_data_size:
+ Writes the RBU data size supplied by the user, if the 
+ data size supplied is non zero number, this function 
+ records the packet size for any packet allocations.
+ If a byte size of zero is supplied this function will free
+ the previously allocated packets.
+*/
+static ssize_t write_packet_data_size(struct kobject *kobj, 
+				      char *buffer, 
+				      loff_t pos, 
+				      size_t count)
+{
+	int retval = count;
+
+	spin_lock(&rbu_data.lock);
+	/* extract the image size */
+	sscanf(buffer, "%lu",&rbu_data.packetsize);
+	/* free the previous packet lists */
+	packet_empty_list();
+
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ read_rbu_data_size:
+ Returns the size of an RBU image previously downloaded
+ if no image is downloaded the size returned is 0
+*/
+static ssize_t read_rbu_data_size(struct kobject *kobj, 
+				  char *buffer, 
+				  loff_t pos, 
+				  size_t count)
+{
+	unsigned int size = 0;
+	if (pos == 0)
+		size = sprintf(buffer, "%lu\n", rbu_data.bios_image_size);
+	return size;
+} 
+
+/*
+ write_rbu_data_size:
+ Writes the RBU data size supplied by the user, if the 
+ data size supplied is non zero number, this function 
+ allocates the contiguous physical memory pages for the 
+ supplied size , if it fails this function returns error.
+ If a byte size of zero is supplied this function will free
+ the previously allocated contiguous pages.
+*/
+static ssize_t write_rbu_data_size(struct kobject *kobj, 
+				   char *buffer, 
+				   loff_t pos, 
+				   size_t count)
+{
+	int retval = count;
+
+	spin_lock(&rbu_data.lock);
+	/* extract the image size */
+	sscanf(buffer, "%lu",&rbu_data.bios_image_size);
+
+	if (rbu_data.bios_image_size !=0 ) {
+		if (img_update_realloc(rbu_data.bios_image_size) < 0) {
+			pr_debug("write_rbu_data_size: failed to allocate "
+				"mem size %lu\n", 
+				(unsigned long)rbu_data.bios_image_size);
+			rbu_data.bios_image_size = 0;
+			retval = -ENOMEM;
+		} 
+	} else {
+		pr_debug(" freeing %ld size memory \n", 
+				rbu_data.bios_image_size);
+		img_update_free();
+	}
+	
+	pr_debug("write_rbu_data_size: = %lu\n", 
+		(unsigned long)rbu_data.bios_image_size);
+
+	spin_unlock(&rbu_data.lock);
+	return retval;
+} /* write_rbu_data_size*/
+
+/*
+ read_rbu_data:
+ Reads the BIOS image file from previously allocated contiguous physical 
+ pages in to the buffer supplied in this call. 
+ The reading is done in chunks of bytes supplied in the count argument.
+ The reading stops when the total number of bytes read equals the image 
+ size given previously.
+ If image size is not specified or if the image size is zero this function 
+ returns failure.
+ Parameters: 
+ kobj is the kernel object 
+ buffer is the pointer to the incoming data buffer.
+ count is the value of the incoming buffer size,
+ pos is the amount of bytes already read.
+*/
+static ssize_t read_rbu_data(struct kobject *kobj, 
+			     char *buffer,
+			     loff_t pos, 
+			     size_t count)
+{
+	unsigned char *ptemp = NULL;
+	int retval =0;
+	size_t bytes_left = 0;
+	size_t data_length = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check to see if we have something to return */
+	if ((rbu_data.image_update_buffer == NULL) || 
+		(rbu_data.bios_image_size == 0)) {
+		pr_debug("read_rbu_data: image_update_buffer %p ,"
+			"bios_image_size %lu\n",
+			rbu_data.image_update_buffer, 
+			rbu_data.bios_image_size);
+		retval = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+	
+	if ( pos > rbu_data.bios_image_size ) {
+		retval = 0;
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = rbu_data.bios_image_size - pos;
+	data_length = max(bytes_left,count);
+
+	ptemp = rbu_data.image_update_buffer;
+	memcpy(buffer, (ptemp + pos), data_length);
+
+	if ((pos + count) > rbu_data.bios_image_size)
+		/* this was the last copy */
+		retval = bytes_left;
+	else 
+		retval = count;
+	
+read_rbu_data_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ write_rbu_data
+ Writes from the incoming BIOS image file to the pre-allocated 
+ contiguous physical memory pages. 
+ The writes occur in chunks of memory supplied by the count. The writes 
+ stops when the total memory supplied equals the image size given previously.
+ If no memory size is previously specified or if the previously specified size 
+ is zero the write returns error.
+*/
+static ssize_t write_rbu_data(struct kobject *kobj, 
+			      char *buffer,
+			      loff_t pos, 
+			      size_t count)
+{
+	unsigned char *pDest = NULL;
+	unsigned char *ptemp = NULL;
+	int retval = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check if the image size is given */
+	if (0 == rbu_data.bios_image_size) {
+		printk(KERN_WARNING "write_rbu_data: BIOS image size "
+				"not set\n");
+		retval = -ENOMEM;
+		goto error_exit;
+	}
+
+	if ((pos + count) > rbu_data.bios_image_size) {
+		pr_debug("write_rbu_data: data_over_run, file pos %lu "
+			"bios_image_size %lu\n",
+			(unsigned long)pos,
+			rbu_data.bios_image_size);
+		retval = -ENOMEM;
+		goto error_exit;
+	}
+
+	pDest = (unsigned char*)rbu_data.image_update_buffer;
+	ptemp = pDest + pos;
+	memcpy(ptemp, buffer,  count);
+	retval = count;
+	pr_debug("write_rbu_data : retval = %d\n", retval);
+error_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ read_rbu_packet_data:
+ Reads the BIOS image file from previously allocated contiguous physical 
+ pages in to the buffer supplied in this call. 
+ The reading is done in chunks of bytes supplied in the count argument.
+ The reading stops when the total number of bytes read equals the image 
+ size given previously.
+ If image size is not specified or if the image size is zero this function 
+ returns failure.
+ Parameters: 
+ kobj is the kernel object 
+ buffer is the pointer to the incoming data buffer.
+ count is the value of the incoming buffer size,
+ pos is the amount of bytes already read.
+*/
+static ssize_t read_rbu_packet_data(struct kobject *kobj, 
+				    char *buffer,
+				    loff_t pos, 
+				    size_t count)
+{
+	int retval;
+	size_t bytes_left;
+	size_t data_length;
+	char *ptempBuf = buffer;
+	unsigned long imagesize;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check to see if we have something to return */
+	if (rbu_data.num_packets == 0) {
+		pr_debug("read_rbu_packet_data: no packets written\n");
+		retval = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+
+	imagesize = rbu_data.num_packets * rbu_data.packetsize;
+	
+	if ( pos > imagesize ) {
+		retval = 0;
+		printk(KERN_WARNING "read_rbu_packet_data: data underrun\n");
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = imagesize - pos;
+	data_length = max(bytes_left, count);
+
+	if ((retval = packet_read_list(ptempBuf, &data_length)) < 0)
+		goto read_rbu_data_exit;
+
+	if ((pos + count) > imagesize) {
+		rbu_data.packet_read_count = 0;
+		/* this was the last copy */
+		retval = bytes_left;
+	}
+	else 
+		retval = count;
+	
+read_rbu_data_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ write_rbu_packet_data
+ Writes from the incoming BIOS image file to the pre-allocated 
+ contiguous physical memory pages. 
+ The writes occur in chunks of memory supplied by the count. The writes 
+ stops when the total memory supplied equals the image size given previously.
+ If no memory size is previously specified or if the previously specified size 
+ is zero the write returns error.
+*/
+static ssize_t write_rbu_packet_data(struct kobject *kobj, 
+				     char *buffer,
+				     loff_t pos, 
+				     size_t count)
+{
+	int retval = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check if the packet size is given */
+	if (0 == rbu_data.packetsize) {
+		printk(KERN_WARNING "write_rbu_packet_data: packetsize " 
+			"not set\n");
+		retval = -ENOMEM;
+		goto error_exit;
+	}
+
+	if ((pos + count) > rbu_data.packetsize) {
+		pr_debug("write_rbu_packet_data: data_over_run, file pos %lu,"
+			"packetsize %lu\n",
+			(unsigned long)pos,
+			rbu_data.packetsize);
+		retval = -ENOMEM;
+
+		/* 
+		 We have a write data overrun, obviously this is 
+		 not the corret file, so free the previous data
+		*/
+		pr_debug("data overrun freeing all the previous packets\n");
+		packet_empty_list();
+		goto error_exit;
+	}
+
+	if ((retval = packetize_data(buffer, count)) < 0 ) {
+		pr_debug(KERN_WARNING "write_rbu_packet_data: packetize_data "
+			"failed with status %d\n", retval);
+		retval = -EIO;
+		goto error_exit;
+	} 
+
+	retval = count;
+
+	pr_debug("write_rbu_packet_data : retval = %d\n", retval);
+
+error_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+
+static struct bin_attribute rbudata_attr = {
+	.attr = {.name = "rbudata", .owner = THIS_MODULE, .mode = 0644},
+	.read = read_rbu_data,
+	.write = write_rbu_data,
+};
+
+static struct bin_attribute rbudatasize_attr = {
+	.attr = { .name = "rbudatasize", .owner = THIS_MODULE, .mode = 0644},
+	.read = read_rbu_data_size,
+	.write= write_rbu_data_size,
+};
+
+static struct bin_attribute packetdatasize_attr = {
+	.attr = { .name = "packetdatasize", .owner = THIS_MODULE, .mode = 0644},
+	.read = read_packet_data_size,
+	.write= write_packet_data_size,
+};
+
+static struct bin_attribute packetdata_attr = {
+	.attr = { .name = "packetdata", .owner = THIS_MODULE, .mode = 0644},
+	.read = read_rbu_packet_data,
+	.write= write_rbu_packet_data,
+};
+
+static int __init dcdrbu_init(void)
+{
+	int rc;
+
+	spin_lock_init(&rbu_data.lock);
+
+	init_packet_head();
+	
+	rc = firmware_register(&rbu_subsys);
+	if (rc < 0) {
+		printk(KERN_WARNING "dcdrbu_init: firmware_register failed\n");
+		return rc;
+	}
+
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&rbudata_attr);
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&rbudatasize_attr);
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&packetdatasize_attr);
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&packetdata_attr);
+
+	return rc;
+}
+
+static __exit void dcdrbu_exit( void)
+{
+	spin_lock(&rbu_data.lock);
+	packet_empty_list();
+	img_update_free();
+	spin_unlock(&rbu_data.lock);
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &rbudata_attr);
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &rbudatasize_attr);
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &packetdatasize_attr);
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &packetdata_attr);
+	firmware_unregister(&rbu_subsys);
+} 
+
+module_exit(dcdrbu_exit);
+module_init(dcdrbu_init);
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Kconfig linux-2.6.11.8/drivers/firmware/Kconfig
--- linux-2.6.11.8.ORIG/drivers/firmware/Kconfig	2005-05-13 12:07:58.000000000 -0500
+++ linux-2.6.11.8/drivers/firmware/Kconfig	2005-05-13 12:07:00.000000000 -0500
@@ -58,4 +58,16 @@ config EFI_PCDP
 
 	  See <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
 
+config DELL_RBU
+        tristate "BIOS update support for DELL systems via sysfs"
+        default n
+        help
+          Say Y if you want to have the option of updating the BIOS for your
+	  DELL system. Note you need a supporting application to comunicate 
+	  with the BIOS regardign the new image for the image update to 
+	  take effect.
+
+	  See <file:Documentation/DELL_RBU.txt> for more details on the driver.
+
+
 endmenu
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Makefile linux-2.6.11.8/drivers/firmware/Makefile
--- linux-2.6.11.8.ORIG/drivers/firmware/Makefile	2005-05-13 12:08:12.000000000 -0500
+++ linux-2.6.11.8/drivers/firmware/Makefile	2005-05-09 15:15:16.000000000 -0500
@@ -4,3 +4,4 @@
 obj-$(CONFIG_EDD)             	+= edd.o
 obj-$(CONFIG_EFI_VARS)		+= efivars.o
 obj-$(CONFIG_EFI_PCDP)		+= pcdp.o
+obj-$(CONFIG_DELL_RBU)		+= dell_rbu.o

^ permalink raw reply	[flat|nested] 28+ messages in thread
* [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver
@ 2005-05-13 19:00 Abhay Salunke
  2005-05-13 21:11 ` Alexey Dobriyan
  0 siblings, 1 reply; 28+ messages in thread
From: Abhay Salunke @ 2005-05-13 19:00 UTC (permalink / raw)
  To: linux-kernel, Andrew Morton; +Cc: abhay_salunke, matt_domsch

This is a resubmit of the patch after incorporating first round of 
suggestson from Andrew Morton.

By making a contribution to this project, I certify that:
The contribution was created in whole or in part by me and I have the 
right to submit it under the open source license indicated in the file.

Signed-off-by: Abhay Salunke <Abhay_Salunke@dell.com>

Thanks,
Abhay Salunke
Software Engineer.
DELL Inc

diff -uprN linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt linux-2.6.11.8/Documentation/DELL_RBU.txt
--- linux-2.6.11.8.ORIG/Documentation/DELL_RBU.txt	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/Documentation/DELL_RBU.txt	2005-05-11 13:22:45.712684696 -0500
@@ -0,0 +1,92 @@
+Purpose:
+Demonstrate the usage of the DELL_RBU (DELL Remote BIOS Update) driver
+for updating BIOS images on Dell hardware.
+
+Scope:
+This document discusses the functionality of the DELL_RBU driver. 
+This driver is required by BIOS update applications shipped by DELL for updating
+BIOS on DELL servers and client systems. 
+
+Overview:
+The rbu driver is designed to be running on 2.6 kernel. 
+This driver is one single dell_rbu.c file (approx 800 lines total).
+The BIOS update is done by writing the new BIOS image in to contiguous physical
+memory addressable by the BIOS. The user application indicates the BIOS regarding 
+the update of a fresh image. The BIOS then scans the memory to find the image and 
+it will then update itself. There are basically two different mechanisms for 
+writing the BIOS image in to contiguous memory 
+1> By writing the image to one single shunk of contiguous physical memory.
+2> By writing image in to smaller chunks of contiguous physical memory.
+The update mechanism is determined by the update application based on the 
+particular system type.
+
+Update mechanism using single physical chunk of memory:
+The rbu driver on its load time created the following entries in sysfs
+/sys/firmware/rbu/rbudatasize
+/sys/firmware/rbu/rbudata
+
+Steps to update the BIOS image:
+
+1> Set the incoming BIOS image size in the /sys/firmware/rbudatasize file.
+
+ e.g. echo XXXXXX > /sys/firmware/rbudatasize 
+NOTE: the size specified is always in decimal.
+
+you can also read back the image size by doing
+cat /sys/firmware/rbudatasize
+
+2> Download the BIOS image by copying the image file to /sys/firmware/rbudata 
+file.
+e.g. cat image.hdr > /sys/firmware/rbudata
+
+you can also read back the image using 
+cat /sys/firmware/rbu/rbudata
+This is usually helpful in verifying the image downloaded.
+
+Step#1 results in the driver allocating contiguous physical memory  of the size
+echoed in to rbudatasize. The subsequent writes to rbudata as described in
+step #2 results in the image getting written to the allocated contiguous physical 
+pages. Repeating step #2 will overwrite the previous data in rbudata file.
+
+On a driver unload the allocated memory is freed and the rbudatasize file reads 0.
+The user should not unload the driver after downloading the new BIOS image for 
+if it wants to update BIOS with that image.
+
+The user can overwrite the rbudata file with a new image. The user has to make 
+sure that the new image size is less than or equal to the image size copied to 
+the rbudatasize file. 
+If the new image is grater than the allocated size then only the allocated size
+gets copied the rest will not.
+
+The user can also free the previous BIOS image as follows
+echo 0 > /sys/firmware/rbu/rbudatasize
+
+If the user tries to set the BIOS image size there is a possiblity that the 
+system may not have enough contiguous physical memory for upadtes, thus the 
+image allocation will fail. The user the needs to verify this by reading back 
+the rbudatasize which will be set to 0.
+
+Update using smaller chunks (packets) of contiguous memory:
+The disadvantage of contiguous allocation is that it may not be always possible
+to get that size of contiuguous chunk of avaliable physical pages as in most 
+Linux systems the memory gets fragmented immideately after a reboot.
+The update using smaller chunks fixes this issue; it also requires the BIOS on 
+the system to support this feature; the update application needs to query this 
+with the BIOS on the system before using this technique. 
+
+The appplication breaks the BIOS image in to small packets; before starting the 
+update using this technique, the application sets the packetdatasize as follows
+
+echo XXXXXX > /sys/firmware/packetdatasize
+Any writes to /sys/firmware/packetdata results in allocation of contiguous 
+physical memory of packetdatasize and the data is written to that meomry.
+Writing 0 to packetdatasize results in freeing of all packets.
+Unloading the driver will also result in freeing up of the allocated packets.
+
+NOTE:
+Afte updating the BIOS image the appplication needs to communicate with the BIOS 
+for enabling the update on the next reboot. The application can then choose to 
+reboot the system imideately or not reboot the system and leave up to the user 
+to do a reboot.
+
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c linux-2.6.11.8/drivers/firmware/dell_rbu.c
--- linux-2.6.11.8.ORIG/drivers/firmware/dell_rbu.c	1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.11.8/drivers/firmware/dell_rbu.c	2005-05-13 12:38:20.047334448 -0500
@@ -0,0 +1,852 @@
+/*
+ * dell_rbu.c
+ * Bios Update driver for Dell systems
+ * Author: Dell Inc
+ *	   Abhay Salunke <abhay_salunke@dell.com>
+ *
+ * Copyright (C) 2004 Dell Inc.
+ *
+ * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by creating 
+ * entries in the /sys file systems on Linux 2.6 and higher kernels.
+ * The driver supports two mechanism to update the BIOS namely contiguous and packetized.
+ * Both these methods still require to have some application to set the 
+ * CMOS bit indicating the BIOS to update itself after a reboot.
+ * 
+ * Contiguous method:
+ * This driver tries to allocates contiguos physical pages large enough 
+ * to accomodate the BIOS image size specified by the user. The user 
+ * supplied BIOS image is then copied in to the allocated contiguous pages.
+ *
+ * Packetized method:
+ * In case of packetized the driver provides entries in the /sys file systems 
+ * as packetdatasize and packetdata. This driver requires an application to 
+ * break the BIOS image in to fixed sized packet chunks and each packet is written 
+ * to the packetdata entry. The packetdatasize needs to be set once and is fixed 
+ * for all the packets.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation
+ *
+ * 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.
+ *
+ * Changelog:
+ * 
+ * 13 May 2005 Abhay Salunke <Abhay_Salunke@dell.com>
+ * Modified code with suggestions from Andrew Morton; 
+ *
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/blkdev.h>
+#include <linux/firmware.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+
+#define BIOS_SCAN_LIMIT 0xffffffff
+MODULE_AUTHOR("Abhay Salunke <abhay_salunke@dell.com>");
+MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.6");
+
+static struct _rbu_data {
+	void *image_update_buffer;
+	unsigned long image_update_buffer_size;
+	unsigned long bios_image_size;
+	unsigned long image_update_order_number;
+	spinlock_t lock;
+	unsigned long packet_read_count;
+	unsigned long packet_write_count;
+	unsigned long num_packets;
+	unsigned long packetsize;
+} rbu_data;
+
+struct packet_data{
+	struct list_head list;
+	size_t length;
+	void *data;
+	int ordernum;
+};
+
+
+static struct packet_data packet_data_head;
+
+/* no default attributes yet. */
+static struct attribute * def_attrs[] = { NULL, };
+
+/* don't use show and store attribute functions */
+static struct sysfs_ops rbu_attr_ops = { };
+
+static struct kobj_type ktype_rbu = { 
+	.sysfs_ops	= &rbu_attr_ops,
+	.default_attrs	= def_attrs,
+};
+
+static decl_subsys(rbu,&ktype_rbu,NULL);
+
+void init_packet_head(void)
+{
+	INIT_LIST_HEAD(&packet_data_head.list);
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+	rbu_data.packetsize = 0;
+}
+
+static int fill_last_packet(void *data, size_t length)
+{
+	struct list_head *ptemp_list;
+	struct packet_data *ppacket = NULL;
+	int packet_count = 0;
+	
+	pr_debug("fill_last_packet: entry \n");
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets) {
+		pr_debug("fill_last_packet: num_packets=0\n");
+		return -ENOMEM;
+	}
+
+	packet_count = rbu_data.num_packets;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	
+	while(--packet_count) {
+		ptemp_list = ptemp_list->next;
+	}
+
+	ppacket = list_entry(ptemp_list,struct packet_data, list);
+
+	if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {
+		printk(KERN_WARNING "fill_last_packet: packet size data overrun\n");
+		return -ENOMEM;
+	}
+	
+	pr_debug("fill_last_packet : buffer = %p\n", ppacket->data);
+
+	/* copy the incoming data in to the new buffer */
+	memcpy(((char *)ppacket->data + rbu_data.packet_write_count), 
+		data, length);
+	
+	if ((rbu_data.packet_write_count + length) == rbu_data.packetsize) {
+		/* this was the last data chunk in the packet 
+		   so reinitialize the packet data counter to zero */
+		rbu_data.packet_write_count = 0;
+	} else {
+		/* adjust the total packet length */
+		rbu_data.packet_write_count += length;
+	}
+	pr_debug("fill_last_packet: exit \n");
+	return 0;
+}
+
+/*
+ get_free_pages_limited:
+ This is a helper function which allocates free pages based on an upper limit.
+ On x86_64 or 64 bit arch the memory allocation goes above 4GB space which is 
+ not addressable by the BIOS. This function tries to get allocation below the 
+ limit (4GB) address. It first tries to allocate memory normally using the 
+ GFP_KERNEL argument if the incoming limit is non-zero and if the returned 
+ physical memory address exceeds the upper limit, the allocated pages are freed 
+ and the memory is reallocated using the GFP_DMA argument.
+*/
+static void *get_free_pages_limited(unsigned long size,
+                                    int *ordernum,
+				    unsigned long limit)
+{
+	unsigned long img_buf_phys_addr;
+	void *pbuf = NULL;
+
+	*ordernum = get_order(size);
+	/* 
+         * Check if we are not getting a very large file. This can happen as a 
+         * user error in entering the file size 
+        */
+	if (*ordernum == BITS_PER_LONG) {
+		/* The incoming size is very large */
+		pr_debug("get_free_pages_limited: Incoming size is very large\n");
+		return NULL;
+	}
+	
+	/* try allocating a new buffer to fit the request */
+	pbuf =(unsigned char *)__get_free_pages(GFP_KERNEL, *ordernum);
+
+	if (pbuf != NULL) {
+        	/* check if the image is with in limits */
+		img_buf_phys_addr = (unsigned long)virt_to_phys((void *)pbuf);
+		
+		if ((limit != 0) && ((img_buf_phys_addr + size) > limit)) {
+			pr_debug("Got memory above 4GB range, free this and try with DMA memory\n");
+			/* free this memory as we need it with in 4GB range */
+			free_pages ((unsigned long)pbuf, *ordernum);
+			/* 
+                         * Try allocating a new buffer from the GFP_DMA range 
+			 * as it is with in 16MB range.
+			 */
+			pbuf =(unsigned char *)__get_free_pages(GFP_DMA, *ordernum);
+			if (pbuf == NULL)
+				pr_debug("Failed to get memory of size %ld using GFP_DMA\n", size);
+		}
+	}
+	return pbuf;
+}
+
+static int create_packet(size_t length)
+{
+	struct packet_data *newpacket;
+	int ordernum = 0;
+
+	pr_debug("create_packet: entry \n");
+
+	if (rbu_data.packetsize == 0 ) {
+		pr_debug("create_packet: packetsize not specified\n");
+		return -EINVAL;
+	}
+
+	newpacket = kmalloc(sizeof(struct packet_data) ,GFP_KERNEL);
+	if(newpacket == NULL) {
+		printk(KERN_WARNING"create_packet: failed to allocate new packet\n");
+		return -ENOMEM;
+	}
+
+	/* there is no upper limit on memory address for packetized mechanism */
+	newpacket->data = get_free_pages_limited(rbu_data.packetsize,&ordernum,	0);
+	pr_debug("create_packet: newpacket %p\n", newpacket->data);
+		
+	if(newpacket->data == NULL) {
+		printk(KERN_WARNING"create_packet: failed to allocate new packet\n");
+		return -ENOMEM;
+	}
+
+	newpacket->ordernum = ordernum;
+	++rbu_data.num_packets;
+	/* initialize the newly created packet headers */
+	INIT_LIST_HEAD(&newpacket->list);
+	/* add this packet to the link list */
+	list_add_tail(&newpacket->list, &packet_data_head.list);
+	/* 
+	 * packets are of fixed sizes so initialize 
+	 * the length to rbu_data.packetsize
+	 */
+	newpacket->length = rbu_data.packetsize;
+	
+	pr_debug("create_packet: exit \n");
+
+	return 0;
+}
+
+
+static int packetize_data(void *data, size_t length) 
+{
+	int rc = 0;
+
+	pr_debug("packetize_data : entry\n");
+	if (rbu_data.packet_write_count == 0) {
+		/* create a new packet */
+		if ((rc = create_packet(length)) != 0 )
+			return rc;
+	}
+	/* fill data in to the packet */	
+	if ((rc = fill_last_packet(data, length)) != 0)
+		return rc;
+	
+	pr_debug("packetize_data : exit\n");
+	return rc;
+}
+
+
+/*
+ do_packet_read :
+ This is a helper function which reads the packet data of the 
+ current list.
+ data: is the incoming buffer
+ ptemp_list: points to the incoming list item
+ length: is the length of the free space in the buffer.
+ bytes_read: is the total number of bytes read already from 
+ the packet list
+ list_read_count: is the counter to keep track of the number 
+ of bytes read out of each packet.
+*/
+int do_packet_read(char *data, 
+		   struct list_head *ptemp_list, 
+		   int length,
+		   int bytes_read, 
+		   int *list_read_count)
+{
+	void *ptemp_buf;
+	struct packet_data *newpacket = NULL;
+	int bytes_copied = 0;
+	int j = 0;
+
+	newpacket = list_entry(ptemp_list,struct packet_data, list);
+	*list_read_count += newpacket->length;
+
+	if (*list_read_count > bytes_read) {
+		/* point to the start of unread data */
+		j = newpacket->length - (*list_read_count - bytes_read);
+		/* point to the offset in the packet buffer*/
+		ptemp_buf = (u8 *)newpacket->data + j;
+		/* check if there is enough room in the	incoming buffer*/
+		if (length > (*list_read_count - bytes_read)) 
+			/* copy what ever is there in this packet and move on*/
+			bytes_copied = (*list_read_count - bytes_read);
+		else 
+			/* copy the remaining */
+			bytes_copied = length;
+		memcpy(data, ptemp_buf, bytes_copied);
+	} 
+	return bytes_copied;
+}
+
+/*
+ packet_read_list:
+ This function reads the data out of the packet link list.
+ It will read data from multiple packets depending upon the 
+ size of the incoming buffer.
+ data: is the incoming buffer pointer
+ *pread_length: is the length of the incoming buffer. At return 
+ this value is adjusted to the actual size of the data read.
+*/
+static int packet_read_list(char *data, size_t *pread_length)
+{
+	struct list_head *ptemp_list;
+	int temp_count = 0;
+	int bytes_copied = 0;
+	int bytes_read = 0;
+	int remaining_bytes =0;
+	char *pdest = data;
+
+	/* check if we have any packets */
+	if (0 == rbu_data.num_packets)
+		return -ENOMEM;
+
+	remaining_bytes = *pread_length;
+	/* get the current read count */
+	bytes_read = rbu_data.packet_read_count;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while(!list_empty(ptemp_list)) {
+		/* read data */
+		bytes_copied = do_packet_read(pdest, ptemp_list, remaining_bytes, 
+			bytes_read, &temp_count);
+		/* adjust the remaining bytes */
+		remaining_bytes -= bytes_copied;
+		bytes_read += bytes_copied;
+		/* adjust the data pointer */
+		pdest += bytes_copied;
+
+		/* check if we reached end of buffer before reaching the last packet*/
+		if (remaining_bytes == 0)
+			break;
+
+		/* point to the next packet in the list */
+		ptemp_list = ptemp_list->next;
+	}
+	/*finally set the bytes read */
+	*pread_length = bytes_read - rbu_data.packet_read_count;
+	rbu_data.packet_read_count = bytes_read;
+	return 0;
+}
+
+static void packet_empty_list(void)
+{
+	struct list_head *ptemp_list;
+	struct list_head *pnext_list;
+	struct packet_data *newpacket;
+
+	ptemp_list = (&packet_data_head.list)->next;
+	while(!list_empty(ptemp_list)) {
+		newpacket = list_entry(ptemp_list, struct packet_data, list);
+		/* get the next list ptr before we delete this entry */
+		pnext_list = ptemp_list->next;
+		/* remove the list entry */
+		list_del(ptemp_list);
+		/* set the list to next */
+		ptemp_list = pnext_list;
+		/* zero out the RBU packet memory before freeing */
+		memset(newpacket->data, 0, rbu_data.packetsize);
+		/* free the memory pointed by this packet */
+		free_pages((unsigned long)newpacket->data, newpacket->ordernum);
+		/* now free the packet*/
+		kfree(newpacket);
+	}
+	rbu_data.packet_write_count = 0;
+	rbu_data.packet_read_count = 0;
+	rbu_data.num_packets = 0;
+}
+
+
+/*
+ img_update_free:
+ Frees the buffer allocated for storing BIOS image
+ Always called with lock held and returned with lock held
+*/
+static void img_update_free( void)
+{
+	if (rbu_data.image_update_buffer == NULL)
+		return;
+	
+	/* zero out this buffer before freeing it */
+	memset(rbu_data.image_update_buffer, 0, rbu_data.image_update_buffer_size);
+ 	free_pages((unsigned long)rbu_data.image_update_buffer, 
+		rbu_data.image_update_order_number);
+	/* Re-initialize the rbu_data variables after a free */
+	rbu_data.image_update_buffer = NULL;
+	rbu_data.image_update_buffer_size = 0;
+	rbu_data.bios_image_size = 0;
+}
+
+/*
+ img_update_realloc:
+ This function allocates the contiguous pages to accomodate the requested
+ size of data. The memory address and size values are stored globally and 
+ on every call to this function the new size is checked to see if more 
+ data is required than the existing size. If true the previous memory is freed
+ and new allocation is done to accomodate the new size. If the incoming size is 
+ less then than the already allocated size, then that  memory is reused.
+ This function is called with lock held and returna with lock held.
+*/
+static int img_update_realloc(unsigned long size)
+{
+	unsigned char *image_update_buffer = NULL;
+	unsigned long rc;
+	int ordernum =0;
+
+
+	/* check if the buffer of sufficient size has been already allocated */
+    if (rbu_data.image_update_buffer_size >= size) {
+		/* check for corruption */
+		if ((size != 0) && (rbu_data.image_update_buffer == NULL)) {
+			pr_debug("img_update_realloc: corruption check failed\n");
+			return -EINVAL;
+		}
+		/* we have a valid pre-allocated buffer with sufficient size */
+		return 0;
+    }
+
+	/* free any previously allocated buffer */
+	img_update_free();
+
+	/* This has already been called as locked so we can now unlock 
+	and proceed to calling get_free_pages_limited as this function
+	can sleep*/
+	spin_unlock(&rbu_data.lock);
+
+	image_update_buffer = (unsigned char *)get_free_pages_limited(size,
+		&ordernum,
+		BIOS_SCAN_LIMIT);
+	
+	/* acquire the spinlock again */
+	spin_lock(&rbu_data.lock);
+
+	if (image_update_buffer != NULL) {
+		/* store address for the new buffer */
+		rbu_data.image_update_buffer = image_update_buffer;
+		/* adjust allocated size */
+		rbu_data.image_update_buffer_size = PAGE_SIZE << ordernum;
+		/* save the current order number */
+		rbu_data.image_update_order_number = ordernum;
+		/* initialize the new buffer data to 0 */
+		memset(rbu_data.image_update_buffer,0, rbu_data.image_update_buffer_size);
+		pr_debug("img_update_realloc: success\n");
+		/* success */
+		rc = 0; 
+	} else {
+		pr_debug("Not enough memory for image update:order number = %d"
+			",size = %ld\n",ordernum, size);
+		rc = -ENOMEM;
+	}
+
+	return rc;
+} /* img_update_realloc */
+
+
+/*
+ read_packet_data_size:
+ Returns the size of an RBU packet; if no packets present returns 0
+*/
+static ssize_t read_packet_data_size(struct kobject *kobj, 
+				     char *buffer,
+   				     loff_t pos, 
+				     size_t count)
+{
+	unsigned int size = 0;
+	if (pos == 0)
+		size = sprintf(buffer, "%lu\n", rbu_data.packetsize);
+	return size;
+} 
+
+/*
+ write_packet_data_size:
+ Writes the RBU data size supplied by the user, if the 
+ data size supplied is non zero number, this function 
+ records the packet size for any packet allocations.
+ If a byte size of zero is supplied this function will free
+ the previously allocated packets.
+*/
+static ssize_t write_packet_data_size(struct kobject *kobj, 
+				      char *buffer, 
+				      loff_t pos, 
+				      size_t count)
+{
+	int retval = count;
+
+	spin_lock(&rbu_data.lock);
+	/* extract the image size */
+	sscanf(buffer, "%lu",&rbu_data.packetsize);
+	/* free the previous packet lists */
+	packet_empty_list();
+
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ read_rbu_data_size:
+ Returns the size of an RBU image previously downloaded
+ if no image is downloaded the size returned is 0
+*/
+static ssize_t read_rbu_data_size(struct kobject *kobj, 
+				  char *buffer, 
+				  loff_t pos, 
+				  size_t count)
+{
+	unsigned int size = 0;
+	if (pos == 0)
+		size = sprintf(buffer, "%lu\n", rbu_data.bios_image_size);
+	return size;
+} 
+
+/*
+ write_rbu_data_size:
+ Writes the RBU data size supplied by the user, if the 
+ data size supplied is non zero number, this function 
+ allocates the contiguous physical memory pages for the 
+ supplied size , if it fails this function returns error.
+ If a byte size of zero is supplied this function will free
+ the previously allocated contiguous pages.
+*/
+static ssize_t write_rbu_data_size(struct kobject *kobj, 
+				   char *buffer, 
+				   loff_t pos, 
+				   size_t count)
+{
+	int retval = count;
+
+	spin_lock(&rbu_data.lock);
+	/* extract the image size */
+	sscanf(buffer, "%lu",&rbu_data.bios_image_size);
+
+	if (rbu_data.bios_image_size !=0 ) {
+		if (img_update_realloc(rbu_data.bios_image_size) < 0) {
+			pr_debug("write_rbu_data_size: failed to allocate mem size %lu\n", 
+				(unsigned long)rbu_data.bios_image_size);
+			rbu_data.bios_image_size = 0;
+			retval = -ENOMEM;
+		} 
+	} else {
+		pr_debug(" freeing %ld size memory \n", rbu_data.bios_image_size);
+		/* free any allocated RBU memory */
+		img_update_free();
+	}
+	
+	pr_debug("write_rbu_data_size: = %lu\n", 
+		(unsigned long)rbu_data.bios_image_size);
+
+	spin_unlock(&rbu_data.lock);
+	return retval;
+} /* write_rbu_data_size*/
+
+/*
+ read_rbu_data:
+ Reads the BIOS image file from previously allocated contiguous physical 
+ pages in to the buffer supplied in this call. 
+ The reading is done in chunks of bytes supplied in the count argument.
+ The reading stops when the total number of bytes read equals the image 
+ size given previously.
+ If image size is not specified or if the image size is zero this function 
+ returns failure.
+ Parameters: 
+ kobj is the kernel object 
+ buffer is the pointer to the incoming data buffer.
+ count is the value of the incoming buffer size,
+ pos is the amount of bytes already read.
+*/
+static ssize_t read_rbu_data(struct kobject *kobj, 
+			     char *buffer,
+			     loff_t pos, 
+			     size_t count)
+{
+	unsigned char *ptemp = NULL;
+	int retval =0;
+	size_t bytes_left = 0;
+	size_t data_length = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check to see if we have something to return */
+	if ((rbu_data.image_update_buffer == NULL) || 
+		(rbu_data.bios_image_size == 0)) {
+		pr_debug("read_rbu_data: image_update_buffer %p ,"
+			"bios_image_size %lu\n",
+			rbu_data.image_update_buffer, 
+			rbu_data.bios_image_size);
+		retval = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+	
+	if ( pos > rbu_data.bios_image_size ) {
+		retval = 0;
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = rbu_data.bios_image_size - pos;
+	data_length = max(bytes_left,count);
+
+	ptemp = rbu_data.image_update_buffer;
+	memcpy(buffer, (ptemp + pos), data_length);
+
+	if ((pos + count) > rbu_data.bios_image_size)
+		/* this was the last copy */
+		retval = bytes_left;
+	else 
+		retval = count;
+	
+read_rbu_data_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ write_rbu_data
+ Writes from the incoming BIOS image file to the pre-allocated 
+ contiguous physical memory pages. 
+ The writes occur in chunks of memory supplied by the count. The writes 
+ stops when the total memory supplied equals the image size given previously.
+ If no memory size is previously specified or if the previously specified size 
+ is zero the write returns error.
+*/
+static ssize_t write_rbu_data(struct kobject *kobj, 
+			      char *buffer,
+			      loff_t pos, 
+			      size_t count)
+{
+	unsigned char *pDest = NULL;
+	unsigned char *ptemp = NULL;
+	int retval = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check if the image size is given */
+	if (0 == rbu_data.bios_image_size) {
+		printk(KERN_WARNING"write_rbu_data: BIOS image size not set\n");
+		retval = -ENOMEM;
+		goto error_exit;
+	}
+
+	if ((pos + count) > rbu_data.bios_image_size) {
+		pr_debug("write_rbu_data: data_over_run, file pos %lu "
+			"bios_image_size %lu\n",
+			(unsigned long)pos,
+			rbu_data.bios_image_size);
+		retval = -ENOMEM;
+		goto error_exit;
+	}
+
+	pDest = (unsigned char*)rbu_data.image_update_buffer;
+	ptemp = pDest + pos;
+	memcpy(ptemp, buffer,  count);
+	retval = count;
+	pr_debug("write_rbu_data : retval = %d\n", retval);
+error_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ read_rbu_packet_data:
+ Reads the BIOS image file from previously allocated contiguous physical 
+ pages in to the buffer supplied in this call. 
+ The reading is done in chunks of bytes supplied in the count argument.
+ The reading stops when the total number of bytes read equals the image 
+ size given previously.
+ If image size is not specified or if the image size is zero this function 
+ returns failure.
+ Parameters: 
+ kobj is the kernel object 
+ buffer is the pointer to the incoming data buffer.
+ count is the value of the incoming buffer size,
+ pos is the amount of bytes already read.
+*/
+static ssize_t read_rbu_packet_data(struct kobject *kobj, 
+				    char *buffer,
+				    loff_t pos, 
+				    size_t count)
+{
+	int retval;
+	size_t bytes_left;
+	size_t data_length;
+	char *ptempBuf = buffer;
+	unsigned long imagesize;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check to see if we have something to return */
+	if (rbu_data.num_packets == 0) {
+		pr_debug("read_rbu_packet_data: no packets written\n");
+		retval = -ENOMEM;
+		goto read_rbu_data_exit;
+	}
+
+	imagesize = rbu_data.num_packets * rbu_data.packetsize;
+	
+	if ( pos > imagesize ) {
+		retval = 0;
+		printk(KERN_WARNING"read_rbu_packet_data: data underrun\n");
+		goto read_rbu_data_exit;
+	}
+
+	bytes_left = imagesize - pos;
+	data_length = max(bytes_left, count);
+
+	if ((retval = packet_read_list(ptempBuf, &data_length)) < 0)
+		goto read_rbu_data_exit;
+
+	if ((pos + count) > imagesize) {
+		rbu_data.packet_read_count = 0;
+		/* this was the last copy */
+		retval = bytes_left;
+	}
+	else 
+		retval = count;
+	
+read_rbu_data_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+/*
+ write_rbu_packet_data
+ Writes from the incoming BIOS image file to the pre-allocated 
+ contiguous physical memory pages. 
+ The writes occur in chunks of memory supplied by the count. The writes 
+ stops when the total memory supplied equals the image size given previously.
+ If no memory size is previously specified or if the previously specified size 
+ is zero the write returns error.
+*/
+static ssize_t write_rbu_packet_data(struct kobject *kobj, 
+				     char *buffer,
+				     loff_t pos, 
+				     size_t count)
+{
+	int retval = 0;
+
+	spin_lock(&rbu_data.lock);
+
+	/* check if the packet size is given */
+	if (0 == rbu_data.packetsize) {
+		printk(KERN_WARNING"write_rbu_packet_data: packetsize not set\n");
+		retval = -ENOMEM;
+		goto error_exit;
+	}
+
+	if ((pos + count) > rbu_data.packetsize) {
+		pr_debug("write_rbu_packet_data: data_over_run, file pos %lu,"
+			"packetsize %lu\n",
+			(unsigned long)pos,
+			rbu_data.packetsize);
+		retval = -ENOMEM;
+
+		/* We have a write data overrun, obviously this is 
+		not the corret file, so free the previous data*/
+		pr_debug("data overrun freeing all the previous packets\n");
+		packet_empty_list();
+		goto error_exit;
+	}
+
+	if ((retval = packetize_data(buffer, count)) < 0 ) {
+		pr_debug(KERN_WARNING"write_rbu_packet_data: packetize_data "
+			"failed with status %d\n", retval);
+		retval = -EIO;
+		goto error_exit;
+	} 
+
+	retval = count;
+
+	pr_debug("write_rbu_packet_data : retval = %d\n", retval);
+
+error_exit:
+	spin_unlock(&rbu_data.lock);
+	return retval;
+}
+
+
+static struct bin_attribute rbudata_attr = {
+	.attr = {.name = "rbudata", .owner = THIS_MODULE, .mode = 0644},
+	.read = read_rbu_data,
+	.write = write_rbu_data,
+};
+
+static struct bin_attribute rbudatasize_attr = {
+	.attr = { .name = "rbudatasize", .owner = THIS_MODULE, .mode = 0644 },
+	.read = read_rbu_data_size,
+	.write= write_rbu_data_size,
+};
+
+static struct bin_attribute packetdatasize_attr = {
+	.attr = { .name = "packetdatasize", .owner = THIS_MODULE, .mode = 0644 },
+	.read = read_packet_data_size,
+	.write= write_packet_data_size,
+};
+
+static struct bin_attribute packetdata_attr = {
+	.attr = { .name = "packetdata", .owner = THIS_MODULE, .mode = 0644 },
+	.read = read_rbu_packet_data,
+	.write= write_rbu_packet_data,
+};
+
+static int __init dcdrbu_init(void)
+{
+	int rc;
+
+	spin_lock_init(&rbu_data.lock);
+
+	init_packet_head();
+	
+	rc = firmware_register(&rbu_subsys);
+	if (rc < 0) {
+		printk(KERN_WARNING"dcdrbu_init: firmware_register failed\n");
+		return rc;
+	}
+
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&rbudata_attr);
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&rbudatasize_attr);
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&packetdatasize_attr);
+	sysfs_create_bin_file(&rbu_subsys.kset.kobj,&packetdata_attr);
+
+	return rc;
+}
+
+static __exit void dcdrbu_exit( void)
+{
+	spin_lock(&rbu_data.lock);
+	packet_empty_list();
+	img_update_free();
+	spin_unlock(&rbu_data.lock);
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &rbudata_attr );
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &rbudatasize_attr );
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &packetdatasize_attr );
+	sysfs_remove_bin_file(&rbu_subsys.kset.kobj, &packetdata_attr );
+	firmware_unregister(&rbu_subsys);
+} 
+
+module_exit(dcdrbu_exit);
+module_init(dcdrbu_init);
+
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Kconfig linux-2.6.11.8/drivers/firmware/Kconfig
--- linux-2.6.11.8.ORIG/drivers/firmware/Kconfig	2005-05-13 12:07:58.965181016 -0500
+++ linux-2.6.11.8/drivers/firmware/Kconfig	2005-05-13 12:07:00.566059032 -0500
@@ -58,4 +58,16 @@ config EFI_PCDP
 
 	  See <http://www.dig64.org/specifications/DIG64_HCDPv20_042804.pdf>
 
+config DELL_RBU
+        tristate "BIOS update support for DELL systems via sysfs"
+        default n
+        help
+          Say Y if you want to have the option of updating the BIOS for your
+	  DELL system. Note you need a supporting application to comunicate 
+	  with the BIOS regardign the new image for the image update to 
+	  take effect.
+
+	  See <file:Documentation/DELL_RBU.txt> for more details on the driver.
+
+
 endmenu
diff -uprN linux-2.6.11.8.ORIG/drivers/firmware/Makefile linux-2.6.11.8/drivers/firmware/Makefile
--- linux-2.6.11.8.ORIG/drivers/firmware/Makefile	2005-05-13 12:08:12.839071864 -0500
+++ linux-2.6.11.8/drivers/firmware/Makefile	2005-05-09 15:15:16.000000000 -0500
@@ -4,3 +4,4 @@
 obj-$(CONFIG_EDD)             	+= edd.o
 obj-$(CONFIG_EFI_VARS)		+= efivars.o
 obj-$(CONFIG_EFI_PCDP)		+= pcdp.o
+obj-$(CONFIG_DELL_RBU)		+= dell_rbu.o

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2005-07-20 18:52 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-05-19 12:03 [patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver Abhay_Salunke
2005-05-19 14:42 ` Greg KH
  -- strict thread matches above, loose matches on Subject: below --
2005-07-20 23:50 [patch 2.6.12-rc3]dell_rbu: " Abhay Salunke
2005-07-09  1:07 Abhay Salunke
2005-06-17 14:55 [patch 2.6.12-rc3] dell_rbu: " Abhay_Salunke
2005-06-17 15:29 ` Greg KH
2005-06-15 17:59 Abhay Salunke
2005-06-16 18:52 ` Greg KH
2005-06-20  0:36 ` Andrew Morton
2005-06-02 23:26 Abhay Salunke
2005-06-02 23:58 ` Marcel Holtmann
2005-06-03 12:32   ` Andreas Henriksson
2005-06-05 21:51   ` Jesper Juhl
2005-06-02 18:36 Abhay Salunke
2005-06-02 21:44 ` Marcel Holtmann
2005-05-26 18:43 Abhay_Salunke
2005-05-26 16:37 Abhay_Salunke
2005-05-26 16:56 ` Matt Domsch
2005-05-26 20:37 ` Greg KH
2005-05-26 21:36   ` Marcel Holtmann
2005-05-23 14:52 Abhay_Salunke
2005-05-23 14:58 ` Arjan van de Ven
2005-05-23 14:59 ` Marcel Holtmann
2005-05-23 15:48 ` Greg KH
2005-05-18 18:13 Abhay Salunke
2005-05-19  3:32 ` Greg KH
2005-05-13 19:00 Abhay Salunke
2005-05-13 21:11 ` Alexey Dobriyan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).