How to flash coreboot to the thinkpad x60 the proper way

What is so special about the x60 when running vendor bios?

Vendor BIOS write protects its bootblock, which means the lowest 64K of the flash can’t be modified

This is a problem since the first code that runs on the CPU comes from there and if we ever want to run coreboot the cpu must start with coreboot code. This write protection is set in the PBR (protect bios range) registers on the southbridge, there is currently no known way to change them back once they are locked by setting the SPI Configuration Lock-Down bit.

FLASH (2M)  
    +-------------+
    |             | 
    Z             Z 
    |             | 
    |             |  ---> Read and write: ok
    |             | 
    |             |
    |             |
    +-------------+ -> 0x1F0000
    |             |
    | bootblock_0 |  ---> Only read allowed
    |    64K      |
    +-------------+ -> 0x200000

Vendor BIOS prohibits many flash operations

Naturally flash chips have many different operation, for example multiple read, write, erase and ID probe operation. The vendor bios prohibits a lot of those, which is why stock flashrom will fail to work properly.

How to solve these issue without needing external or hardware flashing

The write protection problem

Luckily the chipset provides a feature called BUC.TS (back up control top swap). In this mode the way the flash gets decoded (read by the cpu) changes. In this mode not the lowest 64K get read by the CPU first but the 64K above those. This is great since we can now put our coreboot bootblock in that 64K region above and that will execute first on the CPU instead of the vendor BIOS bootblock!

This ascii art tries to explain it.

FLASH (2M)                          Memory Map BUC.TS=0               Memory Map BUC.TS=1
    |             |                     |             |                     |             |
    Z             Z                     Z             Z                     Z             Z
    Z             Z                     Z             Z                     Z             Z
    |             |                     |             |                     |             |
    |             |                     |             |                     |             |
    |             |                     |             |                     |             |
    +-------------+ -> 0x1E0000         +-------------+ -> 0xFFFE0000       +-------------+ -> 0xFFFE0000
    |             |                     |             |                     |             |
    | bootblock_1 |                     | bootblock_1 |                     | bootblock_0 |
    |             |                     |             |                     |             |
    +-------------+ -> 0x1F0000         +-------------+ -> 0xFFFF0000       +-------------+ -> 0xFFFF0000
    |             |                     |             |                     |             |
    | bootblock_0 |                     | bootblock_0 |                     | bootblock_1 |
    |             |                     |             |                     |             |
    +-------------+ -> 0x200000         +-------------+ -> 0xFFFFFFFF       +-------------+ -> 0xFFFFFFFF

the flash operations restriction

The only real issue will be that the flash cannot be detected with default flashrom ID operation. Luckily the flash has backup operation for probing the ID so we can patch flashrom to use those.

There is some WIP to probe chips with multiple method which would obsolete the hack we need here.

Some preparations

Because of all of this some tools will be needed

bucts

To swap the BUC.TS bit we will need a tool called buc.ts Lets fetch it. Run this inside the coreboot tree (assumes you are on coreboot git):

git fetch https://review.coreboot.org/coreboot refs/changes/24/18224/5 && git cherry-pick FETCH_HEAD

EDIT: this tool has been merged as of 19/11/2018 so no need to use this out of tree patch.

Now go to the directory where the code is located and build it

cd util/bucts/
make

flashrom

Flashrom needs some patching to be able to probe the flashchips and write to them. On the X60 2 different flashchips can occur so we need to patch flashrom for two of these.

First download flashrom (best outside the coreboot tree to keep everything tidy):

wget https://download.flashrom.org/releases/flashrom-1.0.tar.bz2
tar xvf flashrom-1.0.tar.bz2

Go inside the flashrom dir, fetch the patches and apply them:

cd flashrom
wget https://notabug.org/libreboot/libreboot/raw/master/resources/flashrom/patch/lenovobios_sst.diff
wget https://notabug.org/libreboot/libreboot/raw/master/resources/flashrom/patch/lenovobios_macronix.diff
patch -p0 < lenovobios_sst.diff
patch -p0 < lenovobios_macronix.diff
make

Now our flashrom is ready to use (remember to use this flashrom and not the one from your distro).

bootblock

We need a second bootblock at a 64K offset of the bottom.

In the menuconfig select:

CONFIG_INTEL_ADD_TOP_SWAP_BOOTBLOCK=y

and make sure the bootblock size remains to the default 0x10000:

CONFIG_INTEL_TOP_SWAP_BOOTBLOCK_SIZE=0x10000

Coreboot set BILD (BIOS Interface Lock-Down) register, which prevents changes to BUC.TS. Since we only set BUC.TS to flash coreboot once but want to unset it later on we want

# CONFIG_INTEL_CHIPSET_LOCKDOWN is not set

which prevents coreboot from locking down this register.

Now we are ready to build the image which will be covered in the next section.

Building the coreboot image

This section assumes you have got the coreboot toolchain built.

Now it’s just a matter selecting some options and compiling the rom we will flash.

The defaults are ok (SeaBIOS with native graphic init) so the only thing we will need to select are the binary repository for microcode select ‘Allow use of binary-only repository'(CONFIG_USE_BLOBS) in ‘make menuconfig’ under general the options from the previous section and build it:

make

make sure at the end of the build process you see the following line at the end:

bootblock                      0x1dfdc0   bootblock      131072 none

The bootblock file must be exactly 0x131072 (128K) large. It contains both the regular and the bucts bootblock.

Flashing the coreboot rom

This is the exciting part where we will actually flash coreboot to the motherboard.

So our rom is now inside the coreboot tree sitting at build/coreboot.rom flashrom is in whichever place you downloaded, patched and build flashrom and bucts is under util/bucts/bucts

Let’s get started. First backup the vendor bios in case something goes wrong. For this we need to use our special flashrom:

./path/to/flashrom/flashrom --programmer internal -r backup.rom

We want to be absolutely sure we got a proper backup so let’s verify it against the flash.

./path/to/flashrom/flashrom --programmer internal --verify backup.rom

Now put that backup.rom somewhere safe like on an USB drive.

Now we want to flip BUC.TS

util/bucts/bucts --set

Now the scary/fun part: flashing the actual rom. Because the bottom part of the flash is write protected we want to skip writing to it because that will have flashrom print out some scary messages. To do this we will be using a layout. Create a file, x60_layout with the following content:

0x00000000:0x001effff RW
0x001f0000:0x001fffff RO

And let’s flash it using this layout

./path/to/flashrom/flashrom --programmer internal --write build/coreboot.rm --layout x60_layout --image RW

Flashrom will try some erase function (that the vendor BIOS prohibits using) but will succeed in the end. Note that it can take some time because slow write operations have have to be used.

One thing you still want to do before rebooting is setting coreboot settings, located in the rtc nvram or cmos to their default values: start in the coreboot tree

cd util/nvramtool
make
./nvramtool -y ../../src/mainboard/lenovo/x60/cmos.layout -i ../../src/mainboard/lenovo/x60/cmos.default

And done!

NOTE: If flashrom complains don’t reboot and report to #coreboot chat on freenode irc!

Well almost. If BUC.TS gets 0 again (which happens if you pull the RTC CMOS ‘yellow’ battery) it will start the vendor bootblock again and we have a brick. So we need to flash coreboot again which is now possible because coreboot does not write protect the flash/bootblock like vendor does it.

So reboot from first flash, assuming flashrom did not complain previous. Simply flash the rom again and we are done (for real now) and set bucts to 0 again:

flashrom -p internal -w build/coreboot.rom
util/bucts/bucts --unset

It is really important to set BUC.TS to zero again. When you build coreboot normally it won’t have a bucts bootblock and will therefore fail to boot (although pulling the cmos battery will do the trick too).

Note that you can use your distro’s flashrom now. When building a new coreboot rom, you now don’t need to select CONFIG_INTEL_ADD_TOP_SWAP_BOOTBLOCK=y anymore.

NOTE: you might want to re-flash a new build with CONFIG_INTEL_CHIPSET_LOCKDOWN=y set since locking down the system is a desirable security feature.

Was originally published here.

I will try to merge this in the official in tree documentation once the bucts tool gets merged.

Porting the Intel DG41WV and Intel 4 series DDR3 raminit to coreboot

This post will explain a bit how the Intel 4 series DDR3 raminit came to be and will introduce a new board that can make use of this code, namely the Intel DG41WV.

DDR3 raminit

In the past I have worked quite on a bit on the coreboot code that support the Intel 4 series desktop chipset (those chipsets go by the name of G41, G43, G45, Q43, Q45, P41, P43, P45, B43 with each having a somewhat different feature set). In the past I have made quite a few improvements to the coreboot code for this platform:

  • Improve the dram compatibility
  • Rework how the SPD’s get decoded
  • Rework the DQS receive enable calibration
  • Add support for using external graphic devices in the PEG slot
  • Add resume from S3 support
  • Make properly rebooting possible
  • Getting variants with the ICH10 southbridge to work

Given the work I did on this and other northbridges/raminit code I was ready for something more challenging and I thought adding DDR3 support would be something nice (previously. DDR3 is quite a different beast however.

DDR3 fly-by topology

To increase the clock frequency beyond DDR2 frequencies something had to be done to the topology of dram DIMMs. To increase the signal integrity a fly by topology had to be adopted.

The data signals follow a shorter path and come from below, but where the clock signal previously followed a T-branch path and arrived more or less at the same time in the chips, DDR3 technology adopted a fly-by topology. The clock signal arrives at a different time for each data signal pair, DQ (data) and DQS (data strobe).

Memory controllers naturally can delay some of its signals using DLL’s (delay locked loop). The clock signal will generally be fixed at the start of the dram initialization and it will be the firmware’s job to find out good values for the delays on the other signal lines. The delays are therefore always delays with respect to this ‘master’ clock signal.

Write leveling

A first ‘training’ that is done on DDR3 dimms is called write leveling. This is a special mode of operation on DDR3 dimm’s which allow you to sample back the DQS on the DQ signal in order to find a rising edge with respect to the clock:

Read and Write training

After that you need to adjust the delays such that the skew between DQ and DQS is fine for both read and write operations. The algorithms for these are rather straightforward. First you start with write training. You sample all delays starting from the DQS delays obtained in the write leveling (the DQ delays are always with respect to the strobe DQS) and save where write start working and start failing. After that you pick the centered value.

DQS delay value (start)
      ^                             middle value
      |  write to dram              ideal DQ delay     write to dram
      |  don't read back                  ^            don't read back
      |         |                         |                  |
      +----------------------|++++++++++++++++++++++++|-----------
                                   write to dram
                                   read back fine

Something analogous is done for DQS read delay but instead of trying to write back something you loop over all possible read DQS delays (starting from 0) and try to read back something.

S3 support and faster boot times

All these trainings are destroy the the dram’s content. This is fine if you boot for the first time but when you are trying to resume from S3 (suspend to ram) this is not an option. To keep the ram content in place you need to save the result from those trainings and restore those values when resuming from S3. Previously the RTC nvram (cmos battery backed) was used to store a few things but its size is severely limited (128 bytes) so another non volatile medium was needed. The other easily accessible non volatile medium is the flash chip itself which is usually at least 1MiB large. So what we do is to reserve a small space (64KiB) on that flash where we can store the results of our dram training.

Typically users don’t change DIMM’s on their system that often. Fetching information from these DIMM’s that the dram controller needs is quite slow (reading a full SPD chips takes at least 50ms for each DIMM depending the protocol used). The SPI flash cache we previously used to save the training results can come in handy here to cache that information too (we only need to check if DIMM’s have changed which can be done by just reading the ID bytes on the SPD chip). The end result is that with this cache the boot times are noticeably reduced. On the first boot it takes at least 500ms to initialize the dram but on subsequent boots it only takes ~20ms since everything is cached on a fast medium (SPI flash).

Intel DG41WV

intel DG41WV

The board I chose to develop the DDR3 raminit on was an Intel DG41WV. I chose it for a few reasons:

  1. It was cheap
  2. It had a socket (DIP8) spi flash which makes reflashing easy and avoid unexpected electrical difficulties which might require desoldering it.
  3. It has a SuperIO supported by coreboot
  4. It has a serial port

Obviously getting the ram to work was by far the largest part of this port. The board port part is usually really small and with some experience it can usually be done in a few hours. The DG41WV was no different in that aspect. One peculiarity was that the on the VGA output the display was garbled. I had this problem on other boards before and is typically an issue with the default clockgen configuration having a wrong clock frequency for the VGA output. Publicly available documentation for CK505 clockgens is scarce so one solution is to program whatever vendor has set.

First figure out on which number Linux maps the SMBUS:

modprobe i2c-dev
i2cdetect -l

The result will look like:

i2c-3   i2c             i915 gmbus dpc                          I2C adapter
i2c-1   i2c             i915 gmbus vga                          I2C adapter
i2c-8   i2c             DPDDC-D                                 I2C adapter
i2c-6   i2c             DPDDC-B                                 I2C adapter
i2c-4   i2c             i915 gmbus dpb                          I2C adapter
i2c-2   i2c             i915 gmbus panel                        I2C adapter
i2c-0   i2c             i915 gmbus ssc                          I2C adapter
i2c-9   smbus           SMBus I801 adapter at 0400              SMBus adapter (this is the one)
i2c-7   i2c             DPDDC-C                                 I2C adapter
i2c-5   i2c             i915 gmbus dpd                          I2C adapter

Now we can read back to ck505 configuration while running vendor firmware using smbus block read operation:

i2cdump -y 9 0x69 s

Now you put this information in the devicetree in coreboot

            device pci 1f.3 on      # SMbus
                subsystemid 0x8086 0x5756
                    chip drivers/i2c/ck505
                    register "mask" = "{ 0xff, 0xff, 0xff,
                         0xff, 0xff, 0xff, 0xff, 0xff,
                         0xff, 0xff, 0xff, 0xff, 0xff,
                         0xff, 0xff, 0xff, 0xff, 0xff,
                         0xff, 0xff, 0xff }"
                    register "regs" = "{ 0x41, 0x99, 0xff,
                         0xff, 0xff, 0x00, 0x00, 0x06,
                         0x03, 0x65, 0x83, 0x80, 0x15,
                         0xc0, 0x09, 0x00, 0x00, 0x00,
                         0x06, 0x00, 0xea }"
                    device i2c 69 on end
                end
            end

What works on this board?

  • 2 x 4GB DDR3 DIMMs (using dual rank DIMMs)
  • Core 2 CPU’s (duo and quad) LGA775 CPU’s (including modded LGA771 CPU’s)
  • PCI, PCIe, USB, PS2, Serial
  • Native graphic init with VGA output
  • S3 resume

TD;DL how do I use coreboot on the Intel DG41WV?

Building a basic but working image with SeaBIOS for this board is really easy. Run the following in the coreboot directory:

echo CONFIG_VENDOR_INTEL=y > .config
echo CONFIG_BOARD_INTEL_DG41WV=y >> .config
make olddefconfig
make

Now you can flash this to the flash chip. This can be done internally since the vendor firmware does not lock the SPI flash.

flashrom -p internal -w build/coreboot.rom

Feel free to ask any questions!

Hacking ath9k wifi device adventures

So here is where it all started. I have this apple branded atheros AR5BXB92, with an ar9280 chipset, which is supported by the ath9k Linux drivers. This chipset is supposed to support both 5GHz and 2.4GHz frequencies, but using the ‘wavemon’ tool 5GHz SSID never show up even when I’m next to a device that emits one.

‘iw list’ shows the following:

Band 2:
	Capabilities: 0x11ce
		HT20/HT40
		SM Power Save disabled
		RX HT40 SGI
		TX STBC
		RX STBC 1-stream
		Max AMSDU length: 3839 bytes
		DSSS/CCK HT40
	Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
	Minimum RX AMPDU time spacing: 8 usec (0x06)
	HT TX/RX MCS rate indexes supported: 0-15
	Bitrates (non-HT):
		* 6.0 Mbps
		* 9.0 Mbps
		* 12.0 Mbps
		* 18.0 Mbps
		* 24.0 Mbps
		* 36.0 Mbps
		* 48.0 Mbps
		* 54.0 Mbps
	Frequencies:
		* 5180 MHz [36] (16.0 dBm) (no IR)
		* 5200 MHz [40] (16.0 dBm) (no IR)
		* 5220 MHz [44] (16.0 dBm) (no IR)
		* 5240 MHz [48] (16.0 dBm) (no IR)
		* 5260 MHz [52] (17.0 dBm) (no IR, radar detection)
		* 5280 MHz [56] (16.0 dBm) (no IR, radar detection)
		* 5300 MHz [60] (16.0 dBm) (no IR, radar detection)
		* 5320 MHz [64] (16.0 dBm) (no IR, radar detection)
		* 5500 MHz [100] (disabled)
		* 5520 MHz [104] (disabled)
		* 5540 MHz [108] (disabled)
		* 5560 MHz [112] (disabled)
		* 5580 MHz [116] (disabled)
		* 5600 MHz [120] (disabled)
		* 5620 MHz [124] (disabled)
		* 5640 MHz [128] (disabled)
		* 5660 MHz [132] (disabled)
		* 5680 MHz [136] (disabled)
		* 5700 MHz [140] (disabled)
		* 5745 MHz [149] (disabled)
		* 5765 MHz [153] (disabled)
		* 5785 MHz [157] (disabled)
		* 5805 MHz [161] (disabled)
		* 5825 MHz [165] (disabled)

So most of the 5GHz channels which are avaible in my country were disabled… Changing the reg domain

iw reg set BE

did not change this.

On the web I found some articles about modifying the reg domain in the eeprom that is present on that wifi card. One of these is http://blog.asiantuntijakaveri.fi/2014/08/one-of-my-atheros-ar9280-minipcie-cards.html The link to the iwleeprom code is dead but can be found https://github.com/andyvand/AtherosROMKit doing so I found out that my region code is 0x65. I tried to modify that eeprom content but that didn’t seem to work properly for some reason…

I knew that almost everything is done by the kernel driver so I looked for the possibilty to override this regional setting an I found it! By changing the reg_rules for my 0x65 domain device and recompiling the ‘ath’ module I got those 5GHz appearing in wavemon and listed as supported in ‘iw list’!

diff --git a/drivers/net/wireless/ath/regd.c b/drivers/net/wireless/ath/regd.c
index e25bfdf78c2e..e4cbf366b4e3 100644
--- a/drivers/net/wireless/ath/regd.c
+++ b/drivers/net/wireless/ath/regd.c
@@ -74,12 +74,12 @@ static const struct ieee80211_regdomain ath_world_regdom_60_61_62 = {
 
 /* Can be used by 0x63 and 0x65 */
 static const struct ieee80211_regdomain ath_world_regdom_63_65 = {
-	.n_reg_rules = 4,
+	.n_reg_rules = 5,
 	.alpha2 =  "99",
 	.reg_rules = {
-		ATH9K_2GHZ_CH01_11,
-		ATH9K_2GHZ_CH12_13,
-		ATH9K_5GHZ_NO_MIDBAND,
+		ATH9K_2GHZ_ALL,
+		ATH9K_5GHZ_ALL,
+
 	}
 };

Not wanting to recompile this module each time I get a kernel update, I looked into modifying the eeprom again to change it to a value where those channel were permanently enabled (0x60 for instance).

So I looked at the the dump iweeprom provided me

# ./iwleeprom -o mydump.bin

The dump looked like this

00000000: 5AA5 0000 0300 0060 8C16 2900 0860 0100  Z......`..)..`..
00000010: 8002 2C60 6B10 8F00 0050 8C16 2A00 0850  ..,`k....P..*..P
00000020: 0100 8002 2C50 6B10 8F00 6450 C00C 0405  ....,Pk...dP....
00000030: 6C50 1138 0300 0440 3B07 4000 7440 0300  lP.8...@;.@.t@..
00000040: 0000 0040 0150 C2FF 1440 0004 003A 3460  ...@.P...@...:4`
00000050: 4400 0000 FFFF FFFF FFFF FFFF FFFF FFFF  D...............
00000060: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000070: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000080: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000090: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000000a0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000000b0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000000c0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000000d0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000000e0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000000f0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000100: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000110: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000120: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000130: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000140: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000150: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000160: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000170: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000180: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000190: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000001a0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000001b0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000001c0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000001d0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000001e0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
000001f0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00000200: B80C AB15 16E0 0302 6500 1F00 D49A 2060  ........e..... `
00000210: 06C6 0303 0000 0000 0000 0004 0900 0501  ................
00000220: 01FF 0200 0001 0000 00FB 0000 0000 0000  ................
00000230: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000240: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000250: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000270: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000280: 4001 0000 4001 0000 0000 0000 1002 0000  @...@...........
00000290: 0000 002C 2020 0B00 000B E200 0E0E 0E00  ...,  ..........
000002a0: 020E 1CCA CACA 0301 0000 0000 0000 0604  ................
000002b0: 0400 0000 0E0E 0215 1500 1F1F 002C 0000  .............,..
000002c0: 0000 0000 0404 1D09 0000 0000 0000 8E8C  ................
000002d0: 888E 8C88 0080 0000 0000 0000 0000 0000  ................
000002e0: 0000 0000 0000 0000 1000 0000 1000 0000  ................
000002f0: 0000 0000 1002 0000 0000 002D 2020 0B00  ...........-  ..
00000300: 0010 E200 0D0D 0D00 020E 1CFF FFFF 0301  ................

etc...

From the previously linked blogpost it looks like the country code resides at offset 520 or 0x208 and indeed that one contains 0x65. So one needs to change that to 0x60. (one still needs to change the checksum but later more about that)

When trying to write this back to the eeprom, it does not seem to do anything. A first clue comes for the iwleeprom output.

ath9k short eeprom base: 0  size: 512

The things I changed reside at addresses larger than byte 512. So I changed the iwleeprom to do just that.

diff --git a/iwleeprom/ath9kio.c b/iwleeprom/ath9kio.c
index c57062d..83187e3 100644
--- a/iwleeprom/ath9kio.c
+++ b/iwleeprom/ath9kio.c
@@ -799,8 +799,8 @@ static bool ath9k_eeprom_check(struct pcidev *dev)
 		goto ssize_ok;
 	}
 	if (dev->ops->eeprom_read16(dev, 512, &data) && (3256 == data)) {
-		short_eeprom_base =  0;
-		short_eeprom_size = 512;
+		short_eeprom_base =  512;
+		short_eeprom_size = 1024 * 4;
 		goto ssize_ok;
 	}
 	if (dev->ops->eeprom_read16(dev, 256, &data) && (727 == data)) {

Using this I was able to write back my changes to the eeprom. But Linux won’t be happy about this since the eeprom’s checksum still needs to be fixes. Looking at the linux source it looks like the checksum is computed by XORing each word of the eeprom and expects the result to be 0xffff. The bytes to fix this checksum result are bytes 514-515.

After some trial and error I found that the checksum needs to be computed from byte 512 to byte 4096, which the iwleeprom tool will correctly provide with the patch mentioned above.

Supported devices detected: 
  [1] 0000:02:00.0 [RW] AR928X Wireless Adapter (PCI-E) (168c:002a, 106b:008f)
Select device [1-1] (or 0 to quit): 1
Using device 0000:02:00.0 [RW] AR928X Wireless Adapter (PCI-E) 
IO driver: ath9k
HW: AR9280 (PCI-E) rev 0002
RF: integrated
Checking NVM size...
ath9k short eeprom base: 512  size: 4096
MAC address : d4:9a:20:60:06:c6
Reg. domain : 0065
Capabilities: 0203
       Bands:  5GHz 2.4GHz
       HT 2G:  HT20 HT40
       HT 5G:  HT20 HT40
CRC (stored): 15ab
Calculating EEPROM CRC...............................
CRC (eval)  : 15ab

What needs to be programmed at byte 0x202-0x203 is computed in the CRC (eval) entry. Use your favorite hexeditor to change this and write it back. (make a backup of the file you want to edit first) Reload the ath9k module

# ./iwleeprom -i modified.bin
# rmmod ath9k && modprobe ath9k 

and finally we get

Frequencies:
	* 5180 MHz [36] (20.0 dBm) (no IR)
	* 5200 MHz [40] (20.0 dBm) (no IR)
	* 5220 MHz [44] (20.0 dBm) (no IR)
	* 5240 MHz [48] (20.0 dBm) (no IR)
	* 5260 MHz [52] (20.0 dBm) (no IR, radar detection)
	* 5280 MHz [56] (20.0 dBm) (no IR, radar detection)
	* 5300 MHz [60] (20.0 dBm) (no IR, radar detection)
	* 5320 MHz [64] (20.0 dBm) (no IR, radar detection)
	* 5500 MHz [100] (27.0 dBm) (no IR, radar detection)
	* 5520 MHz [104] (27.0 dBm) (no IR, radar detection)
	* 5540 MHz [108] (27.0 dBm) (no IR, radar detection)
	* 5560 MHz [112] (27.0 dBm) (no IR, radar detection)
	* 5580 MHz [116] (27.0 dBm) (no IR, radar detection)
	* 5600 MHz [120] (27.0 dBm) (no IR, radar detection)
	* 5620 MHz [124] (27.0 dBm) (no IR, radar detection)
	* 5640 MHz [128] (27.0 dBm) (no IR, radar detection)
	* 5660 MHz [132] (27.0 dBm) (no IR, radar detection)
	* 5680 MHz [136] (27.0 dBm) (no IR, radar detection)
	* 5700 MHz [140] (27.0 dBm) (no IR, radar detection)
	* 5745 MHz [149] (disabled)
	* 5765 MHz [153] (disabled)
	* 5785 MHz [157] (disabled)
	* 5805 MHz [161] (disabled)
	* 5825 MHz [165] (disabled)