Skip to content

Instantly share code, notes, and snippets.

@mefistotelis
Last active June 7, 2022 11:59
Show Gist options
  • Save mefistotelis/ee88b8c5a213e78f6f0467573d28b970 to your computer and use it in GitHub Desktop.
Save mefistotelis/ee88b8c5a213e78f6f0467573d28b970 to your computer and use it in GitHub Desktop.

Dji Mavic Pro Flight Controller firmware vs GPL code

What is this

This file documents GPL violation by SZ DJI Technology Co., Ltd.. On 2016-09-28, the company released a new product - Mavic Pro. This drone uses Atmel ATSAME70Q21 micro-controller as the Flight Control unit. It looks like some shortcuts were taken while porting the FC code to the new MCU. Low-level driver code for MMC and SD memory was copied from U-boot, which is an amazing bootloader based on GNU General Public License. Here you will find proof of the violation, and the means to reproduce the findings.

Functions comparison

Below, specific functions will be presented. The right column will show GPL-licensed U-boot code, while left column will show pseudo-C code generated by IDA Pro Advanced. IDA is an expensive tool, but currently unmatched in functionality. While comparison on assembly level would show the violation more directly, comparing pseudo-C equivalent is more accessible - more people can read C.

Due to the nature of compilation process, and linker optimizations in particular, the code cannot be always decompiled to the exact same source it was built from. Such differences will be commented. The functions will come in the same order they apear in the violating binary.

Dji firmware binaries come without symbols. Functions, global and local variables, structs and struct members were named manually in IDA project. Names were used mostly from U-boot, though a few functions had their name in message strings stored in the binary.

For the first function, IDA prepared shorter equivalent than the original source. We can see the traversal through several structs. After giving proper names to these structs, the call is identical. Also, offsets within the structs are the same - see Structs comparison chapter for details.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_send_cmd(struct mmc *mmc,
		struct mmc_cmd *cmd, struct mmc_data *data)
{
  return mmc->cfg->ops->send_cmd(mmc, cmd, data);
}
int mmc_send_cmd(struct mmc *mmc,
		struct mmc_cmd *cmd, struct mmc_data *data)
{
	int ret;
	ret = mmc->cfg->ops->send_cmd(mmc, cmd, data);
	return ret;
}

The below function introduces use of constants. These were not properly defined in IDA project as this is time consuming. But all the integer values from wm220_0306 match their equivalent in u-boot sorce; in case of commands sent to hardware they actually have to match - after all, they are both driving MMC card.

The code by DJI introduced slight modifications to u-boot function - instead of udelay(), getting current time and time comparison is used when waiting for timeout. IDA also reconstructed some conditionals in reverted form, and was unable to find a way of presenting source without goto - this is typical for code re-created from assembly. But the general structure of the function is identical, and text messages from u-boot are left unchanged in wm220_0306 code.

WM220 is a code name for Mavic Pro aircraft.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_send_status(struct mmc *mmc, int timeout)
{
	signed int retries; // r4
	struct mmc *lmmc; // r8
	int v4, v7, v8, err;
	struct mmc_cmd cmd; // [sp+0h] [bp-38h]
​
	retries = 5;
	lmmc = mmc;
	v4 = ms_to_time(1000, timeout);
	v7 = v4 + get_time();
	cmd.cmdidx = 13;
	cmd.resp_type = 21;
	v8 = (unsigned __int16)lmmc->rca << 16;
	cmd.cmdarg = (unsigned __int16)lmmc->rca << 16;
	while ( 1 )
	{
		if ( get_time() - v7 >= 0 )
		{
			printf_s(7, "Timeout waiting card ready\n");
			return -19;
		}
		err = mmc_send_cmd(lmmc, &cmd, 0);
		if ( err )
		{
			if ( --retries < 0 )
				return err;
			goto LABEL_4;
		}
		if ( cmd.response[0] & 0x100 &&
		   ((cmd.response[0] >> 9) & 0xF) != 7 )
			break;
		if ( cmd.response[0] & 0xFDF94080 )
		{
			printf_s(7, "Status Error: 0x%08X\n",
				cmd.response[0]);
			return -18;
		}
LABEL_4:
		v8 = usleep(20);
	}
	if ( !(cmd.response[0] & 0x80) )
		return 0;
	printf_s(7, "Status MMC_STATUS_SWITCH_ERROR: 0x%08X\n",
		cmd.response[0]);
	return -20;
}
int mmc_send_status(struct mmc *mmc, int timeout)
{
	struct mmc_cmd cmd;
	int err, retries = 5;
​
	cmd.cmdidx = MMC_CMD_SEND_STATUS;
	cmd.resp_type = MMC_RSP_R1;
	if (!mmc_host_is_spi(mmc))
		cmd.cmdarg = mmc->rca << 16;
​
	do {
		err = mmc_send_cmd(mmc, &cmd, NULL);
		if (!err) {
			if ((cmd.response[0] & MMC_STATUS_RDY_FOR_DATA) &&
			    (cmd.response[0] & MMC_STATUS_CURR_STATE) !=
			     MMC_STATE_PRG)
				break;
			else if (cmd.response[0] & MMC_STATUS_MASK) {
				printf("Status Error: 0x%08X\n",
					cmd.response[0]);
				return COMM_ERR;
			}
		} else if (--retries < 0)
			return err;
​
		udelay(1000);
​
	} while (timeout--);
​
	if (timeout <= 0) {
		printf("Timeout waiting card ready\n");
		return TIMEOUT;
	}
	if (cmd.response[0] & MMC_STATUS_SWITCH_ERROR)
		return SWITCH_ERR;
​
	return 0;
}

The next one is identical for both sources, reversed one only has some integer defined inserted directly:

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_set_blocklen(struct mmc *mmc, int len)
{
	struct mmc_cmd cmd; // [sp+0h] [bp-20h]
​
	if ( mmc->ddr_mode )
		return 0;
	cmd.cmdidx = 16;
	cmd.resp_type = 21;
	cmd.cmdarg = len;
	return mmc_send_cmd(mmc, &cmd, 0);
}
int mmc_set_blocklen(struct mmc *mmc, int len)
{
	struct mmc_cmd cmd;
​
	if (mmc->ddr_mode)
		return 0;
​
	cmd.cmdidx = MMC_CMD_SET_BLOCKLEN;
	cmd.resp_type = MMC_RSP_R1;
	cmd.cmdarg = len;
​
	return mmc_send_cmd(mmc, &cmd, NULL);
}

Now we have one where wm220_0306 implementation is clearly different, even if function declaration is identical. It is just a simplification though - instead of linked list, MMC devices in wm220_0306 are stored as static array. The struct mmc is copied from u-boot - see Structs comparison chapter for details.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
struct mmc *__fastcall find_mmc_device(signed int dev_num)
{
	struct mmc *m; // r0
​
	if ( dev_num < 2 )
		m = mmc_devs_ptr[dev_num];
	else
		m = 0;
	return m;
}
struct mmc *find_mmc_device(int dev_num)
{
	struct mmc *m;
	struct list_head *entry;
​
	list_for_each(entry, &mmc_devices) {
		m = list_entry(entry, struct mmc, link);
​
		if (m->block_dev.dev == dev_num)
			return m;
	}
​
	return NULL;
}

Next one does the same things in the same way, text messages also match. One of printf() calls reveals that function name was identical in wm220_0306 as well - in original source, compiler macro __func__ was there. But there is a difference in how retry is handled - the wm220_0306 version has no retry implemented on this level.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_read_blocks(struct mmc *mmc,
		void *dst, uint32_t start, uint32_t blkcnt)
{
	unsigned int lblkcnt; // r4
	struct mmc *lmmc; // r5
	uint16_t v6; // r0
	int ret; // r6
	struct mmc_cmd cmd; // [sp+0h] [bp-48h]
	struct mmc_data data; // [sp+1Ch] [bp-2Ch]
​
	lblkcnt = blkcnt;
	lmmc = mmc;
	if ( blkcnt <= 1 )
		v6 = 17;
	else
		v6 = 18;
	cmd.cmdidx = v6;
	if ( lmmc->high_capacity )
		cmd.cmdarg = start;
	else
		cmd.cmdarg = lmmc->read_bl_len * start;
	data.blocksize = blkcnt;
	data.src_dest = (char *)dst;
	cmd.resp_type = 21;
	data.flags = lmmc->read_bl_len;
	data.blocks = 1;
	ret = mmc_send_cmd(lmmc, &cmd, &data);
	if ( ret )
		printf_s(7, "%s, mmc cmd failed \r\n",
			"mmc_read_blocks");
	if ( lblkcnt > 1 )
	{
		cmd.cmdidx = 12;
		cmd.cmdarg = 0;
		cmd.resp_type = 29;
		if ( mmc_send_cmd(lmmc, &cmd, 0) )
		{
			printf_s(7, "mmc fail to send stop cmd\n");
			return 0;
		}
	}
	if ( ret )
		return 0;
	return lblkcnt;
}
static int mmc_read_blocks(struct mmc *mmc,
		void *dst, lbaint_t start, lbaint_t blkcnt)
{
	struct mmc_cmd cmd;
	struct mmc_data data;
	int ret = 0, err = 0, err_flag = 0, retries = 0;
​
__RETRY:
	if (blkcnt > 1)
		cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK;
	else
		cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK;
​
	if (mmc->high_capacity)
		cmd.cmdarg = start;
	else
		cmd.cmdarg = start * mmc->read_bl_len;
​
	cmd.resp_type = MMC_RSP_R1;
​
	data.dest = dst;
	data.blocks = blkcnt;
	data.blocksize = mmc->read_bl_len;
	data.flags = MMC_DATA_READ;
​
	ret = mmc_send_cmd(mmc, &cmd, &data);
	if (blkcnt > 1) {
		cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
		cmd.cmdarg = 0;
		cmd.resp_type = MMC_RSP_R1b;
		err = mmc_send_cmd(mmc, &cmd, NULL);
		if (err) {
			printf("mmc fail to send stop cmd\n");
		}
	}
	if (ret || err) {
		if (err_flag == 0) {
			err_flag = 1;
			retries = 5;
		}
		if (retries) {
			printf("retry read, count: %d\n", retries);
			retries--;
			goto __RETRY;
		}
		printf("retry read error !!!\n");
		return 0;
	}
	return blkcnt;
}

Another one, almost identical, with only one error condition check removed. If you're already tired by comparing the columns, focus on the timeout variable instead. Yes, it is uninitialized in version from wm220_0306. Though in assembly, it is stored in register R1, and that register stores pointer to a variabe on stack. So not an issue - SP is high enough for the timeout to never happen.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
uint32_t __cdecl mmc_write_blocks(struct mmc *mmc,
		uint32_t start, uint32_t blkcnt, const void *src)
{
	uint32_t lblkcnt; // r4
	struct mmc *lmmc; // r6
	uint16_t v6; // r0
	int v7, timeout, ret;
	struct mmc_cmd cmd; // [sp+0h] [bp-48h]
	struct mmc_data data; // [sp+1Ch] [bp-2Ch]
​
	lblkcnt = blkcnt;
	lmmc = mmc;
	if (!blkcnt)
		return 0;
​
	if ( blkcnt == 1 )
		v6 = 24;
	else
		v6 = 25;
	cmd.cmdidx = v6;
​
	if ( lmmc->high_capacity )
		cmd.cmdarg = start;
	else
		cmd.cmdarg = lmmc->write_bl_len * start;
​
	data.blocksize = blkcnt;
	data.field_4 = (int)src;
	cmd.resp_type = 21;
	data.flags = lmmc->write_bl_len;
	data.blocks = 2;
​
	v7 = mmc_send_cmd(lmmc, &cmd, &data);
	ret = v7;
	if ( v7 )
		printf_s(7, "%s, mmc cmd failed, ret:%d\r\n",
			"mmc_write_blocks", v7);
​
	if ( lblkcnt > 1) {
		cmd.cmdidx = 12;
		cmd.cmdarg = 0;
		cmd.resp_type = 29;
		if (mmc_send_cmd(lmmc, &cmd, 0)) {
			printf_s(7, "mmc fail to send stop cmd\n");
			return 0;
		}
	}
​
	if (mmc_send_status(lmmc, timeout) || ret)
		return 0;
	return lblkcnt;
}
static ulong mmc_write_blocks(struct mmc *mmc,
		lbaint_t start, lbaint_t blkcnt, const void *src)
{
	struct mmc_cmd cmd;
	struct mmc_data data;
	int timeout = 1000;
	int ret;
​
	if ((start + blkcnt) > mmc->block_dev.lba) {
		printf("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n",
				 start + blkcnt, mmc->block_dev.lba);
		return 0;
	}
​
	if (blkcnt == 0)
		return 0;
	else if (blkcnt == 1)
		cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK;
	else
		cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK;
​
	if (mmc->high_capacity)
		cmd.cmdarg = start;
	else
		cmd.cmdarg = start * mmc->write_bl_len;
​
	cmd.resp_type = MMC_RSP_R1;
	data.src = src;
	data.blocks = blkcnt;
	data.blocksize = mmc->write_bl_len;
	data.flags = MMC_DATA_WRITE;
​
	ret = mmc_send_cmd(mmc, &cmd, &data);
	if (ret)
		printf("mmc write failed\n");
	/* SPI multiblock writes terminate using a special
	 * token, not a STOP_TRANSMISSION request
	 */
	if (!mmc_host_is_spi(mmc) && blkcnt > 1) {
		cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION;
		cmd.cmdarg = 0;
		cmd.resp_type = MMC_RSP_R1b;
		if (mmc_send_cmd(mmc, &cmd, NULL)) {
			printf("mmc fail to send stop cmd\n");
			return 0;
		}
	}
	if (ret)
		return 0;
​
	/* Waiting for the ready status */
	if (mmc_send_status(mmc, timeout))
		return 0;
	return blkcnt;
}

Next one is easier. IDA generated reverse condition at the end, but everything matches. Since there is no udelay() implemented for Atmel, the function was defined as usleep(). Most developers don't know the difference anyway.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_go_idle(struct mmc *mmc)
{
	struct mmc *lmmc; // r4
	int err; // r0
	struct mmc_cmd cmd; // [sp+0h] [bp-28h]
​
	lmmc = mmc;
	usleep(1000);
​
	cmd.cmdidx = 0;
	cmd.cmdarg = 0;
	cmd.resp_type = 0;
​
	err = mmc_send_cmd(lmmc, &cmd, 0);
	if ( !err )
	{
		usleep(2000);
		err = 0;
	}
	return err;
}
static int mmc_go_idle(struct mmc *mmc)
{
	struct mmc_cmd cmd;
	int err;
​
	udelay(1000);
​
	cmd.cmdidx = MMC_CMD_GO_IDLE_STATE;
	cmd.cmdarg = 0;
	cmd.resp_type = MMC_RSP_NONE;
​
	err = mmc_send_cmd(mmc, &cmd, NULL);
​
	if (err)
		return err;
​
	udelay(2000);
​
	return 0;
}

There seem to be some confusion between the use of op_cond_response and ocr. It would require investigation to check whether changes is wm220_0306 fixed that confusion, or just introduced a bug because of it.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_send_op_cond_iter(struct mmc *mmc,
		int use_arg)
{
	struct mmc *lmmc; // r4
	struct mmc_cmd cmd; // [sp+0h] [bp-28h]
​
	lmmc = mmc;
	cmd.cmdidx = 1;
	cmd.resp_type = 1;
	cmd.cmdarg = 0;
	if ( use_arg ) {
		cmd.cmdarg =
			mmc->cfg->voltages &
			mmc->ocr & 0x7FFF80 |
			mmc->ocr & 0x60000000 | 0x40000000;
	}
	err = mmc_send_cmd(mmc, &cmd, 0);
	if (err)
		return err;
	lmmc->ocr = cmd.response[0];
	return 0;
}
static int mmc_send_op_cond_iter(struct mmc *mmc,
		struct mmc_cmd *cmd, int use_arg)
{
	int err;
​
	cmd->cmdidx = MMC_CMD_SEND_OP_COND;
	cmd->resp_type = MMC_RSP_R3;
	cmd->cmdarg = 0;
	if (use_arg && !mmc_host_is_spi(mmc)) {
		cmd->cmdarg =
			(mmc->cfg->voltages &
			(mmc->op_cond_response & OCR_VOLTAGE_MASK)) |
			(mmc->op_cond_response & OCR_ACCESS_MODE);
​
		if (mmc->cfg->host_caps & MMC_MODE_HC)
			cmd->cmdarg |= OCR_HCS;
	}
	err = mmc_send_cmd(mmc, cmd, NULL);
	if (err)
		return err;
	mmc->op_cond_response = cmd->response[0];
	return 0;
}

An interesting compiler optimization - writing two sibling 32-bit values in one 64-bit write.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_send_ext_csd(struct mmc *mmc, uint8_t *ext_csd)
{
	__int64 v2; // r1
	struct mmc_data data; // [sp+4h] [bp-34h]
	struct mmc_cmd cmd; // [sp+18h] [bp-20h]
​
	cmd.cmdidx = 8;
	data.src_dest = (char *)ext_csd;
	LODWORD(v2) = 1;
	cmd.resp_type = 21;
	cmd.cmdarg = 0;
	HIDWORD(v2) = 512;
	data.blocks = 1;
	*(_QWORD *)&data.blocksize = v2;
​
	return mmc_send_cmd(mmc, &cmd, &data);
}
static int mmc_send_ext_csd(struct mmc *mmc, u8 *ext_csd)
{
	struct mmc_cmd cmd;
	struct mmc_data data;
	int err;
​
	/* Get the Card Status Register */
	cmd.cmdidx = MMC_CMD_SEND_EXT_CSD;
	cmd.resp_type = MMC_RSP_R1;
	cmd.cmdarg = 0;
	data.dest = (char *)ext_csd;
	data.blocks = 1;
	data.blocksize = MMC_MAX_BLOCK_LEN;
	data.flags = MMC_DATA_READ;
​
	err = mmc_send_cmd(mmc, &cmd, &data);
	return err;
}

Someone at DJI seem to have a thing for using stack pointers as timeout values.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_switch(struct mmc *mmc, uint8_t set,
    		uint8_t index, uint8_t value)
{
	struct mmc *lmmc; // r4
	int timeout, ret;
	struct mmc_cmd cmd; // [sp+0h] [bp-28h]
​
	lmmc = mmc;
	cmd.cmdidx = 6;
	cmd.resp_type = 29;
	cmd.cmdarg = (index << 16) |
		(value << 8) |
		0x3000000;
	ret = mmc_send_cmd(mmc, &cmd, 0);
​
	if (!ret)
		ret = mmc_send_status(mmc, timeout);
​
	return ret;
}
int mmc_switch(struct mmc *mmc, u8 set,
    		u8 index, u8 value)
{
	struct mmc_cmd cmd;
	int timeout = 1000;
	int ret;
​
	cmd.cmdidx = MMC_CMD_SWITCH;
	cmd.resp_type = MMC_RSP_R1b;
	cmd.cmdarg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
				 (index << 16) |
				 (value << 8);
	ret = mmc_send_cmd(mmc, &cmd, NULL);
​
	/* Waiting for the ready status */
	if (!ret)
		ret = mmc_send_status(mmc, timeout);
​
	return ret;
}

Here the only change is reordering of some assignments; yes compilers can do that.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __fastcall sd_switch(struct mmc *mmc,
	int mode, int group, uint8_t value, uint8_t *resp)
{
	struct mmc_data data; // [sp+4h] [bp-3Ch]
	struct mmc_cmd cmd; // [sp+18h] [bp-28h]
​
	cmd.cmdidx = 6;
	cmd.resp_type = 21;
	data.src_dest = (char *)resp;
	data.flags = 64;
	cmd.cmdarg = ((mode << 31) | 0xFFFFFF) &
		~(15 << 4 * group) |
		(value << 4 * group);
	data.blocksize = 1;
	data.blocks = 1;
​
	return mmc_send_cmd(mmc, &cmd, &data);
}
static int sd_switch(struct mmc *mmc,
		int mode, int group, u8 value, u8 *resp)
{
	struct mmc_cmd cmd;
	struct mmc_data data;
​
	/* Switch the frequency */
	cmd.cmdidx = SD_CMD_SWITCH_FUNC;
	cmd.resp_type = MMC_RSP_R1;
	cmd.cmdarg = (mode << 31) | 0xffffff;
	cmd.cmdarg &= ~(0xf << (group * 4));
	cmd.cmdarg |= value << (group * 4);
	data.dest = (char *)resp;
	data.blocksize = 64;
	data.blocks = 1;
	data.flags = MMC_DATA_READ;
​
	return mmc_send_cmd(mmc, &cmd, &data);
}

Here is a messy one. With multiple conditions and switch() involved, IDA had to resort to some goto use. The wm220_0306 also simplifies buffers allocation - instead of ALLOC_CACHE_ALIGN_BUFFER() which in u-boot returns aligned buffer on stack, static variables are used. Not a problem if you only have one CPU core, and some excess of RAM.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl sd_change_freq(struct mmc *mmc)
{
	struct mmc *lmmc; // r4
	signed int v3, v4;
	_BOOL1 v5; // cf
	uint8_t *v6; // r1
	unsigned int v7; // r0
	int v8, ver, err;
	_BOOL1 v10; // nf
	int v11; // r0
	unsigned int timeout; // r5
	int v13; // r0
	struct mmc_cmd cmd; // [sp+4h] [bp-4Ch]
	struct mmc_data data; // [sp+20h] [bp-30h]
​
	lmmc = mmc;
	mmc->card_caps = 0;
	cmd.cmdidx = 55;
	cmd.resp_type = 21;
	cmd.cmdarg = (unsigned __int16)mmc->rca << 16;
​
	err = mmc_send_cmd(mmc, &cmd, 0);
	if (err)
		return err;
​
	cmd.resp_type = 21;
	cmd.cmdarg = 0;
	timeout = 3;
	v3 = 8;
	v4 = 1;
	cmd.cmdidx = 51;
	while ( 1 )
	{
		data.src_dest = (char *)sd_scr;
		*(_QWORD *)&data.blocksize = *(_QWORD *)&v4;
		data.blocks = 1;
		err = mmc_send_cmd(lmmc, &cmd, &data);
		if (!err)
			break;
		v5 = timeout-- >= 1;
		if ( !v5 )
			return err;
	}
​
	v6 = sd_scr;
	v7 = *(_DWORD *)sd_scr;
	lmmc->scr[0] = *(_DWORD *)sd_scr;
	lmmc->scr[1] = *((_DWORD *)v6 + 1);
	v8 = (v7 >> 24) & 0xF;
	switch ( v8 )
	{
		case 0:
			goto LABEL_9;
		case 1:
			ver = SD_VERSION_1_10;
			break;
		case 2:
			lmmc->version = SD_VERSION_2;
			if ( !(v7 >> 15 << 31) ) {
				goto LABEL_14;
			}
			ver = SD_VERSION_3;
			break;
		default:
LABEL_9:
			lmmc->version = 0x80010000;
			goto LABEL_14;
	}
	lmmc->version = ver;
LABEL_14:
	v10 = ((v7 << 13) & 0x80000000) != 0;
	v11 = lmmc->card_caps;
	if ( v10 )
	{
		v11 |= 4u;
		lmmc->card_caps = v11;
	}
	lmmc->card_caps = v11 | 4;
	if ( lmmc->version == SD_VERSION_1_0 )
		return 0;
​
	timeout = 4;
	do {
		v5 = timeout-- >= 1;
		if ( !v5 )
			break;
		err = sd_switch(lmmc, 0, 0, 1u, sd_switch_status);
		if (err)
			return err;
	}
	while ( *((_DWORD *)sd_switch_status + 7) & 0x20000 );
​
	if ( *((_DWORD *)sd_switch_status + 3) & 0x20000 )
	{
		v13 = lmmc->cfg->host_caps;
		if ( v13 & 2 )
		{
			if ( lmmc->cfg->host_caps << 31)
			err = sd_switch(lmmc, 1, 0, 1u,
				sd_switch_status)
			if (err)
				return err;
			if ( ((*((_DWORD *)sd_switch_status + 4)
			    >> 24) & 0xF) == 1 )
				lmmc->card_caps |= 1u;
		}
	}
	return 0;
}
static int sd_change_freq(struct mmc *mmc)
{
	int err;
	struct mmc_cmd cmd;
	ALLOC_CACHE_ALIGN_BUFFER(uint, scr, 2);
	ALLOC_CACHE_ALIGN_BUFFER(uint, switch_status, 16);
	struct mmc_data data;
	int timeout;
​
	mmc->card_caps = 0;
​
	if (mmc_host_is_spi(mmc))
		return 0;
​
	/* Read the SCR to find out if this card supports higher speeds */
	cmd.cmdidx = MMC_CMD_APP_CMD;
	cmd.resp_type = MMC_RSP_R1;
	cmd.cmdarg = mmc->rca << 16;
​
	err = mmc_send_cmd(mmc, &cmd, NULL);
	if (err)
		return err;
​
	cmd.cmdidx = SD_CMD_APP_SEND_SCR;
	cmd.resp_type = MMC_RSP_R1;
	cmd.cmdarg = 0;
	timeout = 3;
retry_scr:
	data.dest = (char *)scr;
	data.blocksize = 8;
	data.blocks = 1;
	data.flags = MMC_DATA_READ;
​
	err = mmc_send_cmd(mmc, &cmd, &data);
	if (err) {
		if (timeout--)
			goto retry_scr;
		return err;
	}
​
	mmc->scr[0] = __be32_to_cpu(scr[0]);
	mmc->scr[1] = __be32_to_cpu(scr[1]);
​
	switch ((mmc->scr[0] >> 24) & 0xf) {
		case 0:
			mmc->version = SD_VERSION_1_0;
			break;
		case 1:
			mmc->version = SD_VERSION_1_10;
			break;
		case 2:
			mmc->version = SD_VERSION_2;
			if ((mmc->scr[0] >> 15) & 0x1)
				mmc->version = SD_VERSION_3;
			break;
		default:
			mmc->version = SD_VERSION_1_0;
			break;
	}
​
	if (mmc->scr[0] & SD_DATA_4BIT)
		mmc->card_caps |= MMC_MODE_4BIT;
	/* Version 1.0 doesn't support switching */
	if (mmc->version == SD_VERSION_1_0)
		return 0;
​
	timeout = 4;
	while (timeout--) {
		err = sd_switch(mmc, SD_SWITCH_CHECK, 0, 1,
				(u8 *)switch_status);
		if (err)
			return err;
		/* The high-speed function is busy.  Try again */
		if (!(__be32_to_cpu(switch_status[7]) & SD_HIGHSPEED_BUSY))
			break;
	}
​
	/* If high-speed isn't supported, we return */
	if (!(__be32_to_cpu(switch_status[3]) & SD_HIGHSPEED_SUPPORTED))
		return 0;
​
	/*
	 * If the host doesn't support SD_HIGHSPEED, do not switch card to
	 * HIGHSPEED mode even if the card support SD_HIGHSPPED.
	 * This can avoid furthur problem when the card runs in different
	 * mode between the host.
	 */
	if (!((mmc->cfg->host_caps & MMC_MODE_HS_52MHz) &&
		(mmc->cfg->host_caps & MMC_MODE_HS)))
		return 0;
​
	err = sd_switch(mmc, SD_SWITCH_SWITCH, 0, 1, (u8 *)switch_status);
	if (err)
		return err;
​
	if ((__be32_to_cpu(switch_status[4]) & 0x0f000000) == 0x01000000)
		mmc->card_caps |= MMC_MODE_HS;
​
	return 0;
}

Fortunately, now an easy one.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
void __cdecl mmc_set_clock(struct mmc *mmc, uint32_t clock)
{
	struct mmc_config *lmmc_cfg; // r3
​
	lmmc_cfg = mmc->cfg;
	if ( lmmc_cfg->f_max < clock )
		clock = lmmc_cfg->f_max;
​
	if ( lmmc_cfg->f_min > clock )
		clock = lmmc_cfg->f_min;
​
	mmc->clock = clock;
	mmc_set_ios(mmc);
}
void mmc_set_clock(struct mmc *mmc, uint clock)
{
	if (clock > mmc->cfg->f_max)
		clock = mmc->cfg->f_max;
​
	if (clock < mmc->cfg->f_min)
		clock = mmc->cfg->f_min;
​
	mmc->clock = clock;
	mmc_set_ios(mmc);
}

Next one is the largest mess, unfortunately. Mostly because the compiler optimized acces to array within a struct. Instead of first adding offet where the array starts, then offset of the item at specific index, and then offset within that item - the optimizer merged steps 1 and 3. It is more efficient, but IDA gets completely confused by such magic.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_startup(struct mmc *mmc)
{
	struct mmc *lmmc; // r4
	signed int v2; // r10
	int *v3; // r6
	int err; // r0
	uint32_t v5, v6, v7, v12, v13;
	int *v8; // r7
	uint32_t v9; // r0
	int v10, v11, v15, v16, v17;
	MMC_VERSION v14; // r2
	__int64 v18; // r0
	int v19, v20, v21, v25;
	_BOOL1 v22; // zf
	unsigned int v23; // r0
	unsigned __int8 *v24; // r0
	unsigned int v26, v27;
	MMC_VERSION v28; // r1
	uint64_t v29; // r1
	int v30, v31, v32, v33, v34, v37, v38, v39;
	unsigned int v35, v36, v40;
	int v41, v42, v43, v46;
	__int64 v44; // r1
	_BOOL1 v45; // zf
	uint64_t v47; // r1
	int v48, v49, v50, v52, v53;
	struct mmc *v51; // r0
	unsigned int v54, v60;
	int v55, v56, v57, v58, v59, v61;
	struct mmc_cmd cmd; // [sp+0h] [bp-40h]
​
	lmmc = mmc;
	v2 = 0;
	v3 = 0;
	cmd.cmdidx = 2;
	cmd.resp_type = 7;
	cmd.cmdarg = 0;
	err = mmc_send_cmd(mmc, &cmd, 0);
	if ( err )
		return err;
​
	v5 = cmd.response[1];
	v6 = cmd.response[2];
	v7 = cmd.response[3];
	v8 = lmmc->cid;
	*v8 = cmd.response[0];
	v8[1] = v5;
	v8[2] = v6;
	v8[3] = v7;
​
	cmd.cmdidx = 3;
	v9 = (unsigned __int16)lmmc->rca << 16;
	cmd.resp_type = 21;
	cmd.cmdarg = v9;
	err = mmc_send_cmd(lmmc, &cmd, 0);
	if ( err )
		return err;
	if ( lmmc->version < 0 )
		lmmc->rca = HIWORD(cmd.response[0]);
​
	cmd.cmdidx = 9;
	cmd.resp_type = 7;
	cmd.cmdarg = (unsigned __int16)lmmc->rca << 16;
	v10 = mmc_send_cmd(lmmc, &cmd, 0);
	mmc_send_status(lmmc, v11);
	if ( v10 )
		return v10;
​
	lmmc->csd[0] = cmd.response[0];
	v12 = cmd.response[1];
	lmmc->csd[1] = cmd.response[1];
	v13 = cmd.response[2];
	lmmc->csd[2] = cmd.response[2];
	lmmc->csd[3] = cmd.response[3];
​
	if ( lmmc->version == MMC_VERSION_UNKNOWN )
	{
		switch ( (cmd.response[0] >> 26) & 0xF )
		{
			case 1u:
				v14 = MMC_VERSION_1_4;
				goto LABEL_13;
			case 2u:
				v14 = MMC_VERSION_2_2;
				goto LABEL_13;
			case 3u:
				v14 = MMC_VERSION_3;
				goto LABEL_13;
			case 4u:
				v14 = MMC_VERSION_4;
LABEL_13:
				lmmc->version = v14;
				break;
			default:
				lmmc->version = MMC_VERSION_1_2;
				break;
		}
	}
​
	lmmc->tran_speed = mmc_fbase[cmd.response[0] & 7] *
		mmc_multipliers[(cmd.response[0] >> 3) & 0xF];
	lmmc->dsr_imp = (cmd.response[1] >> 12) & 1;
	v15 = 1 << (BYTE2(cmd.response[1]) & 0xF);
	lmmc->read_bl_len = v15;
​
	if ( lmmc->version >= 0 )
		lmmc->write_bl_len = 1 << ((cmd.response[3] >> 22) & 0xF);
	else
		lmmc->write_bl_len = v15;
​
	if ( lmmc->high_capacity ) {
		v16 = 8;
		v17 = (v12 << 16) & 0x3FFFFF | (v13 >> 16);
	} else {
		v16 = (v13 >> 15) & 7;
		v17 = 4 * v12 & 0xFFF | (v13 >> 30);
	}
	LODWORD(v18) = bit_shift_left(v17 + 1,
		((unsigned __int64)(unsigned int)v17 + 1) >> 32, v16 + 2);
	lmmc->capacity_user = (unsigned int)v15 * v18;
	LODWORD(lmmc->capacity_boot) = 0;
	HIDWORD(lmmc->capacity_boot) = 0;
	LODWORD(lmmc->capacity_rpmb) = 0;
	HIDWORD(lmmc->capacity_rpmb) = 0;
	v19 = 0;
	do
	{
		v20 = (int)lmmc + 8 * v19++;
		*(_DWORD *)(v20 + 152) = 0;
		*(_DWORD *)(v20 + 156) = 0;
	}
	while ( v19 < 4 );
​
	if ( lmmc->read_bl_len > 0x200u )
		lmmc->read_bl_len = 512;
	if ( lmmc->write_bl_len > 0x200u )
		lmmc->write_bl_len = 512;
​
	if ( lmmc->dsr_imp )
	{
		v21 = lmmc->dsr;
		if ( v21 != -1 )
		{
			cmd.cmdidx = 4;
			cmd.resp_type = 0;
			cmd.cmdarg = v21 << 16;
			if ( mmc_send_cmd(lmmc, &cmd, 0) )
				printf_s(7, "MMC: SET_DSR failed\n");
		}
	}
	cmd.cmdidx = 7;
	cmd.resp_type = 21;
	cmd.cmdarg = (unsigned __int16)lmmc->rca << 16;
	err = mmc_send_cmd(lmmc, &cmd, 0);
	v22 = err == 0;
	do
	{
		if ( !v22 )
			return err;
		lmmc->erase_grp_size = 1;
		lmmc->part_config = -1;
		v23 = lmmc->version;
		if ( (v23 & 0x80000000) != 0 || v23 < MMC_VERSION_4 )
			goto LABEL_81;
		err = mmc_send_ext_csd(lmmc, (uint8_t *)ext_csd);
		v22 = err == 0;
	}
	while ( err );
	v24 = (unsigned __int8 *)ext_csd;
	if ( *(unsigned __int8 *)(ext_csd + 192) >= 2u )
	{
		v25 = *(_DWORD *)(ext_csd + 212) & 0xFFFFFF |
			(*(unsigned __int8 *)(ext_csd + 215) << 24);
		v26 = (unsigned __int64)v25 >> 23;
		v27 = v25 << 9;
		if ( __PAIR__(v26 >> 20, (v27 >> 20) | (v26 << 12)) > 0x800 )
		{
			LODWORD(lmmc->capacity_user) = v27;
			HIDWORD(lmmc->capacity_user) = v26;
		}
	}
	switch ( v24[192] )
	{
		case 1u:
			v28 = MMC_VERSION_4_1;
			goto LABEL_44;
		case 2u:
			v28 = MMC_VERSION_4_2;
			goto LABEL_44;
		case 3u:
			v28 = MMC_VERSION_4_3;
			goto LABEL_44;
		case 5u:
			v28 = MMC_VERSION_4_41;
			goto LABEL_44;
		case 6u:
			v28 = 1074005248;
			goto LABEL_44;
		case 7u:
			v28 = MMC_VERSION_5_0;
LABEL_44:
			lmmc->version = v28;
			break;
		default:
			break;
	}
	v15 = v24[155] & 1;
	lmmc->part_support = v24[160];
	if ( v24[160] << 31 || v24[226] )
		lmmc->part_config = v24[179];
	if ( v15 && v24[160] & 2 )
		lmmc->part_attr = v24[156];
	LODWORD(v29) = v24[226] << 17;
	HIDWORD(v29) = (signed int)v29 >> 31;
	lmmc->capacity_boot = v29;
	LODWORD(v29) = v24[168] << 17;
	HIDWORD(v29) = (signed int)v29 >> 31;
	lmmc->capacity_rpmb = v29;
	for (v30 = 0; v30 < 4; ++v30)
	{
		v31 = (v24[3 * v30 + 145] << 16) +
			(v24[3 * v30 + 144] << 8) +
			v24[3 * v30 + 143];
		if ( v31 )
			v2 = 1;
		if ( v15 )
		{
			v32 = (int)lmmc + 8 * v30;
			v33 = (int)lmmc + 8 * v30;
			*(_DWORD *)(v32 + 152) = (v24[3 * v30 + 145] << 16)
				+ (v24[3 * v30 + 144] << 8) + v24[3 * v30 + 143];
			*(_DWORD *)(v32 + 156) = 0;
			v34 = v31 * v24[224];
			v35 = v24[224] * (unsigned __int64)(unsigned int)v31 >> 32;
			*(_DWORD *)(v33 + 152) = v34;
			*(_DWORD *)(v33 + 156) = v35;
			v36 = v34 * v24[221];
			v37 = v24[221] * __PAIR__(v35, v34) >> 32 << 19;
			*(_DWORD *)(v33 + 152) = v36 << 19;
			*(_DWORD *)(v33 + 156) = v37 | (v36 >> 13);
		}
	}
	if ( v15 )
	{
		v38 = (v24[142] << 16) + (v24[141] << 8) + v24[140];
		*(_DWORD *)&lmmc->field_BC[4] = v38;
		*(_DWORD *)&lmmc->field_BC[8] = v38 >> 31;
		v39 = v24[224];
		v40 = (unsigned __int64)v24[224] * v38 >> 32;
		*(_DWORD *)&lmmc->field_BC[4] = v38 * v39;
		*(_DWORD *)&lmmc->field_BC[8] = v40;
		v41 = v38 * v39 * v24[221];
		v42 = (signed __int64)(v24[221] * __PAIR__(v40, v38 * v39)) >> 13;
		*(_DWORD *)&lmmc->field_BC[4] = v41 << 19;
		*(_DWORD *)&lmmc->field_BC[8] = v42;
		v43 = v24[136] + (v24[139] << 24) + (v24[138] << 16) + (v24[137] << 8);
		*(_DWORD *)&lmmc->field_A4[20] = v43;
		*(_DWORD *)lmmc->field_BC = v43 >> 31;
		if ( lmmc->high_capacity )
		{
			HIDWORD(v44) = (unsigned __int64)v43 >> 23;
			LODWORD(v44) = v43 << 9;
			*(_QWORD *)&lmmc->field_A4[20] = v44;
		}
		v2 = 1;
	}
	v3 = &ext_csd;
	if ( (!(v24[160] << 31) || !(v24[156] << 27)) && !v2 )
	{
LABEL_75:
		v46 = *v3;
		if ( *(unsigned __int8 *)(*v3 + 175) << 31 )
		{
			lmmc->erase_grp_size = *(unsigned __int8 *)(*v3 + 224) << 10;
			if ( lmmc->high_capacity && v15 )
			{
				LODWORD(v47) = *(_DWORD *)(v46 + 212) & 0xFFFFFF |
					(*(unsigned __int8 *)(v46 + 215) << 24);
				HIDWORD(v47) = (unsigned __int64)(signed int)v47 >> 23;
				LODWORD(v47) = (_DWORD)v47 << 9;
				lmmc->capacity_user = v47;
			}
		}
		else
		{
			lmmc->erase_grp_size = 
				(signed __int16)(((LOWORD(lmmc->csd[2]) >> 10) & 0x1F) + 1) *
				(signed __int16)(((LOWORD(lmmc->csd[2]) >> 5) & 0x1F) + 1);
		}
		lmmc->hc_wp_grp_size = (*(unsigned __int8 *)(v46 + 224) << 10) *
			*(unsigned __int8 *)(v46 + 221);
		lmmc->wr_rel_set = *(_BYTE *)(v46 + 167);
LABEL_81:
		v48 = (unsigned __int8)lmmc->part_num;
		switch ( v48 )
		{
			case 0:
				*(_QWORD *)&v49 = lmmc->capacity_user;
				v51 = lmmc;
				goto LABEL_86;
			case 1:
			case 2:
				*(_QWORD *)&v49 = lmmc->capacity_boot;
				v51 = lmmc;
				goto LABEL_86;
			case 3:
				*(_QWORD *)&v49 = lmmc->capacity_rpmb;
				v51 = lmmc;
				goto LABEL_86;
			case 4:
			case 5:
			case 6:
			case 7:
				*(_QWORD *)&v49 = *(_QWORD *)&lmmc->field_78[8 * v48];
				v51 = lmmc;
LABEL_86:
				*(_DWORD *)v51->field_78 = v49;
				err = 0;
				*(_DWORD *)&lmmc->field_78[4] = v50;
				break;
			default:
				err = -1;
				break;
		}
		v45 = err == 0;
		do
		{
			if ( !v45 )
				goto LABEL_72;
			if ( lmmc->version < 0 )
				err = sd_change_freq(lmmc);
			else
				err = mmc_change_freq(lmmc);
			v52 = err;
			v45 = err == 0;
		}
		while ( err );
		v53 = lmmc->card_caps & lmmc->cfg->host_caps;
		lmmc->card_caps = v53;
		v54 = lmmc->version;
		if ( (v54 & 0x80000000) == 0 )
		{
			if ( v54 < MMC_VERSION_4 )
				goto LABEL_120;
			mmc_unk_AB8C = 36;
			mmc_unk_AB90 = 40;
			mmc_unk_AB7C = 4;
			v58 = 0;
			mmc_unk_AB80 = 8;
			while ( 1 )
			{
				v59 = ext_csd_bits[v58];
				v60 = ext_to_hostcaps[v59];
				if ( !v59 && lmmc->bus_width == 1 )
					break;
				if ( !(v60 & ~lmmc->card_caps) )
				{
					v52 = mmc_switch(lmmc, 1u, 0xB7u, v59);
					if ( !v52 )
					{
						lmmc->ddr_mode = (v60 >> 5) & 1;
						mmc_set_bus_width(lmmc, mmc_widths[v58]);
					}
				}
				if ( (unsigned int)++v58 >= 5 )
				{
					if ( v52 )
						return v52;
					break;
				}
			}
			v61 = lmmc->card_caps;
			if ( !(v61 << 31) )
				goto LABEL_120;
			if ( v61 & 2 )
				v57 = 52000000;
			else
				v57 = 26000000;
		}
		else
		{
			if ( v53 & 4 )
			{
				cmd.cmdidx = 55;
				cmd.resp_type = 21;
				cmd.cmdarg = (unsigned __int16)lmmc->rca << 16;
				v55 = mmc_send_cmd(lmmc, &cmd, 0);
				v10 = v55;
				if ( v55 )
				{
					printf_s(7, "mmc cmd:%d error: %d\n\r",
						cmd.cmdidx, v55);
					return v10;
				}
				cmd.cmdidx = 6;
				cmd.resp_type = 21;
				cmd.cmdarg = 2;
				v56 = mmc_send_cmd(lmmc, &cmd, 0);
				v52 = v56;
				if ( v56 )
				{
					printf_s(7, "mmc cmd:%d error: %d\n\r",
						cmd.cmdidx, v56);
					return v52;
				}
				mmc_set_bus_width(lmmc, 4u);
			}
			if ( LOBYTE(lmmc->card_caps) << 31 )
				v57 = 50000000;
			else
				v57 = 25000000;
		}
		lmmc->tran_speed = v57;
LABEL_120:
		mmc_set_clock(lmmc, lmmc->tran_speed);
		if ( lmmc->ddr_mode )
		{
			lmmc->read_bl_len = 512;
			lmmc->write_bl_len = 512;
		}
		return 0;
	}
	err = mmc_switch(lmmc, 1u, 0xAFu, 1u);
	v45 = err == 0;
LABEL_72:
	if ( v45 )
	{
		*(_BYTE *)(*v3 + 175) = 1;
		goto LABEL_75;
	}
	return err;
}
static int mmc_startup(struct mmc *mmc)
{
	int err, i;
	uint mult, freq;
	u64 cmult, csize, capacity;
	struct mmc_cmd cmd;
	ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
	ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN);
	int timeout = 1000;
​
	/* Put the Card in Identify Mode */
	cmd.cmdidx = mmc_host_is_spi(mmc) ? MMC_CMD_SEND_CID :
		MMC_CMD_ALL_SEND_CID; /* cmd not supported in spi */
	cmd.resp_type = MMC_RSP_R2;
	cmd.cmdarg = 0;
	err = mmc_send_cmd(mmc, &cmd, NULL);
	if (err)
		return err;
​
	memcpy(mmc->cid, cmd.response, 16);
​
​
​
	/*
	 * For MMC cards, set the Relative Address.
	 * For SD cards, get the Relatvie Address.
	 * This also puts the cards into Standby State
	 */
	if (!mmc_host_is_spi(mmc)) { /* cmd not supported in spi */
		cmd.cmdidx = SD_CMD_SEND_RELATIVE_ADDR;
		cmd.cmdarg = mmc->rca << 16;
		cmd.resp_type = MMC_RSP_R6;
		err = mmc_send_cmd(mmc, &cmd, NULL);
		if (err)
			return err;
		if (IS_SD(mmc))
			mmc->rca = (cmd.response[0] >> 16) & 0xffff;
	}
​
	/* Get the Card-Specific Data */
	cmd.cmdidx = MMC_CMD_SEND_CSD;
	cmd.resp_type = MMC_RSP_R2;
	cmd.cmdarg = mmc->rca << 16;
	err = mmc_send_cmd(mmc, &cmd, NULL);
	/* Waiting for the ready status */
	mmc_send_status(mmc, timeout);
	if (err)
		return err;
​
	mmc->csd[0] = cmd.response[0];
	mmc->csd[1] = cmd.response[1];
	mmc->csd[2] = cmd.response[2];
	mmc->csd[3] = cmd.response[3];
​
	if (mmc->version == MMC_VERSION_UNKNOWN) {
		int version = (cmd.response[0] >> 26) & 0xf;
		switch (version) {
			case 0:
				mmc->version = MMC_VERSION_1_2;
				break;
			case 1:
				mmc->version = MMC_VERSION_1_4;
				break;
			case 2:
				mmc->version = MMC_VERSION_2_2;
				break;
			case 3:
				mmc->version = MMC_VERSION_3;
				break;
			case 4:
				mmc->version = MMC_VERSION_4;
				break;
			default:
				mmc->version = MMC_VERSION_1_2;
				break;
		}
	}
​
	/* divide frequency by 10, since the mults are 10x bigger */
	freq = fbase[(cmd.response[0] & 0x7)];
	mult = multipliers[((cmd.response[0] >> 3) & 0xf)];
	mmc->tran_speed = freq * mult;
	mmc->dsr_imp = ((cmd.response[1] >> 12) & 0x1);
	mmc->read_bl_len = 1 << ((cmd.response[1] >> 16) & 0xf);
​
	if (IS_SD(mmc))
		mmc->write_bl_len = mmc->read_bl_len;
	else
		mmc->write_bl_len = 1 << ((cmd.response[3] >> 22) & 0xf);
​
	if (mmc->high_capacity) {
		csize = (mmc->csd[1] & 0x3f) << 16
			| (mmc->csd[2] & 0xffff0000) >> 16;
		cmult = 8;
	} else {
		csize = (mmc->csd[1] & 0x3ff) << 2
			| (mmc->csd[2] & 0xc0000000) >> 30;
		cmult = (mmc->csd[2] & 0x00038000) >> 15;
	}
​
	mmc->capacity_user = (csize + 1) << (cmult + 2);
	mmc->capacity_user *= mmc->read_bl_len;
	mmc->capacity_boot = 0;
	mmc->capacity_rpmb = 0;
	for (i = 0; i < 4; i++)
		mmc->capacity_gp[i] = 0;
​
	if (mmc->read_bl_len > MMC_MAX_BLOCK_LEN)
		mmc->read_bl_len = MMC_MAX_BLOCK_LEN;
​
	if (mmc->write_bl_len > MMC_MAX_BLOCK_LEN)
		mmc->write_bl_len = MMC_MAX_BLOCK_LEN;
​
	if ((mmc->dsr_imp) && (0xffffffff != mmc->dsr)) {
		cmd.cmdidx = MMC_CMD_SET_DSR;
		cmd.cmdarg = (mmc->dsr & 0xffff) << 16;
		cmd.resp_type = MMC_RSP_NONE;
		if (mmc_send_cmd(mmc, &cmd, NULL))
			printf("MMC: SET_DSR failed\n");
	}
​
	/* Select the card, and put it into Transfer Mode */
	if (!mmc_host_is_spi(mmc)) { /* cmd not supported in spi */
		cmd.cmdidx = MMC_CMD_SELECT_CARD;
		cmd.resp_type = MMC_RSP_R1;
		cmd.cmdarg = mmc->rca << 16;
		err = mmc_send_cmd(mmc, &cmd, NULL);
​
		if (err)
			return err;
	}
​
	/*
	 * For SD, its erase group is always one sector
	 */
	mmc->erase_grp_size = 1;
	mmc->part_config = MMCPART_NOAVAILABLE;
	if (!IS_SD(mmc) && (mmc->version >= MMC_VERSION_4)) {
		/* check  ext_csd version and capacity */
		err = mmc_send_ext_csd(mmc, ext_csd);
		if (!err && (ext_csd[EXT_CSD_REV] >= 2)) {
			/*
			 * According to the JEDEC Standard, the value of
			 * ext_csd's capacity is valid if the value is more
			 * than 2GB
			 */
			capacity = ext_csd[EXT_CSD_SEC_CNT] << 0
					| ext_csd[EXT_CSD_SEC_CNT + 1] << 8
					| ext_csd[EXT_CSD_SEC_CNT + 2] << 16
					| ext_csd[EXT_CSD_SEC_CNT + 3] << 24;
			capacity *= MMC_MAX_BLOCK_LEN;
			if ((capacity >> 20) > 2 * 1024)
				mmc->capacity_user = capacity;
		}
​
		switch (ext_csd[EXT_CSD_REV]) {
		case 1:
			mmc->version = MMC_VERSION_4_1;
			break;
		case 2:
			mmc->version = MMC_VERSION_4_2;
			break;
		case 3:
			mmc->version = MMC_VERSION_4_3;
			break;
		case 5:
			mmc->version = MMC_VERSION_4_41;
			break;
		case 6:
			mmc->version = MMC_VERSION_4_5;
			break;
		case 7:
			mmc->version = MMC_VERSION_5_0;
			break;
		case 8:
			mmc->version = MMC_VERSION_5_1;
			break;
		}
​
		/* dev life time estimate type A/B */
		mmc->dev_lifetime_est_typ_a
			= ext_csd[EXT_CSD_DEV_LIFETIME_EST_TYP_A];
		mmc->dev_lifetime_est_typ_b
			= ext_csd[EXT_CSD_DEV_LIFETIME_EST_TYP_B];
​
		/*
		 * Host needs to enable ERASE_GRP_DEF bit if device is
		 * partitioned. This bit will be lost every time after a reset
		 * or power off. This will affect erase size.
		 */
		if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) &&
		    (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB)) {
			err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
				EXT_CSD_ERASE_GROUP_DEF, 1);
​
			if (err)
				return err;
			else
				ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1;
​
			/* Read out group size from ext_csd */
			mmc->erase_grp_size =
				ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024;
			/*
			 * if high capacity and partition setting completed
			 * SEC_COUNT is valid even if it is smaller than 2 GiB
			 * JEDEC Standard JESD84-B45, 6.2.4
			 */
			if (mmc->high_capacity &&
			    (ext_csd[EXT_CSD_PARTITION_SETTING] &
			     EXT_CSD_PARTITION_SETTING_COMPLETED)) {
				capacity = (ext_csd[EXT_CSD_SEC_CNT]) |
					(ext_csd[EXT_CSD_SEC_CNT + 1] << 8) |
					(ext_csd[EXT_CSD_SEC_CNT + 2] << 16) |
					(ext_csd[EXT_CSD_SEC_CNT + 3] << 24);
				capacity *= MMC_MAX_BLOCK_LEN;
				mmc->capacity_user = capacity;
			}
		} else {
			/* Calculate the group size from the csd value. */
			int erase_gsz, erase_gmul;
			erase_gsz = (mmc->csd[2] & 0x00007c00) >> 10;
			erase_gmul = (mmc->csd[2] & 0x000003e0) >> 5;
			mmc->erase_grp_size = (erase_gsz + 1)
				* (erase_gmul + 1);
		}
​
		/* store the partition info of emmc */
		if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) ||
		    ext_csd[EXT_CSD_BOOT_MULT])
			mmc->part_config = ext_csd[EXT_CSD_PART_CONF];
​
		mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17;
​
		mmc->capacity_rpmb = ext_csd[EXT_CSD_RPMB_MULT] << 17;
​
		for (i = 0; i < 4; i++) {
			int idx = EXT_CSD_GP_SIZE_MULT + i * 3;
			mmc->capacity_gp[i] = (ext_csd[idx + 2] << 16) +
				(ext_csd[idx + 1] << 8) + ext_csd[idx];
			mmc->capacity_gp[i] *=
				ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
			mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
		}
		mmc->hc_wp_grp_size = 1024
			* ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]
			* ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
​
		mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT];
	}
​
	err = mmc_set_capacity(mmc, mmc->part_num);
	if (err)
		return err;
​
	if (IS_SD(mmc))
		err = sd_change_freq(mmc);
	else
		err = mmc_change_freq(mmc);
​
	if (err)
		return err;
​
	/* Restrict card's capabilities by what the host can do */
	mmc->card_caps &= mmc->cfg->host_caps;
​
	if (IS_SD(mmc)) {
		if (mmc->card_caps & MMC_MODE_4BIT) {
			cmd.cmdidx = MMC_CMD_APP_CMD;
			cmd.resp_type = MMC_RSP_R1;
			cmd.cmdarg = mmc->rca << 16;
​
			err = mmc_send_cmd(mmc, &cmd, NULL);
			if (err)
				return err;
​
			cmd.cmdidx = SD_CMD_APP_SET_BUS_WIDTH;
			cmd.resp_type = MMC_RSP_R1;
			cmd.cmdarg = 2;
			err = mmc_send_cmd(mmc, &cmd, NULL);
			if (err)
				return err;
​
			mmc_set_bus_width(mmc, 4);
		}
​
		if (mmc->card_caps & MMC_MODE_HS)
			mmc->tran_speed = 50000000;
		else
			mmc->tran_speed = 25000000;
	} else {
		int idx;
​
		/* An array of possible bus widths in order of preference */
		static unsigned ext_csd_bits[] = {
			EXT_CSD_DDR_BUS_WIDTH_8,
			EXT_CSD_DDR_BUS_WIDTH_4,
			EXT_CSD_BUS_WIDTH_8,
			EXT_CSD_BUS_WIDTH_4,
			EXT_CSD_BUS_WIDTH_1,
		};
​
		/* An array to map CSD bus widths to host cap bits */
		static unsigned ext_to_hostcaps[] = {
			[EXT_CSD_DDR_BUS_WIDTH_4] =
				MMC_MODE_DDR_52MHz | MMC_MODE_4BIT,
			[EXT_CSD_DDR_BUS_WIDTH_8] =
				MMC_MODE_DDR_52MHz | MMC_MODE_8BIT,
			[EXT_CSD_BUS_WIDTH_4] = MMC_MODE_4BIT,
			[EXT_CSD_BUS_WIDTH_8] = MMC_MODE_8BIT,
		};
​
		/* An array to map chosen bus width to an integer */
		static unsigned widths[] = {
			8, 4, 8, 4, 1,
		};
​
		for (idx=0; idx < ARRAY_SIZE(ext_csd_bits); idx++) {
			unsigned int extw = ext_csd_bits[idx];
			unsigned int caps = ext_to_hostcaps[extw];
​
			/*
			 * Check to make sure the card and controller support
			 * these capabilities
			 */
			if ((mmc->card_caps & caps) != caps)
				continue;
​
			err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
					EXT_CSD_BUS_WIDTH, extw);
​
			if (err)
				continue;
​
			mmc->ddr_mode = (caps & MMC_MODE_DDR_52MHz) ? 1 : 0;
			mmc_set_bus_width(mmc, widths[idx]);
			err = mmc_send_ext_csd(mmc, test_csd);
​
			if (err)
				continue;
​
			/* Only compare read only fields */
			if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT]
				== test_csd[EXT_CSD_PARTITIONING_SUPPORT] &&
			    ext_csd[EXT_CSD_HC_WP_GRP_SIZE]
				== test_csd[EXT_CSD_HC_WP_GRP_SIZE] &&
			    ext_csd[EXT_CSD_REV]
				== test_csd[EXT_CSD_REV] &&
			    ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]
				== test_csd[EXT_CSD_HC_ERASE_GRP_SIZE] &&
			    memcmp(&ext_csd[EXT_CSD_SEC_CNT],
				   &test_csd[EXT_CSD_SEC_CNT], 4) == 0)
				break;
			else
				err = SWITCH_ERR;
		}
​
		if (err)
			return err;
​
		if (mmc->card_caps & MMC_MODE_HS) {
			if (mmc->card_caps & MMC_MODE_HS_52MHz)
				mmc->tran_speed = 52000000;
			else
				mmc->tran_speed = 26000000;
		}
	}
​
	mmc_set_clock(mmc, mmc->tran_speed);
​
	if (mmc->card_caps & MMC_MODE_HS) {
		err = aml_emmc_refix(mmc);
		if (!err)
			printf("[%s] mmc refix success\n", __func__);
		else
			printf("[%s] mmc refix error\n", __func__);
	}
​
	/* Fix the block length for DDR mode */
	if (mmc->ddr_mode) {
		mmc->read_bl_len = MMC_MAX_BLOCK_LEN;
		mmc->write_bl_len = MMC_MAX_BLOCK_LEN;
	}
​
	/* fill in device description */
	mmc->block_dev.lun = 0;
	mmc->block_dev.type = 0;
	mmc->block_dev.blksz = mmc->read_bl_len;
	mmc->block_dev.log2blksz = LOG2(mmc->block_dev.blksz);
	mmc->block_dev.lba = lldiv(mmc->capacity, mmc->read_bl_len);
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
	sprintf(mmc->block_dev.vendor, "Man %06x Snr %04x%04x",
		mmc->cid[0] >> 24, (mmc->cid[2] & 0xffff),
		(mmc->cid[3] >> 16) & 0xffff);
	sprintf(mmc->block_dev.product, "%c%c%c%c%c%c", mmc->cid[0] & 0xff,
		(mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff,
		(mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff,
		(mmc->cid[2] >> 24) & 0xff);
	sprintf(mmc->block_dev.revision, "%d.%d", (mmc->cid[2] >> 20) & 0xf,
		(mmc->cid[2] >> 16) & 0xf);
#else
	mmc->block_dev.vendor[0] = 0;
	mmc->block_dev.product[0] = 0;
	mmc->block_dev.revision[0] = 0;
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBDISK_SUPPORT)
	init_part(&mmc->block_dev);
#endif
​
	return 0;
}

Now after the largest function is behing us, the rest will be a breeze. This one is interesting as it presents how the optimizer inlines functions. There are two functions here which body has been copied directly to this function.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_start_init(struct mmc *mmc)
{
	struct mmc *lmmc; // r5
	int err, v3, v4;
	struct mmc_cmd cmd; // [sp+0h] [bp-38h]
​
	lmmc = mmc;
	if ( mmc->has_init )
		return 0;
​
	mmc->cfg->ops->init(mmc);
​
	lmmc->ddr_mode = 0;
	mmc_set_bus_width(lmmc, 1u);
	mmc_set_clock(lmmc, 1u);
​
	err = mmc_go_idle(lmmc);
	if ( err )
		return err;
​
	lmmc->part_num = 0;
​
	/* inline: err = mmc_send_if_cond(mmc); */
	cmd.cmdidx = 8;
	v3 = lmmc->cfg->voltages & 0xFF8000;
	cmd.cmdarg = ((v3 != 0) << 8) | 0xAA;
	cmd.resp_type = 21;
	if ( !mmc_send_cmd(lmmc, &cmd, 0) && LOBYTE(cmd.response[0]) == 170 )
		lmmc->version = 0x80020000;
	/* inline ends */
​
	err = sd_send_op_cond(lmmc);
	if ( err != -19 ) {
		if ( err )
			return err;
		goto LABEL_10;
	}
​
	/* inline: err = mmc_send_op_cond(mmc); */
	mmc_go_idle(lmmc);
	v4 = 0;
	while ( 1 ) {
		err = mmc_send_op_cond_iter(lmmc, v4 != 0);
		if ( err )
			break;
		if ( lmmc->ocr >= 0 && ++v4 < 2 )
			continue;
		err = 0;
		lmmc->op_cond_pending = 1;
		break;
	}
	/* inline ends */
​
	if ( err ) {
		printf_s(7, "Card did not respond to voltage select!\n");
		return -17;
	}
LABEL_10:
	lmmc->init_in_progress = 1;
	return err;
}
int mmc_start_init(struct mmc *mmc)
{
	int err;
​
	/* we pretend there's no card when init is NULL */
	if (mmc_getcd(mmc) == 0 || mmc->cfg->ops->init == NULL) {
		mmc->has_init = 0;
		return NO_CARD_ERR;
	}
​
	if (mmc->has_init)
		return 0;
​
	board_mmc_power_init();
​
	/* made sure it's not NULL earlier */
	err = mmc->cfg->ops->init(mmc);
	if (err)
		return err;
​
	mmc->ddr_mode = 0;
	mmc_set_bus_width(mmc, 1);
	mmc_set_clock(mmc, 1);
​
	/* Reset the Card */
	err = mmc_go_idle(mmc);
	if (err)
		return err;
​
	/* The internal partition reset to user partition(0) at every CMD0*/
	mmc->part_num = 0;
​
	/* Test for SD version 2 */
	err = mmc_send_if_cond(mmc);
​
	/* Now try to get the SD card's operating condition */
	err = sd_send_op_cond(mmc);
​
	/* If the command timed out, we check for an MMC card */
	if (err == TIMEOUT) {
		err = mmc_send_op_cond(mmc);
​
		if (err && err != IN_PROGRESS) {
			printf("Card did not respond to voltage select!\n");
			return UNUSABLE_ERR;
		}
	}
​
	if (err == IN_PROGRESS)
		mmc->init_in_progress = 1;
	return err;
}
​
static int mmc_send_if_cond(struct mmc *mmc)
{
	struct mmc_cmd cmd;
	int err;
	cmd.cmdidx = SD_CMD_SEND_IF_COND;
	/* We set the bit if the host supports voltages between 2.7 and 3.6 V */
	cmd.cmdarg = ((mmc->cfg->voltages & 0xff8000) != 0) << 8 | 0xaa;
	cmd.resp_type = MMC_RSP_R7;
	err = mmc_send_cmd(mmc, &cmd, NULL);
	if (err)
		return err;
	if ((cmd.response[0] & 0xff) != 0xaa)
		return UNUSABLE_ERR;
	else
		mmc->version = SD_VERSION_2;
	return 0;
}
​
static int mmc_send_op_cond(struct mmc *mmc)
{
	int err, i;
	/* Some cards seem to need this */
	mmc_go_idle(mmc);
 	/* Asking to the card its capabilities */
	for (i = 0; i < 2; i++) {
		err = mmc_send_op_cond_iter(mmc, i != 0);
		if (err)
			return err;
		/* exit if not busy (flag seems to be inverted) */
		if (mmc->ocr & OCR_BUSY)
			break;
	}
	mmc->op_cond_pending = 1;
	return 0;
}

This one has structure from u-boot, but text strings do not match. But don't think these new strings come from DJI - they were copied as well, just not from GPL code, this time - they come from Atmel SAM71 SDK.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_init(struct mmc *mmc)
{
	struct mmc *lmmc; // r5
	int err; // r4
	uint64_t v3; // r0
​
	lmmc = mmc;
	mmc->has_init = 0;
​
	if ( mmc->init_in_progress || (err = mmc_start_init(mmc)) == 0 )
	{
		err = mmc_complete_init(lmmc);
		if ( !err )
		{
			printf_s(7, "SD/MMC card initialization successful\n\r");
			v3 = lldiv(lmmc->capacity_user, 1000000u);
			printf_s(7, "Card size: %lld MB, blksize: %d\r\n",
					v3, lmmc->read_bl_len);
		}
	}
	return err;
}
int mmc_init(struct mmc *mmc)
{
	int err = IN_PROGRESS, i;
	unsigned start;
​
	if (mmc->has_init)
		return 0;
	start = get_timer(0);
​
	if (!mmc->init_in_progress)
		err = mmc_start_init(mmc);
	if (!err || err == IN_PROGRESS)
		err = mmc_complete_init(mmc);
	debug("%s: %d, time %lu\n", __func__, err, get_timer(start));
	if (err)
		return err;
	printf("[%s] mmc init success\n", __func__);
	if (mmc->block_dev.dev == CONFIG_SYS_MMC_ENV_DEV)  {
		device_boot_flag = EMMC_BOOT_FLAG;
		secure_storage_set_info(STORAGE_DEV_EMMC);
	}
	return err;
}

Nothing new below. IDA got a bit confused with the timeout comparison.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl sd_send_op_cond(struct mmc *mmc)
{
	int err;
	signed int timeout; // r5
	struct mmc *lmmc; // r4
	_BOOL1 v3, v4;
	uint32_t v5; // r0
	struct mmc_cmd cmd; // [sp+0h] [bp-40h]
​
	timeout = 1000;
	lmmc = mmc;
	while ( 1 )
	{
		cmd.cmdidx = 55;
		cmd.resp_type = 21;
		cmd.cmdarg = 0;
​
		err = mmc_send_cmd(lmmc, &cmd, 0);
		if ( err )
			return err;
​
		cmd.cmdidx = 41;
		cmd.resp_type = 1;
​
		cmd.cmdarg = lmmc->cfg->voltages & 0xFF8000;
		if ( lmmc->version == SD_VERSION_2 )
			cmd.cmdarg |= 0x40000000u;
​
		err = mmc_send_cmd(lmmc, &cmd, 0);
		if ( err )
			return err;
​
		if ( (cmd.response[0] & 0x80000000) != 0 )
			break;
		v3 = timeout == 0;
		v4 = timeout-- < 0;
		if ( (unsigned __int8)v4 | (unsigned __int8)v3 )
			return -17;
		usleep(1000);
	}
	if ( lmmc->version != SD_VERSION_2 )
		lmmc->version = SD_VERSION_1_0;
	v5 = cmd.response[0];
	lmmc->ocr = cmd.response[0];
	lmmc->high_capacity = (v5 >> 30) & 1;
	lmmc->rca = 0;
​
	return 0;
}
static int sd_send_op_cond(struct mmc *mmc)
{
	int timeout = 1000;
	int err;
	struct mmc_cmd cmd;
​
	do {
		cmd.cmdidx = MMC_CMD_APP_CMD;
		cmd.resp_type = MMC_RSP_R1;
		cmd.cmdarg = 0;
​
		err = mmc_send_cmd(mmc, &cmd, NULL);
		if (err)
			return err;
​
		cmd.cmdidx = SD_CMD_APP_SEND_OP_COND;
		cmd.resp_type = MMC_RSP_R3;
​
		/*
		 * Most cards do not answer if some reserved bits
		 * in the ocr are set. However, Some controller
		 * can set bit 7 (reserved for low voltages), but
		 * how to manage low voltages SD card is not yet
		 * specified.
		 */
		cmd.cmdarg = mmc_host_is_spi(mmc) ? 0 :
			(mmc->cfg->voltages & 0xff8000);
		if (mmc->version == SD_VERSION_2)
			cmd.cmdarg |= OCR_HCS;
​
		err = mmc_send_cmd(mmc, &cmd, NULL);
		if (err)
			return err;
​
		udelay(1000);
	} while ((!(cmd.response[0] & OCR_BUSY)) && timeout--);
​
	if (timeout <= 0)
		return UNUSABLE_ERR;
​
	if (mmc->version != SD_VERSION_2)
		mmc->version = SD_VERSION_1_0;
​
	if (mmc_host_is_spi(mmc)) { /* read OCR for spi */
		cmd.cmdidx = MMC_CMD_SPI_READ_OCR;
		cmd.resp_type = MMC_RSP_R3;
		cmd.cmdarg = 0;
​
		err = mmc_send_cmd(mmc, &cmd, NULL);
​
		if (err)
			return err;
	}
​
	mmc->ocr = cmd.response[0];
	mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS);
	mmc->rca = 0;
​
	return 0;
}

We're getting close to an end. Not much to see here, conditionals are optimized towards the end, but it is carbon copy of the u-boot code. This time even the buffer is allocated on a stack, as in original.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_change_freq(struct mmc *mmc)
{
	struct mmc *lmmc; // r4
	int err; // r0
	char cardtype; // r5
	int v4, v5;
	uint8_t ext_csd[512]; // [sp+0h] [bp-210h]
​
	lmmc = mmc;
	mmc->card_caps = 0;
​
	if ( mmc->version < MMC_VERSION_4 )
		return 0;
​
	mmc->card_caps = 12;
	err = mmc_send_ext_csd(mmc, ext_csd);
	if ( err )
		return err;
​
	cardtype = ext_csd[196] & 0xF;
​
	err = mmc_switch(lmmc, 1u, 0xB9u, 1u);
	if ( err ) {
		if ( err != -20 )
			return err;
		return 0;
	}
​
	err = mmc_send_ext_csd(lmmc, ext_csd);
	if ( err )
		return err;
​
	err = ext_csd[185];
	if ( ext_csd[185] )
	{
		if ( cardtype & 2 )
		{
			v4 = lmmc->card_caps;
			if ( cardtype & 4 )
			{
				v4 |= 0x20u;
				lmmc->card_caps = v4;
			}
			v5 = v4 | 3;
		}
		else
		{
			v5 = lmmc->card_caps | 1;
		}
		lmmc->card_caps = v5;
		return 0;
	}
	return err;
}
static int mmc_change_freq(struct mmc *mmc)
{
	ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
	char cardtype;
	int err;
​
	mmc->card_caps = MMC_MODE_4BIT | MMC_MODE_8BIT;
​
	if (mmc_host_is_spi(mmc))
		return 0;
​
	/* Only version 4 supports high-speed */
	if (mmc->version < MMC_VERSION_4)
		return 0;
​
	err = mmc_send_ext_csd(mmc, ext_csd);
	if (err)
		return err;
​
	cardtype = ext_csd[EXT_CSD_CARD_TYPE] & 0xf;
​
	err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 1);
	if (err)
		return err == SWITCH_ERR ? 0 : err;
​
	/* Now check to see that it worked */
	err = mmc_send_ext_csd(mmc, ext_csd);
	if (err)
		return err;
​
	/* No high-speed support */
	if (!ext_csd[EXT_CSD_HS_TIMING])
		return 0;
​
	/* High Speed is set, there are two types: 52MHz and 26MHz */
	if (cardtype & EXT_CSD_CARD_TYPE_52) {
		if (cardtype & EXT_CSD_CARD_TYPE_DDR_1_8V)
			mmc->card_caps |= MMC_MODE_DDR_52MHz;
		mmc->card_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
	} else {
		mmc->card_caps |= MMC_MODE_HS;
	}
​
	return 0;
}

Now two short ones.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
void __cdecl mmc_set_ios(struct mmc *mmc)
{
	void (*set_ios)(struct mmc *); // r1
	set_ios = mmc->cfg->ops->set_ios;
	if ( set_ios )
		set_ios(mmc);
}
static void mmc_set_ios(struct mmc *mmc)
{
	if (mmc->cfg->ops->set_ios)
		mmc->cfg->ops->set_ios(mmc);
}
wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
void __cdecl mmc_set_bus_width(struct mmc *mmc,
    		uint32_t width)
{
	mmc->bus_width = width;
	mmc_set_ios(mmc);
}
void mmc_set_bus_width(struct mmc *mmc,
    		uint width)
{
	mmc->bus_width = width;
​
	mmc_set_ios(mmc);
}

And the last exercise in inlining.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
int __cdecl mmc_complete_init(struct mmc *mmc)
{
	struct mmc *v1; // r4
	int v2, v3, v4, v5, v6, v8, v9, v10, err;
​
	mmc->init_in_progress = 0;
	v1 = mmc;
	if ( mmc->op_cond_pending )
	{
		/* inline: err = mmc_complete_op_cond(mmc) */
		v2 = ms_to_time(600);
		v5 = get_time(v2, v3, v4);
		v1->op_cond_pending = 0;
		v6 = v2 + v5;
		if ( v1->ocr < 0 )
		{
LABEL_10:
			v1->version = 0x40000000;
			v1->high_capacity = ((unsigned int)v1->ocr >> 30) & 1;
			err = 0;
			v1->rca = 1;
		}
		else
		{
			while ( 1 )
			{
				err = mmc_send_op_cond_iter(v1, 1);
				if ( err )
					goto LABEL_11;
				v10 = v1->ocr;
				if ( v10 < 0 )
					goto LABEL_10;
				if ( get_time(v10, v8, v9) - v6 < 0 )
					break;
				usleep(100);
			}
			err = -17;
		}
LABEL_11:
		/* inline ends */
		if ( err )
			goto LABEL_18;
	}
	err = mmc_startup(v1);
	if ( err )
	{
LABEL_18:
		v1->has_init = 0;
		printf_s(7, "mmc_complete_init error:%d\n\r", err);
	}
	else
	{
		v1->has_init = 1;
	}
	return err;
}
static int mmc_complete_init(struct mmc *mmc)
{
	int err = 0;
​
	if (mmc->op_cond_pending)
		err = mmc_complete_op_cond(mmc);
​
	if (!err)
		err = mmc_startup(mmc);
	if (err)
		mmc->has_init = 0;
	else
		mmc->has_init = 1;
	mmc->init_in_progress = 0;
	return err;
}
​
static int mmc_complete_op_cond(struct mmc *mmc)
{
	struct mmc_cmd cmd;
	int timeout = 1000;
	uint start;
	int err;
​
	mmc->op_cond_pending = 0;
	start = get_timer(0);
	do {
		err = mmc_send_op_cond_iter(mmc, &cmd, 1);
		if (err)
			return err;
		if (get_timer(start) > timeout)
			return UNUSABLE_ERR;
		udelay(100);
	} while (!(mmc->op_cond_response & OCR_BUSY));
​
	if (mmc_host_is_spi(mmc)) { /* read OCR for spi */
		cmd.cmdidx = MMC_CMD_SPI_READ_OCR;
		cmd.resp_type = MMC_RSP_R3;
		cmd.cmdarg = 0;
​
		err = mmc_send_cmd(mmc, &cmd, NULL);
​
		if (err)
			return err;
	}
​
	mmc->version = MMC_VERSION_UNKNOWN;
	mmc->ocr = cmd.response[0];
​
	mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS);
	mmc->rca = 1;
​
	return 0;
}

That should be enough to prove the GPL contamination.

Structs comparison

A compiled binary without symbols has no concept of structs; but areas of memory are often accessed by a pointer to some beginning address and additional offsets. This is how we can recognize that a global variable, or parameter passed to a function, should be a pointer to a struct or class. Offsets and size of data accessed allow us to re-create such struct; in this case, names of fields were taken from u-boot as they clearly come from it. Comparisons of the functions above us these structs, and specific fields were named after their use in functions.

You will notice some additional fields in the original u-boot code; these were either removed from wm220_0306, or an older version of u-boot was the base, and it didn't had these fields yet. Also note some padding which makes 32-bit fields start at an offset which is multiplication of 4 bytes - this padding is added by the compiler.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
struct mmc {
  char pdev;
  char field_1[3];
  struct mmc_config *cfg;
  int version;
  void *priv;
  int has_init;
  int high_capacity;
  int bus_width;
  int clock;
  int card_caps;
  int ocr;
  int dsr;
  int dsr_imp;
  int scr[2];
  int csd[4];
  int cid[4];
  __int16 rca;
  char part_support;
  char part_attr;
  char wr_rel_set;
  char part_config;
  char part_num;
  char field_5F;
  int tran_speed;
  int read_bl_len;
  int write_bl_len;
  int erase_grp_size;
  int hc_wp_grp_size;
  int ssr;
  char field_78[8];
  uint64_t capacity_user;
  uint64_t capacity_boot;
  uint64_t capacity_rpmb;
  char field_98[8];
  int field_A0;
  char field_A4[24];
  char field_BC[12];
  char op_cond_pending;
  char init_in_progress;
  char preinit;
  char field_CB;
  int ddr_mode;
};
​
struct mmc {
	struct list_head link;
	const struct mmc_config *cfg;	/* provided configuration */
	struct clock_lay_t clk_lay;
	uint version;
	void *priv;
	uint has_init;
	int high_capacity;
	uint bus_width;
	uint clock;
	uint card_caps;
	uint ocr;
	uint dsr;
	uint dsr_imp;
	uint scr[2];
	uint csd[4];
	uint cid[4];
	ushort rca;
	char part_config;
	char part_num;
	uint tran_speed;
	u8 part_support;
	u8 part_attr;
	u8 wr_rel_set;
	uint read_bl_len;
	uint write_bl_len;
	uint erase_grp_size;
	uint dev_lifetime_est_typ_a;
	uint dev_lifetime_est_typ_b;
	u64 capacity;
	u64 capacity_user;
	u64 capacity_boot;
	u64 capacity_rpmb;
	u64 capacity_gp[4];
	u64 boot_size;
	block_dev_desc_t block_dev;
	char op_cond_pending;	/* 1 if we are waiting on an op_cond command */
	char init_in_progress;	/* 1 if we have done mmc_start_init() */
	char preinit;		/* start init as early as possible */
	uint op_cond_response;	/* the response byte from the last op_cond */
	int ddr_mode;
	unsigned char calout[20][20];
	int refix;
	int fixdiv;
	uint hc_wp_grp_size;	/* in 512-byte sectors */
};

Now a few smaller structs. Not much more to comment here.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
struct mmc_cmd {
  uint16_t cmdidx;
  __int16 padding1;
  uint32_t resp_type;
  uint32_t cmdarg;
  uint32_t response[4];
};
struct mmc_cmd {
	ushort cmdidx;
	uint resp_type;
	uint cmdarg;
	uint response[4];
};

IDA Pro does support unions; but size of the unioned type here wouldn't allow IDA to sepect proper member ayway, so the first entry was not defined as union.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
struct mmc_data {
  char *src_dest;
​
  int field_4;
  int blocks;
  int blocksize;
  int flags;
};
struct mmc_data {
	union {
		char *dest;
		const char *src; /* src buffers don't get written to */
	};
	uint flags;
	uint blocks;
	uint blocksize;
};

This struct in binary seem to also be smaller. Either that, or the additional entries are just unused.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
struct mmc_ops {
	int (__cdecl *send_cmd)(struct mmc *mmc,
			struct mmc_cmd *cmd,
			struct mmc_data *data);
	void (__cdecl *set_ios)(struct mmc *mmc);
	void (__fastcall *init)(struct mmc *);
	int (__cdecl *getcd)(struct mmc *mmc);
	int (__cdecl *getwp)(struct mmc *mmc);
};
​
struct mmc_ops {
	int (*send_cmd)(struct mmc *mmc,
			struct mmc_cmd *cmd,
			struct mmc_data *data);
	void (*set_ios)(struct mmc *mmc);
	int (*init)(struct mmc *mmc);
	int (*getcd)(struct mmc *mmc);
	int (*getwp)(struct mmc *mmc);
	int (*calibration)(struct mmc *mmc);
	int (*refix)(struct mmc *mmc);
	int (*calc)(struct mmc *mmc);
};

And here we have a perfect match.

wm220_0306 v03.02.35.05 u-boot Vim3-pie-V190704
struct mmc_config {
	const char *name;
	const struct mmc_ops *ops;
	int host_caps;
	int voltages;
	int f_min;
	int f_max;
	int b_max;
	uint8_t part_type;
};
struct mmc_config {
	const char *name;
	const struct mmc_ops *ops;
	uint host_caps;
	uint voltages;
	uint f_min;
	uint f_max;
	uint b_max;
	unsigned char part_type;
};

How to DIY

The above comparisons can be easily re-created, though this requires a commercial tool, IDA Pro Advanced. Without IDA, assembly can be compared, or pseudo-code generated with a different tool.

Steps:

1. Download firmware file

The file you want is V01.03.0900_Mavic_dji_system.bin. You can use DankDroneDownloader, or any other means to get that version.

2. Extract the firmware package

tar -xf V01.03.0900_Mavic_dji_system.bin

Inside, there's wm220_0306_v03.02.35.05_20170525.pro.fw.sig. That's the only file we will need.

3. Prepare dji-firmware-tools

Download dji-firmware-tools. Install Python3, and libraries required for it. You will find some installation instructions in Issues tab of the project, ie info on installing it on MinGW.

4. Un-sign and decrypt the binary

./dji_imah_fwsig.py -vv -u -i wm220_0306_v03.02.35.05_20170525.pro.fw.sig
./dji_mvfc_fwpak.py dec -i wm220_0306_v03.02.35.05_20170525.pro.fw_0306.bin

You should now have a file 'wm220_0306_v03.02.35.05_20170525.pro.fw_0306.decrypted.bin. To confirm that decryption worked, you can use:

strings -n10 wm220_0306_v03.02.35.05_20170525.pro.fw_0306.decrypted.bin

This should result in printing quite a lot of readable function names and text from the binary. If instead there is no text which makes readable words, then your decryption failed.

5. Convert the plain binary back to ELF format

./arm_bin2elf.py -vvv -e -b 0x420000 --section .ARM.exidx@0x01265E0:0 --section .bss@0x1ffe0000:0x60100 \
 --section .bss2@0x3fcc0000:0x2000 -p wm220_0306_v03.02.35.05_20170525.pro.fw_0306.decrypted.bin

The ELF format is easier to use within disassemblers, so we're wrapping the binary with ELF header.

6. Create IDA project

Load the ELF file into IDA Pro. It won't ask you many questions, as ELF header has all the data it needs.

When IDA finishes its thing, use File -> Script file.., and select symbols/wm220_0306_v03.02.35.05_20170525.pro.idc from the dji-firmware-tools project folder. This file contains re-created symbols from the binary - function names, variable names, struct definitions etc. If you're using a different disassembler tool than IDS, this step will require more work from you - you have to somehow convert that IDA-specific file to a format which your tools can read. You can also use wm220_0306_v03.02.35.05_20170525.pro.map which is also in that folder - it doesn't contain type definitions and function arguments, but it at least stores function and global names in a universal format.

7. Start comparing

That's it. Find the function you want to check, and compare it to GPL projects.

Scope of the GPL taint

The u-boot internals were use within a monolithic firmware binary. External interface exception from u-boot binary does not apply here - that external interface was not even included in the compiled code from DJI.

The fact that whole FC code is statically linked into a single binary, makes the scope of GPL taint to be the whole FC code.

Ending thoughts

Chinese companies have a strong tendency for copying existing solutions. In that area, chinese engineers seem to have different phisosophy than western ones. Their focus seem to be on copying from the most successful, while western world puts higher pressure on creative thinking and preparing solutions for your own.

Companies which are entering global market must be prepared to be treated equally as western ones. GPL contamination is an important issue for every international corporation. They coach their developers, send them to courses, pay for software which automatically parses sources in search of GPL signatures.

In the area of open-source, DJI seem to be focusing on marketing rather than real compliance. This will need to change. DJI hoped that the double AES encryption they used will hide the code inside well enough. Now their Flight Controller code is forever marked with the GPL violation.

@mingtaoxin
Copy link

Great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment