Linux 4.x MTD原始碼分析-核心資料結構

rexnie發表於2019-01-20

mtd_info

mtd_info從物理性狀和快閃記憶體操作(讀,寫,擦除等)的角度描述了快閃記憶體板塊,其內容來自對具體快閃記憶體晶片的查詢,結構中有些內容來自cfi_private。mtd_info有一個指標priv指向map_info結構,而map_info有一個指標fldrv_priv指向cfi_private。mtd_info結構還包含了在物理上實際操作快閃記憶體板塊,實際上是板塊上的晶片的函式集合。後面會結合程式碼詳細講到如何初始化該結構。

struct mtd_info {
	u_char type;
	// 儲存器型別,MTD_RAM/_ROM/_NORFLASH/_NANDFLASH/。。。
	uint32_t flags;
	uint64_t size;	 // Total size of the MTD

	/* "Major" erase size for the device. Naïve users may take this
	 * to be the only erase size available, or may use the more detailed
	 * information below if they desire
	 */
	uint32_t erasesize;
	/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
	 * though individual bits can be cleared), in case of NAND flash it is
	 * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
	 * it is of ECC block size, etc. It is illegal to have writesize = 0.
	 * Any driver registering a struct mtd_info must ensure a writesize of
	 * 1 or larger.
	 */
	uint32_t writesize;

	/*
	 * Size of the write buffer used by the MTD. MTD devices having a write
	 * buffer can write multiple writesize chunks at a time. E.g. while
	 * writing 4 * writesize bytes to a device with 2 * writesize bytes
	 * buffer the MTD driver can (but doesn't have to) do 2 writesize
	 * operations, but not 4. Currently, all NANDs have writebufsize
	 * equivalent to writesize (NAND page size). Some NOR flashes do have
	 * writebufsize greater than writesize.
	 */
	uint32_t writebufsize;

	uint32_t oobsize;   // Amount of OOB data per block (e.g. 16)
	uint32_t oobavail;  // Available OOB bytes per block

	/*
	 * If erasesize is a power of 2 then the shift is stored in
	 * erasesize_shift otherwise erasesize_shift is zero. Ditto writesize.
	 */
	unsigned int erasesize_shift;
	unsigned int writesize_shift;
	/* Masks based on erasesize_shift and writesize_shift */
	unsigned int erasesize_mask;
	unsigned int writesize_mask;

	/*
	 * read ops return -EUCLEAN if max number of bitflips corrected on any
	 * one region comprising an ecc step equals or exceeds this value.
	 * Settable by driver, else defaults to ecc_strength.  User can override
	 * in sysfs.  N.B. The meaning of the -EUCLEAN return code has changed;
	 * see Documentation/ABI/testing/sysfs-class-mtd for more detail.
	 */
	unsigned int bitflip_threshold;

	// Kernel-only stuff starts here.
	const char *name;
	int index;

	/* ECC layout structure pointer - read only! */
	struct nand_ecclayout *ecclayout;

	/* the ecc step size. */
	unsigned int ecc_step_size;

	/* max number of correctible bit errors per ecc step */
	unsigned int ecc_strength;

	/* Data for variable erase regions. If numeraseregions is zero,
	 * it means that the whole device has erasesize as given above.
	 */
	int numeraseregions;
	struct mtd_erase_region_info *eraseregions;

	/*
	 * Do not call via these pointers, use corresponding mtd_*()
	 * wrappers instead.
	 */
	int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
	int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
		       size_t *retlen, void **virt, resource_size_t *phys);
	int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
	unsigned long (*_get_unmapped_area) (struct mtd_info *mtd,
					     unsigned long len,
					     unsigned long offset,
					     unsigned long flags);
	int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,
		      size_t *retlen, u_char *buf);
	int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,
		       size_t *retlen, const u_char *buf);
	int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
			     size_t *retlen, const u_char *buf);
	int (*_read_oob) (struct mtd_info *mtd, loff_t from,
			  struct mtd_oob_ops *ops);
	int (*_write_oob) (struct mtd_info *mtd, loff_t to,
			   struct mtd_oob_ops *ops);
	int (*_get_fact_prot_info) (struct mtd_info *mtd, size_t len,
				    size_t *retlen, struct otp_info *buf);
	int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
				    size_t len, size_t *retlen, u_char *buf);
	int (*_get_user_prot_info) (struct mtd_info *mtd, size_t len,
				    size_t *retlen, struct otp_info *buf);
	int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
				    size_t len, size_t *retlen, u_char *buf);
	int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
				     size_t len, size_t *retlen, u_char *buf);
	int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
				    size_t len);
	int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
			unsigned long count, loff_t to, size_t *retlen);
	void (*_sync) (struct mtd_info *mtd);
	int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
	int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
	int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
	int (*_block_isreserved) (struct mtd_info *mtd, loff_t ofs);
	int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
	int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
	int (*_suspend) (struct mtd_info *mtd);
	void (*_resume) (struct mtd_info *mtd);
	void (*_reboot) (struct mtd_info *mtd);
	/*
	 * If the driver is something smart, like UBI, it may need to maintain
	 * its own reference counting. The below functions are only for driver.
	 */
	int (*_get_device) (struct mtd_info *mtd);
	void (*_put_device) (struct mtd_info *mtd);

	/* Backing device capabilities for this device
	 * - provides mmap capabilities
	 */
	struct backing_dev_info *backing_dev_info;

	struct notifier_block reboot_notifier;  /* default mode before reboot */

	/* ECC status information */
	struct mtd_ecc_stats ecc_stats;
	/* Subpage shift (NAND) */
	int subpage_sft;

	void *priv;

	struct module *owner;
	struct device dev;
	int usecount;
};
複製程式碼

map_info

map_info從記憶體訪問的角度描述了一種快閃記憶體晶片。它儲存著快閃記憶體的物理開始地址,儲存空間大小,匯流排頻寬等資訊。map_info最主要的任務是把快閃記憶體的實體地址空間對映到核心的線性地址空間,這樣訪問快閃記憶體空間的上層user modules就不用關心具體硬體的差異了。

struct map_info {
	const char *name;
	//快閃記憶體的大小
	unsigned long size;
	//快閃記憶體的實體地址
	resource_size_t phys;
#define NO_XIP (-1UL)

    //快閃記憶體的實體地址被對映到核心的線性地址,核心其他模組的程式碼要訪問快閃記憶體都是使用虛擬地址
	void __iomem *virt;
	void *cached;

	int swap; /* this mapping's byte-swapping requirement */
	//匯流排位寬,1=8bit, 2=16bit...
	int bankwidth; /* in octets. This isn't necessarily the width
		       of actual bus cycles -- it's the repeat interval
		      in bytes, before you are talking to the first chip again.
		      */
//定義CONFIG_MTD_COMPLEX_MAPPINGS表示使用非線性的地址對映,如果有定義該巨集chip driver需要實現
//下面4個api,實現從虛擬地址到實體地址的轉化,同時讀寫對應實體地址中的內容
//核心一般呼叫map_read/map_write等巨集訪問快閃記憶體,當定義了CONFIG_MTD_COMPLEX_MAPPINGS,
//該巨集會呼叫這4個API
//使用1對1的對映比較常見,所以一般不需要定義該巨集
#ifdef CONFIG_MTD_COMPLEX_MAPPINGS
	map_word (*read)(struct map_info *, unsigned long);
	void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);

	void (*write)(struct map_info *, const map_word, unsigned long);
	void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);

	/* We can perhaps put in 'point' and 'unpoint' methods, if we really
	   want to enable XIP for non-linear mappings. Not yet though. */
#endif
	/* It's possible for the map driver to use cached memory in its
	   copy_from implementation (and _only_ with copy_from).  However,
	   when the chip driver knows some flash area has changed contents,
	   it will signal it to the map driver through this routine to let
	   the map driver invalidate the corresponding cache as needed.
	   If there is no cache to care about this can be set to NULL. */
	void (*inval_cache)(struct map_info *, unsigned long, ssize_t);

	/* set_vpp() must handle being reentered -- enable, enable, disable
	   must leave it enabled. */
	void (*set_vpp)(struct map_info *, int);

	unsigned long pfow_base;
	unsigned long map_priv_1;
	unsigned long map_priv_2;
	struct device_node *device_node;
	void *fldrv_priv;
	struct mtd_chip_driver *fldrv;
};
複製程式碼

cfi_private

cfi_private是對快閃記憶體板塊的綜合描述。該結構是不定長度的,取決於板塊實際上有多少塊晶片,numchips域指示了晶片的數量,根據該值即可知道chips陣列的大小。

struct cfi_private {
	uint16_t cmdset;
	void *cmdset_priv;
	int interleave;
	int device_type;
	int cfi_mode;		/* Are we a JEDEC device pretending to be CFI? */
	int addr_unlock1;
	int addr_unlock2;
	struct mtd_info *(*cmdset_setup)(struct map_info *);
	struct cfi_ident *cfiq; /* For now only one. We insist that all devs
				  must be of the same type. */
	// 在AutoSelect mode下,從flash讀出來的 Device ID資訊
	int mfr, id;
	int numchips;
	map_word sector_erase_cmd;
	unsigned long chipshift; /* Because they're of the same type */
	const char *im_name;	 /* inter_module name for cmdset_setup */
	struct flchip chips[0];  /* per-chip data structure for each chip */
};

複製程式碼

flchip

用於對大CHIP(如果2片並列或者4片並列)的操作。

struct flchip {
    // 記錄該晶片在記憶體中的偏移
	unsigned long start; /* Offset within the map */
	//	unsigned long len;
	/* We omit len for now, because when we group them together
	   we insist that they're all of the same size, and the chip size
	   is held in the next level up. If we get more versatile later,
	   it'll make it a damn sight harder to find which chip we want from
	   a given offset, and we'll want to add the per-chip length field
	   back in.
	*/
	int ref_point_counter;
	// 記錄晶片當前的狀態,probe時初始化為FL_READY
	flstate_t state;
	flstate_t oldstate;

	unsigned int write_suspended:1;
	unsigned int erase_suspended:1;
	unsigned long in_progress_block_addr;

	struct mutex mutex;
	wait_queue_head_t wq; /* Wait on here when we're waiting for the chip
			     to be ready */
	int word_write_time;
	int buffer_write_time;
	int erase_time;

	int word_write_time_max;
	int buffer_write_time_max;
	int erase_time_max;

	void *priv;
};
複製程式碼

cfi_pri_intelext

Intel/Sharp擴充套件命令集0x0001提供的產商特定的關於晶片的資訊。

/* Vendor-Specific PRI for Intel/Sharp Extended Command Set (0x0001) */

struct cfi_pri_intelext {
    // "PRI"
	uint8_t  pri[3];
	// Major/Minor version number, ASCII
	uint8_t  MajorVersion;
	uint8_t  MinorVersion;
	
	//P+5 chip支援的功能的bitmask, bit X = 1/0 某功能支援/不支援,具體功能看intel spec
	//bit 0 Chip erase supported bit 0 = 0 No
    //bit 1 Suspend erase supported bit 1 = 
    //bit 2 Suspend program supported bit 2 = 
    //bit 3 Legacy lock/unlock supported bit 3 = 
    //bit 4 Queued erase supported bit 4 = 
    //bit 5 Instant Individual block locking supported bit 5 = 
    //bit 6 Protection bits supported bit 6 = 
    //bit 7 Page-mode read supported bit 7 = 
    //bit 8 Synchronous read supported bit 8 = 
	uint32_t FeatureSupport; /* if bit 31 is set then an additional uint32_t feature
				    block follows - FIXME - not currently supported */
	//P+9 suspended erase/program 狀態下,chip支援的功能的bitmask
	//read Array, Status,Query總是支援的,其它支援特性包括:
	//bit 0 Program supported after erase suspend bit 0 = 1 Yes
    //bits 1–7 reserved; undefined bits are “0”
	uint8_t  SuspendCmdSupport;
	
	// P+A  Block status register mask
    //bit 0 Block Lock-Bit Status register active bit 0 = 1 Yes
    //bit 1 Block Lock-Down Bit Status active
    //bits 2–15 are Reserved; undefined bits are “0” 3C: --00
	uint16_t BlkStatusRegMask;
	
	// P+C 地址
	uint8_t  VccOptimal;
	// P+D 地址
	uint8_t  VppOptimal;
	// P+E 在JEDEC ID地址空間中的Protection register fields的個數
	uint8_t  NumProtectionFields;
	// 一個保護暫存器域對應4B的配置
	uint16_t ProtRegAddr;
	uint8_t  FactProtRegSize;
	uint8_t  UserProtRegSize;
	
	uint8_t  extra[0];
} __packed;
複製程式碼

cfi_ident

在nor進入QRY模式時,從 Query Struct中查詢出的關於晶片的基本查詢資訊。具體看CFI規範紀要 查詢資訊在nor中以小端方式儲存,讀出來後要轉為cpu位元組序。 在QRY mode下一次資料只有低8bit有效,所以一次只能讀1B. 這個結構體是個變長的,在讀出nor具有的擦除區數量後,會在這個結構體尾部給每個擦除區分配4B空間放擦除區資訊。

/* Basic Query Structure */
struct cfi_ident {
	uint8_t  qry[3];
	// chip首選演算法命令集
	uint16_t P_ID;
	// chip首選演算法相關的查詢表,這是可選的表,存放比如主/次版本等各種產商定義的特性
	uint16_t P_ADR;
	
	// 和上面兩個欄位類似,差別是這是備選的演算法
	uint16_t A_ID;
	uint16_t A_ADR;
	uint8_t  VccMin;
	uint8_t  VccMax;
	uint8_t  VppMin;
	uint8_t  VppMax;
	// 寫一個word的timeout, 如果WordWriteTimeoutTyp=n, 則timeout為2^n us
	// =0表不支援,會hardcode一個預設值
	uint8_t  WordWriteTimeoutTyp;
	// 多位元組寫的timeout,不支援時,設定為0,表不使用
	uint8_t  BufWriteTimeoutTyp;
	uint8_t  BlockEraseTimeoutTyp;
	uint8_t  ChipEraseTimeoutTyp;
	uint8_t  WordWriteTimeoutMax;
	uint8_t  BufWriteTimeoutMax;
	uint8_t  BlockEraseTimeoutMax;
	uint8_t  ChipEraseTimeoutMax;
	//表示該chip的大小,如果DevSize=n, 則chip容量為2^n 位元組
	uint8_t  DevSize;
	uint16_t InterfaceDesc;
	// 0x2A地址,多位元組寫時的buffer大小,如果該值為n, 則buffer大小為2^n位元組
	uint16_t MaxBufWriteSize;
	//norflash有多少個擦除區(erase region)
	//根據手冊,擦除區是指具有同樣大小的連續的擦除塊( Erase Block),
	//注意一定要是連續的擦除塊,不連續的就算兩個區了
	// num_erase_regions=0表沒有擦除區,或者只能整個device都擦除
	uint8_t  NumEraseRegions;
	//bits 15-0=y 表示該擦除區所包含的擦除塊的塊數=y+1
	//bits 31-16=z 表示擦除塊的大小=256*Z
	uint32_t EraseRegionInfo[0]; /* Not host ordered */
} __packed;
複製程式碼

相關文章