bioscd.c revision 119482
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__FBSDID("$FreeBSD: head/sys/boot/i386/libi386/bioscd.c 119482 2003-08-25 23:28:32Z obrien $");
30
31/*
32 * BIOS CD device handling for CD's that have been booted off of via no
33 * emulation booting as defined in the El Torito standard.
34 *
35 * Ideas and algorithms from:
36 *
37 * - FreeBSD libi386/biosdisk.c
38 *
39 */
40
41#include <stand.h>
42
43#include <sys/param.h>
44#include <machine/bootinfo.h>
45#include <machine/psl.h>
46
47#include <stdarg.h>
48
49#include <bootstrap.h>
50#include <btxv86.h>
51#include "libi386.h"
52
53#define BIOSCD_SECSIZE		2048
54#define BUFSIZE			(1 * BIOSCD_SECSIZE)
55#define	MAXBCDEV		1
56
57/* Major numbers for devices we frontend for. */
58#define ACDMAJOR		117
59#define	CDMAJOR			15
60
61#ifdef DISK_DEBUG
62# define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
63#else
64# define DEBUG(fmt, args...)
65#endif
66
67struct specification_packet {
68	u_char	sp_size;
69	u_char	sp_bootmedia;
70	u_char	sp_drive;
71	u_char	sp_controller;
72	u_int	sp_lba;
73	u_short	sp_devicespec;
74	u_short	sp_buffersegment;
75	u_short	sp_loadsegment;
76	u_short	sp_sectorcount;
77	u_short	sp_cylsec;
78	u_char	sp_head;
79};
80
81/*
82 * List of BIOS devices, translation from disk unit number to
83 * BIOS unit number.
84 */
85static struct bcinfo {
86	int	bc_unit;		/* BIOS unit number */
87	struct specification_packet bc_sp;
88} bcinfo [MAXBCDEV];
89static int nbcinfo = 0;
90
91static int	bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
92static int	bc_init(void);
93static int	bc_strategy(void *devdata, int flag, daddr_t dblk,
94		    size_t size, char *buf, size_t *rsize);
95static int	bc_realstrategy(void *devdata, int flag, daddr_t dblk,
96		    size_t size, char *buf, size_t *rsize);
97static int	bc_open(struct open_file *f, ...);
98static int	bc_close(struct open_file *f);
99static void	bc_print(int verbose);
100
101struct devsw bioscd = {
102	"cd",
103	DEVT_CD,
104	bc_init,
105	bc_strategy,
106	bc_open,
107	bc_close,
108	noioctl,
109	bc_print,
110	NULL
111};
112
113/*
114 * Translate between BIOS device numbers and our private unit numbers.
115 */
116int
117bc_bios2unit(int biosdev)
118{
119	int i;
120
121	DEBUG("looking for bios device 0x%x", biosdev);
122	for (i = 0; i < nbcinfo; i++) {
123		DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
124		if (bcinfo[i].bc_unit == biosdev)
125			return(i);
126	}
127	return(-1);
128}
129
130int
131bc_unit2bios(int unit)
132{
133	if ((unit >= 0) && (unit < nbcinfo))
134		return(bcinfo[unit].bc_unit);
135	return(-1);
136}
137
138/*
139 * We can't quiz, we have to be told what device to use, so this functoin
140 * doesn't do anything.  Instead, the loader calls bc_add() with the BIOS
141 * device number to add.
142 */
143static int
144bc_init(void)
145{
146
147	return (0);
148}
149
150int
151bc_add(int biosdev)
152{
153
154	if (nbcinfo >= MAXBCDEV)
155		return (-1);
156	bcinfo[nbcinfo].bc_unit = biosdev;
157	v86.ctl = V86_FLAGS;
158	v86.addr = 0x13;
159	v86.eax = 0x4b01;
160	v86.edx = biosdev;
161	v86.ds = VTOPSEG(&bcinfo[nbcinfo].bc_sp);
162	v86.esi = VTOPOFF(&bcinfo[nbcinfo].bc_sp);
163	v86int();
164	if ((v86.eax & 0xff00) != 0)
165		return (-1);
166
167	printf("BIOS CD is cd%d\n", nbcinfo);
168	nbcinfo++;
169	return(0);
170}
171
172/*
173 * Print information about disks
174 */
175static void
176bc_print(int verbose)
177{
178	int i;
179	char line[80];
180
181	for (i = 0; i < nbcinfo; i++) {
182		sprintf(line, "    cd%d: Device 0x%x\n", i,
183		    bcinfo[i].bc_sp.sp_devicespec);
184		pager_output(line);
185	}
186}
187
188/*
189 * Attempt to open the disk described by (dev) for use by (f).
190 */
191static int
192bc_open(struct open_file *f, ...)
193{
194	va_list ap;
195	struct i386_devdesc *dev;
196	int error;
197
198	va_start(ap, f);
199	dev = va_arg(ap, struct i386_devdesc *);
200	va_end(ap);
201	if (dev->d_kind.bioscd.unit >= nbcinfo) {
202		DEBUG("attempt to open nonexistent disk");
203		return(ENXIO);
204	}
205
206	return(0);
207}
208
209static int
210bc_close(struct open_file *f)
211{
212
213	return(0);
214}
215
216static int
217bc_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf,
218    size_t *rsize)
219{
220	struct i386_devdesc *dev;
221	int unit;
222	int blks;
223#ifdef BD_SUPPORT_FRAGS
224	char fragbuf[BIOSCD_SECSIZE];
225	size_t fragsize;
226
227	fragsize = size % BIOSCD_SECSIZE;
228#else
229	if (size % BIOSCD_SECSIZE)
230		return (EINVAL);
231#endif
232
233	if (rw != F_READ)
234		return(EROFS);
235	dev = (struct i386_devdesc *)devdata;
236	unit = dev->d_kind.bioscd.unit;
237	blks = size / BIOSCD_SECSIZE;
238	if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
239		return (EINVAL);
240	dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
241	DEBUG("read %d from %d to %p", blks, dblk, buf);
242
243	if (rsize)
244		*rsize = 0;
245	if (blks && bc_read(unit, dblk, blks, buf)) {
246		DEBUG("read error");
247		return (EIO);
248	}
249#ifdef BD_SUPPORT_FRAGS
250	DEBUG("bc_strategy: frag read %d from %d+%d to %p",
251	    fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
252	if (fragsize && bc_read(unit, dblk + blks, 1, fragsize)) {
253		DEBUG("frag read error");
254		return(EIO);
255	}
256	bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
257#endif
258	if (rsize)
259		*rsize = size;
260	return (0);
261}
262
263static int
264bc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
265{
266	u_int result, resid, retry;
267	static unsigned short packet[8];
268	int biosdev;
269#ifdef DISK_DEBUG
270	int error;
271#endif
272
273	/* Just in case some idiot actually tries to read -1 blocks... */
274	if (blks < 0)
275		return (-1);
276
277	/* If nothing to do, just return succcess. */
278	if (blks == 0)
279		return (0);
280
281	biosdev = bc_unit2bios(unit);
282	/*
283	 * Loop retrying the operation a couple of times.  The BIOS
284	 * may also retry.
285	 */
286	for (retry = 0; retry < 3; retry++) {
287		/* If retrying, reset the drive */
288		if (retry > 0) {
289			v86.ctl = V86_FLAGS;
290			v86.addr = 0x13;
291			v86.eax = 0;
292			v86.edx = biosdev;
293			v86int();
294		}
295
296		packet[0] = 0x10;
297		packet[1] = blks;
298		packet[2] = VTOPOFF(dest);
299		packet[3] = VTOPSEG(dest);
300		packet[4] = dblk & 0xffff;
301		packet[5] = dblk >> 16;
302		packet[6] = 0;
303		packet[7] = 0;
304		v86.ctl = V86_FLAGS;
305		v86.addr = 0x13;
306		v86.eax = 0x4200;
307		v86.edx = biosdev;
308		v86.ds = VTOPSEG(packet);
309		v86.esi = VTOPOFF(packet);
310		v86int();
311		result = (v86.efl & PSL_C);
312		if (result == 0)
313			break;
314	}
315
316#ifdef DISK_DEBUG
317	error = (v86.eax >> 8) & 0xff;
318#endif
319	DEBUG("%d sectors from %ld to %p (0x%x) %s", blks, dblk, dest,
320	    VTOP(dest), result ? "failed" : "ok");
321	DEBUG("unit %d  status 0x%x",  unit, error);
322
323/*	hexdump(dest, (blks * BIOSCD_SECSIZE)); */
324	return(0);
325}
326
327/*
328 * Return a suitable dev_t value for (dev).
329 */
330int
331bc_getdev(struct i386_devdesc *dev)
332{
333    int biosdev, unit;
334    int major;
335    int rootdev;
336
337    unit = dev->d_kind.bioscd.unit;
338    biosdev = bc_unit2bios(unit);
339    DEBUG("unit %d BIOS device %d", unit, biosdev);
340    if (biosdev == -1)				/* not a BIOS device */
341	return(-1);
342
343    /*
344     * XXX: Need to examine device spec here to figure out if SCSI or
345     * ATAPI.  No idea on how to figure out device number.  All we can
346     * really pass to the kernel is what bus and device on which bus we
347     * were booted from, which dev_t isn't well suited to since those
348     * number don't match to unit numbers very well.  We may just need
349     * to engage in a hack where we pass -C to the boot args if we are
350     * the boot device.
351     */
352    major = ACDMAJOR;
353    unit = 0;	/* XXX */
354
355    /* XXX: Assume partition 'a'. */
356    rootdev = MAKEBOOTDEV(major, 0, 0, unit, 0);
357    DEBUG("dev is 0x%x\n", rootdev);
358    return(rootdev);
359}
360