移植u-boot-2019.10到jz2440——修改程序以支持NorFlash

前言

本文屬於移植u-boot-2019.10到jz2440的一部分,主要記錄如何修改u-boot-2019.10的源碼從而支持jz2440所選用的NorFlash。

1 NorFlash簡單介紹

這裏不會介紹NorFlash的集成電路層面的原理(事實上我也不知道),我會把視角集中在作爲一個嵌入式軟件開發者,應該怎麼去看NorFlash。不妨把NorFlash抽象成一個盒子,這個盒子有地址線和數據線,且支持隨機讀,但不支持隨機寫。如果要寫NorFlash,需要先擦除,再通過特定的操作序列,才能完成對某個地址的寫。正是因爲支持隨機讀,NorFlash纔可以支持XIP(片上執行),進而u-boot才能夠從NorFlash啓動。而上述所謂的操作序列,指的是由往某個地址寫某個數據的操作爲元素組成的序列。

之所以需要在寫之前先擦除,這是Flash的特性決定的,即只能每個bit從1置爲0,而不能從0置爲1。擦除的目的是將NorFlash中的bit位置1,如此就可以寫任何數據了。

2 NorFlash的JEDEC標準和CFI標準

JEDEC指的是Joint Electron Device Engineering Council(電子元件工業聯合會),NorFlash的JEDEC標準則是由這個組織爲NorFlash制定的標準,通過這個標準,我們可以從NorFlash讀取製造商ID和設備ID,而更多的信息比如NorFlash的大小、電氣特性等信息則被記錄在了NorFlash之外,通常這麼來管理這些信息:將軟件需要支持的各種NorFlash的信息結構化,組成一個數組,然後根據從NorFlash中讀取的廠商ID和設備ID來搜索這個數組,找到匹配的數組項,該項就存儲了當前正在使用的NorFlash的各種信息,進而根據這些信息就可以操作NorFlash了。

上述管理NorFlash的做法不夠靈活,當要支持的NorFlash種類非常多時,就要花費不少內存,而且當我們要添加對一種新的NorFlash的支持,就需要擴充上述數組,且不可避免的需要重新編譯軟件。爲了解決這個問題,CFI(Common FlashInterface)就出現了,CFI標準的思路很簡單,由NorFlash自己記錄跟自己有關的信息,比如廠商ID、電氣特性、存儲容量等,並提供統一的讀取這些信息的接口(統一規定一些操作序列,一個操作序列能夠讀一種信息)。這樣一來,我們就不需要維護保存NorFlash信息的數組了,只要支持CFI,則操作NorFlash所需的信息都可以從NorFlash本身按統一規定的接口讀取到。

3 u-boot怎麼獲取用戶NorFlash的信息

用戶對NorFlash的選擇不可預測,而操作NorFlash需要其本身的信息,那麼u-boot是怎麼獲取這些信息的呢,是用上文所說的CFI接口嗎?我們不妨從u-boot對NorFlash的初始化函數initr_flash開始看起,一邊看源碼一邊尋找答案。

3.1 initr_flash

#if defined(CONFIG_MTD_NOR_FLASH) /* 支持NorFlash需要配置CONFIG_MTD_NOR_FLASH */
static int initr_flash(void)
{
	/*
		根據當前的配置去掉了一些不會發揮作用的源碼
	*/
	ulong flash_size = 0;	/* 初始化Flash的大小爲0 */
	bd_t *bd = gd->bd;		/* 獲取在內存中保留的bd_info結構的地址 */

	puts("Flash: ");

	/* 檢測寫保護是否開啓,移植者可以自己實現該函數,否則默認的版本直接返回0 */
	/* 沒有實現該函數,因此條件不成立 */
	if (board_flash_wp_on())
		printf("Uninitialized - Write Protect On\n");
	else
		/* 執行flash_init,該函數返回NorFlash的大小 */
		flash_size = flash_init();

	/* 打印NorFlash的大小(保留小數點後一位) */
	print_size(flash_size, "");
	putc('\n');

	/* 記錄NorFlash的起始地址bd->bi_flashstart = 0x00000000 */
	bd->bi_flashstart = CONFIG_SYS_FLASH_BASE;
	
	/* 記錄NorFlash的大小 */
	bd->bi_flashsize = flash_size;

	/* 記錄存儲在NorFlash中的u-boot鏡像佔了多少空間 */
	bd->bi_flashoffset = monitor_flash_len;

	return 0;
}
#endif

3.2 flash_init

initr_flash設置了bd_info結構中的一些信息,包括NorFlash的起始地址、NorFlash的大小以及NorFlash中的u-boot鏡像佔了多少空間。當然,最重要的是調用了flash_init

unsigned long flash_init(void)
{
	/*
		根據當前的配置去掉了一些不會發揮作用的源碼
	*/
	unsigned long size = 0;
	int i;
	
	/* CONFIG_SYS_MAX_FLASH_BANKS在jz2440.h中被定義爲1,表明僅有一片NorFlash */
	for (i = 0; i < CONFIG_SYS_MAX_FLASH_BANKS; ++i) {
		flash_info[i].flash_id = FLASH_UNKNOWN;

		/* CONFIG_SYS_CFI_FLASH_CONFIG_REGS未配置,該函數爲空 */
		cfi_flash_set_config_reg(cfi_flash_bank_addr(i),
					 cfi_flash_config_reg(i));

		/* 先使用flash_detect_legacy去讀NorFlash的參數並填充flash_info[i] */
		/* 如果flash_detect_legacy沒有成功,再使用flash_get_size做同樣的事情 */
		/* flash_detect_legacy是舊的閃存檢測的方法,保留在這裏可能是出於前向兼容的考慮 */
		if (!flash_detect_legacy(cfi_flash_bank_addr(i), i))
			flash_get_size(cfi_flash_bank_addr(i), i);
		
		/* 經過閃存檢測後,flash_info[i]被正確的填充,因此就可以訪問size成員獲取閃存的尺寸了 */
		size += flash_info[i].size;

		/* 如果閃存檢測失敗則打印提示信息 */
		if (flash_info[i].flash_id == FLASH_UNKNOWN) {
			printf("## Unknown flash on Bank %d ", i + 1);
			printf("- Size = 0x%08lx = %ld MB\n",
			       flash_info[i].size,
			       flash_info[i].size >> 20);
		}
	}

	/*
		根據當前配置,該函數會把u-boot鏡像保護起來(相應的塊只能讀)
		若CONFIG_SYS_FLASH_PROTECTION未配置則採用軟件保護(設標誌位);
		否則使用NorFlash命令進行硬件層面的保護
	*/
	flash_protect_default();

	return (size);
}

3.3 flash_detect_legacy

根據源碼,jz2440.c中提供了board_flash_get_legacy,因此實際使用了舊的閃存檢測接口flash_detect_legacy

static int flash_detect_legacy(phys_addr_t base, int banknum)
{
	flash_info_t *info = &flash_info[banknum];

	/*
		info->portwidth = FLASH_CFI_16BIT
		info->chipwidth = FLASH_CFI_BY16
		info->interface = FLASH_CFI_X16
		board_flash_get_legacy返回1,因此條件滿足
		
		順帶介紹一下NorFlash的portwidth及chipwidth。一般來說,NorFlash的一個地址對應的一個存儲
		單元有多少個bit,就會引出多少條數據線,同時可能提供出WORD/BYTE等模式選擇的引腳。不妨假設有
		一NorFlash,一個存儲單元爲16bit,對外引出了16條數據線,且工作在WORD模式下,那麼如果用2片這
		樣的NorFlash組合使用,共向SoC提供32條數據線,則portwidth是32bits,而chipwidth是16bits.
	*/
	if (board_flash_get_legacy(base, banknum, info)) {
		/* 如果開發板相關程序已經將info結構填充好了,各種信息已經有了,就沒必要繼續了 */
		/* 這裏u-boot使用vendor成員是否被填充來判斷info是否已經填充完成 */
		if (!info->vendor) {
			int modes[] = {
				/* intel標準和AMD標準讀廠家ID和設備ID的操作是有區別的 */
				CFI_CMDSET_AMD_STANDARD,		
				CFI_CMDSET_INTEL_STANDARD
			};
			int i;
			/* 拿兩種標準都進行嘗試 */
			for (i = 0; i < ARRAY_SIZE(modes); i++) {
				info->vendor = modes[i];
				/* info->start[0] = base(0x00000000) */
				info->start[0] = (ulong)map_physmem(base,
							   info->portwidth,
							   MAP_NOCACHE);
				/* 選擇合適的解鎖地址 */
				if (info->portwidth == FLASH_CFI_8BIT &&
				    info->interface == FLASH_CFI_X8X16) {
					info->addr_unlock1 = 0x2AAA;
					info->addr_unlock2 = 0x5555;
				} else {
					/* 實際執行該分支 */
					/* 可能會有讀者疑惑,這裏的地址跟手冊上的555、2AA不太對應 */
					/* 可以追蹤一下flash_read_jedec_ids,可以發現發送的地址要經過flash_map */
					/* 而flash_map會把地址乘以info->portwidth,對於當前源碼就是乘2 */
					/* 乘2的結果是AAAA、5554,考慮到555 * 2 = AAA,2AA * 2 = 554,低位就對應上了 */
					/* 至於爲什麼要乘以2,看原理圖地址線怎麼接的就知道了 */
					info->addr_unlock1 = 0x5555;
					info->addr_unlock2 = 0x2AAA;
				}
				/* 讀JEDEC索引(也就是廠家ID以及設備ID) */
				flash_read_jedec_ids(info);
				debug("JEDEC PROBE: ID %x %x %x\n",
				      info->manufacturer_id,
				      info->device_id,
				      info->device_id2);
				
				/* 從存放各種型號NorFlash信息的數組中查找我們實際使用的那種 */
				/* 查找的依據就是上面的程序讀出來的廠家ID以及設備ID */
				/*
					!!!也就是說要想成功檢測閃存就必須滿足2個條件!!!
					1.flash_read_jedec_ids能夠正確的讀出廠家ID以及設備ID
					2.上述存放各種型號NorFlash信息的數組確實收錄了當前使用的NorFlash
				*/
				if (jedec_flash_match(info, info->start[0]))
					break;

				unmap_physmem((void *)info->start[0],
					      info->portwidth);
			}
		}

		switch (info->vendor) {
		case CFI_CMDSET_INTEL_PROG_REGIONS:
		case CFI_CMDSET_INTEL_STANDARD:
		case CFI_CMDSET_INTEL_EXTENDED:
			info->cmd_reset = FLASH_CMD_RESET;
			break;
		case CFI_CMDSET_AMD_STANDARD:
		case CFI_CMDSET_AMD_EXTENDED:
		case CFI_CMDSET_AMD_LEGACY:
			info->cmd_reset = AMD_CMD_RESET;
			break;
		}
		/*
			設置flash_id
			個人感覺這裏設置flash_id不太妥當,因爲程序執行到這裏不一定意味着檢測閃存成功,
			但上一層的函數flash_init中,會使用flash_id來判斷是否打印Unknown flash on Bank等信息,
			如果沒有檢測成功,比如廠家ID都沒讀到,那應該打印出這些調試信息纔對,而設置了flash_id就不會打印了。
		*/
		info->flash_id = FLASH_MAN_CFI;
		return 1;
	}
	return 0; /* use CFI */
}

3.4 jedec_flash_match

最後看一下jedec_flash_match

int jedec_flash_match(flash_info_t *info, ulong base)
{
	int ret = 0;
	int i;
	ulong mask = 0xFFFF;
	if (info->chipwidth == 1)
		mask = 0xFF;

	for (i = 0; i < ARRAY_SIZE(jedec_table); i++) {
		/* 根據廠家ID和設備ID;來搜索NorFlash信息數組(jedec_table) */
		if ((jedec_table[i].mfr_id & mask) == (info->manufacturer_id & mask) &&
		    (jedec_table[i].dev_id & mask) == (info->device_id & mask)) {
		    /* 如果找到一個匹配的數組項,則用該數組項紀錄的信息來填充info結構 */
			fill_info(info, &jedec_table[i], base);
			/* 查到成功則返回1,否則返回0 */
			ret = 1;
			break;
		}
	}
	return ret;
}

3.5 總結

經過以上分析,我們就可以回答第3節開頭的疑問了。當前配置下,u-boot沒有使用CFI接口讀取用戶NorFlash的信息,而是使用傳統的NorFlash信息數組的方式來存儲信息,使用廠家ID、設備ID來搜索數組,從而得到用戶NorFlash信息。獲取到NorFlash信息之後,就可以用這些信息去操作NorFlash了,操作NorFlash的程序是u-boot做好的,不需要移植者實現。因此只要正確的檢測到閃存就可以使用了,而想要正確的檢測閃存,就必須滿足2個條件,這也是我們的目標——支持NorFlash的關鍵:

  1. flash_read_jedec_ids能夠正確的讀出廠家ID以及設備ID
  2. 存放各種型號NorFlash信息的數組確實收錄了當前使用的NorFlash

4 修改程序以支持jz2440的NorFlash

4.1 配置CONFIG_MTD_NOR_FLASH

jz2440_defconfig中添加一個默認配置項:

CONFIG_MTD_NOR_FLASH=y

4.2 確認廠家ID、設備ID是否讀取正確

cfi_flash.c中打開調試信息:

#define DEBUG

然後編譯燒寫到NorFlash,上電後查看輸出的信息:

JEDEC PROBE: ID c2 2249 0

查看MX29LV160DB(開發板實際使用的是這款NorFlash)的手冊,對比後發現廠家ID、設備ID讀取正確。

4.3 在jedec_table中添加我們使用的NorFlash的信息

首先查看jedec_table中是否存放有MX29LV160DB的信息,查看後發現沒有(這是閃存檢查失敗的原因,開發板輸出的信息Flash: 0 Bytes也驗證了這一點)。因此,我們需要在jedec_table數組(位於jedec_flash.c)中添加一項:

	/* jz2440使用的是MX29LV160DB,1MB x 16 */
#ifdef CONFIG_SYS_FLASH_LEGACY_1Mx16
#define MX29LV160DB AM29LV160DB
	{
		.mfr_id		= (u16)MX_MANUFACT,
		.dev_id		= MX29LV160DB,
		.name		= "MXIC MX29LV160DB",
		.uaddr		= {
			[1] = MTD_UADDR_0x0555_0x02AA 		/* x16 */
		},
		.DevSize	= SIZE_2MiB,				/* NorFlash大小爲2MB */
		.CmdSet		= CFI_CMDSET_AMD_LEGACY,
		.NumEraseRegions = 4,					/* 4種扇區 */
		.regions	= {
			ERASEINFO(0x04000, 1),				/* 16K-Byte x 1 */
			ERASEINFO(0x02000, 2),				/* 8K-Byte x 2 */
			ERASEINFO(0x08000, 1),				/* 32K-Byte x 1 */
			ERASEINFO(0x10000, 31),				/* 64K-Byte x 31 */
		}
	}
#endif

同時在jz2440.c中添加配置項CONFIG_SYS_FLASH_LEGACY_1Mx16,並刪除原來的CONFIG_SYS_FLASH_LEGACY_512Kx16

5 測試

完成上述修改後,編譯燒寫NorFlash,再次上電後,輸出信息:

Flash: fwc addr 00000000 cmd f0 00f0 16bit x 16 bit
fwc addr 0000aaaa cmd aa 00aa 16bit x 16 bit
fwc addr 00005554 cmd 55 0055 16bit x 16 bit
fwc addr 0000aaaa cmd 90 0090 16bit x 16 bit
info->ext_addr = 0x0, cfi_version = 0x0
fwc addr 00000000 cmd f0 00f0 16bit x 16 bit
JEDEC PROBE: ID c2 2249 0
ERROR: too many flash sectors
2 MiB

可見NorFlash的大小已經正確的輸出了,同時打印出了一條錯誤提示ERROR: too many flash sectors,這是因爲jz2440.h中使用配置宏CONFIG_SYS_MAX_FLASH_SECT限定了最大扇區數,將這個數值調大一些(超過35)即可,然後再次編譯燒寫,發現錯誤提示已經沒有了。

接下來我們執行flinfo查看一下NorFlash的一些信息:

Bank # 1: MXIC MX29LV160DB flash (16 x 16)  Size: 2 MB in 35 Sectors
  AMD Legacy command set, Manufacturer ID: 0xC2, Device ID: 0x2249
  Erase timeout: 30000 ms, write timeout: 100 ms

  Sector Start Addresses:
  00000000   RO   00004000   RO   00006000   RO   00008000   RO   00010000   RO 
  00020000   RO   00030000   RO   00040000   RO   00050000        00060000      
  00070000        00080000        00090000        000A0000        000B0000      
  000C0000        000D0000        000E0000        000F0000        00100000      
  00110000        00120000        00130000        00140000        00150000      
  00160000        00170000        00180000        00190000        001A0000      
  001B0000        001C0000        001D0000        001E0000        001F0000    

其中被標記爲RO的扇區就是被保護起來的扇區,回憶一下上文提及的flash_protect_default函數,根據當前配置,該函數會把u-boot鏡像保護起來(相應的塊只能讀)。因此我們做擦寫NorFlash的測試時要避開這些扇區。

下面就做NorFlash的擦寫測試,已驗證對NorFlash的支持是否實現。執行erase 60000 6ffff擦除扇區,將SDRAM中從0x30000000開始的內容拷貝到擦除的扇區中cp.b 30000000 60000 10000,比較兩者之間的數據cmp.b 30000000 60000 10000,得到的輸出信息爲:

Total of 65536 byte(s) were the same

這標誌着我們已經完成了對NorFlash的支持。

參考文獻

[1] 韋東山老師二期視頻教程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章