xref: /titanic_52/usr/src/boot/sys/boot/i386/libi386/bioscd.c (revision a5a5c3b743b38bcec1dcdc221fc56e3c2272e79f)
1 /*-
2  * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3  * Copyright (c) 2001 John H. Baldwin <jhb@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 
30 /*
31  * BIOS CD device handling for CD's that have been booted off of via no
32  * emulation booting as defined in the El Torito standard.
33  *
34  * Ideas and algorithms from:
35  *
36  * - FreeBSD libi386/biosdisk.c
37  *
38  */
39 
40 #include <stand.h>
41 
42 #include <sys/param.h>
43 #include <machine/bootinfo.h>
44 
45 #include <stdarg.h>
46 
47 #include <bootstrap.h>
48 #include <btxv86.h>
49 #include <edd.h>
50 #include "libi386.h"
51 
52 #define BIOSCD_SECSIZE		2048
53 #define BUFSIZE			(1 * BIOSCD_SECSIZE)
54 #define	MAXBCDEV		1
55 
56 /* Major numbers for devices we frontend for. */
57 #define ACDMAJOR		117
58 #define	CDMAJOR			15
59 
60 #ifdef DISK_DEBUG
61 # define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
62 #else
63 # define DEBUG(fmt, args...)
64 #endif
65 
66 struct specification_packet {
67 	u_char	sp_size;
68 	u_char	sp_bootmedia;
69 	u_char	sp_drive;
70 	u_char	sp_controller;
71 	u_int	sp_lba;
72 	u_short	sp_devicespec;
73 	u_short	sp_buffersegment;
74 	u_short	sp_loadsegment;
75 	u_short	sp_sectorcount;
76 	u_short	sp_cylsec;
77 	u_char	sp_head;
78 };
79 
80 /*
81  * List of BIOS devices, translation from disk unit number to
82  * BIOS unit number.
83  */
84 static struct bcinfo {
85 	int	bc_unit;		/* BIOS unit number */
86 	struct specification_packet bc_sp;
87 	int	bc_open;		/* reference counter */
88 	void	*bc_bcache;		/* buffer cache data */
89 } bcinfo [MAXBCDEV];
90 static int nbcinfo = 0;
91 
92 #define	BC(dev)	(bcinfo[(dev)->d_unit])
93 
94 static int	bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
95 static int	bc_init(void);
96 static int	bc_strategy(void *devdata, int flag, daddr_t dblk,
97     size_t size, char *buf, size_t *rsize);
98 static int	bc_realstrategy(void *devdata, int flag, daddr_t dblk,
99     size_t size, char *buf, size_t *rsize);
100 static int	bc_open(struct open_file *f, ...);
101 static int	bc_close(struct open_file *f);
102 static int	bc_print(int verbose);
103 
104 struct devsw bioscd = {
105 	"cd",
106 	DEVT_CD,
107 	bc_init,
108 	bc_strategy,
109 	bc_open,
110 	bc_close,
111 	noioctl,
112 	bc_print,
113 	NULL
114 };
115 
116 /*
117  * Translate between BIOS device numbers and our private unit numbers.
118  */
119 int
120 bc_bios2unit(int biosdev)
121 {
122 	int i;
123 
124 	DEBUG("looking for bios device 0x%x", biosdev);
125 	for (i = 0; i < nbcinfo; i++) {
126 		DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
127 		if (bcinfo[i].bc_unit == biosdev)
128 			return(i);
129 	}
130 	return(-1);
131 }
132 
133 int
134 bc_unit2bios(int unit)
135 {
136 	if ((unit >= 0) && (unit < nbcinfo))
137 		return(bcinfo[unit].bc_unit);
138 	return(-1);
139 }
140 
141 /*
142  * We can't quiz, we have to be told what device to use, so this functoin
143  * doesn't do anything.  Instead, the loader calls bc_add() with the BIOS
144  * device number to add.
145  */
146 static int
147 bc_init(void)
148 {
149 
150 	return (0);
151 }
152 
153 int
154 bc_add(int biosdev)
155 {
156 
157 	if (nbcinfo >= MAXBCDEV)
158 		return (-1);
159 	bcinfo[nbcinfo].bc_unit = biosdev;
160 	v86.ctl = V86_FLAGS;
161 	v86.addr = 0x13;
162 	v86.eax = 0x4b01;
163 	v86.edx = biosdev;
164 	v86.ds = VTOPSEG(&bcinfo[nbcinfo].bc_sp);
165 	v86.esi = VTOPOFF(&bcinfo[nbcinfo].bc_sp);
166 	v86int();
167 	if ((v86.eax & 0xff00) != 0)
168 		return (-1);
169 
170 	printf("BIOS CD is cd%d\n", nbcinfo);
171 	nbcinfo++;
172 	bcache_add_dev(nbcinfo);	/* register cd device in bcache */
173 	return(0);
174 }
175 
176 /*
177  * Print information about disks
178  */
179 static int
180 bc_print(int verbose)
181 {
182 	char line[80];
183 	int i, ret = 0;
184 
185 	if (nbcinfo == 0)
186 		return (0);
187 
188 	printf("%s devices:", bioscd.dv_name);
189 	if ((ret = pager_output("\n")) != 0)
190 		return (ret);
191 
192 	for (i = 0; i < nbcinfo; i++) {
193 		sprintf(line, "    cd%d: Device 0x%x\n", i,
194 		    bcinfo[i].bc_sp.sp_devicespec);
195 		ret = pager_output(line);
196 		if (ret != 0)
197 			return (ret);
198 	}
199 	return (ret);
200 }
201 
202 /*
203  * Attempt to open the disk described by (dev) for use by (f).
204  */
205 static int
206 bc_open(struct open_file *f, ...)
207 {
208 	va_list ap;
209 	struct i386_devdesc *dev;
210 
211 	va_start(ap, f);
212 	dev = va_arg(ap, struct i386_devdesc *);
213 	va_end(ap);
214 	if (dev->d_unit >= nbcinfo) {
215 		DEBUG("attempt to open nonexistent disk");
216 		return(ENXIO);
217 	}
218 
219 	BC(dev).bc_open++;
220 	if (BC(dev).bc_bcache == NULL)
221 	    BC(dev).bc_bcache = bcache_allocate();
222 	return(0);
223 }
224 
225 static int
226 bc_close(struct open_file *f)
227 {
228 	struct i386_devdesc *dev;
229 
230 	dev = (struct i386_devdesc *)f->f_devdata;
231 	BC(dev).bc_open--;
232 	if (BC(dev).bc_open == 0) {
233 	    bcache_free(BC(dev).bc_bcache);
234 	    BC(dev).bc_bcache = NULL;
235 	}
236 	return(0);
237 }
238 
239 static int
240 bc_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
241     char *buf, size_t *rsize)
242 {
243 	struct bcache_devdata bcd;
244 	struct i386_devdesc *dev;
245 
246 	dev = (struct i386_devdesc *)devdata;
247 	bcd.dv_strategy = bc_realstrategy;
248 	bcd.dv_devdata = devdata;
249 	bcd.dv_cache = BC(dev).bc_bcache;
250 
251 	return (bcache_strategy(&bcd, rw, dblk, size, buf, rsize));
252 }
253 
254 static int
255 bc_realstrategy(void *devdata, int rw, daddr_t dblk, size_t size,
256     char *buf, size_t *rsize)
257 {
258 	struct i386_devdesc *dev;
259 	int unit;
260 	int blks;
261 #ifdef BD_SUPPORT_FRAGS
262 	char fragbuf[BIOSCD_SECSIZE];
263 	size_t fragsize;
264 
265 	fragsize = size % BIOSCD_SECSIZE;
266 #else
267 	if (size % BIOSCD_SECSIZE)
268 		return (EINVAL);
269 #endif
270 
271 	if ((rw & F_MASK) != F_READ)
272 		return(EROFS);
273 	dev = (struct i386_devdesc *)devdata;
274 	unit = dev->d_unit;
275 	blks = size / BIOSCD_SECSIZE;
276 	if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
277 		return (EINVAL);
278 	dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
279 	DEBUG("read %d from %lld to %p", blks, dblk, buf);
280 
281 	if (rsize)
282 		*rsize = 0;
283 	if ((blks = bc_read(unit, dblk, blks, buf)) < 0) {
284 		DEBUG("read error");
285 		return (EIO);
286 	} else {
287 		if (size / BIOSCD_SECSIZE > blks) {
288 			if (rsize)
289 				*rsize = blks * BIOSCD_SECSIZE;
290 			return (0);
291 		}
292 	}
293 #ifdef BD_SUPPORT_FRAGS
294 	DEBUG("frag read %d from %lld+%d to %p",
295 	    fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
296 	if (fragsize && bc_read(unit, dblk + blks, 1, fragbuf) != 1) {
297 		if (blks) {
298 			if (rsize)
299 				*rsize = blks * BIOSCD_SECSIZE;
300 			return (0);
301 		}
302 		DEBUG("frag read error");
303 		return(EIO);
304 	}
305 	bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
306 #endif
307 	if (rsize)
308 		*rsize = size;
309 	return (0);
310 }
311 
312 /* return negative value for an error, otherwise blocks read */
313 static int
314 bc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
315 {
316 	u_int maxfer, resid, result, retry, x;
317 	caddr_t bbuf, p, xp;
318 	static struct edd_packet packet;
319 	int biosdev;
320 #ifdef DISK_DEBUG
321 	int error;
322 #endif
323 
324 	/* Just in case some idiot actually tries to read -1 blocks... */
325 	if (blks < 0)
326 		return (-1);
327 
328 	/* If nothing to do, just return succcess. */
329 	if (blks == 0)
330 		return (0);
331 
332 	/* Decide whether we have to bounce */
333 	if (VTOP(dest) >> 20 != 0) {
334 		/*
335 		 * The destination buffer is above first 1MB of
336 		 * physical memory so we have to arrange a suitable
337 		 * bounce buffer.
338 		 */
339 		x = V86_IO_BUFFER_SIZE / BIOSCD_SECSIZE;
340 		if (x == 0)
341 			panic("BUG: Real mode buffer is too small\n");
342 		x = min(x, (unsigned)blks);
343 		bbuf = PTOV(V86_IO_BUFFER);
344 		maxfer = x;
345 	} else {
346 		bbuf = NULL;
347 		maxfer = 0;
348 	}
349 
350 	biosdev = bc_unit2bios(unit);
351 	resid = blks;
352 	p = dest;
353 
354 	while (resid > 0) {
355 		if (bbuf)
356 			xp = bbuf;
357 		else
358 			xp = p;
359 		x = resid;
360 		if (maxfer > 0)
361 			x = min(x, maxfer);
362 
363 		/*
364 		 * Loop retrying the operation a couple of times.  The BIOS
365 		 * may also retry.
366 		 */
367 		for (retry = 0; retry < 3; retry++) {
368 			/* If retrying, reset the drive */
369 			if (retry > 0) {
370 				v86.ctl = V86_FLAGS;
371 				v86.addr = 0x13;
372 				v86.eax = 0;
373 				v86.edx = biosdev;
374 				v86int();
375 			}
376 
377 			packet.len = sizeof(struct edd_packet);
378 			packet.count = x;
379 			packet.off = VTOPOFF(xp);
380 			packet.seg = VTOPSEG(xp);
381 			packet.lba = dblk;
382 			v86.ctl = V86_FLAGS;
383 			v86.addr = 0x13;
384 			v86.eax = 0x4200;
385 			v86.edx = biosdev;
386 			v86.ds = VTOPSEG(&packet);
387 			v86.esi = VTOPOFF(&packet);
388 			v86int();
389 			result = V86_CY(v86.efl);
390 			if (result == 0)
391 				break;
392 			/* fall back to 1 sector read */
393 			x = 1;
394 		}
395 
396 #ifdef DISK_DEBUG
397 		error = (v86.eax >> 8) & 0xff;
398 #endif
399 		DEBUG("%d sectors from %lld to %p (0x%x) %s", x, dblk, p,
400 		    VTOP(p), result ? "failed" : "ok");
401 		DEBUG("unit %d  status 0x%x", unit, error);
402 
403 		/* still an error? break off */
404 		if (result != 0)
405 			break;
406 
407 		if (bbuf != NULL)
408 			bcopy(bbuf, p, x * BIOSCD_SECSIZE);
409 		p += (x * BIOSCD_SECSIZE);
410 		dblk += x;
411 		resid -= x;
412 	}
413 
414 /*	hexdump(dest, (blks * BIOSCD_SECSIZE)); */
415 
416 	if (blks - resid == 0)
417 		return (-1);		/* read failed */
418 
419 	return (blks - resid);
420 }
421 
422 /*
423  * Return a suitable dev_t value for (dev).
424  */
425 int
426 bc_getdev(struct i386_devdesc *dev)
427 {
428     int biosdev, unit;
429     int major;
430     int rootdev;
431 
432     unit = dev->d_unit;
433     biosdev = bc_unit2bios(unit);
434     DEBUG("unit %d BIOS device %d", unit, biosdev);
435     if (biosdev == -1)				/* not a BIOS device */
436 	return(-1);
437 
438     /*
439      * XXX: Need to examine device spec here to figure out if SCSI or
440      * ATAPI.  No idea on how to figure out device number.  All we can
441      * really pass to the kernel is what bus and device on which bus we
442      * were booted from, which dev_t isn't well suited to since those
443      * number don't match to unit numbers very well.  We may just need
444      * to engage in a hack where we pass -C to the boot args if we are
445      * the boot device.
446      */
447     major = ACDMAJOR;
448     unit = 0;	/* XXX */
449 
450     /* XXX: Assume partition 'a'. */
451     rootdev = MAKEBOOTDEV(major, 0, unit, 0);
452     DEBUG("dev is 0x%x\n", rootdev);
453     return(rootdev);
454 }
455