1158559Snyan/*-
2158559Snyan * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3158559Snyan * Copyright (c) 2001 John H. Baldwin <jhb@FreeBSD.org>
4158559Snyan * All rights reserved.
5158559Snyan *
6158559Snyan * Redistribution and use in source and binary forms, with or without
7158559Snyan * modification, are permitted provided that the following conditions
8158559Snyan * are met:
9158559Snyan * 1. Redistributions of source code must retain the above copyright
10158559Snyan *    notice, this list of conditions and the following disclaimer.
11158559Snyan * 2. Redistributions in binary form must reproduce the above copyright
12158559Snyan *    notice, this list of conditions and the following disclaimer in the
13158559Snyan *    documentation and/or other materials provided with the distribution.
14158559Snyan *
15158559Snyan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16158559Snyan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17158559Snyan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18158559Snyan * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19158559Snyan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20158559Snyan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21158559Snyan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22158559Snyan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23158559Snyan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24158559Snyan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25158559Snyan * SUCH DAMAGE.
26158559Snyan */
27158559Snyan
28158559Snyan#include <sys/cdefs.h>
29158559Snyan__FBSDID("$FreeBSD$");
30158559Snyan
31158559Snyan/*
32158559Snyan * BIOS CD device handling for CD's that have been booted off of via no
33158559Snyan * emulation booting as defined in the El Torito standard.
34158559Snyan *
35158559Snyan * Ideas and algorithms from:
36158559Snyan *
37158559Snyan * - FreeBSD libi386/biosdisk.c
38158559Snyan *
39158559Snyan */
40158559Snyan
41158559Snyan#include <stand.h>
42158559Snyan
43158559Snyan#include <sys/param.h>
44158559Snyan#include <machine/bootinfo.h>
45158559Snyan
46158559Snyan#include <stdarg.h>
47158559Snyan
48158559Snyan#include <bootstrap.h>
49158559Snyan#include <btxv86.h>
50158559Snyan#include "libi386.h"
51158559Snyan
52158559Snyan#define BIOSCD_SECSIZE		2048
53158559Snyan#define BUFSIZE			(1 * BIOSCD_SECSIZE)
54158559Snyan#define	MAXBCDEV		1
55158559Snyan
56158559Snyan/* Major numbers for devices we frontend for. */
57158559Snyan#define ACDMAJOR		117
58158559Snyan#define	CDMAJOR			15
59158559Snyan
60158559Snyan#ifdef DISK_DEBUG
61158559Snyan# define DEBUG(fmt, args...)	printf("%s: " fmt "\n" , __func__ , ## args)
62158559Snyan#else
63158559Snyan# define DEBUG(fmt, args...)
64158559Snyan#endif
65158559Snyan
66158559Snyanstruct specification_packet {
67158559Snyan	u_char	sp_size;
68158559Snyan	u_char	sp_bootmedia;
69158559Snyan	u_char	sp_drive;
70158559Snyan	u_char	sp_controller;
71158559Snyan	u_int	sp_lba;
72158559Snyan	u_short	sp_devicespec;
73158559Snyan	u_short	sp_buffersegment;
74158559Snyan	u_short	sp_loadsegment;
75158559Snyan	u_short	sp_sectorcount;
76158559Snyan	u_short	sp_cylsec;
77158559Snyan	u_char	sp_head;
78158559Snyan};
79158559Snyan
80158559Snyan/*
81158559Snyan * List of BIOS devices, translation from disk unit number to
82158559Snyan * BIOS unit number.
83158559Snyan */
84158559Snyanstatic struct bcinfo {
85158559Snyan	int	bc_unit;		/* BIOS unit number */
86158559Snyan	struct specification_packet bc_sp;
87158559Snyan} bcinfo [MAXBCDEV];
88158559Snyanstatic int nbcinfo = 0;
89158559Snyan
90158559Snyanstatic int	bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
91158559Snyanstatic int	bc_init(void);
92158559Snyanstatic int	bc_strategy(void *devdata, int flag, daddr_t dblk,
93158559Snyan		    size_t size, char *buf, size_t *rsize);
94158559Snyanstatic int	bc_open(struct open_file *f, ...);
95158559Snyanstatic int	bc_close(struct open_file *f);
96158559Snyanstatic void	bc_print(int verbose);
97158559Snyan
98158559Snyanstruct devsw bioscd = {
99158559Snyan	"cd",
100158559Snyan	DEVT_CD,
101158559Snyan	bc_init,
102158559Snyan	bc_strategy,
103158559Snyan	bc_open,
104158559Snyan	bc_close,
105158559Snyan	noioctl,
106158559Snyan	bc_print,
107158559Snyan	NULL
108158559Snyan};
109158559Snyan
110158559Snyan/*
111158559Snyan * Translate between BIOS device numbers and our private unit numbers.
112158559Snyan */
113158559Snyanint
114158559Snyanbc_bios2unit(int biosdev)
115158559Snyan{
116158559Snyan	int i;
117158559Snyan
118158559Snyan	DEBUG("looking for bios device 0x%x", biosdev);
119158559Snyan	for (i = 0; i < nbcinfo; i++) {
120158559Snyan		DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
121158559Snyan		if (bcinfo[i].bc_unit == biosdev)
122158559Snyan			return(i);
123158559Snyan	}
124158559Snyan	return(-1);
125158559Snyan}
126158559Snyan
127158559Snyanint
128158559Snyanbc_unit2bios(int unit)
129158559Snyan{
130158559Snyan	if ((unit >= 0) && (unit < nbcinfo))
131158559Snyan		return(bcinfo[unit].bc_unit);
132158559Snyan	return(-1);
133158559Snyan}
134158559Snyan
135158559Snyan/*
136158559Snyan * We can't quiz, we have to be told what device to use, so this functoin
137158559Snyan * doesn't do anything.  Instead, the loader calls bc_add() with the BIOS
138158559Snyan * device number to add.
139158559Snyan */
140158559Snyanstatic int
141158559Snyanbc_init(void)
142158559Snyan{
143158559Snyan
144158559Snyan	return (0);
145158559Snyan}
146158559Snyan
147158559Snyanint
148158559Snyanbc_add(int biosdev)
149158559Snyan{
150158559Snyan
151158559Snyan	if (nbcinfo >= MAXBCDEV)
152158559Snyan		return (-1);
153158559Snyan	bcinfo[nbcinfo].bc_unit = biosdev;
154158559Snyan
155158559Snyan	/* SCSI CD-ROM only */
156158559Snyan	if ((biosdev & 0xf0) != 0xa0)
157158559Snyan		return (-1);
158158559Snyan	if ((((uint32_t *)PTOV(0xA1460))[biosdev & 0x0f] & 0x1f) != 5)
159158559Snyan		return (-1);
160158559Snyan
161158559Snyan	printf("BIOS CD is cd%d\n", nbcinfo);
162158559Snyan	nbcinfo++;
163158559Snyan	return(0);
164158559Snyan}
165158559Snyan
166158559Snyan/*
167158559Snyan * Print information about disks
168158559Snyan */
169158559Snyanstatic void
170158559Snyanbc_print(int verbose)
171158559Snyan{
172190146Snyan	char line[80];
173158559Snyan	int i;
174190146Snyan
175158559Snyan	for (i = 0; i < nbcinfo; i++) {
176158559Snyan		sprintf(line, "    cd%d: Device 0x%x\n", i,
177158559Snyan		    bcinfo[i].bc_sp.sp_devicespec);
178158559Snyan		pager_output(line);
179158559Snyan	}
180158559Snyan}
181158559Snyan
182158559Snyan/*
183158559Snyan * Attempt to open the disk described by (dev) for use by (f).
184158559Snyan */
185158559Snyanstatic int
186158559Snyanbc_open(struct open_file *f, ...)
187158559Snyan{
188158559Snyan	va_list ap;
189158559Snyan	struct i386_devdesc *dev;
190158559Snyan
191158559Snyan	va_start(ap, f);
192158559Snyan	dev = va_arg(ap, struct i386_devdesc *);
193158559Snyan	va_end(ap);
194163897Smarcel	if (dev->d_unit >= nbcinfo) {
195158559Snyan		DEBUG("attempt to open nonexistent disk");
196158559Snyan		return(ENXIO);
197158559Snyan	}
198158559Snyan
199158559Snyan	return(0);
200158559Snyan}
201158559Snyan
202158559Snyanstatic int
203158559Snyanbc_close(struct open_file *f)
204158559Snyan{
205158559Snyan
206158559Snyan	return(0);
207158559Snyan}
208158559Snyan
209158559Snyanstatic int
210158559Snyanbc_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf,
211158559Snyan    size_t *rsize)
212158559Snyan{
213158559Snyan	struct i386_devdesc *dev;
214158559Snyan	int unit;
215158559Snyan	int blks;
216158559Snyan#ifdef BD_SUPPORT_FRAGS
217158559Snyan	char fragbuf[BIOSCD_SECSIZE];
218158559Snyan	size_t fragsize;
219158559Snyan
220158559Snyan	fragsize = size % BIOSCD_SECSIZE;
221158559Snyan#else
222158559Snyan	if (size % BIOSCD_SECSIZE)
223158559Snyan		return (EINVAL);
224158559Snyan#endif
225158559Snyan
226158559Snyan	if (rw != F_READ)
227158559Snyan		return(EROFS);
228158559Snyan	dev = (struct i386_devdesc *)devdata;
229163897Smarcel	unit = dev->d_unit;
230158559Snyan	blks = size / BIOSCD_SECSIZE;
231158559Snyan	if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
232158559Snyan		return (EINVAL);
233158559Snyan	dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
234190146Snyan	DEBUG("read %d from %lld to %p", blks, dblk, buf);
235158559Snyan
236158559Snyan	if (rsize)
237158559Snyan		*rsize = 0;
238158559Snyan	if (blks && bc_read(unit, dblk, blks, buf)) {
239158559Snyan		DEBUG("read error");
240158559Snyan		return (EIO);
241158559Snyan	}
242158559Snyan#ifdef BD_SUPPORT_FRAGS
243190146Snyan	DEBUG("frag read %d from %lld+%d to %p",
244158559Snyan	    fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
245190146Snyan	if (fragsize && bc_read(unit, dblk + blks, 1, fragbuf)) {
246158559Snyan		DEBUG("frag read error");
247158559Snyan		return(EIO);
248158559Snyan	}
249158559Snyan	bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
250158559Snyan#endif
251158559Snyan	if (rsize)
252158559Snyan		*rsize = size;
253158559Snyan	return (0);
254158559Snyan}
255158559Snyan
256190146Snyan/* Max number of sectors to bounce-buffer at a time. */
257190146Snyan#define	CD_BOUNCEBUF	8
258190146Snyan
259158559Snyanstatic int
260158559Snyanbc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
261158559Snyan{
262190146Snyan	u_int maxfer, resid, result, retry, x;
263190146Snyan	caddr_t bbuf, p, xp;
264158559Snyan	int biosdev;
265158559Snyan#ifdef DISK_DEBUG
266158559Snyan	int error;
267158559Snyan#endif
268158559Snyan
269158559Snyan	/* Just in case some idiot actually tries to read -1 blocks... */
270158559Snyan	if (blks < 0)
271158559Snyan		return (-1);
272158559Snyan
273158559Snyan	/* If nothing to do, just return succcess. */
274158559Snyan	if (blks == 0)
275158559Snyan		return (0);
276158559Snyan
277190146Snyan	/* Decide whether we have to bounce */
278190146Snyan	if (VTOP(dest) >> 20 != 0) {
279190146Snyan		/*
280190146Snyan		 * The destination buffer is above first 1MB of
281190146Snyan		 * physical memory so we have to arrange a suitable
282190146Snyan		 * bounce buffer.
283190146Snyan		 */
284190146Snyan		x = min(CD_BOUNCEBUF, (unsigned)blks);
285190146Snyan		bbuf = alloca(x * BIOSCD_SECSIZE);
286190146Snyan		maxfer = x;
287190146Snyan	} else {
288190146Snyan		bbuf = NULL;
289190146Snyan		maxfer = 0;
290190146Snyan	}
291190146Snyan
292158559Snyan	biosdev = bc_unit2bios(unit);
293190146Snyan	resid = blks;
294190146Snyan	p = dest;
295190146Snyan
296190146Snyan	while (resid > 0) {
297190146Snyan		if (bbuf)
298190146Snyan			xp = bbuf;
299190146Snyan		else
300190146Snyan			xp = p;
301190146Snyan		x = resid;
302190146Snyan		if (maxfer > 0)
303190146Snyan			x = min(x, maxfer);
304190146Snyan
305190146Snyan		/*
306190146Snyan		 * Loop retrying the operation a couple of times.  The BIOS
307190146Snyan		 * may also retry.
308190146Snyan		 */
309190146Snyan		for (retry = 0; retry < 3; retry++) {
310190146Snyan			/* If retrying, reset the drive */
311190146Snyan			if (retry > 0) {
312190146Snyan				v86.ctl = V86_FLAGS;
313190146Snyan				v86.addr = 0x1b;
314190146Snyan				v86.eax = 0x0300 | biosdev;
315190146Snyan				v86int();
316190146Snyan			}
317190146Snyan
318158559Snyan			v86.ctl = V86_FLAGS;
319158559Snyan			v86.addr = 0x1b;
320190146Snyan			v86.eax = 0x0600 | (biosdev & 0x7f);
321190147Snyan			v86.ebx = x * BIOSCD_SECSIZE;
322190146Snyan			v86.ecx = dblk & 0xffff;
323190146Snyan			v86.edx = (dblk >> 16) & 0xffff;
324190147Snyan			v86.ebp = VTOPOFF(xp);
325190147Snyan			v86.es = VTOPSEG(xp);
326158559Snyan			v86int();
327226746Sjhb			result = V86_CY(v86.efl);
328190146Snyan			if (result == 0)
329190146Snyan				break;
330158559Snyan		}
331158559Snyan
332158559Snyan#ifdef DISK_DEBUG
333190146Snyan		error = (v86.eax >> 8) & 0xff;
334158559Snyan#endif
335190146Snyan		DEBUG("%d sectors from %lld to %p (0x%x) %s", x, dblk, p,
336190146Snyan		    VTOP(p), result ? "failed" : "ok");
337190146Snyan		DEBUG("unit %d  status 0x%x", unit, error);
338190146Snyan		if (bbuf != NULL)
339190146Snyan			bcopy(bbuf, p, x * BIOSCD_SECSIZE);
340190146Snyan		p += (x * BIOSCD_SECSIZE);
341190146Snyan		dblk += x;
342190146Snyan		resid -= x;
343190146Snyan	}
344158559Snyan
345158559Snyan/*	hexdump(dest, (blks * BIOSCD_SECSIZE)); */
346158559Snyan	return(0);
347158559Snyan}
348158559Snyan
349158559Snyan/*
350158559Snyan * Return a suitable dev_t value for (dev).
351158559Snyan */
352158559Snyanint
353158559Snyanbc_getdev(struct i386_devdesc *dev)
354158559Snyan{
355158559Snyan    int biosdev, unit, device;
356158559Snyan    int major;
357158559Snyan    int rootdev;
358158559Snyan
359163897Smarcel    unit = dev->d_unit;
360158559Snyan    biosdev = bc_unit2bios(unit);
361158559Snyan    DEBUG("unit %d BIOS device %d", unit, biosdev);
362158559Snyan    if (biosdev == -1)				/* not a BIOS device */
363158559Snyan	return(-1);
364158559Snyan
365158559Snyan    device = biosdev & 0xf0;
366158559Snyan    if (device == 0x80)
367158559Snyan	major = ACDMAJOR;
368158559Snyan    else if (device == 0xa0)
369158559Snyan	major = CDMAJOR;
370158559Snyan    else
371158559Snyan	return (-1);
372158559Snyan
373158559Snyan    unit = 0;	/* XXX */
374158559Snyan
375158559Snyan    /* XXX: Assume partition 'a'. */
376172921Sjhb    rootdev = MAKEBOOTDEV(major, 0, unit, 0);
377158559Snyan    DEBUG("dev is 0x%x\n", rootdev);
378158559Snyan    return(rootdev);
379158559Snyan}
380