Initial commit
[savezelda.git] / loader / fat.c
1 // Copyright 2009  Segher Boessenkool  <segher@kernel.crashing.org>
2 // This code is licensed to you under the terms of the GNU GPL, version 2;
3 // see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
4
5
6 #include "loader.h"
7
8 #ifdef FAT_TEST
9 #include <stdio.h>
10 #endif
11
12
13 #define RAW_BUF 0x200
14 static u8 raw_buf[RAW_BUF] __attribute__((aligned(32)));
15
16 static int raw_read(u32 sector)
17 {
18         static u32 current = -1;
19
20         if (current == sector)
21                 return 0;
22         current = sector;
23
24         return sd_read_sector(raw_buf, sector);
25 }
26
27 static u64 partition_start_offset;
28
29 static int read(u8 *data, u64 offset, u32 len)
30 {
31         offset += partition_start_offset;
32
33         while (len) {
34                 u32 buf_off = offset % RAW_BUF;
35                 u32 n;
36
37                 n = RAW_BUF - buf_off;
38                 if (n > len)
39                         n = len;
40
41                 int err = raw_read(offset / RAW_BUF);
42                 if (err)
43                         return err;
44
45                 memcpy(data, raw_buf + buf_off, n);
46
47                 data += n;
48                 offset += n;
49                 len -= n;
50         }
51
52         return 0;
53 }
54
55
56 static u32 bytes_per_cluster;
57 static u32 root_entries;
58 static u32 clusters;
59 static u32 fat_type;    // 12, 16, or 32
60
61 static u64 fat_offset;
62 static u64 root_offset;
63 static u64 data_offset;
64
65
66 static u32 get_fat(u32 cluster)
67 {
68         u8 fat[4];
69
70         u32 offset_bits = cluster*fat_type;
71         int err = read(fat, fat_offset + offset_bits/8, 4);
72         if (err)
73                 return 0;
74
75         u32 res = le32(fat) >> (offset_bits % 8);
76         res &= (1 << fat_type) - 1;
77         res &= 0x0fffffff;              // for FAT32
78
79         return res;
80 }
81
82
83 static u64 extent_offset;
84 static u32 extent_len;
85 static u32 extent_next_cluster;
86
87 static void get_extent(u32 cluster)
88 {
89         extent_len = 0;
90         extent_next_cluster = 0;
91
92         if (cluster == 0) {     // Root directory.
93                 if (fat_type != 32) {
94                         extent_offset = root_offset;
95                         extent_len = 0x20*root_entries;
96
97                         return;
98                 }
99                 cluster = root_offset;
100         }
101
102         if (cluster - 2 >= clusters)
103                 return;
104
105         extent_offset = data_offset + (u64)bytes_per_cluster*(cluster - 2);
106
107         for (;;) {
108                 extent_len += bytes_per_cluster;
109
110                 u32 next_cluster = get_fat(cluster);
111
112                 if (next_cluster - 2 >= clusters)
113                         break;
114
115                 if (next_cluster != cluster + 1) {
116                         extent_next_cluster = next_cluster;
117                         break;
118                 }
119
120                 cluster = next_cluster;
121         }
122 }
123
124
125 static int read_extent(u8 *data, u32 len)
126 {
127         while (len) {
128                 if (extent_len == 0)
129                         return -1;
130
131                 u32 this = len;
132                 if (this > extent_len)
133                         this = extent_len;
134
135                 int err = read(data, extent_offset, this);
136                 if (err)
137                         return err;
138
139                 extent_offset += this;
140                 extent_len -= this;
141
142                 data += this;
143                 len -= this;
144
145                 if (extent_len == 0 && extent_next_cluster)
146                         get_extent(extent_next_cluster);
147         }
148
149         return 0;
150 }
151
152
153 int fat_read(void *data, u32 len)
154 {
155         return read_extent(data, len);
156 }
157
158
159 static u8 fat_name[11];
160
161 static u8 ucase(char c)
162 {
163         if (c >= 'a' && c <= 'z')
164                 return c - 'a' + 'A';
165
166         return c;
167 }
168
169 static const char *parse_component(const char *path)
170 {
171         u32 i = 0;
172
173         while (*path == '/')
174                 path++;
175
176         while (*path && *path != '/' && *path != '.') {
177                 if (i < 8)
178                         fat_name[i++] = ucase(*path);
179                 path++;
180         }
181
182         while (i < 8)
183                 fat_name[i++] = ' ';
184
185         if (*path == '.')
186                 path++;
187
188         while (*path && *path != '/') {
189                 if (i < 11)
190                         fat_name[i++] = ucase(*path);
191                 path++;
192         }
193
194         while (i < 11)
195                 fat_name[i++] = ' ';
196
197         if (fat_name[0] == 0xe5)
198                 fat_name[0] = 0x05;
199
200         return path;
201 }
202
203
204 u32 fat_file_size;
205
206 int fat_open(const char *name)
207 {
208         u32 cluster = 0;
209
210         while (*name) {
211                 get_extent(cluster);
212
213                 name = parse_component(name);
214
215                 while (extent_len) {
216                         u8 dir[0x20];
217
218                         int err = read_extent(dir, 0x20);
219                         if (err)
220                                 return err;
221
222                         if (dir[0] == 0)
223                                 return -1;
224
225                         if (dir[0x0b] & 0x08)   // volume label or LFN
226                                 continue;
227                         if (dir[0x00] == 0xe5)  // deleted file
228                                 continue;
229
230                         if (!!*name != !!(dir[0x0b] & 0x10))    // dir vs. file
231                                 continue;
232
233                         if (memcmp(fat_name, dir, 11) == 0) {
234                                 cluster = le16(dir + 0x1a);
235                                 if (fat_type == 32)
236                                         cluster |= le16(dir + 0x14) << 16;
237
238                                 if (*name == 0) {
239                                         fat_file_size = le32(dir + 0x1c);
240                                         get_extent(cluster);
241
242                                         return 0;
243                                 }
244
245                                 break;
246                         }
247                 }
248         }
249
250         return -1;
251 }
252
253
254 #ifdef FAT_TEST
255 static void print_dir_entry(u8 *dir)
256 {
257         int i, n;
258
259         if (dir[0x0b] & 0x08)   // volume label or LFN
260                 return;
261         if (dir[0x00] == 0xe5)  // deleted file
262                 return;
263
264         if (fat_type == 32) {
265                 fprintf(stderr, "#%04x", le16(dir + 0x14));
266                 fprintf(stderr, "%04x  ", le16(dir + 0x1a));
267         } else
268                 fprintf(stderr, "#%04x  ", le16(dir + 0x1a));   // start cluster
269         u16 date = le16(dir + 0x18);
270         fprintf(stderr, "%04d-%02d-%02d ", 1980 + (date >> 9), (date >> 5) & 0x0f, date & 0x1f);
271         u16 time = le16(dir + 0x16);
272         fprintf(stderr, "%02d:%02d:%02d  ", time >> 11, (time >> 5) & 0x3f, 2*(time & 0x1f));
273         fprintf(stderr, "%10d  ", le32(dir + 0x1c));    // file size
274         u8 attr = dir[0x0b];
275         for (i = 0; i < 6; i++)
276                 fprintf(stderr, "%c", (attr & (1 << i)) ? "RHSLDA"[i] : ' ');
277         fprintf(stderr, "  ");
278         for (n = 8; n && dir[n - 1] == ' '; n--)
279                 ;
280         for (i = 0; i < n; i++)
281                 fprintf(stderr, "%c", dir[i]);
282         for (n = 3; n && dir[8 + n - 1] == ' '; n--)
283                 ;
284         if (n) {
285                 fprintf(stderr, ".");
286                 for (i = 0; i < n; i++)
287                         fprintf(stderr, "%c", dir[8 + i]);
288         }
289
290         fprintf(stderr, "\n");
291 }
292
293
294 int print_dir(u32 cluster)
295 {
296         u8 dir[0x20];
297
298         get_extent(cluster);
299
300         while (extent_len) {
301                 int err = read_extent(dir, 0x20);
302                 if (err)
303                         return err;
304
305                 if (dir[0] == 0)
306                         break;
307
308                 print_dir_entry(dir);
309         }
310
311         return 0;
312 }
313 #endif
314
315
316 static int fat_init_fs(const u8 *sb)
317 {
318         u32 bytes_per_sector = le16(sb + 0x0b);
319         u32 sectors_per_cluster = sb[0x0d];
320         bytes_per_cluster = bytes_per_sector * sectors_per_cluster;
321
322         u32 reserved_sectors = le16(sb + 0x0e);
323         u32 fats = sb[0x10];
324         root_entries = le16(sb + 0x11);
325         u32 total_sectors = le16(sb + 0x13);
326         u32 sectors_per_fat = le16(sb + 0x16);
327
328         // For FAT16 and FAT32:
329         if (total_sectors == 0)
330                 total_sectors = le32(sb + 0x20);
331
332         // For FAT32:
333         if (sectors_per_fat == 0)
334                 sectors_per_fat = le32(sb + 0x24);
335
336         // XXX: For FAT32, we might want to look at offsets 28, 2a
337         // XXX: We _do_ need to look at 2c
338
339         u32 fat_sectors = sectors_per_fat * fats;
340         u32 root_sectors = (0x20*root_entries + bytes_per_sector - 1)
341                            / bytes_per_sector;
342
343         u32 fat_start_sector = reserved_sectors;
344         u32 root_start_sector = fat_start_sector + fat_sectors;
345         u32 data_start_sector = root_start_sector + root_sectors;
346
347         clusters = (total_sectors - data_start_sector) / sectors_per_cluster;
348
349         if (clusters < 0x0ff5)
350                 fat_type = 12;
351         else if (clusters < 0xfff5)
352                 fat_type = 16;
353         else
354                 fat_type = 32;
355
356         fat_offset = (u64)bytes_per_sector*fat_start_sector;
357         root_offset = (u64)bytes_per_sector*root_start_sector;
358         data_offset = (u64)bytes_per_sector*data_start_sector;
359
360         if (fat_type == 32)
361                 root_offset = le32(sb + 0x2c);
362
363 #ifdef FAT_TEST
364         fprintf(stderr, "bytes_per_sector    = %08x\n", bytes_per_sector);
365         fprintf(stderr, "sectors_per_cluster = %08x\n", sectors_per_cluster);
366         fprintf(stderr, "bytes_per_cluster   = %08x\n", bytes_per_cluster);
367         fprintf(stderr, "root_entries        = %08x\n", root_entries);
368         fprintf(stderr, "clusters            = %08x\n", clusters);
369         fprintf(stderr, "fat_type            = %08x\n", fat_type);
370         fprintf(stderr, "fat_offset          = %012llx\n", fat_offset);
371         fprintf(stderr, "root_offset         = %012llx\n", root_offset);
372         fprintf(stderr, "data_offset         = %012llx\n", data_offset);
373 #endif
374
375         return 0;
376 }
377
378
379 static int is_fat_fs(const u8 *sb)
380 {
381         // Bytes per sector should be 512, 1024, 2048, or 4096
382         u32 bps = le16(sb + 0x0b);
383         if (bps < 0x0200 || bps > 0x1000 || bps & (bps - 1))
384                 return 0;
385
386         // Media type should be f0 or f8,...,ff
387         if (sb[0x15] < 0xf8 && sb[0x15] != 0xf0)
388                 return 0;
389
390         // If those checks didn't fail, it's FAT.  We hope.
391         return 1;
392 }
393
394
395 int fat_init(void)
396 {
397         u8 buf[0x200];
398         int err;
399
400         partition_start_offset = 0;
401         err = read(buf, 0, 0x200);
402         if (err)
403                 return err;
404
405         if (le16(buf + 0x01fe) != 0xaa55)       // Not a DOS disk.
406                 return -1;
407
408         if (is_fat_fs(buf))
409                 return fat_init_fs(buf);
410
411         // Maybe there's a partition table?  Let's try the first partition.
412         if (buf[0x01c2] == 0)
413                 return -1;
414
415         partition_start_offset = 0x200ULL*le32(buf + 0x01c6);
416
417         err = read(buf, 0, 0x200);
418         if (err)
419                 return err;
420
421         if (is_fat_fs(buf))
422                 return fat_init_fs(buf);
423
424         return -1;
425 }