summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormav <mav@FreeBSD.org>2012-07-29 11:51:48 +0000
committermav <mav@FreeBSD.org>2012-07-29 11:51:48 +0000
commit24017b5387054ebed0abcea2a144f0db7f108d51 (patch)
tree07c23269365b3097a0872aa1a4fe569caf1995e1
parent6009a2eb434a1c413557b4d2bc90c32da1b6e1d4 (diff)
downloadFreeBSD-src-24017b5387054ebed0abcea2a144f0db7f108d51.zip
FreeBSD-src-24017b5387054ebed0abcea2a144f0db7f108d51.tar.gz
Implement media change notification for DA and CD removable media devices.
It includes three parts: 1) Modifications to CAM to detect media media changes and report them to disk(9) layer. For modern SATA (and potentially UAS) devices it utilizes Asynchronous Notification mechanism to receive events from hardware. Active polling with TEST UNIT READY commands with 3 seconds period is used for incapable hardware. After that both CD and DA drivers work the same way, detecting two conditions: "NOT READY: Medium not present" after medium was detected previously, and "UNIT ATTENTION: Not ready to ready change, medium may have changed". First one reported to disk(9) as media removal, second as media insert/change. To reliably receive second event new AC_UNIT_ATTENTION async added to make UAs broadcasted to all periphs by generic error handling code in cam_periph_error(). 2) Modifications to GEOM core to handle media remove and change events. Media removal handled by spoiling all consumers attached to the provider. Media change event also schedules provider retaste after spoiling to probe new media. New flag G_CF_ORPHAN was added to consumers to reflect that consumer is in process of destruction. It allows retaste to create new geom instance of the same class, while previous one is still dying. 3) Modifications to some GEOM classes: DEV -- to report media change events to devd; VFS -- to handle spoiling same as orphan to prevent accessing replaced media. PART class already handles spoiling alike to orphan. Reviewed by: silence on geom@ and scsi@ Tested by: avg Sponsored by: iXsystems, Inc. / PC-BSD MFC after: 2 months
-rw-r--r--sys/cam/ata/ata_all.h1
-rw-r--r--sys/cam/ata/ata_xpt.c12
-rw-r--r--sys/cam/cam_ccb.h1
-rw-r--r--sys/cam/cam_periph.c7
-rw-r--r--sys/cam/cam_xpt.c1
-rw-r--r--sys/cam/scsi/scsi_cd.c130
-rw-r--r--sys/cam/scsi/scsi_da.c133
-rw-r--r--sys/geom/geom.h6
-rw-r--r--sys/geom/geom_dev.c20
-rw-r--r--sys/geom/geom_disk.c26
-rw-r--r--sys/geom/geom_disk.h2
-rw-r--r--sys/geom/geom_event.c6
-rw-r--r--sys/geom/geom_io.c2
-rw-r--r--sys/geom/geom_slice.c1
-rw-r--r--sys/geom/geom_subr.c95
-rw-r--r--sys/geom/geom_vfs.c31
-rw-r--r--sys/geom/part/g_part.c1
17 files changed, 434 insertions, 41 deletions
diff --git a/sys/cam/ata/ata_all.h b/sys/cam/ata/ata_all.h
index 924fdfe..848a8fc 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 20f22eb..91f985d 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 52d7496..1f12d91 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 d947732..4ebf84c 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 3065c12..4a174d7 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 7b24ddb..c102c60 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 5c82f32..3253e02 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)
@@ -2493,6 +2597,23 @@ daerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags)
}
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)
{
struct da_softc *softc;
diff --git a/sys/geom/geom.h b/sys/geom/geom.h
index 526fbdb..6a447ae 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 f3165a0..7936740 100644
--- a/sys/geom/geom_dev.c
+++ b/sys/geom/geom_dev.c
@@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$");
#include <sys/conf.h>
#include <sys/ctype.h>
#include <sys/bio.h>
+#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/proc.h>
@@ -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 1d6f867..f863b2c 100644
--- a/sys/geom/geom_disk.c
+++ b/sys/geom/geom_disk.c
@@ -660,6 +660,32 @@ disk_attr_changed(struct disk *dp, const char *attr, int 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)
{
struct g_geom *gp;
diff --git a/sys/geom/geom_disk.h b/sys/geom/geom_disk.h
index 4862fc5..cf53839 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 1c92ccb..b02d088 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 b4044a7..1e0f28e 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 407afdc..1100854 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 6f009f0..4d8623f 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 ade1790..86d0b55 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 45ab5ce..48ae931 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);
}
OpenPOWER on IntegriCloud