Firmware Extraction Series - SD Card Unlock

Thursday, October 18, 2018 🌐中文

Preface

The SD card (Secure Digital Memory Card) is a NAND flash-based storage medium designed as a successor to the MMC (Multimedia Card). It is commonly found in multimedia players, cameras, and smartphones, and has since been widely adopted in IoT devices and automotive electronics. Physically, SD cards are categorized into three sizes: SD, miniSD, and microSD.

sd_size

There are two common speed class markings: the “standard” speed class and the UHS speed class. Each corresponds to a different bus interface mode. The SD Association has also introduced the Video Speed Class, which utilizes the UHS bus. SD card capacities are also standardized, ranging from SDSC (Standard Capacity) to SDUC (Ultra Capacity).

Micro-SD-speeds bus_speed_img

SD card parameters can often be seen on the label sticker or silkscreen.

sd_sticker

microSD was originally known as T-Flash (TransFlash). SD slots are backward compatible with MMC cards. Additionally, eMMC chips can be wired to an SD slot for interfacing.

sd_emmc

This post is based on the SD Physical Layer Simplified Specification, Version 6, and focuses on the unlock mechanism.

SDIO (Secure Digital Input Output) is an extension of the SD standard derived from the SD Physical Layer specification. Beyond SDIO memory cards, the specification supports SDIO peripheral devices such as Wi-Fi modules, GPS modules, and CMOS sensors.

I/O Information

Below is a pin comparison between MMC and SD cards.

pin_compare

These pins serve different functions depending on the bus protocol and transfer mode. This post focuses primarily on the SD-mode specification. The pin types are defined as follows:

  • S - Power Supply
  • I - Input
  • O - Output using Pull Push Drivers
  • PP - I/O using Pull Push Drivers
Pin # Name Type Description
1 CD/DAT3 I/O/PP Card Detect/Data Line [Bit3]
2 CMD I/O/PP Command/Response
3 VSS1 S Supply voltage ground
4 VDD S Supply voltage
5 CLK I Clock
6 VSS2 S Supply voltage ground
7 DAT0 I/O/PP Data Line [Bit0]
8 DAT1 I/O/PP Data Line [Bit1]
9 DAT2 I/O/PP Data Line [Bit2]

Below is the SD card under analysis: an SDSC card (max 2GB), labeled “M2B9 2GB Made in Japan”. These markings yielded no search results.

SD_card_internal

The SD controller chip is marked “56X31B002 AC00145R”, which also returned no search results. However, the underlying memory chip features a Micron logo. Using the FBGA & Component Marking Decoder, I identified it as an SLC NAND Flash in a VBGA100 package. The official database seems incomplete, listing only a 16GB version.

SD_card_internal2

Registers

Registers such as OCR, CID, CSD, and SCR contain card-specific information. RCA and DSR store configuration parameters, while SSR and CSR convey status information. The specification authors seem partial to three-letter abbreviations; when three letters suffice, the “R” (for Register) is often omitted.

Name length(bit) Description Optional
CID 128 Card Identification Data, includes Manufacturer ID, OEM ID, Product Name, Product Revision, Manufacturing Date, and checksum Required
RCA 16 Relative Card Address, used for addressing; default 0x0000; not available in SPI mode Required
DSR 16 Driver Stage Register, used to improve bus performance Optional
CSD 128 Card Special Data, complex; includes operating conditions such as error types, maximum data access time, speed, DSR availability, etc. Required
SCR 64 SD Configuration Register, includes supported features and spec version Required
OCR 32 Operation Condition Register, carries current power information Required
SSR 512 SD Status Register, carries current SD card characteristics and application-specific status info such as bus width, security mode, card type, speed, etc. Required
CSR 32 Card Status Register, carries the status when executing commands (reflected in responses), such as lock state and error info Required

Architecture

The card contains a power-on detection circuit connected to both the host interface and the storage-area interface. Each pin connects to the card controller, and the controller operates on the storage area via the storage interface.

architecture

SDIO

The SDIO specification defines two types of cards: low-speed and high-speed. Three underlying bus modes are supported:

  • SPI bus mode
  • 1-bit SD bus mode: Command and data lines are strictly separated.
  • 4-bit SD bus mode: Supported by high-speed cards. Commands utilize a dedicated channel, while four other lines transfer data. This is the default configuration for SD mode.

SD Bus Protocol

SD bus communication relies primarily on the CMD (Command) and DAT (Data) lines. CMD and DAT signals are transmitted in parallel on separate channels. The command channel is bidirectional: CMD tokens are sent by the host, and Responses are returned by the SD card. A Response is only generated for valid commands.

sd_protocal

The SD protocol specifies block-based read/write operations. The data block transmission follows the command sequence. Each data block is terminated with CRC bits. An operation is concluded by sending a stop command.

sd_protocal_r

The write process includes a busy signal.

sd_protocal_w

CMD Format

The Command Token length is 48 bits. The start bit is 0. The transmission bit is 1, indicating data from the host. Content carries the command, address information, and parameters. It ends with a 7-bit CRC (Cyclic Redundancy Check), and the end bit is 0. Because of this property, the first byte of a Response is 0x40 smaller than the first byte of a CMD.

cmd_format

There are four response token scenarios. Depending on the scenario, different lengths are used: 48-bit (R1, R3, R6) and 136-bit (R2). The transmission bit is 0, indicating data from the SD card.

response_format

Data Packet Format

In normal mode, data is transmitted via DAT[0-3]. Like CMD, it has a start bit 0, an end bit 1, and CRC. The default bus width is 1-bit.

data_packet_format

CRC7

SD cards use the CRC7/MMC algorithm for CMD checking. The formula is:

crc7_calc

Convert the polynomial to binary G(x):

10001001

Append (divisor-1) bits (i.e., 7) to the data frame M(x), then perform modulo-2 division: divide the data frame by 10001001 to obtain the CRC.

crc7

CRC16

SD cards use CRC-16/CCITT-XMODEM for Data checking. In wide-bus mode, each line computes CRC independently. The formula is:

crc16_calc

Convert the polynomial to binary G(x); the 16th bit exceeds the length and is ignored:

0001000000100001

crc16

Response Type

There are five types of SD card responses; SDIO supports additional response types R4 and R5. Except for R3, response packets end with CRC.

  • R1 Normal response; may carry a busy signal (R1b)
  • R2 CID/CSD response
  • R3 OCR response
  • R6 RCA response
  • R7 Card interface condition response

Formats for different response types

The parameter field of R1 is 32 bits and corresponds to card status. Due to version iteration, the spec doesn’t explicitly call it CSR, and the CSR register was defined later. R2 directly returns CID or CSD data. R3 returns OCR data.

response_r1 response_r2 response_r3 response_r6 response_r7

Function mode

There are two operating modes for SD cards; by default the card is inactive:

  • card indentification mode
  • data transfer mode

Under UHS-II and SD mode, the card identification mode differs.

Command

Commands are mainly of two types—broadcast and addressed. More specifically, there are four categories:

  • broadcast commands (bc), no response
  • broadcast commands with response (bcr) (Note: No open drain on SD card)
  • addressed (point-to-point) commands (ac), no data transfer on DAT lines
  • addressed (point-to-point) data transfer commands (adtc), data transfer on DAT lines

CMD5 and CMD52-54 are SDIO-specific.

Most Significant Bit (MSB) is transmitted first; Least Significant Bit (LSB) is transmitted last.

command_format

Commands are grouped into classes:

Class # Name
0 Basic Commands
1 Command and Queue Function Commands
2 Block Oriented Read Commands
3 Reserved
4 Block Oriented Write Commands
5 Erase Commands
6 Block Oriented Write Protection Commands
7 Lock Card
8 Application Specific Commands
9 I/O Mode Commands
10 Switch Function Commands
11 Function Extension Commands

Class 7 contains three commands related to card locking. CMD16 sets the block length (often used before setting a password). CMD42 performs the actual lock/unlock operations. Even when locked, a card responds to Class 0 commands.

card_lock_class

For SDSC cards, SET_BLOCK_LEN can be used to specify the data block length. For SDHC and SDXC cards, the block length is fixed at 512 bytes.

Application-Specific Commands

Application-Specific Commands extend Commands. CMD55 is the condition to trigger ACMD; ACMD41 is CMD55 followed by CMD41.

ACMD41 is the initialization command and sets HCS (Host Capacity Support), determining SD card type, power control, and voltage levels. ACMD6 sets the bus width, choosing 1-bit or 4-bit data transfer. Only unlocked cards in transfer state can use ACMD6.

Status Information

SD card status information can be seen in R1-type responses, e.g., CMD13. SD cards have three kinds of status information:

  • SD Status
  • Card Status
  • Task Status

Below are the type definitions for each bit:

E - Error bit S - Status bit R - Detected and set for the actual command response X - Detected and set during command execution. The host can obtain status info from command responses

And the clear conditions for status bits:

Status bits may be cleared under the following conditions:

A - According to the current card status. B - Always related to the previous command. Cleared by receiving a new command. C - Cleared by read.

R1 responses carry the 32-bit card status information, which is derived from the CSR. The table below defines each bit (reserved bits omitted).

Bits Identifier Type Value Description Clear Condition
31 OUT_OF_RANGE E R X ‘0’= no error
‘1’= error
The command’s argument was out of the allowed range for this card. C
30 ADDRESS_ERROR E R X ‘0’= no error
‘1’= error
A misaligned address which did not match the block length was used in the command. C
29 BLOCK_LEN_ERROR E R X ‘0’= no error
‘1’= error
The transferred block length is not allowed for this card, or the number of transferred bytes does not match the block length. C
28 ERASE_SEQ_ERROR E R ‘0’= no error
‘1’= error
An error in the sequence of erasecommands occurred. C
27 ERASE_PARAM E R X ‘0’= no error
‘1’= error
An invalid selection of write-blocksfor erase occurred. C
26 WP_VIOLATION E R X ‘0’= no protected
‘1’= protected
Set when the host attempts to writeto a protected block or to the temporary or permanent write protected card. C
25 CARD_IS_LOCKED S X ‘0’= card unlocked
‘1’= card locked
When set, signals that the card is locked by the host. A
24 LOCK_UNLOCK_FAILED E R X ‘0’= no error
‘1’= error
Set when a sequence or passworderror has been detected in lock/unlock card command. C
23 COM_CRC_ERROR E R ‘0’= no error
‘1’= error
The CRC check of the previous command failed. B
22 ILLEGAL_COMMAND E R ‘0’= no error
‘1’= error
Command not legal for the cardstate B
21 CARD_ECC_FAILED E R X ‘0’= no error
‘1’= error
Card internal ECC was applied butfailed to correct the data. C
20 CC_ERROR E R X ‘0’= no error
‘1’= error
Internal card controller error. C
19 ERROR E R X ‘0’= no error
‘1’= error
A general or an unknown error occurred during the operation. C
16 CSD_OVERWRITE E R X ‘0’= no error
‘1’= error
Can be either one of the following errors:
- The read only section of the CSD does not match the card content.
- An attempt to reverse the copy (set as original) or permanent WP (unprotected) bits was made.
C
15 WP_ERASE_SKIP E R X ‘0’= no protected
‘1’= protected
Set when only partial address space was erased due to existing write protected blocks or the temporary or permanent write protected card was erased. C
14 CARD_ECC_DISABLED S X ‘0’= enabled
‘1’= disabled
The command has been executed without using the internal ECC. A
13 ERASE_RESET S R ‘0’= cleared
‘1’= set
An erase sequence was cleared before executing because an out of erase sequence command was received. C
12:9 CURRENT_STATE S X 0 = idle
1 = ready
2 = ident
3 = stby
4 = tran
5 = data
6 = rcv
7 = prg
8 = dis
9-14 = reserved
15 = reserved for
I/O mode
The state of the card when receiving the command. If the command execution causes a state change, it will be visible to the host in the response to the next command.
The four bits are interpreted as a binary coded number between 0 and 15.
B
8 READY_FOR_DATA S X ‘0’= not ready
‘1’= ready
Corresponds to buffer empty signaling on the bus. A
6 FX_EVENT S X ‘0’= No event
‘1’= Event invoked
Extension Functions may set this bit to get host to deal with events. A
5 APP_CMD S R ‘0’= enabled
‘1’= disabled
The card will expect ACMD, or an indication that the command has been interpreted as ACMD. C
3 AKE_SEQ_ERROR(SD Memory Card app. spec.) E R ‘0’= no error
‘1’= error
Error in the sequence of the authentication process. C

12:9 is 4 bits long. Converting to decimal corresponds to the card’s operating stage:

state_mode

SD Protocol Sniff

Length matching is a PCB design term, commonly used for high-speed I/O such as DDR. When using fly-wires, try to keep equal lengths and control the distance between the clock line and other signal lines—this is also an SD card vendor requirement for PCB design.

wiring

In practice, standard SD “High Speed” mode operates at 25MHz/50MHz. While length matching is less critical at these speeds than for UHS modes, signal integrity remains important. The crucial factor here is the logic analyzer’s sampling rate. My initial attempts with a 100MHz sampling rate produced garbage data; switching to an LA5016 at 500MHz resolved the issue.

LA5016

The selected logic level must match the device voltage. Configure the analyzer for SDIO and trigger on the rising edge of the clock signal. This ensures accurate logic level capture. As shown below, the data line state is 0 at the rising clock edge.

rising_edge

SD Unlock

The unlock session remains valid only for the current power cycle; the SD card will revert to a locked state upon the next power-up. The unlock sequence begins with CMD7 to select the card. If the Force Erase Password (FEP) bit is set, the COP (Card Ownership Protection) must be cleared. Next, CMD16 sets the necessary block length: 1 byte for operation mode + 1 byte for password length + the actual password length. Finally, CMD42 is issued to perform the unlock.

unlock_operations

CMD 42

CMD 42

CMD42 manages card locking and unlocking. The feature has two versions: V1.0 and V2.0. Unlocking can be performed via two methods: Force Erase Password (FEP) or standard password authentication. FEP uses a non-volatile register available only on cards supporting COP (Card Ownership Protection). COP was introduced in SD Specification 6.0, and such cards remain rare in the market.

From the CMD format, 01 indicates a CMD request, 101010 represents 42, so the first byte of CMD42 should be 01101010, i.e. 6A. The first 4 bits of CMD42’s argument should be 0. The corresponding CSR status bit-25 should change from 1 to 0, and bit-24 should be 0.

unlock_parameters

By decoding the communication between the host and the SD card with a logic analyzer, I obtained the following frame. The Command Index, Arguments, and CRC7 all match the definitions above.

6A 00 00 00 00 51

The last byte 51 can be validated via CRC7.

The target device studied in this post is relatively old and does not support COP. In the lock/unlock bit, 0 indicates unlock and 1 indicates lock. Since we use CMD42, the operation parameter is 00000000, i.e. CMD42[00h].

For the Data portion of CMD42: the first byte describes the operation. The first three bits are reserved and are typically set to 0. The fourth bit indicates the COP feature. During unlock, the remaining four bits should all be 0. The second byte is the password length field, 8 bits wide, in bytes. Therefore the maximum password length is 128 bytes. Here the password length is 16 bytes. Starting from the third byte is the password data, and finally a 16-bit CRC is appended.

password

The address in CMD7 is dynamically assigned (Random Card Address):

47 4B 47 00 00 6F
[47:41] 01000111  start bit + Command index
[40:32] 01001011  RCA 4B
[31:24] 01000111  RCA 47
[23:16] 00000000  stuff bits
[15:8]  00000000
[7:0]   01101111  CRC7 + end bit

07 02 00 07 00 79

CMD16 is shown below. The value 0x12 (decimal 18) sets the block length to 18 bytes (2 bytes for header/length + 16 bytes for the password).

50 00 00 00 12 2F
[47:41] 01010000  start bit + Command index
[40:32] 00000000
[31:24] 00000000
[23:16] 00000000
[15:8]  00010010  SET_BLOCKLEN
[7:0]   00101111  CRC7 + end bit

CMD16 response:

10 02 00 09 00 07
[47:41] 00010000  start bit + Command index
[40:32] 00000010  Card Status
[31:24] 00000000  Card Status
[23:16] 00001001  Card Status
[15:8]  00000000  Card Status
[7:0]   00000111  CRC7 + end bit

The first byte of the CMD42 response is 00101010 (0x2A), indicating an R1 response.

I captured the CMD42 response using a logic analyzer.

Decoding the Parameter field reveals the card status: Locked, Ready, Transfer Mode.

2a 02 00 09 00 6f
[47:41] 00010000  start bit + Command index
[40:32] 00000010  Card Status Locked
[31:24] 00000000  Card Status
[23:16] 00001001  Card Status
[15:8]  00000000  Card Status
[7:0]   00000111  CRC7 + end bit

Following the delivery of CMD42 data, CMD13 is sent to check status. The CSR bits now indicate: Unlocked, Ready, Transfer Mode, with APP_CMD disabled.

0D 00 00 09 00 07
[47:41] 00010000  start bit + Command index
[40:32] 00000000  Card Status Unlocked
[31:24] 00000000  Card Status
[23:16] 00001001  Card Status
[15:8]  00000000  Card Status
[7:0]   01101111  CRC7 + end bit

Data Analysis

In KingstVIS, I located the Data0 channel, aligned it to the rising edge following CMD42, selected the relevant interval, and exported the clock and Data0 channels to a TXT file.

exporting_data

The exported data is effectively CSV-formatted. Note that KingstVIS exports to CSV in reverse chronological order, so the TXT format was preferred. The encoding is ANSI, which may display incorrectly on Linux; simply removing the header line resolved this.

csv_data_exported

The script below parses the Data0 output into a readable format.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import csv
import sys
import getopt
import binascii

def parse_csv(csv_file):
    try:
        with open(csv_file, 'r') as f:
            reader = csv.reader(f)
            last_ch0_v = 0
            bits = []
            for line in reader:
                current_ch0_v = int(line[1])
                if (current_ch0_v ^ last_ch0_v) and last_ch0_v == 0:
                    bits.append(str(int(line[2])))
                last_ch0_v = current_ch0_v
            return bits

    except Exception as e:
        print(e)

def crc16_calc(data):
    crc = 0x0000
    poly = 0x1021
    for b in data:
        cur_byte = 0xFF & b
        for i in range(0, 8):
            bit = ((cur_byte >> (7-i) & 1) == 1)
            c15 = ((crc >> 15 & 1) == 1)
            crc <<= 1
            if (c15 ^ bit):
                crc ^= poly
    return crc & 0xFFFF

if __name__ == '__main__':
    if len(sys.argv) < 3:
        usage()
    try:
        options, args = getopt.getopt(sys.argv[1:], "f:o")
        csv_file = ''
        output_file = ''
        for opt, arg in options:
            if opt == '-f':
                csv_file = arg
            elif opt == '-o':
                output_file = arg

        bits = parse_csv(csv_file)

        remainder = len(bits) % 8
        bits = bits[0:len(bits) - remainder]

        keys_list = []
        crc_data = []
        keys_len = 0

        # Remove the first start bit of Data
        if bits[0] == '0':
            bits = bits[1:]
            for i in range(0, len(bits), 8):
                byte = bits[i] + bits[i+1] + bits[i+2] + bits[i+3] + \
                    bits[i+4] + bits[i+5] + bits[i+6] + bits[i+7]
                n = i/8
                if n == 0 :
                    if int(byte, 2) == 0:
                        keys_list.append(int(byte, 2))
                    else:
                        print("CMD42 data block parameters error.")
                        exit()
                elif n == 1:
                    keys_len = int(byte, 2)
                    keys_list.append(keys_len)
                    print("Keys length: " + str(keys_len) + "-bytes")
                elif n > 1 and n <= (keys_len + 1):
                    keys_list.append(int(byte, 2))
                elif (keys_len + 2) <= n <= (keys_len + 3):
                    crc_data.append(int(byte, 2))
                else:
                    if (crc_data[0]*16*16 + crc_data[1]) == crc16_calc(keys_list):
                        x = bytearray(keys_list[2:keys_len+2])
                        print("Key:", str(binascii.b2a_hex(x))[2:(keys_len+1)*2])
                        print("CRC:", hex(crc16_calc(keys_list)))
                        print("\nAnalysis completed!")
                    else:
                        print("CRC error!")
                    exit()
        else:
            print("Data Error")

    except Exception as e:
        print(e)
        usage()

def usage():
    print("Usage:python kinstvis_sdio_parser.py -f test.csv")

After removing the start bit, parsing the CMD42 data structure, and verifying the CRC, the analysis confirmed a valid password.

parse_key

Extracted password:

5ffca19ffcdb5899a82c4e265f99c76b

MMC Utils

The next step involved creating an SD unlock tool. Micron’s documentation provides a demo for adding lock/unlock functionality; I modified mmc-utils to implement this.

git clone git://git.kernel.org/pub/scm/linux/kernel/git/cjb/mmc-utils.git

mmc-utils/mmc.h

#define MMC_SET_BLOCKLEN        16 /* ac [31:0] block len R1 */
#define MMC_LOCK_UNLOCK         42 /* adtc R1b */
#define MMC_CMD42_UNLOCK        0x0 /* UNLOCK */
#define MMC_CMD42_SET_PWD       0x1 /* SET_PWD */
#define MMC_CMD42_CLR_PWD       0x2 /* CLR_PWD */
#define MMC_CMD42_LOCK          0x4 /* LOCK */
#define MMC_CMD42_SET_LOCK      0x5 /* SET_PWD & LOCK */
#define MMC_CMD42_ERASE         0x8 /* ERASE */
#define MAX_PWD_LENGTH          32 /* max PWDS_LEN: old+new */
#define MMC_BLOCK_SIZE          512 /* data blk size for cmd42 */
#define MMC_R1_ERROR            (1 << 19) /* R1 bit19 */
#define MMC_R1_LOCK_ULOCK_FAIL  (1 << 24) /* R1 bit24 */

mmc-utils/mmc.c

{do_lock_unlock, -3,
        "cmd42", "<password> <s|c|l|u|e> <device>\n"
                "s\tset password\n"
                "c\tclear password\n"
                "l\tlock\n"
                "sl\tset password and lock\n"
                "u\tunlock\n"
                "e\tforce erase\n",
        NULL},

mmc-utils/mmc_cmds.h

//lock/unlock feature implementation
int do_lock_unlock(int nargs, char **argv)
{
    int fd, ret = 0;
    char *device;
    __u8 data_block[MMC_BLOCK_SIZE] = {0};
    __u8 data_block_onebyte[1] = {0};
    int block_size = 0;
    struct mmc_ioc_cmd idata;
    int cmd42_para;               //parameter of cmd42
    char pwd[MAX_PWD_LENGTH + 1]; //password
    int pwd_len;                  //password length
    __u32 r1_response;            //R1 response token
    if (nargs != 4)
    {
        fprintf(stderr, "Usage: mmc cmd42 <password> <s|c|l|u|e> <device> \n");
        exit(1);
    }
    strcpy(pwd, argv[1]);
    pwd_len = strlen(pwd);
    if (!strcmp("s", argv[2]))
    {
        cmd42_para = MMC_CMD42_SET_PWD;
        printf("Set password: password=%s ...\n", pwd);
    }
    else if (!strcmp("c", argv[2]))
    {
        cmd42_para = MMC_CMD42_CLR_PWD;
        printf("Clear password: password=%s ...\n", pwd);
    }
    else if (!strcmp("l", argv[2]))
    {
        cmd42_para = MMC_CMD42_LOCK;
        printf("Lock the card: password=%s ...\n", pwd);
    }
    else if (!strcmp("sl", argv[2]))
    {
        cmd42_para = MMC_CMD42_SET_LOCK;
        printf("Set password and lock the card: password - %s ...\n", pwd);
    }
    else if (!strcmp("u", argv[2]))
    {
        cmd42_para = MMC_CMD42_UNLOCK;
        printf("Unlock the card: password=%s ...\n", pwd);
    }
    else if (!strcmp("e", argv[2]))
    {
        cmd42_para = MMC_CMD42_ERASE;
        printf("Force erase ... (Warning: all card data will be erased together with PWD!)\n");
    }
    else
    {
        printf("Invalid parameter:\n"
               "s\tset password\n"
               "c\tclear password\n"
               "l\tlock\n"
               "sl\tset password and lock\n"
               "u\tunlock\n"
               "e\tforce erase\n");
        exit(1);
    }
    device = argv[nargs - 1];
    fd = open(device, O_RDWR);
    if (fd < 0)
    {
        perror("open");
        exit(1);
    }
    if (cmd42_para == MMC_CMD42_ERASE)
        block_size = 2; //set blk size to 2-byte for Force Erase @DDR50 compability
    else block_size = MMC_BLOCK_SIZE;
    ret = set_block_len(fd, block_size); //set data block size prior to cmd42
        printf("Set to data block length = %d byte(s).\n", block_size);
    if (cmd42_para == MMC_CMD42_ERASE)
    {
        data_block_onebyte[0] = cmd42_para;
    }
    else
    {
        data_block[0] = cmd42_para;
        data_block[1] = pwd_len;
        memcpy((char *)(data_block + 2), pwd, pwd_len);
    }
    memset(&idata, 0, sizeof(idata));
    idata.write_flag = 1;
    idata.opcode = MMC_LOCK_UNLOCK;
    idata.arg = 0; //set all 0 for cmd42 arg
    idata.flags = MMC_RSP_R1 | MMC_CMD_AC | MMC_CMD_ADTC;
    idata.blksz = block_size;
    idata.blocks = 1;
    if (cmd42_para == MMC_CMD42_ERASE)
        mmc_ioc_cmd_set_data(idata, data_block_onebyte);
    else
        mmc_ioc_cmd_set_data(idata, data_block);
    ret = ioctl(fd, MMC_IOC_CMD, &idata); //Issue CMD42
    r1_response = idata.response[0];
    printf("cmd42 response: 0x%08x\n", r1_response);
    if (r1_response & MMC_R1_ERROR)
    { //check CMD42 error
        printf("cmd42 error! Error code: 0x%08x\n", r1_response & MMC_R1_ERROR);
        ret = -1;
    }
    if (r1_response & MMC_R1_LOCK_ULOCK_FAIL)
    {
        //check lock/unlock error
        printf("Card lock/unlock fail! Error code: 0x%08x\n", r1_response & MMC_R1_LOCK_ULOCK_FAIL);
        ret = -1;
    }
    close(fd);
    return ret;
}

//change data block length
int set_block_len(int fd, int blk_len)
{
    int ret = 0;
    struct mmc_ioc_cmd idata;
    memset(&idata, 0, sizeof(idata));
    idata.opcode = MMC_SET_BLOCKLEN;
    idata.arg = blk_len;
    idata.flags = MMC_RSP_R1 | MMC_CMD_AC;
    ret = ioctl(fd, MMC_IOC_CMD, &idata);
    return ret;
}

In practice, this is controlled via ioctl. After compilation, I successfuly locked the card, but subsequent unlock attempts failed. I had to pivot to another approach.

https://github.com/torvalds/linux/blob/master/include/uapi/linux/mmc/ioctl.h

sudo ./mmc scr read /sys/bus/mmc/devices/mmc0:aaaa/
type: 'SD'
version: SD 3.0x
bus widths: 4bit, 1bit,

sudo ./mmc cmd42 123456 s /sys/bus/mmc/devices/mmc0:aaaa/
Set password: password=123456 ...

Modification of the Kernel Module

Since existing user-space tools fell short, I proceeded to modify the kernel module directly. My development environment was Arch Linux. As the MMC driver is compiled as a module, I could modify it without rebuilding the entire kernel (unlike certain Ubuntu configurations where it may be built-in).

The relevant kernel module directories on Arch Linux are listed below. Testing was performed on an HP 840G3 laptop where the SD card reader is connected via the PCI bus. The following drivers are involved:

/lib/modules/$(uname -r)/kernel/drivers/mmc/core/mmc_core.ko.xz
/lib/modules/$(uname -r)/kernel/drivers/mmc/core/mmc_block.ko.xz
/lib/modules/$(uname -r)/kernel/drivers/mmc/host/rtsx_pci_sdmmc.ko.xz
/lib/modules/$(uname -r)/kernel/drivers/misc/cardreader/rtsx_pci.ko.xz

When building kernel modules from source, vermagic mismatch errors can occur if the kernel version or CPU feature flags do not align with the running kernel. Additionally, since kernel 3.7, Linux enforces module signing; lacking a valid signature can prevent module loading. Use modinfo to inspect the module properties.

$ modinfo mmc_core
filename:       /lib/modules/4.18.10-arch1-1-ARCH/kernel/drivers/mmc/core/mmc_core.ko.xz
license:        GPL
srcversion:     72D2DBEB18AB4B898BE5331
depends:
retpoline:      Y
intree:         Y
name:           mmc_core
vermagic:       4.18.10-arch1-1-ARCH SMP preempt mod_unload modversions
sig_id:         PKCS#7
signer:
sig_key:
sig_hashalgo:   md4
signature:      30:82:02:A5:06:09:2A:86:48:86:F7:0D:01:07:02:A0:82:02:96:30
                以下省略
parm:           use_spi_crc:bool

We can build directly in /usr/lib/modules/$(uname -r)/build/ without additional configuration. Simply copy the MMC core code to the target directory, run make, and reload the modules.

cp ./core/* /usr/lib/modules/$(uname -r)/build/drivers/mmc/core/
make modules SUBDIRS=drivers/mmc/core
  Building modules, stage 2.
  MODPOST 7 modules
rmmod rtsx_pci_sdmmc && rmmod mmc_core
insmod /usr/lib/modules/$(uname -r)/build/drivers/mmc/core/mmc_core.ko
insmod /lib/modules/$(uname -r)/kernel/drivers/mmc/host/rtsx_pci_sdmmc.ko.xz

Add Unlock Function

Add the unlock_mmc declaration in mmc_ops.h:

int unlock_mmc(struct mmc_card *card, u8* key_buf,int key_len);

Then implement it: first set the block length, then send the CMD42 ADTC command.

int unlock_mmc(struct mmc_card *card, u8* key_buf,int key_len)
{
    int err;
    int block_size = key_len + 2;
    struct mmc_request mrq;
    struct mmc_command cmd_sbl;
    struct mmc_command cmd;
    struct mmc_data data;
    struct scatterlist sg;
    u8 *data_buf = NULL;

    /*------------CMD 16----------------*/
    // 1 byteflag + 1 byte password length + 16 bytes password
    memset(&cmd_sbl, 0, sizeof(struct mmc_command));

    cmd_sbl.opcode = MMC_SET_BLOCKLEN;
    cmd_sbl.arg = block_size;
    cmd_sbl.flags = MMC_RSP_R1 | MMC_CMD_AC;
    err = mmc_wait_for_cmd(card->host, &cmd_sbl, MMC_CMD_RETRIES);
    if (err)
    {
        printk("%s failed block_size=%d \n",__func__,block_size);
        goto out;
    }

    /*-----------CMD 42-----------------*/
    // CMD
    memset(&cmd, 0, sizeof(struct mmc_command));
    cmd.opcode = MMC_LOCK_UNLOCK; // CMD 42
    cmd.arg = 0;   // set all 0 for cmd42 arg
    cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;

    // Data
    memset(&data, 0, sizeof(struct mmc_data));
    data.timeout_ns = (2*1000*1000*1000);
    data.blksz = block_size;
    data.blocks = 1;
    data.flags = MMC_DATA_WRITE;
    data.sg = &sg;
    data.sg_len = 1;
    mmc_set_data_timeout(&data, card);

    memset(&mrq, 0, sizeof(struct mmc_request));
    mrq.cmd = &cmd;
    mrq.data = &data;

    // Set Data for DMA
    data_buf = kzalloc(block_size, GFP_KERNEL);
    if (!data_buf)
    {
        printk("%s kzalloc failed\n",__func__);
        return -ENOMEM;
    }
    memset(data_buf, 0, block_size);
    data_buf[0] = 0;
    data_buf[1] = key_len;
    memcpy(data_buf + 2, key_buf, key_len);
    sg_init_one(&sg, data_buf, block_size);

    // request
    mmc_wait_for_req(card->host, &mrq);

    err = cmd.error;
    if (err)
        printk("%s: unlock cmd error %d\n", __func__, cmd.error);
    else
        err = data.error;

    if(err)
        goto out;
    printk("[SDLOCK] %s MMC_LOCK_UNLOCK \r\n",__func__);
out:
    kfree(data_buf);
    return err;
}

Before mmc_sd_setup_card in sd.c, add the unlock logic.

// Check if locked
u32 status = 0;
err = mmc_send_status(card, &status);
if (err)
    goto free_card;
if (status & R1_CARD_IS_LOCKED)
{
    mmc_card_set_encrypted(card);
    mmc_card_set_locked(card);
}

// For debugging convenience
bool auto_unlock = true;
char unlock_pwd[16] = {0x5f,0xfc,0xa1,0x9f,0xfc,0xdb,0x58,0x99,0xa8,0x2c,0x4e,0x26,0x5f,0x99,0xc7,0x6b};

if (status & R1_CARD_IS_LOCKED) {
    if(auto_unlock)
    {
        //unlock sd card
        err = unlock_mmc(card, unlock_pwd, 16);
        if(err)
        {
            printk("[SDLOCK] %s unlock failed \n",__func__);
        }
        else
        {
            printk("[SDLOCK] %s unlock success \n",__func__);

            if(!mmc_card_locked(card))
            {
                auto_unlock = false;
                printk("[SDLOCK] %s unlock success and sdcard status is unlocked.\n",__func__);
            }
            else
            {
                printk("[SDLOCK] %s unlock success but sdcard status is locked, abnormal status.\n",__func__);
            }

        }
        //Check if card is locked
        err = mmc_send_status(card, &status);
        if (err)
        {
            printk("[SDLOCK] %s resume sd card exception /n",__func__);
            goto free_card;
        }
    }

    if (status & R1_CARD_IS_LOCKED)
    {
        printk(KERN_WARNING "[SDLOCK] sdcard is locked\n");
        goto done;
    }
    else
    {
        printk(KERN_WARNING "[SDLOCK] sdcard resume to unlocked\n");
    }
}
else
{
    printk(KERN_WARNING "[SDLOCK] sdcard is unlocked\n");
}

Next, define the necessary macros in card.h:

#define mmc_card_mmc(c)     ((c)->type == MMC_TYPE_MMC)
#define mmc_card_sd(c)      ((c)->type == MMC_TYPE_SD)
#define mmc_card_sdio(c)    ((c)->type == MMC_TYPE_SDIO)
#define mmc_card_locked(c)  ((c)->state & MMC_STATE_LOCKED)
#define mmc_card_encrypt(c) ((c)->state & MMC_STATE_ENCRYPT)

#define MMC_STATE_PRESENT        (1<<0)       /* present in sysfs */
#define MMC_STATE_READONLY       (1<<1)       /* card is read-only */
#define MMC_STATE_BLOCKADDR      (1<<2)       /* card uses block-addressing */
#define MMC_CARD_SDXC            (1<<3)       /* card is SDXC */
#define MMC_CARD_REMOVED         (1<<4)       /* card has been removed */
#define MMC_STATE_DOING_BKOPS    (1<<5)       /* card is doing BKOPS */
#define MMC_STATE_LOCKED         (1<<12)      /* card is currently locked */
#define MMC_STATE_ENCRYPT        (1<<13)      /* card is currently encrypt */
#define MMC_STATE_ULTRAHIGHSPEED (1<<14)      /* card is in ultra high speed mode */

#define MMC_STATE_SUSPENDED      (1<<6)       /* card is suspended */
#define MMC_STATE_CMDQ           (1<<7)       /* card is in cmd queue mode */

#define MMC_LOCK_MODE_ERASE      (1<<3)
#define MMC_LOCK_MODE_LOCK       (1<<2)
#define MMC_LOCK_MODE_CLR_PWD    (1<<1)
#define MMC_LOCK_MODE_SET_PWD    (1<<0)
#define MMC_LOCK_MODE_UNLOCK     0

To debug, I added instrumentation to mmc_wait_for_req_done within core.c and monitored the logs using dmesg:

printk("[mmc] CMD %d err number: %d", mrq->cmd->opcode, mrq->cmd->error);

UHS-I

UHS-I (Ultra High Speed Phase I) is a bus interface designed for high-speed data transfer on SDHC and SDXC cards. It supports Low Voltage Signaling (LVS) and offers seven operating modes:

  • DS (Default Speed): Up to 25MHz, 3.3V signaling
  • HS (High Speed): Up to 50MHz, 3.3V signaling
  • SDR12: SDR up to 25MHz, 1.8V signaling
  • SDR25: SDR up to 50MHz, 1.8V signaling
  • SDR50: SDR up to 100MHz, 1.8V signaling
  • SDR104: SDR up to 208MHz, 1.8V signaling
  • DDR50: DDR up to 50MHz, 1.8V signaling

Initially, CMD0 is used to select the bus mode (SPI or SD mode). Note that 1.8V signaling is only available in SD mode.

uhs_command_sequence

As shown in the logs below, the unlock attempt failed; CMD42 returned error -22.

$ dmesg -l 0,1,2,3,4,5,6,7
[62696.072102] [mmc] CMD 52 err number: -110
[62696.175620] [mmc] CMD 52 err number: -110
[62696.177592] [mmc] CMD 0 err number: 0
[62696.181590] [mmc] CMD 8 err number: 0
[62696.282033] [mmc] CMD 5 err number: -110
[62696.385534] [mmc] CMD 5 err number: -110
[62696.492060] [mmc] CMD 5 err number: -110
[62696.595641] [mmc] CMD 5 err number: -110
[62696.596514] [mmc] CMD 55 err number: 0
[62696.597226] [mmc] CMD 41 err number: 0
[62696.626822] [mmc] CMD 0 err number: 0
[62696.630372] [mmc] CMD 8 err number: 0
[62696.631051] [mmc] CMD 55 err number: 0
[62696.631758] [mmc] CMD 41 err number: 0
[62696.642862] [mmc] CMD 55 err number: 0
[62696.643590] [mmc] CMD 41 err number: 0
[62696.656036] [mmc] CMD 55 err number: 0
[62696.656752] [mmc] CMD 41 err number: 0
[62696.669827] [mmc] CMD 55 err number: 0
[62696.670711] [mmc] CMD 41 err number: 0
[62696.683086] [mmc] CMD 55 err number: 0
[62696.683976] [mmc] CMD 41 err number: 0
[62696.685084] [mmc] CMD 2 err number: 0
[62696.685781] [mmc] CMD 3 err number: 0
[62696.686493] [mmc] CMD 13 err number: 0
[62696.687827] [mmc] CMD 9 err number: 0
[62696.688551] [mmc] CMD 7 err number: 0
[62696.689227] [mmc] CMD 16 err number: 0
[62696.690033] [mmc] CMD 42 err number: -22
[62696.690041] unlock_mmc: unlock cmd error -22
[62696.690045] [SDLOCK] mmc_sd_init_card unlock failed
[62696.690749] [mmc] CMD 13 err number: 0
[62696.690755] [SDLOCK] sdcard is locked
[62696.690773] mmc0: new SD card at address f317
[62696.691709] mmcblk0: mmc0:f317 MF02B 1.88 GiB

Linux error -22 corresponds to EINVAL (Invalid Argument). I traced the error propagation through the driver stack, eventually reaching drivers/mmc/host/sdhci.c:

filename: drivers/mmc/core/core.c
functions:
mmc_wait_for_req -> __mmc_start_request -> host->ops->request


filename: drivers/mmc/host/rtsx_pci_sdmmc.c
functions:
sdmmc_request -> schedule_work -> sd_request -> sd_send_cmd_get_rsp -> sd_normal_rw -> sd_write_data -> rtsx_pci_write_ppbuf -> rtsx_pci_send_cmd


filename: drivers/misc/cardreader/rtsx_pcr.c
rtsx_pci_write_ppbuf
rtsx_pci_add_cmd
rtsx_pci_send_cmd

Since I am testing on a laptop with a Realtek SD card reader, the relevant code is in the Realtek PCI driver (rtsx_pci). On embedded devices or phones, this might correspond to drivers/mfd/rtsx_pcr.c.

int rtsx_pci_send_cmd(struct rtsx_pcr *pcr, int timeout)
{
    ...
    if (pcr->trans_result == TRANS_RESULT_FAIL)
        err = -EINVAL;
    ...
}
cp ./drivers/misc/cardreader/* /usr/lib/modules/$(uname -r)/build/drivers/misc/cardreader/
make modules SUBDIRS=drivers/misc/cardreader

By inspecting the parameters passed to rtsx_pci_add_cmd, I verified that the key was being transmitted correctly.

sd_cmd_set_sd_cmd

[75754.016095] [rtsx] cmd_type:1, reg_addr:fda9, ptr:0x50, val:2108292944 SD_CMD0 16
[75754.016100] [rtsx] cmd_type:1, reg_addr:fdaa, ptr:0x0, val:2108358400  SD_CMD1
[75754.016104] [rtsx] cmd_type:1, reg_addr:fdab, ptr:0x0, val:2108423936  SD_CMD2
[75754.016111] [rtsx] cmd_type:1, reg_addr:fdac, ptr:0x0, val:2108489472  SD_CMD3
[75754.016115] [rtsx] cmd_type:1, reg_addr:fdad, ptr:0x12, val:2108555026 SD_CMD4 0x12

sd_send_cmd_get_rsp

[75754.016119] [rtsx] cmd_type:1, reg_addr:fda1, ptr:0x1, val:2107768577  WRITE_REG_CMD SD_CFG2
[75754.016122] [rtsx] cmd_type:1, reg_addr:fd5b, ptr:0x1, val:2103116033  CARD_DATA_SOURCE
[75754.016126] [rtsx] cmd_type:1, reg_addr:fdb3, ptr:0x88, val:2108948360  WRITE_REG_CMD SD_TRANSFER
[75754.016130] [rtsx] cmd_type:2, reg_addr:fdb3, ptr:0x60, val:-1112317856 CHECK_REG_CMD SD_TRANSFER

[75754.016134] [rtsx] cmd_type:0, reg_addr:fda9, ptr:0x0, val:1034485760  sd_cmd_set_sd_cmd 0
[75754.016138] [rtsx] cmd_type:0, reg_addr:fdaa, ptr:0x0, val:1034551296  SD_CMD1
[75754.016142] [rtsx] cmd_type:0, reg_addr:fdab, ptr:0x0, val:1034616832  SD_CMD1
[75754.016146] [rtsx] cmd_type:0, reg_addr:fdac, ptr:0x0, val:1034682368  SD_CMD2
[75754.016150] [rtsx] cmd_type:0, reg_addr:fdad, ptr:0x0, val:1034747904  SD_CMD3
[75754.016153] [rtsx] cmd_type:0, reg_addr:fda3, ptr:0x0, val:1034092544  SD_STAT1


sd_cmd_set_sd_cmd

[75754.016787] [SDLOCK] unlock_mmc MMC_SET_BLOCKLEN 18
[75754.016820] [rtsx] cmd_type:1, reg_addr:fda9, ptr:0x6a, val:2108292970 SD_CMD0 42
[75754.016823] [rtsx] cmd_type:1, reg_addr:fdaa, ptr:0x0, val:2108358400  SD_CMD1
[75754.016826] [rtsx] cmd_type:1, reg_addr:fdab, ptr:0x0, val:2108423936  SD_CMD2
[75754.016829] [rtsx] cmd_type:1, reg_addr:fdac, ptr:0x0, val:2108489472  SD_CMD3
[75754.016831] [rtsx] cmd_type:1, reg_addr:fdad, ptr:0x0, val:2108555008  SD_CMD4

[75754.016834] [rtsx] cmd_type:1, reg_addr:fda1, ptr:0x1, val:2107768577  WRITE_REG_CMD SD_CFG2
[75754.016837] [rtsx] cmd_type:1, reg_addr:fd5b, ptr:0x1, val:2103116033  CARD_DATA_SOURCE
[75754.016839] [rtsx] cmd_type:1, reg_addr:fdb3, ptr:0x88, val:2108948360 WRITE_REG_CMD SD_TRANSFER
[75754.016842] [rtsx] cmd_type:2, reg_addr:fdb3, ptr:0x60, val:-1112317856 CHECK_REG_CMD SD_TRANSFER

[75754.016845] [rtsx] cmd_type:0, reg_addr:fda9, ptr:0x0, val:1034485760  sd_cmd_set_sd_cmd 0
[75754.016848] [rtsx] cmd_type:0, reg_addr:fdaa, ptr:0x0, val:1034551296  SD_CMD1
[75754.016851] [rtsx] cmd_type:0, reg_addr:fdab, ptr:0x0, val:1034616832  SD_CMD2
[75754.016854] [rtsx] cmd_type:0, reg_addr:fdac, ptr:0x0, val:1034682368  SD_CMD3
[75754.016856] [rtsx] cmd_type:0, reg_addr:fdad, ptr:0x0, val:1034747904  SD_CMD4
[75754.016862] [rtsx] cmd_type:0, reg_addr:fda3, ptr:0x0, val:1034092544  SD_STAT1

write data

[75754.017492] [rtsx] cmd_type:1, reg_addr:fa00, ptr:0x0, val:2046885632
[75754.017496] [rtsx] cmd_type:1, reg_addr:fa01, ptr:0x10, val:2046951184
[75754.017499] [rtsx] cmd_type:1, reg_addr:fa02, ptr:0x5f, val:2047016799
[75754.017502] [rtsx] cmd_type:1, reg_addr:fa03, ptr:0xfc, val:2047082492
[75754.017505] [rtsx] cmd_type:1, reg_addr:fa04, ptr:0xa1, val:2047147937
[75754.017508] [rtsx] cmd_type:1, reg_addr:fa05, ptr:0x9f, val:2047213471
[75754.017511] [rtsx] cmd_type:1, reg_addr:fa06, ptr:0xfc, val:2047279100
[75754.017513] [rtsx] cmd_type:1, reg_addr:fa07, ptr:0xdb, val:2047344573
[75754.017518] [rtsx] cmd_type:1, reg_addr:fa08, ptr:0x58, val:2047410008
[75754.017521] [rtsx] cmd_type:1, reg_addr:fa09, ptr:0x99, val:2047475609
[75754.017524] [rtsx] cmd_type:1, reg_addr:fa0a, ptr:0xa8, val:2047541160
[75754.017526] [rtsx] cmd_type:1, reg_addr:fa0b, ptr:0x2c, val:2047606572
[75754.017529] [rtsx] cmd_type:1, reg_addr:fa0c, ptr:0x4e, val:2047672142
[75754.017531] [rtsx] cmd_type:1, reg_addr:fa0d, ptr:0x26, val:2047737638
[75754.017534] [rtsx] cmd_type:1, reg_addr:fa0e, ptr:0x5f, val:2047803231
[75754.017537] [rtsx] cmd_type:1, reg_addr:fa0f, ptr:0x99, val:2047868825
[75754.017539] [rtsx] cmd_type:1, reg_addr:fa10, ptr:0xc7, val:2047934407
[75754.017542] [rtsx] cmd_type:1, reg_addr:fa11, ptr:0x6b, val:2047999851

sd_cmd_set_block_len

[75754.017569] [rtsx] cmd_type:1, reg_addr:fdb1, ptr:0x1, val:2108817153  SD_BLOCK_CNT_L 1
[75754.017572] [rtsx] cmd_type:1, reg_addr:fdb2, ptr:0x0, val:2108882688  SD_BLOCK_CNT_H
[75754.017575] [rtsx] cmd_type:1, reg_addr:fdaf, ptr:0x12, val:2108686098 SD_BYTE_CNT_L  18
[75754.017577] [rtsx] cmd_type:1, reg_addr:fdb0, ptr:0x0, val:2108751616  SD_BYTE_CNT_H

[75754.017580] [rtsx] cmd_type:1, reg_addr:fda1, ptr:0x0, val:2107768576  WRITE_REG_CMD SD_CFG2
[75754.017583] [rtsx] cmd_type:1, reg_addr:fdb3, ptr:0x81, val:2108948353 WRITE_REG_CMD SD_TRANSFER
[75754.017585] [rtsx] cmd_type:2, reg_addr:fdb3, ptr:0x40, val:-1112326080 CHECK_REG_CMD SD_TRANSFER

Unlocking SD Card via Raspberry Pi

Since the Realtek driver proved problematic, I switched to using a Raspberry Pi.

sudo dd bs=4M if=/home/cygnus/IMG/2018-06-27-raspbian-stretch-lite/2018-06-27-raspbian-stretch-lite.img of=/dev/mmcblk0 status=progress conv=fsync

I downloaded the appropriate Raspberry Pi kernel source and added the cross-compilation toolchain to my PATH:

git clone --depth=1 --branch rpi-4.14.y https://github.com/raspberrypi/linux
export PATH=$PATH:/home/cygnus/git/rapi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin

I used the BCM2709 configuration. In menuconfig, I configured the SD card and MMC drivers to be built as modules. Finally, I built the zImage, modules, and device tree blobs (dtbs), and updated the files on the boot partition.

  • kernel.img: RPi 1B, 1A, A+, B+, 2B (v1), Zero, Zero W, CM1
  • kernel7.img: RPi 2B (v2), 3B, CM3, CM3L
KERNEL=kernel7
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16 zImage modules dtbs
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/run/media/cygnus/rootfs modules_install

Update kernel:

sudo cp /run/media/cygnus/boot/$KERNEL.img /run/media/cygnus/boot/$KERNEL-backup.img
sudo cp arch/arm/boot/zImage /run/media/cygnus/boot/$KERNEL.img

Update device trees:

sudo cp arch/arm/boot/dts/*.dtb /run/media/cygnus/boot/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /run/media/cygnus/boot/overlays/

Add the following to the boot config:

dtoverlay=sdio,poll_once=off

Wire the SD card to the Raspberry Pi GPIO pins as follows:

Name SD Card Raspberry Pi Pin Num
VCC 4 17
GND 6 20
CLK/SCLK 5 15
CMD/MOSI 2 16
DAT0/MISO 7 18
DAT1 8 22
DAT2 9 37
DAT3/CS 1 13

Reference

SD Simplified Specifications

Wiki - Secure_Digital

SD Standard Overview

Firmware Extraction SeriesSD cardSD specificationSDIO

TinyScheme File I/O

Huawei E5885L 4G Router Tinkering Notes