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: stable/11/stand/pc98/libpc98/bioscd.c 333049 2018-04-27 02:39:36Z nyan $");
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;
87298230Sallanjude	int	bc_open;		/* reference counter */
88298230Sallanjude	void	*bc_bcache;		/* buffer cache data */
89158559Snyan} bcinfo [MAXBCDEV];
90158559Snyanstatic int nbcinfo = 0;
91158559Snyan
92332154Skevans#define	BC(dev)	(bcinfo[(dev)->dd.d_unit])
93298230Sallanjude
94158559Snyanstatic int	bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
95158559Snyanstatic int	bc_init(void);
96158559Snyanstatic int	bc_strategy(void *devdata, int flag, daddr_t dblk,
97333049Snyan    size_t size, char *buf, size_t *rsize);
98298230Sallanjudestatic int	bc_realstrategy(void *devdata, int flag, daddr_t dblk,
99333049Snyan    size_t size, char *buf, size_t *rsize);
100158559Snyanstatic int	bc_open(struct open_file *f, ...);
101158559Snyanstatic int	bc_close(struct open_file *f);
102328889Skevansstatic int	bc_print(int verbose);
103158559Snyan
104158559Snyanstruct devsw bioscd = {
105158559Snyan	"cd",
106158559Snyan	DEVT_CD,
107158559Snyan	bc_init,
108158559Snyan	bc_strategy,
109158559Snyan	bc_open,
110158559Snyan	bc_close,
111158559Snyan	noioctl,
112158559Snyan	bc_print,
113158559Snyan	NULL
114158559Snyan};
115158559Snyan
116158559Snyan/*
117158559Snyan * Translate between BIOS device numbers and our private unit numbers.
118158559Snyan */
119158559Snyanint
120158559Snyanbc_bios2unit(int biosdev)
121158559Snyan{
122158559Snyan	int i;
123158559Snyan
124158559Snyan	DEBUG("looking for bios device 0x%x", biosdev);
125158559Snyan	for (i = 0; i < nbcinfo; i++) {
126158559Snyan		DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
127158559Snyan		if (bcinfo[i].bc_unit == biosdev)
128158559Snyan			return(i);
129158559Snyan	}
130158559Snyan	return(-1);
131158559Snyan}
132158559Snyan
133158559Snyanint
134158559Snyanbc_unit2bios(int unit)
135158559Snyan{
136158559Snyan	if ((unit >= 0) && (unit < nbcinfo))
137158559Snyan		return(bcinfo[unit].bc_unit);
138158559Snyan	return(-1);
139158559Snyan}
140158559Snyan
141158559Snyan/*
142158559Snyan * We can't quiz, we have to be told what device to use, so this functoin
143158559Snyan * doesn't do anything.  Instead, the loader calls bc_add() with the BIOS
144158559Snyan * device number to add.
145158559Snyan */
146158559Snyanstatic int
147158559Snyanbc_init(void)
148158559Snyan{
149158559Snyan
150158559Snyan	return (0);
151158559Snyan}
152158559Snyan
153158559Snyanint
154158559Snyanbc_add(int biosdev)
155158559Snyan{
156158559Snyan
157158559Snyan	if (nbcinfo >= MAXBCDEV)
158158559Snyan		return (-1);
159158559Snyan	bcinfo[nbcinfo].bc_unit = biosdev;
160158559Snyan
161158559Snyan	/* SCSI CD-ROM only */
162158559Snyan	if ((biosdev & 0xf0) != 0xa0)
163158559Snyan		return (-1);
164158559Snyan	if ((((uint32_t *)PTOV(0xA1460))[biosdev & 0x0f] & 0x1f) != 5)
165158559Snyan		return (-1);
166158559Snyan
167158559Snyan	printf("BIOS CD is cd%d\n", nbcinfo);
168158559Snyan	nbcinfo++;
169298230Sallanjude	bcache_add_dev(nbcinfo);	/* register cd device in bcache */
170158559Snyan	return(0);
171158559Snyan}
172158559Snyan
173158559Snyan/*
174158559Snyan * Print information about disks
175158559Snyan */
176328889Skevansstatic int
177158559Snyanbc_print(int verbose)
178158559Snyan{
179190146Snyan	char line[80];
180328889Skevans	int i, ret = 0;
181190146Snyan
182328889Skevans	if (nbcinfo == 0)
183328889Skevans		return (0);
184328889Skevans
185328889Skevans	printf("%s devices:", bioscd.dv_name);
186328889Skevans	if ((ret = pager_output("\n")) != 0)
187328889Skevans		return (ret);
188328889Skevans
189158559Snyan	for (i = 0; i < nbcinfo; i++) {
190158559Snyan		sprintf(line, "    cd%d: Device 0x%x\n", i,
191158559Snyan		    bcinfo[i].bc_sp.sp_devicespec);
192328889Skevans		if ((ret = pager_output(line)) != 0)
193300117Simp			break;
194158559Snyan	}
195328889Skevans	return (ret);
196158559Snyan}
197158559Snyan
198158559Snyan/*
199158559Snyan * Attempt to open the disk described by (dev) for use by (f).
200158559Snyan */
201158559Snyanstatic int
202158559Snyanbc_open(struct open_file *f, ...)
203158559Snyan{
204158559Snyan	va_list ap;
205158559Snyan	struct i386_devdesc *dev;
206158559Snyan
207158559Snyan	va_start(ap, f);
208158559Snyan	dev = va_arg(ap, struct i386_devdesc *);
209158559Snyan	va_end(ap);
210332154Skevans	if (dev->dd.d_unit >= nbcinfo) {
211158559Snyan		DEBUG("attempt to open nonexistent disk");
212158559Snyan		return(ENXIO);
213158559Snyan	}
214158559Snyan
215298230Sallanjude	BC(dev).bc_open++;
216298230Sallanjude	if (BC(dev).bc_bcache == NULL)
217298230Sallanjude		BC(dev).bc_bcache = bcache_allocate();
218158559Snyan	return(0);
219158559Snyan}
220158559Snyan
221158559Snyanstatic int
222158559Snyanbc_close(struct open_file *f)
223158559Snyan{
224298230Sallanjude	struct i386_devdesc *dev;
225158559Snyan
226298230Sallanjude	dev = (struct i386_devdesc *)f->f_devdata;
227298230Sallanjude	BC(dev).bc_open--;
228298230Sallanjude	if (BC(dev).bc_open == 0) {
229298230Sallanjude		bcache_free(BC(dev).bc_bcache);
230298230Sallanjude		BC(dev).bc_bcache = NULL;
231298230Sallanjude	}
232158559Snyan	return(0);
233158559Snyan}
234158559Snyan
235298230Sallanjudestatic int
236313355Stsoomebc_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
237298230Sallanjude    char *buf, size_t *rsize)
238298230Sallanjude{
239298230Sallanjude	struct bcache_devdata bcd;
240298230Sallanjude	struct i386_devdesc *dev;
241298230Sallanjude
242298230Sallanjude	dev = (struct i386_devdesc *)devdata;
243298230Sallanjude	bcd.dv_strategy = bc_realstrategy;
244298230Sallanjude	bcd.dv_devdata = devdata;
245298230Sallanjude	bcd.dv_cache = BC(dev).bc_bcache;
246298230Sallanjude
247313355Stsoome	return (bcache_strategy(&bcd, rw, dblk, size, buf, rsize));
248298230Sallanjude}
249298230Sallanjude
250158559Snyanstatic int
251313355Stsoomebc_realstrategy(void *devdata, int rw, daddr_t dblk, size_t size,
252298230Sallanjude    char *buf, size_t *rsize)
253158559Snyan{
254158559Snyan	struct i386_devdesc *dev;
255158559Snyan	int unit;
256158559Snyan	int blks;
257158559Snyan#ifdef BD_SUPPORT_FRAGS
258158559Snyan	char fragbuf[BIOSCD_SECSIZE];
259158559Snyan	size_t fragsize;
260158559Snyan
261158559Snyan	fragsize = size % BIOSCD_SECSIZE;
262158559Snyan#else
263158559Snyan	if (size % BIOSCD_SECSIZE)
264158559Snyan		return (EINVAL);
265158559Snyan#endif
266158559Snyan
267158559Snyan	if (rw != F_READ)
268158559Snyan		return(EROFS);
269158559Snyan	dev = (struct i386_devdesc *)devdata;
270332154Skevans	unit = dev->dd.d_unit;
271158559Snyan	blks = size / BIOSCD_SECSIZE;
272158559Snyan	if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
273158559Snyan		return (EINVAL);
274158559Snyan	dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
275190146Snyan	DEBUG("read %d from %lld to %p", blks, dblk, buf);
276158559Snyan
277158559Snyan	if (rsize)
278158559Snyan		*rsize = 0;
279158559Snyan	if (blks && bc_read(unit, dblk, blks, buf)) {
280158559Snyan		DEBUG("read error");
281158559Snyan		return (EIO);
282158559Snyan	}
283158559Snyan#ifdef BD_SUPPORT_FRAGS
284190146Snyan	DEBUG("frag read %d from %lld+%d to %p",
285158559Snyan	    fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
286190146Snyan	if (fragsize && bc_read(unit, dblk + blks, 1, fragbuf)) {
287158559Snyan		DEBUG("frag read error");
288158559Snyan		return(EIO);
289158559Snyan	}
290158559Snyan	bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
291158559Snyan#endif
292158559Snyan	if (rsize)
293158559Snyan		*rsize = size;
294158559Snyan	return (0);
295158559Snyan}
296158559Snyan
297190146Snyan/* Max number of sectors to bounce-buffer at a time. */
298190146Snyan#define	CD_BOUNCEBUF	8
299190146Snyan
300158559Snyanstatic int
301158559Snyanbc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
302158559Snyan{
303190146Snyan	u_int maxfer, resid, result, retry, x;
304190146Snyan	caddr_t bbuf, p, xp;
305158559Snyan	int biosdev;
306158559Snyan#ifdef DISK_DEBUG
307158559Snyan	int error;
308158559Snyan#endif
309158559Snyan
310158559Snyan	/* Just in case some idiot actually tries to read -1 blocks... */
311158559Snyan	if (blks < 0)
312158559Snyan		return (-1);
313158559Snyan
314158559Snyan	/* If nothing to do, just return succcess. */
315158559Snyan	if (blks == 0)
316158559Snyan		return (0);
317158559Snyan
318190146Snyan	/* Decide whether we have to bounce */
319190146Snyan	if (VTOP(dest) >> 20 != 0) {
320190146Snyan		/*
321190146Snyan		 * The destination buffer is above first 1MB of
322190146Snyan		 * physical memory so we have to arrange a suitable
323190146Snyan		 * bounce buffer.
324190146Snyan		 */
325190146Snyan		x = min(CD_BOUNCEBUF, (unsigned)blks);
326190146Snyan		bbuf = alloca(x * BIOSCD_SECSIZE);
327190146Snyan		maxfer = x;
328190146Snyan	} else {
329190146Snyan		bbuf = NULL;
330190146Snyan		maxfer = 0;
331190146Snyan	}
332190146Snyan
333158559Snyan	biosdev = bc_unit2bios(unit);
334190146Snyan	resid = blks;
335190146Snyan	p = dest;
336190146Snyan
337190146Snyan	while (resid > 0) {
338190146Snyan		if (bbuf)
339190146Snyan			xp = bbuf;
340190146Snyan		else
341190146Snyan			xp = p;
342190146Snyan		x = resid;
343190146Snyan		if (maxfer > 0)
344190146Snyan			x = min(x, maxfer);
345190146Snyan
346190146Snyan		/*
347190146Snyan		 * Loop retrying the operation a couple of times.  The BIOS
348190146Snyan		 * may also retry.
349190146Snyan		 */
350190146Snyan		for (retry = 0; retry < 3; retry++) {
351190146Snyan			/* If retrying, reset the drive */
352190146Snyan			if (retry > 0) {
353190146Snyan				v86.ctl = V86_FLAGS;
354190146Snyan				v86.addr = 0x1b;
355190146Snyan				v86.eax = 0x0300 | biosdev;
356190146Snyan				v86int();
357190146Snyan			}
358190146Snyan
359158559Snyan			v86.ctl = V86_FLAGS;
360158559Snyan			v86.addr = 0x1b;
361190146Snyan			v86.eax = 0x0600 | (biosdev & 0x7f);
362190147Snyan			v86.ebx = x * BIOSCD_SECSIZE;
363190146Snyan			v86.ecx = dblk & 0xffff;
364190146Snyan			v86.edx = (dblk >> 16) & 0xffff;
365190147Snyan			v86.ebp = VTOPOFF(xp);
366190147Snyan			v86.es = VTOPSEG(xp);
367158559Snyan			v86int();
368226746Sjhb			result = V86_CY(v86.efl);
369190146Snyan			if (result == 0)
370190146Snyan				break;
371158559Snyan		}
372158559Snyan
373158559Snyan#ifdef DISK_DEBUG
374190146Snyan		error = (v86.eax >> 8) & 0xff;
375158559Snyan#endif
376190146Snyan		DEBUG("%d sectors from %lld to %p (0x%x) %s", x, dblk, p,
377190146Snyan		    VTOP(p), result ? "failed" : "ok");
378190146Snyan		DEBUG("unit %d  status 0x%x", unit, error);
379190146Snyan		if (bbuf != NULL)
380190146Snyan			bcopy(bbuf, p, x * BIOSCD_SECSIZE);
381190146Snyan		p += (x * BIOSCD_SECSIZE);
382190146Snyan		dblk += x;
383190146Snyan		resid -= x;
384190146Snyan	}
385158559Snyan
386158559Snyan/*	hexdump(dest, (blks * BIOSCD_SECSIZE)); */
387158559Snyan	return(0);
388158559Snyan}
389158559Snyan
390158559Snyan/*
391158559Snyan * Return a suitable dev_t value for (dev).
392158559Snyan */
393158559Snyanint
394158559Snyanbc_getdev(struct i386_devdesc *dev)
395158559Snyan{
396158559Snyan    int biosdev, unit, device;
397158559Snyan    int major;
398158559Snyan    int rootdev;
399158559Snyan
400332154Skevans    unit = dev->dd.d_unit;
401158559Snyan    biosdev = bc_unit2bios(unit);
402158559Snyan    DEBUG("unit %d BIOS device %d", unit, biosdev);
403158559Snyan    if (biosdev == -1)				/* not a BIOS device */
404158559Snyan	return(-1);
405158559Snyan
406158559Snyan    device = biosdev & 0xf0;
407158559Snyan    if (device == 0x80)
408158559Snyan	major = ACDMAJOR;
409158559Snyan    else if (device == 0xa0)
410158559Snyan	major = CDMAJOR;
411158559Snyan    else
412158559Snyan	return (-1);
413158559Snyan
414158559Snyan    unit = 0;	/* XXX */
415158559Snyan
416158559Snyan    /* XXX: Assume partition 'a'. */
417172921Sjhb    rootdev = MAKEBOOTDEV(major, 0, unit, 0);
418158559Snyan    DEBUG("dev is 0x%x\n", rootdev);
419158559Snyan    return(rootdev);
420158559Snyan}
421