Broadwell, Instability, and Microcode

September 25, 2015

This issue has since been resolved by the updated microcode packages available in nearly all Linux distributions, and by the updated UEFI packages provided by most motherboard vendors.


I recently built a new computer with the i5-5675C “Broadwell” processor. It’s one of the only two desktop Broadwell processors Intel has released. Due to issues with the 14nm process, it seems that Broadwell’s release was pushed too close to Skylake, which is already shipping.

This processor is a rather odd choice, especially since it’s currently retailing well above the MSRP, but I picked it for the Iris Pro 6200 graphics, which should be fast enough to let me avoid a discrete GPU, and all the driver-related headaches that go along with that on Linux. Unfortunately, I didn’t realize until I started trying to use it that there’s been a whole slew of Linux stability issues with it and its sibling, the i7-5775C.

Personally, I was a bit suspicious about the microcode, since the last microcode update package Intel released for Linux is from January, while Windows last got an update in June.

If you don’t know what microcode is, it’s essentially just a binary firmware update for your processor. Modern CPUs are incredibly complex, and often ship with bugs, to the point that Intel has had to recall processors. With microcode updates, the manufacturer (Intel, AMD, etc) can turn off broken functionality, or improve performance by tweaking the hardware’s configuration or how instructions are processed.

Naturally, I wanted to see if I could extract the Windows microcode and use it on Linux. I got an ISO of Windows 7 for free though my University with DreamSpark, and proceeded to install it in KVM. I then installed the June 2015 microcode update while running procmon to figure out that the update overwrites mcupdate_GenuineIntel.dll.

Armed with the source code to iucode-tool, I managed to quickly hack together a script in Python that can extract anything that looks like microcode from a binary file:

#!/usr/bin/env python2
# License: GPLv3+

import struct
import sys
import itertools

# struct intel_ucode_v1_hdr { /* 48 bytes */
#     uint32_t        hdrver; /* must be 0x1 */
#     int32_t         rev;    /* yes, it IS signed */
#     uint32_t        date;   /* packed BCD, MMDDYYYY */
#     uint32_t        sig;
#     uint32_t        cksum;
#     uint32_t        ldrver;
#     uint32_t        pf_mask;
#     uint32_t        datasize;  /* 0 means 2000 */
#     uint32_t        totalsize; /* 0 means 2048 */
#     uint32_t        reserved[3];
# } __attribute__((packed));

STRUCT_FMT = '<IiIIIIIIIIII'
STRUCT_SIZE = struct.calcsize(STRUCT_FMT)

with open(sys.argv[1], 'rb') as f:
    for i in itertools.count():
        f.seek(i)
        header_bytes = f.read(STRUCT_SIZE)
        if len(header_bytes) < STRUCT_SIZE: # EOF
            break
        hdrvr, rev, date, sig, \
            cksum, ldrvr, pf_mask, \
            datasize, totalsize, \
            reserved0, reserved1, reserved2 = \
            struct.unpack(STRUCT_FMT, header_bytes)

        # validate the header
        if hdrvr != 1:
            continue
        # newer microcode updates include a size field, whereas older containers
        # set it at 0 and are exactly 2048 bytes long
        totalsize = totalsize or 2048
        if not 1024 * 2 <= totalsize < 1024 * 50:
            # microcode updates should probably be 5KB to 50KB
            continue
        if datasize + STRUCT_SIZE > totalsize:
            continue

        body_nbytes = totalsize - len(header_bytes)
        body_bytes = f.read(body_nbytes)
        if len(body_bytes) < body_nbytes:
            continue # partial read
        with open('microcode/%s.bin' % i, 'wb') as out:
            out.write(header_bytes)
            out.write(body_bytes)

Of course, it’s possible to write a cleaner solution, or one with less false-positives than this, but when run against mcupdate_GenuineIntel.dll, I managed to extract a bunch of blobs, some of which had matching checksums with known Linux microcode updates!

While they’re undocumented, it’s known that the microcode updates are validated before being applied by the processor, and iucode-tool does a bunch of its own sanity checks, so it’s pretty safe to blindly apply all of them, including the false positives:

$ for i in *.bin; do echo $i; sudo iucode-tool $i; done

However, checking the microcode version before and after via /proc/cpuinfo, I saw no changes — still version 0x10. I might already have the latest microcode if Intel hasn’t shipped any updates for this processor, if the January microcode package already included them, or if my motherboard’s UEFI installed it for me.

At least for now, I guess I’m stuck with running with the kernel flags:

processor.max_cstate=0 intel_idle.max_cstate=0 idle=poll

and dealing with the occasional kernel panic.

Update

Since originally writing this article, I’ve improved my script slightly (updated in-place) to generate less false positives. I’ve also discovered that certain UEFI updates (but not any Windows updates) include a microcode update that does solve this problem!

I’ve published an installer for these updates on GitHub.

The views expressed on this site are my own and do not reflect those of my employer.