aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86_64/kernel
diff options
context:
space:
mode:
authorIngo Molnar <mingo@elte.hu>2006-12-20 05:53:32 -0500
committerLinus Torvalds <torvalds@woody.osdl.org>2006-12-21 03:08:28 -0500
commit136f1e7a8cb7d17ff91706518549697071640ae4 (patch)
tree5a6ad3a174c8d274905c5ec34052e091563a97b5 /arch/x86_64/kernel
parenta9622f6219ce58faba1417743bf3078501eb3434 (diff)
[PATCH] x86_64: fix boot time hang in detect_calgary()
if CONFIG_CALGARY_IOMMU is built into the kernel via CONFIG_CALGARY_IOMMU_ENABLED_BY_DEFAULT, or is enabled via the iommu=calgary boot option, then the detect_calgary() function runs to detect the presence of a Calgary IOMMU. detect_calgary() first searches the BIOS EBDA area for a "rio_table_hdr" BIOS table. It has this parsing algorithm for the EBDA: while (offset) { ... /* The next offset is stored in the 1st word. 0 means no more */ offset = *((unsigned short *)(ptr + offset)); } got that? Lets repeat it slowly: we've got a BIOS-supplied data structure, plus Linux kernel code that will only break out of an infinite parsing loop once the BIOS gives a zero offset. Ok? Translation: what an excellent opportunity for BIOS writers to lock up the Linux boot process in an utterly hard to debug place! Indeed the BIOS jumped on that opportunity on my box, which has the following EBDA chaining layout: 384, 65282, 65535, 65535, 65535, 65535, 65535, 65535 ... see the pattern? So my, definitely non-Calgary system happily locks up in detect_calgary()! the patch below fixes the boot hang by trusting the BIOS-supplied data structure a bit less: the parser always has to make forward progress, and if it doesnt, we break out of the loop and i get the expected kernel message: Calgary: Unable to locate Rio Grande Table in EBDA - bailing! Signed-off-by: Ingo Molnar <mingo@elte.hu> Acked-by: Muli Ben-Yehuda <muli@il.ibm.com> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'arch/x86_64/kernel')
-rw-r--r--arch/x86_64/kernel/pci-calgary.c11
1 files changed, 8 insertions, 3 deletions
diff --git a/arch/x86_64/kernel/pci-calgary.c b/arch/x86_64/kernel/pci-calgary.c
index 3215675ab128..87d90cb68a74 100644
--- a/arch/x86_64/kernel/pci-calgary.c
+++ b/arch/x86_64/kernel/pci-calgary.c
@@ -1052,7 +1052,7 @@ void __init detect_calgary(void)
1052 void *tbl; 1052 void *tbl;
1053 int calgary_found = 0; 1053 int calgary_found = 0;
1054 unsigned long ptr; 1054 unsigned long ptr;
1055 int offset; 1055 unsigned int offset, prev_offset;
1056 int ret; 1056 int ret;
1057 1057
1058 /* 1058 /*
@@ -1071,15 +1071,20 @@ void __init detect_calgary(void)
1071 ptr = (unsigned long)phys_to_virt(get_bios_ebda()); 1071 ptr = (unsigned long)phys_to_virt(get_bios_ebda());
1072 1072
1073 rio_table_hdr = NULL; 1073 rio_table_hdr = NULL;
1074 prev_offset = 0;
1074 offset = 0x180; 1075 offset = 0x180;
1075 while (offset) { 1076 /*
1077 * The next offset is stored in the 1st word.
1078 * Only parse up until the offset increases:
1079 */
1080 while (offset > prev_offset) {
1076 /* The block id is stored in the 2nd word */ 1081 /* The block id is stored in the 2nd word */
1077 if (*((unsigned short *)(ptr + offset + 2)) == 0x4752){ 1082 if (*((unsigned short *)(ptr + offset + 2)) == 0x4752){
1078 /* set the pointer past the offset & block id */ 1083 /* set the pointer past the offset & block id */
1079 rio_table_hdr = (struct rio_table_hdr *)(ptr + offset + 4); 1084 rio_table_hdr = (struct rio_table_hdr *)(ptr + offset + 4);
1080 break; 1085 break;
1081 } 1086 }
1082 /* The next offset is stored in the 1st word. 0 means no more */ 1087 prev_offset = offset;
1083 offset = *((unsigned short *)(ptr + offset)); 1088 offset = *((unsigned short *)(ptr + offset));
1084 } 1089 }
1085 if (!rio_table_hdr) { 1090 if (!rio_table_hdr) {