Root/
1 | /* |
2 | * Copyright (C) 2010, Maarten ter Huurne <maarten@treewalker.org> |
3 | * Flash Translation Layer for media players using firmware from China Chip. |
4 | * |
5 | * This initial implementation provides read-only access. |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify it |
8 | * under the terms of the GNU General Public License as published by the |
9 | * Free Software Foundation; either version 2 of the License, or (at your |
10 | * option) any later version. |
11 | * |
12 | * You should have received a copy of the GNU General Public License along |
13 | * with this program; if not, write to the Free Software Foundation, Inc., |
14 | * 675 Mass Ave, Cambridge, MA 02139, USA. |
15 | * |
16 | */ |
17 | |
18 | #include <linux/errno.h> |
19 | #include <linux/hdreg.h> |
20 | #include <linux/init.h> |
21 | #include <linux/module.h> |
22 | #include <linux/mtd/blktrans.h> |
23 | #include <linux/mtd/mtd.h> |
24 | #include <linux/mtd/nand.h> |
25 | #include <linux/slab.h> |
26 | #include <linux/types.h> |
27 | #include <mtd/mtd-abi.h> |
28 | |
29 | |
30 | #define SECTOR_SIZE 512 |
31 | |
32 | struct cc_ftl_partition { |
33 | struct mtd_blktrans_dev mbd; |
34 | |
35 | uint32_t *map; |
36 | }; |
37 | |
38 | static int cc_ftl_readsect(struct mtd_blktrans_dev *dev, unsigned long block, |
39 | char *buffer) |
40 | { |
41 | struct cc_ftl_partition *partition = (struct cc_ftl_partition *)dev; |
42 | struct mtd_info *mtd = dev->mtd; |
43 | uint64_t log_offs, phy_offs; |
44 | uint32_t log_blk, phy_blk; |
45 | size_t retlen; |
46 | int ret; |
47 | |
48 | /* Find physical location. */ |
49 | if (block >= dev->size) |
50 | return -EIO; |
51 | log_offs = ((uint64_t)block) * SECTOR_SIZE; |
52 | log_blk = mtd_div_by_eb(log_offs, mtd); |
53 | phy_blk = partition->map[log_blk]; |
54 | if (phy_blk == (uint32_t)-1) |
55 | return -EIO; |
56 | phy_offs = (uint64_t)phy_blk * mtd->erasesize; |
57 | phy_offs += mtd_mod_by_eb(log_offs, mtd); |
58 | |
59 | /* Read data. */ |
60 | ret = mtd_read(mtd, phy_offs, SECTOR_SIZE, &retlen, buffer); |
61 | if (ret == -EUCLEAN) /* sector contains correctable errors */ |
62 | ret = 0; |
63 | if (ret) |
64 | return ret; |
65 | if (retlen != SECTOR_SIZE) |
66 | return -EIO; |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | uint32_t *cc_ftl_build_block_map(struct mtd_info *mtd) |
72 | { |
73 | uint32_t num_blk = mtd_div_by_eb(mtd->size, mtd); |
74 | uint32_t blk_found; |
75 | uint32_t pages_per_blk = mtd_div_by_ws(mtd->erasesize, mtd); |
76 | uint32_t phy_blk; |
77 | uint8_t oob_buf[mtd->oobsize]; |
78 | unsigned int seq_offs = mtd->oobsize - 4; |
79 | uint32_t *map; |
80 | |
81 | // TODO: Is it worth reading multiple oobs at once? |
82 | // Reading two will at least help against bit errors. |
83 | struct mtd_oob_ops oob_ops = { |
84 | .mode = MTD_OPS_RAW, |
85 | .len = 0, |
86 | .ooblen = mtd->oobsize, |
87 | .datbuf = NULL, |
88 | .oobbuf = oob_buf, |
89 | }; |
90 | |
91 | map = kmalloc(sizeof(uint32_t) * num_blk, GFP_KERNEL); |
92 | if (!map) |
93 | return NULL; |
94 | memset(map, 0xFF, sizeof(uint32_t) * num_blk); |
95 | |
96 | blk_found = 0; |
97 | for (phy_blk = 0; phy_blk < num_blk; phy_blk++) { |
98 | loff_t ofs = (loff_t)phy_blk << mtd->erasesize_shift; |
99 | uint16_t signature; |
100 | uint16_t last_page; |
101 | uint32_t log_blk; |
102 | int err; |
103 | |
104 | if (mtd_block_isbad(mtd, ofs)) |
105 | continue; |
106 | err = mtd_read_oob(mtd, ofs, &oob_ops); |
107 | if (err) |
108 | continue; |
109 | signature = oob_buf[0] | ((uint16_t)oob_buf[1] << 8); |
110 | if (signature != 0x00FF) |
111 | continue; |
112 | last_page = oob_buf[2] | ((uint16_t)oob_buf[3] << 8); |
113 | if (last_page >= pages_per_blk) |
114 | continue; |
115 | log_blk = oob_buf[seq_offs] | |
116 | ((uint32_t)oob_buf[seq_offs + 1] << 8) | |
117 | ((uint32_t)oob_buf[seq_offs + 2] << 16) | |
118 | ((uint32_t)oob_buf[seq_offs + 3] << 24); |
119 | if (log_blk == 0xFFFFFFFF) |
120 | continue; |
121 | if (log_blk >= num_blk) { |
122 | printk(KERN_WARNING "physical block %d claims " |
123 | "logical block %d which is beyond " |
124 | "partition end %d\n", |
125 | phy_blk, log_blk, num_blk |
126 | ); |
127 | continue; |
128 | } |
129 | if (map[log_blk] != 0xFFFFFFFF) { |
130 | // TODO: Version number might sort this out. |
131 | printk(KERN_WARNING "physical block %d and %d both " |
132 | "claim logical block %d\n", |
133 | map[log_blk], phy_blk, log_blk |
134 | ); |
135 | continue; |
136 | } |
137 | map[log_blk] = phy_blk; |
138 | blk_found++; |
139 | } |
140 | |
141 | if (blk_found == 0) { |
142 | kfree(map); |
143 | return NULL; |
144 | } |
145 | |
146 | if (0) { |
147 | uint32_t log_blk; |
148 | for (log_blk = 0; log_blk < num_blk; log_blk++) { |
149 | if (map[log_blk] != 0xFFFFFFFF) { |
150 | printk("%04X:%04X ", log_blk, map[log_blk]); |
151 | } |
152 | } |
153 | printk("\n"); |
154 | } |
155 | |
156 | return map; |
157 | } |
158 | |
159 | static void cc_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) |
160 | { |
161 | struct cc_ftl_partition *partition; |
162 | uint32_t *map; |
163 | |
164 | /* Check for NAND first, so we know we can use the "chip" pointer. */ |
165 | if (mtd->type != MTD_NANDFLASH) |
166 | return; |
167 | |
168 | /* Erase size must be a power of two. */ |
169 | if (mtd->erasesize_shift == 0) |
170 | return; |
171 | |
172 | /* Erase size must be a multiple of sector size. */ |
173 | if ((mtd->erasesize & (SECTOR_SIZE - 1)) != 0) |
174 | return; |
175 | |
176 | /* Probably this translation layer can work with different oob sizes, |
177 | * but for now we only accept the layout it was tested with. |
178 | */ |
179 | if (mtd->oobsize != 128) |
180 | return; |
181 | |
182 | map = cc_ftl_build_block_map(mtd); |
183 | if (!map) |
184 | return; |
185 | |
186 | partition = kzalloc(sizeof(struct cc_ftl_partition), GFP_KERNEL); |
187 | if (!partition) |
188 | goto err_map; |
189 | |
190 | partition->mbd.mtd = mtd; |
191 | // TODO: More reasonable guess. |
192 | partition->mbd.size = mtd->size / SECTOR_SIZE; |
193 | partition->mbd.tr = tr; |
194 | partition->mbd.devnum = -1; |
195 | partition->map = map; |
196 | |
197 | if (add_mtd_blktrans_dev((struct mtd_blktrans_dev *)partition)) |
198 | goto err_partition; |
199 | |
200 | return; |
201 | |
202 | err_partition: |
203 | kfree(partition); |
204 | err_map: |
205 | kfree(map); |
206 | } |
207 | |
208 | static void cc_ftl_remove_dev(struct mtd_blktrans_dev *dev) |
209 | { |
210 | struct cc_ftl_partition *partition = (struct cc_ftl_partition *)dev; |
211 | |
212 | if (del_mtd_blktrans_dev(dev)) |
213 | return; |
214 | |
215 | kfree(partition->map); |
216 | kfree(partition); |
217 | } |
218 | |
219 | static struct mtd_blktrans_ops cc_ftl_tr = { |
220 | .name = "ccnand", |
221 | .major = 242, /* TODO: Register an official major number. */ |
222 | .part_bits = 4, |
223 | .blksize = SECTOR_SIZE, |
224 | |
225 | .readsect = cc_ftl_readsect, |
226 | .add_mtd = cc_ftl_add_mtd, |
227 | .remove_dev = cc_ftl_remove_dev, |
228 | |
229 | .owner = THIS_MODULE, |
230 | }; |
231 | |
232 | static int __init cc_ftl_init(void) |
233 | { |
234 | return register_mtd_blktrans(&cc_ftl_tr); |
235 | } |
236 | module_init(cc_ftl_init); |
237 | |
238 | static void __exit cc_ftl_exit(void) |
239 | { |
240 | deregister_mtd_blktrans(&cc_ftl_tr); |
241 | } |
242 | module_exit(cc_ftl_exit); |
243 | |
244 | MODULE_LICENSE("GPL"); |
245 | MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>"); |
246 | MODULE_DESCRIPTION("Flash Translation Layer for media players " |
247 | "using firmware from China Chip"); |
248 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9