xref: /inferno-os/os/boot/pc/dosboot.c (revision d0e1d143ef6f03c75c008c7ec648859dd260cbab)
1 #include	"u.h"
2 #include	"lib.h"
3 #include	"mem.h"
4 #include	"dat.h"
5 #include	"fns.h"
6 #include	"fs.h"
7 
8 struct Dosboot{
9 	uchar	magic[3];
10 	uchar	version[8];
11 	uchar	sectsize[2];
12 	uchar	clustsize;
13 	uchar	nresrv[2];
14 	uchar	nfats;
15 	uchar	rootsize[2];
16 	uchar	volsize[2];
17 	uchar	mediadesc;
18 	uchar	fatsize[2];
19 	uchar	trksize[2];
20 	uchar	nheads[2];
21 	uchar	nhidden[4];
22 	uchar	bigvolsize[4];
23 /* fat 32 */
24 	uchar	bigfatsize[4];
25 	uchar	extflags[2];
26 	uchar	fsversion[2];
27 	uchar	rootdirstartclust[4];
28 	uchar	fsinfosect[2];
29 	uchar	backupbootsect[2];
30 /* ???
31 	uchar	driveno;
32 	uchar	reserved0;
33 	uchar	bootsig;
34 	uchar	volid[4];
35 	uchar	label[11];
36 	uchar	reserved1[8];
37 */
38 };
39 
40 struct Dosdir{
41 	uchar	name[8];
42 	uchar	ext[3];
43 	uchar	attr;
44 	uchar	lowercase;
45 	uchar	hundredth;
46 	uchar	ctime[2];
47 	uchar	cdate[2];
48 	uchar	adate[2];
49 	uchar	highstart[2];
50 	uchar	mtime[2];
51 	uchar	mdate[2];
52 	uchar	start[2];
53 	uchar	length[4];
54 };
55 
56 #define	DOSRONLY	0x01
57 #define	DOSHIDDEN	0x02
58 #define	DOSSYSTEM	0x04
59 #define	DOSVLABEL	0x08
60 #define	DOSDIR	0x10
61 #define	DOSARCH	0x20
62 
63 /*
64  *  predeclared
65  */
66 static void	bootdump(Dosboot*);
67 static void	setname(Dosfile*, char*);
68 
69 /*
70  *  debugging
71  */
72 #define chatty	0
73 #define chat	if(chatty)print
74 
75 /*
76  *  block io buffers
77  */
78 enum
79 {
80 	Nbio=	16,
81 };
82 typedef struct	Clustbuf	Clustbuf;
83 struct Clustbuf
84 {
85 	int	age;
86 	long	sector;
87 	uchar	*iobuf;
88 	Dos	*dos;
89 	int	size;
90 };
91 Clustbuf	bio[Nbio];
92 
93 /*
94  *  get an io block from an io buffer
95  */
96 Clustbuf*
97 getclust(Dos *dos, long sector)
98 {
99 	Fs *fs;
100 	Clustbuf *p, *oldest;
101 	int size;
102 
103 	chat("getclust @ %ld\n", sector);
104 
105 	/*
106 	 *  if we have it, just return it
107 	 */
108 	for(p = bio; p < &bio[Nbio]; p++){
109 		if(sector == p->sector && dos == p->dos){
110 			p->age = m->ticks;
111 			chat("getclust %ld in cache\n", sector);
112 			return p;
113 		}
114 	}
115 
116 	/*
117 	 *  otherwise, reuse the oldest entry
118 	 */
119 	oldest = bio;
120 	for(p = &bio[1]; p < &bio[Nbio]; p++){
121 		if(p->age <= oldest->age)
122 			oldest = p;
123 	}
124 	p = oldest;
125 
126 	/*
127 	 *  make sure the buffer is big enough
128 	 */
129 	size = dos->clustsize*dos->sectsize;
130 	if(p->iobuf==0 || p->size < size)
131 		p->iobuf = ialloc(size, 0);
132 	p->size = size;
133 
134 	/*
135 	 *  read in the cluster
136 	 */
137 	fs = (Fs*)dos;
138 	chat("getclust addr %lud %p %p %p\n", (ulong)((sector+dos->start)*(vlong)dos->sectsize),
139 		fs, fs->diskseek, fs->diskread);
140 	if(fs->diskseek(fs, (sector+dos->start)*(vlong)dos->sectsize) < 0){
141 		chat("can't seek block\n");
142 		return 0;
143 	}
144 	if(fs->diskread(fs, p->iobuf, size) != size){
145 		chat("can't read block\n");
146 		return 0;
147 	}
148 
149 	p->age = m->ticks;
150 	p->dos = dos;
151 	p->sector = sector;
152 	chat("getclust %ld read\n", sector);
153 	return p;
154 }
155 
156 /*
157  *  walk the fat one level ( n is a current cluster number ).
158  *  return the new cluster number or -1 if no more.
159  */
160 static long
161 fatwalk(Dos *dos, int n)
162 {
163 	ulong k, sect;
164 	Clustbuf *p;
165 	int o;
166 
167 	chat("fatwalk %d\n", n);
168 
169 	if(n < 2 || n >= dos->fatclusters)
170 		return -1;
171 
172 	switch(dos->fatbits){
173 	case 12:
174 		k = (3*n)/2; break;
175 	case 16:
176 		k = 2*n; break;
177 	case 32:
178 		k = 4*n; break;
179 	default:
180 		return -1;
181 	}
182 	if(k >= dos->fatsize*dos->sectsize)
183 		panic("getfat");
184 
185 	sect = (k/(dos->sectsize*dos->clustsize))*dos->clustsize + dos->fataddr;
186 	o = k%(dos->sectsize*dos->clustsize);
187 	p = getclust(dos, sect);
188 	k = p->iobuf[o++];
189 	if(o >= dos->sectsize*dos->clustsize){
190 		p = getclust(dos, sect+dos->clustsize);
191 		o = 0;
192 	}
193 	k |= p->iobuf[o++]<<8;
194 	if(dos->fatbits == 12){
195 		if(n&1)
196 			k >>= 4;
197 		else
198 			k &= 0xfff;
199 		if(k >= 0xff8)
200 			k = -1;
201 	}
202 	else if (dos->fatbits == 32){
203 		if(o >= dos->sectsize*dos->clustsize){
204 			p = getclust(dos, sect+dos->clustsize);
205 			o = 0;
206 		}
207 		k |= p->iobuf[o++]<<16;
208 		k |= p->iobuf[o]<<24;
209 		if (k >= 0xfffffff8)
210 			k = -1;
211 	}
212 	else
213 		k = k < 0xfff8 ? k : -1;
214 	chat("fatwalk %d -> %lud\n", n, k);
215 	return k;
216 }
217 
218 /*
219  *  map a file's logical cluster address to a physical sector address
220  */
221 static long
222 fileaddr(Dosfile *fp, long ltarget)
223 {
224 	Dos *dos = fp->dos;
225 	long l;
226 	long p;
227 
228 	chat("fileaddr %8.8s %ld\n", fp->name, ltarget);
229 	/*
230 	 *  root directory is contiguous and easy (unless FAT32)
231 	 */
232 	if(fp->pstart == 0 && dos->rootsize != 0) {
233 		if(ltarget*dos->sectsize*dos->clustsize >= dos->rootsize*sizeof(Dosdir))
234 			return -1;
235 		l = dos->rootaddr + ltarget*dos->clustsize;
236 		chat("fileaddr %ld -> %ld\n", ltarget, l);
237 		return l;
238 	}
239 
240 	/*
241 	 *  anything else requires a walk through the fat
242 	 */
243 	if(ltarget >= fp->lcurrent && fp->pcurrent){
244 		/* start at the currrent point */
245 		l = fp->lcurrent;
246 		p = fp->pcurrent;
247 	} else {
248 		/* go back to the beginning */
249 		l = 0;
250 		p = fp->pstart;
251 	}
252 	while(l != ltarget){
253 		/* walk the fat */
254 		p = fatwalk(dos, p);
255 		if(p < 0)
256 			return -1;
257 		l++;
258 	}
259 	fp->lcurrent = l;
260 	fp->pcurrent = p;
261 
262 	/*
263 	 *  clusters start at 2 instead of 0 (why? - presotto)
264 	 */
265 	l =  dos->dataaddr + (p-2)*dos->clustsize;
266 	chat("fileaddr %ld -> %ld\n", ltarget, l);
267 	return l;
268 }
269 
270 /*
271  *  read from a dos file
272  */
273 long
274 dosread(Dosfile *fp, void *a, long n)
275 {
276 	long addr;
277 	long rv;
278 	int i;
279 	int off;
280 	Clustbuf *p;
281 	uchar *from, *to;
282 
283 	if((fp->attr & DOSDIR) == 0){
284 		if(fp->offset >= fp->length)
285 			return 0;
286 		if(fp->offset+n > fp->length)
287 			n = fp->length - fp->offset;
288 	}
289 
290 	to = a;
291 	for(rv = 0; rv < n; rv+=i){
292 		/*
293 		 *  read the cluster
294 		 */
295 		addr = fileaddr(fp, fp->offset/fp->dos->clustbytes);
296 		if(addr < 0)
297 			return -1;
298 		p = getclust(fp->dos, addr);
299 		if(p == 0)
300 			return -1;
301 
302 		/*
303 		 *  copy the bytes we need
304 		 */
305 		off = fp->offset % fp->dos->clustbytes;
306 		from = &p->iobuf[off];
307 		i = n - rv;
308 		if(i > fp->dos->clustbytes - off)
309 			i = fp->dos->clustbytes - off;
310 		memmove(to, from, i);
311 		to += i;
312 		fp->offset += i;
313 	}
314 
315 	return rv;
316 }
317 
318 /*
319  *  walk a directory returns
320  * 	-1 if something went wrong
321  *	 0 if not found
322  *	 1 if found
323  */
324 int
325 doswalk(File *f, char *name)
326 {
327 	Dosdir d;
328 	long n;
329 	Dosfile *file;
330 
331 	chat("doswalk %s\n", name);
332 
333 	file = &f->dos;
334 
335 	if((file->attr & DOSDIR) == 0){
336 		chat("walking non-directory!\n");
337 		return -1;
338 	}
339 
340 	setname(file, name);
341 
342 	file->offset = 0;	/* start at the beginning */
343 	while((n = dosread(file, &d, sizeof(d))) == sizeof(d)){
344 		chat("comparing to %8.8s.%3.3s\n", (char*)d.name, (char*)d.ext);
345 		if(memcmp(file->name, d.name, sizeof(d.name)) != 0)
346 			continue;
347 		if(memcmp(file->ext, d.ext, sizeof(d.ext)) != 0)
348 			continue;
349 		if(d.attr & DOSVLABEL){
350 			chat("%8.8s.%3.3s is a LABEL\n", (char*)d.name, (char*)d.ext);
351 			continue;
352 		}
353 		file->attr = d.attr;
354 		file->pstart = GSHORT(d.start);
355 		if (file->dos->fatbits == 32)
356 			file->pstart |= GSHORT(d.highstart) << 16;
357 		file->length = GLONG(d.length);
358 		file->pcurrent = 0;
359 		file->lcurrent = 0;
360 		file->offset = 0;
361 		return 1;
362 	}
363 	return n >= 0 ? 0 : -1;
364 }
365 
366 /*
367  *  instructions that boot blocks can start with
368  */
369 #define	JMPSHORT	0xeb
370 #define JMPNEAR		0xe9
371 
372 /*
373  *  read in a segment
374  */
375 long
376 dosreadseg(File *f, void *va, long len)
377 {
378 	char *a;
379 	long n, sofar;
380 	Dosfile *fp;
381 
382 	fp = &f->dos;
383 	a = va;
384 	for(sofar = 0; sofar < len; sofar += n){
385 		n = 8*1024;
386 		if(len - sofar < n)
387 			n = len - sofar;
388 		n = dosread(fp, a + sofar, n);
389 		if(n <= 0)
390 			break;
391 		print(".");
392 	}
393 	return sofar;
394 }
395 
396 int
397 dosinit(Fs *fs)
398 {
399 	Clustbuf *p;
400 	Dosboot *b;
401 	int i;
402 	Dos *dos;
403 	Dosfile *root;
404 
405 chat("dosinit0 %p %p %p\n", fs, fs->diskseek, fs->diskread);
406 
407 	dos = &fs->dos;
408 	/* defaults till we know better */
409 	dos->sectsize = 512;
410 	dos->clustsize = 1;
411 
412 	/* get first sector */
413 	p = getclust(dos, 0);
414 	if(p == 0){
415 		chat("can't read boot block\n");
416 		return -1;
417 	}
418 
419 chat("dosinit0a\n");
420 
421 	p->dos = 0;
422 	b = (Dosboot *)p->iobuf;
423 	if(b->magic[0] != JMPNEAR && (b->magic[0] != JMPSHORT || b->magic[2] != 0x90)){
424 		chat("no dos file system %x %x %x %x\n",
425 			b->magic[0], b->magic[1], b->magic[2], b->magic[3]);
426 		return -1;
427 	}
428 
429 	if(chatty)
430 		bootdump(b);
431 
432 	if(b->clustsize == 0) {
433 unreasonable:
434 		if(chatty){
435 			print("unreasonable FAT BPB: ");
436 			for(i=0; i<3+8+2+1; i++)
437 				print(" %.2ux", p->iobuf[i]);
438 			print("\n");
439 		}
440 		return -1;
441 	}
442 
443 chat("dosinit1\n");
444 
445 	/*
446 	 * Determine the systems' wondrous properties.
447 	 * There are heuristics here, but there's no real way
448 	 * of knowing if this is a reasonable FAT.
449 	 */
450 	dos->fatbits = 0;
451 	dos->sectsize = GSHORT(b->sectsize);
452 	if(dos->sectsize & 0xFF)
453 		goto unreasonable;
454 	dos->clustsize = b->clustsize;
455 	dos->clustbytes = dos->sectsize*dos->clustsize;
456 	dos->nresrv = GSHORT(b->nresrv);
457 	dos->nfats = b->nfats;
458 	dos->fatsize = GSHORT(b->fatsize);
459 	dos->rootsize = GSHORT(b->rootsize);
460 	dos->volsize = GSHORT(b->volsize);
461 	if(dos->volsize == 0)
462 		dos->volsize = GLONG(b->bigvolsize);
463 	dos->mediadesc = b->mediadesc;
464 	if(dos->fatsize == 0) {
465 		chat("fat32\n");
466 		dos->rootsize = 0;
467 		dos->fatsize = GLONG(b->bigfatsize);
468 		dos->fatbits = 32;
469 	}
470 	dos->fataddr = dos->nresrv;
471 	if (dos->rootsize == 0) {
472 		dos->rootaddr = 0;
473 		dos->rootclust = GLONG(b->rootdirstartclust);
474 		dos->dataaddr = dos->fataddr + dos->nfats*dos->fatsize;
475 	} else {
476 		dos->rootaddr = dos->fataddr + dos->nfats*dos->fatsize;
477 		i = dos->rootsize*sizeof(Dosdir) + dos->sectsize - 1;
478 		i = i/dos->sectsize;
479 		dos->dataaddr = dos->rootaddr + i;
480 	}
481 	dos->fatclusters = 2+(dos->volsize - dos->dataaddr)/dos->clustsize;
482 	if(dos->fatbits != 32) {
483 		if(dos->fatclusters < 4087)
484 			dos->fatbits = 12;
485 		else
486 			dos->fatbits = 16;
487 	}
488 	dos->freeptr = 2;
489 
490 	if(dos->clustbytes < 512 || dos->clustbytes > 64*1024)
491 		goto unreasonable;
492 
493 chat("dosinit2\n");
494 
495 	/*
496 	 *  set up the root
497 	 */
498 
499 	fs->root.fs = fs;
500 	root = &fs->root.dos;
501 	root->dos = dos;
502 	root->pstart = dos->rootsize == 0 ? dos->rootclust : 0;
503 	root->pcurrent = root->lcurrent = 0;
504 	root->offset = 0;
505 	root->attr = DOSDIR;
506 	root->length = dos->rootsize*sizeof(Dosdir);
507 
508 chat("dosinit3\n");
509 
510 	fs->read = dosreadseg;
511 	fs->walk = doswalk;
512 	return 0;
513 }
514 
515 static void
516 bootdump(Dosboot *b)
517 {
518 	if(chatty == 0)
519 		return;
520 	print("magic: 0x%2.2x 0x%2.2x 0x%2.2x ",
521 		b->magic[0], b->magic[1], b->magic[2]);
522 	print("version: \"%8.8s\"\n", (char*)b->version);
523 	print("sectsize: %d ", GSHORT(b->sectsize));
524 	print("allocsize: %d ", b->clustsize);
525 	print("nresrv: %d ", GSHORT(b->nresrv));
526 	print("nfats: %d\n", b->nfats);
527 	print("rootsize: %d ", GSHORT(b->rootsize));
528 	print("volsize: %d ", GSHORT(b->volsize));
529 	print("mediadesc: 0x%2.2x\n", b->mediadesc);
530 	print("fatsize: %d ", GSHORT(b->fatsize));
531 	print("trksize: %d ", GSHORT(b->trksize));
532 	print("nheads: %d ", GSHORT(b->nheads));
533 	print("nhidden: %d ", GLONG(b->nhidden));
534 	print("bigvolsize: %d\n", GLONG(b->bigvolsize));
535 /*
536 	print("driveno: %d\n", b->driveno);
537 	print("reserved0: 0x%2.2x\n", b->reserved0);
538 	print("bootsig: 0x%2.2x\n", b->bootsig);
539 	print("volid: 0x%8.8x\n", GLONG(b->volid));
540 	print("label: \"%11.11s\"\n", b->label);
541 */
542 }
543 
544 
545 /*
546  *  set up a dos file name
547  */
548 static void
549 setname(Dosfile *fp, char *from)
550 {
551 	char *to;
552 
553 	to = fp->name;
554 	for(; *from && to-fp->name < 8; from++, to++){
555 		if(*from == '.'){
556 			from++;
557 			break;
558 		}
559 		if(*from >= 'a' && *from <= 'z')
560 			*to = *from + 'A' - 'a';
561 		else
562 			*to = *from;
563 	}
564 	while(to - fp->name < 8)
565 		*to++ = ' ';
566 
567 	/* from might be 12345678.123: don't save the '.' in ext */
568 	if(*from == '.')
569 		from++;
570 
571 	to = fp->ext;
572 	for(; *from && to-fp->ext < 3; from++, to++){
573 		if(*from >= 'a' && *from <= 'z')
574 			*to = *from + 'A' - 'a';
575 		else
576 			*to = *from;
577 	}
578 	while(to-fp->ext < 3)
579 		*to++ = ' ';
580 
581 	chat("name is %8.8s.%3.3s\n", fp->name, fp->ext);
582 }
583