/* $OpenBSD: softraid.c,v 1.10 2007/03/30 20:12:28 marco Exp $ */ /* * Copyright (c) 2007 Marco Peereboom * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "bio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SR_DEBUG uint32_t sr_debug = 0 /* | SR_D_CMD */ /* | SR_D_MISC */ /* | SR_D_INTR */ /* | SR_D_IOCTL */ /* | SR_D_CCB */ /* | SR_D_WU */ /* | SR_D_META */ /* | SR_D_DIS */ | SR_D_STATE ; #endif int sr_match(struct device *, void *, void *); void sr_attach(struct device *, struct device *, void *); int sr_detach(struct device *, int); int sr_activate(struct device *, enum devact); struct cfattach softraid_ca = { sizeof(struct sr_softc), sr_match, sr_attach, sr_detach, sr_activate }; struct cfdriver softraid_cd = { NULL, "softraid", DV_DULL }; int sr_scsi_cmd(struct scsi_xfer *); void sr_minphys(struct buf *bp); void sr_copy_internal_data(struct scsi_xfer *, void *, size_t); int sr_scsi_ioctl(struct scsi_link *, u_long, caddr_t, int, struct proc *); int sr_ioctl(struct device *, u_long, caddr_t); int sr_ioctl_inq(struct sr_softc *, struct bioc_inq *); int sr_ioctl_vol(struct sr_softc *, struct bioc_vol *); int sr_ioctl_disk(struct sr_softc *, struct bioc_disk *); int sr_ioctl_setstate(struct sr_softc *, struct bioc_setstate *); int sr_ioctl_createraid(struct sr_softc *, struct bioc_createraid *); int sr_parse_chunks(struct sr_softc *, char *, struct sr_chunk_head *); void sr_unwind_chunks(struct sr_softc *, struct sr_chunk_head *); /* work units & ccbs */ int sr_alloc_ccb(struct sr_discipline *); void sr_free_ccb(struct sr_discipline *); struct sr_ccb *sr_get_ccb(struct sr_discipline *); void sr_put_ccb(struct sr_ccb *); int sr_alloc_wu(struct sr_discipline *); void sr_free_wu(struct sr_discipline *); struct sr_workunit *sr_get_wu(struct sr_discipline *); void sr_put_wu(struct sr_workunit *); /* discipline functions */ int sr_raid1_alloc_resources(struct sr_discipline *); int sr_raid1_free_resources(struct sr_discipline *); int sr_raid1_rw(struct sr_workunit *); int sr_raid1_inquiry(struct sr_workunit *); int sr_raid1_read_cap(struct sr_workunit *); int sr_raid1_tur(struct sr_workunit *); int sr_raid1_request_sense( struct sr_workunit *); int sr_raid1_start_stop(struct sr_workunit *); int sr_raid1_sync(struct sr_workunit *); void sr_raid1_intr(struct buf *); void sr_raid1_set_chunk_state(struct sr_discipline *, int, int); void sr_raid1_set_vol_state(struct sr_discipline *); struct scsi_adapter sr_switch = { sr_scsi_cmd, sr_minphys, NULL, NULL, sr_scsi_ioctl }; struct scsi_device sr_dev = { NULL, NULL, NULL, NULL }; int sr_match(struct device *parent, void *match, void *aux) { return (1); } void sr_attach(struct device *parent, struct device *self, void *aux) { struct sr_softc *sc = (void *)self; DNPRINTF(SR_D_MISC, "\n%s: sr_attach", DEVNAME(sc)); rw_init(&sc->sc_lock, "sr_lock"); if (bio_register(&sc->sc_dev, sr_ioctl) != 0) printf("%s: controller registration failed", DEVNAME(sc)); else sc->sc_ioctl = sr_ioctl; printf("\n"); } int sr_detach(struct device *self, int flags) { return (0); } int sr_activate(struct device *self, enum devact act) { return (1); } void sr_minphys(struct buf *bp) { DNPRINTF(SR_D_MISC, "sr_minphys: %d\n", bp->b_bcount); /* XXX currently using SR_MAXFER = MAXPHYS */ if (bp->b_bcount > SR_MAXFER) bp->b_bcount = SR_MAXFER; minphys(bp); } void sr_copy_internal_data(struct scsi_xfer *xs, void *v, size_t size) { size_t copy_cnt; DNPRINTF(SR_D_MISC, "sr_copy_internal_data xs: %p size: %d\n", xs, size); if (xs->datalen) { copy_cnt = MIN(size, xs->datalen); bcopy(v, xs->data, copy_cnt); } } int sr_alloc_ccb(struct sr_discipline *sd) { struct sr_ccb *ccb; int i; if (!sd) return (1); DNPRINTF(SR_D_CCB, "%s: sr_alloc_ccb\n", DEVNAME(sd->sd_sc)); if (sd->sd_ccb) return (1); sd->sd_ccb = malloc(sizeof(struct sr_ccb) * sd->sd_max_wu * sd->sd_max_ccb_per_wu, M_DEVBUF, M_WAITOK); memset(sd->sd_ccb, 0, sizeof(struct sr_ccb) * sd->sd_max_wu * sd->sd_max_ccb_per_wu); TAILQ_INIT(&sd->sd_ccb_freeq); for (i = 0; i < sd->sd_max_wu * sd->sd_max_ccb_per_wu; i++) { ccb = &sd->sd_ccb[i]; ccb->ccb_dis = sd; sr_put_ccb(ccb); } DNPRINTF(SR_D_CCB, "%s: sr_alloc_ccb ccb: %d\n", DEVNAME(sd->sd_sc), sd->sd_max_wu * sd->sd_max_ccb_per_wu); return (0); } void sr_free_ccb(struct sr_discipline *sd) { struct sr_ccb *ccb; if (!sd) return; DNPRINTF(SR_D_CCB, "%s: sr_free_ccb %p\n", DEVNAME(sd->sd_sc), sd); while ((ccb = TAILQ_FIRST(&sd->sd_ccb_freeq)) != NULL) TAILQ_REMOVE(&sd->sd_ccb_freeq, ccb, ccb_link); if (sd->sd_ccb) free(sd->sd_ccb, M_DEVBUF); } struct sr_ccb * sr_get_ccb(struct sr_discipline *sd) { struct sr_ccb *ccb; int s; s = splbio(); ccb = TAILQ_FIRST(&sd->sd_ccb_freeq); if (ccb) { TAILQ_REMOVE(&sd->sd_ccb_freeq, ccb, ccb_link); ccb->ccb_state = SR_CCB_INPROGRESS; } splx(s); DNPRINTF(SR_D_CCB, "%s: sr_get_ccb: %p\n", DEVNAME(sd->sd_sc), ccb); return (ccb); } void sr_put_ccb(struct sr_ccb *ccb) { struct sr_discipline *sd = ccb->ccb_dis; int s; DNPRINTF(SR_D_CCB, "%s: sr_put_ccb: %p\n", DEVNAME(sd->sd_sc), ccb); s = splbio(); ccb->ccb_wu = NULL; ccb->ccb_state = SR_CCB_FREE; TAILQ_INSERT_TAIL(&sd->sd_ccb_freeq, ccb, ccb_link); splx(s); } int sr_alloc_wu(struct sr_discipline *sd) { struct sr_workunit *wu; int i, no_wu; if (!sd) return (1); DNPRINTF(SR_D_WU, "%s: sr_alloc_wu %p %d\n", DEVNAME(sd->sd_sc), sd, sd->sd_max_wu); if (sd->sd_wu) return (1); no_wu = sd->sd_max_wu; sd->sd_wu = malloc(sizeof(struct sr_workunit) * no_wu, M_DEVBUF, M_WAITOK); memset(sd->sd_wu, 0, sizeof(struct sr_workunit) * no_wu); TAILQ_INIT(&sd->sd_wu_freeq); for (i = 0; i < no_wu; i++) { wu = &sd->sd_wu[i]; wu->swu_dis = sd; sr_put_wu(wu); } return (0); } void sr_free_wu(struct sr_discipline *sd) { struct sr_workunit *wu; if (!sd) return; DNPRINTF(SR_D_WU, "%s: sr_free_wu %p\n", DEVNAME(sd->sd_sc), sd); while ((wu = TAILQ_FIRST(&sd->sd_wu_freeq)) != NULL) TAILQ_REMOVE(&sd->sd_wu_freeq, wu, swu_link); if (sd->sd_wu) free(sd->sd_wu, M_DEVBUF); } void sr_put_wu(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; int s; DNPRINTF(SR_D_WU, "%s: sr_put_wu: %p\n", DEVNAME(sd->sd_sc), wu); s = splbio(); wu->swu_xs = NULL; wu->swu_state = SR_WU_FREE; wu->swu_ios_complete = 0; wu->swu_io_count = 0; wu->swu_ios = NULL; TAILQ_INSERT_TAIL(&sd->sd_wu_freeq, wu, swu_link); splx(s); } struct sr_workunit * sr_get_wu(struct sr_discipline *sd) { struct sr_workunit *wu; int s; s = splbio(); wu = TAILQ_FIRST(&sd->sd_wu_freeq); if (wu) { TAILQ_REMOVE(&sd->sd_wu_freeq, wu, swu_link); wu->swu_state = SR_WU_INPROGRESS; } splx(s); DNPRINTF(SR_D_WU, "%s: sr_get_wu: %p\n", DEVNAME(sd->sd_sc), wu); return (wu); } int sr_scsi_cmd(struct scsi_xfer *xs) { int s; struct scsi_link *link = xs->sc_link; struct sr_softc *sc = link->adapter_softc; struct sr_workunit *wu; struct sr_discipline *sd; DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd: scsibus: %d xs: %p " "flags: %#x\n", DEVNAME(sc), link->scsibus, xs, xs->flags); sd = sc->sc_dis[link->scsibus]; if (sd == NULL) { s = splhigh(); sd = sc->sc_attach_dis; splx(s); DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd: attaching %p\n", DEVNAME(sc), sd); if (sd == NULL) { wu = NULL; printf("%s: sr_scsi_cmd NULL discipline\n", DEVNAME(sc)); goto stuffup; } } if ((wu = sr_get_wu(sd)) == NULL) { DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd no wu\n", DEVNAME(sc)); return (TRY_AGAIN_LATER); } xs->error = XS_NOERROR; wu->swu_xs = xs; /* if we have sense data let the midlayer know */ if (sd->sd_scsi_sense.flags != SKEY_NO_SENSE && (!(xs->cmd->opcode == REQUEST_SENSE || xs->cmd->opcode == INQUIRY))) { DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd opcode 0x%02x sense " "0x%02x 0x%02x\n", DEVNAME(sc), xs->cmd->opcode, sd->sd_scsi_sense.add_sense_code, sd->sd_scsi_sense.add_sense_code_qual); xs->error = XS_SENSE; xs->flags |= ITSDONE; goto complete; } switch (xs->cmd->opcode) { case READ_COMMAND: case READ_BIG: case WRITE_COMMAND: case WRITE_BIG: DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd: READ/WRITE %02x\n", DEVNAME(sc), xs->cmd->opcode); if (sd->sd_scsi_rw(wu)) goto stuffup; break; case SYNCHRONIZE_CACHE: DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd: SYNCHRONIZE_CACHE\n", DEVNAME(sc)); if (sd->sd_scsi_sync(wu)) goto stuffup; goto complete; case TEST_UNIT_READY: DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd: TEST_UNIT_READY\n", DEVNAME(sc)); if (sd->sd_scsi_tur(wu)) goto stuffup; goto complete; case START_STOP: DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd: START_STOP\n", DEVNAME(sc)); if (sd->sd_scsi_start_stop(wu)) goto stuffup; goto complete; case INQUIRY: DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd: INQUIRY\n", DEVNAME(sc)); if (sd->sd_scsi_inquiry(wu)) goto stuffup; goto complete; case READ_CAPACITY: DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd READ CAPACITY\n", DEVNAME(sc)); if (sd->sd_scsi_read_cap(wu)) goto stuffup; goto complete; case REQUEST_SENSE: DNPRINTF(SR_D_CMD, "%s: sr_scsi_cmd REQUEST SENSE\n", DEVNAME(sc)); if (sd->sd_scsi_req_sense(wu)) goto stuffup; goto complete; default: DNPRINTF(SR_D_CMD, "%s: unsupported scsi command %x\n", DEVNAME(sc), xs->cmd->opcode); /* XXX might need to add generic function to handle others */ goto stuffup; } return (SUCCESSFULLY_QUEUED); stuffup: xs->error = XS_DRIVER_STUFFUP; xs->flags |= ITSDONE; complete: s = splbio(); scsi_done(xs); splx(s); if (wu) sr_put_wu(wu); return (COMPLETE); } int sr_scsi_ioctl(struct scsi_link *link, u_long cmd, caddr_t addr, int flag, struct proc *p) { DNPRINTF(SR_D_IOCTL, "%s: sr_scsi_ioctl cmd: %#x\n", DEVNAME((struct sr_softc *)link->adapter_softc), cmd); return (sr_ioctl(link->adapter_softc, cmd, addr)); } int sr_ioctl(struct device *dev, u_long cmd, caddr_t addr) { struct sr_softc *sc = (struct sr_softc *)dev; int rv = 0; DNPRINTF(SR_D_IOCTL, "%s: sr_ioctl ", DEVNAME(sc)); rw_enter_write(&sc->sc_lock); switch (cmd) { case BIOCINQ: DNPRINTF(SR_D_IOCTL, "inq\n"); rv = sr_ioctl_inq(sc, (struct bioc_inq *)addr); break; case BIOCVOL: DNPRINTF(SR_D_IOCTL, "vol\n"); rv = sr_ioctl_vol(sc, (struct bioc_vol *)addr); break; case BIOCDISK: DNPRINTF(SR_D_IOCTL, "disk\n"); rv = sr_ioctl_disk(sc, (struct bioc_disk *)addr); break; case BIOCALARM: DNPRINTF(SR_D_IOCTL, "alarm\n"); /*rv = sr_ioctl_alarm(sc, (struct bioc_alarm *)addr); */ break; case BIOCBLINK: DNPRINTF(SR_D_IOCTL, "blink\n"); /*rv = sr_ioctl_blink(sc, (struct bioc_blink *)addr); */ break; case BIOCSETSTATE: DNPRINTF(SR_D_IOCTL, "setstate\n"); rv = sr_ioctl_setstate(sc, (struct bioc_setstate *)addr); break; case BIOCCREATERAID: DNPRINTF(SR_D_IOCTL, "createraid\n"); rv = sr_ioctl_createraid(sc, (struct bioc_createraid *)addr); break; default: DNPRINTF(SR_D_IOCTL, "invalid ioctl\n"); rv = EINVAL; } rw_exit_write(&sc->sc_lock); return (rv); } int sr_ioctl_inq(struct sr_softc *sc, struct bioc_inq *bi) { int i, vol, disk; for (i = 0, vol = 0, disk = 0; i < SR_MAXSCSIBUS; i++) /* XXX this will not work when we stagger disciplines */ if (sc->sc_dis[i]) { vol++; disk += sc->sc_dis[i]->sd_vol.sv_meta.svm_no_chunk; } strlcpy(bi->bi_dev, sc->sc_dev.dv_xname, sizeof(bi->bi_dev)); bi->bi_novol = vol; bi->bi_nodisk = disk; return (0); } int sr_ioctl_vol(struct sr_softc *sc, struct bioc_vol *bv) { int i, vol, rv = EINVAL; struct sr_volume *sv; for (i = 0, vol = -1; i < SR_MAXSCSIBUS; i++) { /* XXX this will not work when we stagger disciplines */ if (sc->sc_dis[i]) vol++; if (vol != bv->bv_volid) continue; sv = &sc->sc_dis[i]->sd_vol; bv->bv_status = sv->sv_meta.svm_status; bv->bv_size = sv->sv_meta.svm_size; bv->bv_level = sv->sv_meta.svm_level; bv->bv_nodisk = sv->sv_meta.svm_no_chunk; strlcpy(bv->bv_dev, sv->sv_meta.svm_devname, sizeof(bv->bv_dev)); strlcpy(bv->bv_vendor, sv->sv_meta.svm_vendor, sizeof(bv->bv_vendor)); rv = 0; break; } return (rv); } int sr_ioctl_disk(struct sr_softc *sc, struct bioc_disk *bd) { int i, vol, rv = EINVAL; struct sr_chunk *src; for (i = 0, vol = -1; i < SR_MAXSCSIBUS; i++) { /* XXX this will not work when we stagger disciplines */ if (sc->sc_dis[i]) vol++; if (vol != bd->bd_volid) continue; src = sc->sc_dis[i]->sd_vol.sv_chunks[bd->bd_diskid]; bd->bd_status = src->src_meta.scm_status; bd->bd_size = src->src_meta.scm_size; bd->bd_channel = i; bd->bd_target = bd->bd_diskid; strlcpy(bd->bd_vendor, src->src_meta.scm_devname, sizeof(bd->bd_vendor)); rv = 0; break; } return (rv); } int sr_ioctl_setstate(struct sr_softc *sc, struct bioc_setstate *bs) { int rv = EINVAL; #ifdef SR_UNIT_TEST #endif return (rv); } int sr_ioctl_createraid(struct sr_softc *sc, struct bioc_createraid *bc) { char *devl; int i, s, no_chunk, rv = EINVAL, sb = -1; size_t bytes = 0; u_quad_t vol_size, max_chunk_sz = 0, min_chunk_sz; struct sr_chunk_head *cl; struct sr_discipline *sd = NULL; struct sr_chunk *ch_entry; struct device *dev, *dev2; struct scsibus_attach_args saa; DNPRINTF(SR_D_IOCTL, "%s: sr_ioctl_createraid\n", DEVNAME(sc)); /* user input */ devl = malloc(bc->bc_dev_list_len + 1, M_DEVBUF, M_WAITOK); memset(devl, 0, bc->bc_dev_list_len + 1); copyinstr(bc->bc_dev_list, devl, bc->bc_dev_list_len, &bytes); DNPRINTF(SR_D_IOCTL, "%s\n", devl); sd = malloc(sizeof(struct sr_discipline), M_DEVBUF, M_WAITOK); memset(sd, 0, sizeof(struct sr_discipline)); sd->sd_sc = sc; cl = &sd->sd_vol.sv_chunk_list; /* check if we have valid user input */ SLIST_INIT(cl); if ((no_chunk = sr_parse_chunks(sc, devl, cl)) == -1) goto unwind; /* we have a valid list now create an array index into it */ sd->sd_vol.sv_chunks = malloc(sizeof(struct sr_chunk *) * no_chunk, M_DEVBUF, M_WAITOK); memset(sd->sd_vol.sv_chunks, 0, sizeof(struct sr_chunk *) * no_chunk); i = 0; SLIST_FOREACH(ch_entry, cl, src_link) { sd->sd_vol.sv_chunks[i++] = ch_entry; /* while looping get the largest chunk size */ if (ch_entry->src_meta.scm_size > max_chunk_sz) max_chunk_sz = ch_entry->src_meta.scm_size; } min_chunk_sz = max_chunk_sz; SLIST_FOREACH(ch_entry, cl, src_link) { if (ch_entry->src_meta.scm_size < min_chunk_sz) min_chunk_sz = ch_entry->src_meta.scm_size; } /* whine if chunks are not the same size */ if (min_chunk_sz != max_chunk_sz) printf("%s: chunk sizes are not equal. Wasted blocks per " "chunk: %llu\n", DEVNAME(sc), max_chunk_sz - min_chunk_sz); switch (bc->bc_level) { case 1: if (no_chunk < 2) goto unwind; /* fill out discipline members */ sd->sd_type = SR_MD_RAID1; sd->sd_max_ccb_per_wu = no_chunk; sd->sd_max_wu = SR_RAID1_NOWU; strlcpy(sd->sd_name, "RAID 1", sizeof(sd->sd_name)); vol_size = min_chunk_sz - (1 + 1 + no_chunk + SR_META_FUDGE); /* XXX coerce all chunks here */ /* setup pointers */ sd->sd_alloc_resources = sr_raid1_alloc_resources; sd->sd_free_resources = sr_raid1_free_resources; sd->sd_scsi_inquiry = sr_raid1_inquiry; sd->sd_scsi_read_cap = sr_raid1_read_cap; sd->sd_scsi_tur = sr_raid1_tur; sd->sd_scsi_req_sense = sr_raid1_request_sense; sd->sd_scsi_start_stop = sr_raid1_start_stop; sd->sd_scsi_sync = sr_raid1_sync; sd->sd_scsi_rw = sr_raid1_rw; sd->sd_set_chunk_state = sr_raid1_set_chunk_state; sd->sd_set_vol_state = sr_raid1_set_vol_state; break; default: goto unwind; } /* fill out all volume meta data */ DNPRINTF(SR_D_IOCTL, "%s: sr_ioctl_createraid: max_chunk_sz: " "%llu min_chunk_sz: %llu vol_size: %llu\n", DEVNAME(sc), max_chunk_sz, min_chunk_sz, vol_size); sd->sd_vol.sv_meta.svm_no_chunk = no_chunk; sd->sd_vol.sv_meta.svm_size = vol_size; sd->sd_vol.sv_meta.svm_status = BIOC_SVONLINE; sd->sd_vol.sv_meta.svm_level = bc->bc_level; strlcpy(sd->sd_vol.sv_meta.svm_vendor, "OPENBSD", sizeof(sd->sd_vol.sv_meta.svm_vendor)); snprintf(sd->sd_vol.sv_meta.svm_product, sizeof(sd->sd_vol.sv_meta.svm_product), "SR %s", sd->sd_name); snprintf(sd->sd_vol.sv_meta.svm_revision, sizeof(sd->sd_vol.sv_meta.svm_revision), "%03d", SR_META_VERSION); /* allocate all resources */ sd->sd_alloc_resources(sd); /* metadata SHALL be fully filled in at this point */ sd->sd_link.openings = 1; /* sc->sc_max_cmds; */ sd->sd_link.device = &sr_dev; sd->sd_link.device_softc = sc; sd->sd_link.adapter_softc = sc; sd->sd_link.adapter = &sr_switch; sd->sd_link.adapter_target = SR_MAX_LD; sd->sd_link.adapter_buswidth = 1; bzero(&saa, sizeof(saa)); saa.saa_sc_link = &sd->sd_link; /* we passed all checks return ENXIO if volume can't be created */ rv = ENXIO; /* clear sense data */ bzero(&sd->sd_scsi_sense, sizeof(sd->sd_scsi_sense)); sd->sd_scsi_sense.error_code = SSD_ERRCODE_CURRENT; sd->sd_scsi_sense.segment = 0; sd->sd_scsi_sense.flags = SKEY_NO_SENSE; *(u_int32_t*)sd->sd_scsi_sense.info = htole32(0); sd->sd_scsi_sense.extra_len = 0; /* use temporary discipline pointer */ s = splhigh(); sc->sc_attach_dis = sd; splx(s); dev2 = config_found(&sc->sc_dev, &saa, scsiprint); s = splhigh(); sc->sc_attach_dis = NULL; splx(s); TAILQ_FOREACH(dev, &alldevs, dv_list) if (dev->dv_parent == dev2) break; if (dev == NULL) goto unwind; strlcpy(sd->sd_vol.sv_meta.svm_devname, dev->dv_xname, sizeof(sd->sd_vol.sv_meta.svm_devname)); DNPRINTF(SR_D_IOCTL, "%s: sr device added: %s on scsibus: %d\n", DEVNAME(sc), dev->dv_xname, sd->sd_link.scsibus); sc->sc_dis[sd->sd_link.scsibus] = sd; sb = sd->sd_link.scsibus; rv = 0; return (rv); unwind: if (sb != -1) sc->sc_dis[sb] = NULL; /* XXX free scsibus */ if (sd) { if (sd->sd_free_resources) sd->sd_free_resources(sd); if (sd->sd_vol.sv_chunks) free(sd->sd_vol.sv_chunks, M_DEVBUF); free(sd, M_DEVBUF); sr_unwind_chunks(sc, cl); } return (rv); } int sr_parse_chunks(struct sr_softc *sc, char *lst, struct sr_chunk_head *cl) { struct sr_chunk *ch_entry, *ch_next, *ch_prev; struct nameidata nd; struct disklabel label; struct vattr va; int error; char ss, *name; char *s, *e; u_int32_t sz = 0; int i; char dummy = 0; DNPRINTF(SR_D_IOCTL, "%s: sr_parse_chunks\n", DEVNAME(sc)); if (!lst) { lst = &dummy; goto bad; } s = e = lst; ch_prev = NULL; /* make sure we have a valid device list like /dev/sdNa,/dev/sdNNa */ while (*e != '\0') { if (*e == ',') s = e + 1; else if (*(e + 1) == '\0' || *(e + 1) == ',') { sz = e - s + 1; /* got one */ ch_entry = malloc(sizeof(struct sr_chunk), M_DEVBUF, M_WAITOK); memset(ch_entry, 0, sizeof(struct sr_chunk)); if (sz + 1 > sizeof(ch_entry->src_meta.scm_devname)) goto unwind; strlcpy(ch_entry->src_meta.scm_devname, s, sz + 1); /* keep disks in user supplied order */ if (ch_prev) SLIST_INSERT_AFTER(ch_prev, ch_entry, src_link); else SLIST_INSERT_HEAD(cl, ch_entry, src_link); ch_prev = ch_entry; } e++; } /* check for dups */ SLIST_FOREACH(ch_entry, cl, src_link) { SLIST_FOREACH(ch_next, cl, src_link) { if (ch_next == ch_entry) continue; if (!strcmp(ch_next->src_meta.scm_devname, ch_entry->src_meta.scm_devname)) goto unwind; } } /* fill out chunk list */ i = 0; SLIST_FOREACH(ch_entry, cl, src_link) { /* printf("name: %s\n", ch_entry->src_meta.scm_devname); */ name = ch_entry->src_meta.scm_devname; ch_entry->src_dev_vn = NULL; NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, name, curproc); /* open disk */ error = vn_open(&nd, FREAD | FWRITE, 0); if (error) { printf("%s: could not open %s error %d\n", DEVNAME(sc), name, error); goto unwind; /* open failed */ } ch_entry->src_dev_vn = nd.ni_vp; /* partition already in use? */ if (nd.ni_vp->v_usecount > 1) { printf("%s: %s in use\n", DEVNAME(sc), name); goto unlock; } /* get attributes */ error = VOP_GETATTR(nd.ni_vp, &va, curproc->p_ucred, curproc); if (error) { printf("%s: %s can't retrieve attributes\n", DEVNAME(sc), name); goto unlock; } /* is partition VBLK? */ if (va.va_type != VBLK) { printf("%s: %s not of VBLK type\n", DEVNAME(sc), name); goto unlock; } /* make sure we are not a raw partition */ if (DISKPART(nd.ni_vp->v_rdev) == RAW_PART) { printf("%s: %s can not use raw partitions\n", DEVNAME(sc), name); goto unlock; } /* get disklabel */ error = VOP_IOCTL(nd.ni_vp, DIOCGDINFO, (caddr_t)&label, FREAD, curproc->p_ucred, curproc); if (error) { printf("%s: %s could not read disklabel err %d\n", DEVNAME(sc), name, error); goto unlock; /* disklabel failed */ } /* get partition size */ ss = name[strlen(name) - 1]; ch_entry->src_meta.scm_size = label.d_partitions[ss - 'a'].p_size; if (ch_entry->src_meta.scm_size == 0) { printf("%s: %s partition size = 0\n", DEVNAME(sc), name); goto unlock; } /* make sure the partition is of the right type */ if (label.d_partitions[ss - 'a'].p_fstype != FS_RAID) { printf("%s: %s partition not of type RAID (%d)\n", DEVNAME(sc), name, label.d_partitions[ss - 'a'].p_fstype); goto unlock; } /* XXX check for stale metadata */ ch_entry->src_meta.scm_chunk_id = i++; ch_entry->src_dev_mm = nd.ni_vp->v_rdev; /* major/minor */ DNPRINTF(SR_D_IOCTL, "%s: found %s size %d\n", DEVNAME(sc), name, ch_entry->src_meta.scm_size); /* mark chunk online */ ch_entry->src_meta.scm_status = BIOC_SDONLINE; /* unlock the vnode */ VOP_UNLOCK(ch_entry->src_dev_vn, 0, curproc); } return (i); unlock: VOP_UNLOCK(ch_entry->src_dev_vn, 0, curproc); unwind: sr_unwind_chunks(sc, cl); bad: printf("%s: invalid device list %s\n", DEVNAME(sc), lst); return (-1); } void sr_unwind_chunks(struct sr_softc *sc, struct sr_chunk_head *cl) { struct sr_chunk *ch_entry, *ch_next; DNPRINTF(SR_D_IOCTL, "%s: sr_unwind_chunks\n", DEVNAME(sc)); for (ch_entry = SLIST_FIRST(cl); ch_entry != SLIST_END(cl); ch_entry = ch_next) { ch_next = SLIST_NEXT(ch_entry, src_link); if (ch_entry->src_dev_vn != NULL) { vn_close(ch_entry->src_dev_vn, FREAD | FWRITE, curproc->p_ucred, curproc); } free(ch_entry, M_DEVBUF); } SLIST_INIT(cl); } /* RAID 1 functions */ int sr_raid1_alloc_resources(struct sr_discipline *sd) { int rv = EINVAL; if (!sd) return (rv); DNPRINTF(SR_D_DIS, "%s: sr_raid1_alloc_resources\n", DEVNAME(sd->sd_sc)); sr_alloc_wu(sd); sr_alloc_ccb(sd); rv = 0; return (rv); } int sr_raid1_free_resources(struct sr_discipline *sd) { int rv = EINVAL; if (!sd) return (rv); DNPRINTF(SR_D_DIS, "%s: sr_raid1_free_resources\n", DEVNAME(sd->sd_sc)); sr_free_wu(sd); sr_free_ccb(sd); rv = 0; return (rv); } int sr_raid1_inquiry(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; struct scsi_xfer *xs = wu->swu_xs; struct scsi_inquiry_data inq; DNPRINTF(SR_D_DIS, "%s: sr_raid1_inquiry\n", DEVNAME(sd->sd_sc)); bzero(&inq, sizeof(inq)); inq.device = T_DIRECT; inq.dev_qual2 = 0; inq.version = 2; inq.response_format = 2; inq.additional_length = 32; strlcpy(inq.vendor, sd->sd_vol.sv_meta.svm_vendor, sizeof(inq.vendor)); strlcpy(inq.product, sd->sd_vol.sv_meta.svm_product, sizeof(inq.product)); strlcpy(inq.revision, sd->sd_vol.sv_meta.svm_revision, sizeof(inq.revision)); sr_copy_internal_data(xs, &inq, sizeof(inq)); return (0); } int sr_raid1_read_cap(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; struct scsi_xfer *xs = wu->swu_xs; struct scsi_read_cap_data rcd; DNPRINTF(SR_D_DIS, "%s: sr_raid1_read_cap\n", DEVNAME(sd->sd_sc)); bzero(&rcd, sizeof(rcd)); _lto4b(sd->sd_vol.sv_meta.svm_size, rcd.addr); _lto4b(512, rcd.length); sr_copy_internal_data(xs, &rcd, sizeof(rcd)); return (0); } int sr_raid1_tur(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; DNPRINTF(SR_D_DIS, "%s: sr_raid1_tur\n", DEVNAME(sd->sd_sc)); if (sd->sd_vol.sv_meta.svm_status == BIOC_SVOFFLINE) { sd->sd_scsi_sense.error_code = SSD_ERRCODE_CURRENT; sd->sd_scsi_sense.segment = 0; sd->sd_scsi_sense.flags = SKEY_NOT_READY; sd->sd_scsi_sense.add_sense_code = 0x04; sd->sd_scsi_sense.add_sense_code_qual = 0x11; *(u_int32_t*)sd->sd_scsi_sense.info = htole32(0); sd->sd_scsi_sense.extra_len = 0; return (1); } else if (sd->sd_vol.sv_meta.svm_status == BIOC_SVINVALID) { sd->sd_scsi_sense.error_code = SSD_ERRCODE_CURRENT; sd->sd_scsi_sense.segment = 0; sd->sd_scsi_sense.flags = SKEY_HARDWARE_ERROR; sd->sd_scsi_sense.add_sense_code = 0x05; sd->sd_scsi_sense.add_sense_code_qual = 0x00; *(u_int32_t*)sd->sd_scsi_sense.info = htole32(0); sd->sd_scsi_sense.extra_len = 0; return (1); } return (0); } int sr_raid1_request_sense(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; struct scsi_xfer *xs = wu->swu_xs; DNPRINTF(SR_D_DIS, "%s: sr_raid1_request_sense\n", DEVNAME(sd->sd_sc)); /* use latest sense data */ sr_copy_internal_data(xs, &sd->sd_scsi_sense, sizeof(sd->sd_scsi_sense)); /* clear sense data */ bzero(&sd->sd_scsi_sense, sizeof(sd->sd_scsi_sense)); sd->sd_scsi_sense.error_code = SSD_ERRCODE_CURRENT; sd->sd_scsi_sense.segment = 0; sd->sd_scsi_sense.flags = SKEY_NO_SENSE; *(u_int32_t*)sd->sd_scsi_sense.info = htole32(0); sd->sd_scsi_sense.extra_len = 0; return (0); } int sr_raid1_start_stop(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; struct scsi_xfer *xs = wu->swu_xs; struct scsi_start_stop *ss = (struct scsi_start_stop *)xs->cmd; int rv = 1; DNPRINTF(SR_D_DIS, "%s: sr_raid1_start_stop\n", DEVNAME(sd->sd_sc)); if (!ss) return (rv); if (ss->byte2 == 0x00) { /* START */ if (sd->sd_vol.sv_meta.svm_status == BIOC_SVOFFLINE) { /* bring volume online */ /* XXX check to see if volume can be brought online */ sd->sd_vol.sv_meta.svm_status = BIOC_SVONLINE; } rv = 0; } else /* XXX is this the check? if (byte == 0x01) */ { /* STOP */ if (sd->sd_vol.sv_meta.svm_status == BIOC_SVONLINE) { /* bring volume offline */ sd->sd_vol.sv_meta.svm_status = BIOC_SVOFFLINE; } rv = 0; } return (rv); } int sr_raid1_sync(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; struct sr_chunk *ch_entry; DNPRINTF(SR_D_DIS, "%s: sr_raid1_sync\n", DEVNAME(sd->sd_sc)); /* drain all io */ SLIST_FOREACH(ch_entry, &sd->sd_vol.sv_chunk_list, src_link) { DNPRINTF(SR_D_DIS, "%s: %s: %s: sync\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, ch_entry->src_meta.scm_devname); vn_lock(ch_entry->src_dev_vn, LK_EXCLUSIVE | LK_RETRY, curproc); /* XXX we want MNT_WAIT but that hangs in fdisk */ VOP_FSYNC(ch_entry->src_dev_vn, curproc->p_ucred, 0 /* MNT_WAIT */, curproc); VOP_UNLOCK(ch_entry->src_dev_vn, 0, curproc); } return (0); } int sr_raid1_rw(struct sr_workunit *wu) { struct sr_discipline *sd = wu->swu_dis; struct scsi_xfer *xs = wu->swu_xs; struct sr_ccb *ccb; int rv = 1, ios, x, i; daddr_t blk; DNPRINTF(SR_D_DIS, "%s: sr_raid1_rw 0x%02x\n", DEVNAME(sd->sd_sc), xs->cmd->opcode); if (xs->datalen == 0) { printf("%s: %s: illegal block count\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname); goto bad; } if (xs->cmdlen == 10) blk = _4btol(((struct scsi_rw_big *)xs->cmd)->addr); else if (xs->cmdlen == 6) blk = _3btol(((struct scsi_rw *)xs->cmd)->addr); else { printf("%s: %s: illegal cmdlen\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname); goto bad; } if (xs->flags & SCSI_DATA_IN) ios = 1; else ios = sd->sd_vol.sv_meta.svm_no_chunk; for (i = 0; i < ios; i++) { ccb = sr_get_ccb(sd); if (!ccb) { /* should never happen but handle more gracefully */ printf("%s: %s: too many ccbs queued\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname); goto bad; } if (xs->flags & SCSI_POLL) { ccb->ccb_buf.b_flags = 0; ccb->ccb_buf.b_iodone = NULL; } else { ccb->ccb_buf.b_flags = B_CALL; ccb->ccb_buf.b_iodone = sr_raid1_intr; } ccb->ccb_buf.b_blkno = blk; ccb->ccb_buf.b_bcount = xs->datalen; ccb->ccb_buf.b_bufsize = xs->datalen; ccb->ccb_buf.b_resid = xs->datalen; ccb->ccb_buf.b_data = xs->data; ccb->ccb_buf.b_error = 0; ccb->ccb_buf.b_proc = curproc; wu->swu_io_count = ios; if (xs->flags & SCSI_DATA_IN) { /* interleave reads */ x = sd->mds.mdd_raid1.sr1_counter % sd->sd_vol.sv_meta.svm_no_chunk; sd->mds.mdd_raid1.sr1_counter++; ccb->ccb_buf.b_flags |= B_READ; } else { /* writes go on all disks */ x = i; ccb->ccb_buf.b_flags |= B_WRITE; } ccb->ccb_buf.b_dev = sd->sd_vol.sv_chunks[x]->src_dev_mm; ccb->ccb_buf.b_vp = sd->sd_vol.sv_chunks[x]->src_dev_vn; LIST_INIT(&ccb->ccb_buf.b_dep); ccb->ccb_wu = wu; DNPRINTF(SR_D_DIS, "%s: %s: sr_raid1: b_bcount: %d " "b_blkno: %x b_flags 0x%0x b_data %p\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, ccb->ccb_buf.b_bcount, ccb->ccb_buf.b_blkno, ccb->ccb_buf.b_flags, ccb->ccb_buf.b_data); /* vprint("despatch: ", ccb->ccb_buf.b_vp); */ ccb->ccb_buf.b_vp->v_numoutput++; VOP_STRATEGY(&ccb->ccb_buf); if (xs->flags & SCSI_POLL) { /* polling, wait for completion */ biowait(&ccb->ccb_buf); if (ccb->ccb_buf.b_flags & B_ERROR) { printf("%s: %s: %s: i/o error on block %d\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, sd->sd_vol.sv_chunks[x]->src_meta.scm_devname, ccb->ccb_buf.b_blkno); /* don't abort other ios because of error */ } sr_put_ccb(ccb); } } rv = 0; bad: return (rv); } void sr_raid1_intr(struct buf *bp) { struct sr_ccb *ccb = (struct sr_ccb *)bp; struct sr_workunit *wu = ccb->ccb_wu; struct sr_discipline *sd = wu->swu_dis; struct scsi_xfer *xs = wu->swu_xs; struct sr_softc *sc = sd->sd_sc; int s; DNPRINTF(SR_D_INTR, "%s: sr_intr bp %x xs %x\n", DEVNAME(sc), bp, xs); DNPRINTF(SR_D_INTR, "%s: sr_intr: b_bcount: %d b_resid: %d" " b_flags: 0x%0x\n", DEVNAME(sc), ccb->ccb_buf.b_bcount, ccb->ccb_buf.b_resid, ccb->ccb_buf.b_flags); if (ccb->ccb_buf.b_flags & B_ERROR) { printf("%s: i/o error on block %d\n", DEVNAME(sc), ccb->ccb_buf.b_blkno); wu->swu_state = SR_WU_FAILED; } sr_put_ccb(ccb); wu->swu_ios_complete++; DNPRINTF(SR_D_INTR, "%s: sr_intr: comp: %d count: %d\n", DEVNAME(sc), wu->swu_ios_complete, wu->swu_io_count); if (wu->swu_ios_complete == wu->swu_io_count) { /* do something smarter here instead of failing the whole WU */ if (wu->swu_state == SR_WU_FAILED) xs->error = XS_DRIVER_STUFFUP; else xs->error = XS_NOERROR; xs->resid = 0; xs->flags |= ITSDONE; s = splbio(); scsi_done(xs); splx(s); sr_put_wu(wu); } } void sr_raid1_set_chunk_state(struct sr_discipline *sd, int c, int new_state) { int old_state, s; DNPRINTF(SR_D_STATE, "%s: %s: %s: sr_raid1_set_chunk_state %d -> %d\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, sd->sd_vol.sv_chunks[c]->src_meta.scm_devname, c, new_state); /* ok to go to splbio since this only happens in error path */ s = splbio(); old_state = sd->sd_vol.sv_chunks[c]->src_meta.scm_status; /* there is a race on status here but we don't care for now */ switch (old_state) { case BIOC_SDONLINE: switch (new_state) { case BIOC_SDOFFLINE: break; case BIOC_SDFAILED: break; case BIOC_SDSCRUB: break; default: goto die; } break; case BIOC_SDOFFLINE: if (new_state == BIOC_SDREBUILD) { ; } else goto die; break; case BIOC_SDFAILED: if (new_state == BIOC_SDREBUILD) { ; } else goto die; break; case BIOC_SDSCRUB: switch (new_state) { case BIOC_SDONLINE: break; case BIOC_SDFAILED: break; default: goto die; } break; case BIOC_SDREBUILD: switch (new_state) { case BIOC_SDONLINE: break; case BIOC_SDFAILED: break; default: goto die; } break; case BIOC_SDHOTSPARE: if (new_state == BIOC_SDREBUILD) { ; } else goto die; break; default: die: splx(s); /* XXX */ panic("%s: %s: %s: invalid chunk state transition " "%d -> %d\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, sd->sd_vol.sv_chunks[c]->src_meta.scm_devname, old_state, new_state); /* NOTREACHED */ } sd->sd_vol.sv_chunks[c]->src_meta.scm_status = new_state; sd->sd_set_vol_state(sd); splx(s); } void sr_raid1_set_vol_state(struct sr_discipline *sd) { int states[SR_MAX_STATES]; int new_state, i, s, nd; int old_state = sd->sd_vol.sv_meta.svm_status; nd = sd->sd_vol.sv_meta.svm_no_chunk; for (i = 0; i < nd; i++) states[i] = 0; for (i = 0; i < nd; i++) { s = sd->sd_vol.sv_chunks[i]->src_meta.scm_status; if (s > SR_MAX_STATES) panic("%s: %s: %s: invalid chunk state", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, sd->sd_vol.sv_chunks[i]->src_meta.scm_devname); states[s]++; } if (states[BIOC_SVONLINE] == nd) new_state = BIOC_SVONLINE; else if (states[BIOC_SVSCRUB] != 0) new_state = BIOC_SVSCRUB; else if (states[BIOC_SVBUILDING] != 0) new_state = BIOC_SVBUILDING; else if (states[BIOC_SVREBUILD] != 0) new_state = BIOC_SVREBUILD; else if (states[BIOC_SVDEGRADED] != 0) new_state = BIOC_SVDEGRADED; else if (states[BIOC_SVONLINE] == 0) new_state = BIOC_SVOFFLINE; else { printf("old_state = %d, ", old_state); for (i = 0; i < SR_MAX_STATES; i++) printf("%d = %d, ", i, sd->sd_vol.sv_chunks[i]->src_meta.scm_status); panic("invalid new_state"); } DNPRINTF(SR_D_STATE, "%s: %s: sr_raid1_set_vol_state %d -> %d\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, old_state, new_state); switch (old_state) { case BIOC_SVONLINE: switch (new_state) { case BIOC_SVOFFLINE: case BIOC_SVDEGRADED: break; default: goto die; } break; case BIOC_SVOFFLINE: /* XXX this might be a little too much */ goto die; case BIOC_SVSCRUB: switch (new_state) { case BIOC_SVONLINE: case BIOC_SVOFFLINE: case BIOC_SVDEGRADED: case BIOC_SVSCRUB: /* can go to same state */ break; default: goto die; } break; case BIOC_SVBUILDING: switch (new_state) { case BIOC_SVONLINE: case BIOC_SVOFFLINE: case BIOC_SVBUILDING: /* can go to the same state */ break; default: goto die; } break; case BIOC_SVREBUILD: switch (new_state) { case BIOC_SVONLINE: case BIOC_SVOFFLINE: case BIOC_SVREBUILD: /* can go to the same state */ break; default: goto die; } break; case BIOC_SVDEGRADED: switch (new_state) { case BIOC_SVOFFLINE: case BIOC_SVREBUILD: case BIOC_SVDEGRADED: /* can go to the same state */ break; default: goto die; } break; default: die: panic("%s: %s: invalid volume state transition " "%d -> %d\n", DEVNAME(sd->sd_sc), sd->sd_vol.sv_meta.svm_devname, old_state, new_state); /* NOTREACHED */ } }