Re: [patch 2.6.12-rc3] dell_rbu: New Dell BIOS update driver

From: Andrew Morton
Date: Tue May 10 2005 - 19:02:40 EST


Abhay Salunke <Abhay_Salunke@xxxxxxxx> wrote:
>
> The dell_rbu driver is required for updating BIOS on DELL servers and client
> systems. The driver lets a user application download the BIOS image in to
> contiguous physical memory pages; the driver exposes the memory via sysfs
> filesystem. The update mechanism basically has two approcahes; one by
> allocating contiguous physical memory and the second approach is by allocating
> small chunks of contiguous physical memory.
>
> The patch file is attached to this mail.

Greg, could you please review the sysfs usage?

> --- 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-09 16:34:47.477703152 -0500

2.6.11.8 is very old in kernel terms. It's OK for this patch because it's
pretty standalone. But in general, please raise patches against
contemporary kernels. 2.6.12-rc4 would be appropriate at present.

> +
> +#define BIT(x) (1UL << ((x)%BITS_PER_LONG))

This is used only for:

+ rbu_data.image_update_buffer_size = BIT(ordernum) * PAGE_SIZE;

Please remove BIT() and just do

+ rbu_data.image_update_buffer_size = PAGE_SIZE << ordernum;

> +
> +static struct {

Some gcc's don't like anonymous structs (I think?). Still, it would be
more conventional to give this guy a name.

> + 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 ;

Make this

} rbu_data;

> +struct packet_data{

struct packet_data {

> + struct list_head list;
> + size_t length;
> + void *rbu_data;

Should that be

struct rbu_data *rbu_data;

?

> +
> +static decl_subsys(rbu,&ktype_rbu,NULL);
> +

static decl_subsys(rbu, &ktype_rbu, NULL);

> +static void init_rbu_lock(void *plock)
> +{
> + spin_lock_init((spinlock_t *)plock);
> +}
> +
> +static void lock_rbu_access(void *plock)
> +{
> + spin_lock((spinlock_t *)plock);
> +}
> +
> +static void unlock_rbu_access(void *plock)
> +{
> + spin_unlock((spinlock_t *)plock);
> +}

Remove these and simply open-code the spinlock functions.

> +void init_packet_head(void)
> +{
> + INIT_LIST_HEAD((struct list_head *)&packet_data_head);

OK, in lots of places the patch typecasts packet_data_head to a list_head,
then does list_head operations on it, relying on the fact that
packet_data.list is the first element. Very weird.

Please replace all those with (for example)

INIT_LIST_HEAD(&packet_data_head.list);

> +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){

I know that some people like the `if (constant == variable)' thing to pick
up "= instead of ==" typos. But gcc does warn about that mistake anyway,
and I think most people find the above construct to be artificial, and
harder to read. Would prefer that all these be swapped around please.

Would also prefer a space before the '{'!


> + ptemp_list = ((struct list_head *)&packet_data_head)->next;

Use packet_head_data.list

> +
> + while(--packet_count)
> + {

while (--packet_count) {

> + if ((rbu_data.packet_write_count + length) > rbu_data.packetsize) {

^^

> + printk(KERN_WARNING"fill_last_packet: packet size data overrun\n");

^ space here

> +*/
> +static void *get_free_pages_limited(unsigned long size,
> + int *ordernum,
> + unsigned long limit)

Wild whitespace here?

> +{
> + unsigned long ImgBufPhysAddr;

Please stick with lower case and underscores in identifiers.

> + 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 */

The comment layout is a bit odd.

/*
* 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){

^ space

> + /* 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 */
> + ImgBufPhysAddr = (unsigned long)virt_to_phys((void *)pbuf);
> +
> + if ((limit != 0) &&
> + ((ImgBufPhysAddr + size) > limit)) {
> +
> + pr_debug("Got memory above 4GB range, free this and try with DMA memory\n");

The indenting has gone funny here.

> +
> + /* 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;
> +}

What architecture is this code designed for? On x86 a GFP_KERNEL
allocation will never return highmem. I guess x86_64?

I assume this code is here because the x86_64 BIOS will only access the
lower 4GB? If so, a comment to that extent would be useful.

Sometime I expect that x86_64 will gain a new zone, GFP_DMA32 which will be
guaranteed to return memory below he 4GB point. When that happens, this
driver should be converted to use it.

> +static int create_packet(size_t length)
> +{
> + struct packet_data *newpacket;
> + int ordernum =0;

^ space

> + newpacket = kmalloc(sizeof(struct packet_data) , GFP_KERNEL);
^

> + if(newpacket == NULL){

^ ^ spaces

> + if(newpacket->rbu_data == NULL){

Ditto. Lots of places.

> + 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, (struct list_head *)&packet_data_head);

Does this list not need locking?

Use packet_data_head.list, remove the typecast.

> + /* packets are of fixed sizes so initialize the length to rbu_data.packetsize*/

Try to fit the code into an 80-column xterm, please.

> +/*
> + 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,

It's (a bit) more conventional to not have the space after the `*' when
declaring pointers:

char *data

Here, both styles are used in the same line!

> +{
> + void *ptemp_buf;
> + struct packet_data *newpacket = NULL;

This initialisation is unneeded.

> + int bytes_copied = 0;
> + void *pDest = data;

lowercase and underscore in identifiers

> + 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->rbu_data + j;

The cast is actually unneeded - gcc will treat void* as a "pointer to
something which has sizeof==1" when performing such arithmetic. I believe
this is a gcc extension.

> + /* check if there is enough room in the
> + incoming buffer*/

Odd comment layout.

> +/*
> + 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;

Are all these initialisations actually needed?

> + ptemp_list = ((struct list_head *)&packet_data_head)->next;

packet_data_head.next

> + while(!list_empty(ptemp_list))
> + {

while (!list_empty(ptemp_list)) {

> +static void packet_empty_list(void)
> +{
> + struct list_head *ptemp_list;
> + struct list_head *pnext_list;
> + struct packet_data *newpacket;
> +
> + ptemp_list = ((struct list_head *)&packet_data_head)->next;

ditto

> + while(!list_empty(ptemp_list))
> + {

ditto

I think list_for_each_entry_safe() would work here.

> +/*
> + img_update_free:
> + Frees the buffer allocated for storing BIOS image
> + Always called with lock held
> +*/
> +static void img_update_free( void)

static void img_update_free(void)

> +{
> + if (rbu_data.image_update_buffer == NULL)
> + return;

Can this happen?

> + /* zero out this buffer before freeing it */
> + memset(rbu_data.image_update_buffer, 0, rbu_data.image_update_buffer_size);
> + /* unlock as free_pages might sleep */
> + unlock_rbu_access(&rbu_data.lock);
> + free_pages((unsigned long)rbu_data.image_update_buffer,
> + rbu_data.image_update_order_number);
> + lock_rbu_access(&rbu_data.lock);

free_pages() will never sleep.

> + rbu_data.image_update_buffer = NULL;
> + rbu_data.image_update_buffer_size = 0;
> + rbu_data.bios_image_size = 0;
> +}

Are these assignments needed?

> +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 -ENOMEM;

ENOMEM seems to be the wrong error to return here.

> + }
> + /* 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*/
> + unlock_rbu_access(&rbu_data.lock);
> +
> + image_update_buffer = (unsigned char *)get_free_pages_limited(size,

get_free_pages_limited() returns void*, so no typecast is needed.

> + &ordernum,
> + BIOS_SCAN_LIMIT);
> +
> + /* acquire the semaphore again */

It's actually a spinlock, not a semaphore.

> + lock_rbu_access(&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 = BIT(ordernum) * PAGE_SIZE;
> + /* 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 */

The comment over img_update_realloc() should mention that it returns with
the lock held.

> +
> +/*
> + 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) {
> + lock_rbu_access(&rbu_data.lock);
> + size = sprintf(buffer, "%lu\n", rbu_data.packetsize);
> + unlock_rbu_access(&rbu_data.lock);

The locking here is meaningless. rbu_data.packetsize can change any time
before or after the read.


> + }
> + 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)

80-cols.

> +static ssize_t read_rbu_data_size(struct kobject *kobj, char *buffer,
> + loff_t pos, size_t count)

ditto.

> +{
> + unsigned int size = 0;
> + if (pos == 0) {
> + lock_rbu_access(&rbu_data.lock);
> + size = sprintf(buffer, "%lu\n", rbu_data.bios_image_size);
> + unlock_rbu_access(&rbu_data.lock);

Unneeded or incorrect locking?

> +*/
> +static ssize_t write_rbu_data_size(struct kobject *kobj, char *buffer,
> + loff_t pos, size_t count)

80-cols?

> +static ssize_t read_rbu_data(struct kobject *kobj, char *buffer,
> + loff_t pos, size_t count)
> +{
> + unsigned char *ptemp = NULL;
> + int retVal =0;
> + int bytes_left = 0;
> + int data_length = 0;

Unneeded initialisations. Please review all the code for that.

s/retVal/retval/

> + data_length = ((bytes_left > count) ? count : bytes_left);

max()?

> + ptemp = (char *)rbu_data.image_update_buffer;

unneeded typecast

> + memcpy(buffer, (char *)(ptemp + pos), data_length);

unneeded typecast

> + if ( pos > imagesize ) {

Quite a mix of coding styles in this patch!

> + data_length = ((bytes_left > count) ? count : bytes_left);

max()

> + 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;
> +

} else {
retVal = count;
}

(opinions differ...)

> + 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 );
> + 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 );

whitepsace

>
> +config DELL_RBU
> + bool "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.
> +

Should this feature be available for all architectures, or only for X86 ||
X86_64? (If it compiles OK for other architectures then I'd leave it
as-is, for coverage).

The patch seems to alternate between using hard tabs and using
eight-spaces. Hard tabs are preferred. Please hunt that down and fix it
up.

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/