#ident "@(#)id.c 1.1 94/10/31 SMI" /* * Copyright (c) 1989 by Sun Microsystems, Inc. */ /* * IPI disk driver. * * Handles the following string controllers and disks: * Sun Panther VME-based IPI-2 String Controller * CDC MPI PA8R2A IPI-2 Disk * CDC MPI/CM-3 String Controller */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for MAXINT */ #define DEBUG #define alloc(type, count) ((type *)kmem_zalloc((count)*sizeof (type))) static struct id_ctlr **id_ctlr; /* pointer to ctlr pointer array */ static int nidc; /* maximum controller number plus 1 */ #define NIDC_EXTEND 8 /* amount to extend pointer array by */ static struct id_unit **id_unit; /* pointer to unit pointer array */ static int nid; #define NID_EXTEND 8 /* number of units to extend by */ static int id_debug = 0; /* debugging code */ static int id_print_attr; static int id_watch_enable = 0; /* enable watchdog for unit status */ #define MULT_RBUF 0 /* set when physio supports multiple raw buffers */ #ifdef MULT_MAJOR static u_char bmajor_lookup[NMAJOR]; /* index of block major device */ static u_char cmajor_lookup[NMAJOR]; /* index of character major device */ static u_char cmajor_trans[NMAJOR]; /* blk maj num corresponding to char */ #endif /* MULT_MAJOR */ #define ALIGN(x, i) (((u_long)x + (u_long)i) & ~(u_long)(i - 1)) #define NBLKS(n, un) ((u_int)(n) >> (un)->un_log_bshift) #define MAX_CMDLEN 64 #define MAX_RESPLEN 256 /* * Time limits in seconds. */ #define ID_TIMEOUT 20 /* time limit on commands (in seconds) */ #define ID_REC_TIMEOUT 16 /* time limit on recovery commands (seconds) */ #define ID_DUMP_TIMEOUT 16 /* time limit on ISP-80 dump */ #define ID_IDLE_TIME 10 /* wait time for ctlr to go idle */ #define ID_RST_TIMEOUT 45 /* wait time for controller reset */ /* * Maximum number of commands that can be outstanding to controllers which * do not sort to attempt to optimize seek distance. */ #define MAX_UNSORTED 4 /* maximum outstanding cmds if no seek alg */ /* * ID_MAX_PARMS = maximum number of attribute parameters that shall be * requested per attribute command. (The controller may have a lower * restriction.) */ #define ID_MAX_PARMS 16 #define ID_RESP_PER_CTLR 4 /* allocated responses per ctrl */ /* * Maximum byte count for an I/O operation. */ #define ID_MAXPHYS (63 * 1024) /* * Definitions for watchdog routines */ #define ID_WATCH_TIME 17 /* seconds between watchdog runs */ #define ID_MAX_IDLE (2*ID_WATCH_TIME) /* maximum idle time for online device */ static char id_watch_started; static void id_watch(); static void id_watch_intr(); static void id_missing_req(); static int id_recover(); static int id_recover_start(); static void id_recover_intr(); static void id_recover_ctlr(); #define b_cylin b_resid /* cylinder number for disksort */ /* * Autoconfiguration data. */ static int idprobe(), idslave(); static void idintr(); static void idasync(); static int idrestart(); static int idstart(); static struct mb_ctlr **idcinfo; struct mb_driver idcdriver = { idprobe, idslave, NULL, NULL, NULL, NULL, 1, "id", NULL, "idc", NULL, /* mdr_cinfo = idcinfo set by idprobe */ MDR_BIODMA }; /* * Attribute tables and functions. */ static int id_attr_vendor(); static int id_ctlr_conf(); static int id_ctlr_reconf(); static int id_set_ctlr_reconf(); static int id_ctlr_fat_attr(); static struct ipi_resp_table vendor_id_table[] = { /* parm-id minimum-len function */ ATTR_VENDOR, 28, id_attr_vendor, 0, 0, NULL, }; static struct ipi_resp_table ctlr_conf_table[] = { /* parm-id minimum-len function */ ATTR_ADDR_CONF, sizeof (struct addr_conf_parm), id_ctlr_conf, ATTR_SLVCNF_BIT, sizeof (struct reconf_bs_parm), id_ctlr_reconf, ATTR_FAC_ATTACH, 0, id_ctlr_fat_attr, 0, 0, NULL }; static struct ipi_resp_table ctlr_set_conf_table[] = { /* parm-id minimum-len function */ ATTR_SLVCNF_BIT, sizeof (struct reconf_bs_parm), id_set_ctlr_reconf, 0, 0, NULL }; static int id_attr_physdk(); static int id_attr_physdk_cm3(); static int id_phys_bsize(); static int id_log_bsize(); static int id_attr_nblks(); static struct ipi_resp_table attach_resp_table[] = { /* parm-id minimum-len function */ ATTR_PHYSDK, sizeof (struct physdk_parm), id_attr_physdk, ATTR_PHYS_BSIZE, sizeof (struct physbsize_parm), id_phys_bsize, ATTR_LOG_BSIZE, sizeof (struct datbsize_parm), id_log_bsize, ATTR_LOG_GEOM, sizeof (struct numdatblks_parm), id_attr_nblks, 0, 0, NULL }; static int id_respx(); static int id_deflist_parmlen(); static struct ipi_resp_table id_deflist_table[] = { /* parm-id minimum-len function */ RESP_EXTENT, sizeof (struct respx_parm), id_respx, ATTR_PARMLEN, sizeof (struct parmlen_parm), id_deflist_parmlen, 0, 0, NULL }; static int id_format(); static int id_rdwr_deflist(); static int id_ioctl_cmd(); static int id_error(); static void id_printerr(); static void id_retry(); static void id_q_retry(); static void id_cmd_intr(); static int id_send_cmd(); static void id_build_rdwr(); static int id_kmem_realloc(); static void id_update_map(); /* * Table for handling response. */ struct ipi_err_table { char et_byte; /* byte in parameter (ID is byte 0) */ u_char et_mask; /* mask to apply or parameter ID */ u_long et_flags; /* error code */ char *et_msg; /* message to print */ }; #define et_parmid et_mask /* mask doubles as parameter ID for byte 0 */ /* * Constant used to determine equivalent facility parameters from * the slave parameter ID. The facility ID is always 0x10 more. */ #define IPI_FAC_PARM 0x10 /* * Macros to initialize table. Used by IPI_ERR_TABLE_INIT */ #define IPI_ERR_PARM(sid, fid, flags, msg) \ { 0, (sid), (flags), (msg) }, #define IPI_ERR_BIT(byte, bit, flags, msg) \ { (byte), 1<<(bit), (flags), (msg) }, static struct ipi_err_table ipi_err_table[] = { IPI_ERR_TABLE_INIT() { 0, 0, 0, NULL } }; static struct ipi_err_table ipi_cond_table[] = { IPI_COND_TABLE_INIT() { 0, 0, 0, NULL } }; static struct opcode_name { u_char opcode; char *name; } opcode_name[] = { { IP_NOP, "no-op", }, { IP_ATTRIBUTES, "Attributes" }, { IP_REPORT_STAT, "Report Status" }, { IP_READ, "read" }, { IP_WRITE, "write" }, { IP_FORMAT, "format" }, { IP_READ_DEFLIST, "read defects list" }, { IP_WRITE_DEFLIST, "write defect list" }, { IP_REALLOC, "reallocate" }, { IP_ALLOC_RESTOR, "allocate restore" }, { IP_DIAG_CTL, "diag ctl" }, { 0xff, "async rupt" }, { 0, NULL } /* table end */ }; /* * Variables for write checking (read-back). */ #define ID_WCHK_BUFL (64*1024) /* length of buffer for read-back */ static struct buf *id_wchk_buf; /* pointer to buffer for read-back */ static void id_alloc_wchk(); /* allocate read-back buffer */ static void id_readback(); /* issue command for read-back */ long dk_hexunit; /* mask for dk numbers using hex unit number */ /* XXX - duplicates line in sys/dk.h */ extern char * strncpy(); /* * Determine existence of a controller. Called at boot time only. */ static idprobe(reg, ctlr) u_short *reg; /* IPI device (channel/slave/fac) number */ int ctlr; /* slave (string controller) number */ { struct id_ctlr *c; int unit; int fac; int csf = (int)reg; /* IPI device (channel/slave/fac) number */ #if MULT_MAJOR /* * Must initialize major number mapping even if no devices exist, * since open routines could still be called. */ id_major_map_init(); #endif if (IPI_FAC(csf) != IPI_NO_ADDR) { printf("idc%d: configured with bad IPI address %x\n", ctlr, csf); return (0); } /* * Call ipi_device to be sure channel is configured. * It will be called again in id_init_ctlr(), so some args not needed. */ if (ipi_device(csf, 0, 0, (void (*)())NULL, (void (*)())NULL, (caddr_t)NULL)) return (0); /* channel not present */ /* * Make sure we have enough controller pointers. */ if (ctlr >= nidc) { if (id_kmem_realloc((caddr_t *) &id_ctlr, (u_int)nidc * sizeof (struct id_ctlr), (u_int)(ctlr+NIDC_EXTEND) * sizeof (struct id_ctlr))) return (0); if (id_kmem_realloc((caddr_t *) &idcinfo, (u_int)nidc * sizeof (struct mb_ctlr *), (u_int)(ctlr+NIDC_EXTEND) * sizeof (struct mb_ctlr *))) return (0); idcdriver.mdr_cinfo = idcinfo; nidc = ctlr + NIDC_EXTEND; } /* * Allocate controller structure. */ if ((c = id_ctlr[ctlr]) != NULL) return (0); /* already allocated */ if ((c = alloc(struct id_ctlr, 1)) == NULL) return (0); /* no memory */ id_ctlr[ctlr] = c; c->c_flags |= IE_INIT_STAT; /* the status is unknown */ c->c_fac_flags = (1 << IDC_NUNIT) - 1; /* assume all facilities */ c->c_ipi_addr = csf; /* * Initialize controller by reading Vendor-ID, attributes. */ if (id_init_ctlr(csf, ctlr, ID_SYNCH)) { /* * It's there. Configure all online drives on this controller. */ unit = ID_MAKE_UNIT(IPI_CHAN(csf), IPI_SLAVE(csf), 0); for (fac = 0; fac < IDC_NUNIT; fac++, unit++) if (c->c_fac_flags & (1 << fac)) (void) id_findslave(unit, c, ID_SYNCH); } return (IPI_NSLAVE); } /* * Initialize controller if it is present. * Called through idprobe at boot and by idopen. * Returns non-zero if controller is present. */ static id_init_ctlr(reg, ctlr, mode) int reg; /* IPI device (channel/slave/fac) number */ int ctlr; /* slave (string controller) number */ int mode; /* ID_SYNCH or ID_ASYNCWAIT */ { register struct id_ctlr *c; struct mb_ctlr *mc; struct mb_driver *mdr; ASSERT(mode != ID_ASYNCH); c = id_ctlr[ctlr]; c->c_ctype = DKC_UNKNOWN; c->c_max_q = 1; /* temporarily limit to one cmd at a time */ c->c_max_q_len = MAXINT; c->c_max_parms = 1; c->c_ctlr = ctlr; c->c_ipi_addr = reg; /* * Search for mb_ctlr structure. mdr_cinfo isn't setup at probe time. * This could be eliminated later with an autoconf change. XXX */ for (mc = mbcinit; mdr = mc->mc_driver; mc++) { if (mdr == &idcdriver && mc->mc_ctlr == ctlr) break; } if (mdr) { c->c_intpri = mc->mc_intpri; } if (ipi_device(reg, (int)c->c_intpri, 0, idasync, id_recover_ctlr, (caddr_t)c)) return (0); /* channel not present */ c->c_recover.rec_history = 0; c->c_recover.rec_state = IDR_RST_CTLR1; c->c_recover.rec_substate = 0; if (mode == ID_SYNCH) c->c_recover.rec_flags = IP_SYNC | IP_NO_RETRY; else c->c_recover.rec_flags = IP_NO_RETRY; (void) id_recover(c, (struct id_unit *)NULL); if (mode == ID_ASYNCHWAIT) while (c->c_flags & IE_RECOVER) (void) sleep((caddr_t)c, PRIBIO); if (c->c_recover.rec_state != IDR_NORMAL) return (0); /* * Allocate enough command and response packets. * * BSA Bug ID 1115859 * Use c->c_max_q + 4 below to allocate a few more empty q's * than maximum I/O's a board can handle so that if board dies * with all possible q's outstanding, id_recover logic can still * allocate some q's for a recover/reset. Also, second call to * ipi_alloc() removed because it always turns out to be a NOP. */ if (ipi_alloc(reg, c->c_max_q + 4, (int)c->c_rw_cmd_len) == 0) return (0); (void) ipi_timeout(reg, ID_TIMEOUT); return (IPI_NSLAVE); /* number of slaves */ } /* * Inspect vendor ID parameter, and set controller type. */ /* ARGSUSED */ static int id_attr_vendor(q, parm_id, parm, len, cp) struct ipiq *q; int parm_id; /* parameter ID (0x50) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t cp; /* pointer id_ctlr structure */ { register struct vendor_parm *vp; register struct id_ctlr *c = (struct id_ctlr *)cp; vp = (struct vendor_parm *) parm; /* * Set command packet length for controllers which don't require * the transfer notification parameter. */ c->c_rw_cmd_len = sizeof (struct ipi_rdwr_cmd) - sizeof (u_short); c->c_max_parms = ID_MAX_PARMS; /* * Set controller type based on manufacturer and model. */ if (strncmp(vp->manuf, "MPI/CM-3", 8) == 0) { c->c_ctype = DKC_CDC_9057; c->c_flags |= ID_CTLR_VERIFY; c->c_max_parms = 1; /* XXX - debug */ } else if (strcmp(vp->manuf, "IntelliStor") == 0 && strcmp(vp->model, "Model 00") == 0 && strcmp((char *)vp->rev, "R001") == 0) { c->c_ctype = DKC_FJ_M1060; /* FJ/Intellistor */ c->c_flags |= ID_CTLR_VERIFY; } else if (strncmp(vp->manuf, "SUNMICRO", 8) == 0 && strncmp(vp->model, "PANTHER", 7) == 0) { c->c_ctype = DKC_SUN_IPI1; /* Panther */ /* * Set flag indicating this controller has enough * sense to schedule requests based on head position. */ c->c_flags |= ID_SEEK_ALG; c->c_rw_cmd_len = sizeof (struct ipi_rdwr_cmdx) - sizeof (u_short); c->c_max_parms = 1; /* * Early versions of Panther don't support the slave * reconfiguration parameter. */ if (vp->rev[3] == 0) c->c_flags |= ID_NO_RECONF; } else { c->c_ctype = DKC_UNKNOWN; printf("idc%d: unknown ctlr vendor id: manuf '%s' model '%s'\n", c->c_ctlr, vp->manuf, vp->model); } } /* * Handle addressee configuration parameter for controller. */ /* ARGSUSED */ static int id_ctlr_conf(q, parm_id, parm, len, cp) struct ipiq *q; int parm_id; /* parameter ID (0x65) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t cp; /* pointer id_ctlr structure */ { register struct id_ctlr *c = (struct id_ctlr *)cp; register struct addr_conf_parm *acp = (struct addr_conf_parm *) parm; c->c_max_cmdlen = acp->ac_max_cmdlen; c->c_max_resplen = acp->ac_max_resplen; c->c_max_q = (acp->ac_max_queue > 0) ? acp->ac_max_queue : MAXSHORT; c->c_max_q_len = (acp->ac_cmdbufsize > 0) ? acp->ac_cmdbufsize : MAXINT; } /* * Handle slave reconfiguration (bit fields) parameter for controller. */ /* ARGSUSED */ static int id_ctlr_reconf(q, parm_id, parm, len, cp) struct ipiq *q; int parm_id; /* parameter ID (0x6e) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t cp; /* pointer id_ctlr structure */ { register struct id_ctlr *c = (struct id_ctlr *)cp; register struct reconf_bs_parm *rp = (struct reconf_bs_parm *) parm; if (rp->sr_seek_alg) c->c_flags |= ID_SEEK_ALG; c->c_reconf_bs_parm = *rp; } /* * Handle setting of slave reconfiguration (bit fields) parameter for ctlr. */ /* ARGSUSED */ static int id_set_ctlr_reconf(q, parm_id, parm, len, cp) struct ipiq *q; int parm_id; /* parameter ID (0x6e) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t cp; /* pointer id_ctlr structure */ { register struct id_ctlr *c = (struct id_ctlr *)cp; register struct reconf_bs_parm *rp; rp = &c->c_reconf_bs_parm; /* point to copy in ctlr struct */ rp->sr_inh_resp_succ = 0; rp->sr_inh_ext_substat = 0; rp->sr_tnp_req = 1; rp->sr_inh_slv_msg = 0; /* enable slave messages */ rp->sr_dis_cl1x = 0; /* enable class 1 transitions */ *(struct reconf_bs_parm *)parm = *rp; /* set parm */ return (0); } /* * Handle facilities attached parameter for controller. */ /* ARGSUSED */ static int id_ctlr_fat_attr(q, parm_id, parm, len, cp) struct ipiq *q; int parm_id; /* parameter ID (0x6e) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t cp; /* pointer id_ctlr structure */ { struct id_ctlr *c = (struct id_ctlr *)cp; struct fat_parm *fp = (struct fat_parm *)parm; int flags = 0; for (; len >= sizeof (*fp); fp++, len -= sizeof (*fp)) flags |= 1 << fp->fa_addr; c->c_fac_flags = flags; } /* * Slave routine. * Just return zero since the controller probe configures everything. */ /* ARGSUSED */ static idslave(md, reg) struct mb_device *md; caddr_t reg; /* IPI address */ { return (0); } /* * Initialize unit by taking status and reading label via recovery procedure. */ static id_init_unit(un, mode) struct id_unit *un; int mode; { if (un->un_lp == NULL && (un->un_lp=(struct dk_label *)kmem_zalloc(DEV_BSIZE)) == NULL) { printf("id%3x: out of memory for label info\n", un->un_unit); return; } /* * reset most flags. */ un->un_flags = (un->un_flags & IE_INIT_STAT) | IE_RECOVER; un->un_recover.rec_history = 0; un->un_recover.rec_state = IDR_STAT_UNIT; un->un_recover.rec_substate = 0; if (mode == ID_SYNCH) un->un_recover.rec_flags = IP_SYNC; else un->un_recover.rec_flags = 0; (void) id_recover(un->un_ctlr, un); if (mode == ID_ASYNCHWAIT) while (un->un_flags & IE_RECOVER) (void) sleep((caddr_t)un, PRIBIO); } static id_findslave(unit, c, mode) int unit; struct id_ctlr *c; int mode; { struct id_unit *un; struct mb_device *md; int ctlr; /* controller number */ int fac; /* facility number */ int csf; /* IPI channel/slave/facility address */ extern int dkn; /* disk index for iostats */ ctlr = c->c_ctlr; /* * Make sure we have enough unit pointers. */ if (unit >= nid) { if (id_kmem_realloc((caddr_t *)&id_unit, (u_int)nid * sizeof (struct id_unit *), ((u_int)unit + NID_EXTEND) * sizeof (struct id_unit *))) return (0); nid = unit + NID_EXTEND; } /* * Allocate unit structure. */ if ((un = id_unit[unit]) == NULL) { if ((un = alloc(struct id_unit, 1)) == NULL) return (0); /* no memory */ id_unit[unit] = un; un->un_unit = ID_CSF_PRINT(unit); } fac = ID_FAC(unit); csf = ID_CSF(unit); if (c->c_unit[fac] != NULL) return (0); /* unit already configured */ un->un_ipi_addr = csf; un->un_flags |= IE_INIT_STAT; /* suppress offline messages */ c->c_unit[fac] = un; un->un_ctlr = c; /* * Setup number for I/O stats. Must do before reading label. */ if (dkn < DK_NDRIVE) un->un_dk = dkn++; else un->un_dk = -1; /* * Get attributes and label. */ id_init_unit(un, mode); if (!(un->un_flags & ID_UN_PRESENT)) { c->c_unit[fac] = NULL; un->un_ipi_addr = -1; un->un_ctlr = NULL; id_unit[unit] = NULL; /* * Give back dk number if no other driver took one. */ if (dkn == un->un_dk) dkn--; kmem_free((caddr_t)un, sizeof (struct id_unit)); return (0); } if (un->un_dk >= 0) dk_hexunit |= 1 << un->un_dk; un->un_flags |= ID_ATTACHED; /* * We found an online disk. If it has * never been attached before, we simulate * the autoconf code and set up the necessary * data. */ for (md = mbdinit; md->md_driver; md++) { if (md->md_driver == &idcdriver && md->md_unit == unit) { md->md_alive = 1; md->md_ctlr = ctlr; if ((md->md_mc = idcinfo[ctlr]) != NULL) { md->md_hd = md->md_mc->mc_mh; md->md_addr = md->md_mc->mc_addr; } md->md_dk = un->un_dk; #ifdef sun4m (void) add_ipi_drives(md, md->md_driver); #endif sun4m break; } } /* * Print the found message and attach the * disk to the system. */ printf("id%3x at idc%d facility %x\n", un->un_unit, ctlr, ID_FAC(unit)); return (1); } /* * Inspect Physical disk configuration parameter - set unit geometry. */ /* ARGSUSED */ static int id_attr_physdk(q, parm_id, parm, len, unp) struct ipiq *q; int parm_id; /* parameter ID (0x5f) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t unp; /* pointer to id_unit structure */ { register struct id_unit *un = (struct id_unit *)unp; register struct physdk_parm *pp = (struct physdk_parm *)parm; register struct dk_geom *g = &un->un_g; if (un->un_ctlr->c_ctype == DKC_CDC_9057) return (id_attr_physdk_cm3(q, parm_id, parm, len, unp)); g->dkg_pcyl = pp->pp_last_cyl + 1; /* number of physical cyls */ g->dkg_ncyl = pp->pp_last_cyl; /* allow one alternate */ g->dkg_acyl = 1; /* alternate cylinders */ g->dkg_bcyl = 0; /* beginning cylinder */ g->dkg_nhead = pp->pp_nheads; /* tracks per cylinder */ /* * rot/min = 60 sec/min * 1000000 usec/sec / usec/rot * but try not to divide by zero. */ if (pp->pp_rotper > 0) g->dkg_rpm = 60 * 1000000 / pp->pp_rotper; else g->dkg_rpm = 3600; /* reasonable default */ return (0); } /* * Inspect Physical disk configuration parameter - set unit geometry. * This version just for CDC CM-3 which has non-standard layout. */ /* ARGSUSED */ static int id_attr_physdk_cm3(q, parm_id, parm, len, unp) struct ipiq *q; int parm_id; /* parameter ID (0x5f) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t unp; /* pointer to id_unit structure */ { register struct id_unit *un = (struct id_unit *)unp; register struct physdk_parm_cm3 *pp = (struct physdk_parm_cm3 *)parm; register struct dk_geom *g = &un->un_g; g->dkg_pcyl = pp->pp_last_cyl + 1; /* number of physical cyls */ g->dkg_ncyl = pp->pp_last_cyl; /* allow one alternate */ g->dkg_acyl = 1; /* alternate cylinders */ g->dkg_bcyl = 0; /* beginning cylinder */ g->dkg_nhead = pp->pp_nheads; /* tracks per cylinder */ /* * rot/min = 60 sec/min * 1000000 usec/sec / usec/rot * but try not to divide by zero. */ if (pp->pp_rotper > 0) g->dkg_rpm = 60 * 1000000 / pp->pp_rotper; else g->dkg_rpm = 3600; /* reasonable default */ return (0); } /* * Inspect Logical Geometry parameter - get logical blocks per track. * The geometry should be already set from the physical disk configuration * parameter, so doublecheck with this parameter. */ /* ARGSUSED */ static int id_attr_nblks(q, parm_id, parm, len, unp) struct ipiq *q; int parm_id; /* parameter ID (0x5f) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t unp; /* pointer to id_unit structure */ { register struct id_unit *un = (struct id_unit *)unp; register struct numdatblks_parm *pp = (struct numdatblks_parm *)parm; register struct dk_geom *g = &un->un_g; int i; int unit = un->un_unit; g->dkg_nsect = pp->bpertrk; /* number of blocks per track */ un->un_first_block = pp->strtadr; if (pp->strtadr != 0) { printf("id%3x: warning: starting block address nonzero: %d\n", unit, pp->strtadr); } if (pp->bpertrk == 0) { printf("id%3x: zero blocks per track\n", unit); return; } i = pp->bpercyl / pp->bpertrk; /* tracks per cylinder */ if (i != g->dkg_nhead) { printf( "id%3x: inconsistent geometry: blk/cyl %d blk/trk %d heads %d\n", unit, pp->bpercyl, pp->bpertrk, g->dkg_nhead); } if (pp->bpercyl == 0) { printf("id%3x: zero blocks per cylinder\n", unit); return; } i = pp->bperpart / pp->bpercyl; /* cylinder count */ if (i > g->dkg_pcyl) { printf( "id%3x: inconsistent geometry: tot blks %d blks/cyl %d pcyl %d\n", unit, pp->bperpart, pp->bpercyl, g->dkg_pcyl); g->dkg_pcyl = MIN(i, g->dkg_ncyl); g->dkg_ncyl = g->dkg_pcyl - g->dkg_acyl; printf("id%3x: setting pcyl %d ncyl %d acyl %d\n", unit, g->dkg_pcyl, g->dkg_ncyl, g->dkg_acyl); } } /* * Handle parameter: Size of disk physical blocks. */ /* ARGSUSED */ static int id_phys_bsize(q, parm_id, parm, len, unp) struct ipiq *q; int parm_id; /* parameter ID (0x5f) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t unp; /* pointer to id_unit structure */ { register struct id_unit *un = (struct id_unit *)unp; register struct physbsize_parm *pp = (struct physbsize_parm *)parm; if ((un->un_phys_bsize = pp->pblksize) > 0) un->un_flags |= ID_FORMATTED; } /* * Handle parameter: Size of disk logical blocks. */ /* ARGSUSED */ static int id_log_bsize(q, parm_id, parm, len, unp) struct ipiq *q; int parm_id; /* parameter ID (0x5f) */ u_char *parm; /* parameter */ int len; /* length of parameter */ caddr_t unp; /* pointer to id_unit structure */ { register struct id_unit *un = (struct id_unit *)unp; register struct datbsize_parm *pp = (struct datbsize_parm *)parm; int i; un->un_log_bsize = pp->dblksize; for (i = DEV_BSHIFT; i < NBBY * NBPW; i++) { if (un->un_log_bsize == (1 << i)) { un->un_log_bshift = i; break; } } if (i == NBBY * NBPW) { printf("id%3x: invalid logical block size %d\n", un->un_unit, un->un_log_bsize); } } /* * Open routines. * * The block and character devices use diferent open routines so that * the multiple major number support can work. */ idbopen(dev, flag) dev_t dev; int flag; { return (idopen(ID_BMINOR(dev), flag, 0)); } idcopen(dev, flag) dev_t dev; int flag; { return (idopen(ID_CMINOR(dev), flag, 1)); } static idopen(minor_dev, flag, char_dev) u_int minor_dev; int flag; int char_dev; /* non-zero if called by character device */ { int unit; int ctlr; int csf; /* IPI address */ register struct id_ctlr *c; register struct id_unit *un; int s; un = NULL; if ((unit = ID_UNIT(minor_dev)) < nid) un = id_unit[unit]; /* * If this drive wasn't found by auto configuration or a previous * open, see if it has become available since then. */ if (un == NULL || !(un->un_flags & ID_UN_PRESENT)) { /* * Disable IPI interrupts. * * This is minor overkill, since all we really need to do * is lock out disk interrupts while we are doing disk * commands. However, there is no easy way to tell what * the disk priority is at this point. Since this situation * only occurs once at disk spin-up, the extra lock-out * shouldn't hurt very much. */ s = spl_ipi(); /* * Search for an existing controller. */ csf = ID_CTLR_CSF(unit); /* controller's address */ for (ctlr = 0; ctlr < nidc; ctlr++) { if ((c = id_ctlr[ctlr]) && c->c_ipi_addr == csf && ((c->c_flags & ID_C_PRESENT) || id_init_ctlr(csf, ctlr, ID_ASYNCHWAIT))) break; } /* * If a controller was found. Try to initialize unit. */ if (ctlr < nidc && c && id_findslave(unit, c, ID_ASYNCHWAIT)) { un = id_unit[unit]; } (void) splx(s); } /* * By the time we get here the disk is marked present if it exists * at all. We simply check to be sure the partition being opened * is nonzero in size. If a raw partition is opened with the * nodelay flag, we let it succeed even if the size is zero. This * allows ioctls to later set the geometry and partitions. */ if (un && (un->un_flags & ID_UN_PRESENT) && (un->un_flags & ID_ATTACHED) && IE_STAT_PRESENT(un->un_flags) && IE_STAT_PRESENT(un->un_ctlr->c_flags)) { /* * If this is the first open, schedule the watchdog routine. */ if (!id_watch_started) { id_watch_started = 1; id_watch(); } if (un->un_lpart[ID_LPART(minor_dev)].un_map.dkl_nblk > 0) return (0); if (char_dev && (flag & (FNDELAY | FNBIO))) return (0); } return (ENXIO); } int (*idstrategy_tstpoint)(); idstrategy(bp) register struct buf *bp; { register int unit; /* unit number */ register struct id_unit *un; /* unit structure */ register struct id_ctlr *c; /* controller struct */ register struct dk_geom *g; /* geometry pointer */ register struct diskhd *dp; /* queue header*/ daddr_t blkno; /* block number */ register struct dk_map *lp; /* logical partition */ int s; if (idstrategy_tstpoint != NULL) { if ((*idstrategy_tstpoint)(bp)) { return; } } if ((unit = ID_BUNIT(bp->b_dev)) >= nid || (un=id_unit[unit]) == NULL) { bp->b_error = ENXIO; bp->b_flags |= B_ERROR; iodone(bp); return; } c = un->un_ctlr; g = &un->un_g; lp = &un->un_lpart[ID_LPART(bp->b_dev)].un_map; if (!(un->un_flags & ID_ATTACHED) || !IE_STAT_PRESENT(un->un_flags) || !IE_STAT_PRESENT(c->c_flags)) { bp->b_error = ENXIO; bp->b_flags |= B_ERROR; iodone(bp); } else if ((blkno = dkblock(bp)) >= lp->dkl_nblk || lp->dkl_nblk == 0) { if ((blkno != lp->dkl_nblk) || (bp->b_flags & B_READ) == 0) { bp->b_error = EINVAL; bp->b_flags |= B_ERROR; } bp->b_resid = bp->b_bcount; /* generate EOF return */ iodone(bp); } else if (un->un_log_bsize == 0) { iodone(bp); } else if (bp->b_bcount < un->un_log_bsize) { bp->b_error = EINVAL; /* not reading a full block */ bp->b_flags |= B_ERROR; iodone(bp); } else { bp->b_cylin = blkno / (g->dkg_nsect * g->dkg_nhead) + lp->dkl_cylno + g->dkg_bcyl; /* * If the operation is off the end of the disk, it's an error. * This really shouldn't happen since we checked the block * number against the end of the partition, but the label * might be wrong. */ if (bp->b_cylin >= g->dkg_ncyl) { bp->b_flags |= B_ERROR; iodone(bp); return; } /* * While playing with queues, lock out interrupts. * If there are already requests queued for this disk, * sort the new request into the list. * Then call idstart to send the command if possible. */ dp = &un->un_bufs; s = spl_ipi(); if (dp->b_actf) { disksort(dp, bp); } else { bp->av_forw = NULL; dp->b_actf = dp->b_actl = bp; } (void) idstart(un); (void) splx(s); } } int idstart_busy; /* statistics */ /* * This routine starts an operation for a specific unit. It is used only * in the normal asynchronous case, and is only called if the controller * is not known to be busy. It is OK to call this routine even if there * are no outstanding operations, so it is called whenever the unit * becomes free. * * It is used by idintr() and idstrategy(). It is always called * at disk interrupt priority. */ static int idstart(un) register struct id_unit *un; { register struct diskhd *dp = &un->un_bufs; register struct buf *bp; register struct ipiq *q; register struct id_ctlr *c = un->un_ctlr; int dk; daddr_t blkno; int nblks; struct un_lpart *lp; /* logical partition */ struct ipiq *rbq; /* read-back request */ while (((q = un->un_wait_q) != NULL || (bp = dp->b_actf) != NULL) && IE_STAT_READY(un->un_flags) && IE_STAT_READY(c->c_flags) && !(un->un_flags & ID_LOCKED)) { if (c->c_cmd_q >= c->c_max_q || c->c_cmd_q_len + c->c_rw_cmd_len >= c->c_max_q_len) { c->c_flags |= ID_CTLR_WAIT; idstart_busy++; break; } /* * First check for a queue already setup but waiting for a * writeback. If none, setup DMA and get command block. */ if (q != NULL) { un->un_wait_q = NULL; bp = q->q_buf; } else if ((q = ipi_setup(un->un_ipi_addr, bp, c->c_rw_cmd_len, MAX_RESPLEN, idrestart, 0)) == NULL) { if (bp->b_flags & B_ERROR) { dp->b_actf = bp->av_forw; if (dp->b_actl == bp) dp->b_actl = NULL; iodone(bp); continue; } return (DVMA_RUNOUT); /* setup failed */ } /* * If writing and write checking is on for the partition, * set a flag so that read-back will occur. * Also, be sure that read-back buffer is setup. * If it isn't possible to allocate a queue for readback, * put the write ipiq on a list for later. * If the controller is capable of doing readback by itself, * set both IP_WCHK and IP_WCHK_READ to tell id_build_rdwr(), * to add the verify parameter. */ q_related(q) = NULL; rbq = NULL; if ((bp->b_flags & B_READ) == 0 && (un->un_wchkmap & (1 << ID_LPART(bp->b_dev)))) { if (c->c_flags & ID_CTLR_VERIFY) { q->q_flag |= IP_WCHK | IP_WCHK_READ; } else { if ((rbq = ipi_setup(un->un_ipi_addr, id_wchk_buf, c->c_rw_cmd_len, MAX_RESPLEN, idrestart, 0)) == NULL) { if (id_wchk_buf->b_flags & B_ERROR) { id_printerr(q, "idstart: error setting up readback"); id_wchk_buf->b_flags &= ~B_ERROR; bp->b_flags |= B_ERROR; dp->b_actf = bp->av_forw; if (dp->b_actl == bp) dp->b_actl = NULL; iodone(bp); continue; } /* * Put write request on queue until read * request can be setup. */ q->q_next = NULL; un->un_wait_q = q; return (DVMA_RUNOUT); /* setup failed */ } q_related(rbq) = q; q_related(q) = rbq; q->q_flag |= IP_WCHK; } } /* * Take the buffer off the queue for this disk. */ dp->b_actf = bp->av_forw; if (dp->b_actl == bp) dp->b_actl = NULL; /* * If the transfer size would take it past the end of the * partition, trim it down. Also trim it down to a multiple * of the block size. */ blkno = dkblock(bp); lp = &un->un_lpart[ID_LPART(bp->b_dev)]; nblks = NBLKS(bp->b_bcount, un); if (blkno + nblks > lp->un_map.dkl_nblk) nblks = lp->un_map.dkl_nblk - blkno; bp->b_resid = bp->b_bcount - (nblks << un->un_log_bshift); /* * Map block number within partition to real block number. */ blkno += lp->un_blkno; q->q_errblk = (int)blkno; /* * Build the IPI command packet. */ id_build_rdwr(q, un, blkno, nblks); /* form command */ /* * Build readback command if needed. */ if (rbq != NULL) id_build_rdwr(rbq, un, blkno, nblks); /* * Update counts of outstanding commands. */ un->un_cmd_q++; c->c_cmd_q++; c->c_cmd_q_len += c->c_rw_cmd_len; /* * Send command. */ ipi_start(q, (long)0, idintr); /* * Update iostat statistics. */ if ((dk = un->un_dk) >= 0) { dk_busy |= 1 << dk; dk_seek[dk]++; dk_xfer[dk]++; if (bp->b_flags & B_READ) dk_read[dk]++; dk_wds[dk] += bp->b_bcount >> 6; } } /* * If the unit or controller has gone away, give errors for all * queued bufs. */ if (!IE_STAT_PRESENT(c->c_flags) || !IE_STAT_PRESENT(un->un_flags)) { while ((bp = dp->b_actf) != NULL) { dp->b_actf = bp->av_forw; if (dp->b_actl == bp) dp->b_actl = NULL; bp->b_error = ENXIO; bp->b_flags |= B_ERROR; iodone(bp); } } return (0); } /* * Call idstart for any disks that have queued requests. * Used as a callback routine from ipi_setup. Called when * resources become available. * * Returns non-zero if any commands could not be restarted. */ struct id_restart_trace { int un; int res; caddr_t arg; int filler; }; #define ID_RESTART_NTRACE 1024 struct id_restart_trace id_restart_trace[ID_RESTART_NTRACE]; struct id_restart_trace *id_restart_ptr = id_restart_trace; static int idrestart(arg) caddr_t arg; /* controller pointer or NULL for all controllers */ { struct id_unit *un; struct id_ctlr *c = (struct id_ctlr *)arg; register int s; register int stat = 0; int unit; static int next_unit; s = spl_ipi(); unit = next_unit; do { if (++unit >= nid) unit = 0; if ((un = id_unit[unit]) && (c == NULL || un->un_ctlr == c) && (un->un_bufs.b_actf || un->un_wait_q)) { stat = idstart(un); id_restart_ptr->un = un->un_unit; id_restart_ptr->res = stat; id_restart_ptr->arg = arg; id_restart_ptr++; if (id_restart_ptr >= &id_restart_trace[ID_RESTART_NTRACE]) id_restart_ptr = id_restart_trace; if (stat != 0 && c == NULL) break; } } while (unit != next_unit); next_unit = unit; (void) splx(s); return (stat); } /* * Build a read/write command for request. Ipi_setup has already been called. */ static void id_build_rdwr(q, un, blkno, nblks) struct ipiq *q; /* setup request */ struct id_unit *un; /* unit structure */ daddr_t blkno; /* absolute block number */ int nblks; /* length in blocks */ { register struct ipi3header *ip; union packet { u_char *pp; /* pointer to next packet byte */ u_long *lp; /* pointer parm len, id, and pad) */ struct cmdx_parm *cxp; /* pointer to command extent parm */ } pu; q->q_dev = (caddr_t)un; q->q_dma_len = nblks << un->un_log_bshift; /* * Form IPI-3 Command packet. */ ip = (struct ipi3header *)q->q_cmd; if (q->q_buf->b_flags & B_READ) { ip->hdr_opcode = IP_READ; } else { ip->hdr_opcode = IP_WRITE; } ip->hdr_mods = IP_OM_BLOCK; /* * fill in command extent (block count and block address). */ pu.pp = (u_char *)ip + sizeof (struct ipi3header); *pu.lp++ = (sizeof (struct cmdx_parm)+1) << 8 | CMD_EXTENT; pu.cxp->cx_count = nblks; pu.cxp->cx_addr = blkno; pu.pp += sizeof (struct cmdx_parm); /* * Add Transfer parameter if a verify is requested. * Form the three bytes (plus pad byte) in one word. * The combination of IP_WCHK and IP_WCHK_READ flags is used to * indicate that this is desired, and these flags are cleared * after the parameter is added. */ if ((q->q_flag & (IP_WCHK | IP_WCHK_READ)) == (IP_WCHK | IP_WCHK_READ)) { *pu.lp++ = 2 << 16 | XFER_PARM << 8 | XFER_VERIFY; q->q_flag &= ~(IP_WCHK | IP_WCHK_READ); } /* * Fill in the transfer notification parameter if needed. * Note that this may leave pu.pp non-word-aligned. */ if (q->q_tnp_len == sizeof (caddr_t)) { *pu.lp++ = (sizeof (caddr_t)+1) << 8 | XFER_NOTIFY; *pu.lp++ = * (u_long *) q->q_tnp; } else if (q->q_tnp_len > 0) { *pu.pp++ = q->q_tnp_len + 1; /* parameter length */ *pu.pp++ = XFER_NOTIFY; /* parameter ID */ bcopy(q->q_tnp, (caddr_t)pu.pp, q->q_tnp_len); pu.pp += q->q_tnp_len; } /* * Form packet length based on where the pointer ended up. */ ip->hdr_pktlen = (pu.pp - (u_char *)ip) - sizeof (ip->hdr_pktlen); } /* * Main Interrupt handler - used only for commands issued by idstart(). */ static void idintr(q) register struct ipiq *q; { register struct id_unit *un; register struct id_ctlr *c; register struct buf *bp; int started = 0; int done = 0; int flags; int dk; int s; if (q) { bp = q->q_buf; c = (struct id_ctlr *)q->q_ctlr; if ((un = (struct id_unit *)q->q_dev) == NULL) { printf("idintr: bad rupt. NULL q_dev q %x.\n", q); id_printerr(q, "idintr: bad rupt NULL q_dev\n"); return; } /* * Check for missing interrupt indication before decrementing * queue counts, since this isn't a completion. */ if (q->q_result == IP_MISSING) { id_missing_req(q); return; } if (--un->un_cmd_q == 0 && (un->un_flags & ID_WANTED)) wakeup((caddr_t)un); c->c_cmd_q--; c->c_cmd_q_len -= c->c_rw_cmd_len; if (c->c_cmd_q_len < 0) { id_printerr(q, "idintr: neg c_cmd_q_len"); c->c_cmd_q_len = 0; } switch (q->q_result) { case IP_SUCCESS: /* * If successful, start any waiting commands right away. */ if ((q->q_flag & IP_WCHK) == 0) { s = spl_ipi(); (void) idstart(un); (void) splx(s); started = 1; } done = 1; break; case IP_ERROR: case IP_COMPLETE: flags = id_error_parse(q, ipi_err_table, id_error, c, un, 0); done = 1; if ((flags & IE_RETRY)) { id_q_retry(q); done = 0; } else if ((flags & IE_CMD) == 0) { /* non fatal */ q->q_result = IP_SUCCESS; } else if (bp) { bp->b_flags |= B_ERROR; } break; case IP_RETRY: id_q_retry(q); break; default: if (bp) bp->b_flags |= B_ERROR; done = 1; break; } if (done) { /* * If a read back must be done on this buffer, * start it now, and delay the completion of the * write request until the readback completes. */ if ((q->q_flag & IP_WCHK) && q->q_result == IP_SUCCESS) { id_readback(q); } else if (q->q_flag & IP_WCHK_READ) { struct ipiq *rbq = q; /* * This is the completion of a read-back. */ q = q_related(q); bp = q->q_buf; if (rbq->q_result == IP_SUCCESS) { ipi_free(rbq); ipi_free(q); if (bp) iodone(bp); } else if (rbq->q_result == IP_RETRY_FAILED) { ipi_free(rbq); ipi_free(q); if (bp) { bp->b_flags |= B_ERROR; iodone(bp); } } else { id_printerr(q, "readback failed. retrying write"); id_q_retry(q); } } else { /* * Normal (non-readback) completion. * * Since ipi_free may do cache flushes to make * CPU and I/O caches consistent, must do it * before calling iodone(). */ ipi_free(q); if (bp) iodone(bp); } } if (c->c_flags & ID_CTLR_WAIT) { c->c_flags &= ~ID_CTLR_WAIT; (void)idrestart((caddr_t)c); } else if (!started) { s = spl_ipi(); (void) idstart(un); (void) splx(s); } un->un_idle_time = 0; /* * If nothing queued or running on unit, mark it not busy * for iostats. */ if (un->un_cmd_q == 0 && (dk = un->un_dk) >= 0 && un->un_bufs.b_actf == NULL) dk_busy &= ~(1 << dk); } } /* * Send IPI command. * * Mode selects whether driver should wait by polling or sleeping or not * wait at all. Returns non-zero on error or poll timeout. */ static int id_send_cmd(q, flags, mode) struct ipiq *q; long flags; int mode; { struct id_unit *un; struct id_ctlr *c; if ((un = (struct id_unit *)q->q_dev) != NULL) { if (!IE_STAT_READY(un->un_flags)) { return (ENXIO); } un->un_cmd_q++; } if ((c = (struct id_ctlr *)q->q_ctlr) != NULL) { if (!IE_STAT_READY(c->c_flags)) { if (un) un->un_cmd_q--; return (ENXIO); } c->c_cmd_q++; c->c_cmd_q_len += q->q_cmd->hdr_pktlen + sizeof (u_short); } switch (mode) { case ID_SYNCH: ipi_start(q, flags | IP_SYNC, id_cmd_intr); if (ipi_poll(q, (long)ID_POLL_TIMEOUT) == 0) { id_printerr(q, "poll timeout"); return (EIO); } break; case ID_ASYNCHWAIT: ipi_start(q, flags | IP_WAKEUP, id_cmd_intr); while (q->q_result == IP_INPROGRESS || q->q_result == IP_RETRY) (void) sleep((caddr_t)q, PRIBIO); break; case ID_ASYNCH: ipi_start(q, flags, id_cmd_intr); break; } return (0); } /* * Auxilliary Interrupt handler - used for commands not issued via idstart(). * * NOTE: This interface doesn't return sys/errno.h type error codes in the * buffer. It uses the codes defined in idvar.h instead. Since this * is used only by id_ioctl_cmd and related routines, this is OK. */ static void id_cmd_intr(q) register struct ipiq *q; { register struct id_unit *un; register struct id_ctlr *c; register struct buf *bp; int err; int flags; int len; if (q) { /* * Check for missing interrupt indication before decrementing * queue counts, since this isn't a completion. */ if (q->q_result == IP_MISSING) { id_missing_req(q); return; } err = IDE_NOERROR; bp = q->q_buf; c = (struct id_ctlr *)q->q_ctlr; if ((un = (struct id_unit *)q->q_dev) != NULL && --un->un_cmd_q == 0 && (un->un_flags & ID_WANTED)) wakeup((caddr_t)un); c->c_cmd_q--; if ((len = q->q_cmd->hdr_pktlen) > 0) c->c_cmd_q_len -= len + sizeof (u_short); if (c->c_cmd_q_len < 0) { id_printerr(q, "id_cmd_intr: ctlr cmd_q_len negative"); c->c_cmd_q_len = 0; } switch (q->q_result) { case IP_SUCCESS: if (q->q_retry) err = IDE_RETRIED; /* not an error */ /* XXX - need to know more about the error that caused retry. */ break; case IP_ERROR: case IP_COMPLETE: flags = id_error_parse(q, ipi_err_table, id_error, c, un, 0); if ((flags & IE_CMD) == 0 && (q->q_flag & IP_DIAGNOSE) == 0) { q->q_result = IP_SUCCESS; } if (flags & IE_CORR) { err = IDE_CORR; } else if (flags & IE_UNCORR) { err = IDE_UNCORR; } else { err = IDE_FATAL; } break; case IP_RETRY: id_q_retry(q); return; /* don't do wakeup */ default: err = IDE_FATAL; break; } if (err && bp) { bp->b_flags |= B_ERROR; bp->b_error = err; } if (c->c_flags & ID_CTLR_WAIT) { c->c_flags &= ~ID_CTLR_WAIT; (void)idrestart((caddr_t)c); } if (q->q_flag & IP_WAKEUP) wakeup((caddr_t)q); if (un) { un->un_idle_time = 0; } } } /* * Asynchronous interrupt handler */ static void idasync(q) register struct ipiq *q; /* interrupt info */ { register int fac; register struct id_ctlr *c; /* device structure */ struct id_unit *un; struct id_unit new_unit; /* temporary unit structure */ c = (struct id_ctlr *)q->q_ctlr; if ((fac = q->q_resp->hdr_facility) != IPI_NO_ADDR && fac < IDC_NUNIT) { un = c->c_unit[fac]; /* * If the unit structure wasn't allocated, this might be * a new device coming on-line. Use a temporary unit * structure to make the messages come out right. The real * unit structure will be allocated when the disk is opened. */ if (un == NULL) { bzero((caddr_t)&new_unit, sizeof (new_unit)); un = &new_unit; un->un_ipi_addr = q->q_csf; un->un_unit = IPI_CHAN(q->q_csf) << 8 | IPI_SLAVE(q->q_csf) << 4 | fac; un->un_ctlr = c; un->un_dk = -1; c->c_unit[fac] = un; } q->q_dev = (caddr_t)un; (void) id_error_parse(q, ipi_err_table, id_error, c, un, 0); if (un == &new_unit) { q->q_dev = NULL; c->c_unit[fac] = NULL; } } else (void) id_error_parse(q, ipi_err_table, id_error, c, (struct id_unit *)NULL, 0); } /* * Error handler for all kinds of interrupts. Called through id_error_parse. * Handles asynchronous interrupts also. */ static int id_error(q, c, un, code, flags, msg, parm) struct ipiq *q; /* request containing response */ struct id_ctlr *c; /* controller */ struct id_unit *un; /* unit - may be NULL */ int code; /* error result/action code */ int flags; /* error flags */ char *msg; /* message */ u_char *parm; /* raw parameter (incl ID/len) */ { int new_flags = 0; u_int old_stat; u_int *fp; /* flags pointer */ u_int change_mask; /* online/offline status change */ if ((flags & (IE_MSG | IE_PRINT)) && !(q->q_flag & IP_SILENT)) printf("%s. ", msg); if ((flags & IE_FAC) && un == NULL) { printf("idc%d: facility status for unknown unit\n", c->c_ctlr); new_flags |= IE_PRINT; } switch (code) { case 0: /* nothing */ break; case IE_RESP_EXTENT: /* response extent parameter */ (void) id_respx(q, 0, (struct respx_parm *)(parm + 2), 0, q->q_buf); break; case IE_MESSAGE: /* for microcode messages */ { struct msg_ucode_parm *p; char m, *cp; int i; p = (struct msg_ucode_parm *)&parm[2]; if (p->mu_flags & IPMU_FAIL_MSG) printf("idc%d: ctlr failure fru %x message: '", p->mu_u.mu_m.mu_fru, c->c_ctlr); else printf("idc%d: ctlr message: '", c->c_ctlr); cp = p->mu_u.mu_m.mu_msg; /* first char of msg */ i = parm[0] + 1 - (cp - (char *)parm); /* msg len */ while (i-- > 0) { m = *cp++; printf("%c", m == '\n' ? ' ' : m); } printf("'\n"); } break; case IE_STAT_OFF: /* unit or controller went offline */ case IE_STAT_ON: /* unit or controller went online */ change_mask = (flags & IE_STAT_MASK); if (flags & IE_FAC) { if (un) fp = &un->un_flags; else break; } else { fp = &c->c_flags; } old_stat = *fp; if (code == IE_STAT_OFF) *fp &= ~change_mask; else *fp |= change_mask; /* * Print a message if the status has changed and a message * hasn't been already printed due to the flags settings. * However, don't print this message if the unit/ctlr flags * or the passed-in flags indicate that this is the * initial status for the unit/ctlr. */ if (!((flags | old_stat) & IE_INIT_STAT) && old_stat != *fp && !(flags & (IE_MSG | IE_PRINT))) { id_printerr(q, msg); } break; case IE_IML: /* reload slave */ id_printerr(q, "id_error: slave IML not supported"); /* XXX */ new_flags |= IE_RETRY; /* retry request */ break; case IE_RESET: /* reset slave */ id_printerr(q, "id_error: slave reset not supported"); /* XXX */ new_flags |= IE_RETRY; /* retry request */ break; case IE_QUEUE_FULL: /* buffer limits exceeded */ id_printerr(q, "id_error: full queue: not handled"); /* XXX */ new_flags |= IE_RETRY; /* retry request */ break; case IE_READ_LOG: /* log full */ id_printerr(q, "id_error: log read not supported"); /* XXX */ new_flags |= IE_RETRY; /* retry request */ break; case IE_ABORTED: /* command aborted */ id_printerr(q, "id_error: aborted command not supported"); /* XXX */ new_flags |= IE_DUMP | IE_CMD; /* dump cmd/resp */ break; default: break; } return (new_flags); } int idsize(dev) dev_t dev; { int unit; register struct id_unit *un; /* * Call open in case the device wasn't configured yet. */ if (idbopen(dev, 0)) return (-1); if ((unit = ID_BUNIT(dev)) >= nid || (un = id_unit[unit]) == NULL || !(un->un_flags & ID_ATTACHED)) return (-1); return ((int)un->un_lpart[ID_LPART(dev)].un_map.dkl_nblk); } /* * Perform logical disk section mapping. * Map logical block number into physical block number. */ static daddr_t id_map(minor_dev, blkno, nblk) u_int minor_dev; daddr_t blkno, nblk; { register struct id_unit *un = id_unit[ID_UNIT(minor_dev)]; register struct un_lpart *lp; if (un == NULL || !(un->un_flags & ID_UN_PRESENT)) return (-1); lp = &un->un_lpart[ID_LPART(minor_dev)]; if (blkno >= lp->un_map.dkl_nblk || (blkno+nblk) > lp->un_map.dkl_nblk) return (-1); /* * Offset into the correct partition. */ return (blkno + lp->un_blkno); } /* * This routine dumps memory to the disk. It assumes that the memory has * already been mapped into mainbus space. It is called at disk interrupt * priority when the system is in trouble. */ iddump(dev, addr, blkno, nblk) dev_t dev; caddr_t addr; daddr_t blkno, nblk; { int unit; register struct id_unit *un; struct ipiq *q; int errno = 0; daddr_t rblkno; /* real block number */ struct buf dbuf; /* * Check to make sure the operation makes sense. */ if ((unit = ID_BUNIT(dev)) >= nid || (un = id_unit[unit]) == NULL || !(un->un_flags & ID_UN_PRESENT)) return (ENXIO); bzero((caddr_t)&dbuf, sizeof (dbuf)); dbuf.b_un.b_addr = (caddr_t)addr; dbuf.b_flags = B_BUSY; dbuf.b_bcount = nblk * DEV_BSIZE; if (un->un_log_bsize != DEV_BSIZE) { blkno = (blkno * DEV_BSIZE) >> un->un_log_bshift; nblk = (nblk * DEV_BSIZE) >> un->un_log_bshift; } if ((rblkno = id_map(ID_BMINOR(dev), blkno, nblk)) < 0) return (EINVAL); rblkno += un->un_first_block; /* usually zero */ if ((q = ipi_setup(un->un_ipi_addr, &dbuf, un->un_ctlr->c_rw_cmd_len, MAX_RESPLEN, IPI_NULL, IP_NO_DMA_SETUP)) == NULL) return (EIO); /* * Construct IPI-3 packet. */ id_build_rdwr(q, un, rblkno, (int)nblk); /* * Send command. */ if (id_send_cmd(q, (long)0, ID_SYNCH) || q->q_result != IP_SUCCESS) errno = EIO; ipi_free(q); return (errno); } static void id_minphys(bp) struct buf *bp; { if (bp->b_bcount > ID_MAXPHYS) bp->b_bcount = ID_MAXPHYS; } #if !MULT_RBUF static caddr_t physio_bufs; #endif /* * Do a raw read using the unit's raw I/O buffer. * Called from cdevsw() at normal priority. */ int idread(dev, uio) dev_t dev; struct uio *uio; { register int unit = ID_CUNIT(dev); #if !MULT_RBUF struct buf *bp; int err; #endif if (unit >= nid) return (ENXIO); if ((uio->uio_fmode & FSETBLK) == 0 && uio->uio_offset % DEV_BSIZE != 0) return (EINVAL); #if MULT_RBUF return (physio(idstrategy, NULL, ID_BLKDEV(dev), B_READ, id_minphys, uio)); #else bp = (struct buf *) kmem_fast_zalloc(&physio_bufs, sizeof (struct buf), 1); bp->b_flags = 0; err = physio(idstrategy, bp, ID_BLKDEV(dev), B_READ, id_minphys, uio); kmem_fast_free(&physio_bufs, (caddr_t)bp); return (err); #endif } /* * Do a raw write using the special per unit buffer. * Called from cdevsw() at normal priority. */ int idwrite(dev, uio) dev_t dev; struct uio *uio; { register int unit = ID_CUNIT(dev); #if !MULT_RBUF struct buf *bp; int err; #endif if (unit >= nid) return (ENXIO); if ((uio->uio_fmode & FSETBLK) == 0 && uio->uio_offset % DEV_BSIZE != 0) return (EINVAL); #if MULT_RBUF return (physio(idstrategy, NULL, ID_BLKDEV(dev), B_WRITE, id_minphys, uio)); #else bp = (struct buf *) kmem_fast_zalloc(&physio_bufs, sizeof (struct buf), 1); bp->b_flags = 0; err = physio(idstrategy, bp, ID_BLKDEV(dev), B_WRITE, id_minphys, uio); kmem_fast_free(&physio_bufs, (caddr_t)bp); return (err); #endif } /* * This routine implements the ioctl calls for the disk. * It is called from the device switch at normal priority. */ /* ARGSUSED */ idioctl(dev, cmd, data, flag) dev_t dev; int cmd, flag; caddr_t data; { register struct id_unit *un = id_unit[ID_CUNIT(dev)]; struct id_ctlr *c; register union id_ptr { /* union for usages of third arg */ struct dk_info *inf; struct dk_conf *conf; struct dk_type *typ; struct dk_cmd *com; struct dk_diag *diag; struct dk_geom *geom; struct dk_map *map; int *wchk; caddr_t data; } p; int i, s, err; p.data = data; /* set union to fourth argument */ err = 0; /* no error */ if (un == NULL) return (ENXIO); c = un->un_ctlr; switch (cmd) { /* * Return info concerning the controller. */ case DKIOCINFO: p.inf->dki_ctlr = i = c->c_ipi_addr; p.inf->dki_unit = IPI_SLAVE(i); p.inf->dki_ctype = c->c_ctype; #define ID_DKI_FLAGS (DKI_FMTCYL | DKI_HEXUNIT) p.inf->dki_flags = ID_DKI_FLAGS; break; /* * Return configuration info */ case DKIOCGCONF: (void) strncpy(p.conf->dkc_cname, idcdriver.mdr_cname, DK_DEVLEN); p.conf->dkc_ctype = c->c_ctype; p.conf->dkc_flags = ID_DKI_FLAGS; p.conf->dkc_cnum = c->c_ctlr; p.conf->dkc_addr = i = un->un_ipi_addr; p.conf->dkc_slave = IPI_FAC(i); p.conf->dkc_unit = ID_CUNIT(dev); p.conf->dkc_space = idcinfo[c->c_ctlr]->mc_space; p.conf->dkc_prio = c->c_intpri; p.conf->dkc_vec = 0; (void) strncpy(p.conf->dkc_dname, idcdriver.mdr_dname, DK_DEVLEN); break; /* * Return drive info */ case DKIOCGTYPE: p.typ->dkt_drtype = 0; /* drive type not used */ p.typ->dkt_hsect = un->un_g.dkg_nsect; p.typ->dkt_promrev = 0; p.typ->dkt_drstat = 0; break; /* * Set drive info -- only affects drive type. * This doesn't make sense for IPI disks. The disk * identifies itself. Ignore without returning error. */ case DKIOCSTYPE: break; /* * Return the geometry of the specified unit. */ case DKIOCGGEOM: *p.geom = un->un_g; break; /* * Set the geometry of the specified unit. * Currently only the number of cylinders is expected to change from * what was given in the label or attributes. */ case DKIOCSGEOM: s = spl_ipi(); un->un_g = *p.geom; /* set geometry */ id_update_map(un); /* fix partition table - just in case */ (void) splx(s); break; /* * Return the map for the specified logical partition. */ case DKIOCGPART: *p.map = un->un_lpart[ID_LPART(dev)].un_map; break; /* * Set the map for the specified logical partition. * We raise the priority just to make sure an interrupt * doesn't come in while the map is half updated. */ case DKIOCSPART: s = spl_ipi(); un->un_lpart[ID_LPART(dev)].un_map = *p.map; id_update_map(un); (void) splx(s); break; /* * Return the map for all logical partitions. */ case DKIOCGAPART: for (i = 0; i < ID_NLPART; i++) p.map[i] = un->un_lpart[i].un_map; break; /* * Set the map for all logical partitions. We raise * the priority just to make sure an interrupt doesn't * come in while the map is half updated. */ case DKIOCSAPART: s = spl_ipi(); for (i = 0; i < ID_NLPART; i++) un->un_lpart[i].un_map = p.map[i]; id_update_map(un); (void) splx(s); break; /* * Generic IPI command */ case DKIOCSCMD: err = id_ioctl_cmd(un, p.com); break; /* * Get diagnostics */ case DKIOCGDIAG: *p.diag = un->un_diag; break; /* * Handle Write Check: turn on or off verification (per partition). */ case DKIOCWCHK: if (!suser()) return (u.u_error); if (un->un_lpart[ID_LPART(dev)].un_map.dkl_nblk == 0) return (ENXIO); i = *p.wchk; s = spl_ipi(); *p.wchk = ((un->un_wchkmap & (1<un_wchkmap |= (1 << ID_LPART(dev)); (void) splx(s); id_alloc_wchk(); log(LOG_INFO, "id%3x%c: write check enabled\n", un->un_unit, 'a' + ID_LPART(dev)); } else { un->un_wchkmap &= ~(1 << ID_LPART(dev)); (void) splx(s); log(LOG_INFO, "id%3x%c: write check disabled\n", un->un_unit, 'a' + ID_LPART(dev)); } break; default: err = ENOTTY; break; } return (err); } /* * IPI commands via ioctl interface. */ static int id_ioctl_cmd(un, com) struct id_unit *un; struct dk_cmd *com; { int s; int flags = 0; long ipi_flags; u_char opcode; int opmod; int err = 0; struct buf *bp = NULL; faultcode_t fault_err; if (id_debug) printf("id_ioctl_cmd: cmd %x flags %x blkno %x secnt %x bufaddr %x buflen %x\n", com->dkc_cmd, com->dkc_flags, com->dkc_blkno, com->dkc_secnt, com->dkc_bufaddr, com->dkc_buflen); /* XXX - debug */ opcode = com->dkc_cmd; /* IPI-3 opcode */ opmod = com->dkc_cmd >> 8; /* opcode modifiers */ /* * Check the parameters. */ switch (opcode) { case IP_READ: flags |= B_READ; case IP_WRITE: if (com->dkc_buflen != (com->dkc_secnt << un->un_log_bshift)) return (EINVAL); break; case IP_READ_DEFLIST: flags |= B_READ; /* fallthrough */ case IP_WRITE_DEFLIST: if (com->dkc_buflen == 0) return (EINVAL); break; case IP_FORMAT: case IP_REALLOC: if (com->dkc_buflen != 0) return (EINVAL); break; default: return (EINVAL); } /* * Don't allow more than max at once. */ if (com->dkc_buflen > ID_MAXBUFSIZE) return (EINVAL); s = spl_ipi(); /* * If this command wants exclusive use of the drive, or if there * is another exclusive user, wait for it. */ while (un->un_flags & ID_LOCKED) { un->un_flags |= ID_WANTED; (void) sleep((caddr_t)un, PZERO+1); /* interruptible sleep */ un->un_flags &= ~ID_WANTED; } if (com->dkc_flags & DK_ISOLATE) { un->un_flags |= ID_LOCKED; /* prevent further commands. */ while (un->un_cmd_q != 0) { un->un_flags |= ID_WANTED; (void) sleep((caddr_t)un, PRIBIO); un->un_flags &= ~ID_WANTED; } } /* * If this command moves data, we need to map * the user buffer into DVMA or IPI space. */ if (com->dkc_buflen > 0) { /* * Get a raw I/O buf. */ bp = alloc(struct buf, 1); if (bp == NULL) { err = ENOMEM; goto errout; } bp->b_flags = B_BUSY | B_PHYS | flags; bp->b_un.b_addr = com->dkc_bufaddr; bp->b_bcount = com->dkc_buflen; bp->b_resid = 0; bp->b_blkno = com->dkc_blkno; bp->b_proc = u.u_procp; u.u_procp->p_flag |= SPHYSIO; /* * Fault lock the address range of the buffer. */ fault_err = as_fault(u.u_procp->p_as, bp->b_un.b_addr, (u_int)bp->b_bcount, F_SOFTLOCK, (flags & B_READ) ? S_READ : S_WRITE); if (fault_err) { if (FC_CODE(fault_err) == FC_OBJERR) err = FC_ERRNO(fault_err); else err = EFAULT; } else if (buscheck(bp) < 0) { err = EFAULT; } } /* * Set IPI layer flags. */ ipi_flags = 0; if (com->dkc_flags & DK_DIAGNOSE) ipi_flags |= IP_DIAGNOSE; if (com->dkc_flags & DK_SILENT) ipi_flags |= IP_SILENT; /* * Execute the command. */ if (err == 0) { switch (opcode) { case IP_FORMAT: err = id_format(un, com->dkc_blkno, com->dkc_secnt, opmod); break; case IP_READ: case IP_WRITE: err = id_rdwr(un, bp, com->dkc_blkno, ipi_flags); break; case IP_READ_DEFLIST: case IP_WRITE_DEFLIST: err = id_rdwr_deflist(un, bp, opmod); break; case IP_REALLOC: err = id_realloc(un, com->dkc_blkno, com->dkc_secnt, opmod); break; case IP_ATTRIBUTES: default: err = EINVAL; break; } } /* * Release memory and DVMA resources. */ if (com->dkc_buflen > 0 && fault_err == 0) { (void) as_fault(u.u_procp->p_as, bp->b_un.b_addr, (u_int)bp->b_bcount, F_SOFTUNLOCK, (flags & B_READ) ? S_READ : S_WRITE); } errout: if (err && (com->dkc_flags & DK_DIAGNOSE)) { un->un_diag.dkd_errcmd = com->dkc_cmd; if (bp && (bp->b_flags & B_ERROR)) { un->un_diag.dkd_errsect = bp->b_blkno; un->un_diag.dkd_severe = IDE_SEV(bp->b_error); un->un_diag.dkd_errno = bp->b_error; } else { un->un_diag.dkd_errsect = com->dkc_blkno; un->un_diag.dkd_severe = DK_FATAL; un->un_diag.dkd_errno = IDE_FATAL; } } if (id_debug && err) { /* XXX */ printf("id_ioctl_cmd: id%3x err %d (0x%x)\n", un->un_unit, err, err); } if (bp) kmem_free((caddr_t)bp, sizeof (struct buf)); /* * If we locked it, unlock it. */ if (com->dkc_flags & DK_ISOLATE) { un->un_flags &= ~ID_LOCKED; /* prevent further commands. */ if (un->un_flags & ID_WANTED) wakeup((caddr_t)un); } (void) splx(s); return (err); } /* * This routine verifies that the block read is indeed a disk label. */ static islabel(unit, l) u_int unit; register struct dk_label *l; { if (l->dkl_magic != DKL_MAGIC) return (0); if (!ck_cksum(l)) { printf("id%3x: corrupt label\n", unit); return (0); } return (1); } /* * This routine checks the checksum of the disk label. * It is used by islabel(). */ static ck_cksum(l) register struct dk_label *l; { register short *sp, sum = 0; register short count = sizeof (struct dk_label)/sizeof (short); sp = (short *)l; while (count--) sum ^= *sp++; return (sum ? 0 : 1); } /* * This routine puts the label information into the various parts of * the unit structure. It is always called at disk interrupt priority. */ static uselabel(un, l) register struct id_unit *un; register struct dk_label *l; { register struct dk_geom *g = &un->un_g; int i, intrlv; /* * Print out the disk description. */ if (l->dkl_magic == DKL_MAGIC) printf("id%3x: <%s>\n", un->un_unit, l->dkl_asciilabel); /* * Check the label geometry against the geometry values read * from the physical disk configuration attribute parameter. * If they don't match, take the ones from the label, since they * affect conversion of the partition starting cylinder to block number. */ g->dkg_acyl = g->dkg_pcyl - g->dkg_ncyl; /* for checking */ if (l->dkl_ncyl > g->dkg_ncyl || l->dkl_pcyl > g->dkg_pcyl || l->dkl_acyl < g->dkg_acyl || g->dkg_nhead != l->dkl_nhead || g->dkg_nsect != l->dkl_nsect || g->dkg_rpm != l->dkl_rpm) { printf( "id%3x: warning: label doesn't match IPI attribute info\n", un->un_unit); printf( "id%3x: label ncyl %d pcyl %d acyl %d head %d sect %d rpm %d\n", un->un_unit, l->dkl_ncyl, l->dkl_pcyl, l->dkl_acyl, l->dkl_nhead, l->dkl_nsect, l->dkl_rpm); printf( "id%3x: geom ncyl %d pcyl %d acyl %d head %d sect %d rpm %d\n", un->un_unit, g->dkg_ncyl, g->dkg_pcyl, g->dkg_acyl, g->dkg_nhead, g->dkg_nsect, g->dkg_rpm); } g->dkg_intrlv = l->dkl_intrlv; g->dkg_nhead = l->dkl_nhead; g->dkg_nsect = l->dkl_nsect; g->dkg_rpm = l->dkl_rpm; g->dkg_ncyl = l->dkl_ncyl; g->dkg_pcyl = l->dkl_pcyl; g->dkg_acyl = l->dkl_acyl; /* * Fill in the logical partition map. */ for (i = 0; i < NDKMAP; i++) un->un_lpart[i].un_map = l->dkl_map[i]; id_update_map(un); /* update map with geometry info */ /* * Set up data for iostat. */ if (un->un_dk >= 0) { intrlv = g->dkg_intrlv; if (intrlv <= 0 || intrlv >= g->dkg_nsect) intrlv = 1; dk_bps[un->un_dk] = (60 << un->un_log_bshift) * g->dkg_nsect / intrlv; } un->un_flags |= ID_LABEL_VALID; } /* * Read or write disk data. * * The block number is absolute: no patrition mapping is needed. * * Returns non-zero error number on failure. * Calls iodone on buffer when fininshed. */ /* ARGUSED */ static id_rdwr(un, bp, block, ipi_flags) register struct id_unit *un; /* unit structure pointer */ register struct buf *bp; /* buffer info */ daddr_t block; /* starting physical block number */ long ipi_flags; { struct ipiq *q; int errno = 0; /* error return code */ int nblks; if ((q = ipi_setup(un->un_ipi_addr, bp, un->un_ctlr->c_rw_cmd_len, MAX_RESPLEN, IPI_SLEEP, (int)ipi_flags)) == NULL) return (EIO); nblks = NBLKS(bp->b_bcount, un); q->q_errblk = (long)block; /* * Construct IPI-3 packet. */ id_build_rdwr(q, un, block, nblks); /* * Send command. */ if ((errno = id_send_cmd(q, (long)IP_ABS_BLOCK, ID_ASYNCHWAIT)) == 0 && q->q_result != IP_SUCCESS) { bp->b_blkno = (daddr_t)q->q_errblk; errno = EIO; } ipi_free(q); return (errno); } /* * Format disk. * If first_block is less than zero, format entire disk. */ static id_format(un, first_block, nblocks, opmod) register struct id_unit *un; /* unit structure pointer */ daddr_t first_block; /* first block number */ int nblocks; /* block count, all if < 0 */ int opmod; /* opcode modifier */ { struct ipiq *q; int errno = 0; /* error return code */ int blk_per_cyl; struct ipi3header *ip; int i; char *cp; /* * If a block number range is specified, make sure it is * on a cylinder boundary. */ if (nblocks > 0 || first_block != 0) { blk_per_cyl = un->un_g.dkg_nsect; if ((first_block % blk_per_cyl) || (nblocks % blk_per_cyl)) return (EINVAL); } if ((q = ipi_setup(un->un_ipi_addr, (struct buf *)NULL, sizeof (*ip), MAX_RESPLEN, IPI_SLEEP, 0)) == NULL) return (EIO); /* * Form IPI-3 Command packet. */ ip = q->q_cmd; ip->hdr_opcode = IP_FORMAT; ip->hdr_mods = opmod | IP_OM_BLOCK; cp = (char *)(ip + 1); /* point past header */ /* * Insert block size parameter, if used. */ if ((un->un_ctlr->c_flags & ID_NO_BLK_SIZE) == 0) { for (i = (int)(cp + 2); (i % sizeof (long)) != 0; i++) *cp++ = 0; /* pad */ *cp++ = sizeof (struct bsize_parm) + 1; /* parm + id */ *cp++ = BSIZE_PARM; ((struct bsize_parm *) cp)->blk_size = DEV_BSIZE; cp += sizeof (struct bsize_parm); } /* * Insert extent parameter, if needed. */ if (nblocks > 0 || first_block != 0) { for (i = (int)(cp + 2); (i % sizeof (long)) != 0; i++) *cp++ = 0; /* pad */ *cp++ = sizeof (struct cmdx_parm) + 1; /* parm + id */ *cp++ = CMD_EXTENT; ((struct cmdx_parm *) cp)->cx_count = nblocks; ((struct cmdx_parm *) cp)->cx_addr = first_block; cp += sizeof (struct cmdx_parm); } ip->hdr_pktlen = (cp - (char *)ip) - sizeof (ip->hdr_pktlen); q->q_dev = (caddr_t)un; q->q_time = 0; /* no timeout */ /* * Send command. */ if ((errno = id_send_cmd(q, (long)IP_ABS_BLOCK, ID_ASYNCHWAIT)) == 0) { if (q->q_result != IP_SUCCESS) { printf("id%3x: id_format failed. result %x\n", un->un_unit, q->q_result); errno = EIO; } } ipi_free(q); return (errno); } /* * Read or write defect list. */ static id_rdwr_deflist(un, bp, opmod) register struct id_unit *un; /* unit structure pointer */ register struct buf *bp; /* buffer */ int opmod; /* opcode modifier */ { struct ipiq *q; int errno = 0; /* error return code */ struct ipi3header *ip; int i; char *cp; if ((q = ipi_setup(un->un_ipi_addr, bp, sizeof (*ip), MAX_RESPLEN, IPI_SLEEP, 0)) == NULL) return (EIO); /* * Form IPI-3 Command packet. */ ip = q->q_cmd; ip->hdr_opcode = (bp->b_flags & B_READ) ? IP_READ_DEFLIST : IP_WRITE_DEFLIST; ip->hdr_mods = opmod; cp = (char *)(ip + 1); /* point past header */ /* * Add Transfer notification if required. */ if (q->q_tnp_len == sizeof (caddr_t)) { for (i = (int)(cp + 2); (i % sizeof (caddr_t)) != 0; i++) *cp++ = 0; /* pad */ *cp++ = sizeof (caddr_t) + 1; *cp++ = XFER_NOTIFY; * ((caddr_t *) cp)++ = * (caddr_t *) q->q_tnp; } else if (q->q_tnp_len > 0) { *cp++ = q->q_tnp_len + 1; *cp++ = XFER_NOTIFY; bcopy(q->q_tnp, cp, q->q_tnp_len); cp += q->q_tnp_len; } /* * Insert Request Parameters as Data Parameter. */ *cp++ = 3; /* parameter length */ *cp++ = ATTR_REQPARM; /* request parm parameter */ if (un->un_ctlr->c_ctype == DKC_SUN_IPI1) *cp++ = RESP_AS_NDATA; else *cp++ = RESP_AS_DATA; /* parms as data */ *cp++ = DEFLIST_TRACK; /* track defects list parm */ /* * Add command extent parameter for controllers that need it. */ if (un->un_ctlr->c_ctype == DKC_SUN_IPI1) { for (i = (int)(cp + 2); (i % sizeof (long)) != 0; i++) *cp++ = 0; /* pad */ *cp++ = sizeof (struct cmdx_parm) + 1; /* len including ID */ *cp++ = CMD_EXTENT; ((struct cmdx_parm *) cp)->cx_count = bp->b_bcount; ((struct cmdx_parm *) cp)->cx_addr = 0; cp += sizeof (struct cmdx_parm); } ip->hdr_pktlen = (cp - (char *)ip) - sizeof (ip->hdr_pktlen); q->q_dev = (caddr_t)un; bp->b_resid = bp->b_bcount; /* * Send command. */ if ((errno = id_send_cmd(q, (long)IP_RESP_SUCCESS | IP_ABS_BLOCK | IP_BYTE_EXT, ID_ASYNCHWAIT)) == 0) { if (q->q_result != IP_SUCCESS) { bp->b_blkno = (daddr_t)q->q_errblk; printf("id%3x: id_rdwr_deflist failed. result %x\n", un->un_unit, q->q_result); errno = EIO; } else if (q->q_resp) { /* * Handle response extent or parameter length parameter. * This will set the residual count in the buffer. */ ipi_parse_resp(q, id_deflist_table, (caddr_t)bp); } } ipi_free(q); return (errno); } /* * Handle response extent parameter from read/write defect list. */ /* ARGSUSED */ static int id_respx(q, id, rp, len, bp) struct ipiq *q; /* request */ int id; /* parameter ID - not used */ struct respx_parm *rp; /* parameter */ int len; /* parameter length - not used */ struct buf *bp; /* buffer */ { struct id_unit *un; if (bp != NULL) { q->q_errblk = rp->rx_addr; if (q->q_flag & IP_BYTE_EXT) { bp->b_resid = rp->rx_resid; } else if ((un = (struct id_unit *)q->q_dev) != NULL) { bp->b_resid = rp->rx_resid << un->un_log_bshift; } } return (0); } /* * Handle parameter length parameter from read/write defect list. */ /* ARGSUSED */ static int id_deflist_parmlen(q, id, rp, len, bp) struct ipiq *q; /* request */ int id; /* parameter ID - not used */ struct parmlen_parm *rp; /* parameter */ int len; /* parameter length - not used */ struct buf *bp; /* buffer */ { bp->b_resid = bp->b_bcount - rp->parmlen; return (0); } /* * Reallocate a defective block. */ static id_realloc(un, first_block, nblocks, opmod) register struct id_unit *un; /* unit structure pointer */ daddr_t first_block; /* first block number */ int nblocks; /* block count, all if < 0 */ int opmod; /* opcode modifier */ { struct ipiq *q; int errno = 0; /* error return code */ struct ipi3header *ip; int i; char *cp; if (nblocks <= 0) return (EINVAL); if ((q = ipi_setup(un->un_ipi_addr, (struct buf *)NULL, sizeof (*ip) + sizeof (struct cmdx_parm) + sizeof (long), MAX_RESPLEN, IPI_SLEEP, 0)) == NULL) return (EIO); /* * Form IPI-3 Command packet. */ ip = q->q_cmd; ip->hdr_opcode = IP_REALLOC; ip->hdr_mods = opmod | IP_OM_BLOCK; cp = (char *)(ip + 1); /* point past header */ /* * Insert extent parameter, if needed. */ if (nblocks > 0 || first_block != 0) { for (i = (int)(cp + 2); (i % sizeof (long)) != 0; i++) *cp++ = 0; /* pad */ *cp++ = sizeof (struct cmdx_parm) + 1; /* parm + id */ *cp++ = CMD_EXTENT; ((struct cmdx_parm *) cp)->cx_count = nblocks; ((struct cmdx_parm *) cp)->cx_addr = first_block; cp += sizeof (struct cmdx_parm); } ip->hdr_pktlen = (cp - (char *)ip) - sizeof (ip->hdr_pktlen); q->q_dev = (caddr_t)un; /* * Send command. */ if ((errno = id_send_cmd(q, (long)IP_ABS_BLOCK, ID_ASYNCHWAIT)) == 0) { if (q->q_result != IP_SUCCESS) { printf("id%3x: id_realloc failed. result %x\n", un->un_unit, q->q_result); errno = EIO; } } ipi_free(q); return (errno); } /* * Build attribute command. * Used only during recovery. Leaves the number of parameters read * in c->c_rec_data or un->un_rec_data. (XXX - ugly side-effect). */ static struct ipiq * id_build_attr_cmd(c, un, rt, callback) struct id_ctlr *c; /* controller */ struct id_unit *un; /* unit (may be NULL) */ register struct ipi_resp_table *rt; /* response table */ int (*callback)(); /* callback routine for ipi_setup */ { register struct ipi_resp_table *rtp; /* response table pointer */ struct ipiq *q; int nparms; /* number of parameter IDs */ u_char parms[ID_MAX_PARMS]; u_char *cp; int addr; /* IPI device address */ struct ipi3header *ip; u_char *pp; /* parameter pointer */ int len; /* command length */ cp = parms; nparms = 0; for (rtp = rt; rtp->rt_parm_id != 0 && nparms < c->c_max_parms; rtp++) { if (rtp->rt_parm_id == ATTR_SLVCNF_BIT && (c->c_flags & ID_NO_RECONF)) continue; *cp++ = rtp->rt_parm_id; nparms++; } if (un) { un->un_recover.rec_data = rtp - rt; addr = un->un_ipi_addr; } else { c->c_recover.rec_data = rtp - rt; addr = c->c_ipi_addr; } if (nparms <= 0) return (NULL); /* * Figure length of command packet from table size. * Doing this linearly is very cheap, since the tables are always short. * Length is header + parm len, id, flags, + one for each attribute id. */ len = sizeof (struct ipi3header) + 3 + nparms; if ((q = ipi_setup(addr, (struct buf *)NULL, len, MAX_RESPLEN, callback, 0)) == NULL) return (q); /* * Form IPI-3 Command packet. */ ip = (struct ipi3header *)q->q_cmd; bzero ((caddr_t)ip, (u_int)len); ip->hdr_opcode = IP_ATTRIBUTES; ip->hdr_mods = IP_OM_REPORT; /* * Build request parameters parameter. */ pp = cp = (u_char *)(ip + 1); /* point to start of parameters */ cp++; /* skip parameter length */ *cp++ = ATTR_REQPARM; /* request parm parameter */ *cp++ = RESP_AS_PKT; /* parameters in response */ /* * Insert parameter IDs into request parm parameter. */ bcopy((caddr_t)parms, (caddr_t)cp, (u_int)nparms); cp += nparms; /* * Set parameter length and packet length. */ *pp = (cp - pp) - sizeof (*pp); ip->hdr_pktlen = (cp - (u_char *)ip) - sizeof (ip->hdr_pktlen); if (ip->hdr_pktlen + 2 != len) printf("id_build_attr_cmd: miscalculated len %d pktlen %d", len, ip->hdr_pktlen); q->q_dev = (caddr_t)un; /* may be NULL */ return (q); } /* * Build set attribute command. * Used only during recovery. Leaves the number of parameters read * in c->c_rec_data or un->un_rec_data. (XXX - ugly side-effect). */ static struct ipiq * id_build_set_attr_cmd(c, un, rtp, callback) struct id_ctlr *c; /* controller */ struct id_unit *un; /* unit (may be NULL) */ register struct ipi_resp_table *rtp; /* response table */ int (*callback)(); /* callback routine for ipi_setup */ { struct ipiq *q; u_char *cp; int addr; /* IPI device address */ struct ipi3header *ip; int len; /* command length */ int nparms; /* number of parameters */ caddr_t arg; /* arg to parameter function */ if (rtp->rt_parm_id) nparms = 1; else nparms = 0; if (un) { un->un_recover.rec_data = nparms; addr = un->un_ipi_addr; arg = (caddr_t)un; } else { c->c_recover.rec_data = nparms; addr = c->c_ipi_addr; arg = (caddr_t)c; } if (nparms <= 0) return (NULL); /* * Figure length of command packet from parameter size. * Length is header + parm len, id, flags, + one for each attribute id. */ len = sizeof (struct ipi3header) + 3 + rtp->rt_min_len; if ((q = ipi_setup(addr, (struct buf *)NULL, len, MAX_RESPLEN, callback, 0)) == NULL) return (q); /* * Form IPI-3 Command packet. */ ip = (struct ipi3header *)q->q_cmd; bzero ((caddr_t)ip, (u_int)len); ip->hdr_opcode = IP_ATTRIBUTES; ip->hdr_mods = IP_OM_LOAD; /* * Insert parameter ID into request parm parameter. */ cp = (u_char *)(ip + 1); while (((int)cp + 2) % sizeof (long) != 0) *cp++ = 0; /* pad for alignment */ *cp++ = rtp->rt_min_len + 1; /* length (including ID byte) */ *cp++ = rtp->rt_parm_id; /* parameter ID */ (*rtp->rt_func)(q, rtp->rt_parm_id, cp, rtp->rt_min_len, arg); cp += rtp->rt_min_len; /* * Set parameter length and packet length. */ ip->hdr_pktlen = (cp - (u_char *)ip) - sizeof (ip->hdr_pktlen); q->q_dev = (caddr_t)un; /* may be NULL */ return (q); } /* * This routine prints out an error message with the appropriate device * or controller number and the block number if known. The message may * be NULL, in which case no newline is printed. */ static void id_printerr(q, msg) struct ipiq *q; char *msg; { register struct buf *bp; register struct id_unit *un; register struct opcode_name *np; struct un_lpart *lp; if (q->q_flag & IP_SILENT) return; if ((un = (struct id_unit *)q->q_dev) != NULL) { if ((bp = q->q_buf) != NULL) { if ((un->un_flags & ID_LABEL_VALID) && !(q->q_flag & IP_ABS_BLOCK)) { lp = &un->un_lpart[ID_LPART(bp->b_dev)]; printf("id%3x%c: block %d", un->un_unit, ID_LPART(bp->b_dev) + 'a', q->q_errblk - lp->un_blkno); printf(" (%d abs)", q->q_errblk); } else { printf("id%3x: block %d", un->un_unit, q->q_errblk); } } else { printf("id%3x", un->un_unit); } } else if (q->q_ctlr) { printf("idc%d", ((struct id_ctlr *)(q->q_ctlr))->c_ctlr); } else { printf("id at ipi %x", q->q_csf); } for (np = opcode_name; np->name; np++) { if (np->opcode == q->q_cmd->hdr_opcode) { printf(": %s: ", np->name); break; } } if (np->name == NULL) printf(": op %x: ", q->q_cmd->hdr_opcode); if (msg) printf("%s\n", msg); } #ifdef MULT_MAJOR /* * Establish maps for translating multiple major number into unit number. */ id_major_map_init() { extern int nblkdev, nchrdev; register int i; register int cmajor; register int bmajor; register struct cdevsw *cdp; register struct bdevsw *bdp; static char init_done; if (init_done) return; init_done = 1; for (i = 0; i < NMAJOR; i++) { bmajor_lookup[i] = cmajor_lookup[i] = cmajor_trans[i] = NO_MAJOR; } /* * setup cmajor_lookup[major(dev)] to be set number. */ cmajor = 0; for (cdp = cdevsw, i = 0; i < nchrdev; cdp++, i++) { if (cdp->d_open == idcopen) { cmajor_lookup[i] = cmajor++; } } /* * setup bmajor_lookup[major(dev)] to be set number. */ bmajor = 0; for (bdp = bdevsw, i = 0; i < nblkdev; bdp++, i++) { if (bdp->d_open == idbopen) { bmajor_lookup[i] = bmajor++; } } if (cmajor != bmajor || cmajor <= 0) { printf("id_major_map_init: error: cmajor %d bmajor %d\n", cmajor, bmajor); panic("id_major_map_init: blk/chr major count mismatch"); } /* * setup cmajor_trans[cmajor(i)] to be block major number. */ for (cmajor = 0; cmajor < nchrdev; cmajor++) { if (cmajor_lookup[cmajor] == NO_MAJOR) continue; for (bmajor = 0; bmajor < nblkdev; bmajor++) { if (bmajor_lookup[bmajor] == cmajor_lookup[cmajor]) { cmajor_trans[cmajor] = bmajor; break; } } if (bmajor == nblkdev) { printf("id_major_map_init: major %d\n", cmajor); panic("id_major_map_init: no blk dev for chr dev"); } } /* * check bmajor lookup for completeness. */ for (bmajor = 0; bmajor < nblkdev; bmajor++) { if (bmajor_lookup[bmajor] == NO_MAJOR) continue; for (cmajor = 0; cmajor < nchrdev; cmajor++) { if (bmajor_lookup[bmajor] == cmajor_lookup[cmajor]) { if (cmajor_trans[cmajor] != bmajor) { printf("id_major_map_init: cmaj %d bmaj %d trans %d\n", cmajor, bmajor, cmajor_trans[cmajor]); panic("id_major_map_init: bad trans"); } break; } } if (cmajor == nchrdev) { printf("id_major_map_init: blk major %d ", bmajor); panic("id_major_map_init: no chr dev for blk dev"); } } } #endif /* MULT_MAJOR */ /* * Watchdog for idle controllers or devices that have gone away. */ static void id_watch() { struct id_unit **unp; struct id_unit *un; struct id_ctlr *c; struct ipiq *q; if (id_watch_enable == 0) /* patchable enable/disable */ goto resched; for (unp = id_unit; unp != NULL && unp < &id_unit[nid]; unp++) { if ((un = *unp) == NULL) continue; /* * If the unit isn't idle, assume the command timeout * will trigger if the device goes offline. */ if (un->un_cmd_q > 0) { un->un_idle_time = 0; continue; } /* * If the unit is idle, add the interval since we last * checked to the idle time. This exaggerates a bit. */ un->un_idle_time += ID_WATCH_TIME; /* * If the unit has been idle for too long, issue a REPORT * STATUS command. */ if (un->un_idle_time > ID_MAX_IDLE) { c = un->un_ctlr; if (!IE_STAT_READY(un->un_flags) || !IE_STAT_READY(c->c_flags)) continue; q = ipi_setup(un->un_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), MAX_RESPLEN, IPI_NULL, 0); if (q == NULL) continue; q->q_cmd->hdr_opcode = IP_REPORT_STAT; q->q_cmd->hdr_mods = IP_OM_CONDITION; q->q_cmd->hdr_pktlen = IPI_HDRLEN; q->q_dev = (caddr_t)un; q->q_time = ID_REC_TIMEOUT; un->un_cmd_q++; c->c_cmd_q++; c->c_cmd_q_len += sizeof (struct ipi3header); ipi_start(q, (long)IP_RESP_SUCCESS, id_watch_intr); un->un_idle_time = 0; } } resched: timeout((int (*)())id_watch, (caddr_t)NULL, ID_WATCH_TIME*hz); } /* * Interrupt handler for watchdog. */ static void id_watch_intr(q) struct ipiq *q; { if (q) { id_cmd_intr(q); switch (q->q_result) { case IP_SUCCESS: (void) id_error_parse(q, ipi_cond_table, id_error, (struct id_ctlr *)q->q_ctlr, (struct id_unit *)q->q_dev, IE_FAC); ipi_free(q); break; case IP_MISSING: id_missing_req(q); break; default: ipi_free(q); break; } } } /* * Walk the table for each parameter in the response given. * If any action should be taken, take it by calling the function given. * * Remember, speed is not important here. Robustness and code size are. */ static int id_error_parse(q, err_table, fcn, c, un, flags) register struct ipiq *q; /* request with response */ struct ipi_err_table *err_table; int (*fcn)(); /* function to handle errors */ struct id_ctlr *c; struct id_unit *un; register int flags; /* initial error flags */ { register struct ipi_err_table *etp; /* error table pointer */ register struct ipi3resp *rp; register u_char *cp; struct { /* parameter unpacking buffer */ long align; /* to force alignment */ u_char pad[2]; /* to maintain alignment for buf */ u_char len; /* parameter length */ u_char id; /* parameter ID */ u_char buf[254]; /* rest of parameter (after len/ID) */ } parm; u_char *pp; /* parameter pointer */ int id; /* parameter id */ int plen; /* parameter length */ int rlen; /* response length */ int pflags; /* flags on parameter */ int bflags; /* flags on bit */ int result; /* result for current check */ int dryrun; /* set during dry run */ /* * The table is read twice, once to see if any print flags are set and * again to do the real work. */ dryrun = 1; loop: if ((rp = q->q_resp) == NULL) { id_printerr(q, "id_error_parse: no response where expected"); return (IE_CMD); } rlen = rp->hdr_pktlen + sizeof (rp->hdr_pktlen) - sizeof (struct ipi3resp); cp = (u_char *)rp + sizeof (struct ipi3resp); for (; rlen > 0; cp += plen) { if ((plen = cp[0] + 1) > rlen) break; rlen -= plen; if (plen <= 1) continue; /* padding byte */ /* * Find parameter ID in the table. */ id = cp[1]; pflags = 0; for (etp = err_table; etp->et_mask; etp++) { if (etp->et_byte != 0) continue; if (etp->et_parmid == id) break; if (etp->et_parmid + IPI_FAC_PARM == id) { pflags |= IE_FAC; break; } } /* * If parameter not found in table, skip it. */ if (etp->et_mask == 0) continue; /* * Check (and fix) alignment if the parameter contains more * than just the length and parameter ID. */ pp = cp; if (plen > 2 && (((int)cp + 2) % sizeof (long)) != 0) { pp = &parm.len; bcopy((caddr_t)cp, (caddr_t)pp, (u_int)plen); } /* * Set flags according to flags found in ID entry. */ pflags |= etp->et_flags & ~IE_MASK | flags; flags |= pflags & IE_RESP_MASK; result = etp->et_flags & IE_MASK; if (!dryrun || (pflags & IE_FIRST_PASS)) { if (pflags & (IE_MSG | IE_PRINT)) id_printerr(q, (char *)NULL); /* * run handler for parameter. */ flags |= (*fcn)(q, c, un, result, pflags, etp->et_msg, pp); } /* * Test each condition mentioned in the table until * the next parameter ID entry. */ bflags = 0; /* in case no bit matches */ while ((++etp)->et_byte) { if (etp->et_byte + 1 < plen && (etp->et_mask & cp[etp->et_byte + 1])) { bflags = etp->et_flags & ~IE_MASK | pflags; flags |= bflags & IE_RESP_MASK; result = etp->et_flags & IE_MASK; if (!dryrun) { if ((bflags ^ pflags) & IE_MSG) id_printerr(q, (char *)NULL); /* * run handler for this match. */ flags |= (*fcn)(q, c, un, result, bflags, etp->et_msg, pp); } } } if (!dryrun && ((pflags | bflags) & (IE_MSG | IE_PRINT)) && !(q->q_flag & IP_SILENT)) printf("\n"); } if (dryrun) { dryrun = 0; goto loop; } if ((flags & IE_DUMP) && (!(q->q_flag & IP_SILENT) || id_debug)) { ipi_print_cmd(IPI_CHAN(q->q_csf), q->q_cmd, "command in error"); ipi_print_resp(IPI_CHAN(q->q_csf), rp, "response of error"); } return (flags); } /* * Missing interrupt recovery. * * When an IPI command fails to give a completion response in the allotted * time, this routine is called. * * The recovery method: * * The point of probable failure is determined by attempting to communicate * with the nearest component first and working out on the channel until * the problem area is determined. * * First, a NOP command is sent to the channel adaptor board. If that times * out, the channel board is reset, the controllers reset, and all outstanding * I/O is re-issued. * * XXX - a method to test whether one controller or an entire channel is bad * must be developed. * * If the channel board is OK, a NO-Op command is issued to the particular * slave device. If that times out, the controller is reset and all * outstanding I/O is re-issued. * * If the slave is OK, the command is aborted and re-issued. After a number * of retries, the command is considered to have failed and an error is issued. * * Alternate Reverse order method: * * This is not currently used, but could be a better way of doing things. * * If not an Abort command, issue an Abort command in case the hang up is * in the disk or string controller. Mark the controller so that no other * commands will be issued to the controller until the abort comes back. * * If an Abort command fails or times out, reset the slave. If that times out, * reset the channel and all the slaves on it. If that times out, * reset the entire board (all channels). * * Some controllers may not be able to accept aborts for individual commands. * Handle these by escalating to doing a reset of the controller. * * After abort/reset processing, re-issue any commands that were outstanding * but hadn't completed. * * Always called with interrupts masked. * * If q has IP_SYNC flag set, will not return until recovery completed or * failure has occurred. Otherwise, just starts recovery. The interrupts * generated will continue the recovery. */ static void id_missing_req(q) register struct ipiq *q; { register struct id_ctlr *c; int s; if ((c = (struct id_ctlr *)q->q_ctlr) == NULL) { id_printerr(q, "missing rupt. q_ctlr NULL"); return; } /* * If recovery is already in progress, return. */ if (c->c_flags & IE_RECOVER) { id_printerr(q, "missing interrupt - recovery in progress"); return; } else { id_printerr(q, "missing interrupt - attempting recovery"); } /* * Setup controller in recovery mode. */ c->c_flags |= IE_RECOVER; c->c_flags &= ~IE_TRACE_RECOV; c->c_recover.rec_history = 0; c->c_recover.rec_state = IDR_TEST_BOARD; c->c_recover.rec_substate = 0; c->c_recover.rec_flags = (q->q_flag & IP_SYNC) | IP_NO_RETRY; s = spl_ipi(); (void) id_recover(c, (struct id_unit *)NULL); (void) splx(s); } /* * State transition table for recovery. */ struct id_next_state { char ns_success; /* next state if command successful */ char ns_failed; /* next state if command failed */ char ns_failed_again; /* next state when command fails second time */ char *ns_msg; /* message */ }; /* * Recovery table for controller. */ static struct id_next_state id_next_state[] = { /* present next state next state next state */ /* state on success on failure on 2nd fail */ /* 0 IDR_NORMAL */ IDR_NORMAL, IDR_NORMAL, IDR_NORMAL, "recovery complete", /* * Recovery from missing interrupt. */ /* 1 IDR_TEST_BOARD */ IDR_TEST_CHAN, IDR_RST_BOARD, IDR_REC_FAIL, "test board", /* 2 IDR_RST_BOARD */ IDR_RST_CHAN, IDR_REC_FAIL, IDR_REC_FAIL, "reset board", /* 3 IDR_TEST_CHAN */ IDR_TEST_CTLR, IDR_RST_CHAN, IDR_RST_BOARD, "test chan", /* 4 IDR_RST_CHAN */ IDR_RST_CTLR, IDR_RST_BOARD, IDR_REC_FAIL, "reset chan", /* 5 IDR_TEST_CTLR */ IDR_IDLE_CTLR, IDR_RST_CTLR, IDR_RST_CHAN, "test ctlr", /* 6 IDR_RST_CTLR */ IDR_GET_XFR, IDR_RST_CHAN, IDR_REC_FAIL, "reset ctlr", /* 7 IDR_GET_XFR */ IDR_SET_XFR, IDR_RST_CTLR, IDR_RST_CHAN, "get xfr", /* 8 IDR_SET_XFR */ IDR_STAT_CTLR, IDR_RST_CTLR, IDR_RST_CHAN, "set xfr", /* 9 IDR_STAT_CTLR */ IDR_ID_CTLR, IDR_RST_CTLR, IDR_RST_CHAN, "stat ctlr", /* 10 IDR_ID_CTLR */ IDR_ATTR_CTLR, IDR_RST_CTLR, IDR_RST_CHAN, "id ctlr", /* 11 IDR_ATTR_CTRL */ IDR_SATTR_CTLR1, IDR_RST_CTLR, IDR_RST_CHAN, "attributes", /* 12 IDR_SET_XFR1 */ IDR_RETRY, IDR_RST_CTLR, IDR_RST_CHAN, "set xfr", /* XXX - no path to IDR_ABORT for now (ISP-80 doesn't abort) */ /* 13 IDR_ABORT */ IDR_RETRY, IDR_RST_CTLR, IDR_RST_CHAN, "abort", /* 14 IDR_RETRY */ IDR_NORMAL, IDR_RST_CTLR, IDR_RST_CHAN, "retry", /* 15 IDR_REC_FAIL */ IDR_REC_FAIL, IDR_REC_FAIL, IDR_REC_FAIL, "recovery failed", /* * Controller Re-initialization or initial configuration. */ /* XXX - eventually do more in the failure cases of these. */ /* 16 IDR_RST_CTLR1 */ IDR_GET_XFR1, IDR_REC_FAIL, IDR_REC_FAIL, "reset ctlr", /* 17 IDR_GET_XFR1 */ IDR_SET_XFR2, IDR_REC_FAIL, IDR_REC_FAIL, "get xfr", /* 18 IDR_SET_XFR2 */ IDR_STAT_CTLR1, IDR_REC_FAIL, IDR_REC_FAIL, "set xfr", /* 19 IDR_STAT_CTLR1 */ IDR_ID_CTLR1, IDR_REC_FAIL, IDR_REC_FAIL, "stat ctlr", /* 20 IDR_ID_CTLR1 */ IDR_ATTR_CTLR1, IDR_REC_FAIL, IDR_REC_FAIL, "id ctlr", /* 21 IDR_ATTR_CTRL1 */ IDR_SATTR_CTLR2, IDR_REC_FAIL, IDR_REC_FAIL, "attr ctlr", /* 22 IDR_SET_XFR3 */ IDR_NORMAL, IDR_REC_FAIL, IDR_REC_FAIL, "set xfr", /* * Unit Re-initialization or initial configuration.. */ /* 23 IDR_STAT_UNIT */ IDR_ATTR_UNIT, IDR_UNIT_FAILED, IDR_UNIT_FAILED, "stat unit", /* 24 IDR_ATTR_UNIT */ IDR_READ_LABEL, IDR_UNIT_FAILED, IDR_UNIT_FAILED, "attr unit", /* 25 IDR_READ_LABEL */ IDR_UNIT_NORMAL, IDR_UNIT_FAILED, IDR_UNIT_FAILED, "read label", /* 26 IDR_UNIT_NORMAL */ IDR_UNIT_NORMAL, IDR_UNIT_NORMAL, IDR_UNIT_NORMAL, "unit recovery complete", /* 27 IDR_UNIT_FAILED */ IDR_UNIT_FAILED, IDR_UNIT_FAILED, IDR_UNIT_FAILED, "unit recovery failed", /* * Additional states inserted. */ /* 28 IDR_SATTR_CTLR1 */ IDR_SET_XFR1, IDR_REC_FAIL, IDR_REC_FAIL, "set ctlr attr", /* 29 IDR_SATTR_CTLR2 */ IDR_SET_XFR3, IDR_REC_FAIL, IDR_REC_FAIL, "set ctlr attr", /* 30 IDR_IDLE_CTLR */ IDR_NORMAL, IDR_DUMP_CTLR, IDR_RST_CTLR, "idle ctlr", /* 31 IDR_DUMP_CTLR */ IDR_RST_CTLR, IDR_RST_CTLR, IDR_RST_CTLR, "dump ctlr", }; #define IDR_NSTATES (sizeof (id_next_state) / sizeof (struct id_next_state)) /* * Wrapper for calling id_recover through timeout(). Only one arg. */ static int id_recover_idle(c) struct id_ctlr *c; { int s; s = spl_ipi(); (void) id_recover(c, (struct id_unit *)NULL); (void) splx(s); return (0); } /* * This routine does the real work of recovering a controller. * It gets reinvoked to issue the commands for every stage of recovery * or loops if in synchronous mode. */ static int id_recover(c, un) register struct id_ctlr *c; register struct id_unit *un; { register struct ipiq *q; struct id_rec_state *rp; u_int *flagsp; long ipi_flags; struct buf lbuf; int mode; int state; int next_state; int (*callback)(); int unit; u_char *cp; if (un) { rp = &un->un_recover; flagsp = &un->un_flags; } else { rp = &c->c_recover; flagsp = &c->c_flags; } *flagsp |= IE_RECOVER | IE_RECOVER_ACTV; ipi_flags = rp->rec_flags; /* flags for ipi requests */ callback = (ipi_flags & IP_SYNC) ? IPI_NULL : id_recover_start; if (id_debug) { *flagsp |= IE_TRACE_RECOV; } while (*flagsp & IE_RECOVER) { state = rp->rec_state; if (*flagsp & IE_TRACE_RECOV) { if (un) printf("id%3x: id_recover %d %s\n", un->un_unit, state, id_next_state[state].ns_msg); else printf("idc%d: id_recover %d %s\n", c->c_ctlr, state, id_next_state[state].ns_msg); } next_state = id_next_state[state].ns_success; /* * Build command. */ q = NULL; switch (state) { case IDR_NORMAL: /* recovery complete */ /* * If a re-initialization became necessary during * recovery, switch over to the right state to do it. */ if (c->c_flags & IE_RE_INIT) { c->c_flags &= ~IE_RE_INIT; rp->rec_state = IDR_RST_CTLR1; rp->rec_history = 0; break; } rp->rec_history |= 1 << state; if (c->c_flags & ID_C_PRESENT) printf("idc%d: Recovery complete.\n", c->c_ctlr); /* * If there is no seek algorithm, allow a smaller number * of commands to be sent to the controller so that the * rest can be sorted in the host. */ if (!(c->c_flags & ID_SEEK_ALG)) c->c_max_q = MIN(c->c_max_q, MAX_UNSORTED); c->c_flags &= ~IE_RECOVER; c->c_flags |= ID_C_PRESENT; /* * Call idstart in case I/O was waiting for recovery to * complete. */ for (unit = 0; unit < IDC_NUNIT; unit++) if ((un = c->c_unit[unit]) != NULL) (void) idstart(un); if (!(ipi_flags & IP_SYNC)) wakeup((caddr_t)c); break; case IDR_REC_FAIL: /* recovery failed miserably */ /* * Mark controller unusable. */ if (c->c_flags & ID_C_PRESENT) printf("idc%d: Recovery failed. Controller marked unusable.\n", c->c_ctlr); c->c_flags &= ~(IE_RECOVER | IE_PAV); rp->rec_history |= 1 << state; /* * Issue errors for pending commands. */ while ((q = c->c_retry_q) != NULL) { c->c_retry_q = q->q_next; if ((un = (struct id_unit *)q->q_dev) != NULL) un->un_cmd_q++; c->c_cmd_q++; c->c_cmd_q_len += q->q_cmd->hdr_pktlen + sizeof (u_short); ipi_complete(q, IP_RETRY_FAILED); } if (!(ipi_flags & IP_SYNC)) wakeup((caddr_t)c); break; case IDR_UNIT_NORMAL: /* unit recovery complete */ un->un_flags |= ID_UN_PRESENT; un->un_flags &= ~IE_RECOVER; rp->rec_history |= 1 << state; if ((!q || q->q_result != IP_MISSING) && un->un_lp) { /* * see comment in id_recover_intr() about * reading the label. */ if (islabel(un->un_unit, un->un_lp)) uselabel(un, un->un_lp); kmem_free((caddr_t)un->un_lp, DEV_BSIZE); un->un_lp = NULL; } if (!(ipi_flags & IP_SYNC)) wakeup((caddr_t)un); break; case IDR_UNIT_FAILED: /* * Unit may or may not be usable. The LABEL_VALID * flag and partition sizes will determine whether * it can be opened. Leave it's status alone so it * can be opened for formatting, etc. */ un->un_flags &= ~IE_RECOVER; rp->rec_history |= 1 << state; if ((!q || q->q_result != IP_MISSING) && un->un_lp) { /* * see comment in id_recover_intr() about * reading the label. */ if (islabel(un->un_unit, un->un_lp)) uselabel(un, un->un_lp); kmem_free((caddr_t)un->un_lp, DEV_BSIZE); un->un_lp = NULL; } if (!(ipi_flags & IP_SYNC)) wakeup((caddr_t)un); break; case IDR_TEST_BOARD: /* see if board is present */ q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), 0, callback, 0); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; ipi_test_board(q, ipi_flags, id_recover_intr); break; /* * Unimplemented cases: just go to next step. */ /* XXX - test channel not implemented yet. */ case IDR_TEST_CHAN: rp->rec_history |= 1 << state; rp->rec_state = next_state; rp->rec_substate = 0; break; case IDR_GET_XFR: case IDR_GET_XFR1: c->c_xfer_mode[0] = 0; bzero((caddr_t)&lbuf, sizeof (lbuf)); lbuf.b_flags = B_BUSY | B_READ; lbuf.b_un.b_addr = (caddr_t)c->c_xfer_mode; lbuf.b_bcount = sizeof (c->c_xfer_mode); if ((q = ipi_setup(c->c_ipi_addr, &lbuf, sizeof (struct ipi3header), MAX_RESPLEN, callback, 0)) == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; ipi_get_xfer_mode(q, ipi_flags, id_recover_intr); break; /* * Set transfer settings - use interlocked if possible, * since attributes haven't been read. If not, rate must * not be 20 MB/sec. */ case IDR_SET_XFR: case IDR_SET_XFR2: q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), 0, callback, 0); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; if ((c->c_xfer_mode[0] & IP_INLK_CAP) == 0) mode = IP_STREAM; else mode = 0; ipi_set_xfer_mode(q, ipi_flags, id_recover_intr, mode); break; /* * Set transfer settings - attributes have been read, so * use streaming if possible and proper rate. */ case IDR_SET_XFR1: case IDR_SET_XFR3: q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), 0, callback, 0); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; if ((c->c_xfer_mode[0] & IP_STREAM_CAP)) mode = IP_STREAM; /* XXX - only handles 10 MB/sec now - get attributes later */ else mode = 0; ipi_set_xfer_mode(q, ipi_flags, id_recover_intr, mode); break; case IDR_TEST_CTLR: q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), 0, callback, 0); if (q == NULL) goto setup_failed; q->q_cmd->hdr_opcode = IP_NOP; q->q_cmd->hdr_mods = 0; q->q_cmd->hdr_pktlen = IPI_HDRLEN; q->q_time = ID_REC_TIMEOUT; ipi_start(q, ipi_flags, id_recover_intr); break; case IDR_RST_BOARD: q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), 0, callback, 0); if (q == NULL) goto setup_failed; ipi_reset_board(q, ipi_flags, id_recover_intr); break; case IDR_RST_CHAN: q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), 0, callback, 0); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; ipi_reset_chan(q, ipi_flags, id_recover_intr); break; case IDR_RST_CTLR: case IDR_RST_CTLR1: q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), 0, callback, 0); if (q == NULL) goto setup_failed; q->q_time = ID_RST_TIMEOUT; ipi_reset_slave(q, ipi_flags | IP_LOG_RESET, id_recover_intr); break; case IDR_STAT_CTLR: case IDR_STAT_CTLR1: if ((q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), MAX_RESPLEN, callback, 0)) == NULL) goto setup_failed; q->q_cmd->hdr_opcode = IP_REPORT_STAT; q->q_cmd->hdr_mods = IP_OM_CONDITION; q->q_cmd->hdr_pktlen = IPI_HDRLEN; q->q_time = ID_REC_TIMEOUT; ipi_start(q, ipi_flags | IP_RESP_SUCCESS, id_recover_intr); break; case IDR_STAT_UNIT: if ((q = ipi_setup(un->un_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header), MAX_RESPLEN, callback, 0)) == NULL) goto setup_failed; q->q_cmd->hdr_opcode = IP_REPORT_STAT; q->q_cmd->hdr_mods = IP_OM_CONDITION; q->q_cmd->hdr_pktlen = IPI_HDRLEN; q->q_dev = (caddr_t)un; q->q_time = ID_REC_TIMEOUT; ipi_start(q, ipi_flags | IP_RESP_SUCCESS, id_recover_intr); break; case IDR_ID_CTLR: case IDR_ID_CTLR1: c->c_ctype = DKC_UNKNOWN; q = id_build_attr_cmd(c, (struct id_unit *)NULL, vendor_id_table, callback); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; ipi_start(q, ipi_flags | IP_RESP_SUCCESS, id_recover_intr); break; case IDR_ATTR_CTLR: case IDR_ATTR_CTLR1: q = id_build_attr_cmd(c, (struct id_unit *)NULL, &ctlr_conf_table[rp->rec_substate], callback); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; ipi_start(q, ipi_flags | IP_RESP_SUCCESS, id_recover_intr); break; case IDR_SATTR_CTLR1: case IDR_SATTR_CTLR2: if (c->c_flags & ID_NO_RECONF) { rp->rec_history |= 1 << state; rp->rec_state = next_state; rp->rec_substate = 0; break; } q = id_build_set_attr_cmd(c, (struct id_unit *)NULL, &ctlr_set_conf_table[rp->rec_substate], callback); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; ipi_start(q, ipi_flags | IP_RESP_SUCCESS, id_recover_intr); break; case IDR_ATTR_UNIT: q = id_build_attr_cmd(c, un, &attach_resp_table[rp->rec_substate], callback); if (q == NULL) goto setup_failed; q->q_time = ID_REC_TIMEOUT; ipi_start(q, ipi_flags | IP_RESP_SUCCESS, id_recover_intr); break; case IDR_READ_LABEL: un->un_flags &= ~ID_LABEL_VALID; if (!(un->un_flags & ID_FORMATTED)) { rp->rec_history |= 1 << state; rp->rec_state = next_state; break; } if (un->un_lp == NULL) { id_printerr(q, "id_recover: no memory for label"); /* XXX */ rp->rec_state = IDR_UNIT_FAILED; break; } un->un_lp->dkl_magic = 0; bzero((caddr_t)&lbuf, sizeof (lbuf)); lbuf.b_flags = B_BUSY | B_READ; lbuf.b_un.b_addr = (caddr_t)un->un_lp; lbuf.b_bcount = sizeof (struct dk_label); if ((q = ipi_setup(un->un_ipi_addr, &lbuf, c->c_rw_cmd_len, MAX_RESPLEN, callback, 0)) == NULL) goto setup_failed; id_build_rdwr(q, un, (daddr_t)0, 1); /* 1 block label */ q->q_buf = NULL; /* buffer is local */ ipi_start(q, ipi_flags, id_recover_intr); break; case IDR_RETRY: /* re-issue commands */ while ((q = c->c_retry_q) != NULL) { c->c_retry_q = q->q_next; id_retry(q); } rp->rec_history |= 1 << state; rp->rec_state = next_state; break; case IDR_IDLE_CTLR: /* wait for cmds to finish */ /* * Wait in this state in case the missing interrupt was * because we the controller was just busy with other * commands. No new commands are sent in the hope * that things can get cleaned up. * * rp->rec_substate is time (seconds) in this state. */ if (c->c_cmd_q == 0) { rp->rec_history |= 1 << state; rp->rec_state = next_state; rp->rec_substate = 0; } else if ((ipi_flags & IP_SYNC) == 0 && rp->rec_substate++ < ID_IDLE_TIME) { timeout(id_recover_idle, (caddr_t)c, hz); goto out; /* Think about this. */ } else { /* time is up */ rp->rec_history |= 1 << state; rp->rec_state = id_next_state[state].ns_failed; rp->rec_substate = 0; } break; case IDR_DUMP_CTLR: /* take firmware dump */ if (c->c_ctype != DKC_SUN_IPI1) { rp->rec_history |= 1 << state; rp->rec_state = next_state; break; } printf("idc%d: driver requesting controller dump\n", c->c_ctlr); if ((q = ipi_setup(c->c_ipi_addr, (struct buf *)NULL, sizeof (struct ipi3header) + sizeof (struct ipi_diag_num) + 2, MAX_RESPLEN, callback, 0)) == NULL) goto setup_failed; q->q_cmd->hdr_opcode = IP_DIAG_CTL; q->q_cmd->hdr_mods = 0; cp = (u_char *)(q->q_cmd + 1); /* past header */ *cp++ = sizeof (struct ipi_diag_num)+1; /* parm + id */ *cp++ = IPI_DIAG_NUM; /* parameter ID */ ((struct ipi_diag_num *)cp)->dn_diag_num = IPDN_DUMP; q->q_cmd->hdr_pktlen = cp + sizeof (struct ipi_diag_num) - (u_char *)q->q_cmd - sizeof (u_short); q->q_dev = (caddr_t)un; q->q_time = ID_DUMP_TIMEOUT; ipi_start(q, ipi_flags | IP_SILENT, id_recover_intr); break; default: printf("idc%d: id_recover: bad state %d\n", c->c_ctlr, rp->rec_state); rp->rec_history |= 1 << state; rp->rec_state = IDR_REC_FAIL; break; } /* * Wait for command to complete. * Id_recover_intr() will handle the ending status. */ if (q) { if (ipi_flags & IP_SYNC) { if (ipi_poll(q, (long)ID_POLL_TIMEOUT) == 0) { q->q_result = IP_MISSING; id_recover_intr(q); /* * Clear sync flag so subsequent * controller reset will allow it * to be freed (after IP_RETRY_FAIL). */ q->q_flag &= ~IP_SYNC; } else { ipi_free(q); } } else if (q->q_result == IP_INPROGRESS) { break; /* interrupt routine will restart */ } } } out: *flagsp &= ~IE_RECOVER_ACTV; return (0); setup_failed: if (callback == IPI_NULL) printf("idc%d: synchronous command setup failed\n", c->c_ctlr); *flagsp &= ~IE_RECOVER_ACTV; return (DVMA_RUNOUT); } /* * Called when a controller tried to get recovery started but couldn't get * a request setup. This routine is called through the callback * when a setup becomes possible and should be reattempted. */ /*ARGSUSED*/ static int id_recover_start(arg) caddr_t arg; { register struct id_ctlr *c; struct id_unit *un; int i; int ctlr; int stat; int s; stat = 0; s = spl_ipi(); for (ctlr = 0; ctlr < nidc && stat == 0; ctlr++) { if ((c = id_ctlr[ctlr]) == NULL) continue; if (c->c_flags & IE_RECOVER_ACTV) continue; if (c->c_flags & IE_RECOVER) { stat = id_recover(c, (struct id_unit *)NULL); } else { for (i = 0; i < IDC_NUNIT && stat == 0; i++) { if ((un = c->c_unit[i]) != NULL && un->un_flags & IE_RECOVER) stat = id_recover(c, un); } } } (void) splx(s); return (stat); } /* * Interrupt handler for recovery actions. */ static void id_recover_intr(q) register struct ipiq *q; { register struct id_ctlr *c; register struct id_unit *un; struct id_rec_state *rp; int flags; struct id_next_state *nsp; int state, next_state; int stay_in_state = 0; if (q == NULL) return; c = (struct id_ctlr *)q->q_ctlr; un = (struct id_unit *)q->q_dev; rp = un ? &un->un_recover : &c->c_recover; if ((state = rp->rec_state) >= IDR_NSTATES) { printf("id_recover_intr: bad state %d\n", state); id_printerr(q, "bad recovery state"); return; } switch (q->q_result) { case IP_ERROR: case IP_COMPLETE: if (q->q_resp) { flags = id_error_parse(q, ipi_err_table, id_error, c, un, 0); if ((flags & IE_CMD) == 0) /* non fatal */ q->q_result = IP_SUCCESS; } break; case IP_RETRY_FAILED: /* * When retry has failed, this result means a request * sent by id_recover() was missing and now that the * controller has been reset it can be freed. */ ipi_free(q); return; } switch (state) { case IDR_STAT_CTLR: case IDR_STAT_CTLR1: /* * Evaluate the response from Report Addressee Status. */ if (q->q_result == IP_SUCCESS) (void) id_error_parse(q, ipi_cond_table, id_error, c, (struct id_unit *)NULL, IE_INIT_STAT); else if (q->q_result == IP_MISSING) break; /* * Check flags. If the controller isn't ready, * consider the status operation a failure for the * purposes of selecting the next state. Ignore the * IE_RECOVER and IE_RE_INIT flags for this test. */ if (!IE_STAT_READY(c->c_flags & ~(IE_RECOVER | IE_RE_INIT))) { q->q_result = IP_ERROR; } c->c_flags &= ~IE_INIT_STAT; break; case IDR_STAT_UNIT: /* * Evaluate the response from Report Addressee Status. */ if (q->q_result == IP_SUCCESS) (void) id_error_parse(q, ipi_cond_table, id_error, c, un, IE_FAC); else if (q->q_result == IP_MISSING) break; /* * Check flags. If the unit isn't ready, * consider the status operation a failure for the * purposes of selecting the next state. Ignore the * IE_RECOVER and IE_RE_INIT flags for this test. */ if (!IE_STAT_READY(un->un_flags & ~(IE_RECOVER | IE_RE_INIT))) { q->q_result = IP_ERROR; un->un_flags &= ~ID_UN_PRESENT; } else { un->un_flags |= ID_UN_PRESENT; } un->un_flags &= ~IE_INIT_STAT; break; case IDR_ID_CTLR: case IDR_ID_CTLR1: if (id_print_attr) ipi_print_resp(IPI_CHAN(q->q_csf), q->q_resp, "ctlr id response"); if (q->q_result == IP_SUCCESS) { ipi_parse_resp(q, vendor_id_table, (caddr_t)c); if (c->c_ctype == DKC_UNKNOWN) printf("idc%d: unknown string ctlr type\n", c->c_ctlr); } break; case IDR_ATTR_CTLR: case IDR_ATTR_CTLR1: if (q->q_result == IP_SUCCESS) { if (id_print_attr) ipi_print_resp(IPI_CHAN(q->q_csf), q->q_resp, "ctlr attr response"); ipi_parse_resp(q, &ctlr_conf_table[rp->rec_substate], (caddr_t)c); rp->rec_substate += rp->rec_data; /* * Stay in this state if not done with table. */ stay_in_state = ctlr_conf_table[rp->rec_substate] .rt_parm_id; if (stay_in_state == 0) c->c_flags &= ~IE_RE_INIT; } break; case IDR_ATTR_UNIT: if (q->q_result == IP_SUCCESS) { if (id_print_attr) ipi_print_resp(IPI_CHAN(q->q_csf), q->q_resp, "unit attr response"); ipi_parse_resp(q, &attach_resp_table[rp->rec_substate], (caddr_t)un); rp->rec_substate += rp->rec_data; /* * Stay in this state if not done with table. */ stay_in_state = attach_resp_table[rp->rec_substate].rt_parm_id; } break; case IDR_RETRY: if (c->c_retry_q) stay_in_state = 1; break; case IDR_READ_LABEL: /* * We used to check the label here, but that has the * unfortunate consequence of examing data returned by * dvma before the dvma mappings are released, but more * importantly before the i/o cache has been flushed. * In certain cases islabel() would determine the label * bad while it was simply that not all the bytes of the * label had made it to memory yet. Instead of checking here, * we check in the IDR_UNIT_NORMAL and IDR_UNIT_FAILED * cases in id_recover() just before kmem_free() is called * to release the buffer used for the label. By that point * the i/o cache has been flushed (indirectly by ipi_free() * after the IDR_READ_LABEL case in id_recover()). */ /* if (q->q_result==IP_SUCCESS && islabel(un->un_unit, un->un_lp)) uselabel(un, un->un_lp); */ break; } nsp = &id_next_state[state]; if (!stay_in_state) { if (q->q_result == IP_SUCCESS) next_state = nsp->ns_success; else if (rp->rec_history & (1 << state)) next_state = nsp->ns_failed_again; else next_state = nsp->ns_failed; rp->rec_state = next_state; rp->rec_history |= (1 << state); rp->rec_substate = 0; rp->rec_data = 0; } else { next_state = state; } if (un && (un->un_flags & IE_TRACE_RECOV)) { printf("id%3x: id_recover_intr %d %s result %x\n", un->un_unit, state, nsp->ns_msg, q->q_result); } else if (c->c_flags & IE_TRACE_RECOV) { printf("idc%d: id_recover_intr %d %s result %x\n", c->c_ctlr, state, nsp->ns_msg, q->q_result); } if (!(q->q_flag & IP_SYNC)) { if (q->q_result != IP_MISSING) ipi_free(q); /* return ipiq to free list */ if (((un ? un->un_flags : c->c_flags) & IE_RECOVER_ACTV) == 0) /* run next step of recovery */ (void) id_recover(c, un); } } /* * XXX - recovery should be hierarchical. Find a way to let the * channel driver do its own recovery depending on it's own capabilities. */ /* * Recover controller. * Called by IPI layer when the channel has been reset and a controller * reset is required. * * The channel may have been reset because a controller on the channel * was having problems, or because another channel on the same board * needed recovery and the entire board was reset. * * It may be possible to do a physical-only reset, allowing commands * already in progress to be retained. However, if the entire board * was reset, a physical and logical reset must be performed, otherwise * the channel board state will not be consistent with the requests * outstanding. */ static void id_recover_ctlr(c, code) struct id_ctlr *c; int code; /* XXX - may indicate action eventually */ { printf("idc%d: id_recover_ctlr\n", c->c_ctlr); switch (code) { case IPR_REINIT: case IPR_RESET: c->c_flags |= IE_RE_INIT; c->c_flags &= ~ID_C_PRESENT; break; default: printf("idc%d: id_recover_ctlr: bad case %x\n", c->c_ctlr, code); break; } } static void id_retry(q) struct ipiq *q; { struct id_unit *un; struct id_ctlr *c; struct buf *bp; if ((un = (struct id_unit *)q->q_dev) != NULL) { un->un_cmd_q++; } if ((c = (struct id_ctlr *)q->q_ctlr) != NULL) { c->c_cmd_q++; c->c_cmd_q_len += q->q_cmd->hdr_pktlen + sizeof (u_short); } /* * Reset residue and error block number, if appropriate. */ if ((bp = q->q_buf) != NULL) { q->q_errblk = (int) (dkblock(bp) + un->un_lpart[ID_LPART(bp->b_dev)].un_blkno); bp->b_resid = bp->b_bcount - q->q_dma_len; } ipi_retry(q); } /* * Queue a q for eventual or immediate retry. */ static void id_q_retry(q) struct ipiq *q; { struct id_ctlr *c; if ((c = (struct id_ctlr *)q->q_ctlr) && (c->c_flags & (IE_RECOVER | IE_RE_INIT))) { q->q_next = c->c_retry_q; c->c_retry_q = q; } else { id_retry(q); } } /* * Reallocate kmem_alloc-ed area. * Only useful if newsize larger than oldsize. * Works even if oldsize is zero. * Returns non-zero on error, leaving old area allocated. */ static int id_kmem_realloc(pp, oldsize, newsize) caddr_t *pp; /* pointer to area pointer */ u_int oldsize; /* length of old area */ u_int newsize; /* length of new area */ { caddr_t new; caddr_t old; if (newsize < oldsize || (new = kmem_alloc(newsize)) == NULL) return (-1); old = *pp; bcopy(old, new, oldsize); bzero(new + oldsize, newsize - oldsize); *pp = new; kmem_free(old, oldsize); return (0); } /* * Update starting block numbers for each of the partitions. * This saves converting a cylinder offset to a block offset on each command. * Must be called when changing partition map or geometry. */ static void id_update_map(un) struct id_unit *un; { struct un_lpart *lp; for (lp = un->un_lpart; lp < &un->un_lpart[NDKMAP]; lp++) { lp->un_blkno = (lp->un_map.dkl_cylno + un->un_g.dkg_bcyl) * un->un_g.dkg_nhead * un->un_g.dkg_nsect + un->un_first_block; } } /* * Allocate and initialize read-back buffer for write checking. */ static void id_alloc_wchk() { register struct buf *bp; if (id_wchk_buf == NULL) { bp = alloc(struct buf, 1); id_wchk_buf = bp; bp->b_un.b_addr = (caddr_t)kmem_alloc(ID_WCHK_BUFL); bp->b_flags = B_BUSY | B_PHYS | B_READ; bp->b_bcount = ID_WCHK_BUFL; } } /* * Do read-back after write. * Called with write request after it has completed. * The read-back request is all set up and ready to go. */ static void id_readback(q) struct ipiq *q; { register struct id_unit *un = (struct id_unit *)q->q_dev; register struct id_ctlr *c = un->un_ctlr; int dk; struct ipiq *rbq; /* read-back request */ rbq = q_related(q); /* * Update counts of outstanding commands. */ un->un_cmd_q++; c->c_cmd_q++; c->c_cmd_q_len += c->c_rw_cmd_len; rbq->q_buf = NULL; /* so rupt handler doesn't mess with buf */ /* * Send command. * If the WCHK_READ flag is already on, do a retry, since the command * has been previously issued. Otherwise, do a start. */ if (rbq->q_flag & IP_WCHK_READ) { id_q_retry(rbq); } else { rbq->q_flag |= IP_WCHK_READ; ipi_start(rbq, (long)0, idintr); } /* * Update iostat statistics. */ if ((dk = un->un_dk) >= 0) { dk_busy |= 1 << dk; dk_seek[dk]++; dk_xfer[dk]++; dk_read[dk]++; dk_wds[dk] += q->q_buf->b_bcount >> 6; } } /* Following code is included from SunDBE */ /* #ifdef SUNDBE */ static int idcstrategy(bp) struct buf *bp; { bp->b_dev = ID_BLKDEV(bp->b_dev); return idstrategy(bp); } int (*id_get_strategy(major_dev))() { if (major_dev >= nchrdev || cdevsw[major_dev].d_open != idcopen) return NULL; return idcstrategy; } /* #endif SUNDBE */