[patch 2.6.12-rc3] dell_rbu: Resubmitting patch for new Dell BIOS update driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 <[email protected]>

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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux