brintos

brintos / linux-shallow public Read only

0
0
Text · 17.9 KiB · c546f8c Raw
831 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * rfd_ftl.c -- resident flash disk (flash translation layer)4 *5 * Copyright © 2005  Sean Young <sean@mess.org>6 *7 * This type of flash translation layer (FTL) is used by the Embedded BIOS8 * by General Software. It is known as the Resident Flash Disk (RFD), see:9 *10 *	http://www.gensw.com/pages/prod/bios/rfd.htm11 *12 * based on ftl.c13 */14 15#include <linux/hdreg.h>16#include <linux/init.h>17#include <linux/mtd/blktrans.h>18#include <linux/mtd/mtd.h>19#include <linux/vmalloc.h>20#include <linux/slab.h>21#include <linux/jiffies.h>22#include <linux/module.h>23 24#include <asm/types.h>25 26static int block_size = 0;27module_param(block_size, int, 0);28MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size");29 30#define PREFIX "rfd_ftl: "31 32/* This major has been assigned by device@lanana.org */33#ifndef RFD_FTL_MAJOR34#define RFD_FTL_MAJOR		25635#endif36 37/* Maximum number of partitions in an FTL region */38#define PART_BITS		439 40/* An erase unit should start with this value */41#define RFD_MAGIC		0x919342 43/* the second value is 0xffff or 0xffc8; function unknown */44 45/* the third value is always 0xffff, ignored */46 47/* next is an array of mapping for each corresponding sector */48#define HEADER_MAP_OFFSET	349#define SECTOR_DELETED		0x000050#define SECTOR_ZERO		0xfffe51#define SECTOR_FREE		0xffff52 53#define SECTOR_SIZE		51254 55#define SECTORS_PER_TRACK	6356 57struct block {58	enum {59		BLOCK_OK,60		BLOCK_ERASING,61		BLOCK_ERASED,62		BLOCK_UNUSED,63		BLOCK_FAILED64	} state;65	int free_sectors;66	int used_sectors;67	int erases;68	u_long offset;69};70 71struct partition {72	struct mtd_blktrans_dev mbd;73 74	u_int block_size;		/* size of erase unit */75	u_int total_blocks;		/* number of erase units */76	u_int header_sectors_per_block;	/* header sectors in erase unit */77	u_int data_sectors_per_block;	/* data sectors in erase unit */78	u_int sector_count;		/* sectors in translated disk */79	u_int header_size;		/* bytes in header sector */80	int reserved_block;		/* block next up for reclaim */81	int current_block;		/* block to write to */82	u16 *header_cache;		/* cached header */83 84	int is_reclaiming;85	int cylinders;86	int errors;87	u_long *sector_map;88	struct block *blocks;89};90 91static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf);92 93static int build_block_map(struct partition *part, int block_no)94{95	struct block *block = &part->blocks[block_no];96	int i;97 98	block->offset = part->block_size * block_no;99 100	if (le16_to_cpu(part->header_cache[0]) != RFD_MAGIC) {101		block->state = BLOCK_UNUSED;102		return -ENOENT;103	}104 105	block->state = BLOCK_OK;106 107	for (i=0; i<part->data_sectors_per_block; i++) {108		u16 entry;109 110		entry = le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]);111 112		if (entry == SECTOR_DELETED)113			continue;114 115		if (entry == SECTOR_FREE) {116			block->free_sectors++;117			continue;118		}119 120		if (entry == SECTOR_ZERO)121			entry = 0;122 123		if (entry >= part->sector_count) {124			printk(KERN_WARNING PREFIX125				"'%s': unit #%d: entry %d corrupt, "126				"sector %d out of range\n",127				part->mbd.mtd->name, block_no, i, entry);128			continue;129		}130 131		if (part->sector_map[entry] != -1) {132			printk(KERN_WARNING PREFIX133				"'%s': more than one entry for sector %d\n",134				part->mbd.mtd->name, entry);135			part->errors = 1;136			continue;137		}138 139		part->sector_map[entry] = block->offset +140			(i + part->header_sectors_per_block) * SECTOR_SIZE;141 142		block->used_sectors++;143	}144 145	if (block->free_sectors == part->data_sectors_per_block)146		part->reserved_block = block_no;147 148	return 0;149}150 151static int scan_header(struct partition *part)152{153	int sectors_per_block;154	int i, rc = -ENOMEM;155	int blocks_found;156	size_t retlen;157 158	sectors_per_block = part->block_size / SECTOR_SIZE;159	part->total_blocks = (u32)part->mbd.mtd->size / part->block_size;160 161	if (part->total_blocks < 2)162		return -ENOENT;163 164	/* each erase block has three bytes header, followed by the map */165	part->header_sectors_per_block =166			((HEADER_MAP_OFFSET + sectors_per_block) *167			sizeof(u16) + SECTOR_SIZE - 1) / SECTOR_SIZE;168 169	part->data_sectors_per_block = sectors_per_block -170			part->header_sectors_per_block;171 172	part->header_size = (HEADER_MAP_OFFSET +173			part->data_sectors_per_block) * sizeof(u16);174 175	part->cylinders = (part->data_sectors_per_block *176			(part->total_blocks - 1) - 1) / SECTORS_PER_TRACK;177 178	part->sector_count = part->cylinders * SECTORS_PER_TRACK;179 180	part->current_block = -1;181	part->reserved_block = -1;182	part->is_reclaiming = 0;183 184	part->header_cache = kmalloc(part->header_size, GFP_KERNEL);185	if (!part->header_cache)186		goto err;187 188	part->blocks = kcalloc(part->total_blocks, sizeof(struct block),189			GFP_KERNEL);190	if (!part->blocks)191		goto err;192 193	part->sector_map = vmalloc(array_size(sizeof(u_long),194					      part->sector_count));195	if (!part->sector_map)196		goto err;197 198	for (i=0; i<part->sector_count; i++)199		part->sector_map[i] = -1;200 201	for (i=0, blocks_found=0; i<part->total_blocks; i++) {202		rc = mtd_read(part->mbd.mtd, i * part->block_size,203			      part->header_size, &retlen,204			      (u_char *)part->header_cache);205 206		if (!rc && retlen != part->header_size)207			rc = -EIO;208 209		if (rc)210			goto err;211 212		if (!build_block_map(part, i))213			blocks_found++;214	}215 216	if (blocks_found == 0) {217		printk(KERN_NOTICE PREFIX "no RFD magic found in '%s'\n",218				part->mbd.mtd->name);219		rc = -ENOENT;220		goto err;221	}222 223	if (part->reserved_block == -1) {224		printk(KERN_WARNING PREFIX "'%s': no empty erase unit found\n",225				part->mbd.mtd->name);226 227		part->errors = 1;228	}229 230	return 0;231 232err:233	vfree(part->sector_map);234	kfree(part->header_cache);235	kfree(part->blocks);236 237	return rc;238}239 240static int rfd_ftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)241{242	struct partition *part = container_of(dev, struct partition, mbd);243	u_long addr;244	size_t retlen;245	int rc;246 247	if (sector >= part->sector_count)248		return -EIO;249 250	addr = part->sector_map[sector];251	if (addr != -1) {252		rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen,253			      (u_char *)buf);254		if (!rc && retlen != SECTOR_SIZE)255			rc = -EIO;256 257		if (rc) {258			printk(KERN_WARNING PREFIX "error reading '%s' at "259				"0x%lx\n", part->mbd.mtd->name, addr);260			return rc;261		}262	} else263		memset(buf, 0, SECTOR_SIZE);264 265	return 0;266}267 268static int erase_block(struct partition *part, int block)269{270	struct erase_info *erase;271	int rc;272 273	erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL);274	if (!erase)275		return -ENOMEM;276 277	erase->addr = part->blocks[block].offset;278	erase->len = part->block_size;279 280	part->blocks[block].state = BLOCK_ERASING;281	part->blocks[block].free_sectors = 0;282 283	rc = mtd_erase(part->mbd.mtd, erase);284	if (rc) {285		printk(KERN_ERR PREFIX "erase of region %llx,%llx on '%s' "286				"failed\n", (unsigned long long)erase->addr,287				(unsigned long long)erase->len, part->mbd.mtd->name);288		part->blocks[block].state = BLOCK_FAILED;289		part->blocks[block].free_sectors = 0;290		part->blocks[block].used_sectors = 0;291	} else {292		u16 magic = cpu_to_le16(RFD_MAGIC);293		size_t retlen;294 295		part->blocks[block].state = BLOCK_ERASED;296		part->blocks[block].free_sectors = part->data_sectors_per_block;297		part->blocks[block].used_sectors = 0;298		part->blocks[block].erases++;299 300		rc = mtd_write(part->mbd.mtd, part->blocks[block].offset,301			       sizeof(magic), &retlen, (u_char *)&magic);302		if (!rc && retlen != sizeof(magic))303			rc = -EIO;304 305		if (rc) {306			pr_err(PREFIX "'%s': unable to write RFD header at 0x%lx\n",307			       part->mbd.mtd->name, part->blocks[block].offset);308			part->blocks[block].state = BLOCK_FAILED;309		} else {310			part->blocks[block].state = BLOCK_OK;311		}312	}313 314	kfree(erase);315 316	return rc;317}318 319static int move_block_contents(struct partition *part, int block_no, u_long *old_sector)320{321	void *sector_data;322	u16 *map;323	size_t retlen;324	int i, rc = -ENOMEM;325 326	part->is_reclaiming = 1;327 328	sector_data = kmalloc(SECTOR_SIZE, GFP_KERNEL);329	if (!sector_data)330		goto err3;331 332	map = kmalloc(part->header_size, GFP_KERNEL);333	if (!map)334		goto err2;335 336	rc = mtd_read(part->mbd.mtd, part->blocks[block_no].offset,337		      part->header_size, &retlen, (u_char *)map);338 339	if (!rc && retlen != part->header_size)340		rc = -EIO;341 342	if (rc) {343		printk(KERN_ERR PREFIX "error reading '%s' at "344			"0x%lx\n", part->mbd.mtd->name,345			part->blocks[block_no].offset);346 347		goto err;348	}349 350	for (i=0; i<part->data_sectors_per_block; i++) {351		u16 entry = le16_to_cpu(map[HEADER_MAP_OFFSET + i]);352		u_long addr;353 354 355		if (entry == SECTOR_FREE || entry == SECTOR_DELETED)356			continue;357 358		if (entry == SECTOR_ZERO)359			entry = 0;360 361		/* already warned about and ignored in build_block_map() */362		if (entry >= part->sector_count)363			continue;364 365		addr = part->blocks[block_no].offset +366			(i + part->header_sectors_per_block) * SECTOR_SIZE;367 368		if (*old_sector == addr) {369			*old_sector = -1;370			if (!part->blocks[block_no].used_sectors--) {371				rc = erase_block(part, block_no);372				break;373			}374			continue;375		}376		rc = mtd_read(part->mbd.mtd, addr, SECTOR_SIZE, &retlen,377			      sector_data);378 379		if (!rc && retlen != SECTOR_SIZE)380			rc = -EIO;381 382		if (rc) {383			printk(KERN_ERR PREFIX "'%s': Unable to "384				"read sector for relocation\n",385				part->mbd.mtd->name);386 387			goto err;388		}389 390		rc = rfd_ftl_writesect((struct mtd_blktrans_dev*)part,391				entry, sector_data);392 393		if (rc)394			goto err;395	}396 397err:398	kfree(map);399err2:400	kfree(sector_data);401err3:402	part->is_reclaiming = 0;403 404	return rc;405}406 407static int reclaim_block(struct partition *part, u_long *old_sector)408{409	int block, best_block, score, old_sector_block;410	int rc;411 412	/* we have a race if sync doesn't exist */413	mtd_sync(part->mbd.mtd);414 415	score = 0x7fffffff; /* MAX_INT */416	best_block = -1;417	if (*old_sector != -1)418		old_sector_block = *old_sector / part->block_size;419	else420		old_sector_block = -1;421 422	for (block=0; block<part->total_blocks; block++) {423		int this_score;424 425		if (block == part->reserved_block)426			continue;427 428		/*429		 * Postpone reclaiming if there is a free sector as430		 * more removed sectors is more efficient (have to move431		 * less).432		 */433		if (part->blocks[block].free_sectors)434			return 0;435 436		this_score = part->blocks[block].used_sectors;437 438		if (block == old_sector_block)439			this_score--;440		else {441			/* no point in moving a full block */442			if (part->blocks[block].used_sectors ==443					part->data_sectors_per_block)444				continue;445		}446 447		this_score += part->blocks[block].erases;448 449		if (this_score < score) {450			best_block = block;451			score = this_score;452		}453	}454 455	if (best_block == -1)456		return -ENOSPC;457 458	part->current_block = -1;459	part->reserved_block = best_block;460 461	pr_debug("reclaim_block: reclaiming block #%d with %d used "462		 "%d free sectors\n", best_block,463		 part->blocks[best_block].used_sectors,464		 part->blocks[best_block].free_sectors);465 466	if (part->blocks[best_block].used_sectors)467		rc = move_block_contents(part, best_block, old_sector);468	else469		rc = erase_block(part, best_block);470 471	return rc;472}473 474/*475 * IMPROVE: It would be best to choose the block with the most deleted sectors,476 * because if we fill that one up first it'll have the most chance of having477 * the least live sectors at reclaim.478 */479static int find_free_block(struct partition *part)480{481	int block, stop;482 483	block = part->current_block == -1 ?484			jiffies % part->total_blocks : part->current_block;485	stop = block;486 487	do {488		if (part->blocks[block].free_sectors &&489				block != part->reserved_block)490			return block;491 492		if (part->blocks[block].state == BLOCK_UNUSED)493			erase_block(part, block);494 495		if (++block >= part->total_blocks)496			block = 0;497 498	} while (block != stop);499 500	return -1;501}502 503static int find_writable_block(struct partition *part, u_long *old_sector)504{505	int rc, block;506	size_t retlen;507 508	block = find_free_block(part);509 510	if (block == -1) {511		if (!part->is_reclaiming) {512			rc = reclaim_block(part, old_sector);513			if (rc)514				goto err;515 516			block = find_free_block(part);517		}518 519		if (block == -1) {520			rc = -ENOSPC;521			goto err;522		}523	}524 525	rc = mtd_read(part->mbd.mtd, part->blocks[block].offset,526		      part->header_size, &retlen,527		      (u_char *)part->header_cache);528 529	if (!rc && retlen != part->header_size)530		rc = -EIO;531 532	if (rc) {533		printk(KERN_ERR PREFIX "'%s': unable to read header at "534				"0x%lx\n", part->mbd.mtd->name,535				part->blocks[block].offset);536		goto err;537	}538 539	part->current_block = block;540 541err:542	return rc;543}544 545static int mark_sector_deleted(struct partition *part, u_long old_addr)546{547	int block, offset, rc;548	u_long addr;549	size_t retlen;550	u16 del = cpu_to_le16(SECTOR_DELETED);551 552	block = old_addr / part->block_size;553	offset = (old_addr % part->block_size) / SECTOR_SIZE -554		part->header_sectors_per_block;555 556	addr = part->blocks[block].offset +557			(HEADER_MAP_OFFSET + offset) * sizeof(u16);558	rc = mtd_write(part->mbd.mtd, addr, sizeof(del), &retlen,559		       (u_char *)&del);560 561	if (!rc && retlen != sizeof(del))562		rc = -EIO;563 564	if (rc) {565		printk(KERN_ERR PREFIX "error writing '%s' at "566			"0x%lx\n", part->mbd.mtd->name, addr);567		goto err;568	}569	if (block == part->current_block)570		part->header_cache[offset + HEADER_MAP_OFFSET] = del;571 572	part->blocks[block].used_sectors--;573 574	if (!part->blocks[block].used_sectors &&575	    !part->blocks[block].free_sectors)576		rc = erase_block(part, block);577 578err:579	return rc;580}581 582static int find_free_sector(const struct partition *part, const struct block *block)583{584	int i, stop;585 586	i = stop = part->data_sectors_per_block - block->free_sectors;587 588	do {589		if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i])590				== SECTOR_FREE)591			return i;592 593		if (++i == part->data_sectors_per_block)594			i = 0;595	}596	while(i != stop);597 598	return -1;599}600 601static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, ulong *old_addr)602{603	struct partition *part = container_of(dev, struct partition, mbd);604	struct block *block;605	u_long addr;606	int i;607	int rc;608	size_t retlen;609	u16 entry;610 611	if (part->current_block == -1 ||612		!part->blocks[part->current_block].free_sectors) {613 614		rc = find_writable_block(part, old_addr);615		if (rc)616			goto err;617	}618 619	block = &part->blocks[part->current_block];620 621	i = find_free_sector(part, block);622 623	if (i < 0) {624		rc = -ENOSPC;625		goto err;626	}627 628	addr = (i + part->header_sectors_per_block) * SECTOR_SIZE +629		block->offset;630	rc = mtd_write(part->mbd.mtd, addr, SECTOR_SIZE, &retlen,631		       (u_char *)buf);632 633	if (!rc && retlen != SECTOR_SIZE)634		rc = -EIO;635 636	if (rc) {637		printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n",638				part->mbd.mtd->name, addr);639		goto err;640	}641 642	part->sector_map[sector] = addr;643 644	entry = cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector);645 646	part->header_cache[i + HEADER_MAP_OFFSET] = entry;647 648	addr = block->offset + (HEADER_MAP_OFFSET + i) * sizeof(u16);649	rc = mtd_write(part->mbd.mtd, addr, sizeof(entry), &retlen,650		       (u_char *)&entry);651 652	if (!rc && retlen != sizeof(entry))653		rc = -EIO;654 655	if (rc) {656		printk(KERN_ERR PREFIX "error writing '%s' at 0x%lx\n",657				part->mbd.mtd->name, addr);658		goto err;659	}660	block->used_sectors++;661	block->free_sectors--;662 663err:664	return rc;665}666 667static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf)668{669	struct partition *part = container_of(dev, struct partition, mbd);670	u_long old_addr;671	int i;672	int rc = 0;673 674	pr_debug("rfd_ftl_writesect(sector=0x%lx)\n", sector);675 676	if (part->reserved_block == -1) {677		rc = -EACCES;678		goto err;679	}680 681	if (sector >= part->sector_count) {682		rc = -EIO;683		goto err;684	}685 686	old_addr = part->sector_map[sector];687 688	for (i=0; i<SECTOR_SIZE; i++) {689		if (!buf[i])690			continue;691 692		rc = do_writesect(dev, sector, buf, &old_addr);693		if (rc)694			goto err;695		break;696	}697 698	if (i == SECTOR_SIZE)699		part->sector_map[sector] = -1;700 701	if (old_addr != -1)702		rc = mark_sector_deleted(part, old_addr);703 704err:705	return rc;706}707 708static int rfd_ftl_discardsect(struct mtd_blktrans_dev *dev,709			       unsigned long sector, unsigned int nr_sects)710{711	struct partition *part = container_of(dev, struct partition, mbd);712	u_long addr;713	int rc;714 715	while (nr_sects) {716		if (sector >= part->sector_count)717			return -EIO;718 719		addr = part->sector_map[sector];720 721		if (addr != -1) {722			rc = mark_sector_deleted(part, addr);723			if (rc)724				return rc;725 726			part->sector_map[sector] = -1;727		}728 729		sector++;730		nr_sects--;731	}732 733	return 0;734}735 736static int rfd_ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)737{738	struct partition *part = container_of(dev, struct partition, mbd);739 740	geo->heads = 1;741	geo->sectors = SECTORS_PER_TRACK;742	geo->cylinders = part->cylinders;743 744	return 0;745}746 747static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)748{749	struct partition *part;750 751	if ((mtd->type != MTD_NORFLASH && mtd->type != MTD_RAM) ||752	    mtd->size > UINT_MAX)753		return;754 755	part = kzalloc(sizeof(struct partition), GFP_KERNEL);756	if (!part)757		return;758 759	part->mbd.mtd = mtd;760 761	if (block_size)762		part->block_size = block_size;763	else {764		if (!mtd->erasesize) {765			printk(KERN_WARNING PREFIX "please provide block_size");766			goto out;767		} else768			part->block_size = mtd->erasesize;769	}770 771	if (scan_header(part) == 0) {772		part->mbd.size = part->sector_count;773		part->mbd.tr = tr;774		part->mbd.devnum = -1;775		if (!(mtd->flags & MTD_WRITEABLE))776			part->mbd.readonly = 1;777		else if (part->errors) {778			printk(KERN_WARNING PREFIX "'%s': errors found, "779					"setting read-only\n", mtd->name);780			part->mbd.readonly = 1;781		}782 783		printk(KERN_INFO PREFIX "name: '%s' type: %d flags %x\n",784				mtd->name, mtd->type, mtd->flags);785 786		if (!add_mtd_blktrans_dev(&part->mbd))787			return;788	}789out:790	kfree(part);791}792 793static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev)794{795	struct partition *part = container_of(dev, struct partition, mbd);796	int i;797 798	for (i=0; i<part->total_blocks; i++) {799		pr_debug("rfd_ftl_remove_dev:'%s': erase unit #%02d: %d erases\n",800			part->mbd.mtd->name, i, part->blocks[i].erases);801	}802 803	vfree(part->sector_map);804	kfree(part->header_cache);805	kfree(part->blocks);806	del_mtd_blktrans_dev(&part->mbd);807}808 809static struct mtd_blktrans_ops rfd_ftl_tr = {810	.name		= "rfd",811	.major		= RFD_FTL_MAJOR,812	.part_bits	= PART_BITS,813	.blksize 	= SECTOR_SIZE,814 815	.readsect	= rfd_ftl_readsect,816	.writesect	= rfd_ftl_writesect,817	.discard	= rfd_ftl_discardsect,818	.getgeo		= rfd_ftl_getgeo,819	.add_mtd	= rfd_ftl_add_mtd,820	.remove_dev	= rfd_ftl_remove_dev,821	.owner		= THIS_MODULE,822};823 824module_mtd_blktrans(rfd_ftl_tr);825 826MODULE_LICENSE("GPL");827MODULE_AUTHOR("Sean Young <sean@mess.org>");828MODULE_DESCRIPTION("Support code for RFD Flash Translation Layer, "829		"used by General Software's Embedded BIOS");830 831