most bios kinda suck
The manufacturer is not going to add every random little feature you might want, every feature means testing, regressions, bugs, support, etc. So. What are we going to do about it?
x8x8?
So you get a choice here between x16, and x4x4x4x4. Great. Where is my x8x8?
Lets just skip the bios entirely and write to nvram
MAD SCIENCE
when your pc boots up, that delightful piece of magic sand in the socket gets instructed how to divvy up pcie slots. For slot 2 on this specific board, we want to write UEFI configuration variable: SocketIioConfig[0xAE] = 0x03
The ASUS Pro WS W790E-SAGE
I want slot 2 x8/x8 bifurcation on stock BIOS 2003. Maybe I can just… write a uefi shell program that fires early and does it?
Yeah, we got blocked. of course.
SUCKurity!
We will need to shut this off.
I dont have a user/manager/admin/bios password of any kind set but, this feature gets in the way.
Sneky Snek
Quick python script to set the var now that security is off:
#!/usr/bin/env python3
"""ASUS Pro WS W790E-SAGE SE BIOS 2003: inspect or set slot 2 to x8/x8.
Portable adaptation of the guarded writer used on 2026-09-12. Default operation
is read-only; a write requires --commit, two reviewed preimage hashes, and a new
backup directory. This packaged version has been tested with mocked hardware,
not used for another motherboard write. No third-party Python dependencies.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import subprocess
EFI = Path('/sys/firmware/efi/efivars')
SOCKET = EFI / 'SocketIioConfig-dd84017e-7f52-48f9-b16e-50ed9e0dbe27'
SETUP = EFI / 'Setup-ec87d643-eba4-4bb5-a1e5-3f3e36b20da9'
BOARD = Path('/sys/class/dmi/id/board_name')
BIOS = Path('/sys/class/dmi/id/bios_version')
OFFSET = 4 + 0xAE # efivarfs attributes prefix plus variable payload offset
def require(condition, message):
if not condition:
raise RuntimeError(message)
def digest(data):
return hashlib.sha256(data).hexdigest()
def flags():
return subprocess.check_output(['lsattr', str(SOCKET)], text=True).split()[0]
def read_state():
require(BOARD.read_text().strip() == 'Pro WS W790E-SAGE SE', 'Wrong motherboard')
require(BIOS.read_text().strip() == '2003', 'Wrong BIOS; offsets are version-specific')
socket, setup = SOCKET.read_bytes(), SETUP.read_bytes()
require(len(socket) == 0x201F + 4, 'Unexpected SocketIioConfig length')
require(len(setup) == 0x25C + 4, 'Unexpected Setup length')
require(socket[:4] == setup[:4] == b'\x07\0\0\0', 'Unexpected variable attributes')
return socket, setup
def describe(socket, setup):
return {
'socket_sha256': digest(socket), 'setup_sha256': digest(setup),
'socket_raw_bytes': len(socket), 'setup_raw_bytes': len(setup),
'socket_attributes': socket[:4].hex(),
'IOU0_through_IOU6': [f'{x:02x}' for x in socket[4 + 0xA8:4 + 0xAF]],
'slot2_payload_AE': f'{socket[OFFSET]:02x}',
'port7E_menu_suppression_8B6': f'{socket[4 + 0x8B6]:02x}',
'ASUS_slot2_23E': f'{setup[4 + 0x23E]:02x}',
'runtime_protection_EF': f'{setup[4 + 0xEF]:02x}',
'Redfish_56': f'{setup[4 + 0x56]:02x}',
}
def differences(before, after):
require(len(before) == len(after), 'Comparison length mismatch')
require(before[:4] == after[:4], 'Comparison attributes mismatch')
return [{'payload_offset': hex(i - 4), 'before': f'{a:02x}', 'after': f'{b:02x}'}
for i, (a, b) in enumerate(zip(before, after)) if a != b]
def candidate_for(socket, setup, value):
require(setup[4 + 0xEF] == 0, 'Runtime protection is not disabled')
require(setup[4 + 0x23E] == 0, 'ASUS visible slot-2 choice must be x16 for this procedure')
require(socket[4 + 0xA8:4 + 0xAE] == b'\xff' * 6,
'Other IOU selectors are not Auto; this narrowly scoped procedure does not apply')
require(socket[OFFSET] in (0xFF, 3), 'Unexpected slot-2 selector')
candidate = bytearray(socket)
candidate[OFFSET] = 3 if value == 'x8x8' else 0xFF
candidate = bytes(candidate)
require([i for i, (a, b) in enumerate(zip(socket, candidate)) if a != b]
in ([], [OFFSET]), 'Unexpected candidate changes')
return candidate
def commit(socket, setup, candidate, backup_dir):
require(os.geteuid() == 0, 'Root required')
original_flags = flags()
backup_dir.mkdir(mode=0o700, parents=False, exist_ok=False)
for name, data in [('SocketIioConfig.efivar', socket), ('Setup.efivar', setup)]:
with (backup_dir / name).open('xb') as backup:
backup.write(data)
backup.flush()
os.fsync(backup.fileno())
require((backup_dir / name).read_bytes() == data, 'Backup readback mismatch')
print(f'Full variable backups saved to {backup_dir.resolve()}', flush=True)
fd = None
try:
if 'i' in original_flags:
subprocess.run(['chattr', '-i', str(SOCKET)], check=True)
require(SOCKET.read_bytes() == socket and SETUP.read_bytes() == setup,
'Concurrent variable change; stop and inspect again')
fd = os.open(SOCKET, os.O_WRONLY | os.O_CLOEXEC | os.O_NOFOLLOW)
# One complete attributes+payload write. Never seek, truncate, create,
# delete, loop on a short write, or write a raw flash device.
written = os.write(fd, candidate)
require(written == len(candidate), 'Short firmware write; STOP and do not reboot')
finally:
try:
if fd is not None:
os.close(fd)
finally:
if 'i' in original_flags:
subprocess.run(['chattr', '+i', str(SOCKET)], check=True)
require(SOCKET.read_bytes() == candidate, 'Full readback mismatch; STOP and do not reboot')
require(SETUP.read_bytes() == setup, 'Setup changed unexpectedly; STOP and do not reboot')
require(flags() == original_flags, 'File flags not restored; STOP and inspect')
return {'bytes_written': written, 'readback_exact': True,
'setup_unchanged': True, 'flags_restored': True, 'reboot_performed': False}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--value', choices=['x8x8', 'auto'])
parser.add_argument('--expected-sha')
parser.add_argument('--expected-setup-sha')
parser.add_argument('--commit', action='store_true')
parser.add_argument('--backup-dir', type=Path)
parser.add_argument('--compare-dir', type=Path, help='Read-only comparison with a previous backup')
args = parser.parse_args()
require(not args.commit or args.value is not None, '--commit requires --value')
require(not args.compare_dir or args.value is None, '--compare-dir is read-only')
socket, setup = read_state()
report = describe(socket, setup)
if args.compare_dir:
report['socket_changes_since_backup'] = differences(
(args.compare_dir / 'SocketIioConfig.efivar').read_bytes(), socket)
report['setup_changes_since_backup'] = differences(
(args.compare_dir / 'Setup.efivar').read_bytes(), setup)
if args.value is not None:
require(digest(socket) == args.expected_sha, 'Socket preimage hash missing or mismatched')
require(digest(setup) == args.expected_setup_sha, 'Setup preimage hash missing or mismatched')
candidate = candidate_for(socket, setup, args.value)
report.update(mode='commit' if args.commit else 'dry-run', value=args.value,
candidate_sha256=digest(candidate),
proposed_changes=differences(socket, candidate))
print(json.dumps(report, indent=2), flush=True)
if not args.commit or candidate == socket:
return
require(args.backup_dir is not None, '--commit requires a new --backup-dir')
print(json.dumps(commit(socket, setup, candidate, args.backup_dir), indent=2))
else:
report['mode'] = 'read-only'
print(json.dumps(report, indent=2))
if __name__ == '__main__':
main()
Scope and prerequisites
These offsets are for Pro WS W790E-SAGE SE, BIOS 2003. They should be re-derived for another BIOS or board. This procedure was tested with a W-3400-series processor; it cannot enable CPU lanes that are unavailable with a different processor. ASUS documents different slot availability for W-3400 and W-2400 CPUs. Board specifications.
Writing the wrong firmware variable can prevent POST. Have local console/BMC access, record your existing BIOS settings, and understand the board’s recovery procedures before experimenting. The variable backups below support investigation and a controlled rollback; they are not a complete SPI recovery image. Recovery from a non-POSTing hidden-variable configuration was not demonstrated in this test.
For the actual change, you need:
| Software or hardware | Purpose |
|---|---|
| Bare-metal Linux booted through UEFI, with root access | Access to the motherboard’s EFI Runtime Variable Services through efivarfs |
| Python 3 | Run the guarded variable reader/writer; standard library only |
e2fsprogs |
Provides lsattr and chattr for the Linux immutable-file flag |
pciutils |
Provides lspci for endpoint, root-port, and link-width checks |
coreutils and util-linux |
Hashes, file tools, and mount inspection |
| Passive x16-to-two-x8 breakout, appropriate MCIO cables, powered endpoint risers, two PCIe devices | Exercise both physical lane groups |
On Ubuntu, the software packages you want are:
apt update
apt install python3 e2fsprogs pciutils coreutils util-linux
The final working path used Linux’s existing firmware interface. Firmware extraction and reverse-engineering tools are listed later for anyone who wants to reproduce the analysis. The exact hardware I used was a shinreal passive pcie x16 to two mcio x8x8 ports to two different risers.
What the hidden setting actually controls
The relevant variable is:
Name: SocketIioConfig
GUID: dd84017e-7f52-48f9-b16e-50ed9e0dbe27
Attributes: 0x00000007 (nonvolatile, boot-service, runtime)
Payload size: 0x201F = 8223 bytes
efivarfs file size: 0x2023 = 8227 bytes
Physical slot 2: Intel IOU6 / IIO port 7
Target payload byte: 0xAE
Original value: 0xFF (Auto)
Desired value: 0x03 (x8/x8)
Its Linux path is:
/sys/firmware/efi/efivars/SocketIioConfig-dd84017e-7f52-48f9-b16e-50ed9e0dbe27
The first four bytes of an efivarfs file are the little-endian attributes word. Therefore payload offset 0xAE is file offset 0xB2. Neither is a physical SPI-flash address. Linux efivarfs documentation.
The hidden Intel form defines these selector values:
| Byte | Mode |
|---|---|
FF |
Auto |
00 |
x4/x4/x4/x4 |
01 |
x4/x4/x8 |
02 |
x8/x4/x4 |
03 |
x8/x8 |
04 |
x16 |
The physical slots do not follow the Intel IOU numbering. Disassembly and offline execution of the ASUS board-policy callback established this mapping:
| Physical slot | Intel IOU | SocketIioConfig payload offset |
|---|---|---|
| 1 | IOU0 | 0xA8 |
| 2 | IOU6 | 0xAE |
| 3 | IOU2 | 0xAA |
| 4 | IOU5 | 0xAD |
| 5 | IOU1 | 0xA9 |
| 6 | IOU4 | 0xAC |
| 7 | IOU3 | 0xAB |
Slot 6 is electrically x8 on the W-3400 configuration, so this table is not a claim that every slot can provide two x8 links. ASUS electrical slot widths.
That slot6 is actually shared with the two m.2 ports near the CPU on the motherboard, so internally its doing x8x4x4 already if you change that you will make one/all of that not work right. But… if you used m.2 redrivers converting 2x4 electrical back into x8 mcio you COULD theoretically get a single x16 slot on a riser… MUAHA HAHAH HAHAHAHAHAaaa

ASUS also stores its visible slot-2 choice at Setup[0x23E], in the separate Setup variable with GUID ec87d643-eba4-4bb5-a1e5-3f3e36b20da9. That byte uses ASUS’s own encoding. Do not put Intel’s 03 into the ASUS Setup byte.
The key behavior is that ASUS translates its visible setting only while the effective Intel selector is Auto. This is reconstructed pseudocode from the executable, not ASUS source:
// Earlier firmware copies the saved SocketIioConfig IOU selectors into RAM policy.
if (effective_iou[6] == 0xFF) {
if (Setup[0x23E] == 0x00)
effective_iou[6] = 0x04; // x16
else if (Setup[0x23E] == 0x01)
effective_iou[6] = 0x00; // x4/x4/x4/x4
}
// An explicit 0x03 is preserved by this callback.
That explains why our visible ASUS menu could still say x16 while the actual slot trained as x8/x8.
The write protection that stopped the early attempts
Initial Linux writes failed with EACCES. An external EFI program and HII RouteConfig attempts also received security-policy failures. Even an identical-data request was rejected, so this was not evidence that the firmware specifically prohibited the x8/x8 value.
In the bios menu:
Advanced
-> UEFI Variables Protection
-> Password protection of Runtime Variables
-> Disable
ASUS documents this control on printed page 45 of the W790 series BIOS manual. In BIOS 2003 it corresponds to Setup[0xEF]: 01 enabled, 00 disabled.
The Linux immutable flag is a separate layer. The successful writer temporarily clears that flag on SocketIioConfig and restores its original state afterward. Linux deliberately marks many vendor variables immutable because deleting them can break firmware. Clearing the flag alone does not override firmware authorization. Kernel explanation.
Runtime-variable protection remained disabled at the end of the successful test. Persistence after re-enabling it is still an open verification item; restoring Linux’s immutable flag does not restore ASUS’s protection setting.
Replication procedure
Keep physical slot 2 empty for the first write and reboot. Use a separate display/boot path. An empty slot makes diagnosis easier, but a bad firmware setting can still prevent POST.
1. Confirm the board and UEFI boot, then inspect the variables.
Save the attached slot2_bifurcation.py locally and review it. It is a portable adaptation of the original guarded writer, with automatic backups and an inspection mode added. The original writer performed the hardware test; this packaged version has been tested against temporary files and mocked firmware failures, without another live firmware write.
cat /sys/class/dmi/id/board_name
cat /sys/class/dmi/id/bios_version
findmnt /sys/firmware/efi/efivars
python3 slot2_bifurcation.py
The script refuses a different board, a BIOS other than 2003, or unexpected variable lengths/attributes. If the EFI directory is absent, check that Linux was booted through UEFI on the actual motherboard. A VM’s variables belong to the VM.
After saving the protection change and returning to Linux, inspect again. The starting configuration used here is:
IOU0_through_IOU6: ff ff ff ff ff ff ff
ASUS_slot2_23E: 00 (visible x16 choice)
runtime_protection_EF: 00 (disabled)
Keep the ASUS slot-2 choice at x16 for this procedure. For GPU/NIC use, also check that slot-2 VROC is disabled; it is a separate setting. Record any other configuration differences before proceeding.
2. Review a dry run using your own current hashes.
The inspection prints socket_sha256 and setup_sha256. Substitute those values below. These are guards against changing a different state from the one you inspected; somebody else’s variable hashes will usually differ.
python3 slot2_bifurcation.py --value x8x8 \
--expected-sha YOUR_CURRENT_SOCKET_SHA256 \
--expected-setup-sha YOUR_CURRENT_SETUP_SHA256
The proposed change must be exactly:
[{"payload_offset": "0xae", "before": "ff", "after": "03"}]
The script intentionally refuses other non-Auto IOU selectors or an unexpected ASUS slot-2 setting. Those cases need their own review, not removal of the guards.
3. Commit the one-byte change and verify the complete readback.
Use the same reviewed hashes, without another reboot or intervening BIOS changes:
python3 slot2_bifurcation.py --value x8x8 \
--expected-sha YOUR_CURRENT_SOCKET_SHA256 \
--expected-setup-sha YOUR_CURRENT_SETUP_SHA256 \
--backup-dir /root/w790-slot2-before --commit
Choose a new backup-directory name if it already exists. Before touching the live variable, the program saves complete copies of SocketIioConfig and Setup to a private directory and checks them. Preserve a second copy on another machine or removable storage before rebooting. Firmware configuration backups can contain machine-specific data; keep the full blobs private.
The writer preserves the four-byte attributes header and the entire current payload, changing only the target byte in memory. It makes one full-buffer write to the existing efivarfs object, then checks the whole result and verifies that Setup was unchanged and file flags were restored. It does not reboot.
Expect bytes_written: 8227, readback_exact: true, setup_unchanged: true, and flags_restored: true. Stop on any error, rejection, short write, or unexpected difference. Do not reboot into a state whose write result is uncertain.
Do not substitute a one-byte dd, a hex editor’s generic save operation, file deletion/recreation, or a raw SPI offset. efivarfs is a firmware interface with attributes and variable-update semantics.
4. Reboot with the slot still empty and check persistence.
After the successful checks and off-machine backup:
systemctl reboot
Once Linux returns:
python3 slot2_bifurcation.py --compare-dir /root/w790-slot2-before
lspci -Dtv
The slot-2 selector remained 03. Relative to the pre-write backup, the entire Socket variable had just two differences:
| Payload offset | Before | After reboot | Interpretation |
|---|---|---|---|
0xAE |
FF |
03 |
Requested x8/x8 selector |
0x8B6 |
01 |
00 |
Firmware cleared the known Port 7E configuration-menu suppression flag |
Setup was unchanged. The second byte was changed by firmware during boot; we did not manually write it. Do not copy it or the other menu/status fields as extra bifurcation settings. Review any different changes on your board.
Our empty-slot PCI inventory did not expose two new usable root ports. A saved selector and successful POST are useful checks, but actual devices are needed to prove both branches train.
5. Power down, install the breakout and two endpoints, then boot.
Shut Linux down normally, wait for it to stop, remove PSU/standby power, and then install the breakout, cables, endpoint risers, and device power. Restore power and boot.
Read the variable again, then inspect the PCI tree:
python3 slot2_bifurcation.py --compare-dir /root/w790-slot2-before
lspci -Dtv
lspci -Dnn
The simplified topology from our test was:
Physical PCIEX16(G5)_2 -> passive x8/x8 breakout
CPU root port c9:01.0 -> ca:00.0 NVIDIA GPU : 16 GT/s x8
CPU root port c9:05.0 -> cb:00.0 Intel X540 : 5 GT/s x8
Use your own enumerated bus addresses in these commands:
lspci -D -s 0000:c9:01.0 -vv
lspci -D -s 0000:ca:00.0 -vv
lspci -D -s 0000:c9:05.0 -vv
lspci -D -s 0000:cb:00.0 -vv
cat /sys/bus/pci/devices/0000:ca:00.0/current_link_width
cat /sys/bus/pci/devices/0000:cb:00.0/current_link_width
readlink -f /sys/bus/pci/devices/0000:ca:00.0
readlink -f /sys/bus/pci/devices/0000:cb:00.0
Check LnkSta, the negotiated link, at both ends of each branch. LnkCap is capability, not the negotiated result. Both sysfs width values were 8, and the parent paths led to different CPU root ports. The GPU audio function and NIC’s second function share their respective device links; they are not additional x8 branches.
Linux bus numbers and firmware-reported slot labels can differ from the board silkscreen. In this test one CPU root port reported “Slot #7” despite the owner-confirmed physical installation in slot 2. Use physical placement, the audited mapping, and PCI parentage together.
Rollback and persistence limits
The tested Linux round trip was FF -> 03 -> FF, with complete readback at each stage. To return to ASUS-controlled Auto after the final boot, inspect the current variables, then use freshly reviewed current hashes:
python3 slot2_bifurcation.py
python3 slot2_bifurcation.py --value auto \
--expected-sha YOUR_CURRENT_SOCKET_SHA256 \
--expected-setup-sha YOUR_CURRENT_SETUP_SHA256
After reviewing that dry run:
python3 slot2_bifurcation.py --value auto \
--expected-sha YOUR_CURRENT_SOCKET_SHA256 \
--expected-setup-sha YOUR_CURRENT_SETUP_SHA256 \
--backup-dir /root/w790-slot2-before-rollback --commit
This restores only 0xAE to FF, preserving all other current bytes, including firmware-maintained status. With the visible ASUS choice still at x16, Auto lets its board callback select x16 on the next boot. Power down and remove the breakout before testing that topology. Restore runtime-variable protection through the built-in BIOS UI when finished with the experiment, then verify the resulting variables and behavior.
Our final x8/x8 setting survived an ordinary reboot and a subsequent shutdown/power-off/install boot. The original script’s post-boot Auto rollback was dry-run only; we kept the working split. Persistence across BIOS updates, Load Defaults, profile imports, arbitrary Setup saves, or re-enabling runtime-variable protection was not tested. Treat those as reasons to recheck the saved selector and links.
If the machine stops POSTing, use the board’s documented recovery controls and local/BMC diagnosis; removing the breakout with power fully off is an initial hardware isolation step. Safe Boot, Clear CMOS, and official BIOS FlashBack are documented facilities, but none was demonstrated as a guaranteed reset of this hidden variable. Consult the motherboard manual. Do not blindly copy an old full variable over a newer configuration or write guessed physical NVRAM addresses.
How we found and checked it
For anyone wanting to reproduce the firmware analysis, these were the relevant tools and artifacts:
| Tool | Role |
|---|---|
| UEFITool / UEFIExtract | Open the ASUS capsule and extract firmware modules; the retained tool archive is NE A75 |
| IFRExtractor-RS | Decode the HII forms into text: variable GUIDs, payload offsets, hidden options, and suppression conditions; exact executable version was not retained in the notes |
| pefile 2024.8.26 | Load extracted PE images and interpret their sections/addresses |
| Capstone 5.0.9 | Disassemble the configuration and board-policy code |
| Unicorn 2.1.4 | Execute the original ASUS callback offline with mocked firmware services |
Download BIOS 2003 from the ASUS board support page. The analyzed Pro-WS-W790E-SAGE-SE-ASUS-2003.CAP had SHA-256:
a3af82847942227c11755db9ff5087cc4ef0f072ccfe421f1ed05d77446b5cc4
In UEFITool, locate the firmware files by GUID below and extract the body of their PE32 image section. Feed the Intel and ASUS Setup bodies to IFRExtractor-RS. Its text should show SocketIioConfig, the IOU6 question at 0xAE, and the X8/X8 option value 3. The Intel form group is hidden by an always-true suppression expression.
| Firmware file GUID | What to inspect |
|---|---|
6B6FD380-2C55-42C6-98BF-CBBC5A9AA666 |
Intel Socket Config forms; IOU6 question 0x82, offset 0xAE |
899407D7-99FE-43D8-9A21-79EC328CAC21 |
ASUS forms; visible slot-2 question 0x2EC3 at Setup 0x23E; protection question 0x58E at 0xEF |
EE685731-CFF3-4EE7-9388-7E63FC5A59B0 |
Saved IOU selectors copied into effective policy at policy + 0xA9B + index |
097A2930-1B0B-47B7-BFEE-AFDAECD8C184 |
ASUS board callback: entry 0xFFED33C1, Auto-only write helper 0xFFED33A6 |
The address values in the final row are addresses within the extracted executable’s image mapping, not flash-write offsets. The board-policy PE body hash was:
3fe94e94dcaf037b48bce080663dbce80aec2e69c01cf39f1dc6f3618405ccb9
Stuff I didnt test fully yet
Both new root ports still had ACS request/completion redirection enabled. Bifurcation does not itself establish a GPU P2P path, suitable IOMMU behavior, adequate BAR allocation for a large GPU population, or any particular transfer bandwidth.
But, the end result:
| Branch | CPU root port | Endpoint | Negotiated link |
|---|---|---|---|
| First x8 | 0000:c9:01.0 |
NVIDIA GPU, 10de:25b2, at 0000:ca:00.0 |
16 GT/s, x8 |
| Second x8 | 0000:c9:05.0 |
Intel X540, 8086:1528, at 0000:cb:00.0 |
5 GT/s, x8 |
Both endpoints and their upstream CPU root ports reported x8. Linux sysfs independently agreed. The NIC’s 5 GT/s link matched its advertised capability. The GPU advertised x16 capability, so its x8 “downgraded” label was expected for this split.
So, in closing
IF your bios doesnt forcibly overwrite your intel iou config flags you can just set them how you want without the bios ever being the wiser. Just watch out for surprises if you need to go back in there and change things or you might wind up in an error state until you unplug, power on, run your customization scriptZ again, and plug it all back in.



