You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
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;
}
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Great!