Add support for memory modules of 32 GB and more.
Add support for memory arrays of 2 TB and more.
---
 dmidecode.c |  175 ++++++++++++++++++++++++++++++++++++++++++++++++------------
 util.c      |   16 +++++
 util.h      |    1 
 3 files changed, 158 insertions(+), 34 deletions(-)

--- a/dmidecode.c
+++ b/dmidecode.c
@@ -193,6 +193,50 @@ static void dmi_dump(struct dmi_header *
 	}
 }
 
+/* shift is 0 if the value is in bytes, 1 if it is in kilobytes */
+static void dmi_print_memory_size(u64 code, int shift)
+{
+	unsigned long capacity;
+	u16 split[7];
+	static const char *unit[8] = {
+		"bytes", "kB", "MB", "GB", "TB", "PB", "EB", "ZB"
+	};
+	int i;
+
+	/*
+	 * We split the overall size in powers of thousand: EB, PB, TB, GB,
+	 * MB, kB and B. In practice, it is expected that only one or two
+	 * (consecutive) of these will be non-zero.
+	 */
+	split[0] = code.l & 0x3FFUL;
+	split[1] = (code.l >> 10) & 0x3FFUL;
+	split[2] = (code.l >> 20) & 0x3FFUL;
+	split[3] = ((code.h << 2) & 0x3FCUL) | (code.l >> 30);
+	split[4] = (code.h >> 8) & 0x3FFUL;
+	split[5] = (code.h >> 18) & 0x3FFUL;
+	split[6] = code.h >> 28;
+
+	/*
+	 * Now we find the highest unit with a non-zero value. If the following
+	 * is also non-zero, we use that as our base. If the following is zero,
+	 * we simply display the highest unit.
+	 */
+	for (i = 6; i > 0; i--)
+	{
+		if (split[i])
+			break;
+	}
+	if (i > 0 && split[i - 1])
+	{
+		i--;
+		capacity = split[i] + (split[i + 1] << 10);
+	}
+	else
+		capacity = split[i];
+
+	printf(" %lu %s", capacity, unit[i + shift]);
+}
+
 /*
  * 3.3.1 BIOS Information (Type 0)
  */
@@ -2029,21 +2073,6 @@ static const char *dmi_memory_array_ec_t
 	return out_of_spec;
 }
 
-static void dmi_memory_array_capacity(u32 code)
-{
-	if(code==0x8000000)
-		printf(" Unknown");
-	else
-	{
-		if((code&0x000FFFFF)==0)
-			printf(" %u GB", code>>20);
-		else if((code&0x000003FF)==0)
-			printf(" %u MB", code>>10);
-		else
-			printf(" %u kB", code);
-	}
-}
-
 static void dmi_memory_array_error_handle(u16 code)
 {
 	if(code==0xFFFE)
@@ -2084,6 +2113,19 @@ static void dmi_memory_device_size(u16 c
 	}
 }
 
+static void dmi_memory_device_extended_size(u32 code)
+{
+	code &= 0x7FFFFFFFUL;
+
+	/* Use the most suitable unit depending on size */
+	if (code & 0x3FFUL)
+		printf(" %lu MB", (unsigned long)code);
+	else if (code & 0xFFFFFUL)
+		printf(" %lu GB", (unsigned long)code >> 10);
+	else
+		printf(" %lu TB", (unsigned long)code >> 20);
+}
+
 static const char *dmi_memory_device_form_factor(u8 code)
 {
 	/* 3.3.18.1 */
@@ -2278,12 +2320,22 @@ static void dmi_mapped_address_size(u32
 {
 	if(code==0)
 		printf(" Invalid");
-	else if((code&0x000FFFFF)==0)
-		printf(" %u GB", code>>20);
-	else if((code&0x000003FF)==0)
-		printf(" %u MB", code>>10);
 	else
-		printf(" %u kB", code);
+	{
+		u64 size;
+
+		size.h = 0;
+		size.l = code;
+		dmi_print_memory_size(size, 1);
+	}
+}
+
+static void dmi_mapped_address_extended_size(u64 start, u64 end)
+{
+	if (start.h == end.h && start.l == end.l)
+		printf(" Invalid");
+	else
+		dmi_print_memory_size(u64_range(start, end), 0);
 }
 
 /*
@@ -3309,7 +3361,21 @@ static void dmi_decode(struct dmi_header
 			printf("\tError Correction Type: %s\n",
 				dmi_memory_array_ec_type(data[0x06]));
 			printf("\tMaximum Capacity:");
-			dmi_memory_array_capacity(DWORD(data+0x07));
+			if (DWORD(data + 0x07) == 0x80000000)
+			{
+				if (h->length < 0x17)
+					printf(" Unknown");
+				else
+					dmi_print_memory_size(QWORD(data + 0x0F), 0);
+			}
+			else
+			{
+				u64 capacity;
+
+				capacity.h = 0;
+				capacity.l = DWORD(data + 0x07);
+				dmi_print_memory_size(capacity, 1);
+			}
 			printf("\n");
 			if(!(opt.flags & FLAG_QUIET))
 			{
@@ -3339,7 +3405,10 @@ static void dmi_decode(struct dmi_header
 			dmi_memory_device_width(WORD(data+0x0A));
 			printf("\n");
 			printf("\tSize:");
-			dmi_memory_device_size(WORD(data+0x0C));
+			if (h->length >= 0x20 && WORD(data + 0x0C) == 0x7FFF)
+				dmi_memory_device_extended_size(DWORD(data + 0x1C));
+			else
+				dmi_memory_device_size(WORD(data + 0x0C));
 			printf("\n");
 			printf("\tForm Factor: %s\n",
 				dmi_memory_device_form_factor(data[0x0E]));
@@ -3396,12 +3465,31 @@ static void dmi_decode(struct dmi_header
 		case 19: /* 3.3.20 Memory Array Mapped Address */
 			printf("Memory Array Mapped Address\n");
 			if(h->length<0x0F) break;
-			printf("\tStarting Address: 0x%08X%03X\n",
-				DWORD(data+0x04)>>2, (DWORD(data+0x04)&0x3)<<10);
-			printf("\tEnding Address: 0x%08X%03X\n",
-				DWORD(data+0x08)>>2, ((DWORD(data+0x08)&0x3)<<10)+0x3FF);
-			printf("\tRange Size:");
-			dmi_mapped_address_size(DWORD(data+0x08)-DWORD(data+0x04)+1);
+			if (h->length >= 0x1F && DWORD(data + 0x04) == 0xFFFFFFFF)
+			{
+				u64 start, end;
+
+				start = QWORD(data + 0x0F);
+				end = QWORD(data + 0x17);
+
+				printf("\tStarting Address: 0x%08X%08Xk\n",
+					start.h, start.l);
+				printf("\tEnding Address: 0x%08X%08Xk\n",
+					end.h, end.l);
+				printf("\tRange Size:");
+				dmi_mapped_address_extended_size(start, end);
+			}
+			else
+			{
+				printf("\tStarting Address: 0x%08X%03X\n",
+					DWORD(data + 0x04) >> 2,
+					(DWORD(data + 0x04) & 0x3) << 10);
+				printf("\tEnding Address: 0x%08X%03X\n",
+					DWORD(data + 0x08) >> 2,
+					((DWORD(data + 0x08) & 0x3) << 10) + 0x3FF);
+				printf("\tRange Size:");
+				dmi_mapped_address_size(DWORD(data + 0x08) - DWORD(data + 0x04) + 1);
+			}
 			printf("\n");
 			if(!(opt.flags & FLAG_QUIET))
 				printf("\tPhysical Array Handle: 0x%04X\n",
@@ -3413,12 +3501,31 @@ static void dmi_decode(struct dmi_header
 		case 20: /* 3.3.21 Memory Device Mapped Address */
 			printf("Memory Device Mapped Address\n");
 			if(h->length<0x13) break;
-			printf("\tStarting Address: 0x%08X%03X\n",
-				DWORD(data+0x04)>>2, (DWORD(data+0x04)&0x3)<<10);
-			printf("\tEnding Address: 0x%08X%03X\n",
-				DWORD(data+0x08)>>2, ((DWORD(data+0x08)&0x3)<<10)+0x3FF);
-			printf("\tRange Size:");
-			dmi_mapped_address_size(DWORD(data+0x08)-DWORD(data+0x04)+1);
+			if (h->length >= 0x23 && DWORD(data + 0x04) == 0xFFFFFFFF)
+			{
+				u64 start, end;
+
+				start = QWORD(data + 0x13);
+				end = QWORD(data + 0x1B);
+
+				printf("\tStarting Address: 0x%08X%08Xk\n",
+					start.h, start.l);
+				printf("\tEnding Address: 0x%08X%08Xk\n",
+					end.h, end.l);
+				printf("\tRange Size:");
+				dmi_mapped_address_extended_size(start, end);
+			}
+			else
+			{
+				printf("\tStarting Address: 0x%08X%03X\n",
+					DWORD(data + 0x04) >> 2,
+					(DWORD(data + 0x04) & 0x3) << 10);
+				printf("\tEnding Address: 0x%08X%03X\n",
+					DWORD(data + 0x08) >> 2,
+					((DWORD(data + 0x08) & 0x3) << 10) + 0x3FF);
+				printf("\tRange Size:");
+				dmi_mapped_address_size(DWORD(data + 0x08) - DWORD(data + 0x04) + 1);
+			}
 			printf("\n");
 			if(!(opt.flags & FLAG_QUIET))
 			{
--- a/util.c
+++ b/util.c
@@ -163,3 +163,19 @@ void *mem_chunk(size_t base, size_t len,
 	
 	return p;
 }
+
+/* Returns end - start + 1, assuming start < end */
+u64 u64_range(u64 start, u64 end)
+{
+	u64 res;
+
+	res.h = end.h - start.h;
+	res.l = end.l - start.l;
+
+	if (end.l < start.l)
+		res.h--;
+	if (++res.l == 0)
+		res.h++;
+
+	return res;
+}
--- a/util.h
+++ b/util.h
@@ -6,3 +6,4 @@
 
 int checksum(const u8 *buf, size_t len);
 void *mem_chunk(size_t base, size_t len, const char *devmem);
+u64 u64_range(u64 start, u64 end);
