NetBSD/sys/arch/atari/atari/disksubr.c

679 lines
18 KiB
C

/* $NetBSD: disksubr.c,v 1.17 1999/09/22 07:20:44 leo Exp $ */
/*
* Copyright (c) 1995 Leo Weppelman.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Leo Weppelman.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef DISKLABEL_NBDA
#define DISKLABEL_NBDA /* required */
#endif
#include "opt_compat_netbsd.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ffs/fs.h>
#include <sys/disk.h>
#include <sys/disklabel.h>
#include <machine/ahdilabel.h>
/*
* BBSIZE in <ufs/ffs/fs.h> must be greater than
* or equal to BBMINSIZE in <machine/disklabel.h>
*/
#if BBSIZE < BBMINSIZE
#error BBSIZE smaller than BBMINSIZE
#endif
static void ck_label __P((struct disklabel *, struct cpu_disklabel *));
static int bsd_label __P((dev_t, void (*)(struct buf *),
struct disklabel *, u_int, u_int *));
static int ahdi_label __P((dev_t, void (*)(struct buf *),
struct disklabel *, struct cpu_disklabel *));
static void ahdi_to_bsd __P((struct disklabel *, struct ahdi_ptbl *));
static u_int ahdi_getparts __P((dev_t, void (*)(struct buf *), u_int,
u_int, u_int, struct ahdi_ptbl *));
/*
* XXX unknown function but needed for /sys/scsi to link
*/
void
dk_establish(disk, device)
struct disk *disk;
struct device *device;
{
}
/*
* Determine the size of the transfer, and make sure it is
* within the boundaries of the partition. Adjust transfer
* if needed, and signal errors or early completion.
*/
int
bounds_check_with_label(bp, lp, wlabel)
struct buf *bp;
struct disklabel *lp;
int wlabel;
{
struct partition *pp;
u_int maxsz, sz;
pp = &lp->d_partitions[DISKPART(bp->b_dev)];
if (bp->b_flags & B_RAW) {
if (bp->b_bcount & (lp->d_secsize - 1)) {
bp->b_error = EINVAL;
bp->b_flags |= B_ERROR;
return(-1);
}
if (lp->d_secsize < DEV_BSIZE)
maxsz = pp->p_size / (DEV_BSIZE / lp->d_secsize);
else maxsz = pp->p_size * (lp->d_secsize / DEV_BSIZE);
sz = (bp->b_bcount + DEV_BSIZE - 1) >> DEV_BSHIFT;
} else {
maxsz = pp->p_size;
sz = (bp->b_bcount + lp->d_secsize - 1) / lp->d_secsize;
}
if (bp->b_blkno < 0 || bp->b_blkno + sz > maxsz) {
if (bp->b_blkno == maxsz) {
/*
* trying to get one block beyond return EOF.
*/
bp->b_resid = bp->b_bcount;
return(0);
}
if (bp->b_blkno > maxsz || bp->b_blkno < 0) {
bp->b_error = EINVAL;
bp->b_flags |= B_ERROR;
return(-1);
}
sz = maxsz - bp->b_blkno;
/*
* adjust count down
*/
if (bp->b_flags & B_RAW)
bp->b_bcount = sz << DEV_BSHIFT;
else bp->b_bcount = sz * lp->d_secsize;
}
/*
* calc cylinder for disksort to order transfers with
*/
bp->b_cylinder = (bp->b_blkno + pp->p_offset) / lp->d_secpercyl;
return(1);
}
/*
* Attempt to read a disk label from a device using the
* indicated strategy routine. The label must be partly
* set up before this:
* secpercyl and anything required in the strategy routine
* (e.g. sector size) must be filled in before calling us.
* Returns NULL on success and an error string on failure.
*/
char *
readdisklabel(dev, strat, lp, clp)
dev_t dev;
void (*strat)(struct buf *);
struct disklabel *lp;
struct cpu_disklabel *clp;
{
int e;
bzero(clp, sizeof *clp);
/*
* Give some guaranteed validity to the disk label.
*/
if (lp->d_secsize == 0)
lp->d_secsize = DEV_BSIZE;
if (lp->d_secperunit == 0)
lp->d_secperunit = 0x1fffffff;
if (lp->d_secpercyl == 0)
return("Zero secpercyl");
/*
* Some parts of the kernel (see scsipi/cd.c for an example)
* assume that stuff they already had setup in d_partitions
* is still there after reading the disklabel. Hence the
* 'if 0'
*/
#if 0
bzero(lp->d_partitions, sizeof lp->d_partitions);
#endif
lp->d_partitions[RAW_PART].p_size = lp->d_secperunit;
lp->d_npartitions = RAW_PART + 1;
lp->d_bbsize = BBSIZE;
lp->d_sbsize = SBSIZE;
#ifdef DISKLABEL_NBDA
/* Try the native NetBSD/Atari format first. */
e = bsd_label(dev, strat, lp, 0, &clp->cd_label);
#endif
#if 0
/* Other label formats go here. */
if (e > 0)
e = foo_label(dev, strat, lp, ...);
#endif
#ifdef DISKLABEL_AHDI
/* The unprotected AHDI format comes last. */
if (e > 0)
e = ahdi_label(dev, strat, lp, clp);
#endif
if (e < 0)
return("I/O error");
/* Unknown format or unitialised volume? */
if (e > 0)
uprintf("Warning: unknown disklabel format"
"- assuming empty disk\n");
/* Calulate new checksum. */
lp->d_magic = lp->d_magic2 = DISKMAGIC;
lp->d_checksum = 0;
lp->d_checksum = dkcksum(lp);
return(NULL);
}
/*
* Check new disk label for sensibility before setting it.
*/
int
setdisklabel(olp, nlp, openmask, clp)
struct disklabel *olp, *nlp;
u_long openmask;
struct cpu_disklabel *clp;
{
/* special case to allow disklabel to be invalidated */
if (nlp->d_magic == 0xffffffff) {
*olp = *nlp;
return(0);
}
/* sanity clause */
if (nlp->d_secpercyl == 0 || nlp->d_npartitions > MAXPARTITIONS
|| nlp->d_secsize == 0 || (nlp->d_secsize % DEV_BSIZE) != 0
|| nlp->d_magic != DISKMAGIC || nlp->d_magic2 != DISKMAGIC
|| dkcksum(nlp) != 0)
return(EINVAL);
#ifdef DISKLABEL_AHDI
if (clp && clp->cd_bblock)
ck_label(nlp, clp);
#endif
while (openmask) {
struct partition *op, *np;
int i = ffs(openmask) - 1;
openmask &= ~(1 << i);
if (i >= nlp->d_npartitions)
return(EBUSY);
op = &olp->d_partitions[i];
np = &nlp->d_partitions[i];
if (np->p_offset != op->p_offset || np->p_size < op->p_size)
return(EBUSY);
/*
* Copy internally-set partition information
* if new label doesn't include it. XXX
*/
if (np->p_fstype == FS_UNUSED && op->p_fstype != FS_UNUSED) {
np->p_fstype = op->p_fstype;
np->p_fsize = op->p_fsize;
np->p_frag = op->p_frag;
np->p_cpg = op->p_cpg;
}
}
nlp->d_checksum = 0;
nlp->d_checksum = dkcksum(nlp);
*olp = *nlp;
return(0);
}
/*
* Write disk label back to device after modification.
*/
int
writedisklabel(dev, strat, lp, clp)
dev_t dev;
void (*strat)(struct buf *);
struct disklabel *lp;
struct cpu_disklabel *clp;
{
struct buf *bp;
u_int blk;
int rv;
blk = clp->cd_bblock;
if (blk == NO_BOOT_BLOCK)
return(ENXIO);
bp = geteblk(BBMINSIZE);
bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART);
bp->b_flags = B_BUSY | B_READ;
bp->b_bcount = BBMINSIZE;
bp->b_blkno = blk;
bp->b_cylinder = blk / lp->d_secpercyl;
(*strat)(bp);
rv = biowait(bp);
if (!rv) {
struct bootblock *bb = (struct bootblock *)bp->b_data;
/*
* Allthough the disk pack label may appear anywhere
* in the boot block while reading, it is always
* written at a fixed location.
*/
if (clp->cd_label != LABELOFFSET) {
clp->cd_label = LABELOFFSET;
bzero(bb, sizeof(*bb));
}
bb->bb_magic = (blk == 0) ? NBDAMAGIC : AHDIMAGIC;
BBSETLABEL(bb, lp);
bp->b_flags = B_BUSY | B_WRITE;
bp->b_bcount = BBMINSIZE;
bp->b_blkno = blk;
bp->b_cylinder = blk / lp->d_secpercyl;
(*strat)(bp);
rv = biowait(bp);
}
bp->b_flags |= B_INVAL | B_AGE;
brelse(bp);
return(rv);
}
/*
* Read bootblock at block `blkno' and check
* if it contains a valid NetBSD disk label.
*
* Returns: 0 if successfull,
* -1 if an I/O error occured,
* +1 if no valid label was found.
*/
static int
bsd_label(dev, strat, label, blkno, offset)
dev_t dev;
void (*strat)(struct buf *);
struct disklabel *label;
u_int blkno,
*offset;
{
struct buf *bp;
int rv;
bp = geteblk(BBMINSIZE);
bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART);
bp->b_flags = B_BUSY | B_READ;
bp->b_bcount = BBMINSIZE;
bp->b_blkno = blkno;
bp->b_cylinder = blkno / label->d_secpercyl;
(*strat)(bp);
rv = -1;
if (!biowait(bp)) {
struct bootblock *bb;
u_int32_t *p, *end;
rv = 1;
bb = (struct bootblock *)bp->b_data;
end = (u_int32_t *)((char *)&bb[1] - sizeof(struct disklabel));
for (p = (u_int32_t *)bb; p < end; ++p) {
struct disklabel *dl = (struct disklabel *)&p[1];
/*
* Compatibility kludge: the boot block magic number is
* new in 1.1A, in previous versions the disklabel was
* stored at the end of the boot block (offset 7168).
*/
if ( ( (p[0] == NBDAMAGIC && blkno == 0)
|| (p[0] == AHDIMAGIC && blkno != 0)
#ifdef COMPAT_11
|| (char *)dl - (char *)bb == 7168
#endif
)
&& dl->d_npartitions <= MAXPARTITIONS
&& dl->d_magic2 == DISKMAGIC
&& dl->d_magic == DISKMAGIC
&& dkcksum(dl) == 0
) {
*offset = (char *)dl - (char *)bb;
*label = *dl;
rv = 0;
break;
}
}
}
bp->b_flags = B_INVAL | B_AGE | B_READ;
brelse(bp);
return(rv);
}
#ifdef DISKLABEL_AHDI
/*
* Check for consistency between the NetBSD partition table
* and the AHDI auxilary root sectors. There's no good reason
* to force such consistency, but issueing a warning may help
* an inexperienced sysadmin to prevent corruption of AHDI
* partitions.
*/
static void
ck_label(dl, cdl)
struct disklabel *dl;
struct cpu_disklabel *cdl;
{
u_int *rp, i;
for (i = 0; i < dl->d_npartitions; ++i) {
struct partition *p = &dl->d_partitions[i];
if (i == RAW_PART || p->p_size == 0)
continue;
if ( (p->p_offset >= cdl->cd_bslst
&& p->p_offset <= cdl->cd_bslend)
|| (cdl->cd_bslst >= p->p_offset
&& cdl->cd_bslst < p->p_offset + p->p_size)) {
uprintf("Warning: NetBSD partition %c includes"
" AHDI bad sector list\n", 'a'+i);
}
for (rp = &cdl->cd_roots[0]; *rp; ++rp) {
if (*rp >= p->p_offset
&& *rp < p->p_offset + p->p_size) {
uprintf("Warning: NetBSD partition %c"
" includes AHDI auxilary root\n", 'a'+i);
}
}
}
}
/*
* Check volume for the existance of an AHDI label. Fetch
* NetBSD label from NBD or RAW partition, or otherwise
* create a fake NetBSD label based on the AHDI label.
*
* Returns: 0 if successful,
* -1 if an I/O error occured,
* +1 if no valid AHDI label was found.
*/
int
ahdi_label(dev, strat, dl, cdl)
dev_t dev;
void (*strat)(struct buf *);
struct disklabel *dl;
struct cpu_disklabel *cdl;
{
struct ahdi_ptbl apt;
u_int i;
int j;
/*
* The AHDI format requires a specific block size.
*/
if (dl->d_secsize != AHDI_BSIZE)
return(1);
/*
* Fetch the AHDI partition descriptors.
*/
apt.at_cdl = cdl;
apt.at_nroots = apt.at_nparts = 0;
i = ahdi_getparts(dev, strat, dl->d_secpercyl,
AHDI_BBLOCK, AHDI_BBLOCK, &apt);
if (i) {
if (i < dl->d_secperunit)
return(-1); /* disk read error */
else return(1); /* reading past end of medium */
}
/*
* Perform sanity checks.
*/
if (apt.at_bslst == 0 || apt.at_bslend == 0) {
/*
* Illegal according to Atari, however some hd-utils
* use it - notably ICD *sigh*
* Work around it.....
*/
apt.at_bslst = apt.at_bslend = 0;
uprintf("Warning: Illegal 'bad sector list' format"
"- assuming non exists\n");
}
if (apt.at_hdsize == 0 || apt.at_nparts == 0) /* unlikely */
return(1);
if (apt.at_nparts > AHDI_MAXPARTS) /* XXX kludge */
return(-1);
for (i = 0; i < apt.at_nparts; ++i) {
struct ahdi_part *p1 = &apt.at_parts[i];
for (j = 0; j < apt.at_nroots; ++j) {
u_int aux = apt.at_roots[j];
if (aux >= p1->ap_st && aux <= p1->ap_end)
return(1);
}
for (j = i + 1; j < apt.at_nparts; ++j) {
struct ahdi_part *p2 = &apt.at_parts[j];
if (p1->ap_st >= p2->ap_st && p1->ap_st <= p2->ap_end)
return(1);
if (p2->ap_st >= p1->ap_st && p2->ap_st <= p1->ap_end)
return(1);
}
if (p1->ap_st >= apt.at_bslst && p1->ap_st <= apt.at_bslend)
return(1);
if (apt.at_bslst >= p1->ap_st && apt.at_bslst <= p1->ap_end)
return(1);
}
/*
* Search for a NetBSD disk label
*/
apt.at_bblock = NO_BOOT_BLOCK;
for (i = 0; i < apt.at_nparts; ++i) {
struct ahdi_part *pd = &apt.at_parts[i];
u_int id = *((u_int32_t *)&pd->ap_flg);
if (id == AHDI_PID_NBD || id == AHDI_PID_RAW) {
u_int blkno = pd->ap_st;
j = bsd_label(dev, strat, dl, blkno, &apt.at_label);
if (j < 0) {
return(j); /* I/O error */
}
if (!j) {
apt.at_bblock = blkno; /* got it */
ck_label(dl, cdl);
return(0);
}
/*
* Not yet, but if this is the first NBD partition
* on this volume, we'll mark it anyway as a possible
* destination for future writedisklabel() calls, just
* in case there is no valid disk label on any of the
* other AHDI partitions.
*/
if (id == AHDI_PID_NBD
&& apt.at_bblock == NO_BOOT_BLOCK)
apt.at_bblock = blkno;
}
}
/*
* No NetBSD disk label on this volume, use the AHDI
* label to create a fake BSD label. If there is no
* NBD partition on this volume either, subsequent
* writedisklabel() calls will fail.
*/
ahdi_to_bsd(dl, &apt);
return(0);
}
/*
* Map the AHDI partition table to the NetBSD table.
*
* This means:
* Part 0 : Root
* Part 1 : Swap
* Part 2 : Whole disk
* Part 3.. : User partitions
*
* When more than one root partition is found, only the first one will
* be recognized as such. The others are mapped as user partitions.
*/
static void
ahdi_to_bsd(dl, apt)
struct disklabel *dl;
struct ahdi_ptbl *apt;
{
int i, have_root, user_part;
user_part = RAW_PART;
have_root = (apt->at_bblock != NO_BOOT_BLOCK);
for (i = 0; i < apt->at_nparts; ++i) {
struct ahdi_part *pd = &apt->at_parts[i];
int fst, pno = -1;
switch (*((u_int32_t *)&pd->ap_flg)) {
case AHDI_PID_NBD:
/*
* If this partition has been marked as the
* first NBD partition, it will be the root
* partition.
*/
if (pd->ap_st == apt->at_bblock)
pno = 0;
/* FALL THROUGH */
case AHDI_PID_NBR:
/*
* If there is no NBD partition and this is
* the first NBR partition, it will be the
* root partition.
*/
if (!have_root) {
have_root = 1;
pno = 0;
}
/* FALL THROUGH */
case AHDI_PID_NBU:
fst = FS_BSDFFS;
break;
case AHDI_PID_NBS:
case AHDI_PID_SWP:
if (dl->d_partitions[1].p_size == 0)
pno = 1;
fst = FS_SWAP;
break;
case AHDI_PID_BGM:
case AHDI_PID_GEM:
fst = FS_MSDOS;
break;
default:
fst = FS_OTHER;
break;
}
if (pno < 0) {
if((pno = user_part + 1) >= MAXPARTITIONS)
continue;
user_part = pno;
}
dl->d_partitions[pno].p_size = pd->ap_end - pd->ap_st + 1;
dl->d_partitions[pno].p_offset = pd->ap_st;
dl->d_partitions[pno].p_fstype = fst;
}
dl->d_npartitions = user_part + 1;
}
/*
* Fetch the AHDI partitions and auxilary roots.
*
* Returns: 0 if successful,
* otherwise an I/O error occurred, and the
* number of the offending block is returned.
*/
static u_int
ahdi_getparts(dev, strat, secpercyl, rsec, esec, apt)
dev_t dev;
void (*strat)(struct buf *);
u_int secpercyl,
rsec, esec;
struct ahdi_ptbl *apt;
{
struct ahdi_part *part, *end;
struct ahdi_root *root;
struct buf *bp;
u_int rv;
bp = geteblk(AHDI_BSIZE);
bp->b_dev = MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART);
bp->b_flags = B_BUSY | B_READ;
bp->b_bcount = AHDI_BSIZE;
bp->b_blkno = rsec;
bp->b_cylinder = rsec / secpercyl;
(*strat)(bp);
if (biowait(bp)) {
rv = rsec + (rsec == 0);
goto done;
}
root = (struct ahdi_root *)bp->b_data;
if (rsec == AHDI_BBLOCK)
end = &root->ar_parts[AHDI_MAXRPD];
else end = &root->ar_parts[AHDI_MAXARPD];
for (part = root->ar_parts; part < end; ++part) {
u_int id = *((u_int32_t *)&part->ap_flg);
if (!(id & 0x01000000))
continue;
if ((id &= 0x00ffffff) == AHDI_PID_XGM) {
u_int offs = part->ap_st + esec;
if (apt->at_nroots < AHDI_MAXROOTS)
apt->at_roots[apt->at_nroots] = offs;
apt->at_nroots += 1;
rv = ahdi_getparts(dev, strat, secpercyl, offs,
(esec == AHDI_BBLOCK) ? offs : esec, apt);
if (rv)
goto done;
continue;
}
else if (apt->at_nparts < AHDI_MAXPARTS) {
struct ahdi_part *p = &apt->at_parts[apt->at_nparts];
*((u_int32_t *)&p->ap_flg) = id;
p->ap_st = part->ap_st + rsec;
p->ap_end = p->ap_st + part->ap_size - 1;
}
apt->at_nparts += 1;
}
apt->at_hdsize = root->ar_hdsize;
apt->at_bslst = root->ar_bslst;
apt->at_bslend = root->ar_bslst + root->ar_bslsize - 1;
rv = 0;
done:
bp->b_flags = B_INVAL | B_AGE | B_READ;
brelse(bp);
return(rv);
}
#endif /* DISKLABEL_AHDI */