GitHub: crucial-ssd-firmware-grub
0. 前言
本身有台 Thinkpad X220,內裝有 Micron Crucial SSD 兩顆,分別是 Crucial M4 mSata SSD 128G (CT128M4SSD3) 以及 Crucial MX200 256G (CT250MX200SSD1)。前者在 2012 年購買,後者在 2015 年購買。
這次想要升級的原因,是因為在 windows 下發現作為系統碟的 M4 mSata 出現多磁區錯誤以及重新分配的狀況,查看其韌體後發現是最老的版本 (01MH),目前網路上可以查到的版本為 07MH。為了 SSD 的健康,決定升級韌體。
在美光的網站下載相對應的韌體後,解壓縮開啟程式準備安裝。參照最新的 Guideline 後,可以得知目前 (2015後) 都會採用 bootable 的方式進行,因此不用再額外準備 USB 來製作開機碟。這是好事,但是一想到電腦上有 GRUB,就覺得有點問題。
果不其然,在安裝好韌體更新程式後,電腦自動重新開機,回到 GRUB2 的選單,選擇 windows 後轉入 repair mode。但是在這個環節出現無法開機 (not bootable) 的畫面,連續點選確認後,跳回 windows 7 運行選單,選擇後轉回正常 win7 開機畫面。
1. 找尋 Linux / GRUB 解決方案
在網路上翻找一番,在 guillaume 於 2016/2/28 發表的 Update any crucial ssd firmware from linux/grub 找到一份以 perl 寫成的 script,其作法非常簡潔。以 root 權限執行 script 後,會下載在 crucial-fw.cfg 設定相對應得 firmware zip file,解壓出裡面的 iso,放入 /boot 下同時產生 grub config file,使用者只要重新產生 grub.cfg,重新開機後就會看到相對應的選單。
原理是因為,這些 bootable iso 是以 syslinux 的 isolinux 與 memdisk 的格式包裝,因此只要在 GRUB 中做相對應的設定,就可以在開機的時候進入。作者在文章中有提到:
Since these isos are using isolinux … an unfair nonsense.
Why didn’t they add “and Linux and BSD and everything x86”? or simply put “for PC and Mac” (another possible nonsense since a Mac is a classic x86 hardware platform).
2. 分析 crucial-fw.pl
以下簡單分析 crucial-fw.pl 這個 script 在做什麼事情,使他可以做成 GRUB2 的 Crucial firmware update options.
1 2 3 4 5 6 7 8 9 10 |
## Crucial firmware 設定檔,以 name (tab) url 的方式儲存資料 my $LIST = “./crucial-fw.cfg”; ## 下載下來的 zip file 會放置在 fw 中,並且解壓縮 my $DWN_DIR = “./fw”; ## 臨時用來 mount firmware iso 的資料夾,用來檢測 iso 的 boot type my $MNT_DIR = “./mnt”; ## GRUB2 設定檔的位置 my $GRUB_FILE = “/etc/grub.d/45_crucial-fw”; ## 放置 bootable iso 的位置 my $ISO_DIR = “/boot/crucial-fw”; |
可以看到,因為 script 會需要處理到需要 root 權限的東西 (mount, /boot/),因此會要求在執行的時候以 root 權限執行。
重要的部分是在 script 製作 grub config file 以及檢測 iso boot type 的部分。整個 grub config file 的 template 是這樣的格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/sh exec tail –n +3 $0 submenu ‘Crucial Firmware Update’ { menuentry “$tg FW” { insmod loopback set isofile=“$ISO_DIR/tg.iso” search –sf $isofile loopback loop $isofile # Is linux16 (memdisk) # linux16 (loop)/boot/isolinux/memdisk # initrd16 (loop)/boot/isolinux/boot2880.img # Is initrd (isolinux), first parse append boot options # linux (loop)/boot/vmlinuz $append # linux (loop)/boot/core.gz } } |
在每個 iso file 中,script 會將他 mount 到前面設定的 mnt 資料夾中,接著檢查該 iso 中是否有存在 memdisk / boot2880.img 或是 vmlinuz / core.gz 檔案。如果有存在 memdisk 的話,在 menuentry 中會輸出 linux16 的部分。如果有存在 vmlinuz 的話,會輸出 initrd 的部分。
整個 script 最核心的部分就是這邊,透過這樣的方式便能夠輸出一份 GRUB config file,使用者只要在透過 update-gurb2
或 gurb-mkconfig
就能夠輸出含有 Crucial Firmware Update 的 GRUB.cfg。
3. 轉換為 Python (>3.3)
看不太懂 Perl,因此把整個 script 轉換為 Python,除了提升可讀性外,另一個好處是都使用 Python-builtin 的套件,不用像 Perl 需要額外安裝其他套件。
完整的 source code 放置於 GitHub: crucial-ssd-firmware-grub
原本 guillaume 的 code 是宣告為 public domain,在這邊也是以 unlicense 的方式發佈。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
#! /usr/bin/env python3 # -*- coding: utf-8 -*- # # 2017 Louie Lu <me at louie.lu> # # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to <http://unlicense.org/> # import logging import os import shutil import zipfile import tempfile import urllib3 CHUNK_SIZE = 2048 CONFIG_FILE = ‘crucial-fw.cfg’ GRUB_FILE = ‘/etc/grub.d/45_crucial_fw’ BOOT_ISO_DIR = ‘/boot/crucial-fw’ DOWNLOAD_DIR = tempfile.mkdtemp() def read_config(path): f = open(path).readlines() f = filter(lambda x: not x.startswith(‘#’), f) f = filter(lambda x: not x.startswith(‘\n’), f) f = filter(lambda x: not x.startswith(‘\n\r’), f) f = list(map(lambda x: x.strip(‘\n\r’).split(‘\t’), f)) return f def open_custom_grub_config(): f = open(GRUB_FILE, ‘w’) f.write(“#!/bin/sh\nexec tail -n +3 $0\nsubmenu ‘Crucial Firmware Update'{\n”) return f def download_firmware_zip(name, url): logging.info(‘ [*] Downloading…’) http = urllib3.PoolManager() r = http.request(‘GET’, url, preload_content=False) path = ‘%s/%s.zip’ % (DOWNLOAD_DIR, name) with open(path, ‘wb’) as f: while True: data = r.read(CHUNK_SIZE) if not data: break f.write(data) r.release_conn() return path def extract_zip_to_iso(path): logging.info(‘ [*] Uncompressing…’) z = zipfile.ZipFile(path, ‘r’) iso = z.namelist()[0] z.extractall(DOWNLOAD_DIR) z.close() return iso def move_iso_to_boot(iso): logging.info(‘ [*] Moving iso to %s’ % (BOOT_ISO_DIR)) if not os.path.isdir(BOOT_ISO_DIR): os.mkdir(BOOT_ISO_DIR) if os.path.isfile(‘%s/%s’ % (BOOT_ISO_DIR, iso)): os.remove(‘%s/%s’ % (BOOT_ISO_DIR, iso)) shutil.move(‘%s/%s’ % (DOWNLOAD_DIR, iso), BOOT_ISO_DIR) def parse_isolinux_config(path): with open(‘%s/boot/isolinux/isolinux.cfg’ % (path)) as f: for n in f.readlines(): if n.startswith(‘APPEND’): return ‘ ‘.join(n.strip(‘\n’).split(‘ ‘)[1:]) return ” def make_grub_menuentry(fgb, name, iso): with tempfile.TemporaryDirectory() as mount_dir: logging.debug(‘Mount dir: ‘, mount_dir) logging.info(‘ Grub mode:’) os.system(‘mount -o loop %s/%s %s 2>/dev/null’ % (BOOT_ISO_DIR, iso, mount_dir)) fgb.write( ”‘menuentry “%s FW” { insmod loopback set isofile=”%s/%s” search -sf $isofile loopback loop $isofile ‘” % (name, BOOT_ISO_DIR, iso)) if (os.path.isfile(‘%s/boot/isolinux/memdisk’ % (mount_dir)) and os.path.isfile(‘%s/boot/isolinux/boot2880.img’ % (mount_dir))): logging.info(‘ linux16’) fgb.write( ”‘linux16 (loop)/boot/isolinux/memdisk initrd16 (loop)/boot/isolinux/boot2880.img\n’”) elif (os.path.isfile(‘%s/boot/vmlinuz’ % (mount_dir)) and os.path.isfile(‘%s/boot/core.gz’ % (mount_dir))): logging.info(‘ initrd’) append = parse_isolinux_config(mount_dir) fgb.write( ”‘linux (loop)/boot/vmlinuz %s initrd (loop)/boot/core.gz\n’” % (append)) else: logging.info(‘ Unknow type, failed’) fgb.write(‘}\n’) os.system(‘umount %s’ % (mount_dir)) if __name__ == ‘__main__’: logging.basicConfig(level=‘INFO’) logging.debug(‘Download dir: %s’ % (DOWNLOAD_DIR)) firmwares = read_config(CONFIG_FILE) fgb = open_custom_grub_config() for name, url in firmwares: logging.info(‘Target: %s’ % (name)) zp = download_firmware_zip(name, url) iso = extract_zip_to_iso(zp) move_iso_to_boot(iso) make_grub_menuentry(fgb, name, iso) fgb.write(‘}\n’) fgb.close() os.chmod(GRUB_FILE, 0o755) shutil.rmtree(DOWNLOAD_DIR) print(‘Generate GRUB rules in: %s’ % (GRUB_FILE)) print(‘ ISO in: %s’ % (BOOT_ISO_DIR)) print(‘Please run “update-grub2” or “grub-mkconfig -o /boot/grub/grub.cfg’) print(‘to generate new grub file.’) print(‘And reboot to choose “Crucial Firmware Update in GRUB2.\n’) |
4. Syslinux Project
The SYSLINUX Project is a suite of lightweight master boot record (MBR) boot loaders for starting up IBM PC compatible computers with the Linux kernel. Primarily developed by H. Peter Anvin, the SYSLINUX bundle consists of several separate systems used for different purposes, including ISOLINUX, PXELINUX and EXTLINUX. — wikipedia <Syslinux>
Crucial 在不同的 SSD firmware 中,用了不同的方式打包成 bootable iso,一種使用了 memdisk、一種是 isolinux,兩種格式都是 Syslinux Project 底下的格式。
4.1 Memdisk
MEMDISK is meant to allow booting legacy operating systems. MEMDISK can boot floppy images, hard disk images and some ISO images.
MEMDISK simulates a disk by claiming a chunk of high memory for the disk and a (very small – 2K typical) chunk of low (DOS) memory for the driver itself, then hooking the INT 13h (disk driver) and INT 15h (memory query) BIOS interrupts.
我們可以觀察 m4-msata iso 中的檔案,來了解一下 memdisk 的啟動方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ mkdir mnt $ sudo mount –o loop crucial–m4–msata–07mh–m7–00.iso mnt 2>/dev/null $ cd mnt $ ls –al dr–xr–xr–x 1 root root 2048 Jan 4 2013 boot –r–xr–xr–x 1 root root 15867 Jan 4 2013 legal.txt –r–xr–xr–x 1 root root 1 Mar 9 2013 revision.txt $ cd boot/isolinux $ ls –al –r–xr–xr–x 1 root root 2949120 Mar 9 2013 boot2880.img –r–xr–xr–x 1 root root 2048 Mar 9 2013 boot.cat –r–xr–xr–x 1 root root 113 Jan 4 2013 bootmsg.txt –r–xr–xr–x 1 root root 14336 Mar 9 2013 isolinux.bin –r–xr–xr–x 1 root root 210 Jan 4 2013 isolinux.cfg –r–xr–xr–x 1 root root 25340 Jan 4 2013 memdisk –r–xr–xr–x 1 root root 2439 Mar 9 2013 splash.lss |
在 mnt 底下,有 legal.txt 與 revision.txt,打開 legal.txt 後可以發現有使用到 freedos / Grub for DOS / SysLinux
1 2 3 4 5 6 7 8 9 10 11 12 |
The update tool (but not the firmware update itself) may include the following components, which may be subject to the license terms below. FreeDOS – http://www.freedos.org/ COMMAND.COM COUNTRY.SYS FDAPM.COM HDPMI32.EXE MODE.COM MORESYS.SYS GRUB for DOS – https://gna.org/projects/grub4dos/ SysLinux — http://www.syslinux.org/wiki/index.php/The_Syslinux_Project |
進入 boot/isolinux 後可以觀察 isolinux.cfg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
PROMPT 1 TIMEOUT 30 DEFAULT default DISPLAY bootMsg.txt LABEL default KERNEL memdisk append initrd=boot2880.img floppy raw LABEL alternate KERNEL memdisk append initrd=boot2880.img floppy |
我們將會以 floppy 的方式導引開機,在 memdisk 的說明中,如果以 floppy 方式開機的話,其 img 大小會在 4MB 以下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
If the disk image is less than 4,194,304 bytes (4096K, 4 MB) it is assumed to be a floppy image and MEMDISK will try to guess its geometry based on the size of the file. MEMDISK recognizes all the standard floppy sizes as well as common extended formats: 163,840 bytes (160K) c=40 h=1 s=8 5.25” SSSD 184,320 bytes (180K) c=40 h=1 s=9 5.25″ SSSD 327,680 bytes (320K) c=40 h=2 s=8 5.25” DSDD 368,640 bytes (360K) c=40 h=2 s=9 5.25″ DSDD 655,360 bytes (640K) c=80 h=2 s=8 3.5” DSDD 737,280 bytes (720K) c=80 h=2 s=9 3.5″ DSDD 1,222,800 bytes (1200K) c=80 h=2 s=15 5.25” DSHD 1,474,560 bytes (1440K) c=80 h=2 s=18 3.5″ DSHD 1,638,400 bytes (1600K) c=80 h=2 s=20 3.5” DSHD (extended) 1,720,320 bytes (1680K) c=80 h=2 s=21 3.5″ DSHD (extended) 1,763,328 bytes (1722K) c=82 h=2 s=21 3.5” DSHD (extended) 1,784,832 bytes (1743K) c=83 h=2 s=21 3.5″ DSHD (extended) 1,802,240 bytes (1760K) c=80 h=2 s=22 3.5” DSHD (extended) 1,884,160 bytes (1840K) c=80 h=2 s=23 3.5″ DSHD (extended) 1,966,080 bytes (1920K) c=80 h=2 s=24 3.5” DSHD (extended) 2,949,120 bytes (2880K) c=80 h=2 s=36 3.5″ DSED 3,194,880 bytes (3120K) c=80 h=2 s=39 3.5” DSED (extended) 3,276,800 bytes (3200K) c=80 h=2 s=40 3.5″ DSED (extended) 3,604,480 bytes (3520K) c=80 h=2 s=44 3.5” DSED (extended) 3,932,160 bytes (3840K) c=80 h=2 s=48 3.5″ DSED (extended) |
觀察 boot2880.img 的檔案大小,可以發現是 2,949,120 bytes (2880K),符合上表的 c=80 h=2 s=36 的部份。確定是以 floppy 而不是 iso 的方式導引。
4.2 ISOLinux
ISOLINUX is a boot loader for Linux/i386 that operates off ISO 9660/El Torito CD-ROMs in “no emulation” mode. This avoids the need to create an “emulation disk image” with limited space (for “floppy emulation”) or compatibility problems (for “hard disk emulation”).
估計是因為 firmware 本身大於 floppy 的 4MB (core.gz 檔案大小為 8MB),因此使用 ISOLinux 的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
$ sudo mount –o loop MX200_MU03_BOOTABLE.iso mnt 2>/dev/null $ cd mnt $ ls –al dr–xr–xr–x 3 root root 2048 Nov 17 2015 boot dr–xr–xr–x 3 root root 2048 Nov 17 2015 cde drwxr–xr–x 2 root root 2048 Nov 17 2015 scratch $ cd boot $ ls –al –r—r—r— 1 root root 3.3M Nov 17 2015 core.gz dr–xr–xr–x 2 root root 2.0K Nov 17 2015 isolinux –r—r—r— 1 root root 3.3M Nov 17 2015 vmlinuz $ cd isolinux $ ls –al –r—r—r— 1 root root 2048 Nov 17 2015 boot.cat –r—r—r— 1 root root 233 Nov 17 2015 boot.msg –r—r—r— 1 root root 233 Nov 17 2015 f1 –r—r—r— 1 root root 1003 Nov 17 2015 f2 –r—r—r— 1 root root 1186 Nov 17 2015 f3 –r—r—r— 1 root root 1065 Nov 17 2015 f4 –r—r—r— 1 root root 24576 Nov 17 2015 isolinux.bin –r—r—r— 1 root root 213 Nov 17 2015 isolinux.cfg –r—r—r— 1 root root 61312 Nov 17 2015 menu.c32 |
可以看到 mnt 目錄下少了 legal.txt 跟 revision.txt,取而代之的是 cde 與 scratch 資料夾。boot 資料夾中除了 isolinux 也多了 core.gz 與 vmlinuz (initrd ramdisk) 兩個檔案。
觀察 isolinux.cfg,可以發現多了 initrd 以及 boot params 的部份
1 2 3 4 5 6 |
DEFAULT sd LABEL sd KERNEL /boot/vmlinuz INITRD /boot/core.gz APPEND libata.allow_tpm=1 quiet base loglevel=3 waitusb=10 superuser mse–iso rssd–fw–update rssd–fwdir=/opt/firmware rssd–model=MX200 mse–poweroff |
5. 不是使用 GRUB2 的話怎麼辦?
不論是 guillaume 或是我的 script,都是採用 GRUB2 的 config file template,如果使用 syslinux 或是 legacy GRUB 的話,可能會有無法使用的狀況。請務必自行參照 Syslinux 的 wiki 來更改相關的設定。
Memdisk: http://www.syslinux.org/memdisk.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
EXTLINUX/ISOLINUX/PXELINUX/SYSLINUX # Boot Hardware Detection Tool from floppy image LABEL m4_msata_floppy LINUX memdisk INITRD boot2880.img APPEND floppy GRUB and GRUB4DOS title M4 mSata Firmware Update from floppy kernel /memdisk initrd /boot2880.img GRUB2 menuentry “Boot Hardware Detection Tool from floppy” { linux16 memdisk initrd16 boot2880.img } |
6. Clean Up
我們總共動了以下的東西:
- /etc/grub.d/45_crucial_fw
- /boot/crucial-fw
- update-grub2 / grub-mkconfig
因此軔體更新完成後,必須要手動刪除前兩個檔案 / 資料夾,並且重新產生 grub config。
Leave a Reply