Thursday, June 13, 2019

Updating Lenovo T460 BIOS (UEFI, firmware) without a CD on Linux

Some of Lenovo's newer models come with a firmware update distribution compatible with fwupd so you can just do a firmware update directly from the shell.

This is not the case for the T460, which has a bootable ISO, or a Windows installer that ... creates a bootable ISO.

I couldn't find my USB CD/DVD drive so I landed up finding a workaround.



You can't use fwupd install to install this firmware because it's only distributed in a legacy format with a bootable self-installer, even though the system has a UEFI firmware that's entirely capable of self-updating. In fact, all the bootable installer seems to do is tell the UEFI firmware to self-update using the files it contains.

I suspect it's possible to unpack the image and make a fwupd manifest to install these, but I wasn't willing to take the risk. Instead I turned the CD image into a bootable USB key.

You'll need:

  • An up to date full backup of your system because if you break it, you don't get to complain to me!
  • Linux
  • A USB flash drive (USB key) you don't mind overwriting
  • The ISO9660 image for the firmware updater from Lenovo. Mine was r06uj66d.iso
  • In the boot setup menu (F1, UEFI manager / BIOS Setup): Secure Boot disabled and "Boot Mode" set to "Legacy" or to "Both"

You shouldn't actually need to use the backup you just made. But of course you didn't even have to make one because you already know your system is regularly backed up. In fact, you test your backups to make sure they work and are restore-able. Don't you? Yeah, good.

Onwards.

Extract the boot image


The Lenovo-supplied ISO9660 image doesn't appear to contain any files. If you loopback mount it or list its contents with isoinfo it'll look empty:

$ isoinfo -f -i r06uj66d.iso
$

But it isn't really empty. It has an El-Torito HDD-emulation image embedded in it, as you can see with isoinfo:

$ isoinfo -d -i r06uj66d.iso 
CD-ROM is in ISO 9660 format
System id: 
Volume id: R06ET66U
Volume set id: 
Publisher id: 
Data preparer id: 
Application id: NERO BURNING ROM VER 12,5,5,0
Copyright File id: 
Abstract File id: 
Bibliographic File id: 
Volume set size is: 1
Volume set sequence number is: 1
Logical block size is: 2048
Volume size is: 10934
El Torito VD version 1 found, boot catalog is in sector 20
Joliet with UCS level 3 found
NO Rock Ridge present
Eltorito validation header:
    Hid 1
    Arch 0 (x86)
    ID 'NERO BURNING ROM VER 12'
    Key 55 AA
    Eltorito defaultboot header:
        Bootid 88 (bootable)
        Boot media 4 (Hard Disk Emulation)
        Load segment 7C0
        Sys type 6
        Nsect 1
        Bootoff 1B 27

The image embedded within the ISO at `Bootoff` which is decimal 27 measured in 2048b ISO9660 sectors. In bytes that's:

$ echo $((27 * 2048))
55296

and the mode is "hard disk emulation", which is handy because a USB key is presented as a hard disk in legacy boot mode. That means the OS on the image won't get confused to find itself on a USB key not a CD (or floppy!).

All we have to do is extract the embedded image and dd it directly onto a USB key.

Extracting it is as simple as dd'ing from the offset:


$ dd if=r06uj66d.iso of=r06uj66d.img skip=27 bs=2048


... and you have a ready-made USB key image. Sanity-check it:


$ file r06uj66d.img
r06uj66d.img: DOS/MBR boot sector; partition 1 : ID=0x4, active, start-CHS (0x0,1,1), end-CHS (0x14,63,32), startsector 32, 42976 sectors

$ fdisk -l r06uj66d.img
Disk r06uj66d.img: 21.3 MiB, 22337536 bytes, 43628 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000

Device Boot Start End Sectors Size Id Type
r06uj66d.img1 * 32 43007 42976 21M 4 FAT16 <32M


You can also loopback mount it to inspect it if you like. You do not need to perform the following steps, but can if you want to poke around and see what's inside:

$ sudo losetup -f --show --offset=$((32 * 512)) r06uj66d.img
/dev/loop2
$ sudo mkdir -p /mnt/tmp
$ sudo mount -o ro /dev/loop2 /mnt/tmp
$ ls /mnt/tmp
 EFI   FLASH  'System Volume Information'

$ # when you're done
$ sudo umount /mnt/tmp
$ sudo losetup -d /dev/loop2

Now you're ready to copy it to bootable storage.

(Theoretically you could probably boot into the grub2 command line and boot the image with grub's chainloader, but I wasn't willing to risk that with a firwmare updater. Plus it's a legacy-boot image and my grub2 is UEFI.)

Identify and prepare the USB key


To do this it is vital that you have a USB key connected that has no data of any value to you on it because we're about to erase it.

There are probably handy utilities to make this easier, so if you have one, use it. I did it the manual way.

Find the USB key and unmount all its volume(s). Don't use "Eject" in your file manager as on some systems that tries to actually power-down the USB key or otherwise disconnect it, just unmount the file system(s). I suggest using lsblk to identify the USB key, e.g.:

$ sudo lsblk --output=NAME,SIZE,MODEL,VENDOR,SERIAL,TYPE,MOUNTPOINT,TRAN,SUBSYSTEMS
NAME          SIZE MODEL                                    VENDOR   SERIAL               TYPE MOUNTPOINT                     TRAN   SUBSYSTEMS
sda          14.5G                                                   XXXXXXXXXXXXXX       disk                                usb    block:scsi:usb:pci
└─sda1         21M                                                                        part /run/media/craig/Kmlib                block:scsi:usb:pci
nvme0n1     238.5G THNSFXXXXXXXX TOSHIBA                                     XXXXXXXXXXXX disk                                nvme   block:nvme:pci
├─nvme0n1p1   200M                                                                        part /boot/efi                      nvme   block:nvme:pci
├─nvme0n1p2   500M                                                                        part /boot                          nvme   block:nvme:pci
├─nvme0n1p3   7.8G                                                                        part [SWAP]                         nvme   block:nvme:pci
└─nvme0n1p4   230G                                                                        part /home                          nvme   block:nvme:pci

Here I only have one device with TRAN (transport) = usb, so that's easy enough. It's a 14.5G USB key, partitioned, with a single partition that's only 21MB - because I'm showing the one I already disk-imaged.

Beware that on many systems sda might be your main hard drive so do not blindly copy these instructions.

If in doubt, you can also remove the USB key and re-insert it then examine the output of dmesg which should say something like:

[ 2096.634323] usb-storage 1-2:1.0: USB Mass Storage device detected
...
[ 2097.893998] sd 0:0:0:0: [sda] 30310400 512-byte logical blocks: (15.5 GB/14.5 GiB)
....
[ 2097.898336]  sda: sda1
[ 2097.901194] sd 0:0:0:0: [sda] Attached SCSI removable disk

The device name should match up with what you saw in lsblk.

Triple-check it doesn't have anything you care about on it now:

Now unmount any file system(s) on the USB key and flush the kernel write buffers, so you don't get the file system trying to write to the USB key at the same time we dd directly to its underlying storage. Check which partitions are mounted using blockdev output from above, and/or the output of the mount command without arguments. Then unmount any/all by specifying the mountpoint path or block-device path to umount as root. Check each mounted volume before unmounting to verify there's nothing on there you want to keep, e.g.

$ ls /run/media/craig/Kmlib

then unmount. In my particular case I needed to:

$ sudo umount /dev/sda1

or (either would've worked):

$ sudo umount /run/media/craig/Kmlib

Finally we flush any write buffers. This shouldn't be needed, but never hurts. The sync command produces no output.

$ sync

Time to erase the USB key. No turning back now.

Overwrite the USB key with the boot image


DO NOT COPY THESE INSTRUCTIONS WITHOUT HAVING READ AND UNDERSTOOD THE ABOVE. YOU MIGHT ERASE YOUR HARD DRIVE AND LOSE ALL YOUR DATA. DO NOT COPY THESE INSTRUCTIONS BLINDLY. DO NOT ASSUME YOUR USB KEY IS AT /dev/sda. YOU HAVE BEEN WARNED.

Copy the disk image we prepared to the USB key and flush the write buffers:


$ sudo dd if=r06uj66d.img of=/dev/sda conv=dsync status=progress
$


(If you get an error about the status=progress option just leave it off. If you get an error about the conv=dsync option, leave it off and run "sync" afterwards instead.)

Woo. You have a bootable USB firmware updater.

Boot into the updater


Reboot. Press F12 during boot to get the boot menu. Choose "USB HDD:" from the items list.

It'll boot into the updater. From there follow the instructions.

It's not joking about not removing the power or interrupting the process though. It's truly vital you do this on AC power and with a battery that's charged. Really.

No comments:

Post a Comment