diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mtd/Kconfig | 12 | ||||
-rw-r--r-- | drivers/mtd/Makefile | 3 | ||||
-rw-r--r-- | drivers/mtd/maps/Kconfig | 9 | ||||
-rw-r--r-- | drivers/mtd/maps/ts5500_flash.c | 6 | ||||
-rw-r--r-- | drivers/mtd/rfd_ftl.c | 855 |
5 files changed, 873 insertions, 12 deletions
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 027054dea032..843a1cbe0866 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig | |||
@@ -1,4 +1,4 @@ | |||
1 | # $Id: Kconfig,v 1.7 2004/11/22 11:33:56 ijc Exp $ | 1 | # $Id: Kconfig,v 1.9 2005/06/16 08:49:29 sean Exp $ |
2 | 2 | ||
3 | menu "Memory Technology Devices (MTD)" | 3 | menu "Memory Technology Devices (MTD)" |
4 | 4 | ||
@@ -253,6 +253,16 @@ config INFTL | |||
253 | permitted to copy, modify and distribute the code as you wish. Just | 253 | permitted to copy, modify and distribute the code as you wish. Just |
254 | not use it. | 254 | not use it. |
255 | 255 | ||
256 | config RFD_FTL | ||
257 | tristate "Resident Flash Disk (Flash Translation Layer) support" | ||
258 | depends on MTD | ||
259 | ---help--- | ||
260 | This provides support for the flash translation layer known | ||
261 | as the Resident Flash Disk (RFD), as used by the Embedded BIOS | ||
262 | of General Software. | ||
263 | See http://www.gensw.com/pages/prod/bios/rfd.htm for further | ||
264 | information. | ||
265 | |||
256 | source "drivers/mtd/chips/Kconfig" | 266 | source "drivers/mtd/chips/Kconfig" |
257 | 267 | ||
258 | source "drivers/mtd/maps/Kconfig" | 268 | source "drivers/mtd/maps/Kconfig" |
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index e4ad588327f7..cb16b7d478ce 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile | |||
@@ -1,7 +1,7 @@ | |||
1 | # | 1 | # |
2 | # Makefile for the memory technology device drivers. | 2 | # Makefile for the memory technology device drivers. |
3 | # | 3 | # |
4 | # $Id: Makefile.common,v 1.5 2004/08/10 20:51:49 dwmw2 Exp $ | 4 | # $Id: Makefile.common,v 1.6 2005/06/16 08:49:29 sean Exp $ |
5 | 5 | ||
6 | # Core functionality. | 6 | # Core functionality. |
7 | mtd-y := mtdcore.o | 7 | mtd-y := mtdcore.o |
@@ -20,6 +20,7 @@ obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_ro.o mtd_blkdevs.o | |||
20 | obj-$(CONFIG_FTL) += ftl.o mtd_blkdevs.o | 20 | obj-$(CONFIG_FTL) += ftl.o mtd_blkdevs.o |
21 | obj-$(CONFIG_NFTL) += nftl.o mtd_blkdevs.o | 21 | obj-$(CONFIG_NFTL) += nftl.o mtd_blkdevs.o |
22 | obj-$(CONFIG_INFTL) += inftl.o mtd_blkdevs.o | 22 | obj-$(CONFIG_INFTL) += inftl.o mtd_blkdevs.o |
23 | obj-$(CONFIG_RFD_FTL) += rfd_ftl.o mtd_blkdevs.o | ||
23 | 24 | ||
24 | nftl-objs := nftlcore.o nftlmount.o | 25 | nftl-objs := nftlcore.o nftlmount.o |
25 | inftl-objs := inftlcore.o inftlmount.o | 26 | inftl-objs := inftlcore.o inftlmount.o |
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig index 44781a83b2e7..aa5d71f8d812 100644 --- a/drivers/mtd/maps/Kconfig +++ b/drivers/mtd/maps/Kconfig | |||
@@ -97,14 +97,11 @@ config MTD_TS5500 | |||
97 | depends on X86 && MTD_JEDECPROBE && MTD_PARTITIONS | 97 | depends on X86 && MTD_JEDECPROBE && MTD_PARTITIONS |
98 | help | 98 | help |
99 | This provides a driver for the on-board flash of the Technologic | 99 | This provides a driver for the on-board flash of the Technologic |
100 | System's TS-5500 board. The flash is split into 3 partitions | 100 | System's TS-5500 board. The 2MB flash is split into 3 partitions |
101 | which are accessed as separate MTD devices. | 101 | which are accessed as separate MTD devices. |
102 | 102 | ||
103 | mtd0 and mtd2 are the two BIOS drives. Unfortunately the BIOS | 103 | mtd0 and mtd2 are the two BIOS drives, which use the resident |
104 | uses a proprietary flash translation layer from General Software, | 104 | flash disk (RFD) flash translation layer. |
105 | which is not supported (the drives cannot be mounted). You can | ||
106 | create your own file system (jffs for example), but the BIOS | ||
107 | won't be able to boot from it. | ||
108 | 105 | ||
109 | mtd1 allows you to reprogram your BIOS. BE VERY CAREFUL. | 106 | mtd1 allows you to reprogram your BIOS. BE VERY CAREFUL. |
110 | 107 | ||
diff --git a/drivers/mtd/maps/ts5500_flash.c b/drivers/mtd/maps/ts5500_flash.c index 3ebd90f56503..286dd82e8b83 100644 --- a/drivers/mtd/maps/ts5500_flash.c +++ b/drivers/mtd/maps/ts5500_flash.c | |||
@@ -19,13 +19,11 @@ | |||
19 | * | 19 | * |
20 | * Note: | 20 | * Note: |
21 | * - In order for detection to work, jumper 3 must be set. | 21 | * - In order for detection to work, jumper 3 must be set. |
22 | * - Drive A and B use a proprietary FTL from General Software which isn't | 22 | * - Drive A and B use the resident flash disk (RFD) flash translation layer. |
23 | * supported as of yet so standard drives can't be mounted; you can create | ||
24 | * your own (e.g. jffs) file system. | ||
25 | * - If you have created your own jffs file system and the bios overwrites | 23 | * - If you have created your own jffs file system and the bios overwrites |
26 | * it during boot, try disabling Drive A: and B: in the boot order. | 24 | * it during boot, try disabling Drive A: and B: in the boot order. |
27 | * | 25 | * |
28 | * $Id: ts5500_flash.c,v 1.2 2004/11/28 09:40:40 dwmw2 Exp $ | 26 | * $Id: ts5500_flash.c,v 1.3 2005/06/16 08:49:30 sean Exp $ |
29 | */ | 27 | */ |
30 | 28 | ||
31 | #include <linux/config.h> | 29 | #include <linux/config.h> |
diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c new file mode 100644 index 000000000000..7b9f359f04b7 --- /dev/null +++ b/drivers/mtd/rfd_ftl.c | |||
@@ -0,0 +1,855 @@ | |||
1 | /* | ||
2 | * rfd_ftl.c -- resident flash disk (flash translation layer) | ||
3 | * | ||
4 | * Copyright (C) 2005 Sean Young <sean@mess.org> | ||
5 | * | ||
6 | * $Id: rfd_ftl.c,v 1.4 2005/07/31 22:49:14 sean Exp $ | ||
7 | * | ||
8 | * This type of flash translation layer (FTL) is used by the Embedded BIOS | ||
9 | * by General Software. It is known as the Resident Flash Disk (RFD), see: | ||
10 | * | ||
11 | * http://www.gensw.com/pages/prod/bios/rfd.htm | ||
12 | * | ||
13 | * based on ftl.c | ||
14 | */ | ||
15 | |||
16 | #include <linux/hdreg.h> | ||
17 | #include <linux/init.h> | ||
18 | #include <linux/mtd/blktrans.h> | ||
19 | #include <linux/mtd/mtd.h> | ||
20 | #include <linux/vmalloc.h> | ||
21 | |||
22 | #include <asm/types.h> | ||
23 | |||
24 | #define const_cpu_to_le16 __constant_cpu_to_le16 | ||
25 | |||
26 | static int block_size = 0; | ||
27 | module_param(block_size, int, 0); | ||
28 | MODULE_PARM_DESC(block_size, "Block size to use by RFD, defaults to erase unit size"); | ||
29 | |||
30 | #define PREFIX "rfd_ftl: " | ||
31 | |||
32 | /* Major device # for FTL device */ | ||
33 | |||
34 | /* A request for this major has been sent to device@lanana.org */ | ||
35 | #ifndef RFD_FTL_MAJOR | ||
36 | #define RFD_FTL_MAJOR 95 | ||
37 | #endif | ||
38 | |||
39 | /* Maximum number of partitions in an FTL region */ | ||
40 | #define PART_BITS 4 | ||
41 | |||
42 | /* An erase unit should start with this value */ | ||
43 | #define RFD_MAGIC 0x9193 | ||
44 | |||
45 | /* the second value is 0xffff or 0xffc8; function unknown */ | ||
46 | |||
47 | /* the third value is always 0xffff, ignored */ | ||
48 | |||
49 | /* next is an array of mapping for each corresponding sector */ | ||
50 | #define HEADER_MAP_OFFSET 3 | ||
51 | #define SECTOR_DELETED 0x0000 | ||
52 | #define SECTOR_ZERO 0xfffe | ||
53 | #define SECTOR_FREE 0xffff | ||
54 | |||
55 | #define SECTOR_SIZE 512 | ||
56 | |||
57 | #define SECTORS_PER_TRACK 63 | ||
58 | |||
59 | struct block { | ||
60 | enum { | ||
61 | BLOCK_OK, | ||
62 | BLOCK_ERASING, | ||
63 | BLOCK_ERASED, | ||
64 | BLOCK_FAILED | ||
65 | } state; | ||
66 | int free_sectors; | ||
67 | int used_sectors; | ||
68 | int erases; | ||
69 | u_long offset; | ||
70 | }; | ||
71 | |||
72 | struct partition { | ||
73 | struct mtd_blktrans_dev mbd; | ||
74 | |||
75 | u_int block_size; /* size of erase unit */ | ||
76 | u_int total_blocks; /* number of erase units */ | ||
77 | u_int header_sectors_per_block; /* header sectors in erase unit */ | ||
78 | u_int data_sectors_per_block; /* data sectors in erase unit */ | ||
79 | u_int sector_count; /* sectors in translated disk */ | ||
80 | u_int header_size; /* bytes in header sector */ | ||
81 | int reserved_block; /* block next up for reclaim */ | ||
82 | int current_block; /* block to write to */ | ||
83 | u16 *header_cache; /* cached header */ | ||
84 | |||
85 | int is_reclaiming; | ||
86 | int cylinders; | ||
87 | int errors; | ||
88 | u_long *sector_map; | ||
89 | struct block *blocks; | ||
90 | }; | ||
91 | |||
92 | static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf); | ||
93 | |||
94 | static int build_block_map(struct partition *part, int block_no) | ||
95 | { | ||
96 | struct block *block = &part->blocks[block_no]; | ||
97 | int i; | ||
98 | |||
99 | block->offset = part->block_size * block_no; | ||
100 | |||
101 | if (le16_to_cpu(part->header_cache[0]) != RFD_MAGIC) { | ||
102 | block->state = BLOCK_ERASED; /* assumption */ | ||
103 | block->free_sectors = part->data_sectors_per_block; | ||
104 | part->reserved_block = block_no; | ||
105 | return 1; | ||
106 | } | ||
107 | |||
108 | block->state = BLOCK_OK; | ||
109 | |||
110 | for (i=0; i<part->data_sectors_per_block; i++) { | ||
111 | u16 entry; | ||
112 | |||
113 | entry = le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]); | ||
114 | |||
115 | if (entry == SECTOR_DELETED) | ||
116 | continue; | ||
117 | |||
118 | if (entry == SECTOR_FREE) { | ||
119 | block->free_sectors++; | ||
120 | continue; | ||
121 | } | ||
122 | |||
123 | if (entry == SECTOR_ZERO) | ||
124 | entry = 0; | ||
125 | |||
126 | if (entry >= part->sector_count) { | ||
127 | printk(KERN_NOTICE PREFIX | ||
128 | "'%s': unit #%d: entry %d corrupt, " | ||
129 | "sector %d out of range\n", | ||
130 | part->mbd.mtd->name, block_no, i, entry); | ||
131 | continue; | ||
132 | } | ||
133 | |||
134 | if (part->sector_map[entry] != -1) { | ||
135 | printk(KERN_NOTICE PREFIX | ||
136 | "'%s': more than one entry for sector %d\n", | ||
137 | part->mbd.mtd->name, entry); | ||
138 | part->errors = 1; | ||
139 | continue; | ||
140 | } | ||
141 | |||
142 | part->sector_map[entry] = block->offset + | ||
143 | (i + part->header_sectors_per_block) * SECTOR_SIZE; | ||
144 | |||
145 | block->used_sectors++; | ||
146 | } | ||
147 | |||
148 | if (block->free_sectors == part->data_sectors_per_block) | ||
149 | part->reserved_block = block_no; | ||
150 | |||
151 | return 0; | ||
152 | } | ||
153 | |||
154 | static int scan_header(struct partition *part) | ||
155 | { | ||
156 | int sectors_per_block; | ||
157 | int i, rc = -ENOMEM; | ||
158 | int blocks_found; | ||
159 | size_t retlen; | ||
160 | |||
161 | sectors_per_block = part->block_size / SECTOR_SIZE; | ||
162 | part->total_blocks = part->mbd.mtd->size / part->block_size; | ||
163 | |||
164 | if (part->total_blocks < 2) | ||
165 | return -ENOENT; | ||
166 | |||
167 | /* each erase block has three bytes header, followed by the map */ | ||
168 | part->header_sectors_per_block = | ||
169 | ((HEADER_MAP_OFFSET + sectors_per_block) * | ||
170 | sizeof(u16) + SECTOR_SIZE - 1) / SECTOR_SIZE; | ||
171 | |||
172 | part->data_sectors_per_block = sectors_per_block - | ||
173 | part->header_sectors_per_block; | ||
174 | |||
175 | part->header_size = (HEADER_MAP_OFFSET + | ||
176 | part->data_sectors_per_block) * sizeof(u16); | ||
177 | |||
178 | part->cylinders = (part->data_sectors_per_block * | ||
179 | (part->total_blocks - 1) - 1) / SECTORS_PER_TRACK; | ||
180 | |||
181 | part->sector_count = part->cylinders * SECTORS_PER_TRACK; | ||
182 | |||
183 | part->current_block = -1; | ||
184 | part->reserved_block = -1; | ||
185 | part->is_reclaiming = 0; | ||
186 | |||
187 | part->header_cache = kmalloc(part->header_size, GFP_KERNEL); | ||
188 | if (!part->header_cache) | ||
189 | goto err; | ||
190 | |||
191 | part->blocks = kcalloc(part->total_blocks, sizeof(struct block), | ||
192 | GFP_KERNEL); | ||
193 | if (!part->blocks) | ||
194 | goto err; | ||
195 | |||
196 | part->sector_map = vmalloc(part->sector_count * sizeof(u_long)); | ||
197 | if (!part->sector_map) { | ||
198 | printk(KERN_ERR PREFIX "'%s': unable to allocate memory for " | ||
199 | "sector map", part->mbd.mtd->name); | ||
200 | goto err; | ||
201 | } | ||
202 | |||
203 | for (i=0; i<part->sector_count; i++) | ||
204 | part->sector_map[i] = -1; | ||
205 | |||
206 | for (i=0, blocks_found=0; i<part->total_blocks; i++) { | ||
207 | rc = part->mbd.mtd->read(part->mbd.mtd, | ||
208 | i * part->block_size, part->header_size, | ||
209 | &retlen, (u_char*)part->header_cache); | ||
210 | |||
211 | if (!rc && retlen != part->header_size) | ||
212 | rc = -EIO; | ||
213 | |||
214 | if (rc) | ||
215 | goto err; | ||
216 | |||
217 | if (!build_block_map(part, i)) | ||
218 | blocks_found++; | ||
219 | } | ||
220 | |||
221 | if (blocks_found == 0) { | ||
222 | printk(KERN_NOTICE PREFIX "no RFD magic found in '%s'\n", | ||
223 | part->mbd.mtd->name); | ||
224 | rc = -ENOENT; | ||
225 | goto err; | ||
226 | } | ||
227 | |||
228 | if (part->reserved_block == -1) { | ||
229 | printk(KERN_NOTICE PREFIX "'%s': no empty erase unit found\n", | ||
230 | part->mbd.mtd->name); | ||
231 | |||
232 | part->errors = 1; | ||
233 | } | ||
234 | |||
235 | return 0; | ||
236 | |||
237 | err: | ||
238 | vfree(part->sector_map); | ||
239 | kfree(part->header_cache); | ||
240 | kfree(part->blocks); | ||
241 | |||
242 | return rc; | ||
243 | } | ||
244 | |||
245 | static int rfd_ftl_readsect(struct mtd_blktrans_dev *dev, u_long sector, char *buf) | ||
246 | { | ||
247 | struct partition *part = (struct partition*)dev; | ||
248 | u_long addr; | ||
249 | size_t retlen; | ||
250 | int rc; | ||
251 | |||
252 | if (sector >= part->sector_count) | ||
253 | return -EIO; | ||
254 | |||
255 | addr = part->sector_map[sector]; | ||
256 | if (addr != -1) { | ||
257 | rc = part->mbd.mtd->read(part->mbd.mtd, addr, SECTOR_SIZE, | ||
258 | &retlen, (u_char*)buf); | ||
259 | if (!rc && retlen != SECTOR_SIZE) | ||
260 | rc = -EIO; | ||
261 | |||
262 | if (rc) { | ||
263 | printk(KERN_WARNING PREFIX "error reading '%s' at " | ||
264 | "0x%lx\n", part->mbd.mtd->name, addr); | ||
265 | return rc; | ||
266 | } | ||
267 | } else | ||
268 | memset(buf, 0, SECTOR_SIZE); | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | |||
273 | static void erase_callback(struct erase_info *erase) | ||
274 | { | ||
275 | struct partition *part; | ||
276 | u16 magic; | ||
277 | int i, rc; | ||
278 | size_t retlen; | ||
279 | |||
280 | part = (struct partition*)erase->priv; | ||
281 | |||
282 | i = erase->addr / part->block_size; | ||
283 | if (i >= part->total_blocks || part->blocks[i].offset != erase->addr) { | ||
284 | printk(KERN_ERR PREFIX "erase callback for unknown offset %x " | ||
285 | "on '%s'\n", erase->addr, part->mbd.mtd->name); | ||
286 | return; | ||
287 | } | ||
288 | |||
289 | if (erase->state != MTD_ERASE_DONE) { | ||
290 | printk(KERN_WARNING PREFIX "erase failed at 0x%x on '%s', " | ||
291 | "state %d\n", erase->addr, | ||
292 | part->mbd.mtd->name, erase->state); | ||
293 | |||
294 | part->blocks[i].state = BLOCK_FAILED; | ||
295 | part->blocks[i].free_sectors = 0; | ||
296 | part->blocks[i].used_sectors = 0; | ||
297 | |||
298 | kfree(erase); | ||
299 | |||
300 | return; | ||
301 | } | ||
302 | |||
303 | magic = const_cpu_to_le16(RFD_MAGIC); | ||
304 | |||
305 | part->blocks[i].state = BLOCK_ERASED; | ||
306 | part->blocks[i].free_sectors = part->data_sectors_per_block; | ||
307 | part->blocks[i].used_sectors = 0; | ||
308 | part->blocks[i].erases++; | ||
309 | |||
310 | rc = part->mbd.mtd->write(part->mbd.mtd, | ||
311 | part->blocks[i].offset, sizeof(magic), &retlen, | ||
312 | (u_char*)&magic); | ||
313 | |||
314 | if (!rc && retlen != sizeof(magic)) | ||
315 | rc = -EIO; | ||
316 | |||
317 | if (rc) { | ||
318 | printk(KERN_NOTICE PREFIX "'%s': unable to write RFD " | ||
319 | "header at 0x%lx\n", | ||
320 | part->mbd.mtd->name, | ||
321 | part->blocks[i].offset); | ||
322 | part->blocks[i].state = BLOCK_FAILED; | ||
323 | } | ||
324 | else | ||
325 | part->blocks[i].state = BLOCK_OK; | ||
326 | |||
327 | kfree(erase); | ||
328 | } | ||
329 | |||
330 | static int erase_block(struct partition *part, int block) | ||
331 | { | ||
332 | struct erase_info *erase; | ||
333 | int rc = -ENOMEM; | ||
334 | |||
335 | erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL); | ||
336 | if (!erase) | ||
337 | goto err; | ||
338 | |||
339 | erase->mtd = part->mbd.mtd; | ||
340 | erase->callback = erase_callback; | ||
341 | erase->addr = part->blocks[block].offset; | ||
342 | erase->len = part->block_size; | ||
343 | erase->priv = (u_long)part; | ||
344 | |||
345 | part->blocks[block].state = BLOCK_ERASING; | ||
346 | part->blocks[block].free_sectors = 0; | ||
347 | |||
348 | rc = part->mbd.mtd->erase(part->mbd.mtd, erase); | ||
349 | |||
350 | if (rc) { | ||
351 | printk(KERN_WARNING PREFIX "erase of region %x,%x on '%s' " | ||
352 | "failed\n", erase->addr, erase->len, | ||
353 | part->mbd.mtd->name); | ||
354 | kfree(erase); | ||
355 | } | ||
356 | |||
357 | err: | ||
358 | return rc; | ||
359 | } | ||
360 | |||
361 | static int move_block_contents(struct partition *part, int block_no, u_long *old_sector) | ||
362 | { | ||
363 | void *sector_data; | ||
364 | u16 *map; | ||
365 | size_t retlen; | ||
366 | int i, rc = -ENOMEM; | ||
367 | |||
368 | part->is_reclaiming = 1; | ||
369 | |||
370 | sector_data = kmalloc(SECTOR_SIZE, GFP_KERNEL); | ||
371 | if (!sector_data) | ||
372 | goto err3; | ||
373 | |||
374 | map = kmalloc(part->header_size, GFP_KERNEL); | ||
375 | if (!map) | ||
376 | goto err2; | ||
377 | |||
378 | rc = part->mbd.mtd->read(part->mbd.mtd, | ||
379 | part->blocks[block_no].offset, part->header_size, | ||
380 | &retlen, (u_char*)map); | ||
381 | |||
382 | if (!rc && retlen != part->header_size) | ||
383 | rc = -EIO; | ||
384 | |||
385 | if (rc) { | ||
386 | printk(KERN_NOTICE PREFIX "error reading '%s' at " | ||
387 | "0x%lx\n", part->mbd.mtd->name, | ||
388 | part->blocks[block_no].offset); | ||
389 | |||
390 | goto err; | ||
391 | } | ||
392 | |||
393 | for (i=0; i<part->data_sectors_per_block; i++) { | ||
394 | u16 entry = le16_to_cpu(map[HEADER_MAP_OFFSET + i]); | ||
395 | u_long addr; | ||
396 | |||
397 | |||
398 | if (entry == SECTOR_FREE || entry == SECTOR_DELETED) | ||
399 | continue; | ||
400 | |||
401 | if (entry == SECTOR_ZERO) | ||
402 | entry = 0; | ||
403 | |||
404 | /* already warned about and ignored in build_block_map() */ | ||
405 | if (entry >= part->sector_count) | ||
406 | continue; | ||
407 | |||
408 | addr = part->blocks[block_no].offset + | ||
409 | (i + part->header_sectors_per_block) * SECTOR_SIZE; | ||
410 | |||
411 | if (*old_sector == addr) { | ||
412 | *old_sector = -1; | ||
413 | if (!part->blocks[block_no].used_sectors--) { | ||
414 | rc = erase_block(part, block_no); | ||
415 | break; | ||
416 | } | ||
417 | continue; | ||
418 | } | ||
419 | rc = part->mbd.mtd->read(part->mbd.mtd, addr, | ||
420 | SECTOR_SIZE, &retlen, sector_data); | ||
421 | |||
422 | if (!rc && retlen != SECTOR_SIZE) | ||
423 | rc = -EIO; | ||
424 | |||
425 | if (rc) { | ||
426 | printk(KERN_NOTICE PREFIX "'%s': Unable to " | ||
427 | "read sector for relocation\n", | ||
428 | part->mbd.mtd->name); | ||
429 | |||
430 | goto err; | ||
431 | } | ||
432 | |||
433 | rc = rfd_ftl_writesect((struct mtd_blktrans_dev*)part, | ||
434 | entry, sector_data); | ||
435 | |||
436 | if (rc) | ||
437 | goto err; | ||
438 | } | ||
439 | |||
440 | err: | ||
441 | kfree(map); | ||
442 | err2: | ||
443 | kfree(sector_data); | ||
444 | err3: | ||
445 | part->is_reclaiming = 0; | ||
446 | |||
447 | return rc; | ||
448 | } | ||
449 | |||
450 | static int reclaim_block(struct partition *part, u_long *old_sector) | ||
451 | { | ||
452 | int block, best_block, score, old_sector_block; | ||
453 | int rc; | ||
454 | |||
455 | /* we have a race if sync doesn't exist */ | ||
456 | if (part->mbd.mtd->sync) | ||
457 | part->mbd.mtd->sync(part->mbd.mtd); | ||
458 | |||
459 | score = 0x7fffffff; /* MAX_INT */ | ||
460 | best_block = -1; | ||
461 | if (*old_sector != -1) | ||
462 | old_sector_block = *old_sector / part->block_size; | ||
463 | else | ||
464 | old_sector_block = -1; | ||
465 | |||
466 | for (block=0; block<part->total_blocks; block++) { | ||
467 | int this_score; | ||
468 | |||
469 | if (block == part->reserved_block) | ||
470 | continue; | ||
471 | |||
472 | /* | ||
473 | * Postpone reclaiming if there is a free sector as | ||
474 | * more removed sectors is more efficient (have to move | ||
475 | * less). | ||
476 | */ | ||
477 | if (part->blocks[block].free_sectors) | ||
478 | return 0; | ||
479 | |||
480 | this_score = part->blocks[block].used_sectors; | ||
481 | |||
482 | if (block == old_sector_block) | ||
483 | this_score--; | ||
484 | else { | ||
485 | /* no point in moving a full block */ | ||
486 | if (part->blocks[block].used_sectors == | ||
487 | part->data_sectors_per_block) | ||
488 | continue; | ||
489 | } | ||
490 | |||
491 | this_score += part->blocks[block].erases; | ||
492 | |||
493 | if (this_score < score) { | ||
494 | best_block = block; | ||
495 | score = this_score; | ||
496 | } | ||
497 | } | ||
498 | |||
499 | if (best_block == -1) | ||
500 | return -ENOSPC; | ||
501 | |||
502 | part->current_block = -1; | ||
503 | part->reserved_block = best_block; | ||
504 | |||
505 | pr_debug("reclaim_block: reclaiming block #%d with %d used " | ||
506 | "%d free sectors\n", best_block, | ||
507 | part->blocks[best_block].used_sectors, | ||
508 | part->blocks[best_block].free_sectors); | ||
509 | |||
510 | if (part->blocks[best_block].used_sectors) | ||
511 | rc = move_block_contents(part, best_block, old_sector); | ||
512 | else | ||
513 | rc = erase_block(part, best_block); | ||
514 | |||
515 | return rc; | ||
516 | } | ||
517 | |||
518 | /* | ||
519 | * IMPROVE: It would be best to choose the block with the most deleted sectors, | ||
520 | * because if we fill that one up first it'll have the most chance of having | ||
521 | * the least live sectors at reclaim. | ||
522 | */ | ||
523 | static int find_free_block(const struct partition *part) | ||
524 | { | ||
525 | int block, stop; | ||
526 | |||
527 | block = part->current_block == -1 ? | ||
528 | jiffies % part->total_blocks : part->current_block; | ||
529 | stop = block; | ||
530 | |||
531 | do { | ||
532 | if (part->blocks[block].free_sectors && | ||
533 | block != part->reserved_block) | ||
534 | return block; | ||
535 | |||
536 | if (++block >= part->total_blocks) | ||
537 | block = 0; | ||
538 | |||
539 | } while (block != stop); | ||
540 | |||
541 | return -1; | ||
542 | } | ||
543 | |||
544 | static int find_writeable_block(struct partition *part, u_long *old_sector) | ||
545 | { | ||
546 | int rc, block; | ||
547 | size_t retlen; | ||
548 | |||
549 | block = find_free_block(part); | ||
550 | |||
551 | if (block == -1) { | ||
552 | if (!part->is_reclaiming) { | ||
553 | rc = reclaim_block(part, old_sector); | ||
554 | if (rc) | ||
555 | goto err; | ||
556 | |||
557 | block = find_free_block(part); | ||
558 | } | ||
559 | |||
560 | if (block == -1) { | ||
561 | rc = -ENOSPC; | ||
562 | goto err; | ||
563 | } | ||
564 | } | ||
565 | |||
566 | rc = part->mbd.mtd->read(part->mbd.mtd, part->blocks[block].offset, | ||
567 | part->header_size, &retlen, (u_char*)part->header_cache); | ||
568 | |||
569 | if (!rc && retlen != part->header_size) | ||
570 | rc = -EIO; | ||
571 | |||
572 | if (rc) { | ||
573 | printk(KERN_NOTICE PREFIX "'%s': unable to read header at " | ||
574 | "0x%lx\n", part->mbd.mtd->name, | ||
575 | part->blocks[block].offset); | ||
576 | goto err; | ||
577 | } | ||
578 | |||
579 | part->current_block = block; | ||
580 | |||
581 | err: | ||
582 | return rc; | ||
583 | } | ||
584 | |||
585 | static int mark_sector_deleted(struct partition *part, u_long old_addr) | ||
586 | { | ||
587 | int block, offset, rc; | ||
588 | u_long addr; | ||
589 | size_t retlen; | ||
590 | u16 del = const_cpu_to_le16(SECTOR_DELETED); | ||
591 | |||
592 | block = old_addr / part->block_size; | ||
593 | offset = (old_addr % part->block_size) / SECTOR_SIZE - | ||
594 | part->header_sectors_per_block; | ||
595 | |||
596 | addr = part->blocks[block].offset + | ||
597 | (HEADER_MAP_OFFSET + offset) * sizeof(u16); | ||
598 | rc = part->mbd.mtd->write(part->mbd.mtd, addr, | ||
599 | sizeof(del), &retlen, (u_char*)&del); | ||
600 | |||
601 | if (!rc && retlen != sizeof(del)) | ||
602 | rc = -EIO; | ||
603 | |||
604 | if (rc) { | ||
605 | printk(KERN_WARNING PREFIX "error writing '%s' at " | ||
606 | "0x%lx\n", part->mbd.mtd->name, addr); | ||
607 | if (rc) | ||
608 | goto err; | ||
609 | } | ||
610 | if (block == part->current_block) | ||
611 | part->header_cache[offset + HEADER_MAP_OFFSET] = del; | ||
612 | |||
613 | part->blocks[block].used_sectors--; | ||
614 | |||
615 | if (!part->blocks[block].used_sectors && | ||
616 | !part->blocks[block].free_sectors) | ||
617 | rc = erase_block(part, block); | ||
618 | |||
619 | err: | ||
620 | return rc; | ||
621 | } | ||
622 | |||
623 | static int find_free_sector(const struct partition *part, const struct block *block) | ||
624 | { | ||
625 | int i, stop; | ||
626 | |||
627 | i = stop = part->data_sectors_per_block - block->free_sectors; | ||
628 | |||
629 | do { | ||
630 | if (le16_to_cpu(part->header_cache[HEADER_MAP_OFFSET + i]) | ||
631 | == SECTOR_FREE) | ||
632 | return i; | ||
633 | |||
634 | if (++i == part->data_sectors_per_block) | ||
635 | i = 0; | ||
636 | } | ||
637 | while(i != stop); | ||
638 | |||
639 | return -1; | ||
640 | } | ||
641 | |||
642 | static int do_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf, ulong *old_addr) | ||
643 | { | ||
644 | struct partition *part = (struct partition*)dev; | ||
645 | struct block *block; | ||
646 | u_long addr; | ||
647 | int i; | ||
648 | int rc; | ||
649 | size_t retlen; | ||
650 | u16 entry; | ||
651 | |||
652 | if (part->current_block == -1 || | ||
653 | !part->blocks[part->current_block].free_sectors) { | ||
654 | |||
655 | rc = find_writeable_block(part, old_addr); | ||
656 | if (rc) | ||
657 | goto err; | ||
658 | } | ||
659 | |||
660 | block = &part->blocks[part->current_block]; | ||
661 | |||
662 | i = find_free_sector(part, block); | ||
663 | |||
664 | if (i < 0) { | ||
665 | rc = -ENOSPC; | ||
666 | goto err; | ||
667 | } | ||
668 | |||
669 | addr = (i + part->header_sectors_per_block) * SECTOR_SIZE + | ||
670 | block->offset; | ||
671 | rc = part->mbd.mtd->write(part->mbd.mtd, | ||
672 | addr, SECTOR_SIZE, &retlen, (u_char*)buf); | ||
673 | |||
674 | if (!rc && retlen != SECTOR_SIZE) | ||
675 | rc = -EIO; | ||
676 | |||
677 | if (rc) { | ||
678 | printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n", | ||
679 | part->mbd.mtd->name, addr); | ||
680 | if (rc) | ||
681 | goto err; | ||
682 | } | ||
683 | |||
684 | part->sector_map[sector] = addr; | ||
685 | |||
686 | entry = cpu_to_le16(sector == 0 ? SECTOR_ZERO : sector); | ||
687 | |||
688 | part->header_cache[i + HEADER_MAP_OFFSET] = entry; | ||
689 | |||
690 | addr = block->offset + (HEADER_MAP_OFFSET + i) * sizeof(u16); | ||
691 | rc = part->mbd.mtd->write(part->mbd.mtd, addr, | ||
692 | sizeof(entry), &retlen, (u_char*)&entry); | ||
693 | |||
694 | if (!rc && retlen != sizeof(entry)) | ||
695 | rc = -EIO; | ||
696 | |||
697 | if (rc) { | ||
698 | printk(KERN_WARNING PREFIX "error writing '%s' at 0x%lx\n", | ||
699 | part->mbd.mtd->name, addr); | ||
700 | if (rc) | ||
701 | goto err; | ||
702 | } | ||
703 | block->used_sectors++; | ||
704 | block->free_sectors--; | ||
705 | |||
706 | err: | ||
707 | return rc; | ||
708 | } | ||
709 | |||
710 | static int rfd_ftl_writesect(struct mtd_blktrans_dev *dev, u_long sector, char *buf) | ||
711 | { | ||
712 | struct partition *part = (struct partition*)dev; | ||
713 | u_long old_addr; | ||
714 | int i; | ||
715 | int rc = 0; | ||
716 | |||
717 | pr_debug("rfd_ftl_writesect(sector=0x%lx)\n", sector); | ||
718 | |||
719 | if (part->reserved_block == -1) { | ||
720 | rc = -EACCES; | ||
721 | goto err; | ||
722 | } | ||
723 | |||
724 | if (sector >= part->sector_count) { | ||
725 | rc = -EIO; | ||
726 | goto err; | ||
727 | } | ||
728 | |||
729 | old_addr = part->sector_map[sector]; | ||
730 | |||
731 | for (i=0; i<SECTOR_SIZE; i++) { | ||
732 | if (!buf[i]) | ||
733 | continue; | ||
734 | |||
735 | rc = do_writesect(dev, sector, buf, &old_addr); | ||
736 | if (rc) | ||
737 | goto err; | ||
738 | break; | ||
739 | } | ||
740 | |||
741 | if (i == SECTOR_SIZE) | ||
742 | part->sector_map[sector] = -1; | ||
743 | |||
744 | if (old_addr != -1) | ||
745 | rc = mark_sector_deleted(part, old_addr); | ||
746 | |||
747 | err: | ||
748 | return rc; | ||
749 | } | ||
750 | |||
751 | static int rfd_ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo) | ||
752 | { | ||
753 | struct partition *part = (struct partition*)dev; | ||
754 | |||
755 | geo->heads = 1; | ||
756 | geo->sectors = SECTORS_PER_TRACK; | ||
757 | geo->cylinders = part->cylinders; | ||
758 | |||
759 | return 0; | ||
760 | } | ||
761 | |||
762 | static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) | ||
763 | { | ||
764 | struct partition *part; | ||
765 | |||
766 | if (mtd->type != MTD_NORFLASH) | ||
767 | return; | ||
768 | |||
769 | part = kcalloc(1, sizeof(struct partition), GFP_KERNEL); | ||
770 | if (!part) | ||
771 | return; | ||
772 | |||
773 | part->mbd.mtd = mtd; | ||
774 | |||
775 | if (block_size) | ||
776 | part->block_size = block_size; | ||
777 | else { | ||
778 | if (!mtd->erasesize) { | ||
779 | printk(KERN_NOTICE PREFIX "please provide block_size"); | ||
780 | return; | ||
781 | } | ||
782 | else | ||
783 | part->block_size = mtd->erasesize; | ||
784 | } | ||
785 | |||
786 | if (scan_header(part) == 0) { | ||
787 | part->mbd.size = part->sector_count; | ||
788 | part->mbd.blksize = SECTOR_SIZE; | ||
789 | part->mbd.tr = tr; | ||
790 | part->mbd.devnum = -1; | ||
791 | if (!(mtd->flags & MTD_WRITEABLE)) | ||
792 | part->mbd.readonly = 1; | ||
793 | else if (part->errors) { | ||
794 | printk(KERN_NOTICE PREFIX "'%s': errors found, " | ||
795 | "setting read-only", mtd->name); | ||
796 | part->mbd.readonly = 1; | ||
797 | } | ||
798 | |||
799 | printk(KERN_INFO PREFIX "name: '%s' type: %d flags %x\n", | ||
800 | mtd->name, mtd->type, mtd->flags); | ||
801 | |||
802 | if (!add_mtd_blktrans_dev((void*)part)) | ||
803 | return; | ||
804 | } | ||
805 | |||
806 | kfree(part); | ||
807 | } | ||
808 | |||
809 | static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev) | ||
810 | { | ||
811 | struct partition *part = (struct partition*)dev; | ||
812 | int i; | ||
813 | |||
814 | for (i=0; i<part->total_blocks; i++) { | ||
815 | pr_debug("rfd_ftl_remove_dev:'%s': erase unit #%02d: %d erases\n", | ||
816 | part->mbd.mtd->name, i, part->blocks[i].erases); | ||
817 | } | ||
818 | |||
819 | del_mtd_blktrans_dev(dev); | ||
820 | vfree(part->sector_map); | ||
821 | kfree(part->header_cache); | ||
822 | kfree(part->blocks); | ||
823 | kfree(part); | ||
824 | } | ||
825 | |||
826 | struct mtd_blktrans_ops rfd_ftl_tr = { | ||
827 | .name = "rfd", | ||
828 | .major = RFD_FTL_MAJOR, | ||
829 | .part_bits = PART_BITS, | ||
830 | .readsect = rfd_ftl_readsect, | ||
831 | .writesect = rfd_ftl_writesect, | ||
832 | .getgeo = rfd_ftl_getgeo, | ||
833 | .add_mtd = rfd_ftl_add_mtd, | ||
834 | .remove_dev = rfd_ftl_remove_dev, | ||
835 | .owner = THIS_MODULE, | ||
836 | }; | ||
837 | |||
838 | static int __init init_rfd_ftl(void) | ||
839 | { | ||
840 | return register_mtd_blktrans(&rfd_ftl_tr); | ||
841 | } | ||
842 | |||
843 | static void __exit cleanup_rfd_ftl(void) | ||
844 | { | ||
845 | deregister_mtd_blktrans(&rfd_ftl_tr); | ||
846 | } | ||
847 | |||
848 | module_init(init_rfd_ftl); | ||
849 | module_exit(cleanup_rfd_ftl); | ||
850 | |||
851 | MODULE_LICENSE("GPL"); | ||
852 | MODULE_AUTHOR("Sean Young <sean@mess.org>"); | ||
853 | MODULE_DESCRIPTION("Support code for RFD Flash Translation Layer, " | ||
854 | "used by General Software's Embedded BIOS"); | ||
855 | |||