1b39bd2412
More DMA cleanups, including fix for breakage on older Promise controllers. Add more ways of getting to the ATA registers.
1540 lines
43 KiB
C
1540 lines
43 KiB
C
/*-
|
|
* Copyright (c) 1998 - 2003 Søren Schmidt <sos@FreeBSD.org>
|
|
* 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,
|
|
* without modification, immediately at the beginning of the file.
|
|
* 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. 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.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include "opt_ata.h"
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/ata.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/module.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/bio.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/sysctl.h>
|
|
#include <machine/stdarg.h>
|
|
#include <machine/resource.h>
|
|
#include <machine/bus.h>
|
|
#include <sys/rman.h>
|
|
#ifdef __alpha__
|
|
#include <machine/md_var.h>
|
|
#endif
|
|
#include <geom/geom_disk.h>
|
|
#include <dev/ata/ata-all.h>
|
|
#include <dev/ata/ata-disk.h>
|
|
#include <dev/ata/ata-raid.h>
|
|
#include <dev/ata/atapi-all.h>
|
|
|
|
/* device structures */
|
|
static d_ioctl_t ataioctl;
|
|
static struct cdevsw ata_cdevsw = {
|
|
.d_open = nullopen,
|
|
.d_close = nullclose,
|
|
.d_ioctl = ataioctl,
|
|
.d_name = "ata",
|
|
.d_maj = 159,
|
|
};
|
|
|
|
/* prototypes */
|
|
static void ata_boot_attach(void);
|
|
static void ata_intr(void *);
|
|
static int ata_getparam(struct ata_device *, u_int8_t);
|
|
static int ata_service(struct ata_channel *);
|
|
static void bswap(int8_t *, int);
|
|
static void btrim(int8_t *, int);
|
|
static void bpack(int8_t *, int8_t *, int);
|
|
static void ata_change_mode(struct ata_device *, int);
|
|
static u_int8_t ata_enclosure_sensor(struct ata_device *, int, u_int8_t, u_int8_t);
|
|
static int ata_enclosure_status(struct ata_device *, int *, int *, int *, int *);
|
|
|
|
/* sysctl vars */
|
|
SYSCTL_NODE(_hw, OID_AUTO, ata, CTLFLAG_RD, 0, "ATA driver parameters");
|
|
|
|
/* global vars */
|
|
struct intr_config_hook *ata_delayed_attach = NULL;
|
|
devclass_t ata_devclass;
|
|
|
|
/* local vars */
|
|
static MALLOC_DEFINE(M_ATA, "ATA generic", "ATA driver generic layer");
|
|
|
|
/* misc defines */
|
|
#define DEV_ATAPIALL defined(DEV_ATAPICD) || defined(DEV_ATAPIFD) || \
|
|
defined(DEV_ATAPIST) || defined(DEV_ATAPICAM)
|
|
|
|
int
|
|
ata_probe(device_t dev)
|
|
{
|
|
struct ata_channel *ch;
|
|
|
|
if (!dev || !(ch = device_get_softc(dev)))
|
|
return ENXIO;
|
|
|
|
if (ch->r_irq)
|
|
return EEXIST;
|
|
|
|
/* initialize the softc basics */
|
|
ch->active = ATA_IDLE;
|
|
ch->dev = dev;
|
|
ch->device[MASTER].channel = ch;
|
|
ch->device[MASTER].unit = ATA_MASTER;
|
|
ch->device[MASTER].mode = ATA_PIO;
|
|
ch->device[SLAVE].channel = ch;
|
|
ch->device[SLAVE].unit = ATA_SLAVE;
|
|
ch->device[SLAVE].mode = ATA_PIO;
|
|
TAILQ_INIT(&ch->ata_queue);
|
|
TAILQ_INIT(&ch->atapi_queue);
|
|
|
|
/* initialise device(s) on this channel */
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
ata_reset(ch);
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ata_attach(device_t dev)
|
|
{
|
|
struct ata_channel *ch;
|
|
int error, rid;
|
|
|
|
if (!dev || !(ch = device_get_softc(dev)))
|
|
return ENXIO;
|
|
|
|
rid = ATA_IRQ_RID;
|
|
ch->r_irq = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1,
|
|
RF_SHAREABLE | RF_ACTIVE);
|
|
if (!ch->r_irq) {
|
|
ata_printf(ch, -1, "unable to allocate interrupt\n");
|
|
return ENXIO;
|
|
}
|
|
if ((error = bus_setup_intr(dev, ch->r_irq, INTR_TYPE_BIO | INTR_ENTROPY,
|
|
ata_intr, ch, &ch->ih))) {
|
|
ata_printf(ch, -1, "unable to setup interrupt\n");
|
|
return error;
|
|
}
|
|
|
|
if (ch->dma)
|
|
ch->dma->alloc(ch);
|
|
|
|
/*
|
|
* do not attach devices if we are in early boot, this is done later
|
|
* when interrupts are enabled by a hook into the boot process.
|
|
* otherwise attach what the probe has found in ch->devices.
|
|
*/
|
|
if (!ata_delayed_attach) {
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
if (ch->devices & ATA_ATA_SLAVE)
|
|
if (ata_getparam(&ch->device[SLAVE], ATA_C_ATA_IDENTIFY))
|
|
ch->devices &= ~ATA_ATA_SLAVE;
|
|
if (ch->devices & ATA_ATAPI_SLAVE)
|
|
if (ata_getparam(&ch->device[SLAVE], ATA_C_ATAPI_IDENTIFY))
|
|
ch->devices &= ~ATA_ATAPI_SLAVE;
|
|
if (ch->devices & ATA_ATA_MASTER)
|
|
if (ata_getparam(&ch->device[MASTER], ATA_C_ATA_IDENTIFY))
|
|
ch->devices &= ~ATA_ATA_MASTER;
|
|
if (ch->devices & ATA_ATAPI_MASTER)
|
|
if (ata_getparam(&ch->device[MASTER], ATA_C_ATAPI_IDENTIFY))
|
|
ch->devices &= ~ATA_ATAPI_MASTER;
|
|
#ifdef DEV_ATADISK
|
|
if (ch->devices & ATA_ATA_MASTER)
|
|
ad_attach(&ch->device[MASTER]);
|
|
if (ch->devices & ATA_ATA_SLAVE)
|
|
ad_attach(&ch->device[SLAVE]);
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
if (ch->devices & ATA_ATAPI_MASTER)
|
|
atapi_attach(&ch->device[MASTER]);
|
|
if (ch->devices & ATA_ATAPI_SLAVE)
|
|
atapi_attach(&ch->device[SLAVE]);
|
|
#endif
|
|
#ifdef DEV_ATAPICAM
|
|
atapi_cam_attach_bus(ch);
|
|
#endif
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ata_detach(device_t dev)
|
|
{
|
|
struct ata_channel *ch;
|
|
int s;
|
|
|
|
if (!dev || !(ch = device_get_softc(dev)) || !ch->r_irq)
|
|
return ENXIO;
|
|
|
|
/* make sure channel is not busy */
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
ATA_SLEEPLOCK_CH(ch, ATA_CONTROL);
|
|
|
|
s = splbio();
|
|
#ifdef DEV_ATADISK
|
|
if (ch->devices & ATA_ATA_MASTER && ch->device[MASTER].driver)
|
|
ad_detach(&ch->device[MASTER], 1);
|
|
if (ch->devices & ATA_ATA_SLAVE && ch->device[SLAVE].driver)
|
|
ad_detach(&ch->device[SLAVE], 1);
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
if (ch->devices & ATA_ATAPI_MASTER && ch->device[MASTER].driver)
|
|
atapi_detach(&ch->device[MASTER]);
|
|
if (ch->devices & ATA_ATAPI_SLAVE && ch->device[SLAVE].driver)
|
|
atapi_detach(&ch->device[SLAVE]);
|
|
#endif
|
|
#ifdef DEV_ATAPICAM
|
|
atapi_cam_detach_bus(ch);
|
|
#endif
|
|
splx(s);
|
|
|
|
if (ch->device[MASTER].param) {
|
|
free(ch->device[MASTER].param, M_ATA);
|
|
ch->device[MASTER].param = NULL;
|
|
}
|
|
if (ch->device[SLAVE].param) {
|
|
free(ch->device[SLAVE].param, M_ATA);
|
|
ch->device[SLAVE].param = NULL;
|
|
}
|
|
ch->device[MASTER].driver = NULL;
|
|
ch->device[SLAVE].driver = NULL;
|
|
ch->device[MASTER].mode = ATA_PIO;
|
|
ch->device[SLAVE].mode = ATA_PIO;
|
|
ch->devices = 0;
|
|
if (ch->dma)
|
|
ch->dma->free(ch);
|
|
|
|
bus_teardown_intr(dev, ch->r_irq, ch->ih);
|
|
bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq);
|
|
ch->r_irq = NULL;
|
|
ATA_UNLOCK_CH(ch);
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ata_resume(device_t dev)
|
|
{
|
|
struct ata_channel *ch;
|
|
int error;
|
|
|
|
if (!dev || !(ch = device_get_softc(dev)))
|
|
return ENXIO;
|
|
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
error = ata_reinit(ch);
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
ataioctl(dev_t dev, u_long cmd, caddr_t addr, int32_t flag, struct thread *td)
|
|
{
|
|
struct ata_cmd *iocmd = (struct ata_cmd *)addr;
|
|
struct ata_channel *ch;
|
|
device_t device = devclass_get_device(ata_devclass, iocmd->channel);
|
|
int error;
|
|
|
|
if (cmd != IOCATA)
|
|
return ENOTTY;
|
|
|
|
if (iocmd->cmd == ATAGMAXCHANNEL) {
|
|
iocmd->u.maxchan = devclass_get_maxunit(ata_devclass);
|
|
return 0;
|
|
}
|
|
|
|
if (iocmd->channel < -1 || iocmd->device < -1 || iocmd->device > SLAVE)
|
|
return ENXIO;
|
|
|
|
switch (iocmd->cmd) {
|
|
case ATAATTACH:
|
|
/* should enable channel HW on controller that can SOS XXX */
|
|
error = ata_probe(device);
|
|
if (!error)
|
|
error = ata_attach(device);
|
|
return error;
|
|
|
|
case ATADETACH:
|
|
error = ata_detach(device);
|
|
/* should disable channel HW on controller that can SOS XXX */
|
|
return error;
|
|
|
|
case ATAREINIT:
|
|
if (!device || !(ch = device_get_softc(device)))
|
|
return ENXIO;
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
ATA_SLEEPLOCK_CH(ch, ATA_ACTIVE);
|
|
error = ata_reinit(ch);
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
return error;
|
|
|
|
case ATAGMODE:
|
|
if (!device || !(ch = device_get_softc(device)))
|
|
return ENXIO;
|
|
|
|
if ((iocmd->device == MASTER || iocmd->device == -1) &&
|
|
ch->device[MASTER].driver)
|
|
iocmd->u.mode.mode[MASTER] = ch->device[MASTER].mode;
|
|
else
|
|
iocmd->u.mode.mode[MASTER] = -1;
|
|
|
|
if ((iocmd->device == SLAVE || iocmd->device == -1) &&
|
|
ch->device[SLAVE].param)
|
|
iocmd->u.mode.mode[SLAVE] = ch->device[SLAVE].mode;
|
|
else
|
|
iocmd->u.mode.mode[SLAVE] = -1;
|
|
return 0;
|
|
|
|
case ATASMODE:
|
|
if (!device || !(ch = device_get_softc(device)))
|
|
return ENXIO;
|
|
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
if ((iocmd->device == MASTER || iocmd->device == -1) &&
|
|
iocmd->u.mode.mode[MASTER] >= 0 && ch->device[MASTER].param) {
|
|
ata_change_mode(&ch->device[MASTER],iocmd->u.mode.mode[MASTER]);
|
|
iocmd->u.mode.mode[MASTER] = ch->device[MASTER].mode;
|
|
}
|
|
else
|
|
iocmd->u.mode.mode[MASTER] = -1;
|
|
|
|
if ((iocmd->device == SLAVE || iocmd->device == -1) &&
|
|
iocmd->u.mode.mode[SLAVE] >= 0 && ch->device[SLAVE].param) {
|
|
ata_change_mode(&ch->device[SLAVE], iocmd->u.mode.mode[SLAVE]);
|
|
iocmd->u.mode.mode[SLAVE] = ch->device[SLAVE].mode;
|
|
}
|
|
else
|
|
iocmd->u.mode.mode[SLAVE] = -1;
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
return 0;
|
|
|
|
case ATAGPARM:
|
|
if (!device || !(ch = device_get_softc(device)))
|
|
return ENXIO;
|
|
|
|
iocmd->u.param.type[MASTER] =
|
|
ch->devices & (ATA_ATA_MASTER | ATA_ATAPI_MASTER);
|
|
iocmd->u.param.type[SLAVE] =
|
|
ch->devices & (ATA_ATA_SLAVE | ATA_ATAPI_SLAVE);
|
|
|
|
if (ch->device[MASTER].name)
|
|
strcpy(iocmd->u.param.name[MASTER], ch->device[MASTER].name);
|
|
if (ch->device[SLAVE].name)
|
|
strcpy(iocmd->u.param.name[SLAVE], ch->device[SLAVE].name);
|
|
|
|
if (ch->device[MASTER].param)
|
|
bcopy(ch->device[MASTER].param, &iocmd->u.param.params[MASTER],
|
|
sizeof(struct ata_params));
|
|
if (ch->device[SLAVE].param)
|
|
bcopy(ch->device[SLAVE].param, &iocmd->u.param.params[SLAVE],
|
|
sizeof(struct ata_params));
|
|
return 0;
|
|
|
|
case ATAENCSTAT: {
|
|
struct ata_device *atadev;
|
|
|
|
if (!device || !(ch = device_get_softc(device)))
|
|
return ENXIO;
|
|
|
|
if (iocmd->device == SLAVE)
|
|
atadev = &ch->device[SLAVE];
|
|
else
|
|
atadev = &ch->device[MASTER];
|
|
|
|
return ata_enclosure_status(atadev,
|
|
&iocmd->u.enclosure.fan,
|
|
&iocmd->u.enclosure.temp,
|
|
&iocmd->u.enclosure.v05,
|
|
&iocmd->u.enclosure.v12);
|
|
}
|
|
|
|
#ifdef DEV_ATADISK
|
|
case ATARAIDREBUILD:
|
|
return ata_raid_rebuild(iocmd->channel);
|
|
|
|
case ATARAIDCREATE:
|
|
return ata_raid_create(&iocmd->u.raid_setup);
|
|
|
|
case ATARAIDDELETE:
|
|
return ata_raid_delete(iocmd->channel);
|
|
|
|
case ATARAIDSTATUS:
|
|
return ata_raid_status(iocmd->channel, &iocmd->u.raid_status);
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
case ATAPICMD: {
|
|
struct ata_device *atadev;
|
|
caddr_t buf;
|
|
|
|
if (!device || !(ch = device_get_softc(device)))
|
|
return ENXIO;
|
|
|
|
if (!(atadev = &ch->device[iocmd->device]) ||
|
|
!(ch->devices & (iocmd->device == MASTER ?
|
|
ATA_ATAPI_MASTER : ATA_ATAPI_SLAVE)))
|
|
return ENODEV;
|
|
|
|
if (!(buf = malloc(iocmd->u.atapi.count, M_ATA, M_NOWAIT)))
|
|
return ENOMEM;
|
|
|
|
if (iocmd->u.atapi.flags & ATAPI_CMD_WRITE) {
|
|
error = copyin(iocmd->u.atapi.data, buf, iocmd->u.atapi.count);
|
|
if (error) {
|
|
free(buf, M_ATA);
|
|
return error;
|
|
}
|
|
}
|
|
error = atapi_queue_cmd(atadev, iocmd->u.atapi.ccb,
|
|
buf, iocmd->u.atapi.count,
|
|
(iocmd->u.atapi.flags == ATAPI_CMD_READ ?
|
|
ATPR_F_READ : 0) | ATPR_F_QUIET,
|
|
iocmd->u.atapi.timeout, NULL, NULL);
|
|
if (error) {
|
|
iocmd->u.atapi.error = error;
|
|
bcopy(&atadev->result, iocmd->u.atapi.sense_data,
|
|
sizeof(struct atapi_reqsense));
|
|
error = 0;
|
|
}
|
|
else if (iocmd->u.atapi.flags & ATAPI_CMD_READ)
|
|
error = copyout(buf, iocmd->u.atapi.data, iocmd->u.atapi.count);
|
|
|
|
free(buf, M_ATA);
|
|
return error;
|
|
}
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
return ENOTTY;
|
|
}
|
|
|
|
static int
|
|
ata_getparam(struct ata_device *atadev, u_int8_t command)
|
|
{
|
|
struct ata_params *ata_parm;
|
|
int retry = 0;
|
|
|
|
if (!(ata_parm = malloc(sizeof(struct ata_params), M_ATA, M_NOWAIT))) {
|
|
ata_prtdev(atadev, "malloc for identify data failed\n");
|
|
return -1;
|
|
}
|
|
|
|
/* apparently some devices needs this repeated */
|
|
do {
|
|
if (ata_command(atadev, command, 0, 0, 0,
|
|
dumping ? ATA_WAIT_READY : ATA_WAIT_INTR)) {
|
|
ata_prtdev(atadev, "%s identify failed\n",
|
|
command == ATA_C_ATAPI_IDENTIFY ? "ATAPI" : "ATA");
|
|
free(ata_parm, M_ATA);
|
|
return -1;
|
|
}
|
|
if (retry++ > 4) {
|
|
ata_prtdev(atadev, "%s identify retries exceeded\n",
|
|
command == ATA_C_ATAPI_IDENTIFY ? "ATAPI" : "ATA");
|
|
free(ata_parm, M_ATA);
|
|
return -1;
|
|
}
|
|
} while (ata_wait(atadev, ((command == ATA_C_ATAPI_IDENTIFY) ?
|
|
ATA_S_DRQ : (ATA_S_READY|ATA_S_DSC|ATA_S_DRQ))));
|
|
ATA_IDX_INSW(atadev->channel, ATA_DATA, (int16_t *)ata_parm,
|
|
sizeof(struct ata_params)/sizeof(int16_t));
|
|
|
|
if (command == ATA_C_ATA_IDENTIFY ||
|
|
!((ata_parm->model[0] == 'N' && ata_parm->model[1] == 'E') ||
|
|
(ata_parm->model[0] == 'F' && ata_parm->model[1] == 'X') ||
|
|
(ata_parm->model[0] == 'P' && ata_parm->model[1] == 'i')))
|
|
bswap(ata_parm->model, sizeof(ata_parm->model));
|
|
btrim(ata_parm->model, sizeof(ata_parm->model));
|
|
bpack(ata_parm->model, ata_parm->model, sizeof(ata_parm->model));
|
|
bswap(ata_parm->revision, sizeof(ata_parm->revision));
|
|
btrim(ata_parm->revision, sizeof(ata_parm->revision));
|
|
bpack(ata_parm->revision, ata_parm->revision, sizeof(ata_parm->revision));
|
|
bswap(ata_parm->serial, sizeof(ata_parm->serial));
|
|
btrim(ata_parm->serial, sizeof(ata_parm->serial));
|
|
bpack(ata_parm->serial, ata_parm->serial, sizeof(ata_parm->serial));
|
|
atadev->param = ata_parm;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
ata_boot_attach(void)
|
|
{
|
|
struct ata_channel *ch;
|
|
int ctlr;
|
|
|
|
/*
|
|
* run through all ata devices and look for real ATA & ATAPI devices
|
|
* using the hints we found in the early probe, this avoids some of
|
|
* the delays probing of non-exsistent devices can cause.
|
|
*/
|
|
for (ctlr=0; ctlr<devclass_get_maxunit(ata_devclass); ctlr++) {
|
|
if (!(ch = devclass_get_softc(ata_devclass, ctlr)))
|
|
continue;
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
if (ch->devices & ATA_ATA_SLAVE)
|
|
if (ata_getparam(&ch->device[SLAVE], ATA_C_ATA_IDENTIFY))
|
|
ch->devices &= ~ATA_ATA_SLAVE;
|
|
if (ch->devices & ATA_ATAPI_SLAVE)
|
|
if (ata_getparam(&ch->device[SLAVE], ATA_C_ATAPI_IDENTIFY))
|
|
ch->devices &= ~ATA_ATAPI_SLAVE;
|
|
if (ch->devices & ATA_ATA_MASTER)
|
|
if (ata_getparam(&ch->device[MASTER], ATA_C_ATA_IDENTIFY))
|
|
ch->devices &= ~ATA_ATA_MASTER;
|
|
if (ch->devices & ATA_ATAPI_MASTER)
|
|
if (ata_getparam(&ch->device[MASTER], ATA_C_ATAPI_IDENTIFY))
|
|
ch->devices &= ~ATA_ATAPI_MASTER;
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
}
|
|
#ifdef DEV_ATADISK
|
|
/* now we know whats there, do the real attach, first the ATA disks */
|
|
for (ctlr=0; ctlr<devclass_get_maxunit(ata_devclass); ctlr++) {
|
|
if (!(ch = devclass_get_softc(ata_devclass, ctlr)))
|
|
continue;
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
if (ch->devices & ATA_ATA_MASTER)
|
|
ad_attach(&ch->device[MASTER]);
|
|
if (ch->devices & ATA_ATA_SLAVE)
|
|
ad_attach(&ch->device[SLAVE]);
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
}
|
|
#endif
|
|
/* then the atapi devices */
|
|
for (ctlr=0; ctlr<devclass_get_maxunit(ata_devclass); ctlr++) {
|
|
if (!(ch = devclass_get_softc(ata_devclass, ctlr)))
|
|
continue;
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
#if DEV_ATAPIALL
|
|
if (ch->devices & ATA_ATAPI_MASTER)
|
|
atapi_attach(&ch->device[MASTER]);
|
|
if (ch->devices & ATA_ATAPI_SLAVE)
|
|
atapi_attach(&ch->device[SLAVE]);
|
|
#endif
|
|
#ifdef DEV_ATAPICAM
|
|
atapi_cam_attach_bus(ch);
|
|
#endif
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
}
|
|
if (ata_delayed_attach) {
|
|
config_intrhook_disestablish(ata_delayed_attach);
|
|
free(ata_delayed_attach, M_TEMP);
|
|
ata_delayed_attach = NULL;
|
|
}
|
|
#ifdef DEV_ATADISK
|
|
ata_raid_attach();
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
ata_intr(void *data)
|
|
{
|
|
struct ata_channel *ch = (struct ata_channel *)data;
|
|
|
|
/* if device is busy it didn't interrupt */
|
|
if (ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_BUSY) {
|
|
DELAY(100);
|
|
if (!(ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_DRQ))
|
|
return;
|
|
}
|
|
|
|
/* clear interrupt and get status */
|
|
ch->status = ATA_IDX_INB(ch, ATA_STATUS);
|
|
|
|
if (ch->status & ATA_S_ERROR)
|
|
ch->error = ATA_IDX_INB(ch, ATA_ERROR);
|
|
|
|
/* find & call the responsible driver to process this interrupt */
|
|
switch (ch->active) {
|
|
#ifdef DEV_ATADISK
|
|
case ATA_ACTIVE_ATA:
|
|
if (!ch->running || ad_interrupt(ch->running) == ATA_OP_CONTINUES)
|
|
return;
|
|
break;
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
case ATA_ACTIVE_ATAPI:
|
|
if (!ch->running || atapi_interrupt(ch->running) == ATA_OP_CONTINUES)
|
|
return;
|
|
break;
|
|
#endif
|
|
default:
|
|
if (ch->active & ATA_WAIT_INTR)
|
|
wakeup(ch);
|
|
}
|
|
|
|
if (ch->active & ATA_CONTROL) {
|
|
ATA_FORCELOCK_CH(ch, ATA_CONTROL);
|
|
return;
|
|
}
|
|
|
|
if (ch->active & ATA_WAIT_INTR) {
|
|
ATA_UNLOCK_CH(ch);
|
|
return;
|
|
}
|
|
|
|
if ((ch->flags & ATA_QUEUED) &&
|
|
ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_SERVICE) {
|
|
ATA_FORCELOCK_CH(ch, ATA_ACTIVE);
|
|
if (ata_service(ch) == ATA_OP_CONTINUES)
|
|
return;
|
|
}
|
|
ch->running = NULL;
|
|
ATA_UNLOCK_CH(ch);
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
ata_start(ch);
|
|
return;
|
|
}
|
|
|
|
void
|
|
ata_start(struct ata_channel *ch)
|
|
{
|
|
#ifdef DEV_ATADISK
|
|
struct ad_request *ad_request;
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
struct atapi_request *atapi_request;
|
|
#endif
|
|
int s;
|
|
|
|
ch->locking(ch, ATA_LF_LOCK);
|
|
if (!ATA_LOCK_CH(ch, ATA_ACTIVE))
|
|
return;
|
|
|
|
s = splbio();
|
|
#ifdef DEV_ATADISK
|
|
/* find & call the responsible driver if anything on the ATA queue */
|
|
if (TAILQ_EMPTY(&ch->ata_queue)) {
|
|
if (ch->devices & (ATA_ATA_MASTER) && ch->device[MASTER].driver)
|
|
ad_start(&ch->device[MASTER]);
|
|
if (ch->devices & (ATA_ATA_SLAVE) && ch->device[SLAVE].driver)
|
|
ad_start(&ch->device[SLAVE]);
|
|
}
|
|
if ((ad_request = TAILQ_FIRST(&ch->ata_queue))) {
|
|
TAILQ_REMOVE(&ch->ata_queue, ad_request, chain);
|
|
ch->active = ATA_ACTIVE_ATA;
|
|
ch->running = ad_request;
|
|
if (ad_transfer(ad_request) == ATA_OP_CONTINUES) {
|
|
splx(s);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
/* find & call the responsible driver if anything on the ATAPI queue */
|
|
if (TAILQ_EMPTY(&ch->atapi_queue)) {
|
|
if (ch->devices & (ATA_ATAPI_MASTER) && ch->device[MASTER].driver)
|
|
atapi_start(&ch->device[MASTER]);
|
|
if (ch->devices & (ATA_ATAPI_SLAVE) && ch->device[SLAVE].driver)
|
|
atapi_start(&ch->device[SLAVE]);
|
|
}
|
|
if ((atapi_request = TAILQ_FIRST(&ch->atapi_queue))) {
|
|
TAILQ_REMOVE(&ch->atapi_queue, atapi_request, chain);
|
|
ch->active = ATA_ACTIVE_ATAPI;
|
|
ch->running = atapi_request;
|
|
if (atapi_transfer(atapi_request) == ATA_OP_CONTINUES) {
|
|
splx(s);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
ATA_UNLOCK_CH(ch);
|
|
ch->locking(ch, ATA_LF_UNLOCK);
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
ata_reset(struct ata_channel *ch)
|
|
{
|
|
u_int8_t lsb, msb, ostat0, ostat1;
|
|
u_int8_t stat0 = 0, stat1 = 0;
|
|
int mask = 0, timeout;
|
|
|
|
/* do we have any signs of ATA/ATAPI HW being present ? */
|
|
ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
|
|
DELAY(10);
|
|
ostat0 = ATA_IDX_INB(ch, ATA_STATUS);
|
|
if ((ostat0 & 0xf8) != 0xf8 && ostat0 != 0xa5) {
|
|
stat0 = ATA_S_BUSY;
|
|
mask |= 0x01;
|
|
}
|
|
ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_SLAVE);
|
|
DELAY(10);
|
|
ostat1 = ATA_IDX_INB(ch, ATA_STATUS);
|
|
if ((ostat1 & 0xf8) != 0xf8 && ostat1 != 0xa5) {
|
|
stat1 = ATA_S_BUSY;
|
|
mask |= 0x02;
|
|
}
|
|
|
|
ch->devices = 0;
|
|
if (!mask)
|
|
return;
|
|
|
|
/* in some setups we dont want to test for a slave */
|
|
if (ch->flags & ATA_NO_SLAVE) {
|
|
stat1 = 0x0;
|
|
mask &= ~0x02;
|
|
}
|
|
|
|
if (bootverbose)
|
|
ata_printf(ch, -1, "pre reset mask=%02x ostat0=%02x ostat2=%02x\n",
|
|
mask, ostat0, ostat1);
|
|
|
|
/* reset channel */
|
|
ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
|
|
DELAY(10);
|
|
ATA_IDX_OUTB(ch, ATA_ALTSTAT, ATA_A_IDS | ATA_A_RESET);
|
|
DELAY(10000);
|
|
ATA_IDX_OUTB(ch, ATA_ALTSTAT, ATA_A_IDS);
|
|
DELAY(100000);
|
|
ATA_IDX_INB(ch, ATA_ERROR);
|
|
|
|
/* wait for BUSY to go inactive */
|
|
for (timeout = 0; timeout < 310000; timeout++) {
|
|
if (stat0 & ATA_S_BUSY) {
|
|
ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
|
|
DELAY(10);
|
|
|
|
/* check for ATAPI signature while its still there */
|
|
lsb = ATA_IDX_INB(ch, ATA_CYL_LSB);
|
|
msb = ATA_IDX_INB(ch, ATA_CYL_MSB);
|
|
stat0 = ATA_IDX_INB(ch, ATA_STATUS);
|
|
if (!(stat0 & ATA_S_BUSY)) {
|
|
if (bootverbose)
|
|
ata_printf(ch, ATA_MASTER, "ATAPI %02x %02x\n", lsb, msb);
|
|
if (lsb == ATAPI_MAGIC_LSB && msb == ATAPI_MAGIC_MSB)
|
|
ch->devices |= ATA_ATAPI_MASTER;
|
|
}
|
|
}
|
|
if (stat1 & ATA_S_BUSY) {
|
|
ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_SLAVE);
|
|
DELAY(10);
|
|
|
|
/* check for ATAPI signature while its still there */
|
|
lsb = ATA_IDX_INB(ch, ATA_CYL_LSB);
|
|
msb = ATA_IDX_INB(ch, ATA_CYL_MSB);
|
|
stat1 = ATA_IDX_INB(ch, ATA_STATUS);
|
|
if (!(stat1 & ATA_S_BUSY)) {
|
|
if (bootverbose)
|
|
ata_printf(ch, ATA_SLAVE, "ATAPI %02x %02x\n", lsb, msb);
|
|
if (lsb == ATAPI_MAGIC_LSB && msb == ATAPI_MAGIC_MSB)
|
|
ch->devices |= ATA_ATAPI_SLAVE;
|
|
}
|
|
}
|
|
if (mask == 0x01) /* wait for master only */
|
|
if (!(stat0 & ATA_S_BUSY))
|
|
break;
|
|
if (mask == 0x02) /* wait for slave only */
|
|
if (!(stat1 & ATA_S_BUSY))
|
|
break;
|
|
if (mask == 0x03) /* wait for both master & slave */
|
|
if (!(stat0 & ATA_S_BUSY) && !(stat1 & ATA_S_BUSY))
|
|
break;
|
|
DELAY(100);
|
|
}
|
|
DELAY(10);
|
|
ATA_IDX_OUTB(ch, ATA_ALTSTAT, ATA_A_4BIT);
|
|
|
|
if (stat0 & ATA_S_BUSY)
|
|
mask &= ~0x01;
|
|
if (stat1 & ATA_S_BUSY)
|
|
mask &= ~0x02;
|
|
if (bootverbose)
|
|
ata_printf(ch, -1, "after reset mask=%02x stat0=%02x stat1=%02x\n",
|
|
mask, stat0, stat1);
|
|
if (!mask)
|
|
return;
|
|
|
|
if (mask & 0x01 && ostat0 != 0x00 && !(ch->devices & ATA_ATAPI_MASTER)) {
|
|
ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_MASTER);
|
|
DELAY(10);
|
|
ATA_IDX_OUTB(ch, ATA_ERROR, 0x58);
|
|
ATA_IDX_OUTB(ch, ATA_CYL_LSB, 0xa5);
|
|
lsb = ATA_IDX_INB(ch, ATA_ERROR);
|
|
msb = ATA_IDX_INB(ch, ATA_CYL_LSB);
|
|
if (bootverbose)
|
|
ata_printf(ch, ATA_MASTER, "ATA %02x %02x\n", lsb, msb);
|
|
if (lsb != 0x58 && msb == 0xa5)
|
|
ch->devices |= ATA_ATA_MASTER;
|
|
}
|
|
if (mask & 0x02 && ostat1 != 0x00 && !(ch->devices & ATA_ATAPI_SLAVE)) {
|
|
ATA_IDX_OUTB(ch, ATA_DRIVE, ATA_D_IBM | ATA_SLAVE);
|
|
DELAY(10);
|
|
ATA_IDX_OUTB(ch, ATA_ERROR, 0x58);
|
|
ATA_IDX_OUTB(ch, ATA_CYL_LSB, 0xa5);
|
|
lsb = ATA_IDX_INB(ch, ATA_ERROR);
|
|
msb = ATA_IDX_INB(ch, ATA_CYL_LSB);
|
|
if (bootverbose)
|
|
ata_printf(ch, ATA_SLAVE, "ATA %02x %02x\n", lsb, msb);
|
|
if (lsb != 0x58 && msb == 0xa5)
|
|
ch->devices |= ATA_ATA_SLAVE;
|
|
}
|
|
if (bootverbose)
|
|
ata_printf(ch, -1, "devices=%02x\n", ch->devices);
|
|
}
|
|
|
|
int
|
|
ata_reinit(struct ata_channel *ch)
|
|
{
|
|
int devices, misdev, newdev;
|
|
|
|
|
|
if (!ch->r_irq)
|
|
return ENXIO;
|
|
|
|
ATA_FORCELOCK_CH(ch, ATA_CONTROL);
|
|
ch->running = NULL;
|
|
devices = ch->devices;
|
|
ata_printf(ch, -1, "resetting devices ..\n");
|
|
ata_reset(ch);
|
|
|
|
if ((misdev = devices & ~ch->devices)) {
|
|
#ifdef DEV_ATADISK
|
|
if (misdev & ATA_ATA_MASTER && ch->device[MASTER].driver)
|
|
ad_detach(&ch->device[MASTER], 0);
|
|
if (misdev & ATA_ATA_SLAVE && ch->device[SLAVE].driver)
|
|
ad_detach(&ch->device[SLAVE], 0);
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
if (misdev & ATA_ATAPI_MASTER && ch->device[MASTER].driver)
|
|
atapi_detach(&ch->device[MASTER]);
|
|
if (misdev & ATA_ATAPI_SLAVE && ch->device[SLAVE].driver)
|
|
atapi_detach(&ch->device[SLAVE]);
|
|
#endif
|
|
if (misdev & ATA_ATA_MASTER || misdev & ATA_ATAPI_MASTER) {
|
|
if (ch->device[MASTER].param)
|
|
free(ch->device[MASTER].param, M_ATA);
|
|
ch->device[MASTER].param = NULL;
|
|
}
|
|
if (misdev & ATA_ATA_SLAVE || misdev & ATA_ATAPI_SLAVE) {
|
|
if (ch->device[SLAVE].param)
|
|
free(ch->device[SLAVE].param, M_ATA);
|
|
ch->device[SLAVE].param = NULL;
|
|
}
|
|
}
|
|
if ((newdev = ~devices & ch->devices)) {
|
|
if (newdev & ATA_ATA_MASTER)
|
|
if (ata_getparam(&ch->device[MASTER], ATA_C_ATA_IDENTIFY))
|
|
ch->devices &= ~ATA_ATA_MASTER;
|
|
if (newdev & ATA_ATA_SLAVE)
|
|
if (ata_getparam(&ch->device[SLAVE], ATA_C_ATA_IDENTIFY))
|
|
ch->devices &= ~ATA_ATA_SLAVE;
|
|
if (newdev & ATA_ATAPI_MASTER)
|
|
if (ata_getparam(&ch->device[MASTER], ATA_C_ATAPI_IDENTIFY))
|
|
ch->devices &= ~ATA_ATAPI_MASTER;
|
|
if (newdev & ATA_ATAPI_SLAVE)
|
|
if (ata_getparam(&ch->device[SLAVE], ATA_C_ATAPI_IDENTIFY))
|
|
ch->devices &= ~ATA_ATAPI_SLAVE;
|
|
}
|
|
newdev = ~devices & ch->devices;
|
|
#ifdef DEV_ATADISK
|
|
if (newdev & ATA_ATA_SLAVE && !ch->device[SLAVE].driver)
|
|
ad_attach(&ch->device[SLAVE]);
|
|
else if (ch->devices & (ATA_ATA_SLAVE) && ch->device[SLAVE].driver) {
|
|
ata_getparam(&ch->device[SLAVE], ATA_C_ATA_IDENTIFY);
|
|
ad_reinit(&ch->device[SLAVE]);
|
|
}
|
|
if (newdev & ATA_ATA_MASTER && !ch->device[MASTER].driver)
|
|
ad_attach(&ch->device[MASTER]);
|
|
else if (ch->devices & ATA_ATA_MASTER && ch->device[MASTER].driver) {
|
|
ata_getparam(&ch->device[MASTER], ATA_C_ATA_IDENTIFY);
|
|
ad_reinit(&ch->device[MASTER]);
|
|
}
|
|
#endif
|
|
#if DEV_ATAPIALL
|
|
if (newdev & ATA_ATAPI_SLAVE && !ch->device[SLAVE].driver)
|
|
atapi_attach(&ch->device[SLAVE]);
|
|
else if (ch->devices & (ATA_ATAPI_SLAVE) && ch->device[SLAVE].driver) {
|
|
ata_getparam(&ch->device[SLAVE], ATA_C_ATAPI_IDENTIFY);
|
|
atapi_reinit(&ch->device[SLAVE]);
|
|
}
|
|
if (newdev & ATA_ATAPI_MASTER && !ch->device[MASTER].driver)
|
|
atapi_attach(&ch->device[MASTER]);
|
|
else if (ch->devices & (ATA_ATAPI_MASTER) && ch->device[MASTER].driver) {
|
|
ata_getparam(&ch->device[MASTER], ATA_C_ATAPI_IDENTIFY);
|
|
atapi_reinit(&ch->device[MASTER]);
|
|
}
|
|
#endif
|
|
#ifdef DEV_ATAPICAM
|
|
atapi_cam_reinit_bus(ch);
|
|
#endif
|
|
printf("done\n");
|
|
ATA_UNLOCK_CH(ch);
|
|
ata_start(ch);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ata_service(struct ata_channel *ch)
|
|
{
|
|
/* do we have a SERVICE request from the drive ? */
|
|
if ((ch->status & (ATA_S_SERVICE|ATA_S_ERROR|ATA_S_DRQ)) == ATA_S_SERVICE) {
|
|
#if 0 /* XXX */
|
|
ATA_IDX_OUTB(ch->r_bmio, ATA_BMSTAT_PORT,
|
|
ch->dma->status(ch) | ATA_BMSTAT_INTERRUPT);
|
|
#endif
|
|
#ifdef DEV_ATADISK
|
|
if ((ATA_IDX_INB(ch, ATA_DRIVE) & ATA_SLAVE) == ATA_MASTER) {
|
|
if ((ch->devices & ATA_ATA_MASTER) && ch->device[MASTER].driver)
|
|
return ad_service((struct ad_softc *)
|
|
ch->device[MASTER].driver, 0);
|
|
}
|
|
else {
|
|
if ((ch->devices & ATA_ATA_SLAVE) && ch->device[SLAVE].driver)
|
|
return ad_service((struct ad_softc *)
|
|
ch->device[SLAVE].driver, 0);
|
|
}
|
|
#endif
|
|
}
|
|
return ATA_OP_FINISHED;
|
|
}
|
|
|
|
int
|
|
ata_wait(struct ata_device *atadev, u_int8_t mask)
|
|
{
|
|
int timeout = 0;
|
|
|
|
DELAY(1);
|
|
while (timeout < 5000000) { /* timeout 5 secs */
|
|
atadev->channel->status = ATA_IDX_INB(atadev->channel, ATA_STATUS);
|
|
|
|
/* if drive fails status, reselect the drive just to be sure */
|
|
if (atadev->channel->status == 0xff) {
|
|
ata_prtdev(atadev, "no status, reselecting device\n");
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM|atadev->unit);
|
|
DELAY(10);
|
|
atadev->channel->status = ATA_IDX_INB(atadev->channel, ATA_STATUS);
|
|
if (atadev->channel->status == 0xff)
|
|
return -1;
|
|
}
|
|
|
|
/* are we done ? */
|
|
if (!(atadev->channel->status & ATA_S_BUSY))
|
|
break;
|
|
|
|
if (timeout > 1000) {
|
|
timeout += 1000;
|
|
DELAY(1000);
|
|
}
|
|
else {
|
|
timeout += 10;
|
|
DELAY(10);
|
|
}
|
|
}
|
|
if (atadev->channel->status & ATA_S_ERROR)
|
|
atadev->channel->error = ATA_IDX_INB(atadev->channel, ATA_ERROR);
|
|
if (timeout >= 5000000)
|
|
return -1;
|
|
if (!mask)
|
|
return (atadev->channel->status & ATA_S_ERROR);
|
|
|
|
/* Wait 50 msec for bits wanted. */
|
|
timeout = 5000;
|
|
while (timeout--) {
|
|
atadev->channel->status = ATA_IDX_INB(atadev->channel, ATA_STATUS);
|
|
if ((atadev->channel->status & mask) == mask) {
|
|
if (atadev->channel->status & ATA_S_ERROR)
|
|
atadev->channel->error=ATA_IDX_INB(atadev->channel, ATA_ERROR);
|
|
return (atadev->channel->status & ATA_S_ERROR);
|
|
}
|
|
DELAY (10);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ata_command(struct ata_device *atadev, u_int8_t command,
|
|
u_int64_t lba, u_int16_t count, u_int16_t feature, int flags)
|
|
{
|
|
int error = 0;
|
|
#ifdef ATA_DEBUG
|
|
ata_prtdev(atadev, "ata_command: addr=%04lx, cmd=%02x, "
|
|
"lba=%jd, count=%d, feature=%d, flags=%02x\n",
|
|
rman_get_start(atadev->channel->r_io[ATA_DATA].res),
|
|
command, (intmax_t)lba, count, feature, flags);
|
|
#endif
|
|
|
|
/* select device */
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM | atadev->unit);
|
|
|
|
/* disable interrupt from device */
|
|
if (atadev->channel->flags & ATA_QUEUED)
|
|
ATA_IDX_OUTB(atadev->channel, ATA_ALTSTAT, ATA_A_IDS | ATA_A_4BIT);
|
|
|
|
/* ready to issue command ? */
|
|
if (ata_wait(atadev, 0) < 0) {
|
|
ata_prtdev(atadev, "timeout sending command=%02x s=%02x e=%02x\n",
|
|
command, atadev->channel->status, atadev->channel->error);
|
|
return -1;
|
|
}
|
|
|
|
/* only use 48bit addressing if needed because of the overhead */
|
|
if ((lba > 268435455 || count > 256) && atadev->param &&
|
|
atadev->param->support.address48) {
|
|
ATA_IDX_OUTB(atadev->channel, ATA_FEATURE, (feature>>8) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_FEATURE, feature);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_COUNT, (count>>8) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_COUNT, count & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, (lba>>24) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, lba & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CYL_LSB, (lba>>32) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CYL_LSB, (lba>>8) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CYL_MSB, (lba>>40) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CYL_MSB, (lba>>16) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_LBA | atadev->unit);
|
|
|
|
/* translate command into 48bit version */
|
|
switch (command) {
|
|
case ATA_C_READ:
|
|
command = ATA_C_READ48; break;
|
|
case ATA_C_READ_MUL:
|
|
command = ATA_C_READ_MUL48; break;
|
|
case ATA_C_READ_DMA:
|
|
command = ATA_C_READ_DMA48; break;
|
|
case ATA_C_READ_DMA_QUEUED:
|
|
command = ATA_C_READ_DMA_QUEUED48; break;
|
|
case ATA_C_WRITE:
|
|
command = ATA_C_WRITE48; break;
|
|
case ATA_C_WRITE_MUL:
|
|
command = ATA_C_WRITE_MUL48; break;
|
|
case ATA_C_WRITE_DMA:
|
|
command = ATA_C_WRITE_DMA48; break;
|
|
case ATA_C_WRITE_DMA_QUEUED:
|
|
command = ATA_C_WRITE_DMA_QUEUED48; break;
|
|
case ATA_C_FLUSHCACHE:
|
|
command = ATA_C_FLUSHCACHE48; break;
|
|
default:
|
|
ata_prtdev(atadev, "can't translate cmd to 48bit version\n");
|
|
return -1;
|
|
}
|
|
atadev->channel->flags |= ATA_48BIT_ACTIVE;
|
|
}
|
|
else {
|
|
ATA_IDX_OUTB(atadev->channel, ATA_FEATURE, feature);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_COUNT, count);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, lba & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CYL_LSB, (lba>>8) & 0xff);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CYL_MSB, (lba>>16) & 0xff);
|
|
if (atadev->flags & ATA_D_USE_CHS)
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE,
|
|
ATA_D_IBM | atadev->unit | ((lba>>24) & 0xf));
|
|
else
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE,
|
|
ATA_D_IBM | ATA_D_LBA | atadev->unit | ((lba>>24) &0xf));
|
|
atadev->channel->flags &= ~ATA_48BIT_ACTIVE;
|
|
}
|
|
|
|
switch (flags & ATA_WAIT_MASK) {
|
|
case ATA_IMMEDIATE:
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CMD, command);
|
|
|
|
/* enable interrupt */
|
|
if (atadev->channel->flags & ATA_QUEUED)
|
|
ATA_IDX_OUTB(atadev->channel, ATA_ALTSTAT, ATA_A_4BIT);
|
|
break;
|
|
|
|
case ATA_WAIT_INTR:
|
|
atadev->channel->active |= ATA_WAIT_INTR;
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CMD, command);
|
|
|
|
/* enable interrupt */
|
|
if (atadev->channel->flags & ATA_QUEUED)
|
|
ATA_IDX_OUTB(atadev->channel, ATA_ALTSTAT, ATA_A_4BIT);
|
|
|
|
if (tsleep(atadev->channel, PRIBIO, "atacmd", 10 * hz)) {
|
|
ata_prtdev(atadev, "timeout waiting for interrupt\n");
|
|
atadev->channel->active &= ~ATA_WAIT_INTR;
|
|
error = -1;
|
|
}
|
|
break;
|
|
|
|
case ATA_WAIT_READY:
|
|
atadev->channel->active |= ATA_WAIT_READY;
|
|
ATA_IDX_OUTB(atadev->channel, ATA_CMD, command);
|
|
if (ata_wait(atadev, ATA_S_READY) < 0) {
|
|
ata_prtdev(atadev, "timeout waiting for cmd=%02x s=%02x e=%02x\n",
|
|
command, atadev->channel->status,atadev->channel->error);
|
|
error = -1;
|
|
}
|
|
atadev->channel->active &= ~ATA_WAIT_READY;
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
ata_enclosure_start(struct ata_device *atadev)
|
|
{
|
|
ATA_IDX_INB(atadev->channel, ATA_DRIVE);
|
|
DELAY(1);
|
|
ATA_IDX_INB(atadev->channel, ATA_DRIVE);
|
|
DELAY(1);
|
|
ATA_IDX_INB(atadev->channel, ATA_CMD);
|
|
DELAY(1);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM | atadev->unit);
|
|
DELAY(1);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM | atadev->unit);
|
|
DELAY(1);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM | atadev->unit);
|
|
DELAY(1);
|
|
ATA_IDX_INB(atadev->channel, ATA_COUNT);
|
|
DELAY(1);
|
|
ATA_IDX_INB(atadev->channel, ATA_DRIVE);
|
|
DELAY(1);
|
|
}
|
|
|
|
static void
|
|
ata_enclosure_end(struct ata_device *atadev)
|
|
{
|
|
ATA_IDX_OUTB(atadev->channel, ATA_DRIVE, ATA_D_IBM | atadev->unit);
|
|
DELAY(1);
|
|
}
|
|
|
|
static void
|
|
ata_enclosure_chip_start(struct ata_device *atadev)
|
|
{
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x0b);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x0a);
|
|
DELAY(25);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x08);
|
|
}
|
|
|
|
static void
|
|
ata_enclosure_chip_end(struct ata_device *atadev)
|
|
{
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x08);
|
|
DELAY(64);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x0a);
|
|
DELAY(25);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x0b);
|
|
DELAY(64);
|
|
}
|
|
|
|
static u_int8_t
|
|
ata_enclosure_chip_rdbit(struct ata_device *atadev)
|
|
{
|
|
u_int8_t val;
|
|
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0);
|
|
DELAY(64);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x02);
|
|
DELAY(25);
|
|
val = ATA_IDX_INB(atadev->channel, ATA_SECTOR) & 0x01;
|
|
DELAY(38);
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
ata_enclosure_chip_wrbit(struct ata_device *atadev, u_int8_t data)
|
|
{
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x08 | (data & 0x01));
|
|
DELAY(64);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_SECTOR, 0x08 | 0x02 | (data & 0x01));
|
|
DELAY(64);
|
|
}
|
|
|
|
static u_int8_t
|
|
ata_enclosure_chip_rw(struct ata_device *atadev, int rw, u_int8_t val)
|
|
{
|
|
int i;
|
|
|
|
if (rw) {
|
|
for (i = 0; i < 8; i++)
|
|
ata_enclosure_chip_wrbit(atadev, (val & (0x80 >> i)) ? 1 : 0);
|
|
}
|
|
else {
|
|
for (i = 0; i < 8; i++)
|
|
val = (val << 1) | ata_enclosure_chip_rdbit(atadev);
|
|
}
|
|
ata_enclosure_chip_wrbit(atadev, 0);
|
|
return val;
|
|
}
|
|
|
|
static u_int8_t
|
|
ata_enclosure_sensor(struct ata_device *atadev,
|
|
int rw, u_int8_t idx, u_int8_t data)
|
|
{
|
|
ata_enclosure_start(atadev);
|
|
ata_enclosure_chip_start(atadev);
|
|
ata_enclosure_chip_rw(atadev, 1, 0x5a);
|
|
ata_enclosure_chip_rw(atadev, 1, idx);
|
|
if (rw) {
|
|
ata_enclosure_chip_rw(atadev, 1, data);
|
|
}
|
|
else {
|
|
ata_enclosure_chip_end(atadev);
|
|
ata_enclosure_chip_start(atadev);
|
|
ata_enclosure_chip_rw(atadev, 1, 0x5b);
|
|
data = ata_enclosure_chip_rw(atadev, 0, 0);
|
|
}
|
|
ata_enclosure_chip_end(atadev);
|
|
ata_enclosure_end(atadev);
|
|
return data;
|
|
}
|
|
|
|
static int
|
|
ata_enclosure_status(struct ata_device *atadev,
|
|
int *fan, int *temp, int *v05, int *v12)
|
|
{
|
|
u_int8_t id1, id2, cnt, div;
|
|
int error = ENXIO;
|
|
|
|
if (atadev->flags & ATA_D_ENC_PRESENT) {
|
|
atadev->channel->locking(atadev->channel, ATA_LF_LOCK);
|
|
ATA_SLEEPLOCK_CH(atadev->channel, ATA_CONTROL);
|
|
ata_enclosure_sensor(atadev, 1, 0x4e, 0);
|
|
id1 = ata_enclosure_sensor(atadev, 0, 0x4f, 0);
|
|
ata_enclosure_sensor(atadev, 1, 0x4e, 0x80);
|
|
id2 = ata_enclosure_sensor(atadev, 0, 0x4f, 0);
|
|
if (id1 == 0xa3 && id2 == 0x5c) {
|
|
div = 1 << (((ata_enclosure_sensor(atadev, 0, 0x5d, 0)&0x20)>>3)+
|
|
((ata_enclosure_sensor(atadev, 0, 0x47, 0)&0x30)>>4)+1);
|
|
cnt = ata_enclosure_sensor(atadev, 0, 0x28, 0);
|
|
if (cnt == 0xff)
|
|
*fan = 0;
|
|
else
|
|
*fan = 1350000 / cnt / div;
|
|
ata_enclosure_sensor(atadev, 1, 0x4e, 0x01);
|
|
*temp = (ata_enclosure_sensor(atadev, 0, 0x50, 0) * 10) +
|
|
(ata_enclosure_sensor(atadev, 0, 0x50, 0) & 0x80 ? 5 : 0);
|
|
*v05 = ata_enclosure_sensor(atadev, 0, 0x23, 0) * 27;
|
|
*v12 = ata_enclosure_sensor(atadev, 0, 0x24, 0) * 61;
|
|
error = 0;
|
|
}
|
|
ATA_UNLOCK_CH(atadev->channel);
|
|
atadev->channel->locking(atadev->channel, ATA_LF_UNLOCK);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void
|
|
ata_enclosure_print(struct ata_device *atadev)
|
|
{
|
|
u_int8_t id, st;
|
|
int fan, temp, v05, v12;
|
|
|
|
atadev->channel->locking(atadev->channel, ATA_LF_LOCK);
|
|
ATA_SLEEPLOCK_CH(atadev->channel, ATA_CONTROL);
|
|
ata_enclosure_start(atadev);
|
|
id = ATA_IDX_INB(atadev->channel, ATA_DRIVE);
|
|
DELAY(1);
|
|
st = ATA_IDX_INB(atadev->channel, ATA_COUNT);
|
|
DELAY(1);
|
|
ata_enclosure_end(atadev);
|
|
ATA_UNLOCK_CH(atadev->channel);
|
|
atadev->channel->locking(atadev->channel, ATA_LF_UNLOCK);
|
|
|
|
switch (id & 0x93) {
|
|
case 0x00:
|
|
ata_prtdev(atadev, "Universal enclosure");
|
|
break;
|
|
case 0x01:
|
|
ata_prtdev(atadev, "FastSwap enclosure");
|
|
break;
|
|
case 0x10:
|
|
case 0x11:
|
|
ata_prtdev(atadev, "SuperSwap enclosure");
|
|
break;
|
|
default:
|
|
atadev->flags &= ~ATA_D_ENC_PRESENT;
|
|
return;
|
|
}
|
|
atadev->flags |= ATA_D_ENC_PRESENT;
|
|
|
|
ata_enclosure_leds(atadev, ATA_LED_GREEN);
|
|
if (ata_enclosure_status(atadev, &fan, &temp, &v05, &v12))
|
|
printf(" detected\n");
|
|
else
|
|
printf(" [FAN:%drpm TEMP:%d.%01dC %d.%03dV %d.%03dV]\n",
|
|
fan, temp/10, temp%10, v05/1000, v05%1000, v12/1000, v12%1000);
|
|
}
|
|
|
|
void
|
|
ata_enclosure_leds(struct ata_device *atadev, u_int8_t color)
|
|
{
|
|
if (atadev->flags & ATA_D_ENC_PRESENT) {
|
|
u_int8_t reg;
|
|
|
|
ata_enclosure_start(atadev);
|
|
reg = ATA_IDX_INB(atadev->channel, ATA_COUNT);
|
|
DELAY(1);
|
|
ATA_IDX_OUTB(atadev->channel, ATA_COUNT,
|
|
(color & ATA_LED_MASK) | (reg & ~ATA_LED_MASK));
|
|
DELAY(1);
|
|
ata_enclosure_end(atadev);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ata_change_mode(struct ata_device *atadev, int mode)
|
|
{
|
|
ATA_SLEEPLOCK_CH(atadev->channel, ATA_ACTIVE);
|
|
atadev->setmode(atadev, mode);
|
|
ATA_UNLOCK_CH(atadev->channel);
|
|
ata_start(atadev->channel);
|
|
}
|
|
|
|
int
|
|
ata_printf(struct ata_channel *ch, int device, const char * fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
if (device == -1)
|
|
ret = printf("ata%d: ", device_get_unit(ch->dev));
|
|
else {
|
|
if (ch->device[ATA_DEV(device)].name)
|
|
ret = printf("%s: ", ch->device[ATA_DEV(device)].name);
|
|
else
|
|
ret = printf("ata%d-%s: ", device_get_unit(ch->dev),
|
|
(device == ATA_MASTER) ? "master" : "slave");
|
|
}
|
|
va_start(ap, fmt);
|
|
ret += vprintf(fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
ata_prtdev(struct ata_device *atadev, const char * fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
if (atadev->name)
|
|
ret = printf("%s: ", atadev->name);
|
|
else
|
|
ret = printf("ata%d-%s: ", device_get_unit(atadev->channel->dev),
|
|
(atadev->unit == ATA_MASTER) ? "master" : "slave");
|
|
va_start(ap, fmt);
|
|
ret += vprintf(fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
ata_set_name(struct ata_device *atadev, char *name, int lun)
|
|
{
|
|
atadev->name = malloc(strlen(name) + 4, M_ATA, M_NOWAIT);
|
|
if (atadev->name)
|
|
sprintf(atadev->name, "%s%d", name, lun);
|
|
}
|
|
|
|
void
|
|
ata_free_name(struct ata_device *atadev)
|
|
{
|
|
if (atadev->name)
|
|
free(atadev->name, M_ATA);
|
|
atadev->name = NULL;
|
|
}
|
|
|
|
int
|
|
ata_get_lun(u_int32_t *map)
|
|
{
|
|
int lun = ffs(~*map) - 1;
|
|
|
|
*map |= (1 << lun);
|
|
return lun;
|
|
}
|
|
|
|
int
|
|
ata_test_lun(u_int32_t *map, int lun)
|
|
{
|
|
return (*map & (1 << lun));
|
|
}
|
|
|
|
void
|
|
ata_free_lun(u_int32_t *map, int lun)
|
|
{
|
|
*map &= ~(1 << lun);
|
|
}
|
|
|
|
char *
|
|
ata_mode2str(int mode)
|
|
{
|
|
switch (mode) {
|
|
case ATA_PIO: return "BIOSPIO";
|
|
case ATA_PIO0: return "PIO0";
|
|
case ATA_PIO1: return "PIO1";
|
|
case ATA_PIO2: return "PIO2";
|
|
case ATA_PIO3: return "PIO3";
|
|
case ATA_PIO4: return "PIO4";
|
|
case ATA_DMA: return "BIOSDMA";
|
|
case ATA_WDMA0: return "WDMA0";
|
|
case ATA_WDMA1: return "WDMA1";
|
|
case ATA_WDMA2: return "WDMA2";
|
|
case ATA_UDMA0: return "UDMA16";
|
|
case ATA_UDMA1: return "UDMA25";
|
|
case ATA_UDMA2: return "UDMA33";
|
|
case ATA_UDMA3: return "UDMA40";
|
|
case ATA_UDMA4: return "UDMA66";
|
|
case ATA_UDMA5: return "UDMA100";
|
|
case ATA_UDMA6: return "UDMA133";
|
|
default: return "???";
|
|
}
|
|
}
|
|
|
|
int
|
|
ata_pmode(struct ata_params *ap)
|
|
{
|
|
if (ap->atavalid & ATA_FLAG_64_70) {
|
|
if (ap->apiomodes & 0x02)
|
|
return ATA_PIO4;
|
|
if (ap->apiomodes & 0x01)
|
|
return ATA_PIO3;
|
|
}
|
|
if (ap->retired_piomode == 2)
|
|
return ATA_PIO2;
|
|
if (ap->retired_piomode == 1)
|
|
return ATA_PIO1;
|
|
if (ap->retired_piomode == 0)
|
|
return ATA_PIO0;
|
|
if (ap->support_dma)
|
|
return ATA_PIO4;
|
|
return ATA_PIO0;
|
|
}
|
|
|
|
int
|
|
ata_wmode(struct ata_params *ap)
|
|
{
|
|
if (ap->mwdmamodes & 0x04)
|
|
return ATA_WDMA2;
|
|
if (ap->mwdmamodes & 0x02)
|
|
return ATA_WDMA1;
|
|
if (ap->mwdmamodes & 0x01)
|
|
return ATA_WDMA0;
|
|
if (ap->support_dma)
|
|
return ATA_WDMA2;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ata_umode(struct ata_params *ap)
|
|
{
|
|
if (ap->atavalid & ATA_FLAG_88) {
|
|
if (ap->udmamodes & 0x40)
|
|
return ATA_UDMA6;
|
|
if (ap->udmamodes & 0x20)
|
|
return ATA_UDMA5;
|
|
if (ap->udmamodes & 0x10)
|
|
return ATA_UDMA4;
|
|
if (ap->udmamodes & 0x08)
|
|
return ATA_UDMA3;
|
|
if (ap->udmamodes & 0x04)
|
|
return ATA_UDMA2;
|
|
if (ap->udmamodes & 0x02)
|
|
return ATA_UDMA1;
|
|
if (ap->udmamodes & 0x01)
|
|
return ATA_UDMA0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
ata_limit_mode(struct ata_device *atadev, int mode, int maxmode)
|
|
{
|
|
if (maxmode && mode > maxmode)
|
|
mode = maxmode;
|
|
|
|
if (mode >= ATA_UDMA0 && ata_umode(atadev->param) > 0)
|
|
return min(mode, ata_umode(atadev->param));
|
|
|
|
if (mode >= ATA_WDMA0 && ata_wmode(atadev->param) > 0)
|
|
return min(mode, ata_wmode(atadev->param));
|
|
|
|
if (mode > ata_pmode(atadev->param))
|
|
return min(mode, ata_pmode(atadev->param));
|
|
|
|
return mode;
|
|
}
|
|
|
|
static void
|
|
bswap(int8_t *buf, int len)
|
|
{
|
|
u_int16_t *ptr = (u_int16_t*)(buf + len);
|
|
|
|
while (--ptr >= (u_int16_t*)buf)
|
|
*ptr = ntohs(*ptr);
|
|
}
|
|
|
|
static void
|
|
btrim(int8_t *buf, int len)
|
|
{
|
|
int8_t *ptr;
|
|
|
|
for (ptr = buf; ptr < buf+len; ++ptr)
|
|
if (!*ptr)
|
|
*ptr = ' ';
|
|
for (ptr = buf + len - 1; ptr >= buf && *ptr == ' '; --ptr)
|
|
*ptr = 0;
|
|
}
|
|
|
|
static void
|
|
bpack(int8_t *src, int8_t *dst, int len)
|
|
{
|
|
int i, j, blank;
|
|
|
|
for (i = j = blank = 0 ; i < len; i++) {
|
|
if (blank && src[i] == ' ') continue;
|
|
if (blank && src[i] != ' ') {
|
|
dst[j++] = src[i];
|
|
blank = 0;
|
|
continue;
|
|
}
|
|
if (src[i] == ' ') {
|
|
blank = 1;
|
|
if (i == 0)
|
|
continue;
|
|
}
|
|
dst[j++] = src[i];
|
|
}
|
|
if (j < len)
|
|
dst[j] = 0x00;
|
|
}
|
|
|
|
static void
|
|
ata_init(void)
|
|
{
|
|
/* register controlling device */
|
|
make_dev(&ata_cdevsw, 0, UID_ROOT, GID_OPERATOR, 0600, "ata");
|
|
|
|
/* register boot attach to be run when interrupts are enabled */
|
|
if (!(ata_delayed_attach = (struct intr_config_hook *)
|
|
malloc(sizeof(struct intr_config_hook),
|
|
M_TEMP, M_NOWAIT | M_ZERO))) {
|
|
printf("ata: malloc of delayed attach hook failed\n");
|
|
return;
|
|
}
|
|
|
|
ata_delayed_attach->ich_func = (void*)ata_boot_attach;
|
|
if (config_intrhook_establish(ata_delayed_attach) != 0) {
|
|
printf("ata: config_intrhook_establish failed\n");
|
|
free(ata_delayed_attach, M_TEMP);
|
|
}
|
|
}
|
|
SYSINIT(atadev, SI_SUB_DRIVERS, SI_ORDER_SECOND, ata_init, NULL)
|