天天看點

eMMC Firmware Upgrade

問題由來:之前做過的一款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及其含義,

eMMC Firmware Upgrade
eMMC Firmware Upgrade

主要示例代碼如下,

+/* 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);