Great, looks like I have customers.
Doing all those fixups I described in the BIOS is the ideal solution, but is not possible currently because:
1. My fix-up code has hard coded values that assume certain PCI addresses, TOLUD, etc. Dynamic detection anyone?
2. ACPI sleep/resume is broken, so until it's fixed, you probably don't want the mod permanently in the BIOS.
3. I don't know how to inject custom code into the BIOS. So far I only NOPed 3 instructions. BIOS code seems pretty
tightly packed, so I probably can't overwrite existing code and call it.
Right now, the only practical way I know of to do this mod is to write a grub2 module (grub2 has ability to hook int 0x15, e820 and override the physical memory map). This is going to be a hassle for most people who don't have GNU/Linux installed because you'll have to install grub2, which I don't know how to except from Linux (maybe systemrescuecd can let you do this without needing to install Linux). I did write a hello world boot loader for testing purposes during this project, so maybe the fix-ups can be installed into the master boot record.
For those of you who like using bleeding edge software and have grub2, I've posted my grub2 code & how to do the BIOS mod at the bottom (why don't I see attach file option?).
My module should also work for the Thinkpad T60, since they both use the i945. Can someone add a post to the 4Gb sticky thread and tell them > 3Gb is possible? I originally expected to be able to use 3.7Gb RAM, but unfortunately, the GeForce framebuffer had to be mapped on an aligned address, resulting in ~250Mb waste. If your laptop graphics board
doesn't have such a large memory window, then you should be able to get > 3.5Gb.
Then there's the question of how to mod the T60 BIOS. I took a look at Thinkpad BIOS 2.23 and it looks like almost the exact
code as in the Lenovo BIOS is used to set the D_LCK bit of the SMRAM register (ndisasm -a $01A3000.FL1):
I don't know if just replacing the or with nops will work because there's probably a checksum somewhere that will be wrong, resulting in complaints during boot. I used Phoenix BIOS Editor to repack the Lenovo BIOS, and I assume it fixes the checksum.
--------------------------------------------------------------------------------------------Attachments-----------------------------------------------------------------------------------------------------
Code: Select all
-----------------------------------4gb.c---------------------------------
#include <grub/types.h>
#include <grub/dl.h>
#include <grub/pci.h>
#include <stdint.h>
#include <grub/misc.h>
// grub_pci_make_address uses dword instead of byte addressing for register
#define PCI_ADDRESS(bus, dev, fun, reg) (1U << 31) | (bus << 16) | (dev << 11) | (fun << 8) | reg
#define PCI_MEMORY_BASE 0x20
#define PCI_MEMORY_LIMIT 0x22
#define PCI_PREF_MEMORY_BASE 0x24
#define PCI_PREF_MEMORY_LIMIT 0x26
#define PCI_CB_MEMORY_BASE_0 0x1c
#define PCI_CB_MEMORY_LIMIT_0 0x20
#define PCI_CB_MEMORY_BASE_1 0x24
#define PCI_CB_MEMORY_LIMIT_1 0x28
#define true 1
#define false 0
typedef uint8_t bool;
extern uint8_t g_is_4gb;
// set bits [index + n - 1 : index] of x to v
uint32_t SetBits(uint32_t x, uint32_t index, uint32_t n, uint32_t v)
{
uint32_t mask = (1 << n) - 1;
mask <<= index;
x &= ~mask;
x |= (v << index);
return x;
}
#define PCIX_CONF_BAR 0x48
#define PCI_BASE_ADDRESS_0 0x10
#define PCI_BASE_ADDRESS_1 0x14
#define PCI_BASE_ADDRESS_2 0x18
#define PCI_BASE_ADDRESS_3 0x1c
#define PCI_BASE_ADDRESS_4 0x20
#define PCI_BASE_ADDRESS_5 0x24
inline void wrmsr(unsigned reg, uint32_t low, uint32_t high)
{
asm(
"wrmsr"
: : "c"(reg), "a"(low), "d"(high));
}
#define MTRRphysBase_MSR(reg) (0x200 + 2 * (reg))
#define MTRRphysMask_MSR(reg) (0x200 + 2 * (reg) + 1)
#define PAGE_SHIFT 12
// base, size are in pages; size *must* be power of 2 - I didn't realize this and notice memory bandwidth crawl to a halt
// base must be aligned on size
void SetMTRR(unsigned index, uint32_t base, uint32_t size, unsigned type)
{
if (size == 0) {
/* The invalid bit is kept in the mask, so we simply clear the
relevant mask register to disable a range. */
wrmsr(MTRRphysMask_MSR(index), 0, 0);
} else {
wrmsr(MTRRphysBase_MSR(index), base << PAGE_SHIFT | type, (base & 0xf00000) >> (32 - PAGE_SHIFT));
wrmsr(MTRRphysMask_MSR(index), -size << PAGE_SHIFT | 0x800, (-size & 0xf00000) >> (32 - PAGE_SHIFT));
}
}
void SetBridgeMemoryRange(uint32_t pci_base, bool prefetchable, uint32_t base, uint32_t size)
{
// note: bits[15:4] are bits 31:20 of full address - lower 4 bits unused
uint32_t r = pci_base | (prefetchable ? PCI_PREF_MEMORY_BASE : PCI_MEMORY_BASE);
uint16_t v = grub_pci_read_word(r);
v |= (base & 0xfff00000) >> 16;
grub_pci_write_word(r, v);
r = pci_base | (prefetchable ? PCI_PREF_MEMORY_LIMIT : PCI_MEMORY_LIMIT);
v = grub_pci_read_word(r);
v |= ((base + size - 1) & 0xfff00000) >> 16;
grub_pci_write_word(r, v);
}
void SetCardBridgeMemoryRange(uint32_t pci_base, bool prefetchable, uint32_t base, uint32_t size)
{
grub_pci_write(pci_base | (prefetchable ? PCI_CB_MEMORY_BASE_0 : PCI_CB_MEMORY_BASE_1), base);
grub_pci_write(pci_base | (prefetchable ? PCI_CB_MEMORY_LIMIT_0 : PCI_CB_MEMORY_LIMIT_1), (base + size - 1) & 0xfffff000);
}
// PCI spec states base must be aligned on region size to make address decoding easier (no subtracting addresses)
void SetDeviceMemoryBase(uint32_t pci_base, uint32_t reg, uint32_t base)
{
uint32_t v = grub_pci_read(pci_base | reg);
v &= 0xf;
v |= (base & ~0xf);
grub_pci_write(pci_base | reg, v);
}
uint8_t CacheOn(void)
{
uint32_t cr0;
asm("mov %%cr0, %0" : "=r"(cr0));
return ((cr0 >> 30) & 0x1) == 0;
}
void wbinvd(void)
{
asm volatile ("wbinvd");
}
void enable_cache(void)
{
uint32_t cr0;
asm("mov %%cr0, %0" : "=r"(cr0));
cr0 &= 0x9fffffff;
asm("mov %0, %%cr0" : : "r"(cr0));
}
void disable_cache(void)
{
/* Disable and write back the cache */
uint32_t cr0;
asm("mov %%cr0, %0" : "=r"(cr0));
cr0 |= 0x40000000;
wbinvd();
asm("mov %0, %%cr0" : : "r"(cr0));
wbinvd();
}
#define MTRR_TYPE_UNCACHABLE 0
#define MTRR_TYPE_WRCOMB 1
#define MTRR_TYPE_WRTHROUGH 4
#define MTRR_TYPE_WRPROT 5
#define MTRR_TYPE_WRBACK 6
#define TOLUD 0x9c
GRUB_MOD_INIT (4gb)
{
uint32_t mch_base = PCI_ADDRESS(0, 0, 0, 0),
pcix_bridge0_base = PCI_ADDRESS(0, 1, 0, 0), // root port
pcix_bridge2_base = PCI_ADDRESS(0, 0x1c, 1, 0),
geforce_base = PCI_ADDRESS(1, 0, 0, 0),
audio_base = PCI_ADDRESS(0, 0x1b, 0, 0),
usb_ehci_base = PCI_ADDRESS(0, 0x1d, 7, 0),
south_bridge_base = PCI_ADDRESS(0, 0x1e, 0, 0),
broadcom_base = PCI_ADDRESS(3, 0, 0, 0),
realtek_8139_base = PCI_ADDRESS(5, 1, 0, 0),
cardbus_bridge_base = PCI_ADDRESS(5, 4, 0, 0),
firewire_base = PCI_ADDRESS(5, 6, 0, 0),
sd_controller_base = PCI_ADDRESS(5, 6, 1, 0),
mmc_controller_base = PCI_ADDRESS(5, 6, 2, 0),
memory_stick_base = PCI_ADDRESS(5, 6, 3, 0),
picture_card_base = PCI_ADDRESS(5, 6, 4, 0);
g_is_4gb = true; // tell grub_mmap_iterate to report more RAM
disable_cache(); // do we really need to disable?
grub_pci_write_byte(mch_base | TOLUD, 0xe0);
// todo: this doesn't set MTRR on other core - apparently, issuing IPIs to do this seems pretty complicated
SetMTRR(0, 0, 0x80000, MTRR_TYPE_WRBACK);
SetMTRR(1, 0x80000, 0x40000, MTRR_TYPE_WRBACK);
SetMTRR(2, 0xc0000, 0x20000, MTRR_TYPE_WRBACK);
SetMTRR(3, 0xc0000 - 0x100, 0x100, MTRR_TYPE_UNCACHABLE);
SetMTRR(4, 0xe0000 - 0x100, 0x100, MTRR_TYPE_UNCACHABLE);
enable_cache();
uint32_t pcix_conf_bar = grub_pci_read(mch_base | PCIX_CONF_BAR);
pcix_conf_bar = SetBits(pcix_conf_bar, 26, 6, 0x3e);
pcix_conf_bar = SetBits(pcix_conf_bar, 1, 2, 0x2);
// need to write 2x because bits[2:1] need to be set before bits 27:26
// can be written
grub_pci_write(mch_base | PCIX_CONF_BAR, pcix_conf_bar);
grub_pci_write(mch_base | PCIX_CONF_BAR, pcix_conf_bar);
// root bus devices
SetDeviceMemoryBase(audio_base, PCI_BASE_ADDRESS_0, 0xf0500000);
SetDeviceMemoryBase(usb_ehci_base, PCI_BASE_ADDRESS_0, 0xf0504000);
// PCIX root bridge
SetBridgeMemoryRange(pcix_bridge0_base, true, 0xe0000000, 0x10000000);
SetBridgeMemoryRange(pcix_bridge0_base, false, 0xf1000000, 0x2000000);
SetDeviceMemoryBase(geforce_base, PCI_BASE_ADDRESS_0, 0xf2000000);
SetDeviceMemoryBase(geforce_base, PCI_BASE_ADDRESS_1, 0xe0000000);
SetDeviceMemoryBase(geforce_base, PCI_BASE_ADDRESS_3, 0xf1000000);
// PCIX bridge 2
SetBridgeMemoryRange(pcix_bridge2_base, false, 0xf0200000, 0x100000);
SetBridgeMemoryRange(pcix_bridge2_base, true, 0xf0000000, 0x100000);
SetDeviceMemoryBase(broadcom_base, PCI_BASE_ADDRESS_0, 0xf0200000);
SetDeviceMemoryBase(broadcom_base, PCI_BASE_ADDRESS_2, 0xf0000000);
// southbridge
SetBridgeMemoryRange(south_bridge_base, false, 0xf0100000, 0x100000);
SetBridgeMemoryRange(south_bridge_base, true, 0xf4000000, 0x4000000);
SetDeviceMemoryBase(realtek_8139_base, PCI_BASE_ADDRESS_1, 0xf0100000);
SetDeviceMemoryBase(sd_controller_base, PCI_BASE_ADDRESS_0, 0xf0100400);
SetDeviceMemoryBase(firewire_base, PCI_BASE_ADDRESS_0, 0xf0100800);
SetDeviceMemoryBase(mmc_controller_base, PCI_BASE_ADDRESS_0, 0xf0101000);
SetDeviceMemoryBase(memory_stick_base, PCI_BASE_ADDRESS_0, 0xf0101400);
SetDeviceMemoryBase(picture_card_base, PCI_BASE_ADDRESS_0, 0xf0101800);
// cardbus is attached to southbridge
SetCardBridgeMemoryRange(cardbus_bridge_base, true, 0xf4000000, 0x2000000);
SetCardBridgeMemoryRange(cardbus_bridge_base, false, 0xf6000000, 0x2000000);
volatile uint8_t *p;
// DSDT
p = (uint8_t *)0xbfee85b5;
p[9] = 0x3b; // checksum
*(uint32_t *)&p[0x18f5] = 0xf8000000; // pcix ext conf base
*(uint32_t *)&p[0x18f9] = 0x04000000; // pcix ext conf size
*(uint32_t *)&p[0x1961] = 0xf8000000;
*(uint32_t *)&p[0x1965] = 0x04000000;
// ACPI MCFG table
p = (uint8_t *)0xbfeecd58;
p[9] = 0x48; // checksum
*(uint32_t *)&p[0x2c] = 0xf8000000; // pcix ext conf base
p[0x37] = 63; // pcix ext conf last bus
p = (uint8_t *)0xbfeecf3c; // DBGP table
p[9] = 0x99;
*(uint32_t *)&p[0x2c] = 0xf0100800; // 1394 MMIO
}
GRUB_MOD_FINI (4gb)
{
}
--------------------------------------------kern/i386/pc/mmap.c------------------------------------------
#include <grub/machine/init.h>
#include <grub/machine/memory.h>
#include <grub/err.h>
#include <grub/types.h>
#include <grub/i386/pc/memory.h>
#include <stdint.h>
struct Region
{
uint32_t base, size, type;
};
struct Region g_regions[] =
{
{0,0x9f800, GRUB_MACHINE_MEMORY_AVAILABLE},
{0x9f800, 0x800, GRUB_MACHINE_MEMORY_RESERVED},
{0xdc000, 0x8000, GRUB_MACHINE_MEMORY_RESERVED},
{0xe8000, 0x18000, GRUB_MACHINE_MEMORY_RESERVED},
{0x100000, 0xbfde0000, GRUB_MACHINE_MEMORY_AVAILABLE},
{0xbfee0000, 0xd000, GRUB_MACHINE_MEMORY_NVS},
{0xbfeed000, 0x13000, GRUB_MACHINE_MEMORY_NVS},
{0xbff00000, 0x100000, GRUB_MACHINE_MEMORY_RESERVED},
{0xe0000000, 0x10000000, GRUB_MACHINE_MEMORY_RESERVED},
{0xfec00000, 0x10000, GRUB_MACHINE_MEMORY_RESERVED},
{0xfed00000, 0x400, GRUB_MACHINE_MEMORY_RESERVED},
{0xfed14000, 0x4000, GRUB_MACHINE_MEMORY_RESERVED},
{0xfed18000, 0x1000, GRUB_MACHINE_MEMORY_RESERVED},
{0xfed19000, 0x1000, GRUB_MACHINE_MEMORY_RESERVED},
{0xfed1c000, 0x4000, GRUB_MACHINE_MEMORY_RESERVED},
{0xfed20000, 0x70000, GRUB_MACHINE_MEMORY_RESERVED},
{0xfee00000, 0x1000, GRUB_MACHINE_MEMORY_RESERVED},
{0xff000000, 0x1000000, GRUB_MACHINE_MEMORY_RESERVED}
};
uint8_t g_is_4gb = 0;
grub_err_t
grub_machine_mmap_iterate (int NESTED_FUNC_ATTR (*hook) (grub_uint64_t, grub_uint64_t, grub_uint32_t))
{
int i;
if (g_is_4gb)
{
g_regions[8].base = 0xf8000000;
g_regions[8].size = 0x4000000;
}
for (i = 0; i < 8; ++i)
hook(g_regions[i].base, g_regions[i].size, g_regions[i].type);
if (g_is_4gb)
{
hook(0xc0000000, 0x20000000 - 0x100000, GRUB_MACHINE_MEMORY_AVAILABLE);
hook(0xdff00000, 0x100000, GRUB_MACHINE_MEMORY_RESERVED);
}
for (i = 8; i < 18; ++i)
hook(g_regions[i].base, g_regions[i].size, g_regions[i].type);
return 0;
}
---------------------------------------------loader/i386/pc/chainloader.c-----------------------------------------------
(insert code into grub_chainloader_cmd() )
static void *preb_handle = 0; // yale: can't duplicate or else preboot_hook gets called twice
// causing unload_uneeded_dl to unload endlessly
if (!preb_handle)
{
grub_err_t err = malloc_hook(); // register's int15 hook automatically
if (err)
grub_printf("can't allocate hook\n");
}
---------------------------------------------how to mod BIOS (Lenovo 3000 N100)-----------------------------------------------
1. Open the Phoenix WPH file in a hex editor and find the following code.
2. replace the or with nops
3. Unpack the modified WPH with Phoenix BIOS editor. Then Repack again (you need to make a trivial change and undo before the repack button becomes available).
4. Flash with the repacked WPH.