freebsd-dev/sys/dev/ata/ata-raid.c
Søren Schmidt 550abec2a0 Minor changes to the ATA RAID support code, remove some verbosity
and put some under bootverbose..
2000-10-18 18:49:42 +00:00

553 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_config *);
static int32_t ar_promise_magic(struct promise_raid_conf *);
static int ar_promise_conf(struct ad_softc *, struct ar_config *);
static int ar_read(struct ad_softc *, u_int32_t, int, char *);
/* internal vars */
static int ar_init = 0;
static struct ar_config ar_table;
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:
if (ar_promise_conf(adp, &ar_table))
break;
return 0;
case 0x00041103:
if (ar_highpoint_conf(adp, &ar_table))
break;
return 0;
}
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;
}
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_config *raidp)
{
struct highpoint_raid_conf info;
int array_done = 0, r;
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 (r = 0; r < 8; r++) {
if (!raidp->raid[r]) {
raidp->raid[r] =
(struct ar_softc*)malloc(sizeof(struct ar_softc),M_AR,M_NOWAIT);
if (!raidp->raid[r]) {
printf("ar: failed to allocate raid config storage\n");
return 1;
}
bzero(raidp->raid[r], sizeof(struct ar_softc));
}
switch (info.type) {
case HPT_T_RAID_0:
if (raidp->raid[r]->magic && raidp->raid[r]->magic != info.magic_0)
continue;
raidp->raid[r]->magic = info.magic_0;
/* check for the MIRROR bit in "order" smells like bug to me :) */
if (info.order & HPT_O_MIRROR)
goto hpt_mirror;
raidp->raid[r]->flags |= AR_F_RAID_0;
raidp->raid[r]->interleave = 1 << info.raid0_shift;
raidp->raid[r]->subdisk[raidp->raid[r]->num_subdisks] = adp;
raidp->raid[r]->num_subdisks++;
if (raidp->raid[r]->num_subdisks == info.raid_disks)
array_done = 1;
break;
case HPT_T_RAID_1:
if (raidp->raid[r]->magic && raidp->raid[r]->magic != info.magic_1)
continue;
raidp->raid[r]->magic = info.magic_1;
hpt_mirror:
raidp->raid[r]->flags |= AR_F_RAID_1;
if (raidp->raid[r]->num_subdisks == 0) {
raidp->raid[r]->subdisk[raidp->raid[r]->num_subdisks] = adp;
raidp->raid[r]->num_subdisks = 1;
}
else if (raidp->raid[r]->num_subdisks == 1 &&
raidp->raid[r]->num_mirrordisks == 0) {
raidp->raid[r]->mirrordisk[raidp->raid[r]->num_mirrordisks]=adp;
raidp->raid[r]->num_mirrordisks = 1;
}
if ((raidp->raid[r]->num_subdisks +
raidp->raid[r]->num_mirrordisks) == (info.raid_disks * 2))
array_done = 1;
break;
case HPT_T_SPAN:
if (raidp->raid[r]->magic && raidp->raid[r]->magic != info.magic_0)
continue;
raidp->raid[r]->magic = info.magic_0;
raidp->raid[r]->flags |= AR_F_SPAN;
raidp->raid[r]->subdisk[raidp->raid[r]->num_subdisks] = adp;
raidp->raid[r]->num_subdisks++;
if (raidp->raid[r]->num_subdisks == info.raid_disks)
array_done = 1;
break;
default:
printf("HighPoint unknown RAID type 0x%02x\n", info.type);
}
if (array_done) {
raidp->raid[r]->lun = r;
raidp->raid[r]->heads = 255;
raidp->raid[r]->sectors = 63;
raidp->raid[r]->cylinders = (info.total_secs - 9) / (63 * 255);
raidp->raid[r]->total_secs = info.total_secs -
(9 * raidp->raid[r]->num_subdisks);
raidp->raid[r]->offset = 10;
raidp->raid[r]->reserved = 10;
ar_attach(raidp->raid[r]);
return 0;
}
break;
}
return 0;
}
static int32_t
ar_promise_magic(struct promise_raid_conf *info)
{
int i, j;
int32_t magic = 0;
for (i = 0; i < 4; i++) {
if ((info->raid[i].flags != PR_F_CONFED) ||
(((info->raid[i].status & (PR_S_DEFINED|PR_S_ONLINE)) !=
(PR_S_DEFINED|PR_S_ONLINE))))
continue;
for (j = 0; j < info->raid[i].total_disks; j++) {
magic <<= 8;
magic |= ((info->raid[i].disk[j].magic_0 & 0x00ff0000)>>16);
}
}
return magic;
}
/* read the RAID info from a disk on a Promise Fasttrak controller */
static int
ar_promise_conf(struct ad_softc *adp, struct ar_config *raidp)
{
struct promise_raid_conf info;
u_int32_t lba;
u_int32_t cksum, *ckptr;
int count, i, j, r;
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 */
for (i = 0, r = 0; i < 4; i++) {
if ((info.raid[i].flags != PR_F_CONFED) ||
(((info.raid[i].status & (PR_S_DEFINED|PR_S_ONLINE)) !=
(PR_S_DEFINED|PR_S_ONLINE)))) {
continue;
}
if (raidp->raid[r]) {
if (ar_promise_magic(&info) != raidp->raid[r]->magic) {
r++;
i--;
continue;
}
}
else {
if (!(raidp->raid[r] = (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->raid[r], sizeof(struct ar_softc));
}
raidp->raid[r]->magic = ar_promise_magic(&info);
switch (info.raid[i].type) {
case PR_T_STRIPE:
raidp->raid[r]->flags |= AR_F_RAID_0;
raidp->raid[r]->interleave = 1 << info.raid[i].raid0_shift;
break;
case PR_T_MIRROR:
raidp->raid[r]->flags |= AR_F_RAID_1;
break;
case PR_T_SPAN:
raidp->raid[r]->flags |= AR_F_SPAN;
break;
default:
printf("Promise unknown RAID type 0x%02x\n", info.raid[i].type);
}
/* find out where this disk is in the defined array */
/* first RAID0 / SPAN disks */
for (j = 0; j < info.raid[i].raid0_disks; j++) {
if (info.channel == info.raid[i].disk[j].channel &&
info.device == info.raid[i].disk[j].device) {
raidp->raid[r]->subdisk[raidp->raid[r]->num_subdisks] = adp;
raidp->raid[r]->num_subdisks++;
if (raidp->raid[r]->num_subdisks > 1 &&
!(raidp->raid[r]->flags & AR_F_SPAN)) {
raidp->raid[r]->flags |= AR_F_RAID_0;
raidp->raid[r]->interleave = 1 << info.raid[i].raid0_shift;
}
}
}
/* if any left they are RAID1 disks eventually in a RADI0+1 config */
for (; j < info.raid[i].total_disks; j++) {
if (info.channel == info.raid[i].disk[j].channel &&
info.device == info.raid[i].disk[j].device) {
raidp->raid[r]->
mirrordisk[raidp->raid[r]->num_mirrordisks] = adp;
raidp->raid[r]->num_mirrordisks++;
}
}
/* do we have a complete array to attach to ? */
if (raidp->raid[r]->num_subdisks + raidp->raid[r]->num_mirrordisks ==
info.raid[i].total_disks) {
raidp->raid[r]->lun = r;
raidp->raid[r]->heads = info.raid[i].heads + 1;
raidp->raid[r]->sectors = info.raid[i].sectors;
raidp->raid[r]->cylinders = info.raid[i].cylinders + 1;
raidp->raid[r]->total_secs = info.raid[i].total_secs;
raidp->raid[r]->offset = 0;
raidp->raid[r]->reserved = 63;
ar_attach(raidp->raid[r]);
}
r++;
}
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;
}