/* $NetBSD: dsk.c,v 1.19 2022/04/30 03:52:41 rin Exp $ */
/*-
* Copyright (c) 2010 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Tohru Nishimura.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 FOUNDATION OR CONTRIBUTORS
* 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.
*/
/*
* assumptions;
* - up to 4 IDE/SATA drives.
* - a single (master) drive in each IDE channel.
* - all drives are up and spinning.
*/
#include <sys/types.h>
#include <lib/libsa/stand.h>
#include <lib/libsa/ufs.h>
#include <sys/disklabel.h>
#include <sys/bootblock.h>
#include <sys/param.h>
#include <dev/raidframe/raidframevar.h>
#include <machine/bootinfo.h>
#include "globals.h"
/*
* - no vtophys() translation, vaddr_t == paddr_t.
*/
#define CSR_READ_4(r) in32rb(r)
#define CSR_WRITE_4(r,v) out32rb(r,v)
#define CSR_READ_1(r) in8(r)
#define CSR_WRITE_1(r,v) out8(r,v)
struct dskdv {
char *name;
int (*match)(unsigned, void *);
void *(*init)(unsigned, void *);
};
static struct dskdv ldskdv[] = {
{ "pciide", pciide_match, pciide_init },
{ "siisata", siisata_match, siisata_init },
};
static int ndskdv = sizeof(ldskdv)/sizeof(ldskdv[0]);
static void disk_scan(void *);
static int probe_drive(struct dkdev_ata *, int);
static void drive_ident(struct disk *, char *);
static char *mkident(char *, int);
static void set_xfermode(struct dkdev_ata *, int);
static void decode_dlabel(struct disk *, char *);
static struct disklabel *search_dmagic(char *);
static int lba_read(struct disk *, int64_t, int, void *);
static void issue48(struct dvata_chan *, int64_t, int);
static void issue28(struct dvata_chan *, int64_t, int);
static struct disk *lookup_disk(int);
static struct disk ldisk[MAX_UNITS];
int
dskdv_init(void *self)
{
struct pcidev *pci = self;
struct dskdv *dv;
unsigned tag;
int n;
tag = pci->bdf;
for (n = 0; n < ndskdv; n++) {
dv = &ldskdv[n];
if ((*dv->match)(tag, NULL) > 0)
goto found;
}
return 0;
found:
pci->drv = (*dv->init)(tag, NULL);
if (pci->drv == NULL)
return 0;
disk_scan(pci->drv);
return 1;
}
static void
disk_scan(void *drv)
{
struct dkdev_ata *l = drv;
struct disk *d;
static int ndrive = 0;
int n;
for (n = 0; n < 4 && ndrive < MAX_UNITS; n++) {
if (l->presense[n] == 0)
continue;
if (probe_drive(l, n) == 0) {
l->presense[n] = 0;
continue;
}
d = &ldisk[ndrive];
d->dvops = l;
d->unitchan = n;
d->unittag = ndrive;
snprintf(d->xname, sizeof(d->xname), "wd%d", d->unittag);
set_xfermode(l, n);
drive_ident(d, l->iobuf);
decode_dlabel(d, l->iobuf);
ndrive += 1;
}
}
int
spinwait_unbusy(struct dkdev_ata *l, int n, int milli, const char **err)
{
struct dvata_chan *chan = &l->chan[n];
int sts;
const char *msg;
/*
* For best compatibility it is recommended to wait 400ns and
* read the alternate status byte four times before the status
* is valid.
*/
delay(1);
(void)CSR_READ_1(chan->alt);
(void)CSR_READ_1(chan->alt);
(void)CSR_READ_1(chan->alt);
(void)CSR_READ_1(chan->alt);
sts = CSR_READ_1(chan->cmd + _STS);
while (milli-- > 0
&& sts != 0xff
&& (sts & (ATA_STS_BUSY|ATA_STS_DRDY)) != ATA_STS_DRDY) {
delay(1000);
sts = CSR_READ_1(chan->cmd + _STS);
}
msg = NULL;
if (sts == 0xff)
msg = "returned 0xff";
else if (sts & ATA_STS_ERR)
msg = "returned ERR";
else if (sts & ATA_STS_BUSY)
msg = "remains BUSY";
else if ((sts & ATA_STS_DRDY) == 0)
msg = "no DRDY";
if (err != NULL)
*err = msg;
return msg == NULL;
}
int
perform_atareset(struct dkdev_ata *l, int n)
{
struct dvata_chan *chan = &l->chan[n];
CSR_WRITE_1(chan->ctl, ATA_DREQ);
delay(10);
CSR_WRITE_1(chan->ctl, ATA_SRST|ATA_DREQ);
delay(10);
CSR_WRITE_1(chan->ctl, ATA_DREQ);
return spinwait_unbusy(l, n, 1000, NULL);
}
/* clear idle and standby timers to spin up the drive */
void
wakeup_drive(struct dkdev_ata *l, int n)
{
struct dvata_chan *chan = &l->chan[n];
CSR_WRITE_1(chan->cmd + _NSECT, 0);
CSR_WRITE_1(chan->cmd + _CMD, ATA_CMD_IDLE);
(void)CSR_READ_1(chan->alt);
delay(10 * 1000);
CSR_WRITE_1(chan->cmd + _NSECT, 0);
CSR_WRITE_1(chan->cmd + _CMD, ATA_CMD_STANDBY);
(void)CSR_READ_1(chan->alt);
delay(10 * 1000);
}
int
atachkpwr(struct dkdev_ata *l, int n)
{
struct dvata_chan *chan = &l->chan[n];
CSR_WRITE_1(chan->cmd + _CMD, ATA_CMD_CHKPWR);
(void)CSR_READ_1(chan->alt);
delay(10 * 1000);
return CSR_READ_1(chan->cmd + _NSECT);
}
static int
probe_drive(struct dkdev_ata *l, int n)
{
struct dvata_chan *chan = &l->chan[n];
uint16_t *p;
int i;
CSR_WRITE_1(chan->cmd + _CMD, ATA_CMD_IDENT);
(void)CSR_READ_1(chan->alt);
delay(10 * 1000);
if (spinwait_unbusy(l, n, 1000, NULL) == 0)
return 0;
p = (uint16_t *)l->iobuf;
for (i = 0; i < 512; i += 2) {
/* need to have bswap16 */
*p++ = iole16toh(chan->cmd + _DAT);
}
(void)CSR_READ_1(chan->cmd + _STS);
return 1;
}
static void
drive_ident(struct disk *d, char *ident)
{
uint16_t *p;
uint64_t huge;
p = (uint16_t *)ident;
DPRINTF(("[49]%04x [82]%04x [83]%04x [84]%04x "
"[85]%04x [86]%04x [87]%04x [88]%04x\n",
p[49], p[82], p[83], p[84],
p[85], p[86], p[87], p[88]));
huge = 0;
printf("%s: ", d->xname);
printf("<%s> ", mkident((char *)ident + 54, 40));
if (p[49] & (1 << 8))
printf("DMA ");
if (p[49] & (1 << 9)) {
printf("LBA ");
huge = p[60] | (p[61] << 16);
}
if ((p[83] & 0xc000) == 0x4000 && (p[83] & (1 << 10))) {
printf("LBA48 ");
huge = p[100] | (p[101] << 16);
huge |= (uint64_t)p[102] << 32;
huge |= (uint64_t)p[103] << 48;
}
huge >>= (1 + 10);
printf("%d MB\n", (int)huge);
memcpy(d->ident, ident, sizeof(d->ident));
d->nsect = huge;
d->lba_read = lba_read;
}
static char *
mkident(char *src, int len)
{
static char local[40];
char *dst, *end, *last;
if (len > sizeof(local))
len = sizeof(local);
dst = last = local;
end = src + len - 1;
/* reserve space for '\0' */
if (len < 2)
goto out;
/* skip leading white space */
while (*src != '\0' && src < end && *src == ' ')
++src;
/* copy string, omitting trailing white space */
while (*src != '\0' && src < end) {
*dst++ = *src;
if (*src++ != ' ')
last = dst;
}
out:
*last = '\0';
return local;
}
static void
decode_dlabel(struct disk *d, char *iobuf)
{
struct mbr_partition *mp, *bsdp;
struct disklabel *dlp;
struct partition *pp;
int i, first, rf_offset;
bsdp = NULL;
(*d->lba_read)(d, 0, 1, iobuf);
if (bswap16(*(uint16_t *)(iobuf + MBR_MAGIC_OFFSET)) != MBR_MAGIC)
goto skip;
mp = (struct mbr_partition *)(iobuf + MBR_PART_OFFSET);
for (i = 0; i < MBR_PART_COUNT; i++, mp++) {
if (mp->mbrp_type == MBR_PTYPE_NETBSD) {
bsdp = mp;
break;
}
}
skip:
rf_offset = 0;
first = (bsdp) ? bswap32(bsdp->mbrp_start) : 0;
(*d->lba_read)(d, first + LABELSECTOR, 1, iobuf);
dlp = search_dmagic(iobuf);
if (dlp == NULL)
goto notfound;
if (dlp->d_partitions[0].p_fstype == FS_RAID) {
printf("%s%c: raid\n", d->xname, 0 + 'a');
snprintf(d->xname, sizeof(d->xname), "raid.");
rf_offset
= dlp->d_partitions[0].p_offset + RF_PROTECTED_SECTORS;
(*d->lba_read)(d, rf_offset + LABELSECTOR, 1, iobuf);
dlp = search_dmagic(iobuf);
if (dlp == NULL)
goto notfound;
}
for (i = 0; i < dlp->d_npartitions; i += 1) {
const char *type;
pp = &dlp->d_partitions[i];
pp->p_offset += rf_offset;
type = NULL;
switch (pp->p_fstype) {
case FS_SWAP:
type = "swap";
break;
case FS_BSDFFS:
type = "ffs";
break;
case FS_EX2FS:
type = "ext2fs";
break;
}
if (type != NULL)
printf("%s%c: %s\t(%u)\n", d->xname, i + 'a', type,
pp->p_offset);
}
d->dlabel = allocaligned(sizeof(struct disklabel), 4);
memcpy(d->dlabel, dlp, sizeof(struct disklabel));
return;
notfound:
d->dlabel = NULL;
printf("%s: no disklabel\n", d->xname);
return;
}
struct disklabel *
search_dmagic(char *dp)
{
int i;
struct disklabel *dlp;
for (i = 0; i < 512 - sizeof(struct disklabel); i += 4, dp += 4) {
dlp = (struct disklabel *)dp;
if (dlp->d_magic == DISKMAGIC && dlp->d_magic2 == DISKMAGIC)
return dlp;
}
return NULL;
}
static void
set_xfermode(struct dkdev_ata *l, int n)
{
struct dvata_chan *chan = &l->chan[n];
CSR_WRITE_1(chan->cmd + _FEA, ATA_XFER);
CSR_WRITE_1(chan->cmd + _NSECT, XFER_PIO0);
CSR_WRITE_1(chan->cmd + _DEV, ATA_DEV_OBS); /* ??? */
CSR_WRITE_1(chan->cmd + _CMD, ATA_CMD_SETF);
spinwait_unbusy(l, n, 1000, NULL);
}
static int
lba_read(struct disk *d, int64_t bno, int bcnt, void *buf)
{
struct dkdev_ata *l;
struct dvata_chan *chan;
void (*issue)(struct dvata_chan *, int64_t, int);
int n, rdcnt, i, k;
uint16_t *p;
const char *err;
int error;
l = d->dvops;
n = d->unitchan;
p = (uint16_t *)buf;
chan = &l->chan[n];
error = 0;
for ( ; bcnt > 0; bno += rdcnt, bcnt -= rdcnt) {
issue = (bno < (1ULL<<28)) ? issue28 : issue48;
rdcnt = (bcnt > 255) ? 255 : bcnt;
(*issue)(chan, bno, rdcnt);
for (k = 0; k < rdcnt; k++) {
if (spinwait_unbusy(l, n, 1000, &err) == 0) {
printf("%s blk %u %s\n",
d->xname, (unsigned)bno, err);
error = EIO;
break;
}
for (i = 0; i < 512; i += 2) {
/* arrives in native order */
*p++ = *(uint16_t *)(chan->cmd + _DAT);
}
/* clear irq if any */
(void)CSR_READ_1(chan->cmd + _STS);
}
}
return error;
}
static void
issue48(struct dvata_chan *chan, int64_t bno, int nblk)
{
CSR_WRITE_1(chan->cmd + _NSECT, 0); /* always less than 256 */
CSR_WRITE_1(chan->cmd + _LBAL, (bno >> 24) & 0xff);
CSR_WRITE_1(chan->cmd + _LBAM, (bno >> 32) & 0xff);
CSR_WRITE_1(chan->cmd + _LBAH, (bno >> 40) & 0xff);
CSR_WRITE_1(chan->cmd + _NSECT, nblk);
CSR_WRITE_1(chan->cmd + _LBAL, (bno >> 0) & 0xff);
CSR_WRITE_1(chan->cmd + _LBAM, (bno >> 8) & 0xff);
CSR_WRITE_1(chan->cmd + _LBAH, (bno >> 16) & 0xff);
CSR_WRITE_1(chan->cmd + _DEV, ATA_DEV_LBA);
CSR_WRITE_1(chan->cmd + _CMD, ATA_CMD_READ_EXT);
}
static void
issue28(struct dvata_chan *chan, int64_t bno, int nblk)
{
CSR_WRITE_1(chan->cmd + _NSECT, nblk);
CSR_WRITE_1(chan->cmd + _LBAL, (bno >> 0) & 0xff);
CSR_WRITE_1(chan->cmd + _LBAM, (bno >> 8) & 0xff);
CSR_WRITE_1(chan->cmd + _LBAH, (bno >> 16) & 0xff);
CSR_WRITE_1(chan->cmd + _DEV, ((bno >> 24) & 0xf) | ATA_DEV_LBA);
CSR_WRITE_1(chan->cmd + _CMD, ATA_CMD_READ);
}
static struct disk *
lookup_disk(int unit)
{
return (unit >= 0 && unit < MAX_UNITS) ? &ldisk[unit] : NULL;
}
int
dlabel_valid(int unit)
{
struct disk *dsk;
dsk = lookup_disk(unit);
if (dsk == NULL)
return 0;
return dsk->dlabel != NULL;
}
int
dsk_open(struct open_file *f, ...)
{
va_list ap;
int unit, part;
const char *name;
struct disk *d;
struct disklabel *dlp;
struct fs_ops *fs;
int error;
extern struct btinfo_bootpath bi_path;
extern struct btinfo_rootdevice bi_rdev;
extern struct fs_ops fs_ffsv2, fs_ffsv1;
va_start(ap, f);
unit = va_arg(ap, int);
part = va_arg(ap, int);
name = va_arg(ap, const char *);
va_end(ap);
if ((d = lookup_disk(unit)) == NULL)
return ENXIO;
if ((dlp = d->dlabel) == NULL || part >= dlp->d_npartitions)
return ENXIO;
d->part = part;
f->f_devdata = d;
snprintf(bi_path.bootpath, sizeof(bi_path.bootpath), "%s", name);
if (dlp->d_partitions[part].p_fstype == FS_BSDFFS) {
if ((error = ffsv2_open(name, f)) == 0) {
fs = &fs_ffsv2;
goto found;
}
if (error == EINVAL && (error = ffsv1_open(name, f)) == 0) {
fs = &fs_ffsv1;
goto found;
}
return error;
}
return ENXIO;
found:
d->fsops = fs;
f->f_devdata = d;
/* build btinfo to identify disk device */
snprintf(bi_rdev.devname, sizeof(bi_rdev.devname), "wd");
bi_rdev.cookie = (d->unittag << 8) | d->part;
return 0;
}
int
dsk_close(struct open_file *f)
{
struct disk *d = f->f_devdata;
struct fs_ops *fs = d->fsops;
(*fs->close)(f);
d->fsops = NULL;
f->f_devdata = NULL;
return 0;
}
int
dsk_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
void *p, size_t *rsize)
{
struct disk *d = devdata;
struct disklabel *dlp;
int64_t bno;
if (size == 0)
return 0;
if (rw != F_READ)
return EOPNOTSUPP;
bno = dblk;
if ((dlp = d->dlabel) != NULL)
bno += dlp->d_partitions[d->part].p_offset;
(*d->lba_read)(d, bno, size / 512, p);
if (rsize != NULL)
*rsize = size;
return 0;
}
struct fs_ops *
dsk_fsops(struct open_file *f)
{
struct disk *d = f->f_devdata;
return d->fsops;
}