diff --git a/sys/cam/ata/ata_all.h b/sys/cam/ata/ata_all.h index 924fdfe5a6d3..848a8fc2f9db 100644 --- a/sys/cam/ata/ata_all.h +++ b/sys/cam/ata/ata_all.h @@ -35,6 +35,7 @@ struct ccb_ataio; struct cam_periph; union ccb; +#define SID_AEN 0x04 /* Abuse inq_flags bit to track enabled AEN. */ #define SID_DMA 0x10 /* Abuse inq_flags bit to track enabled DMA. */ struct ata_cmd { diff --git a/sys/cam/ata/ata_xpt.c b/sys/cam/ata/ata_xpt.c index 20f22ebad734..91f985da23d8 100644 --- a/sys/cam/ata/ata_xpt.c +++ b/sys/cam/ata/ata_xpt.c @@ -468,6 +468,12 @@ negotiate: 0, 0x02); break; case PROBE_SETAN: + /* Remember what transport thinks about AEN. */ + if (softc->caps & CTS_SATA_CAPS_H_AN) + path->device->inq_flags |= SID_AEN; + else + path->device->inq_flags &= ~SID_AEN; + xpt_async(AC_GETDEV_CHANGED, path, NULL); cam_fill_ataio(ataio, 1, probedone, @@ -1154,6 +1160,12 @@ notsata: cts.xport_specific.sata.valid = CTS_SATA_VALID_CAPS; xpt_action((union ccb *)&cts); softc->caps = caps; + /* Remember what transport thinks about AEN. */ + if (softc->caps & CTS_SATA_CAPS_H_AN) + path->device->inq_flags |= SID_AEN; + else + path->device->inq_flags &= ~SID_AEN; + xpt_async(AC_GETDEV_CHANGED, path, NULL); if (periph->path->device->flags & CAM_DEV_UNCONFIGURED) { path->device->flags &= ~CAM_DEV_UNCONFIGURED; xpt_acquire_device(path->device); diff --git a/sys/cam/cam_ccb.h b/sys/cam/cam_ccb.h index 52d7496fb9db..1f12d91ba26b 100644 --- a/sys/cam/cam_ccb.h +++ b/sys/cam/cam_ccb.h @@ -755,6 +755,7 @@ struct ccb_relsim { * Definitions for the asynchronous callback CCB fields. */ typedef enum { + AC_UNIT_ATTENTION = 0x4000,/* Device reported UNIT ATTENTION */ AC_ADVINFO_CHANGED = 0x2000,/* Advance info might have changes */ AC_CONTRACT = 0x1000,/* A contractual callback */ AC_GETDEV_CHANGED = 0x800,/* Getdev info might have changed */ diff --git a/sys/cam/cam_periph.c b/sys/cam/cam_periph.c index d947732c30f2..4ebf84c5d986 100644 --- a/sys/cam/cam_periph.c +++ b/sys/cam/cam_periph.c @@ -1593,6 +1593,7 @@ cam_periph_error(union ccb *ccb, cam_flags camflags, const char *action_string; cam_status status; int frozen, error, openings, print, lost_device; + int error_code, sense_key, asc, ascq; u_int32_t relsim_flags, timeout; print = 1; @@ -1759,6 +1760,12 @@ cam_periph_error(union ccb *ccb, cam_flags camflags, xpt_async(AC_LOST_DEVICE, newpath, NULL); xpt_free_path(newpath); } + + /* Broadcast UNIT ATTENTIONs to all periphs. */ + } else if (scsi_extract_sense_ccb(ccb, + &error_code, &sense_key, &asc, &ascq) && + sense_key == SSD_KEY_UNIT_ATTENTION) { + xpt_async(AC_UNIT_ATTENTION, orig_ccb->ccb_h.path, orig_ccb); } /* Attempt a retry */ diff --git a/sys/cam/cam_xpt.c b/sys/cam/cam_xpt.c index 3065c12df521..4a174d76c93e 100644 --- a/sys/cam/cam_xpt.c +++ b/sys/cam/cam_xpt.c @@ -4055,6 +4055,7 @@ xpt_async_string(u_int32_t async_code) case AC_GETDEV_CHANGED: return ("AC_GETDEV_CHANGED"); case AC_CONTRACT: return ("AC_CONTRACT"); case AC_ADVINFO_CHANGED: return ("AC_ADVINFO_CHANGED"); + case AC_UNIT_ATTENTION: return ("AC_UNIT_ATTENTION"); } return ("AC_UNKNOWN"); } diff --git a/sys/cam/scsi/scsi_cd.c b/sys/cam/scsi/scsi_cd.c index 7b24ddb9573f..c102c6046171 100644 --- a/sys/cam/scsi/scsi_cd.c +++ b/sys/cam/scsi/scsi_cd.c @@ -97,6 +97,7 @@ typedef enum { CD_FLAG_NEW_DISC = 0x0002, CD_FLAG_DISC_LOCKED = 0x0004, CD_FLAG_DISC_REMOVABLE = 0x0008, + CD_FLAG_SAW_MEDIA = 0x0010, CD_FLAG_CHANGER = 0x0040, CD_FLAG_ACTIVE = 0x0080, CD_FLAG_SCHED_ON_COMP = 0x0100, @@ -110,6 +111,7 @@ typedef enum { CD_CCB_PROBE = 0x01, CD_CCB_BUFFER_IO = 0x02, CD_CCB_WAITING = 0x03, + CD_CCB_TUR = 0x04, CD_CCB_TYPE_MASK = 0x0F, CD_CCB_RETRY_UA = 0x10 } cd_ccb_state; @@ -154,12 +156,14 @@ struct cd_softc { struct cam_periph *periph; int minimum_command_size; int outstanding_cmds; + int tur; struct task sysctl_task; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; STAILQ_HEAD(, cd_mode_params) mode_queue; struct cd_tocdata toc; struct disk *disk; + struct callout mediapoll_c; }; struct cd_page_sizes { @@ -281,6 +285,7 @@ static int cdsendkey(struct cam_periph *periph, struct dvd_authinfo *authinfo); static int cdreaddvdstructure(struct cam_periph *periph, struct dvd_struct *dvdstruct); +static timeout_t cdmediapoll; static struct periph_driver cddriver = { @@ -290,6 +295,9 @@ static struct periph_driver cddriver = PERIPHDRIVER_DECLARE(cd, cddriver); +#ifndef CD_DEFAULT_POLL_PERIOD +#define CD_DEFAULT_POLL_PERIOD 3 +#endif #ifndef CD_DEFAULT_RETRY #define CD_DEFAULT_RETRY 4 #endif @@ -303,6 +311,7 @@ PERIPHDRIVER_DECLARE(cd, cddriver); #define CHANGER_MAX_BUSY_SECONDS 15 #endif +static int cd_poll_period = CD_DEFAULT_POLL_PERIOD; static int cd_retry_count = CD_DEFAULT_RETRY; static int cd_timeout = CD_DEFAULT_TIMEOUT; static int changer_min_busy_seconds = CHANGER_MIN_BUSY_SECONDS; @@ -311,6 +320,9 @@ static int changer_max_busy_seconds = CHANGER_MAX_BUSY_SECONDS; static SYSCTL_NODE(_kern_cam, OID_AUTO, cd, CTLFLAG_RD, 0, "CAM CDROM driver"); static SYSCTL_NODE(_kern_cam_cd, OID_AUTO, changer, CTLFLAG_RD, 0, "CD Changer"); +SYSCTL_INT(_kern_cam_cd, OID_AUTO, poll_period, CTLFLAG_RW, + &cd_poll_period, 0, "Media polling period in seconds"); +TUNABLE_INT("kern.cam.cd.poll_period", &cd_poll_period); SYSCTL_INT(_kern_cam_cd, OID_AUTO, retry_count, CTLFLAG_RW, &cd_retry_count, 0, "Normal I/O retry count"); TUNABLE_INT("kern.cam.cd.retry_count", &cd_retry_count); @@ -494,6 +506,7 @@ cdcleanup(struct cam_periph *periph) xpt_print(periph->path, "can't remove sysctl context\n"); } + callout_drain(&softc->mediapoll_c); disk_destroy(softc->disk); free(softc, M_DEVBUF); cam_periph_lock(periph); @@ -504,6 +517,7 @@ cdasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; + struct cd_softc *softc; periph = (struct cam_periph *)callback_arg; switch (code) { @@ -541,10 +555,39 @@ cdasync(void *callback_arg, u_int32_t code, break; } + case AC_UNIT_ATTENTION: + { + union ccb *ccb; + int error_code, sense_key, asc, ascq; + + softc = (struct cd_softc *)periph->softc; + ccb = (union ccb *)arg; + + /* + * Handle all media change UNIT ATTENTIONs except + * our own, as they will be handled by cderror(). + */ + if (xpt_path_periph(ccb->ccb_h.path) != periph && + scsi_extract_sense_ccb(ccb, + &error_code, &sense_key, &asc, &ascq)) { + if (asc == 0x28 && ascq == 0x00) + disk_media_changed(softc->disk, M_NOWAIT); + } + cam_periph_async(periph, code, path, arg); + break; + } + case AC_SCSI_AEN: + softc = (struct cd_softc *)periph->softc; + if (softc->state == CD_STATE_NORMAL && !softc->tur) { + if (cam_periph_acquire(periph) == CAM_REQ_CMP) { + softc->tur = 1; + xpt_schedule(periph, CAM_PRIORITY_DEV); + } + } + /* FALLTHROUGH */ case AC_SENT_BDR: case AC_BUS_RESET: { - struct cd_softc *softc; struct ccb_hdr *ccbh; softc = (struct cd_softc *)periph->softc; @@ -788,8 +831,8 @@ cdregister(struct cam_periph *periph, void *arg) * Add an async callback so that we get * notified if this device goes away. */ - xpt_register_async(AC_SENT_BDR | AC_BUS_RESET | AC_LOST_DEVICE, - cdasync, periph, periph->path); + xpt_register_async(AC_SENT_BDR | AC_BUS_RESET | AC_LOST_DEVICE | + AC_SCSI_AEN | AC_UNIT_ATTENTION, cdasync, periph, periph->path); /* * If the target lun is greater than 0, we most likely have a CD @@ -1005,6 +1048,17 @@ cdregister(struct cam_periph *periph, void *arg) } } + /* + * Schedule a periodic media polling events. + */ + callout_init_mtx(&softc->mediapoll_c, periph->sim->mtx, 0); + if ((softc->flags & CD_FLAG_DISC_REMOVABLE) && + (softc->flags & CD_FLAG_CHANGER) == 0 && + (cgd->inq_flags & SID_AEN) == 0 && + cd_poll_period != 0) + callout_reset(&softc->mediapoll_c, cd_poll_period * hz, + cdmediapoll, periph); + cdregisterexit: if ((softc->flags & CD_FLAG_CHANGER) == 0) @@ -1500,8 +1554,25 @@ cdstart(struct cam_periph *periph, union ccb *start_ccb) periph->immediate_priority = CAM_PRIORITY_NONE; wakeup(&periph->ccb_list); } else if (bp == NULL) { - xpt_release_ccb(start_ccb); + if (softc->tur) { + softc->tur = 0; + csio = &start_ccb->csio; + scsi_test_unit_ready(csio, + /*retries*/ cd_retry_count, + cddone, + MSG_SIMPLE_Q_TAG, + SSD_FULL_SIZE, + cd_timeout); + start_ccb->ccb_h.ccb_bp = NULL; + start_ccb->ccb_h.ccb_state = CD_CCB_TUR; + xpt_action(start_ccb); + } else + xpt_release_ccb(start_ccb); } else { + if (softc->tur) { + softc->tur = 0; + cam_periph_release_locked(periph); + } bioq_remove(&softc->bio_queue, bp); scsi_read_write(&start_ccb->csio, @@ -1545,7 +1616,7 @@ cdstart(struct cam_periph *periph, union ccb *start_ccb) xpt_action(start_ccb); } - if (bp != NULL) { + if (bp != NULL || softc->tur) { /* Have more work to do, so ensure we stay scheduled */ xpt_schedule(periph, CAM_PRIORITY_NORMAL); } @@ -1840,6 +1911,25 @@ cddone(struct cam_periph *periph, union ccb *done_ccb) wakeup(&done_ccb->ccb_h.cbfcnp); return; } + case CD_CCB_TUR: + { + if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + + if (cderror(done_ccb, CAM_RETRY_SELTO, + SF_RETRY_UA | SF_NO_RECOVERY | SF_NO_PRINT) == + ERESTART) + return; + if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) + cam_release_devq(done_ccb->ccb_h.path, + /*relsim_flags*/0, + /*reduction*/0, + /*timeout*/0, + /*getcount_only*/0); + } + xpt_release_ccb(done_ccb); + cam_periph_release_locked(periph); + return; + } default: break; } @@ -2830,7 +2920,7 @@ cdcheckmedia(struct cam_periph *periph) cdprevent(periph, PR_ALLOW); return (error); } else { - softc->flags |= CD_FLAG_VALID_MEDIA; + softc->flags |= CD_FLAG_SAW_MEDIA | CD_FLAG_VALID_MEDIA; softc->disk->d_sectorsize = softc->params.blksize; softc->disk->d_mediasize = (off_t)softc->params.blksize * softc->params.disksize; @@ -3175,6 +3265,14 @@ cderror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) &error_code, &sense_key, &asc, &ascq)) { if (sense_key == SSD_KEY_ILLEGAL_REQUEST) error = cd6byteworkaround(ccb); + else if (sense_key == SSD_KEY_UNIT_ATTENTION && + asc == 0x28 && ascq == 0x00) + disk_media_changed(softc->disk, M_NOWAIT); + else if (sense_key == SSD_KEY_NOT_READY && + asc == 0x3a && (softc->flags & CD_FLAG_SAW_MEDIA)) { + softc->flags &= ~CD_FLAG_SAW_MEDIA; + disk_media_gone(softc->disk, M_NOWAIT); + } } if (error == ERESTART) @@ -3190,6 +3288,26 @@ cderror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) &softc->saved_ccb)); } +static void +cdmediapoll(void *arg) +{ + struct cam_periph *periph = arg; + struct cd_softc *softc = periph->softc; + + if (softc->flags & CD_FLAG_CHANGER) + return; + + if (softc->state == CD_STATE_NORMAL && !softc->tur) { + if (cam_periph_acquire(periph) == CAM_REQ_CMP) { + softc->tur = 1; + xpt_schedule(periph, CAM_PRIORITY_DEV); + } + } + /* Queue us up again */ + if (cd_poll_period != 0) + callout_schedule(&softc->mediapoll_c, cd_poll_period * hz); +} + /* * Read table of contents */ diff --git a/sys/cam/scsi/scsi_da.c b/sys/cam/scsi/scsi_da.c index 5c82f323c5c7..3253e02f979b 100644 --- a/sys/cam/scsi/scsi_da.c +++ b/sys/cam/scsi/scsi_da.c @@ -77,6 +77,7 @@ typedef enum { DA_FLAG_NEW_PACK = 0x002, DA_FLAG_PACK_LOCKED = 0x004, DA_FLAG_PACK_REMOVABLE = 0x008, + DA_FLAG_SAW_MEDIA = 0x010, DA_FLAG_NEED_OTAG = 0x020, DA_FLAG_WENT_IDLE = 0x040, DA_FLAG_RETRY_UA = 0x080, @@ -101,6 +102,7 @@ typedef enum { DA_CCB_WAITING = 0x04, DA_CCB_DUMP = 0x05, DA_CCB_DELETE = 0x06, + DA_CCB_TUR = 0x07, DA_CCB_TYPE_MASK = 0x0F, DA_CCB_RETRY_UA = 0x10 } da_ccb_state; @@ -150,6 +152,7 @@ struct da_softc { int unmap_max_ranges; int unmap_max_lba; int delete_running; + int tur; da_delete_methods delete_method; struct disk_params params; struct disk *disk; @@ -161,6 +164,7 @@ struct da_softc { uint64_t wwpn; uint8_t unmap_buf[UNMAP_MAX_RANGES * 16 + 8]; struct scsi_read_capacity_data_long rcaplong; + struct callout mediapoll_c; }; struct da_quirk_entry { @@ -857,6 +861,11 @@ static void dasetgeom(struct cam_periph *periph, uint32_t block_len, size_t rcap_size); static timeout_t dasendorderedtag; static void dashutdown(void *arg, int howto); +static timeout_t damediapoll; + +#ifndef DA_DEFAULT_POLL_PERIOD +#define DA_DEFAULT_POLL_PERIOD 3 +#endif #ifndef DA_DEFAULT_TIMEOUT #define DA_DEFAULT_TIMEOUT 60 /* Timeout in seconds */ @@ -871,12 +880,16 @@ static void dashutdown(void *arg, int howto); #endif +static int da_poll_period = DA_DEFAULT_POLL_PERIOD; static int da_retry_count = DA_DEFAULT_RETRY; static int da_default_timeout = DA_DEFAULT_TIMEOUT; static int da_send_ordered = DA_DEFAULT_SEND_ORDERED; static SYSCTL_NODE(_kern_cam, OID_AUTO, da, CTLFLAG_RD, 0, "CAM Direct Access Disk driver"); +SYSCTL_INT(_kern_cam_da, OID_AUTO, poll_period, CTLFLAG_RW, + &da_poll_period, 0, "Media polling period in seconds"); +TUNABLE_INT("kern.cam.da.poll_period", &da_poll_period); SYSCTL_INT(_kern_cam_da, OID_AUTO, retry_count, CTLFLAG_RW, &da_retry_count, 0, "Normal I/O retry count"); TUNABLE_INT("kern.cam.da.retry_count", &da_retry_count); @@ -966,6 +979,9 @@ daopen(struct disk *dp) (softc->quirks & DA_Q_NO_PREVENT) == 0) daprevent(periph, PR_PREVENT); + if (error == 0) + softc->flags |= DA_FLAG_SAW_MEDIA; + cam_periph_unhold(periph); cam_periph_unlock(periph); @@ -1050,7 +1066,8 @@ daschedule(struct cam_periph *periph) /* Check if we have more work to do. */ if (bioq_first(&softc->bio_queue) || - (!softc->delete_running && bioq_first(&softc->delete_queue))) { + (!softc->delete_running && bioq_first(&softc->delete_queue)) || + softc->tur) { prio = CAM_PRIORITY_NORMAL; } @@ -1297,6 +1314,7 @@ dacleanup(struct cam_periph *periph) xpt_print(periph->path, "can't remove sysctl context\n"); } + callout_drain(&softc->mediapoll_c); disk_destroy(softc->disk); callout_drain(&softc->sendordered_c); free(softc, M_DEVBUF); @@ -1308,6 +1326,7 @@ daasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; + struct da_softc *softc; periph = (struct cam_periph *)callback_arg; switch (code) { @@ -1359,10 +1378,43 @@ daasync(void *callback_arg, u_int32_t code, } break; } + case AC_UNIT_ATTENTION: + { + union ccb *ccb; + int error_code, sense_key, asc, ascq; + + softc = (struct da_softc *)periph->softc; + ccb = (union ccb *)arg; + + /* + * Handle all UNIT ATTENTIONs except our own, + * as they will be handled by daerror(). + */ + if (xpt_path_periph(ccb->ccb_h.path) != periph && + scsi_extract_sense_ccb(ccb, + &error_code, &sense_key, &asc, &ascq)) { + if (asc == 0x2A && ascq == 0x09) { + xpt_print(ccb->ccb_h.path, + "capacity data has changed\n"); + dareprobe(periph); + } else if (asc == 0x28 && ascq == 0x00) + disk_media_changed(softc->disk, M_NOWAIT); + } + cam_periph_async(periph, code, path, arg); + break; + } + case AC_SCSI_AEN: + softc = (struct da_softc *)periph->softc; + if (softc->state == DA_STATE_NORMAL && !softc->tur) { + if (cam_periph_acquire(periph) == CAM_REQ_CMP) { + softc->tur = 1; + xpt_schedule(periph, CAM_PRIORITY_DEV); + } + } + /* FALLTHROUGH */ case AC_SENT_BDR: case AC_BUS_RESET: { - struct da_softc *softc; struct ccb_hdr *ccbh; softc = (struct da_softc *)periph->softc; @@ -1698,9 +1750,9 @@ daregister(struct cam_periph *periph, void *arg) * fine without them and the only alternative * would be to not attach the device on failure. */ - xpt_register_async(AC_SENT_BDR | AC_BUS_RESET - | AC_LOST_DEVICE | AC_ADVINFO_CHANGED, - daasync, periph, periph->path); + xpt_register_async(AC_SENT_BDR | AC_BUS_RESET | AC_LOST_DEVICE | + AC_ADVINFO_CHANGED | AC_SCSI_AEN | AC_UNIT_ATTENTION, + daasync, periph, periph->path); /* * Emit an attribute changed notification just in case @@ -1710,6 +1762,16 @@ daregister(struct cam_periph *periph, void *arg) */ disk_attr_changed(softc->disk, "GEOM::physpath", M_NOWAIT); + /* + * Schedule a periodic media polling events. + */ + callout_init_mtx(&softc->mediapoll_c, periph->sim->mtx, 0); + if ((softc->flags & DA_FLAG_PACK_REMOVABLE) && + (cgd->inq_flags & SID_AEN) == 0 && + da_poll_period != 0) + callout_reset(&softc->mediapoll_c, da_poll_period * hz, + damediapoll, periph); + xpt_schedule(periph, CAM_PRIORITY_DEV); return(CAM_REQ_CMP); @@ -1847,9 +1909,25 @@ dastart(struct cam_periph *periph, union ccb *start_ccb) /* Run regular command. */ bp = bioq_takefirst(&softc->bio_queue); if (bp == NULL) { - xpt_release_ccb(start_ccb); + if (softc->tur) { + softc->tur = 0; + scsi_test_unit_ready(&start_ccb->csio, + /*retries*/ da_retry_count, + dadone, + MSG_SIMPLE_Q_TAG, + SSD_FULL_SIZE, + da_default_timeout * 1000); + start_ccb->ccb_h.ccb_bp = NULL; + start_ccb->ccb_h.ccb_state = DA_CCB_TUR; + xpt_action(start_ccb); + } else + xpt_release_ccb(start_ccb); break; } + if (softc->tur) { + softc->tur = 0; + cam_periph_release_locked(periph); + } if ((bp->bio_flags & BIO_ORDERED) != 0 || (softc->flags & DA_FLAG_NEED_OTAG) != 0) { @@ -2417,6 +2495,25 @@ dadone(struct cam_periph *periph, union ccb *done_ccb) case DA_CCB_DUMP: /* No-op. We're polling */ return; + case DA_CCB_TUR: + { + if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + + if (daerror(done_ccb, CAM_RETRY_SELTO, + SF_RETRY_UA | SF_NO_RECOVERY | SF_NO_PRINT) == + ERESTART) + return; + if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) + cam_release_devq(done_ccb->ccb_h.path, + /*relsim_flags*/0, + /*reduction*/0, + /*timeout*/0, + /*getcount_only*/0); + } + xpt_release_ccb(done_ccb); + cam_periph_release_locked(periph); + return; + } default: break; } @@ -2477,6 +2574,13 @@ daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) xpt_print(periph->path, "capacity data has changed\n"); dareprobe(periph); sense_flags |= SF_NO_PRINT; + } else if (sense_key == SSD_KEY_UNIT_ATTENTION && + asc == 0x28 && ascq == 0x00) + disk_media_changed(softc->disk, M_NOWAIT); + else if (sense_key == SSD_KEY_NOT_READY && + asc == 0x3a && (softc->flags & DA_FLAG_SAW_MEDIA)) { + softc->flags &= ~DA_FLAG_SAW_MEDIA; + disk_media_gone(softc->disk, M_NOWAIT); } } if (error == ERESTART) @@ -2492,6 +2596,23 @@ daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) &softc->saved_ccb)); } +static void +damediapoll(void *arg) +{ + struct cam_periph *periph = arg; + struct da_softc *softc = periph->softc; + + if (softc->state == DA_STATE_NORMAL && !softc->tur) { + if (cam_periph_acquire(periph) == CAM_REQ_CMP) { + softc->tur = 1; + daschedule(periph); + } + } + /* Queue us up again */ + if (da_poll_period != 0) + callout_schedule(&softc->mediapoll_c, da_poll_period * hz); +} + static void daprevent(struct cam_periph *periph, int action) { diff --git a/sys/geom/geom.h b/sys/geom/geom.h index 526fbdb15484..6a447aee4c88 100644 --- a/sys/geom/geom.h +++ b/sys/geom/geom.h @@ -174,7 +174,9 @@ struct g_consumer { struct g_provider *provider; LIST_ENTRY(g_consumer) consumers; /* XXX: better name */ int acr, acw, ace; - int spoiled; + int flags; +#define G_CF_SPOILED 0x1 +#define G_CF_ORPHAN 0x4 struct devstat *stat; u_int nstart, nend; @@ -247,6 +249,8 @@ int g_post_event(g_event_t *func, void *arg, int flag, ...); int g_waitfor_event(g_event_t *func, void *arg, int flag, ...); void g_cancel_event(void *ref); int g_attr_changed(struct g_provider *pp, const char *attr, int flag); +int g_media_changed(struct g_provider *pp, int flag); +int g_media_gone(struct g_provider *pp, int flag); void g_orphan_provider(struct g_provider *pp, int error); void g_waitidlelock(void); diff --git a/sys/geom/geom_dev.c b/sys/geom/geom_dev.c index f3165a0578eb..793674098da4 100644 --- a/sys/geom/geom_dev.c +++ b/sys/geom/geom_dev.c @@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -105,6 +106,21 @@ g_dev_print(void) static void g_dev_attrchanged(struct g_consumer *cp, const char *attr) { + struct cdev *dev; + char buf[SPECNAMELEN + 6]; + + if (strcmp(attr, "GEOM::media") == 0) { + dev = cp->geom->softc; + snprintf(buf, sizeof(buf), "cdev=%s", dev->si_name); + devctl_notify_f("DEVFS", "CDEV", "MEDIACHANGE", buf, M_WAITOK); + dev = cp->cp_alias_dev; + if (dev != NULL) { + snprintf(buf, sizeof(buf), "cdev=%s", dev->si_name); + devctl_notify_f("DEVFS", "CDEV", "MEDIACHANGE", buf, + M_WAITOK); + } + return; + } if (strcmp(attr, "GEOM::physpath") != 0) return; @@ -119,7 +135,6 @@ g_dev_attrchanged(struct g_consumer *cp, const char *attr) g_io_getattr("GEOM::physpath", cp, &physpath_len, physpath); g_access(cp, -1, 0, 0); if (error == 0 && strlen(physpath) != 0) { - struct cdev *dev; struct cdev *old_alias_dev; struct cdev **alias_devp; @@ -161,9 +176,6 @@ g_dev_taste(struct g_class *mp, struct g_provider *pp, int insist __unused) g_trace(G_T_TOPOLOGY, "dev_taste(%s,%s)", mp->name, pp->name); g_topology_assert(); - LIST_FOREACH(cp, &pp->consumers, consumers) - if (cp->geom->class == mp) - return (NULL); gp = g_new_geomf(mp, pp->name); cp = g_new_consumer(gp); error = g_attach(cp, pp); diff --git a/sys/geom/geom_disk.c b/sys/geom/geom_disk.c index 1d6f867ada3d..f863b2cfac42 100644 --- a/sys/geom/geom_disk.c +++ b/sys/geom/geom_disk.c @@ -659,6 +659,32 @@ disk_attr_changed(struct disk *dp, const char *attr, int flag) (void)g_attr_changed(pp, attr, flag); } +void +disk_media_changed(struct disk *dp, int flag) +{ + struct g_geom *gp; + struct g_provider *pp; + + gp = dp->d_geom; + if (gp != NULL) { + LIST_FOREACH(pp, &gp->provider, provider) + g_media_changed(pp, flag); + } +} + +void +disk_media_gone(struct disk *dp, int flag) +{ + struct g_geom *gp; + struct g_provider *pp; + + gp = dp->d_geom; + if (gp != NULL) { + LIST_FOREACH(pp, &gp->provider, provider) + g_media_gone(pp, flag); + } +} + void disk_resize(struct disk *dp) { diff --git a/sys/geom/geom_disk.h b/sys/geom/geom_disk.h index 4862fc5ad560..cf538395fa35 100644 --- a/sys/geom/geom_disk.h +++ b/sys/geom/geom_disk.h @@ -109,6 +109,8 @@ void disk_create(struct disk *disk, int version); void disk_destroy(struct disk *disk); void disk_gone(struct disk *disk); void disk_attr_changed(struct disk *dp, const char *attr, int flag); +void disk_media_changed(struct disk *dp, int flag); +void disk_media_gone(struct disk *dp, int flag); void disk_resize(struct disk *dp); #define DISK_VERSION_00 0x58561059 diff --git a/sys/geom/geom_event.c b/sys/geom/geom_event.c index 1c92ccb54a79..b02d0882a3cd 100644 --- a/sys/geom/geom_event.c +++ b/sys/geom/geom_event.c @@ -202,14 +202,12 @@ g_orphan_register(struct g_provider *pp) * Tell all consumers the bad news. * Don't be surprised if they self-destruct. */ - cp = LIST_FIRST(&pp->consumers); - while (cp != NULL) { - cp2 = LIST_NEXT(cp, consumers); + LIST_FOREACH_SAFE(cp, &pp->consumers, consumers, cp2) { KASSERT(cp->geom->orphan != NULL, ("geom %s has no orphan, class %s", cp->geom->name, cp->geom->class->name)); + cp->flags |= G_CF_ORPHAN; cp->geom->orphan(cp); - cp = cp2; } if (LIST_EMPTY(&pp->consumers) && wf) g_destroy_provider(pp); diff --git a/sys/geom/geom_io.c b/sys/geom/geom_io.c index b4044a75e33c..1e0f28e48b0f 100644 --- a/sys/geom/geom_io.c +++ b/sys/geom/geom_io.c @@ -305,6 +305,8 @@ g_io_check(struct bio *bp) /* if provider is marked for error, don't disturb. */ if (pp->error) return (pp->error); + if (cp->flags & G_CF_ORPHAN) + return (ENXIO); switch(bp->bio_cmd) { case BIO_READ: diff --git a/sys/geom/geom_slice.c b/sys/geom/geom_slice.c index 407afdc1b8fc..110085497acb 100644 --- a/sys/geom/geom_slice.c +++ b/sys/geom/geom_slice.c @@ -465,6 +465,7 @@ g_slice_spoiled(struct g_consumer *cp) g_topology_assert(); gp = cp->geom; g_trace(G_T_TOPOLOGY, "g_slice_spoiled(%p/%s)", cp, gp->name); + cp->flags |= G_CF_ORPHAN; gsp = gp->softc; gp->softc = NULL; g_slice_free(gsp); diff --git a/sys/geom/geom_subr.c b/sys/geom/geom_subr.c index 6f009f05f36c..4d8623fc91f3 100644 --- a/sys/geom/geom_subr.c +++ b/sys/geom/geom_subr.c @@ -262,10 +262,11 @@ g_modevent(module_t mod, int type, void *data) static void g_retaste_event(void *arg, int flag) { - struct g_class *cp, *mp; - struct g_geom *gp, *gp2; + struct g_class *mp, *mp2; + struct g_geom *gp; struct g_hh00 *hh; struct g_provider *pp; + struct g_consumer *cp; g_topology_assert(); if (flag == EV_CANCEL) /* XXX: can't happen ? */ @@ -282,17 +283,20 @@ g_retaste_event(void *arg, int flag) } g_trace(G_T_TOPOLOGY, "g_retaste(%s)", mp->name); - LIST_FOREACH(cp, &g_classes, class) { - LIST_FOREACH(gp, &cp->geom, geom) { + LIST_FOREACH(mp2, &g_classes, class) { + LIST_FOREACH(gp, &mp2->geom, geom) { LIST_FOREACH(pp, &gp->provider, provider) { if (pp->acr || pp->acw || pp->ace) continue; - LIST_FOREACH(gp2, &mp->geom, geom) { - if (!strcmp(pp->name, gp2->name)) + LIST_FOREACH(cp, &pp->consumers, consumers) { + if (cp->geom->class == mp && + (cp->flags & G_CF_ORPHAN) == 0) break; } - if (gp2 != NULL) - g_wither_geom(gp2, ENXIO); + if (cp != NULL) { + cp->flags |= G_CF_ORPHAN; + g_wither_geom(cp->geom, ENXIO); + } mp->taste(mp, pp, 0); g_topology_assert(); } @@ -534,7 +538,7 @@ g_new_provider_event(void *arg, int flag) { struct g_class *mp; struct g_provider *pp; - struct g_consumer *cp; + struct g_consumer *cp, *next_cp; g_topology_assert(); if (flag == EV_CANCEL) @@ -545,11 +549,17 @@ g_new_provider_event(void *arg, int flag) G_VALID_PROVIDER(pp); KASSERT(!(pp->flags & G_PF_WITHER), ("g_new_provider_event but withered")); + LIST_FOREACH_SAFE(cp, &pp->consumers, consumers, next_cp) { + if ((cp->flags & G_CF_ORPHAN) == 0 && + cp->geom->attrchanged != NULL) + cp->geom->attrchanged(cp, "GEOM::media"); + } LIST_FOREACH(mp, &g_classes, class) { if (mp->taste == NULL) continue; LIST_FOREACH(cp, &pp->consumers, consumers) - if (cp->geom->class == mp) + if (cp->geom->class == mp && + (cp->flags & G_CF_ORPHAN) == 0) break; if (cp != NULL) continue; @@ -628,8 +638,10 @@ g_resize_provider_event(void *arg, int flag) LIST_FOREACH_SAFE(cp, &pp->consumers, consumers, cp2) { gp = cp->geom; - if (gp->resize == NULL && size < pp->mediasize) + if (gp->resize == NULL && size < pp->mediasize) { + cp->flags |= G_CF_ORPHAN; cp->geom->orphan(cp); + } } pp->mediasize = size; @@ -648,7 +660,8 @@ g_resize_provider_event(void *arg, int flag) if (mp->taste == NULL) continue; LIST_FOREACH(cp, &pp->consumers, consumers) - if (cp->geom->class == mp) + if (cp->geom->class == mp && + (cp->flags & G_CF_ORPHAN) == 0) break; if (cp != NULL) continue; @@ -867,7 +880,7 @@ g_access(struct g_consumer *cp, int dcr, int dcw, int dce) * are probably just ahead of the event telling us that. Fail * now rather than having to unravel this later. */ - if (cp->geom->spoiled != NULL && cp->spoiled && + if (cp->geom->spoiled != NULL && (cp->flags & G_CF_SPOILED) && (dcr > 0 || dcw > 0 || dce > 0)) return (ENXIO); @@ -1017,6 +1030,7 @@ g_std_spoiled(struct g_consumer *cp) g_topology_assert(); G_VALID_CONSUMER(cp); g_trace(G_T_TOPOLOGY, "g_std_spoiled(%p)", cp); + cp->flags |= G_CF_ORPHAN; g_detach(cp); gp = cp->geom; LIST_FOREACH(pp, &gp->provider, provider) @@ -1052,9 +1066,9 @@ g_spoil_event(void *arg, int flag) G_VALID_PROVIDER(pp); for (cp = LIST_FIRST(&pp->consumers); cp != NULL; cp = cp2) { cp2 = LIST_NEXT(cp, consumers); - if (!cp->spoiled) + if ((cp->flags & G_CF_SPOILED) == 0) continue; - cp->spoiled = 0; + cp->flags &= ~G_CF_SPOILED; if (cp->geom->spoiled == NULL) continue; cp->geom->spoiled(cp); @@ -1079,11 +1093,54 @@ g_spoil(struct g_provider *pp, struct g_consumer *cp) KASSERT(cp2->acw == 0, ("spoiling cp->acw = %d", cp2->acw)); */ KASSERT(cp2->ace == 0, ("spoiling cp->ace = %d", cp2->ace)); - cp2->spoiled++; + cp2->flags |= G_CF_SPOILED; } g_post_event(g_spoil_event, pp, M_WAITOK, pp, NULL); } +static void +g_media_changed_event(void *arg, int flag) +{ + struct g_provider *pp; + int retaste; + + g_topology_assert(); + if (flag == EV_CANCEL) + return; + pp = arg; + G_VALID_PROVIDER(pp); + + /* + * If provider was not open for writing, queue retaste after spoiling. + * If it was, retaste will happen automatically on close. + */ + retaste = (pp->acw == 0 && pp->error == 0 && + !(pp->geom->flags & G_GEOM_WITHER)); + g_spoil_event(arg, flag); + if (retaste) + g_post_event(g_new_provider_event, pp, M_WAITOK, pp, NULL); +} + +int +g_media_changed(struct g_provider *pp, int flag) +{ + struct g_consumer *cp; + + LIST_FOREACH(cp, &pp->consumers, consumers) + cp->flags |= G_CF_SPOILED; + return (g_post_event(g_media_changed_event, pp, flag, pp, NULL)); +} + +int +g_media_gone(struct g_provider *pp, int flag) +{ + struct g_consumer *cp; + + LIST_FOREACH(cp, &pp->consumers, consumers) + cp->flags |= G_CF_SPOILED; + return (g_post_event(g_spoil_event, pp, flag, pp, NULL)); +} + int g_getattr__(const char *attr, struct g_consumer *cp, void *var, int len) { @@ -1239,15 +1296,15 @@ db_show_geom_consumer(int indent, struct g_consumer *cp) cp->provider); } gprintln(" access: r%dw%de%d", cp->acr, cp->acw, cp->ace); - gprintln(" spoiled: %d", cp->spoiled); + gprintln(" flags: 0x%04x", cp->flags); gprintln(" nstart: %u", cp->nstart); gprintln(" nend: %u", cp->nend); } else { gprintf("consumer: %p (%s), access=r%dw%de%d", cp, cp->provider != NULL ? cp->provider->name : "none", cp->acr, cp->acw, cp->ace); - if (cp->spoiled) - db_printf(", spoiled=%d", cp->spoiled); + if (cp->flags) + db_printf(", flags=0x%04x", cp->flags); db_printf("\n"); } } diff --git a/sys/geom/geom_vfs.c b/sys/geom/geom_vfs.c index ade17906355d..86d0b55090b9 100644 --- a/sys/geom/geom_vfs.c +++ b/sys/geom/geom_vfs.c @@ -65,11 +65,13 @@ static struct buf_ops __g_vfs_bufops = { struct buf_ops *g_vfs_bufops = &__g_vfs_bufops; static g_orphan_t g_vfs_orphan; +static g_spoiled_t g_vfs_spoiled; static struct g_class g_vfs_class = { .name = "VFS", .version = G_VERSION, .orphan = g_vfs_orphan, + .spoiled = g_vfs_spoiled, }; DECLARE_GEOM_CLASS(g_vfs_class, g_vfs); @@ -215,8 +217,35 @@ g_vfs_orphan(struct g_consumer *cp) if (sc == NULL) return; mtx_lock(&sc->sc_mtx); + destroy = (sc->sc_active == 0 && sc->sc_orphaned == 0); + sc->sc_orphaned = 1; + mtx_unlock(&sc->sc_mtx); + if (destroy) + g_vfs_destroy(cp, 0); + + /* + * Do not destroy the geom. Filesystem will do that during unmount. + */ +} + +static void +g_vfs_spoiled(struct g_consumer *cp) +{ + struct g_geom *gp; + struct g_vfs_softc *sc; + int destroy; + + g_topology_assert(); + + gp = cp->geom; + g_trace(G_T_TOPOLOGY, "g_vfs_spoiled(%p(%s))", cp, gp->name); + cp->flags |= G_CF_ORPHAN; + sc = gp->softc; + if (sc == NULL) + return; + mtx_lock(&sc->sc_mtx); + destroy = (sc->sc_active == 0 && sc->sc_orphaned == 0); sc->sc_orphaned = 1; - destroy = (sc->sc_active == 0); mtx_unlock(&sc->sc_mtx); if (destroy) g_vfs_destroy(cp, 0); diff --git a/sys/geom/part/g_part.c b/sys/geom/part/g_part.c index 45ab5ce9cd4f..48ae931e4940 100644 --- a/sys/geom/part/g_part.c +++ b/sys/geom/part/g_part.c @@ -2055,6 +2055,7 @@ g_part_spoiled(struct g_consumer *cp) G_PART_TRACE((G_T_TOPOLOGY, "%s(%s)", __func__, cp->provider->name)); g_topology_assert(); + cp->flags |= G_CF_ORPHAN; g_part_wither(cp->geom, ENXIO); }