问题由来:之前做过的一款Android手机,在系统启动或者运行过程中,会造成死机的问题;经过大量的测试,发现这是eMMC Firmware的一个bug,但是此时,已经有一大批机器出货,所以解决方案也只能通过Offline eMMC firmware更新来解决。
方案选择:Offline eMMC firmware更新,是指eMMC chip已经安装到PCBA板上,这时更新,只有通过SDIO interface来更新;当时考虑在2个地方开始做更新的动作,一是bootloader, 二是Linux Kernel中eMMC初始化的地方。通过比较,最后选择了方案二。
eMMC Firmware更新的主要步骤,
1. 向eMMC controller写入eMMC Firmware更新的密码,默认情况下,eMMC Firmware是不允许更新的。
1.1, make sure it entered transfer state
1.2 write the "mmc FW update password" to controller
1.3, read back and check the FW update response
2. 向eMMC controller写入eMMCFirmware内容。
2.1, program the Toshiba mmc FW
2.2, read back the Toshiba mmc FW
3. Assert CMD0, 使eMMC进入idle状态。
4. 重启device。
示例代码路径如下,
/drivers/mmc/core/mmc_ops.c
可以在 mmc_init_card()函数内,开始check eMMC Firmware version, 并决定是否进行Firmware更新。
Firmware更新的函数是“toshiba_mmc_fw_update”。
用到的CMD及其含义,
主要示例代码如下,
+/* funtion description: Toshba mmc FW update
+ * paraters:
+ * card: the Toshiba card
+ * return value: 0 for OK; others for error number.
+ * author: Guangwei Jiang
+ * date:2014-12-25
+*/
+int toshiba_mmc_fw_update(struct mmc_card *card)
+{
+ int err = -1;
+ void *buf;
+ int status;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ // step1.1, make sure it entered transfer state
+ err = mmc_send_status(card, &status);
+ if (err)
+ {
+ pr_err("Toshiba mmc fw update failed (mmc send status failed)!\n");
+ return err;
+ }
+ if (R1_CURRENT_STATE(status) != R1_STATE_TRAN)
+ {
+ pr_err("MMC not in the transfer state, exit the FW update\n");
+ return -1;
+ }
+
+ // step1.2, write the "mmc FW update password" to controller
+ buf = kmalloc(TOSHIBA_MMC_UNLOCK_KEY_LENGTH, GFP_KERNEL);
+ if (buf == NULL)
+ {
+ pr_err("Toshiba mmc fw update failed (memory allocate failed)!\n");
+ return -ENOMEM;
+ }
+
+ memcpy(buf, toshiba_fw_update_unlock_key, TOSHIBA_MMC_UNLOCK_KEY_LENGTH);
+
+ err = mmc_gen_cmd(card, buf, 1); // write the FW update command by CMD56
+ if (err)
+ {
+ pr_err("Toshiba mmc fw update failed (mmc unlock key write failed)!\n");
+ return err;
+ }
+
+ // step1.3, read back and check the FW update response
+ memset(buf, 0, TOSHIBA_MMC_UNLOCK_KEY_LENGTH);
+
+ err = mmc_gen_cmd(card, buf, 0); // read out the status by CMD56
+ printk("mmc_gen_cmd(read), err = %d\n", err);
+ if (err)
+ {
+ pr_err("Toshiba mmc fw update failed (mmc unlock key read back failed)!\n");
+ goto _DEVICE_REBOOT;
+ }
+ else
+ {
+ // If bit0 is 0x01 (sub command id), and bit3 is 0xA0, then mmc unlock sucessfully
+ if ((*(u8 *)(buf+0) == 0x01 )&&(*(u8 *)(buf+3) == 0xA0))
+ {
+ pr_info("Toshiba mmc unlock sucessfully, will do FW program next step\n");
+ }
+ else
+ {
+ pr_err("Toshiba mmc fw update failed (mmc unlock response code error)!\n");
+ goto _DEVICE_REBOOT;
+ }
+ }
+
+ if (buf)
+ kfree(buf);
+
+ // step2.1, program the Toshiba mmc FW
+ buf = kmalloc(TOSHIBA_MMC_FW_LENGTH, GFP_KERNEL);
+ if (buf == NULL)
+ {
+ pr_err("Toshiba mmc fw update failed (memory allocate failed)!\n");
+ goto _DEVICE_REBOOT;
+ }
+
+ memcpy(buf, toshiba_19n_fwdata_16GB_016G92, TOSHIBA_MMC_FW_LENGTH);
+
+ err = mmc_fw_multi_block_rw(card, buf, 1); // program the MMC FW
+ if (err)
+ {
+ pr_err("Toshiba mmc fw update failed (mmc fw program failed)!\n");
+ goto _DEVICE_REBOOT;
+ }
+
+ // step2.2, read back the Toshiba mmc FW
+ /*memset(buf, 0, TOSHIBA_MMC_FW_LENGTH);
+
+ err = mmc_fw_multi_block_rw(card, buf, 0); // read back the MMC FW
+ if (err)
+ {
+ pr_err("Toshiba mmc fw update failed(mmc fw read back failed)!\n");
+ goto _DEVICE_REBOOT;
+ }*/
+
+ if (buf)
+ kfree(buf);
+
+ // step3.1, Assert CMD0
+ mmc_go_idle(card->host);
+
+ pr_info("Toshiba mmc fw update sucessfully\n");
+
+_DEVICE_REBOOT:
+ pr_info("Device will reboot in 2 senconds...\n");
+ mdelay(2000);
+ emergency_restart();
+ return err;
+}+/* funtion description: Read and write by CMD56 (MMC_GEN_CMD)
+ * paraters:
+ * card: mmc card type
+ * buf: for read and write purpose, the size is 512B;
+ * write: 1 for write; 0 for read; Don't use other number.
+ * return value: 0 for OK; others for error number.
+ * author: Guangwei Jiang
+ * date:2014-12-18
+*/
+int mmc_gen_cmd(struct mmc_card *card, void *buf, int write)
+{
+ struct mmc_request mrq;
+ struct mmc_command cmd;
+ struct mmc_data data;
+ struct mmc_command stop;
+ struct scatterlist sg;
+ void *data_buf;
+ unsigned long timeout;
+ u32 status;
+ int err;
+ //int i;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ mmc_set_blocklen(card, 512);
+
+ data_buf = kmalloc(512, GFP_KERNEL);
+ if (data_buf == NULL)
+ return -ENOMEM;
+
+ if (write != 0)
+ memcpy(data_buf, buf, 512);
+
+ memset(&mrq, 0, sizeof(struct mmc_request));
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ memset(&data, 0, sizeof(struct mmc_data));
+ memset(&stop, 0, sizeof(struct mmc_command));
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+ mrq.stop = NULL;
+
+ cmd.opcode = MMC_GEN_CMD;
+ cmd.arg = write? 0x00: 0x01;
+
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = 512;
+ data.blocks = 1;
+ data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ /*stop.opcode = MMC_STOP_TRANSMISSION;
+ stop.arg = 0;
+ stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;*/
+
+ sg_init_one(&sg, data_buf, 512);
+
+ mmc_set_data_timeout(&data, card);
+
+ mmc_claim_host(card->host);
+ mmc_wait_for_req(card->host, &mrq);
+ mmc_release_host(card->host);
+
+ /* Must check status to be sure of no errors */
+ timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);
+ do {
+ err = mmc_send_status(card, &status);
+ if (err)
+ return err;
+ if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
+ break;
+ if (mmc_host_is_spi(card->host))
+ break;
+
+ /* Timeout if the device never leaves the program state. */
+ if (time_after(jiffies, timeout)) {
+ pr_err("%s: Card stuck in programming state! %s\n",
+ mmc_hostname(card->host), __func__);
+ return -ETIMEDOUT;
+ }
+ } while (R1_CURRENT_STATE(status) == R1_STATE_PRG);
+
+ if (write == 0)
+ {
+ memcpy(buf, data_buf, 512);
+ }
+ kfree(data_buf);
+
+ /*if (write == 0)
+ pr_info("\n\nCMD56 read content as below:\n");
+ else
+ pr_info("\n\nCMD56 write content as below:\n");
+ for (i = 0; i < 512; i++)
+ {
+ printk("0x%2x ", *(u8 *)(buf+i));
+
+ if ((i+1)%16 == 0)
+ printk("\n");
+ }*/
+
+
+ if (cmd.error)
+ {
+ pr_err("%s, cmd error, error code %d\n", __func__, cmd.error);
+ return cmd.error;
+ }
+ if (data.error)
+ {
+ pr_err("%s, data error, error code %d\n", __func__, data.error);
+ return data.error;
+ }
+ if (stop.error)
+ {
+ pr_err("%s, stop error, error code %d\n", __func__, stop.error);
+ return stop.error;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mmc_gen_cmd);+/* funtion description: Read and write eMMC FW by command
+ * MMC_READ_MULTIPLE_BLOCK(CMD18)/MMC_WRITE_MULTIPLE_BLOCK(CMD25)
+ * paraters:
+ * card: mmc card type
+ * buf: for read and write purpose, the size is defined by TOSHIBA_MMC_FW_LENGTH;
+ * write: 1 for write; 0 for read; Don't use other number.
+ * return value: 0 for OK; others for error number.
+ * author: Guangwei Jiang
+ * date:2014-12-19
+*/
+int mmc_fw_multi_block_rw(struct mmc_card *card, void *buf, int write)
+{
+ struct mmc_request mrq;
+ struct mmc_command cmd;
+ struct mmc_data data;
+ struct mmc_command stop;
+ struct scatterlist sg;
+ void *data_buf;
+ unsigned long timeout;
+ u32 status;
+ int err;
+ //int i;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ mmc_set_blocklen(card, 512);
+
+ data_buf = kmalloc(TOSHIBA_MMC_FW_LENGTH, GFP_KERNEL);
+ if (data_buf == NULL)
+ return -ENOMEM;
+
+ if (write != 0)
+ memcpy(data_buf, buf, TOSHIBA_MMC_FW_LENGTH);
+
+ memset(&mrq, 0, sizeof(struct mmc_request));
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ memset(&data, 0, sizeof(struct mmc_data));
+ memset(&stop, 0, sizeof(struct mmc_command));
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+ mrq.stop = &stop;
+
+ cmd.opcode = write? MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
+ cmd.arg = 0x00;
+
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = 512;
+ data.blocks = TOSHIBA_MMC_FW_LENGTH/512;
+ data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ stop.opcode = MMC_STOP_TRANSMISSION;
+ stop.arg = 0;
+ stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+
+ sg_init_one(&sg, data_buf, TOSHIBA_MMC_FW_LENGTH);
+
+ mmc_set_data_timeout(&data, card);
+
+ mmc_claim_host(card->host);
+ mmc_wait_for_req(card->host, &mrq);
+ mmc_release_host(card->host);
+
+ /* Must check status to be sure of no errors */
+ timeout = jiffies + msecs_to_jiffies(MMC_OPS_TIMEOUT_MS);
+ do {
+ err = mmc_send_status(card, &status);
+ if (err)
+ return err;
+ if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
+ break;
+ if (mmc_host_is_spi(card->host))
+ break;
+
+ /* Timeout if the device never leaves the program state. */
+ if (time_after(jiffies, timeout)) {
+ pr_err("%s: Card stuck in programming state! %s\n",
+ mmc_hostname(card->host), __func__);
+ return -ETIMEDOUT;
+ }
+ } while (R1_CURRENT_STATE(status) == R1_STATE_PRG);
+
+
+ if (write == 0)
+ {
+ memcpy(buf, data_buf, TOSHIBA_MMC_FW_LENGTH);
+ }
+ kfree(data_buf);
+
+ /*if (write == 0)
+ {
+ pr_info("\n\nToshiba emmc fw read content as below:\n");
+ for (i = 0; i < TOSHIBA_MMC_FW_LENGTH; i++)
+ {
+ printk("0x%2x ", *(u8 *)(buf+i));
+
+ if ((i+1)%16 == 0)
+ printk("\n");
+ }
+ }
+ else
+ {
+ printk("\n\nToshiba emmc fw write content as below:\n");
+ for (i = 0; i < TOSHIBA_MMC_FW_LENGTH; i++)
+ {
+ printk("0x%2x ", *(u8 *)(buf+i));
+
+ if ((i+1)%16 == 0)
+ printk("\n");
+ }
+ }*/
+
+ if (cmd.error)
+ {
+ pr_err("%s, cmd error, error code %d\n", __func__, cmd.error);
+ return cmd.error;
+ }
+ if (data.error)
+ {
+ pr_err("%s, data error, error code %d\n", __func__, data.error);
+ return data.error;
+ }
+ if (stop.error)
+ {
+ pr_err("%s, stop error, error code %d\n", __func__, stop.error);
+ return stop.error;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mmc_fw_multi_block_rw);