Scientific progress goes "Boink"?

Xilinx SDK: Bare Metal Program Crashes when Changing SD Cards

In the post ZYNQ: Read a WAV File from SD-Card and Play it on the Audio Codec we started using the SD card. Everything worked fine as long as I didn’t change the SD cards.

I found that it is related to the fact that I was switching between a regular (ancient) SD card and an SDHC card. I suspect that all modern SD cards are probably SDHC cards so most people will not notice the problem. When I found the root cause, I concluded that this is a bug. Luckily there is an easy way to fix it.

I am using Vivado/SDK 2017.2 and the xilffs library 3.6.

The Symptom

  • I used two SD cards: One was a regular SD card. The other one was an SDHC card.
  • When inserting either SD card and then starting the software both cards worked fine.
  • When I switched to the other card and called f_mount(), the software always got stuck and never returned from f_mount().

The Root Cause

The first problem was related to the fact that disk_initialize()  tries to be too smart. It is supposed to call the functions XSdPs_CfgInitialize() and XSdPs_CardInitialize() which initialize the SD card drivers and check what kind of SD card is inserted. In particular, XSdPs_CardInitialize() will find out if it is dealing with an SDHC card.  However disk_initialize()  will check if it  has been initialized before. And if yes, it won’t call XSdPs_CardInitialize() again. That’s unfortunate since the drivers won’t get the chance to change from SDHC to SD mode or vice versa.

Here is the original code:

s = disk_status(pdrv);
/* Check if card is in the socket */
if ((s & STA_NODISK) != 0U) {
	return s;
}
/* If disk is already initialized */
if ((s & STA_NOINIT) == 0U) {
    return s;
}

The status is stored in a global variable names “Stat[]”. Setting Stat[] to its initial value should force the driver to run the initialization again. So I added this code:

Stat[pdrv] = STA_NOINIT;

Now things worked a lot better. I could switch from the SD card to the SDHC card. But not the other way around. It turns out that the driver remembered if it has used an SDHC card before and would never go back to the regular mode.

The problem lies in XSdPs_CfgInitialize() which does not properly initialize its instance memory. XSdPs_CfgInitialize() does not initialize the flag SdInstance[pdrv].HCS. After a fresh start, that value is normally set to 0 (although I am not sure if that is guaranteed). In XSdPs_CardInitialize(), the flag will be set but never cleared. So it is essential to start with an erased instance memory. This line eventually did the trick:

memset(&SdInstance[pdrv],0,sizeof(SdInstance[0]));

This line sets the driver instance to a known state.

The Solution

Of course we could directly edit diskio.c. However, these changes will be overwritten next time we regenerate the Board Support Project. If we change diskio.c in the SDK installation directory, our changes will be permanent (until we upgrade to the next SDK version of course). The file is located in

c:/Xilinx/SDK/2017.2/data/embeddedsw/lib/sw_services/xilffs_v3_6/src/diskio.c

Here is the complete function including my changes:

DSTATUS disk_initialize (
		BYTE pdrv	/* Physical drive number (0) */
)
{
	DSTATUS s;
	s32 Status;

	// 2018/01/13 Harald Rosenfeldt
	// Force new initialization. Also force SdInstance to be 
	// erased since XSdPs_CfgInitialize() forgets
	// to initialize SdInstance[pdrv].HCS
	Stat[pdrv] = STA_NOINIT;
	memset(&SdInstance[pdrv],0,sizeof(SdInstance[0]));
	
#ifdef FILE_SYSTEM_INTERFACE_SD

	XSdPs_Config *SdConfig;

	/*
	 * Check if card is in the socket
	 */
	s = disk_status(pdrv);
	if ((s & STA_NODISK) != 0U) {
		return s;
	}

	/* If disk is already initialized */
	if ((s & STA_NOINIT) == 0U) {
		return s;
	}

	if (CardDetect) {
			/*
			 * Card detection check
			 * If the HC detects the No Card State, power will be cleared
			 */
			while(!((XSDPS_PSR_CARD_DPL_MASK |
					XSDPS_PSR_CARD_STABLE_MASK |
					XSDPS_PSR_CARD_INSRT_MASK) ==
					( XSdPs_GetPresentStatusReg((u32)BaseAddress) &
					(XSDPS_PSR_CARD_DPL_MASK |
					XSDPS_PSR_CARD_STABLE_MASK |
					XSDPS_PSR_CARD_INSRT_MASK))));
	}

	/*
	 * Initialize the host controller
	 */
	SdConfig = XSdPs_LookupConfig((u16)pdrv);
	if (NULL == SdConfig) {
		s |= STA_NOINIT;
		return s;
	}

	Status = XSdPs_CfgInitialize(&SdInstance[pdrv], SdConfig,
					SdConfig->BaseAddress);
	if (Status != XST_SUCCESS) {
		s |= STA_NOINIT;
		return s;
	}

	Status = XSdPs_CardInitialize(&SdInstance[pdrv]);
	if (Status != XST_SUCCESS) {
		s |= STA_NOINIT;
		return s;
	}


	/*
	 * Disk is initialized.
	 * Store the same in Stat.
	 */
	s &= (~STA_NOINIT);

	Stat[pdrv] = s;

#endif

	return s;
}

After changing this file, right-click on your BSP project and select Re-Generate BSP Sources:

Meanwhile Vivado 2017.4 is out. If I have time, I will check if the problem has been solved meanwhile.

Leave a comment

One thought on “Xilinx SDK: Bare Metal Program Crashes when Changing SD Cards”