0cdc179319
boot problems.. However this demands that dangerously dedicated disks use an offset of at least 10 from the start to not overwrite the raid config sector on the HPT...
549 lines
16 KiB
C
549 lines
16 KiB
C
/*-
|
|
* Copyright (c) 2000 Søren Schmidt
|
|
* 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_global.h"
|
|
#include "opt_ata.h"
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/bio.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/disk.h>
|
|
#include <sys/devicestat.h>
|
|
#include <sys/cons.h>
|
|
#include <machine/bus.h>
|
|
#include <dev/ata/ata-all.h>
|
|
#include <dev/ata/ata-disk.h>
|
|
#include <dev/ata/ata-raid.h>
|
|
|
|
/* device structures */
|
|
static d_open_t aropen;
|
|
static d_strategy_t arstrategy;
|
|
static struct cdevsw ar_cdevsw = {
|
|
/* open */ aropen,
|
|
/* close */ nullclose,
|
|
/* read */ physread,
|
|
/* write */ physwrite,
|
|
/* ioctl */ noioctl,
|
|
/* poll */ nopoll,
|
|
/* mmap */ nommap,
|
|
/* strategy */ arstrategy,
|
|
/* name */ "ar",
|
|
/* maj */ 157,
|
|
/* dump */ nodump,
|
|
/* psize */ nopsize,
|
|
/* flags */ D_DISK,
|
|
/* bmaj */ -1
|
|
};
|
|
static struct cdevsw ardisk_cdevsw;
|
|
|
|
/* prototypes */
|
|
static void ar_attach(struct ar_softc *);
|
|
static void ar_done(struct bio *);
|
|
static int ar_highpoint_conf(struct ad_softc *, struct ar_softc **);
|
|
static int ar_promise_conf(struct ad_softc *, struct ar_softc **);
|
|
static int ar_read(struct ad_softc *, u_int32_t, int, char *);
|
|
|
|
/* internal vars */
|
|
static int ar_init = 0;
|
|
static struct ar_softc *ar_table[8];
|
|
MALLOC_DEFINE(M_AR, "AR driver", "ATA RAID driver");
|
|
|
|
/* defines */
|
|
#define PRINT_AD(adp) \
|
|
printf(" ad%d: %luMB <%.40s> [%d/%d/%d] at ata%d-%s %s%s\n", \
|
|
adp->lun, adp->total_secs / ((1024L * 1024L) / DEV_BSIZE), \
|
|
adp->controller->dev_param[ATA_DEV(adp->unit)]->model, \
|
|
adp->total_secs / (adp->heads * adp->sectors), \
|
|
adp->heads, adp->sectors, device_get_unit(adp->controller->dev),\
|
|
(adp->unit == ATA_MASTER) ? "master" : "slave", \
|
|
(adp->flags & AD_F_TAG_ENABLED) ? "tagged " : "", \
|
|
ata_mode2str(adp->controller->mode[ATA_DEV(adp->unit)]))
|
|
|
|
int
|
|
ar_probe(struct ad_softc *adp)
|
|
{
|
|
if (!ar_init) {
|
|
bzero(&ar_table, sizeof(ar_table));
|
|
ar_init = 1;
|
|
}
|
|
|
|
switch(adp->controller->chiptype) {
|
|
case 0x4d33105a:
|
|
case 0x4d38105a:
|
|
case 0x4d30105a:
|
|
case 0x0d30105a:
|
|
return (ar_promise_conf(adp, ar_table));
|
|
|
|
case 0x00041103:
|
|
return (ar_highpoint_conf(adp, ar_table));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
ar_attach(struct ar_softc *raid)
|
|
{
|
|
dev_t dev;
|
|
int i;
|
|
|
|
printf("ar%d: %luMB <ATA ",
|
|
raid->lun, raid->total_secs / ((1024L * 1024L) / DEV_BSIZE));
|
|
switch (raid->flags & (AR_F_RAID_0 | AR_F_RAID_1 | AR_F_SPAN)) {
|
|
case AR_F_RAID_0:
|
|
printf("RAID0 "); break;
|
|
case AR_F_RAID_1:
|
|
printf("RAID1 "); break;
|
|
case AR_F_SPAN:
|
|
printf("SPAN "); break;
|
|
case (AR_F_RAID_0 | AR_F_RAID_1):
|
|
printf("RAID0+1 "); break;
|
|
default:
|
|
printf("unknown array 0x%x ", raid->flags);
|
|
return;
|
|
}
|
|
printf("array> [%d/%d/%d] subdisks:\n",
|
|
raid->cylinders, raid->heads, raid->sectors);
|
|
for (i = 0; i < raid->num_subdisks; i++)
|
|
PRINT_AD(raid->subdisk[i]);
|
|
for (i = 0; i < raid->num_mirrordisks; i++)
|
|
PRINT_AD(raid->mirrordisk[i]);
|
|
|
|
dev = disk_create(raid->lun, &raid->disk, 0, &ar_cdevsw, &ardisk_cdevsw);
|
|
dev->si_drv1 = raid;
|
|
dev->si_iosize_max = 256 * DEV_BSIZE;
|
|
raid->dev = dev;
|
|
}
|
|
|
|
static int
|
|
aropen(dev_t dev, int flags, int fmt, struct proc *p)
|
|
{
|
|
struct ar_softc *rdp = dev->si_drv1;
|
|
struct disklabel *dl;
|
|
|
|
dl = &rdp->disk.d_label;
|
|
bzero(dl, sizeof *dl);
|
|
dl->d_secsize = DEV_BSIZE;
|
|
dl->d_nsectors = rdp->sectors;
|
|
dl->d_ntracks = rdp->heads;
|
|
dl->d_ncylinders = rdp->cylinders;
|
|
dl->d_secpercyl = rdp->sectors * rdp->heads;
|
|
dl->d_secperunit = rdp->total_secs;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
arstrategy(struct bio *bp)
|
|
{
|
|
struct ar_softc *rdp = bp->bio_dev->si_drv1;
|
|
int lba, count, chunk;
|
|
caddr_t data;
|
|
|
|
/* if it's a null transfer, return immediatly. */
|
|
if (bp->bio_bcount == 0) {
|
|
bp->bio_resid = 0;
|
|
biodone(bp);
|
|
return;
|
|
}
|
|
|
|
bp->bio_resid = bp->bio_bcount;
|
|
lba = bp->bio_pblkno;
|
|
data = bp->bio_data;
|
|
for (count = howmany(bp->bio_bcount, DEV_BSIZE); count > 0;
|
|
count -= chunk, lba += chunk, data += (chunk * DEV_BSIZE)) {
|
|
struct ar_buf *buf1, *buf2;
|
|
int plba;
|
|
|
|
buf1 = malloc(sizeof(struct ar_buf), M_AR, M_NOWAIT);
|
|
bzero(buf1, sizeof(struct ar_buf));
|
|
if (rdp->flags & AR_F_SPAN) {
|
|
plba = lba;
|
|
while (plba >= (rdp->subdisk[buf1->drive]->total_secs-rdp->reserved)
|
|
&& buf1->drive < rdp->num_subdisks)
|
|
plba-=(rdp->subdisk[buf1->drive++]->total_secs-rdp->reserved);
|
|
buf1->bp.bio_pblkno = plba;
|
|
chunk = min(rdp->subdisk[buf1->drive]->total_secs -
|
|
rdp->reserved - plba, count);
|
|
}
|
|
else if (rdp->flags & AR_F_RAID_0) {
|
|
plba = lba / rdp->interleave;
|
|
chunk = lba % rdp->interleave;
|
|
buf1->drive = plba % rdp->num_subdisks;
|
|
buf1->bp.bio_pblkno =
|
|
((plba / rdp->num_subdisks) * rdp->interleave) + chunk;
|
|
chunk = min(rdp->interleave - chunk, count);
|
|
}
|
|
else {
|
|
buf1->bp.bio_pblkno = lba;
|
|
buf1->drive = 0;
|
|
chunk = count;
|
|
}
|
|
|
|
if (buf1->drive > 0)
|
|
buf1->bp.bio_pblkno += rdp->offset;
|
|
buf1->bp.bio_caller1 = (void *)rdp;
|
|
buf1->bp.bio_bcount = chunk * DEV_BSIZE;
|
|
buf1->bp.bio_data = data;
|
|
buf1->bp.bio_cmd = bp->bio_cmd;
|
|
buf1->bp.bio_flags = bp->bio_flags;
|
|
buf1->bp.bio_done = ar_done;
|
|
buf1->org = bp;
|
|
|
|
/* simpleminded load balancing on RAID1 arrays */
|
|
if (rdp->flags & AR_F_RAID_1 && bp->bio_cmd == BIO_READ) {
|
|
if (buf1->bp.bio_pblkno <
|
|
(rdp->last_lba[buf1->drive][rdp->last_disk] - 100) ||
|
|
buf1->bp.bio_pblkno >
|
|
(rdp->last_lba[buf1->drive][rdp->last_disk] + 100)) {
|
|
rdp->last_disk = 1 - rdp->last_disk;
|
|
rdp->last_lba[buf1->drive][rdp->last_disk] =
|
|
buf1->bp.bio_pblkno;
|
|
}
|
|
if (rdp->last_disk)
|
|
buf1->bp.bio_dev = rdp->mirrordisk[buf1->drive]->dev;
|
|
else
|
|
buf1->bp.bio_dev = rdp->subdisk[buf1->drive]->dev;
|
|
}
|
|
else
|
|
buf1->bp.bio_dev = rdp->subdisk[buf1->drive]->dev;
|
|
|
|
if (rdp->flags & AR_F_RAID_1 && bp->bio_cmd == BIO_WRITE) {
|
|
buf2 = malloc(sizeof(struct ar_buf), M_AR, M_NOWAIT);
|
|
bcopy(buf1, buf2, sizeof(struct ar_buf));
|
|
buf2->bp.bio_dev = rdp->mirrordisk[buf1->drive]->dev;
|
|
buf2->mirror = buf1;
|
|
buf1->mirror = buf2;
|
|
buf2->bp.bio_dev->si_disk->d_devsw->d_strategy((struct bio *)buf2);
|
|
}
|
|
buf1->bp.bio_dev->si_disk->d_devsw->d_strategy((struct bio *)buf1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ar_done(struct bio *bp)
|
|
{
|
|
struct ar_softc *rdp = (struct ar_softc *)bp->bio_caller1;
|
|
struct ar_buf *buf = (struct ar_buf *)bp;
|
|
int s;
|
|
|
|
s = splbio();
|
|
|
|
if (bp->bio_flags & BIO_ERROR) {
|
|
if (bp->bio_cmd == BIO_WRITE || buf->done || !(rdp->flags&AR_F_RAID_1)){
|
|
buf->org->bio_flags |= BIO_ERROR;
|
|
buf->org->bio_error = bp->bio_error;
|
|
}
|
|
printf("ar%d: subdisk error\n", rdp->lun);
|
|
}
|
|
|
|
if (rdp->flags & AR_F_RAID_1) {
|
|
if (bp->bio_cmd == BIO_WRITE) {
|
|
if (!buf->done) {
|
|
buf->mirror->done = 1;
|
|
goto done;
|
|
}
|
|
}
|
|
else {
|
|
if (!buf->done && bp->bio_flags & BIO_ERROR) {
|
|
/* read error on this disk, try mirror */
|
|
buf->done = 1;
|
|
buf->bp.bio_dev = rdp->mirrordisk[buf->drive]->dev;
|
|
buf->bp.bio_dev->si_disk->d_devsw->d_strategy((struct bio*)buf);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
buf->org->bio_resid -= bp->bio_bcount;
|
|
if (buf->org->bio_resid == 0)
|
|
biodone(buf->org);
|
|
done:
|
|
free(buf, M_AR);
|
|
splx(s);
|
|
}
|
|
|
|
/* read the RAID info from a disk on a HighPoint controller */
|
|
static int
|
|
ar_highpoint_conf(struct ad_softc *adp, struct ar_softc **raidp)
|
|
{
|
|
struct highpoint_raid_conf info;
|
|
struct ar_softc *raid;
|
|
int array;
|
|
|
|
if (ar_read(adp, 0x09, DEV_BSIZE, (char *)&info)) {
|
|
if (bootverbose)
|
|
printf("HighPoint read conf failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/* check if this is a HighPoint RAID struct */
|
|
if (info.magic != HPT_MAGIC_OK) {
|
|
if (bootverbose)
|
|
printf("HighPoint check1 failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/* now convert HighPoint config info into our generic form */
|
|
for (array = 0; array < 8; array++) {
|
|
if (!raidp[array]) {
|
|
raidp[array] =
|
|
(struct ar_softc*)malloc(sizeof(struct ar_softc),M_AR,M_NOWAIT);
|
|
if (!raidp[array]) {
|
|
printf("ar: failed to allocate raid config storage\n");
|
|
return 1;
|
|
}
|
|
bzero(raidp[array], sizeof(struct ar_softc));
|
|
}
|
|
raid = raidp[array];
|
|
|
|
switch (info.type) {
|
|
case HPT_T_RAID_0:
|
|
/* check the order byte to determine what this really is */
|
|
switch (info.order & (HPT_O_MIRROR | HPT_O_STRIPE)) {
|
|
case HPT_O_MIRROR:
|
|
goto hpt_mirror;
|
|
|
|
case HPT_O_STRIPE:
|
|
if (raid->magic_0 && raid->magic_0 != info.magic_0)
|
|
continue;
|
|
raid->magic_0 = info.magic_0;
|
|
raid->flags |= (AR_F_RAID_0 | AR_F_RAID_1);
|
|
raid->interleave = 1 << info.raid0_shift;
|
|
raid->subdisk[info.disk_number] = adp;
|
|
raid->num_subdisks++;
|
|
if ((raid->num_subdisks + raid->num_mirrordisks) ==
|
|
(info.raid_disks * 2))
|
|
raid->flags |= AR_F_CONF_DONE;
|
|
break;
|
|
|
|
case (HPT_O_MIRROR | HPT_O_STRIPE):
|
|
if (raid->magic_1 && raid->magic_1 != info.magic_0)
|
|
continue;
|
|
raid->magic_1 = info.magic_0;
|
|
raid->flags |= (AR_F_RAID_0 | AR_F_RAID_1);
|
|
raid->mirrordisk[info.disk_number] = adp;
|
|
raid->num_mirrordisks++;
|
|
if ((raid->num_subdisks + raid->num_mirrordisks) ==
|
|
(info.raid_disks * 2))
|
|
raid->flags |= AR_F_CONF_DONE;
|
|
break;
|
|
|
|
default:
|
|
if (raid->magic_0 && raid->magic_0 != info.magic_0)
|
|
continue;
|
|
raid->magic_0 = info.magic_0;
|
|
raid->flags |= AR_F_RAID_0;
|
|
raid->interleave = 1 << info.raid0_shift;
|
|
raid->subdisk[info.disk_number] = adp;
|
|
raid->num_subdisks++;
|
|
if (raid->num_subdisks == info.raid_disks)
|
|
raid->flags |= AR_F_CONF_DONE;
|
|
}
|
|
break;
|
|
|
|
case HPT_T_RAID_1:
|
|
hpt_mirror:
|
|
if (raid->magic_0 && raid->magic_0 != info.magic_0)
|
|
continue;
|
|
raid->magic_0 = info.magic_0;
|
|
raid->flags |= AR_F_RAID_1;
|
|
if (info.disk_number == 0 && raid->num_subdisks == 0) {
|
|
raid->subdisk[raid->num_subdisks] = adp;
|
|
raid->num_subdisks = 1;
|
|
}
|
|
if (info.disk_number != 0 && raid->num_mirrordisks == 0) {
|
|
raid->mirrordisk[raid->num_mirrordisks] = adp;
|
|
raid->num_mirrordisks = 1;
|
|
}
|
|
if ((raid->num_subdisks +
|
|
raid->num_mirrordisks) == (info.raid_disks * 2))
|
|
raid->flags |= AR_F_CONF_DONE;
|
|
break;
|
|
|
|
case HPT_T_SPAN:
|
|
if (raid->magic_0 && raid->magic_0 != info.magic_0)
|
|
continue;
|
|
raid->magic_0 = info.magic_0;
|
|
raid->flags |= AR_F_SPAN;
|
|
raid->subdisk[info.disk_number] = adp;
|
|
raid->num_subdisks++;
|
|
if (raid->num_subdisks == info.raid_disks)
|
|
raid->flags |= AR_F_CONF_DONE;
|
|
break;
|
|
|
|
default:
|
|
printf("HighPoint unknown RAID type 0x%02x\n", info.type);
|
|
}
|
|
|
|
/* do we have a complete array to attach to ? */
|
|
if (raid->flags & AR_F_CONF_DONE) {
|
|
raid->lun = array;
|
|
raid->heads = 255;
|
|
raid->sectors = 63;
|
|
raid->cylinders = (info.total_secs - 9) / (63 * 255);
|
|
raid->total_secs = info.total_secs - (9 * raid->num_subdisks);
|
|
raid->offset = 10;
|
|
raid->reserved = 10;
|
|
ar_attach(raid);
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* read the RAID info from a disk on a Promise Fasttrak controller */
|
|
static int
|
|
ar_promise_conf(struct ad_softc *adp, struct ar_softc **raidp)
|
|
{
|
|
struct promise_raid_conf info;
|
|
struct ar_softc *raid;
|
|
u_int32_t lba, magic;
|
|
u_int32_t cksum, *ckptr;
|
|
int count, disk_number, array;
|
|
|
|
lba = adp->total_secs - adp->sectors;
|
|
|
|
if (ar_read(adp, lba, 4 * DEV_BSIZE, (char *)&info)) {
|
|
if (bootverbose)
|
|
printf("Promise read conf failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/* check if this is a Promise RAID struct */
|
|
if (strncmp(info.promise_id, PR_MAGIC, sizeof(PR_MAGIC))) {
|
|
if (bootverbose)
|
|
printf("Promise check1 failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/* check if the checksum is OK */
|
|
for (cksum = 0, ckptr = (int32_t *)&info, count = 0; count < 511; count++)
|
|
cksum += *ckptr++;
|
|
if (cksum != *ckptr) {
|
|
if (bootverbose)
|
|
printf("Promise check2 failed\n");
|
|
return 1;
|
|
}
|
|
|
|
/* now convert Promise config info into our generic form */
|
|
if ((info.raid.flags != PR_F_CONFED) ||
|
|
(((info.raid.status & (PR_S_DEFINED|PR_S_ONLINE)) !=
|
|
(PR_S_DEFINED|PR_S_ONLINE)))) {
|
|
return 1;
|
|
}
|
|
|
|
magic = (adp->controller->chiptype >> 16) | info.raid.array_number << 16;
|
|
|
|
array = info.raid.array_number;
|
|
if (raidp[array]) {
|
|
if (magic != raidp[array]->magic_0)
|
|
return 1;
|
|
}
|
|
else {
|
|
if (!(raidp[array] = (struct ar_softc *)
|
|
malloc(sizeof(struct ar_softc), M_AR, M_NOWAIT))) {
|
|
printf("ar: failed to allocate raid config storage\n");
|
|
return 1;
|
|
}
|
|
else
|
|
bzero(raidp[array], sizeof(struct ar_softc));
|
|
}
|
|
raid = raidp[array];
|
|
raid->magic_0 = magic;
|
|
|
|
switch (info.raid.type) {
|
|
case PR_T_STRIPE:
|
|
raid->flags |= AR_F_RAID_0;
|
|
raid->interleave = 1 << info.raid.raid0_shift;
|
|
break;
|
|
|
|
case PR_T_MIRROR:
|
|
raid->flags |= AR_F_RAID_1;
|
|
break;
|
|
|
|
case PR_T_SPAN:
|
|
raid->flags |= AR_F_SPAN;
|
|
break;
|
|
|
|
default:
|
|
printf("Promise unknown RAID type 0x%02x\n", info.raid.type);
|
|
return 1;
|
|
}
|
|
|
|
/* find out where this disk is in the defined array */
|
|
disk_number = info.raid.disk_number;
|
|
if (disk_number < info.raid.raid0_disks) {
|
|
raid->subdisk[disk_number] = adp;
|
|
raid->num_subdisks++;
|
|
if (raid->num_subdisks > 1 && !(raid->flags & AR_F_SPAN)) {
|
|
raid->flags |= AR_F_RAID_0;
|
|
raid->interleave = 1 << info.raid.raid0_shift;
|
|
}
|
|
}
|
|
else {
|
|
raid->mirrordisk[disk_number - info.raid.raid0_disks] = adp;
|
|
raid->num_mirrordisks++;
|
|
}
|
|
|
|
/* do we have a complete array to attach to ? */
|
|
if (raid->num_subdisks + raid->num_mirrordisks == info.raid.total_disks) {
|
|
raid->flags |= AR_F_CONF_DONE;
|
|
raid->lun = array;
|
|
raid->heads = info.raid.heads + 1;
|
|
raid->sectors = info.raid.sectors;
|
|
raid->cylinders = info.raid.cylinders + 1;
|
|
raid->total_secs = info.raid.total_secs;
|
|
raid->offset = 0;
|
|
raid->reserved = 63;
|
|
ar_attach(raid);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ar_read(struct ad_softc *adp, u_int32_t lba, int count, char *data)
|
|
{
|
|
if (ata_command(adp->controller, adp->unit | ATA_D_LBA,
|
|
(count > DEV_BSIZE) ? ATA_C_READ_MUL : ATA_C_READ,
|
|
(lba >> 8) & 0xffff, (lba >> 24) & 0xff, lba & 0xff,
|
|
count / DEV_BSIZE, 0, ATA_WAIT_INTR)) {
|
|
ata_printf(adp->controller, adp->unit, "RAID read config failed\n");
|
|
return 1;
|
|
}
|
|
if (ata_wait(adp->controller, adp->unit, ATA_S_READY|ATA_S_DSC|ATA_S_DRQ)) {
|
|
ata_printf(adp->controller, adp->unit, "RAID read config timeout\n");
|
|
return 1;
|
|
}
|
|
insw(adp->controller->ioaddr + ATA_DATA, data, count/sizeof(int16_t));
|
|
inb(adp->controller->ioaddr + ATA_STATUS);
|
|
return 0;
|
|
}
|