Print this page
LOCAL: listen for ldi notifications of disk offline/degrade
*** 20,29 ****
--- 20,30 ----
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012 by Delphix. All rights reserved.
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/zfs_context.h>
#include <sys/zfs_zone.h>
#include <sys/spa_impl.h>
*** 40,55 ****
--- 41,193 ----
* Virtual device vector for disks.
*/
extern ldi_ident_t zfs_li;
+ static void vdev_disk_close(vdev_t *);
+
typedef struct vdev_disk_buf {
buf_t vdb_buf;
zio_t *vdb_io;
} vdev_disk_buf_t;
+ typedef struct vdev_disk_ldi_cb {
+ list_node_t lcb_next;
+ ldi_callback_id_t lcb_id;
+ } vdev_disk_ldi_cb_t;
+
+ static void vdev_disk_alloc(vdev_t *vd)
+ {
+ vdev_disk_t *dvd;
+
+ dvd = vd->vdev_tsd = kmem_zalloc(sizeof (vdev_disk_t), KM_SLEEP);
+ /*
+ * Create the LDI event callback list.
+ */
+ list_create(&dvd->vd_ldi_cbs, sizeof (vdev_disk_ldi_cb_t),
+ offsetof(vdev_disk_ldi_cb_t, lcb_next));
+ }
+
+ static void vdev_disk_free(vdev_t *vd)
+ {
+ vdev_disk_t *dvd = vd->vdev_tsd;
+ vdev_disk_ldi_cb_t *lcb;
+
+ /*
+ * We have already closed the LDI handle. Clean up the LDI event
+ * callbacks and free vd->vdev_tsd.
+ */
+ while ((lcb = list_head(&dvd->vd_ldi_cbs)) != NULL) {
+ list_remove(&dvd->vd_ldi_cbs, lcb);
+ (void) ldi_ev_remove_callbacks(lcb->lcb_id);
+ kmem_free(lcb, sizeof (vdev_disk_ldi_cb_t));
+ }
+ list_destroy(&dvd->vd_ldi_cbs);
+ kmem_free(dvd, sizeof (vdev_disk_t));
+ vd->vdev_tsd = NULL;
+ }
+
+ /* ARGSUSED */
+ static int
+ vdev_disk_off_notify(ldi_handle_t lh, ldi_ev_cookie_t ecookie, void *arg,
+ void *ev_data)
+ {
+ vdev_t *vd = (vdev_t *)arg;
+ vdev_disk_t *dvd = vd->vdev_tsd;
+
+ /*
+ * Ignore events other than offline.
+ */
+ if (strcmp(ldi_ev_get_type(ecookie), LDI_EV_OFFLINE) != 0)
+ return (LDI_EV_SUCCESS);
+
+ /*
+ * All LDI handles must be closed for the state change to succeed, so
+ * call on vdev_disk_close() to do this.
+ *
+ * We inform vdev_disk_close that it is being called from offline
+ * notify context so it will defer cleanup of LDI event callbacks and
+ * freeing of vd->vdev_tsd to the offline finalize or a reopen.
+ */
+ dvd->vd_ldi_offline = B_TRUE;
+ vdev_disk_close(vd);
+
+ /*
+ * Now that the device is closed, request that the spa_async_thread
+ * mark the device as REMOVED and notify FMA of the removal.
+ */
+ zfs_post_remove(vd->vdev_spa, vd);
+ vd->vdev_remove_wanted = B_TRUE;
+ spa_async_request(vd->vdev_spa, SPA_ASYNC_REMOVE);
+
+ return (LDI_EV_SUCCESS);
+ }
+
+ /* ARGSUSED */
static void
+ vdev_disk_off_finalize(ldi_handle_t lh, ldi_ev_cookie_t ecookie,
+ int ldi_result, void *arg, void *ev_data)
+ {
+ vdev_t *vd = (vdev_t *)arg;
+ vdev_disk_t *dvd = vd->vdev_tsd;
+ vdev_disk_ldi_cb_t *lcb;
+
+ /*
+ * Ignore events other than offline.
+ */
+ if (strcmp(ldi_ev_get_type(ecookie), LDI_EV_OFFLINE) != 0)
+ return;
+
+ /*
+ * We have already closed the LDI handle in notify.
+ * Clean up the LDI event callbacks and free vd->vdev_tsd.
+ */
+ vdev_disk_free(vd);
+
+ /*
+ * Request that the vdev be reopened if the offline state change was
+ * unsuccessful.
+ */
+ if (ldi_result != LDI_EV_SUCCESS) {
+ vd->vdev_probe_wanted = B_TRUE;
+ spa_async_request(vd->vdev_spa, SPA_ASYNC_PROBE);
+ }
+ }
+
+ static ldi_ev_callback_t vdev_disk_off_callb = {
+ .cb_vers = LDI_EV_CB_VERS,
+ .cb_notify = vdev_disk_off_notify,
+ .cb_finalize = vdev_disk_off_finalize
+ };
+
+ /* ARGSUSED */
+ static void
+ vdev_disk_dgrd_finalize(ldi_handle_t lh, ldi_ev_cookie_t ecookie,
+ int ldi_result, void *arg, void *ev_data)
+ {
+ vdev_t *vd = (vdev_t *)arg;
+
+ /*
+ * Ignore events other than degrade.
+ */
+ if (strcmp(ldi_ev_get_type(ecookie), LDI_EV_DEGRADE) != 0)
+ return;
+
+ /*
+ * Degrade events always succeed. Mark the vdev as degraded.
+ * This status is purely informative for the user.
+ */
+ (void) vdev_degrade(vd->vdev_spa, vd->vdev_guid, 0);
+ }
+
+ static ldi_ev_callback_t vdev_disk_dgrd_callb = {
+ .cb_vers = LDI_EV_CB_VERS,
+ .cb_notify = NULL,
+ .cb_finalize = vdev_disk_dgrd_finalize
+ };
+
+ static void
vdev_disk_hold(vdev_t *vd)
{
ddi_devid_t devid;
char *minor;
*** 139,150 ****
static int
vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize,
uint64_t *ashift)
{
spa_t *spa = vd->vdev_spa;
! vdev_disk_t *dvd;
struct dk_minfo_ext dkmext;
int error;
dev_t dev;
int otyp;
/*
--- 277,290 ----
static int
vdev_disk_open(vdev_t *vd, uint64_t *psize, uint64_t *max_psize,
uint64_t *ashift)
{
spa_t *spa = vd->vdev_spa;
! vdev_disk_t *dvd = vd->vdev_tsd;
struct dk_minfo_ext dkmext;
+ ldi_ev_cookie_t ecookie;
+ vdev_disk_ldi_cb_t *lcb;
int error;
dev_t dev;
int otyp;
/*
*** 157,173 ****
/*
* Reopen the device if it's not currently open. Otherwise,
* just update the physical size of the device.
*/
! if (vd->vdev_tsd != NULL) {
! ASSERT(vd->vdev_reopening);
! dvd = vd->vdev_tsd;
goto skip_open;
}
! dvd = vd->vdev_tsd = kmem_zalloc(sizeof (vdev_disk_t), KM_SLEEP);
/*
* When opening a disk device, we want to preserve the user's original
* intent. We always want to open the device by the path the user gave
* us, even if it is one of multiple paths to the save device. But we
--- 297,325 ----
/*
* Reopen the device if it's not currently open. Otherwise,
* just update the physical size of the device.
*/
! if (dvd != NULL) {
! if (dvd->vd_ldi_offline && dvd->vd_lh == NULL) {
! /*
! * If we are opening a device in its offline notify
! * context, the LDI handle was just closed. Clean
! * up the LDI event callbacks and free vd->vdev_tsd.
! */
! vdev_disk_free(vd);
! } else {
! VERIFY(vd->vdev_reopening);
goto skip_open;
}
+ }
! /*
! * Create vd->vdev_tsd.
! */
! vdev_disk_alloc(vd);
! dvd = vd->vdev_tsd;
/*
* When opening a disk device, we want to preserve the user's original
* intent. We always want to open the device by the path the user gave
* us, even if it is one of multiple paths to the save device. But we
*** 197,223 ****
ddi_devid_t devid;
if (vd->vdev_wholedisk == -1ULL) {
size_t len = strlen(vd->vdev_path) + 3;
char *buf = kmem_alloc(len, KM_SLEEP);
- ldi_handle_t lh;
(void) snprintf(buf, len, "%ss0", vd->vdev_path);
! if (ldi_open_by_name(buf, spa_mode(spa), kcred,
! &lh, zfs_li) == 0) {
spa_strfree(vd->vdev_path);
vd->vdev_path = buf;
vd->vdev_wholedisk = 1ULL;
- (void) ldi_close(lh, spa_mode(spa), kcred);
} else {
kmem_free(buf, len);
}
}
! error = ldi_open_by_name(vd->vdev_path, spa_mode(spa), kcred,
! &dvd->vd_lh, zfs_li);
/*
* Compare the devid to the stored value.
*/
if (error == 0 && vd->vdev_devid != NULL &&
--- 349,380 ----
ddi_devid_t devid;
if (vd->vdev_wholedisk == -1ULL) {
size_t len = strlen(vd->vdev_path) + 3;
char *buf = kmem_alloc(len, KM_SLEEP);
(void) snprintf(buf, len, "%ss0", vd->vdev_path);
! error = ldi_open_by_name(buf, spa_mode(spa), kcred,
! &dvd->vd_lh, zfs_li);
! if (error == 0) {
spa_strfree(vd->vdev_path);
vd->vdev_path = buf;
vd->vdev_wholedisk = 1ULL;
} else {
kmem_free(buf, len);
}
}
! /*
! * If we have not yet opened the device, try to open it by the
! * specified path.
! */
! if (error != 0) {
! error = ldi_open_by_name(vd->vdev_path, spa_mode(spa),
! kcred, &dvd->vd_lh, zfs_li);
! }
/*
* Compare the devid to the stored value.
*/
if (error == 0 && vd->vdev_devid != NULL &&
*** 297,306 ****
--- 454,484 ----
if (minorname)
kmem_free(minorname, strlen(minorname) + 1);
kmem_free(physpath, MAXPATHLEN);
}
+ /*
+ * Register callbacks for the LDI offline event.
+ */
+ if (ldi_ev_get_cookie(dvd->vd_lh, LDI_EV_OFFLINE, &ecookie) ==
+ LDI_EV_SUCCESS) {
+ lcb = kmem_zalloc(sizeof (vdev_disk_ldi_cb_t), KM_SLEEP);
+ list_insert_tail(&dvd->vd_ldi_cbs, lcb);
+ (void) ldi_ev_register_callbacks(dvd->vd_lh, ecookie,
+ &vdev_disk_off_callb, (void *) vd, &lcb->lcb_id);
+ }
+
+ /*
+ * Register callbacks for the LDI degrade event.
+ */
+ if (ldi_ev_get_cookie(dvd->vd_lh, LDI_EV_DEGRADE, &ecookie) ==
+ LDI_EV_SUCCESS) {
+ lcb = kmem_zalloc(sizeof (vdev_disk_ldi_cb_t), KM_SLEEP);
+ list_insert_tail(&dvd->vd_ldi_cbs, lcb);
+ (void) ldi_ev_register_callbacks(dvd->vd_lh, ecookie,
+ &vdev_disk_dgrd_callb, (void *) vd, &lcb->lcb_id);
+ }
skip_open:
/*
* Determine the actual size of the device.
*/
if (ldi_get_size(dvd->vd_lh, psize) != 0) {
*** 348,373 ****
static void
vdev_disk_close(vdev_t *vd)
{
vdev_disk_t *dvd = vd->vdev_tsd;
if (vd->vdev_reopening || dvd == NULL)
return;
! if (dvd->vd_minor != NULL)
ddi_devid_str_free(dvd->vd_minor);
! if (dvd->vd_devid != NULL)
ddi_devid_free(dvd->vd_devid);
! if (dvd->vd_lh != NULL)
(void) ldi_close(dvd->vd_lh, spa_mode(vd->vdev_spa), kcred);
vd->vdev_delayed_close = B_FALSE;
! kmem_free(dvd, sizeof (vdev_disk_t));
! vd->vdev_tsd = NULL;
}
int
vdev_disk_physio(vdev_t *vd, caddr_t data,
size_t size, uint64_t offset, int flags)
--- 526,565 ----
static void
vdev_disk_close(vdev_t *vd)
{
vdev_disk_t *dvd = vd->vdev_tsd;
+ vdev_disk_ldi_cb_t *lcb;
if (vd->vdev_reopening || dvd == NULL)
return;
! if (dvd->vd_minor != NULL) {
ddi_devid_str_free(dvd->vd_minor);
+ dvd->vd_minor = NULL;
+ }
! if (dvd->vd_devid != NULL) {
ddi_devid_free(dvd->vd_devid);
+ dvd->vd_devid = NULL;
+ }
! if (dvd->vd_lh != NULL) {
(void) ldi_close(dvd->vd_lh, spa_mode(vd->vdev_spa), kcred);
+ dvd->vd_lh = NULL;
+ }
vd->vdev_delayed_close = B_FALSE;
! /*
! * If we closed the LDI handle due to an offline notify from LDI,
! * don't free vd->vdev_tsd or unregister the callbacks here;
! * the offline finalize callback or a reopen will take care of it.
! */
! if (dvd->vd_ldi_offline)
! return;
!
! vdev_disk_free(vd);
}
int
vdev_disk_physio(vdev_t *vd, caddr_t data,
size_t size, uint64_t offset, int flags)
*** 376,386 ****
/*
* If the vdev is closed, it's likely in the REMOVED or FAULTED state.
* Nothing to be done here but return failure.
*/
! if (dvd == NULL)
return (EIO);
ASSERT(vd->vdev_ops == &vdev_disk_ops);
return (vdev_disk_ldi_physio(dvd->vd_lh, data, size, offset, flags));
}
--- 568,578 ----
/*
* If the vdev is closed, it's likely in the REMOVED or FAULTED state.
* Nothing to be done here but return failure.
*/
! if (dvd == NULL || (dvd->vd_ldi_offline && dvd->vd_lh == NULL))
return (EIO);
ASSERT(vd->vdev_ops == &vdev_disk_ops);
return (vdev_disk_ldi_physio(dvd->vd_lh, data, size, offset, flags));
}
*** 463,472 ****
--- 655,673 ----
vdev_disk_buf_t *vdb;
struct dk_callback *dkc;
buf_t *bp;
int error;
+ /*
+ * If the vdev is closed, it's likely in the REMOVED or FAULTED state.
+ * Nothing to be done here but return failure.
+ */
+ if (dvd == NULL || (dvd->vd_ldi_offline && dvd->vd_lh == NULL)) {
+ zio->io_error = ENXIO;
+ return (ZIO_PIPELINE_CONTINUE);
+ }
+
if (zio->io_type == ZIO_TYPE_IOCTL) {
/* XXPOLICY */
if (!vdev_readable(vd)) {
zio->io_error = ENXIO;
return (ZIO_PIPELINE_CONTINUE);