From: "Janice M. Girouard" <jgirouar@redhat.com> Subject: repost RHBZ# 214690 RHIT106304 - update_flash is broken across POWER for RHEL5 (fwd) Date: Tue, 28 Nov 2006 16:12:27 -0500 (Eastern Standard Time) Bugzilla: 214690 Message-Id: <Pine.WNT.4.64.0611281610530.2852@IBM-3MTQI3AXJFW> Changelog: update_flash is broken across PPC reposting an earlier post, with the patch content correctly inline. RHBZ#: ------ https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=214690 Description: ------------ When attempting an in-band firmware update on RHEL5, the console displays an oops message and the update fails. This failure will occur on any POWER system running RHEL5. RHEL5 use of 64k pages exposed an undocumented firmware quirk that makes it impossible to update system firmware from within a RHEL5 partition. The attached patch forces the rtas_flash module to use 4k memory blocks and list sizes when preparing and sending a firmware image to RTAS, regardless of the page size of the kernel. It also changes rtas_flash to use a slab cache to ensure 4k alignment of flash block data. Such alignment is a documented requirement. The fix only affects POWER code, and the code path in question only comes into play when attempting to update system firmware. RHEL Version Found: ------------------- Observed on Kernel 2.6.17-1.2519.4.26.el5 Upstream Status: ---------------- This fix has been accepted upstream prior to 2.6.19: http://www.kernel.org/git/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=ae883cab9457aad0fb3342249e1207873d3b64de Test Status: ------------ This patch was tested by Doug Maxey of IBM against a JS20 with the -2739 kernel. Test results showed a successful update of flash without oops. This patch builds cleanly against kernel-2.6.18-1.2747.el5.src.rpm. Proposed Patch: --------------- Please review and ACK for RHEL 5.0 --- diff -puN arch/powerpc/kernel/rtas_flash.c~uf_4k_alt2 arch/powerpc/kernel/rtas_flash.c --- uf_4k/arch/powerpc/kernel/rtas_flash.c~uf_4k_alt2 2006-11-06 16:59:27.000000000 -0600 +++ uf_4k-johnrose/arch/powerpc/kernel/rtas_flash.c 2006-11-07 22:29:52.000000000 -0600 @@ -72,6 +72,10 @@ #define VALIDATE_BUF_SIZE 4096 #define RTAS_MSG_MAXLEN 64 +/* Quirk - RTAS requires 4k list length and block size */ +#define RTAS_BLKLIST_LENGTH 4096 +#define RTAS_BLK_SIZE 4096 + struct flash_block { char *data; unsigned long length; @@ -83,7 +87,7 @@ struct flash_block { * into a version/length and translate the pointers * to absolute. */ -#define FLASH_BLOCKS_PER_NODE ((PAGE_SIZE - 16) / sizeof(struct flash_block)) +#define FLASH_BLOCKS_PER_NODE ((RTAS_BLKLIST_LENGTH - 16) / sizeof(struct flash_block)) struct flash_block_list { unsigned long num_blocks; struct flash_block_list *next; @@ -96,6 +100,9 @@ struct flash_block_list_header { /* just static struct flash_block_list_header rtas_firmware_flash_list = {0, NULL}; +/* Use slab cache to guarantee 4k alignment */ +static kmem_cache_t *flash_block_cache = NULL; + #define FLASH_BLOCK_LIST_VERSION (1UL) /* Local copy of the flash block list. @@ -153,7 +160,7 @@ static int flash_list_valid(struct flash return FLASH_IMG_NULL_DATA; } block_size = f->blocks[i].length; - if (block_size <= 0 || block_size > PAGE_SIZE) { + if (block_size <= 0 || block_size > RTAS_BLK_SIZE) { return FLASH_IMG_BAD_LEN; } image_size += block_size; @@ -177,9 +184,9 @@ static void free_flash_list(struct flash while (f) { for (i = 0; i < f->num_blocks; i++) - free_page((unsigned long)(f->blocks[i].data)); + kmem_cache_free(flash_block_cache, f->blocks[i].data); next = f->next; - free_page((unsigned long)f); + kmem_cache_free(flash_block_cache, f); f = next; } } @@ -278,6 +285,12 @@ static ssize_t rtas_flash_read(struct fi return msglen; } +/* constructor for flash_block_cache */ +void rtas_block_ctor(void *ptr, kmem_cache_t *cache, unsigned long flags) +{ + memset(ptr, 0, RTAS_BLK_SIZE); +} + /* We could be much more efficient here. But to keep this function * simple we allocate a page to the block list no matter how small the * count is. If the system is low on memory it will be just as well @@ -302,7 +315,7 @@ static ssize_t rtas_flash_write(struct f * proc file */ if (uf->flist == NULL) { - uf->flist = (struct flash_block_list *) get_zeroed_page(GFP_KERNEL); + uf->flist = kmem_cache_alloc(flash_block_cache, GFP_KERNEL); if (!uf->flist) return -ENOMEM; } @@ -313,21 +326,21 @@ static ssize_t rtas_flash_write(struct f next_free = fl->num_blocks; if (next_free == FLASH_BLOCKS_PER_NODE) { /* Need to allocate another block_list */ - fl->next = (struct flash_block_list *)get_zeroed_page(GFP_KERNEL); + fl->next = kmem_cache_alloc(flash_block_cache, GFP_KERNEL); if (!fl->next) return -ENOMEM; fl = fl->next; next_free = 0; } - if (count > PAGE_SIZE) - count = PAGE_SIZE; - p = (char *)get_zeroed_page(GFP_KERNEL); + if (count > RTAS_BLK_SIZE) + count = RTAS_BLK_SIZE; + p = kmem_cache_alloc(flash_block_cache, GFP_KERNEL); if (!p) return -ENOMEM; if(copy_from_user(p, buffer, count)) { - free_page((unsigned long)p); + kmem_cache_free(flash_block_cache, p); return -EFAULT; } fl->blocks[next_free].data = p; @@ -791,6 +804,16 @@ int __init rtas_flash_init(void) goto cleanup; rtas_flash_term_hook = rtas_flash_firmware; + + flash_block_cache = kmem_cache_create("rtas_flash_cache", + RTAS_BLK_SIZE, RTAS_BLK_SIZE, 0, + rtas_block_ctor, NULL); + if (!flash_block_cache) { + printk(KERN_ERR "%s: failed to create block cache\n", + __FUNCTION__); + rc = -ENOMEM; + goto cleanup; + } return 0; cleanup: @@ -805,6 +828,10 @@ cleanup: void __exit rtas_flash_cleanup(void) { rtas_flash_term_hook = NULL; + + if (flash_block_cache) + kmem_cache_destroy(flash_block_cache); + remove_flash_pde(firmware_flash_pde); remove_flash_pde(firmware_update_pde); remove_flash_pde(validate_pde); _