Monday, November 28, 2016

Switching from IDE to AHCI on an ICH7-M based laptop

Many older laptops have SATA controllers and SATA drives, but set up the chipset in IDE mode instead of AHCI mode. Compared to IDE mode, AHCI can provide faster transfer rates, NCQ and power savings. It may not be possible to select AHCI mode in the BIOS, but it can be done by writing to PCI configuration registers directly. The actual IDE (PATA) ports are not supported in AHCI mode, so a PATA DVD drive would not be usable when AHCI is enabled.

You can read and write PCI registers using setpci in Linux, but if you want to make this change, it must be done before the device is detected. One way to do it is via a kernel patch, but recompiling the kernel every time for updates is kind of annoying. Another way is via GRUB's setpci command. This is very easy, and works well, but breaks resume from sleep, because GRUB doesn't run then and the kernel will encounter the device in IDE mode.

How to do this via GRUB

First, you need to find your controller. Use lspci and look at the output. On my Inspiron 6400 I find "00:1f.2 IDE interface: Intel Corporation 82801GBM/GHM (ICH7-M Family) SATA Controller [IDE mode] (rev 01)". The 00:1f.2 is the device's identifier in Linux, and lspci -n | grep 00:1f.2 shows "00:1f.2 0101: 8086:27c4 (rev 01)" This gives you the PCI ID of the device: 8086:27c4.

According to the ICH-7 datasheet PDF, you need to set the 8-bit MAP—Address Map Register at offset 0x90 to 0x40. (A patch I had before also set the SCRAE bit in the register at offset 0x94, but that just provides access to some AHCI registers when AHCI is not enabled, and is irrelevant when AHCI is enabled.) This means the following GRUB command is needed.

setpci -d 8086:27c4 90.b=40

You can press e when the GRUB menu is displayed, and add it to the end of the command that boots Linux. Adding it earlier caused a hang, probably because the BIOS cannot handle AHCI mode, and so it needs to be set after the kernel and initrd are loaded. After finding this works, I added an /etc/grub.d/42_ahci file with a stripped down Linux menu entry taken from /boot/grub/grub.cfg. It would be better if I could call the Ubuntu menu entry as a subroutine, but that doesn't seem possible with GRUB.

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
menuentry 'Ubuntu AHCI' --class ubuntu --class gnu-linux --class gnu --class os {
recordfail
insmod ext2
set root='hd0,msdosPARTITION_NUMBER_HERE'
linux /vmlinuz root=UUID=PUT_UUID_HERE ro resume=SWAP_PARTITION
initrd /initrd.img
setpci -d 8086:27c4 90.b=40
}

The Results

This change increases sudo nice -n -19 dd if=/dev/sda bs=1M of=/dev/null count=1000 speed from 128 MB/s to 141 MB/s with a PNY SSD2SC120G1CS1754D117-551 SSD. It also enables NCQ, and causes the DVD drive to not be detected at all. Here are the IDE lines which disappear from dmesg. Note that this is based on diff output and is just a list of lines in order, not a continuous section.

pci 0000:00:1f.2: [8086:27c4] type 00 class 0x010180
pci 0000:00:1f.2: legacy IDE quirk: reg 0x10: [io 0x01f0-0x01f7]
pci 0000:00:1f.2: legacy IDE quirk: reg 0x14: [io 0x03f6]
pci 0000:00:1f.2: legacy IDE quirk: reg 0x18: [io 0x0170-0x0177]
pci 0000:00:1f.2: legacy IDE quirk: reg 0x1c: [io 0x0376]
ata_piix 0000:00:1f.2: version 2.13
ata_piix 0000:00:1f.2: MAP [ P0 P2 IDE IDE ]
scsi host0: ata_piix
scsi host1: ata_piix
ata1: SATA max UDMA/133 cmd 0x1f0 ctl 0x3f6 bmdma 0xbfa0 irq 14
ata2: PATA max UDMA/100 cmd 0x170 ctl 0x376 bmdma 0xbfa8 irq 15
ata2.00: ATAPI: SONY DVD+/-RW DW-Q58A, UDS2, max UDMA/33
ata1.00: 234441648 sectors, multi 2: LBA48 NCQ (depth 0/32)
ata2.00: configured for UDMA/33
scsi 1:0:0:0: CD-ROM SONY DVD+-RW DW-Q58A UDS2 PQ: 0 ANSI: 5
sr 1:0:0:0: [sr0] scsi3-mmc drive: 24x/24x writer cd/rw xa/form2 cdda tray
cdrom: Uniform CD-ROM driver Revision: 3.20
sr 1:0:0:0: Attached scsi CD-ROM sr0
sr 1:0:0:0: Attached scsi generic sg1 type 5


Here are the new lines which appear instead:

pci 0000:00:1f.2: [8086:27c5] type 00 class 0x010601
pci 0000:00:1f.2: reg 0x24: [mem 0x00000000-0x000003ff]
pci 0000:00:1f.2: BAR 5: assigned [mem 0x80000000-0x800003ff]
ahci 0000:00:1f.2: version 3.0
ahci 0000:00:1f.2: enabling device (0005 -> 0007)
ahci 0000:00:1f.2: forcing PORTS_IMPL to 0xf
ahci 0000:00:1f.2: SSS flag set, parallel bus scan disabled
ahci 0000:00:1f.2: AHCI 0001.0100 32 slots 4 ports 1.5 Gbps 0xf impl SATA mode
ahci 0000:00:1f.2: flags: 64bit ncq ilck stag pm led clo pmp pio slum part
scsi host0: ahci
scsi host1: ahci
scsi host2: ahci
scsi host3: ahci
ata1: SATA max UDMA/133 abar m1024@0x80000000 port 0x80000100 irq 27
ata2: SATA max UDMA/133 abar m1024@0x80000000 port 0x80000180 irq 27
ata3: SATA max UDMA/133 abar m1024@0x80000000 port 0x80000200 irq 27
ata4: SATA max UDMA/133 abar m1024@0x80000000 port 0x80000280 irq 27
ata1: SATA link up 1.5 Gbps (SStatus 113 SControl 300)
ata1.00: 234441648 sectors, multi 2: LBA48 NCQ (depth 31/32), AA
ata2: failed to resume link (SControl 0)
ata2: SATA link down (SStatus 0 SControl 0)
ata3: SATA link down (SStatus 0 SControl 300)
ata4: failed to resume link (SControl 0)
ata4: SATA link down (SStatus 0 SControl 0)

A better solution

Another alternative, which can support suspend to RAM, is to set registers from an altered DSDT. Read about the DSDT changes here and read about how to use a custom DSDT in Linux here. You first need to dump your DSDT, decompile it and fix it so it can compile properly. People do this sort of thing for running Mac OS on a PC, and you can find plenty of info on sites relating to that. Then you can add your modifications. In Ubuntu, simply copy the compiled dsdt.aml to /boot and run update-grub.

5 comments:

Drag0nFly said...

Interesting read. Can't believe there are no comments for this.

Since you reference a way to enable the PIIX->AHCI switch directly in the kernel – how did you accomplish this?
I am asking since my system - which is locked to ide-mode - does not have the option to issue 'setpci' via Grub2, and doing it directly via the kernel (either patch or param) would therefore be preferred (and espeically since I am custom-compiling a hardened 32-bit kernel anyway.)

Appreciate any input (in the event you are still following this post!) :)

krendll said...

Very useful article. Switched my ich9 (without any possibility in BIOS, MB Asus p5k-v) to ahci same way.

Federico Segato said...

Hi, and thanks for your suggestions, with a little more work and a couple of epic fails I was able to enable AHCI on a Phoenix BIOS of a Sony VAIO VPCW12J1E (ich7-m chipset).
At the moment, only from the Debian Sid side, using GRUB and initramfs-tools functions to modify the initrd image and adding the needed modules (ahci.ko libahci.ko).
Now, the challenge is to get the same result on the Windows 7 side (I do dual boot but at the moment Windows doesn't seem to like the "setpci" trick and GRUB chainloading fails).
Unfortunately, the BIOS seems uneditable for AHCI, despite the efforts of a kind developer (see the latest posts https://forums.mydigitallife.net/threads/decode-edit-nvram-phoenix-plus-setup-menu.12850
).
I am open to further suggestions, for example if there was a system similar to initrd.img also in Windows it would be great.
So, please, feel free.

Boris Gjenero said...

I have not tried to boot Windows with AHCI. I think the problem there is that Windows uses the BIOS to load the kernel and boot time drivers, and the BIOS cannot access the disk after the controller is reconfigured for AHCI. A solution to that would be a simple boot time Windows driver which loads before Windows' own AHCI driver. According to https://docs.microsoft.com/en-us/windows-hardware/drivers/install/specifying-driver-load-order you can set boot time driver load order via LoadOrderGroup, though I've never explored that. So, Windows uses the BIOS to load the kernel and boot time drivers, the kernel starts, your driver enables AHCI, and then the AHCI driver should work.

Another question is how Windows detects AHCI, like whether ACPI tables need to specify it, or whether it can simply probe the hardware directly like Linux.

Federico Segato said...

Hi Boris, thanks for your answer, full of interesting ideas.
LoadOrderGroup ... I've never explored that me too, but this concept deserves study.
I hope that's a possible solution, as my BIOS, lacking a dedicated setting, reverts the ICH7-M chipset back to IDE mode every time I reboot.
ACPI tables need to specify it oh my! there was a promising thread I tried to follow it but it seems that development has stopped halfway.
Otherwise I would have tried to recompile DSDT and then I would have tried a global ACPI override directly from GRUB2.
I have extracted DSDT with acpica-tools and decomposed a dump of my BIOS (the Sony one is either unobtainable or has never been officially released) with Phoenix BIOS Editor, I fixed a couple of compilation errors between iasl and asl.exe but I can't go further.
But, in this dead thread they suggested a GRUB4DOS approach, and three files: autoexec.bat, which in the middle "call" ahcipci.bat - plus a third batch ahci.bat - and this last one I can't relate to the other two.
I rewrited the syntax for GRUB2, merged first half of autoexec.bat - then placed ahcipci.bat - then second half of autoexec.bat.
Loaded these many lines of setpci into a GRUB2 menuentry for Windows.
But my first try failed with "error: attempt to read or write outside of disk 'hd0'."