xref: /inferno-os/os/boot/pc/devbios.c (revision 7ef44d652ae9e5e1f5b3465d73684e4a54de73c0)
1 /*
2  * boot driver for BIOS devices
3  */
4 #include <u.h>
5 #include "lib.h"
6 #include "mem.h"
7 #include "dat.h"
8 #include "fns.h"
9 #include "io.h"
10 #include "ureg.h"
11 #include "fs.h"
12 
13 typedef uvlong Devbytes, Devsects;
14 
15 typedef struct Biosdrive Biosdrive;	/* 1 drive -> ndevs */
16 typedef struct Biosdev Biosdev;
17 
18 enum {
19 	Debug = 0,
20 	Maxdevs = 4,
21 
22 	CF = 1,
23 	Flopid = 0,			/* first floppy */
24 	Baseid = 0x80,			/* first disk */
25 
26 	/* bios calls: int 13 disk services */
27 	Biosinit	= 0,		/* initialise disk & floppy ctlrs */
28 	Biosdrvsts,
29 	Bioschsrdsects,
30 	Biosdrvparam	= 8,
31 	Biosctlrinit,
32 	Biosreset	=  0xd,		/* reset disk */
33 	Biosdrvrdy	= 0x10,
34 	Biosdrvtype	= 0x15,
35 	Biosckext	= 0x41,
36 	Biosrdsect,
37 	Biosedrvparam	= 0x48,
38 
39 	/* disk types */
40 	Typenone = 0,
41 	Typedisk = 3,
42 };
43 
44 struct Biosdrive {
45 	int	ndevs;
46 };
47 struct Biosdev {
48 	Devbytes size;
49 	Devbytes offset;
50 	uchar	id;			/* drive number; e.g., 0x80 */
51 	char	type;
52 	ushort	sectsz;
53 };
54 
55 typedef struct Extread {
56 	uchar	size;
57 	uchar	unused1;
58 	uchar	nsects;
59 	uchar	unused2;
60 	ulong	addr;		/* segment:offset */
61 	uvlong	stsect;		/* starting sector */
62 } Extread;
63 typedef struct Edrvparam {
64 	/* from edd 1.1 spec */
65 	ushort	size;			/* max. buffer size */
66 	ushort	flags;
67 	ulong	physcyls;
68 	ulong	physheads;
69 	ulong	phystracksects;
70 	uvlong	physsects;
71 	ushort	sectsz;
72 	void	*dpte;			/* ~0ull: invalid */
73 
74 	/* remainder from edd 3.0 spec */
75 	ushort	key;			/* 0xbedd if present */
76 	uchar	dpilen;
77 	uchar	unused1;
78 	ushort	unused2;
79 	char	bustype[4];		/* "PCI" or "ISA" */
80 	char	ifctype[8]; /* "ATA", "ATAPI", "SCSI", "USB", "1394", "FIBRE" */
81 	uvlong	ifcpath;
82 	uvlong	devpath;
83 	uchar	unused3;
84 	uchar	dpicksum;
85 } Edrvparam;
86 
87 void	realmode(int intr, Ureg *ureg);		/* from trap.c */
88 
89 int onlybios0;
90 int biosinited;
91 
92 static Biosdev bdev[Maxdevs];
93 static Biosdrive bdrive;
94 static Ureg regs;
95 
96 static int	dreset(uchar drive);
97 static Devbytes	extgetsize(Biosdev *);
98 static Devsects	getsize(uchar drive, char *type);
99 static int	islba(uchar drive);
100 
101 static int
102 biosdiskcall(Ureg *rp, uchar op, ulong bx, ulong dx, ulong si)
103 {
104 	memset(rp, 0, sizeof *rp);
105 	rp->ax = op << 8;
106 	rp->bx = bx;
107 	rp->dx = dx;			/* often drive id */
108 	rp->si = si;
109 	/* pass command in *rp, get results from there */
110 	realmode(0x13, rp);
111 	if (rp->flags & CF) {
112 //		print("biosdiskcall: int 13 op 0x%ux drive 0x%lux failed, "
113 //			"ah error code 0x%ux\n", op, dx, (uchar)(rp->ax >> 8));
114 		return -1;
115 	}
116 	return 0;
117 }
118 
119 /*
120  * Find out what the bios knows about devices.
121  * our boot device could be usb; ghod only knows where it will appear.
122  */
123 int
124 biosinit(void)
125 {
126 	int devid, lba, mask, lastbit;
127 	Devbytes size;
128 	char type;
129 	Biosdev *bdp;
130 	static int beenhere;
131 
132 	mask = lastbit = 0;
133 	if (beenhere)
134 		return mask;
135 	beenhere = 1;
136 	/* 9pxeload can't use bios int 13 calls; they wedge the machine */
137 	if (pxe || getconf("*nobiosload") != nil || onlybios0 || !biosinited)
138 		return mask;
139 	for (devid = 0; devid < (1 << 8) && bdrive.ndevs < Maxdevs; devid++) {
140 		lba = islba(devid);
141 		if(!lba /* || devid != Baseid && dreset(devid) < 0 */ )
142 			continue;
143 		type = Typedisk;		/* HACK */
144 		if (getsize(devid, &type) == 0) { /* no device, end of range */
145 			devid &= ~0xf;
146 			devid += 0x10;
147 			devid--;
148 			continue;
149 		}
150 		lastbit = 1 << bdrive.ndevs;
151 		mask |= lastbit;
152 		bdp = &bdev[bdrive.ndevs];
153 		bdp->id = devid;
154 		bdp->type = type;
155 		size = extgetsize(bdp);
156 		bdp->size = size;
157 		print("bios%d: drive 0x%ux: %llud bytes, type %d\n",
158 			bdrive.ndevs, devid, size, type);
159 		bdrive.ndevs++;
160 	}
161 	/*
162 	 * bioses seem to only be able to read from drive number 0x80
163 	 * and certainly can't read from the highest drive number when we
164 	 * call them, even if there is only one.  attempting to read from
165 	 * the last drive number yields a hung machine or a two-minute pause.
166 	 */
167 	if (bdrive.ndevs > 0) {
168 		if (bdrive.ndevs == 1) {
169 			print("biosinit: sorry, only one bios drive; "
170 				"can't read last one\n");
171 			onlybios0 = 1;
172 		} else
173 			biosinited = 1;
174 		bdrive.ndevs--;	/* omit last drive number; it can't be read */
175 		mask &= ~lastbit;
176 	}
177 	return mask;
178 }
179 
180 void
181 biosinitdev(int i, char *name)
182 {
183 	if(i >= bdrive.ndevs)
184 		panic("biosinitdev");
185 	sprint(name, "bios%d", i);
186 }
187 
188 void
189 biosprintdevs(int i)
190 {
191 	if(i >= bdrive.ndevs){
192 		print("got a print for %d, only got %d\n", i, bdrive.ndevs);
193 		panic("biosprintdevs");
194 	}
195 	print(" bios%d", i);
196 }
197 
198 int
199 biosboot(int dev, char *file, Boot *b)
200 {
201 	Fs *fs;
202 
203 	if(strncmp(file, "dos!", 4) == 0)
204 		file += 4;
205 	if(strchr(file, '!') != nil || strcmp(file, "") == 0) {
206 		print("syntax is bios0!file\n");
207 		return -1;
208 	}
209 
210 	fs = biosgetfspart(dev, "9fat", 1);
211 	if(fs == nil)
212 		return -1;
213 	return fsboot(fs, file, b);
214 }
215 
216 /* read n bytes at sector offset into a from drive id */
217 long
218 sectread(Biosdev *bdp, void *a, long n, Devsects offset)
219 {
220 	uchar *biosparam, *cp;
221 	Extread *erp;
222 
223 	if(n < 0 || n > bdp->sectsz)
224 		return -1;
225 	if(Debug)
226 		memset((uchar *)BIOSXCHG, 'r', bdp->sectsz); /* preclean the buffer. */
227 
228 	biosdiskcall(&regs, Biosdrvrdy, 0, bdp->id, 0);
229 
230 	/* space for a BIG sector, just in case... */
231 	biosparam = (uchar *)BIOSXCHG + 2*1024;
232 
233 	/* read into BIOSXCHG */
234 	erp = (Extread *)biosparam;
235 	memset(erp, 0, sizeof *erp);
236 	erp->size = sizeof *erp;
237 	erp->nsects = 1;
238 	erp->addr = PADDR(BIOSXCHG);
239 	erp->stsect = offset;
240 	if (biosdiskcall(&regs, Biosrdsect, 0, bdp->id, PADDR(erp)) < 0) {
241 		print("sectread: bios failed to read %ld @ sector %lld of 0x%ux\n",
242 			n, offset, bdp->id);
243 		return -1;
244 	}
245 
246 	/* copy into caller's buffer */
247 	memmove(a, (char *)BIOSXCHG, n);
248 	if(Debug){
249 		cp = (uchar *)BIOSXCHG;
250 		print("-%ux %ux %ux %ux--%16.16s-\n",
251 			cp[0], cp[1], cp[2], cp[3], (char *)cp + 480);
252 	}
253 	return n;
254 }
255 
256 /* not tested yet. */
257 static int
258 dreset(uchar drive)
259 {
260 if (0) {
261 print("devbios: resetting disk controllers...");
262 	biosdiskcall(&regs, Biosinit, 0, drive, 0);
263 print("\n");
264 }
265 	return regs.ax? -1: 0;		/* ax!=0 on error */
266 }
267 
268 static int
269 islba(uchar drive)
270 {
271 	if (biosdiskcall(&regs, Biosckext, 0x55aa, drive, 0) < 0)
272 		return 0;
273 	if(regs.bx != 0xaa55){
274 		print("islba: buggy bios\n");
275 		return 0;
276 	}
277 	if (Debug)
278 		print("islba: drive 0x%ux extensions version %d.%d cx 0x%lux\n",
279 			drive, (uchar)(regs.ax >> 8),
280 			(uchar)regs.ax, regs.cx); /* cx: 4=edd, 1=use dap */
281 	return regs.cx & 1;		/* dap bit */
282 }
283 
284 /*
285  * works so so... some floppies are 0x80+x when they shouldn't be,
286  * and report lba even if they cannot...
287  */
288 static Devsects
289 getsize(uchar id, char *typep)
290 {
291 	int dtype;
292 
293 	if (biosdiskcall(&regs, Biosdrvtype, 0x55aa, id, 0) < 0)
294 		return 0;
295 
296 	dtype = (ushort)regs.ax >> 8;
297 	if(dtype == Typenone){
298 		print("no such device 0x%ux of type %d\n", id, dtype);
299 		return 0;
300 	}
301 	if(dtype != Typedisk){
302 		print("non-disk device 0x%ux of type %d\n", id, dtype);
303 		return 0;
304 	}
305 	*typep = dtype;
306 	return (ushort)regs.cx | regs.dx << 16;
307 }
308 
309 /* extended get size */
310 static Devbytes
311 extgetsize(Biosdev *bdp)
312 {
313 	Edrvparam *edp;
314 
315 	edp = (Edrvparam *)BIOSXCHG;
316 	memset(edp, 0, sizeof *edp);
317 	edp->size = sizeof *edp;
318 	edp->dpilen = 36;
319 	if (biosdiskcall(&regs, Biosedrvparam, 0, bdp->id, PADDR(edp)) < 0)
320 		return 0;
321 	if(Debug) {
322 		print("extgetsize: drive 0x%ux info flags 0x%ux",
323 			bdp->id, edp->flags);
324 		if (edp->key == 0xbedd)
325 			print(" %s %s", edp->bustype, edp->ifctype);
326 		print("\n");
327 	}
328 	if (edp->sectsz <= 0) {
329 		print("extgetsize: drive 0x%ux: non-positive sector size\n",
330 			bdp->id);
331 		edp->sectsz = 1;		/* don't divide by zero */
332 	}
333 	bdp->sectsz = edp->sectsz;
334 	return edp->physsects * edp->sectsz;
335 }
336 
337 long
338 biosread(Fs *fs, void *a, long n)
339 {
340 	int want, got, part;
341 	long totnr, stuck;
342 	Devbytes offset;
343 	Biosdev *bdp;
344 
345 	if(fs->dev > bdrive.ndevs)
346 		return -1;
347 	if (n <= 0)
348 		return n;
349 	bdp = &bdev[fs->dev];
350 	offset = bdp->offset;
351 	stuck = 0;
352 	for (totnr = 0; totnr < n && stuck < 4; totnr += got) {
353 		want = bdp->sectsz;
354 		if (totnr + want > n)
355 			want = n - totnr;
356 		if(Debug)
357 			print("bios%d, read: %ld @ off %lld, want: %d, id: 0x%ux\n",
358 				fs->dev, n, offset, want, bdp->id);
359 		part = offset % bdp->sectsz;
360 		if (part != 0) {	/* back up to start of sector */
361 			offset -= part;
362 			totnr  -= part;
363 			if (totnr < 0) {
364 				print("biosread: negative count %ld\n", totnr);
365 				return -1;
366 			}
367 		}
368 		if ((vlong)offset < 0) {
369 			print("biosread: negative offset %lld\n", offset);
370 			return -1;
371 		}
372 		got = sectread(bdp, (char *)a + totnr, want, offset/bdp->sectsz);
373 		if(got <= 0){
374 //			print("biosread: failed to read %ld @ off %lld of 0x%ux, "
375 //				"want %d got %d\n",
376 //				n, offset, bdp->id, want, got);
377 			return -1;
378 		}
379 		offset += got;
380 		bdp->offset = offset;
381 		if (got < bdp->sectsz)
382 			stuck++;	/* we'll have to re-read this sector */
383 		else
384 			stuck = 0;
385 	}
386 	return totnr;
387 }
388 
389 vlong
390 biosseek(Fs *fs, vlong off)
391 {
392 	if (off < 0) {
393 		print("biosseek(fs, %lld) is illegal\n", off);
394 		return -1;
395 	}
396 	if(fs->dev > bdrive.ndevs) {
397 		print("biosseek: fs->dev %d > bdrive.ndevs %d\n",
398 			fs->dev, bdrive.ndevs);
399 		return -1;
400 	}
401 	bdev[fs->dev].offset = off;	/* do not know size... (yet) */
402 	return off;
403 }
404 
405 void *
406 biosgetfspart(int i, char *name, int chatty)
407 {
408 	static Fs fs;
409 
410 	if(strcmp(name, "9fat") != 0){
411 		if(chatty)
412 			print("unknown partition bios%d!%s (use bios%d!9fat)\n",
413 				i, name, i);
414 		return nil;
415 	}
416 
417 	fs.dev = i;
418 	fs.diskread = biosread;
419 	fs.diskseek = biosseek;
420 
421 	if(dosinit(&fs) < 0){
422 		if(chatty)
423 			print("bios%d!%s does not contain a FAT file system\n",
424 				i, name);
425 		return nil;
426 	}
427 	return &fs;
428 }
429