1baabaca3SWarner Losh /*- 2baabaca3SWarner Losh * Copyright (c) 2015 Netflix, Inc 3baabaca3SWarner Losh * All rights reserved. 4baabaca3SWarner Losh * 5baabaca3SWarner Losh * Redistribution and use in source and binary forms, with or without 6baabaca3SWarner Losh * modification, are permitted provided that the following conditions 7baabaca3SWarner Losh * are met: 8baabaca3SWarner Losh * 1. Redistributions of source code must retain the above copyright 9baabaca3SWarner Losh * notice, this list of conditions and the following disclaimer, 10baabaca3SWarner Losh * without modification, immediately at the beginning of the file. 11baabaca3SWarner Losh * 2. Redistributions in binary form must reproduce the above copyright 12baabaca3SWarner Losh * notice, this list of conditions and the following disclaimer in the 13baabaca3SWarner Losh * documentation and/or other materials provided with the distribution. 14baabaca3SWarner Losh * 15baabaca3SWarner Losh * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16baabaca3SWarner Losh * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17baabaca3SWarner Losh * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18baabaca3SWarner Losh * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19baabaca3SWarner Losh * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20baabaca3SWarner Losh * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21baabaca3SWarner Losh * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22baabaca3SWarner Losh * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23baabaca3SWarner Losh * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24baabaca3SWarner Losh * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25baabaca3SWarner Losh * 26baabaca3SWarner Losh * Derived from ata_da.c: 27baabaca3SWarner Losh * Copyright (c) 2009 Alexander Motin <mav@FreeBSD.org> 28baabaca3SWarner Losh */ 29baabaca3SWarner Losh 30baabaca3SWarner Losh #include <sys/cdefs.h> 31baabaca3SWarner Losh __FBSDID("$FreeBSD$"); 32baabaca3SWarner Losh 33baabaca3SWarner Losh #include <sys/param.h> 34baabaca3SWarner Losh 35baabaca3SWarner Losh #ifdef _KERNEL 36baabaca3SWarner Losh #include <sys/systm.h> 37baabaca3SWarner Losh #include <sys/kernel.h> 38baabaca3SWarner Losh #include <sys/bio.h> 39baabaca3SWarner Losh #include <sys/sysctl.h> 40baabaca3SWarner Losh #include <sys/taskqueue.h> 41baabaca3SWarner Losh #include <sys/lock.h> 42baabaca3SWarner Losh #include <sys/mutex.h> 43baabaca3SWarner Losh #include <sys/conf.h> 44baabaca3SWarner Losh #include <sys/devicestat.h> 45baabaca3SWarner Losh #include <sys/eventhandler.h> 46baabaca3SWarner Losh #include <sys/malloc.h> 47baabaca3SWarner Losh #include <sys/cons.h> 48baabaca3SWarner Losh #include <sys/proc.h> 49baabaca3SWarner Losh #include <sys/reboot.h> 50baabaca3SWarner Losh #include <geom/geom_disk.h> 51baabaca3SWarner Losh #endif /* _KERNEL */ 52baabaca3SWarner Losh 53baabaca3SWarner Losh #ifndef _KERNEL 54baabaca3SWarner Losh #include <stdio.h> 55baabaca3SWarner Losh #include <string.h> 56baabaca3SWarner Losh #endif /* _KERNEL */ 57baabaca3SWarner Losh 58baabaca3SWarner Losh #include <cam/cam.h> 59baabaca3SWarner Losh #include <cam/cam_ccb.h> 60baabaca3SWarner Losh #include <cam/cam_periph.h> 61baabaca3SWarner Losh #include <cam/cam_xpt_periph.h> 62baabaca3SWarner Losh #include <cam/cam_sim.h> 63baabaca3SWarner Losh #include <cam/cam_iosched.h> 64baabaca3SWarner Losh 65baabaca3SWarner Losh #include <cam/nvme/nvme_all.h> 66baabaca3SWarner Losh 67baabaca3SWarner Losh typedef enum { 68baabaca3SWarner Losh NDA_STATE_NORMAL 69baabaca3SWarner Losh } nda_state; 70baabaca3SWarner Losh 71baabaca3SWarner Losh typedef enum { 72baabaca3SWarner Losh NDA_FLAG_OPEN = 0x0001, 73baabaca3SWarner Losh NDA_FLAG_DIRTY = 0x0002, 74baabaca3SWarner Losh NDA_FLAG_SCTX_INIT = 0x0004, 75baabaca3SWarner Losh } nda_flags; 76baabaca3SWarner Losh 77baabaca3SWarner Losh typedef enum { 78baabaca3SWarner Losh NDA_Q_4K = 0x01, 79baabaca3SWarner Losh NDA_Q_NONE = 0x00, 80baabaca3SWarner Losh } nda_quirks; 81baabaca3SWarner Losh 82baabaca3SWarner Losh #define NDA_Q_BIT_STRING \ 83baabaca3SWarner Losh "\020" \ 84baabaca3SWarner Losh "\001Bit 0" 85baabaca3SWarner Losh 86baabaca3SWarner Losh typedef enum { 87baabaca3SWarner Losh NDA_CCB_BUFFER_IO = 0x01, 88baabaca3SWarner Losh NDA_CCB_DUMP = 0x02, 89baabaca3SWarner Losh NDA_CCB_TRIM = 0x03, 90baabaca3SWarner Losh NDA_CCB_TYPE_MASK = 0x0F, 91baabaca3SWarner Losh } nda_ccb_state; 92baabaca3SWarner Losh 93baabaca3SWarner Losh /* Offsets into our private area for storing information */ 94baabaca3SWarner Losh #define ccb_state ppriv_field0 95baabaca3SWarner Losh #define ccb_bp ppriv_ptr1 96baabaca3SWarner Losh 97baabaca3SWarner Losh struct trim_request { 98baabaca3SWarner Losh TAILQ_HEAD(, bio) bps; 99baabaca3SWarner Losh }; 100baabaca3SWarner Losh struct nda_softc { 101baabaca3SWarner Losh struct cam_iosched_softc *cam_iosched; 102baabaca3SWarner Losh int outstanding_cmds; /* Number of active commands */ 103baabaca3SWarner Losh int refcount; /* Active xpt_action() calls */ 104baabaca3SWarner Losh nda_state state; 105baabaca3SWarner Losh nda_flags flags; 106baabaca3SWarner Losh nda_quirks quirks; 107baabaca3SWarner Losh int unmappedio; 108baabaca3SWarner Losh uint32_t nsid; /* Namespace ID for this nda device */ 109baabaca3SWarner Losh struct disk *disk; 110baabaca3SWarner Losh struct task sysctl_task; 111baabaca3SWarner Losh struct sysctl_ctx_list sysctl_ctx; 112baabaca3SWarner Losh struct sysctl_oid *sysctl_tree; 113baabaca3SWarner Losh struct trim_request trim_req; 114baabaca3SWarner Losh #ifdef CAM_IO_STATS 115baabaca3SWarner Losh struct sysctl_ctx_list sysctl_stats_ctx; 116baabaca3SWarner Losh struct sysctl_oid *sysctl_stats_tree; 117baabaca3SWarner Losh u_int timeouts; 118baabaca3SWarner Losh u_int errors; 119baabaca3SWarner Losh u_int invalidations; 120baabaca3SWarner Losh #endif 121baabaca3SWarner Losh }; 122baabaca3SWarner Losh 123baabaca3SWarner Losh /* Need quirk table */ 124baabaca3SWarner Losh 125baabaca3SWarner Losh static disk_strategy_t ndastrategy; 126baabaca3SWarner Losh static dumper_t ndadump; 127baabaca3SWarner Losh static periph_init_t ndainit; 128baabaca3SWarner Losh static void ndaasync(void *callback_arg, u_int32_t code, 129baabaca3SWarner Losh struct cam_path *path, void *arg); 130baabaca3SWarner Losh static void ndasysctlinit(void *context, int pending); 131baabaca3SWarner Losh static periph_ctor_t ndaregister; 132baabaca3SWarner Losh static periph_dtor_t ndacleanup; 133baabaca3SWarner Losh static periph_start_t ndastart; 134baabaca3SWarner Losh static periph_oninv_t ndaoninvalidate; 135baabaca3SWarner Losh static void ndadone(struct cam_periph *periph, 136baabaca3SWarner Losh union ccb *done_ccb); 137baabaca3SWarner Losh static int ndaerror(union ccb *ccb, u_int32_t cam_flags, 138baabaca3SWarner Losh u_int32_t sense_flags); 139baabaca3SWarner Losh static void ndashutdown(void *arg, int howto); 140baabaca3SWarner Losh static void ndasuspend(void *arg); 141baabaca3SWarner Losh 142baabaca3SWarner Losh #ifndef NDA_DEFAULT_SEND_ORDERED 143baabaca3SWarner Losh #define NDA_DEFAULT_SEND_ORDERED 1 144baabaca3SWarner Losh #endif 145baabaca3SWarner Losh #ifndef NDA_DEFAULT_TIMEOUT 146baabaca3SWarner Losh #define NDA_DEFAULT_TIMEOUT 30 /* Timeout in seconds */ 147baabaca3SWarner Losh #endif 148baabaca3SWarner Losh #ifndef NDA_DEFAULT_RETRY 149baabaca3SWarner Losh #define NDA_DEFAULT_RETRY 4 150baabaca3SWarner Losh #endif 151baabaca3SWarner Losh 152baabaca3SWarner Losh 153baabaca3SWarner Losh //static int nda_retry_count = NDA_DEFAULT_RETRY; 154baabaca3SWarner Losh static int nda_send_ordered = NDA_DEFAULT_SEND_ORDERED; 155baabaca3SWarner Losh static int nda_default_timeout = NDA_DEFAULT_TIMEOUT; 156baabaca3SWarner Losh 157baabaca3SWarner Losh /* 158baabaca3SWarner Losh * All NVMe media is non-rotational, so all nvme device instances 159baabaca3SWarner Losh * share this to implement the sysctl. 160baabaca3SWarner Losh */ 161baabaca3SWarner Losh static int nda_rotating_media = 0; 162baabaca3SWarner Losh 163baabaca3SWarner Losh static SYSCTL_NODE(_kern_cam, OID_AUTO, nda, CTLFLAG_RD, 0, 164baabaca3SWarner Losh "CAM Direct Access Disk driver"); 165baabaca3SWarner Losh 166baabaca3SWarner Losh static struct periph_driver ndadriver = 167baabaca3SWarner Losh { 168baabaca3SWarner Losh ndainit, "nda", 169baabaca3SWarner Losh TAILQ_HEAD_INITIALIZER(ndadriver.units), /* generation */ 0 170baabaca3SWarner Losh }; 171baabaca3SWarner Losh 172baabaca3SWarner Losh PERIPHDRIVER_DECLARE(nda, ndadriver); 173baabaca3SWarner Losh 174baabaca3SWarner Losh static MALLOC_DEFINE(M_NVMEDA, "nvme_da", "nvme_da buffers"); 175baabaca3SWarner Losh 176baabaca3SWarner Losh /* 177baabaca3SWarner Losh * nice wrappers. Maybe these belong in nvme_all.c instead of 178baabaca3SWarner Losh * here, but this is the only place that uses these. Should 179baabaca3SWarner Losh * we ever grow another NVME periph, we should move them 180baabaca3SWarner Losh * all there wholesale. 181baabaca3SWarner Losh */ 182baabaca3SWarner Losh 183baabaca3SWarner Losh static void 184baabaca3SWarner Losh nda_nvme_flush(struct nda_softc *softc, struct ccb_nvmeio *nvmeio) 185baabaca3SWarner Losh { 186baabaca3SWarner Losh cam_fill_nvmeio(nvmeio, 187baabaca3SWarner Losh 0, /* retries */ 188baabaca3SWarner Losh ndadone, /* cbfcnp */ 189baabaca3SWarner Losh CAM_DIR_NONE, /* flags */ 190baabaca3SWarner Losh NULL, /* data_ptr */ 191baabaca3SWarner Losh 0, /* dxfer_len */ 192717bff5dSWarner Losh nda_default_timeout * 1000); /* timeout 30s */ 193baabaca3SWarner Losh nvme_ns_flush_cmd(&nvmeio->cmd, softc->nsid); 194baabaca3SWarner Losh } 195baabaca3SWarner Losh 196baabaca3SWarner Losh static void 197baabaca3SWarner Losh nda_nvme_trim(struct nda_softc *softc, struct ccb_nvmeio *nvmeio, 198baabaca3SWarner Losh void *payload, uint32_t num_ranges) 199baabaca3SWarner Losh { 200baabaca3SWarner Losh cam_fill_nvmeio(nvmeio, 201baabaca3SWarner Losh 0, /* retries */ 202baabaca3SWarner Losh ndadone, /* cbfcnp */ 203baabaca3SWarner Losh CAM_DIR_OUT, /* flags */ 204baabaca3SWarner Losh payload, /* data_ptr */ 205baabaca3SWarner Losh num_ranges * sizeof(struct nvme_dsm_range), /* dxfer_len */ 206717bff5dSWarner Losh nda_default_timeout * 1000); /* timeout 30s */ 207baabaca3SWarner Losh nvme_ns_trim_cmd(&nvmeio->cmd, softc->nsid, num_ranges); 208baabaca3SWarner Losh } 209baabaca3SWarner Losh 210baabaca3SWarner Losh static void 211baabaca3SWarner Losh nda_nvme_write(struct nda_softc *softc, struct ccb_nvmeio *nvmeio, 212baabaca3SWarner Losh void *payload, uint64_t lba, uint32_t len, uint32_t count) 213baabaca3SWarner Losh { 214baabaca3SWarner Losh cam_fill_nvmeio(nvmeio, 215baabaca3SWarner Losh 0, /* retries */ 216baabaca3SWarner Losh ndadone, /* cbfcnp */ 217baabaca3SWarner Losh CAM_DIR_OUT, /* flags */ 218baabaca3SWarner Losh payload, /* data_ptr */ 219baabaca3SWarner Losh len, /* dxfer_len */ 220717bff5dSWarner Losh nda_default_timeout * 1000); /* timeout 30s */ 221baabaca3SWarner Losh nvme_ns_write_cmd(&nvmeio->cmd, softc->nsid, lba, count); 222baabaca3SWarner Losh } 223baabaca3SWarner Losh 224baabaca3SWarner Losh static void 225baabaca3SWarner Losh nda_nvme_rw_bio(struct nda_softc *softc, struct ccb_nvmeio *nvmeio, 226baabaca3SWarner Losh struct bio *bp, uint32_t rwcmd) 227baabaca3SWarner Losh { 228baabaca3SWarner Losh int flags = rwcmd == NVME_OPC_READ ? CAM_DIR_IN : CAM_DIR_OUT; 229baabaca3SWarner Losh void *payload; 230baabaca3SWarner Losh uint64_t lba; 231baabaca3SWarner Losh uint32_t count; 232baabaca3SWarner Losh 233baabaca3SWarner Losh if (bp->bio_flags & BIO_UNMAPPED) { 234baabaca3SWarner Losh flags |= CAM_DATA_BIO; 235baabaca3SWarner Losh payload = bp; 236baabaca3SWarner Losh } else { 237baabaca3SWarner Losh payload = bp->bio_data; 238baabaca3SWarner Losh } 239baabaca3SWarner Losh 240baabaca3SWarner Losh lba = bp->bio_pblkno; 241baabaca3SWarner Losh count = bp->bio_bcount / softc->disk->d_sectorsize; 242baabaca3SWarner Losh 243baabaca3SWarner Losh cam_fill_nvmeio(nvmeio, 244baabaca3SWarner Losh 0, /* retries */ 245baabaca3SWarner Losh ndadone, /* cbfcnp */ 246baabaca3SWarner Losh flags, /* flags */ 247baabaca3SWarner Losh payload, /* data_ptr */ 248baabaca3SWarner Losh bp->bio_bcount, /* dxfer_len */ 249717bff5dSWarner Losh nda_default_timeout * 1000); /* timeout 30s */ 250baabaca3SWarner Losh nvme_ns_rw_cmd(&nvmeio->cmd, rwcmd, softc->nsid, lba, count); 251baabaca3SWarner Losh } 252baabaca3SWarner Losh 253baabaca3SWarner Losh static int 254baabaca3SWarner Losh ndaopen(struct disk *dp) 255baabaca3SWarner Losh { 256baabaca3SWarner Losh struct cam_periph *periph; 257baabaca3SWarner Losh struct nda_softc *softc; 258baabaca3SWarner Losh int error; 259baabaca3SWarner Losh 260baabaca3SWarner Losh periph = (struct cam_periph *)dp->d_drv1; 261baabaca3SWarner Losh if (cam_periph_acquire(periph) != CAM_REQ_CMP) { 262baabaca3SWarner Losh return(ENXIO); 263baabaca3SWarner Losh } 264baabaca3SWarner Losh 265baabaca3SWarner Losh cam_periph_lock(periph); 266baabaca3SWarner Losh if ((error = cam_periph_hold(periph, PRIBIO|PCATCH)) != 0) { 267baabaca3SWarner Losh cam_periph_unlock(periph); 268baabaca3SWarner Losh cam_periph_release(periph); 269baabaca3SWarner Losh return (error); 270baabaca3SWarner Losh } 271baabaca3SWarner Losh 272baabaca3SWarner Losh CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, 273baabaca3SWarner Losh ("ndaopen\n")); 274baabaca3SWarner Losh 275baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 276baabaca3SWarner Losh softc->flags |= NDA_FLAG_OPEN; 277baabaca3SWarner Losh 278baabaca3SWarner Losh cam_periph_unhold(periph); 279baabaca3SWarner Losh cam_periph_unlock(periph); 280baabaca3SWarner Losh return (0); 281baabaca3SWarner Losh } 282baabaca3SWarner Losh 283baabaca3SWarner Losh static int 284baabaca3SWarner Losh ndaclose(struct disk *dp) 285baabaca3SWarner Losh { 286baabaca3SWarner Losh struct cam_periph *periph; 287baabaca3SWarner Losh struct nda_softc *softc; 288baabaca3SWarner Losh union ccb *ccb; 289baabaca3SWarner Losh int error; 290baabaca3SWarner Losh 291baabaca3SWarner Losh periph = (struct cam_periph *)dp->d_drv1; 292baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 293baabaca3SWarner Losh cam_periph_lock(periph); 294baabaca3SWarner Losh 295baabaca3SWarner Losh CAM_DEBUG(periph->path, CAM_DEBUG_TRACE | CAM_DEBUG_PERIPH, 296baabaca3SWarner Losh ("ndaclose\n")); 297baabaca3SWarner Losh 298baabaca3SWarner Losh if ((softc->flags & NDA_FLAG_DIRTY) != 0 && 299baabaca3SWarner Losh (periph->flags & CAM_PERIPH_INVALID) == 0 && 300baabaca3SWarner Losh cam_periph_hold(periph, PRIBIO) == 0) { 301baabaca3SWarner Losh 302baabaca3SWarner Losh ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); 303baabaca3SWarner Losh nda_nvme_flush(softc, &ccb->nvmeio); 304baabaca3SWarner Losh error = cam_periph_runccb(ccb, ndaerror, /*cam_flags*/0, 305baabaca3SWarner Losh /*sense_flags*/0, softc->disk->d_devstat); 306baabaca3SWarner Losh 307baabaca3SWarner Losh if (error != 0) 308baabaca3SWarner Losh xpt_print(periph->path, "Synchronize cache failed\n"); 309baabaca3SWarner Losh else 310baabaca3SWarner Losh softc->flags &= ~NDA_FLAG_DIRTY; 311baabaca3SWarner Losh xpt_release_ccb(ccb); 312baabaca3SWarner Losh cam_periph_unhold(periph); 313baabaca3SWarner Losh } 314baabaca3SWarner Losh 315baabaca3SWarner Losh softc->flags &= ~NDA_FLAG_OPEN; 316baabaca3SWarner Losh 317baabaca3SWarner Losh while (softc->refcount != 0) 318baabaca3SWarner Losh cam_periph_sleep(periph, &softc->refcount, PRIBIO, "ndaclose", 1); 319baabaca3SWarner Losh cam_periph_unlock(periph); 320baabaca3SWarner Losh cam_periph_release(periph); 321baabaca3SWarner Losh return (0); 322baabaca3SWarner Losh } 323baabaca3SWarner Losh 324baabaca3SWarner Losh static void 325baabaca3SWarner Losh ndaschedule(struct cam_periph *periph) 326baabaca3SWarner Losh { 327baabaca3SWarner Losh struct nda_softc *softc = (struct nda_softc *)periph->softc; 328baabaca3SWarner Losh 329baabaca3SWarner Losh if (softc->state != NDA_STATE_NORMAL) 330baabaca3SWarner Losh return; 331baabaca3SWarner Losh 332baabaca3SWarner Losh cam_iosched_schedule(softc->cam_iosched, periph); 333baabaca3SWarner Losh } 334baabaca3SWarner Losh 335baabaca3SWarner Losh /* 336baabaca3SWarner Losh * Actually translate the requested transfer into one the physical driver 337baabaca3SWarner Losh * can understand. The transfer is described by a buf and will include 338baabaca3SWarner Losh * only one physical transfer. 339baabaca3SWarner Losh */ 340baabaca3SWarner Losh static void 341baabaca3SWarner Losh ndastrategy(struct bio *bp) 342baabaca3SWarner Losh { 343baabaca3SWarner Losh struct cam_periph *periph; 344baabaca3SWarner Losh struct nda_softc *softc; 345baabaca3SWarner Losh 346baabaca3SWarner Losh periph = (struct cam_periph *)bp->bio_disk->d_drv1; 347baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 348baabaca3SWarner Losh 349baabaca3SWarner Losh cam_periph_lock(periph); 350baabaca3SWarner Losh 351baabaca3SWarner Losh CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ndastrategy(%p)\n", bp)); 352baabaca3SWarner Losh 353baabaca3SWarner Losh /* 354baabaca3SWarner Losh * If the device has been made invalid, error out 355baabaca3SWarner Losh */ 356baabaca3SWarner Losh if ((periph->flags & CAM_PERIPH_INVALID) != 0) { 357baabaca3SWarner Losh cam_periph_unlock(periph); 358baabaca3SWarner Losh biofinish(bp, NULL, ENXIO); 359baabaca3SWarner Losh return; 360baabaca3SWarner Losh } 361baabaca3SWarner Losh 362baabaca3SWarner Losh /* 363baabaca3SWarner Losh * Place it in the queue of disk activities for this disk 364baabaca3SWarner Losh */ 365baabaca3SWarner Losh cam_iosched_queue_work(softc->cam_iosched, bp); 366baabaca3SWarner Losh 367baabaca3SWarner Losh /* 368baabaca3SWarner Losh * Schedule ourselves for performing the work. 369baabaca3SWarner Losh */ 370baabaca3SWarner Losh ndaschedule(periph); 371baabaca3SWarner Losh cam_periph_unlock(periph); 372baabaca3SWarner Losh 373baabaca3SWarner Losh return; 374baabaca3SWarner Losh } 375baabaca3SWarner Losh 376baabaca3SWarner Losh static int 377baabaca3SWarner Losh ndadump(void *arg, void *virtual, vm_offset_t physical, off_t offset, size_t length) 378baabaca3SWarner Losh { 379baabaca3SWarner Losh struct cam_periph *periph; 380baabaca3SWarner Losh struct nda_softc *softc; 381baabaca3SWarner Losh u_int secsize; 382c4231018SWarner Losh struct ccb_nvmeio nvmeio; 383baabaca3SWarner Losh struct disk *dp; 384baabaca3SWarner Losh uint64_t lba; 385baabaca3SWarner Losh uint32_t count; 386baabaca3SWarner Losh int error = 0; 387baabaca3SWarner Losh 388baabaca3SWarner Losh dp = arg; 389baabaca3SWarner Losh periph = dp->d_drv1; 390baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 391baabaca3SWarner Losh cam_periph_lock(periph); 392baabaca3SWarner Losh secsize = softc->disk->d_sectorsize; 393baabaca3SWarner Losh lba = offset / secsize; 394baabaca3SWarner Losh count = length / secsize; 395baabaca3SWarner Losh 396baabaca3SWarner Losh if ((periph->flags & CAM_PERIPH_INVALID) != 0) { 397baabaca3SWarner Losh cam_periph_unlock(periph); 398baabaca3SWarner Losh return (ENXIO); 399baabaca3SWarner Losh } 400baabaca3SWarner Losh 401*fa271a5dSWarner Losh /* xpt_get_ccb returns a zero'd allocation for the ccb, mimic that here */ 402*fa271a5dSWarner Losh memset(&nvmeio, 0, sizeof(nvmeio)); 403baabaca3SWarner Losh if (length > 0) { 404c4231018SWarner Losh xpt_setup_ccb(&nvmeio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); 405c4231018SWarner Losh nvmeio.ccb_h.ccb_state = NDA_CCB_DUMP; 406c4231018SWarner Losh nda_nvme_write(softc, &nvmeio, virtual, lba, length, count); 407c4231018SWarner Losh xpt_polled_action((union ccb *)&nvmeio); 408baabaca3SWarner Losh 409c4231018SWarner Losh error = cam_periph_error((union ccb *)&nvmeio, 410baabaca3SWarner Losh 0, SF_NO_RECOVERY | SF_NO_RETRY, NULL); 411c4231018SWarner Losh if ((nvmeio.ccb_h.status & CAM_DEV_QFRZN) != 0) 412c4231018SWarner Losh cam_release_devq(nvmeio.ccb_h.path, /*relsim_flags*/0, 413baabaca3SWarner Losh /*reduction*/0, /*timeout*/0, /*getcount_only*/0); 414baabaca3SWarner Losh if (error != 0) 415baabaca3SWarner Losh printf("Aborting dump due to I/O error.\n"); 416baabaca3SWarner Losh 417baabaca3SWarner Losh cam_periph_unlock(periph); 418baabaca3SWarner Losh return (error); 419baabaca3SWarner Losh } 420baabaca3SWarner Losh 421baabaca3SWarner Losh /* Flush */ 422c4231018SWarner Losh xpt_setup_ccb(&nvmeio.ccb_h, periph->path, CAM_PRIORITY_NORMAL); 423baabaca3SWarner Losh 424c4231018SWarner Losh nvmeio.ccb_h.ccb_state = NDA_CCB_DUMP; 425c4231018SWarner Losh nda_nvme_flush(softc, &nvmeio); 426c4231018SWarner Losh xpt_polled_action((union ccb *)&nvmeio); 427baabaca3SWarner Losh 428c4231018SWarner Losh error = cam_periph_error((union ccb *)&nvmeio, 429baabaca3SWarner Losh 0, SF_NO_RECOVERY | SF_NO_RETRY, NULL); 430c4231018SWarner Losh if ((nvmeio.ccb_h.status & CAM_DEV_QFRZN) != 0) 431c4231018SWarner Losh cam_release_devq(nvmeio.ccb_h.path, /*relsim_flags*/0, 432baabaca3SWarner Losh /*reduction*/0, /*timeout*/0, /*getcount_only*/0); 433baabaca3SWarner Losh if (error != 0) 434baabaca3SWarner Losh xpt_print(periph->path, "flush cmd failed\n"); 435baabaca3SWarner Losh cam_periph_unlock(periph); 436baabaca3SWarner Losh return (error); 437baabaca3SWarner Losh } 438baabaca3SWarner Losh 439baabaca3SWarner Losh static void 440baabaca3SWarner Losh ndainit(void) 441baabaca3SWarner Losh { 442baabaca3SWarner Losh cam_status status; 443baabaca3SWarner Losh 444baabaca3SWarner Losh /* 445baabaca3SWarner Losh * Install a global async callback. This callback will 446baabaca3SWarner Losh * receive async callbacks like "new device found". 447baabaca3SWarner Losh */ 448baabaca3SWarner Losh status = xpt_register_async(AC_FOUND_DEVICE, ndaasync, NULL, NULL); 449baabaca3SWarner Losh 450baabaca3SWarner Losh if (status != CAM_REQ_CMP) { 451baabaca3SWarner Losh printf("nda: Failed to attach master async callback " 452baabaca3SWarner Losh "due to status 0x%x!\n", status); 453baabaca3SWarner Losh } else if (nda_send_ordered) { 454baabaca3SWarner Losh 455baabaca3SWarner Losh /* Register our event handlers */ 456baabaca3SWarner Losh if ((EVENTHANDLER_REGISTER(power_suspend, ndasuspend, 457baabaca3SWarner Losh NULL, EVENTHANDLER_PRI_LAST)) == NULL) 458baabaca3SWarner Losh printf("ndainit: power event registration failed!\n"); 459baabaca3SWarner Losh if ((EVENTHANDLER_REGISTER(shutdown_post_sync, ndashutdown, 460baabaca3SWarner Losh NULL, SHUTDOWN_PRI_DEFAULT)) == NULL) 461baabaca3SWarner Losh printf("ndainit: shutdown event registration failed!\n"); 462baabaca3SWarner Losh } 463baabaca3SWarner Losh } 464baabaca3SWarner Losh 465baabaca3SWarner Losh /* 466baabaca3SWarner Losh * Callback from GEOM, called when it has finished cleaning up its 467baabaca3SWarner Losh * resources. 468baabaca3SWarner Losh */ 469baabaca3SWarner Losh static void 470baabaca3SWarner Losh ndadiskgonecb(struct disk *dp) 471baabaca3SWarner Losh { 472baabaca3SWarner Losh struct cam_periph *periph; 473baabaca3SWarner Losh 474baabaca3SWarner Losh periph = (struct cam_periph *)dp->d_drv1; 475baabaca3SWarner Losh 476baabaca3SWarner Losh cam_periph_release(periph); 477baabaca3SWarner Losh } 478baabaca3SWarner Losh 479baabaca3SWarner Losh static void 480baabaca3SWarner Losh ndaoninvalidate(struct cam_periph *periph) 481baabaca3SWarner Losh { 482baabaca3SWarner Losh struct nda_softc *softc; 483baabaca3SWarner Losh 484baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 485baabaca3SWarner Losh 486baabaca3SWarner Losh /* 487baabaca3SWarner Losh * De-register any async callbacks. 488baabaca3SWarner Losh */ 489baabaca3SWarner Losh xpt_register_async(0, ndaasync, periph, periph->path); 490baabaca3SWarner Losh #ifdef CAM_IO_STATS 491baabaca3SWarner Losh softc->invalidations++; 492baabaca3SWarner Losh #endif 493baabaca3SWarner Losh 494baabaca3SWarner Losh /* 495baabaca3SWarner Losh * Return all queued I/O with ENXIO. 496baabaca3SWarner Losh * XXX Handle any transactions queued to the card 497baabaca3SWarner Losh * with XPT_ABORT_CCB. 498baabaca3SWarner Losh */ 499baabaca3SWarner Losh cam_iosched_flush(softc->cam_iosched, NULL, ENXIO); 500baabaca3SWarner Losh 501baabaca3SWarner Losh disk_gone(softc->disk); 502baabaca3SWarner Losh } 503baabaca3SWarner Losh 504baabaca3SWarner Losh static void 505baabaca3SWarner Losh ndacleanup(struct cam_periph *periph) 506baabaca3SWarner Losh { 507baabaca3SWarner Losh struct nda_softc *softc; 508baabaca3SWarner Losh 509baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 510baabaca3SWarner Losh 511baabaca3SWarner Losh cam_periph_unlock(periph); 512baabaca3SWarner Losh 513baabaca3SWarner Losh cam_iosched_fini(softc->cam_iosched); 514baabaca3SWarner Losh 515baabaca3SWarner Losh /* 516baabaca3SWarner Losh * If we can't free the sysctl tree, oh well... 517baabaca3SWarner Losh */ 518baabaca3SWarner Losh if ((softc->flags & NDA_FLAG_SCTX_INIT) != 0) { 519baabaca3SWarner Losh #ifdef CAM_IO_STATS 520baabaca3SWarner Losh if (sysctl_ctx_free(&softc->sysctl_stats_ctx) != 0) 521baabaca3SWarner Losh xpt_print(periph->path, 522baabaca3SWarner Losh "can't remove sysctl stats context\n"); 523baabaca3SWarner Losh #endif 524baabaca3SWarner Losh if (sysctl_ctx_free(&softc->sysctl_ctx) != 0) 525baabaca3SWarner Losh xpt_print(periph->path, 526baabaca3SWarner Losh "can't remove sysctl context\n"); 527baabaca3SWarner Losh } 528baabaca3SWarner Losh 529baabaca3SWarner Losh disk_destroy(softc->disk); 530baabaca3SWarner Losh free(softc, M_DEVBUF); 531baabaca3SWarner Losh cam_periph_lock(periph); 532baabaca3SWarner Losh } 533baabaca3SWarner Losh 534baabaca3SWarner Losh static void 535baabaca3SWarner Losh ndaasync(void *callback_arg, u_int32_t code, 536baabaca3SWarner Losh struct cam_path *path, void *arg) 537baabaca3SWarner Losh { 538baabaca3SWarner Losh struct cam_periph *periph; 539baabaca3SWarner Losh 540baabaca3SWarner Losh periph = (struct cam_periph *)callback_arg; 541baabaca3SWarner Losh switch (code) { 542baabaca3SWarner Losh case AC_FOUND_DEVICE: 543baabaca3SWarner Losh { 544baabaca3SWarner Losh struct ccb_getdev *cgd; 545baabaca3SWarner Losh cam_status status; 546baabaca3SWarner Losh 547baabaca3SWarner Losh cgd = (struct ccb_getdev *)arg; 548baabaca3SWarner Losh if (cgd == NULL) 549baabaca3SWarner Losh break; 550baabaca3SWarner Losh 551baabaca3SWarner Losh if (cgd->protocol != PROTO_NVME) 552baabaca3SWarner Losh break; 553baabaca3SWarner Losh 554baabaca3SWarner Losh /* 555baabaca3SWarner Losh * Allocate a peripheral instance for 556baabaca3SWarner Losh * this device and start the probe 557baabaca3SWarner Losh * process. 558baabaca3SWarner Losh */ 559baabaca3SWarner Losh status = cam_periph_alloc(ndaregister, ndaoninvalidate, 560baabaca3SWarner Losh ndacleanup, ndastart, 561baabaca3SWarner Losh "nda", CAM_PERIPH_BIO, 562baabaca3SWarner Losh path, ndaasync, 563baabaca3SWarner Losh AC_FOUND_DEVICE, cgd); 564baabaca3SWarner Losh 565baabaca3SWarner Losh if (status != CAM_REQ_CMP 566baabaca3SWarner Losh && status != CAM_REQ_INPROG) 567baabaca3SWarner Losh printf("ndaasync: Unable to attach to new device " 568baabaca3SWarner Losh "due to status 0x%x\n", status); 569baabaca3SWarner Losh break; 570baabaca3SWarner Losh } 571baabaca3SWarner Losh case AC_ADVINFO_CHANGED: 572baabaca3SWarner Losh { 573baabaca3SWarner Losh uintptr_t buftype; 574baabaca3SWarner Losh 575baabaca3SWarner Losh buftype = (uintptr_t)arg; 576baabaca3SWarner Losh if (buftype == CDAI_TYPE_PHYS_PATH) { 577baabaca3SWarner Losh struct nda_softc *softc; 578baabaca3SWarner Losh 579baabaca3SWarner Losh softc = periph->softc; 580baabaca3SWarner Losh disk_attr_changed(softc->disk, "GEOM::physpath", 581baabaca3SWarner Losh M_NOWAIT); 582baabaca3SWarner Losh } 583baabaca3SWarner Losh break; 584baabaca3SWarner Losh } 585baabaca3SWarner Losh case AC_LOST_DEVICE: 586baabaca3SWarner Losh default: 587baabaca3SWarner Losh cam_periph_async(periph, code, path, arg); 588baabaca3SWarner Losh break; 589baabaca3SWarner Losh } 590baabaca3SWarner Losh } 591baabaca3SWarner Losh 592baabaca3SWarner Losh static void 593baabaca3SWarner Losh ndasysctlinit(void *context, int pending) 594baabaca3SWarner Losh { 595baabaca3SWarner Losh struct cam_periph *periph; 596baabaca3SWarner Losh struct nda_softc *softc; 597baabaca3SWarner Losh char tmpstr[80], tmpstr2[80]; 598baabaca3SWarner Losh 599baabaca3SWarner Losh periph = (struct cam_periph *)context; 600baabaca3SWarner Losh 601baabaca3SWarner Losh /* periph was held for us when this task was enqueued */ 602baabaca3SWarner Losh if ((periph->flags & CAM_PERIPH_INVALID) != 0) { 603baabaca3SWarner Losh cam_periph_release(periph); 604baabaca3SWarner Losh return; 605baabaca3SWarner Losh } 606baabaca3SWarner Losh 607baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 608baabaca3SWarner Losh snprintf(tmpstr, sizeof(tmpstr), "CAM NDA unit %d", periph->unit_number); 609baabaca3SWarner Losh snprintf(tmpstr2, sizeof(tmpstr2), "%d", periph->unit_number); 610baabaca3SWarner Losh 611baabaca3SWarner Losh sysctl_ctx_init(&softc->sysctl_ctx); 612baabaca3SWarner Losh softc->flags |= NDA_FLAG_SCTX_INIT; 6134c484fd2SEd Schouten softc->sysctl_tree = SYSCTL_ADD_NODE_WITH_LABEL(&softc->sysctl_ctx, 614baabaca3SWarner Losh SYSCTL_STATIC_CHILDREN(_kern_cam_nda), OID_AUTO, tmpstr2, 6154c484fd2SEd Schouten CTLFLAG_RD, 0, tmpstr, "device_index"); 616baabaca3SWarner Losh if (softc->sysctl_tree == NULL) { 617baabaca3SWarner Losh printf("ndasysctlinit: unable to allocate sysctl tree\n"); 618baabaca3SWarner Losh cam_periph_release(periph); 619baabaca3SWarner Losh return; 620baabaca3SWarner Losh } 621baabaca3SWarner Losh 622baabaca3SWarner Losh SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), 623baabaca3SWarner Losh OID_AUTO, "unmapped_io", CTLFLAG_RD | CTLFLAG_MPSAFE, 624baabaca3SWarner Losh &softc->unmappedio, 0, "Unmapped I/O leaf"); 625baabaca3SWarner Losh 626baabaca3SWarner Losh SYSCTL_ADD_INT(&softc->sysctl_ctx, 627baabaca3SWarner Losh SYSCTL_CHILDREN(softc->sysctl_tree), 628baabaca3SWarner Losh OID_AUTO, 629baabaca3SWarner Losh "rotating", 630baabaca3SWarner Losh CTLFLAG_RD | CTLFLAG_MPSAFE, 631baabaca3SWarner Losh &nda_rotating_media, 632baabaca3SWarner Losh 0, 633baabaca3SWarner Losh "Rotating media"); 634baabaca3SWarner Losh 635baabaca3SWarner Losh #ifdef CAM_IO_STATS 636baabaca3SWarner Losh softc->sysctl_stats_tree = SYSCTL_ADD_NODE(&softc->sysctl_stats_ctx, 637baabaca3SWarner Losh SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "stats", 638baabaca3SWarner Losh CTLFLAG_RD, 0, "Statistics"); 639baabaca3SWarner Losh if (softc->sysctl_stats_tree == NULL) { 640baabaca3SWarner Losh printf("ndasysctlinit: unable to allocate sysctl tree for stats\n"); 641baabaca3SWarner Losh cam_periph_release(periph); 642baabaca3SWarner Losh return; 643baabaca3SWarner Losh } 644baabaca3SWarner Losh SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, 645baabaca3SWarner Losh SYSCTL_CHILDREN(softc->sysctl_stats_tree), 646baabaca3SWarner Losh OID_AUTO, "timeouts", CTLFLAG_RD | CTLFLAG_MPSAFE, 647baabaca3SWarner Losh &softc->timeouts, 0, 648baabaca3SWarner Losh "Device timeouts reported by the SIM"); 649baabaca3SWarner Losh SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, 650baabaca3SWarner Losh SYSCTL_CHILDREN(softc->sysctl_stats_tree), 651baabaca3SWarner Losh OID_AUTO, "errors", CTLFLAG_RD | CTLFLAG_MPSAFE, 652baabaca3SWarner Losh &softc->errors, 0, 653baabaca3SWarner Losh "Transport errors reported by the SIM."); 654baabaca3SWarner Losh SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, 655baabaca3SWarner Losh SYSCTL_CHILDREN(softc->sysctl_stats_tree), 656baabaca3SWarner Losh OID_AUTO, "pack_invalidations", CTLFLAG_RD | CTLFLAG_MPSAFE, 657baabaca3SWarner Losh &softc->invalidations, 0, 658baabaca3SWarner Losh "Device pack invalidations."); 659baabaca3SWarner Losh #endif 660baabaca3SWarner Losh 661baabaca3SWarner Losh cam_iosched_sysctl_init(softc->cam_iosched, &softc->sysctl_ctx, 662baabaca3SWarner Losh softc->sysctl_tree); 663baabaca3SWarner Losh 664baabaca3SWarner Losh cam_periph_release(periph); 665baabaca3SWarner Losh } 666baabaca3SWarner Losh 667baabaca3SWarner Losh static int 668baabaca3SWarner Losh ndagetattr(struct bio *bp) 669baabaca3SWarner Losh { 670baabaca3SWarner Losh int ret; 671baabaca3SWarner Losh struct cam_periph *periph; 672baabaca3SWarner Losh 673baabaca3SWarner Losh periph = (struct cam_periph *)bp->bio_disk->d_drv1; 674baabaca3SWarner Losh cam_periph_lock(periph); 675baabaca3SWarner Losh ret = xpt_getattr(bp->bio_data, bp->bio_length, bp->bio_attribute, 676baabaca3SWarner Losh periph->path); 677baabaca3SWarner Losh cam_periph_unlock(periph); 678baabaca3SWarner Losh if (ret == 0) 679baabaca3SWarner Losh bp->bio_completed = bp->bio_length; 680baabaca3SWarner Losh return ret; 681baabaca3SWarner Losh } 682baabaca3SWarner Losh 683baabaca3SWarner Losh static cam_status 684baabaca3SWarner Losh ndaregister(struct cam_periph *periph, void *arg) 685baabaca3SWarner Losh { 686baabaca3SWarner Losh struct nda_softc *softc; 687baabaca3SWarner Losh struct disk *disk; 688baabaca3SWarner Losh struct ccb_pathinq cpi; 689baabaca3SWarner Losh const struct nvme_namespace_data *nsd; 690baabaca3SWarner Losh const struct nvme_controller_data *cd; 691baabaca3SWarner Losh char announce_buf[80]; 692baabaca3SWarner Losh u_int maxio; 693baabaca3SWarner Losh int quirks; 694baabaca3SWarner Losh 6959f8ed7e4SWarner Losh nsd = nvme_get_identify_ns(periph); 6969f8ed7e4SWarner Losh cd = nvme_get_identify_cntrl(periph); 697baabaca3SWarner Losh 698baabaca3SWarner Losh softc = (struct nda_softc *)malloc(sizeof(*softc), M_DEVBUF, 699baabaca3SWarner Losh M_NOWAIT | M_ZERO); 700baabaca3SWarner Losh 701baabaca3SWarner Losh if (softc == NULL) { 702baabaca3SWarner Losh printf("ndaregister: Unable to probe new device. " 703baabaca3SWarner Losh "Unable to allocate softc\n"); 704baabaca3SWarner Losh return(CAM_REQ_CMP_ERR); 705baabaca3SWarner Losh } 706baabaca3SWarner Losh 707baabaca3SWarner Losh if (cam_iosched_init(&softc->cam_iosched, periph) != 0) { 708baabaca3SWarner Losh printf("ndaregister: Unable to probe new device. " 709baabaca3SWarner Losh "Unable to allocate iosched memory\n"); 710baabaca3SWarner Losh return(CAM_REQ_CMP_ERR); 711baabaca3SWarner Losh } 712baabaca3SWarner Losh 713baabaca3SWarner Losh /* ident_data parsing */ 714baabaca3SWarner Losh 715baabaca3SWarner Losh periph->softc = softc; 716baabaca3SWarner Losh 717baabaca3SWarner Losh softc->quirks = NDA_Q_NONE; 718baabaca3SWarner Losh 719baabaca3SWarner Losh bzero(&cpi, sizeof(cpi)); 720baabaca3SWarner Losh xpt_setup_ccb(&cpi.ccb_h, periph->path, CAM_PRIORITY_NONE); 721baabaca3SWarner Losh cpi.ccb_h.func_code = XPT_PATH_INQ; 722baabaca3SWarner Losh xpt_action((union ccb *)&cpi); 723baabaca3SWarner Losh 724baabaca3SWarner Losh TASK_INIT(&softc->sysctl_task, 0, ndasysctlinit, periph); 725baabaca3SWarner Losh 726baabaca3SWarner Losh /* 727baabaca3SWarner Losh * The name space ID is the lun, save it for later I/O 728baabaca3SWarner Losh */ 7291d6e8110SWarner Losh softc->nsid = (uint32_t)xpt_path_lun_id(periph->path); 730baabaca3SWarner Losh 731baabaca3SWarner Losh /* 732baabaca3SWarner Losh * Register this media as a disk 733baabaca3SWarner Losh */ 734baabaca3SWarner Losh (void)cam_periph_hold(periph, PRIBIO); 735baabaca3SWarner Losh cam_periph_unlock(periph); 736baabaca3SWarner Losh snprintf(announce_buf, sizeof(announce_buf), 737baabaca3SWarner Losh "kern.cam.nda.%d.quirks", periph->unit_number); 738baabaca3SWarner Losh quirks = softc->quirks; 739baabaca3SWarner Losh TUNABLE_INT_FETCH(announce_buf, &quirks); 740baabaca3SWarner Losh softc->quirks = quirks; 741baabaca3SWarner Losh cam_iosched_set_sort_queue(softc->cam_iosched, 0); 742baabaca3SWarner Losh softc->disk = disk = disk_alloc(); 743baabaca3SWarner Losh strlcpy(softc->disk->d_descr, cd->mn, 744baabaca3SWarner Losh MIN(sizeof(softc->disk->d_descr), sizeof(cd->mn))); 745baabaca3SWarner Losh strlcpy(softc->disk->d_ident, cd->sn, 746baabaca3SWarner Losh MIN(sizeof(softc->disk->d_ident), sizeof(cd->sn))); 74717160457SAlexander Motin disk->d_rotation_rate = DISK_RR_NON_ROTATING; 748baabaca3SWarner Losh disk->d_open = ndaopen; 749baabaca3SWarner Losh disk->d_close = ndaclose; 750baabaca3SWarner Losh disk->d_strategy = ndastrategy; 751baabaca3SWarner Losh disk->d_getattr = ndagetattr; 752baabaca3SWarner Losh disk->d_dump = ndadump; 753baabaca3SWarner Losh disk->d_gone = ndadiskgonecb; 754baabaca3SWarner Losh disk->d_name = "nda"; 755baabaca3SWarner Losh disk->d_drv1 = periph; 756baabaca3SWarner Losh disk->d_unit = periph->unit_number; 757baabaca3SWarner Losh maxio = cpi.maxio; /* Honor max I/O size of SIM */ 758baabaca3SWarner Losh if (maxio == 0) 759baabaca3SWarner Losh maxio = DFLTPHYS; /* traditional default */ 760baabaca3SWarner Losh else if (maxio > MAXPHYS) 761baabaca3SWarner Losh maxio = MAXPHYS; /* for safety */ 762baabaca3SWarner Losh disk->d_maxsize = maxio; 763baabaca3SWarner Losh disk->d_sectorsize = 1 << nsd->lbaf[nsd->flbas.format].lbads; 764baabaca3SWarner Losh disk->d_mediasize = (off_t)(disk->d_sectorsize * nsd->nsze); 765baabaca3SWarner Losh disk->d_delmaxsize = disk->d_mediasize; 766baabaca3SWarner Losh disk->d_flags = DISKFLAG_DIRECT_COMPLETION; 767baabaca3SWarner Losh // if (cd->oncs.dsm) // XXX broken? 768baabaca3SWarner Losh disk->d_flags |= DISKFLAG_CANDELETE; 769baabaca3SWarner Losh if (cd->vwc.present) 770baabaca3SWarner Losh disk->d_flags |= DISKFLAG_CANFLUSHCACHE; 771baabaca3SWarner Losh if ((cpi.hba_misc & PIM_UNMAPPED) != 0) { 772baabaca3SWarner Losh disk->d_flags |= DISKFLAG_UNMAPPED_BIO; 773baabaca3SWarner Losh softc->unmappedio = 1; 774baabaca3SWarner Losh } 775baabaca3SWarner Losh /* 776baabaca3SWarner Losh * d_ident and d_descr are both far bigger than the length of either 777baabaca3SWarner Losh * the serial or model number strings. 778baabaca3SWarner Losh */ 779baabaca3SWarner Losh nvme_strvis(disk->d_descr, cd->mn, 780baabaca3SWarner Losh sizeof(disk->d_descr), NVME_MODEL_NUMBER_LENGTH); 781baabaca3SWarner Losh nvme_strvis(disk->d_ident, cd->sn, 782baabaca3SWarner Losh sizeof(disk->d_ident), NVME_SERIAL_NUMBER_LENGTH); 783baabaca3SWarner Losh disk->d_hba_vendor = cpi.hba_vendor; 784baabaca3SWarner Losh disk->d_hba_device = cpi.hba_device; 785baabaca3SWarner Losh disk->d_hba_subvendor = cpi.hba_subvendor; 786baabaca3SWarner Losh disk->d_hba_subdevice = cpi.hba_subdevice; 787baabaca3SWarner Losh disk->d_stripesize = disk->d_sectorsize; 788baabaca3SWarner Losh disk->d_stripeoffset = 0; 789baabaca3SWarner Losh disk->d_devstat = devstat_new_entry(periph->periph_name, 790baabaca3SWarner Losh periph->unit_number, disk->d_sectorsize, 791baabaca3SWarner Losh DEVSTAT_ALL_SUPPORTED, 792baabaca3SWarner Losh DEVSTAT_TYPE_DIRECT | XPORT_DEVSTAT_TYPE(cpi.transport), 793baabaca3SWarner Losh DEVSTAT_PRIORITY_DISK); 794d45e1674SWarner Losh /* 795d45e1674SWarner Losh * Add alias for older nvd drives to ease transition. 796d45e1674SWarner Losh */ 797d45e1674SWarner Losh disk_add_alias(disk, "nvd"); 798baabaca3SWarner Losh 799baabaca3SWarner Losh /* 800baabaca3SWarner Losh * Acquire a reference to the periph before we register with GEOM. 801baabaca3SWarner Losh * We'll release this reference once GEOM calls us back (via 802baabaca3SWarner Losh * ndadiskgonecb()) telling us that our provider has been freed. 803baabaca3SWarner Losh */ 804baabaca3SWarner Losh if (cam_periph_acquire(periph) != CAM_REQ_CMP) { 805baabaca3SWarner Losh xpt_print(periph->path, "%s: lost periph during " 806baabaca3SWarner Losh "registration!\n", __func__); 807baabaca3SWarner Losh cam_periph_lock(periph); 808baabaca3SWarner Losh return (CAM_REQ_CMP_ERR); 809baabaca3SWarner Losh } 810baabaca3SWarner Losh disk_create(softc->disk, DISK_VERSION); 811baabaca3SWarner Losh cam_periph_lock(periph); 812baabaca3SWarner Losh cam_periph_unhold(periph); 813baabaca3SWarner Losh 814baabaca3SWarner Losh snprintf(announce_buf, sizeof(announce_buf), 815baabaca3SWarner Losh "%juMB (%ju %u byte sectors)", 816baabaca3SWarner Losh (uintmax_t)((uintmax_t)disk->d_mediasize / (1024*1024)), 817baabaca3SWarner Losh (uintmax_t)disk->d_mediasize / disk->d_sectorsize, 818baabaca3SWarner Losh disk->d_sectorsize); 819baabaca3SWarner Losh xpt_announce_periph(periph, announce_buf); 820baabaca3SWarner Losh xpt_announce_quirks(periph, softc->quirks, NDA_Q_BIT_STRING); 821baabaca3SWarner Losh 822baabaca3SWarner Losh /* 823baabaca3SWarner Losh * Create our sysctl variables, now that we know 824baabaca3SWarner Losh * we have successfully attached. 825baabaca3SWarner Losh */ 826baabaca3SWarner Losh if (cam_periph_acquire(periph) == CAM_REQ_CMP) 827baabaca3SWarner Losh taskqueue_enqueue(taskqueue_thread, &softc->sysctl_task); 828baabaca3SWarner Losh 829baabaca3SWarner Losh /* 830baabaca3SWarner Losh * Register for device going away and info about the drive 831baabaca3SWarner Losh * changing (though with NVMe, it can't) 832baabaca3SWarner Losh */ 833baabaca3SWarner Losh xpt_register_async(AC_LOST_DEVICE | AC_ADVINFO_CHANGED, 834baabaca3SWarner Losh ndaasync, periph, periph->path); 835baabaca3SWarner Losh 836baabaca3SWarner Losh softc->state = NDA_STATE_NORMAL; 837baabaca3SWarner Losh return(CAM_REQ_CMP); 838baabaca3SWarner Losh } 839baabaca3SWarner Losh 840baabaca3SWarner Losh static void 841baabaca3SWarner Losh ndastart(struct cam_periph *periph, union ccb *start_ccb) 842baabaca3SWarner Losh { 843baabaca3SWarner Losh struct nda_softc *softc = (struct nda_softc *)periph->softc; 844baabaca3SWarner Losh struct ccb_nvmeio *nvmeio = &start_ccb->nvmeio; 845baabaca3SWarner Losh 846baabaca3SWarner Losh CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ndastart\n")); 847baabaca3SWarner Losh 848baabaca3SWarner Losh switch (softc->state) { 849baabaca3SWarner Losh case NDA_STATE_NORMAL: 850baabaca3SWarner Losh { 851baabaca3SWarner Losh struct bio *bp; 852baabaca3SWarner Losh 853baabaca3SWarner Losh bp = cam_iosched_next_bio(softc->cam_iosched); 854baabaca3SWarner Losh CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("ndastart: bio %p\n", bp)); 855baabaca3SWarner Losh if (bp == NULL) { 856baabaca3SWarner Losh xpt_release_ccb(start_ccb); 857baabaca3SWarner Losh break; 858baabaca3SWarner Losh } 859baabaca3SWarner Losh 860baabaca3SWarner Losh switch (bp->bio_cmd) { 861baabaca3SWarner Losh case BIO_WRITE: 862baabaca3SWarner Losh softc->flags |= NDA_FLAG_DIRTY; 863baabaca3SWarner Losh /* FALLTHROUGH */ 864baabaca3SWarner Losh case BIO_READ: 865baabaca3SWarner Losh { 866baabaca3SWarner Losh #ifdef NDA_TEST_FAILURE 867baabaca3SWarner Losh int fail = 0; 868baabaca3SWarner Losh 869baabaca3SWarner Losh /* 870baabaca3SWarner Losh * Support the failure ioctls. If the command is a 871baabaca3SWarner Losh * read, and there are pending forced read errors, or 872baabaca3SWarner Losh * if a write and pending write errors, then fail this 873baabaca3SWarner Losh * operation with EIO. This is useful for testing 874baabaca3SWarner Losh * purposes. Also, support having every Nth read fail. 875baabaca3SWarner Losh * 876baabaca3SWarner Losh * This is a rather blunt tool. 877baabaca3SWarner Losh */ 878baabaca3SWarner Losh if (bp->bio_cmd == BIO_READ) { 879baabaca3SWarner Losh if (softc->force_read_error) { 880baabaca3SWarner Losh softc->force_read_error--; 881baabaca3SWarner Losh fail = 1; 882baabaca3SWarner Losh } 883baabaca3SWarner Losh if (softc->periodic_read_error > 0) { 884baabaca3SWarner Losh if (++softc->periodic_read_count >= 885baabaca3SWarner Losh softc->periodic_read_error) { 886baabaca3SWarner Losh softc->periodic_read_count = 0; 887baabaca3SWarner Losh fail = 1; 888baabaca3SWarner Losh } 889baabaca3SWarner Losh } 890baabaca3SWarner Losh } else { 891baabaca3SWarner Losh if (softc->force_write_error) { 892baabaca3SWarner Losh softc->force_write_error--; 893baabaca3SWarner Losh fail = 1; 894baabaca3SWarner Losh } 895baabaca3SWarner Losh } 896baabaca3SWarner Losh if (fail) { 897baabaca3SWarner Losh biofinish(bp, NULL, EIO); 898baabaca3SWarner Losh xpt_release_ccb(start_ccb); 899baabaca3SWarner Losh ndaschedule(periph); 900baabaca3SWarner Losh return; 901baabaca3SWarner Losh } 902baabaca3SWarner Losh #endif 903baabaca3SWarner Losh KASSERT((bp->bio_flags & BIO_UNMAPPED) == 0 || 904baabaca3SWarner Losh round_page(bp->bio_bcount + bp->bio_ma_offset) / 905baabaca3SWarner Losh PAGE_SIZE == bp->bio_ma_n, 906baabaca3SWarner Losh ("Short bio %p", bp)); 907baabaca3SWarner Losh nda_nvme_rw_bio(softc, &start_ccb->nvmeio, bp, bp->bio_cmd == BIO_READ ? 908baabaca3SWarner Losh NVME_OPC_READ : NVME_OPC_WRITE); 909baabaca3SWarner Losh break; 910baabaca3SWarner Losh } 911baabaca3SWarner Losh case BIO_DELETE: 912baabaca3SWarner Losh { 913baabaca3SWarner Losh struct nvme_dsm_range *dsm_range; 914baabaca3SWarner Losh 915baabaca3SWarner Losh dsm_range = 916baabaca3SWarner Losh malloc(sizeof(*dsm_range), M_NVMEDA, M_ZERO | M_WAITOK); 917baabaca3SWarner Losh dsm_range->length = 918baabaca3SWarner Losh bp->bio_bcount / softc->disk->d_sectorsize; 919baabaca3SWarner Losh dsm_range->starting_lba = 920baabaca3SWarner Losh bp->bio_offset / softc->disk->d_sectorsize; 921baabaca3SWarner Losh bp->bio_driver2 = dsm_range; 922baabaca3SWarner Losh nda_nvme_trim(softc, &start_ccb->nvmeio, dsm_range, 1); 923baabaca3SWarner Losh start_ccb->ccb_h.ccb_state = NDA_CCB_TRIM; 924baabaca3SWarner Losh start_ccb->ccb_h.flags |= CAM_UNLOCKED; 925851063e1SWarner Losh /* 926851063e1SWarner Losh * Note: We can have multiple TRIMs in flight, so we don't call 927851063e1SWarner Losh * cam_iosched_submit_trim(softc->cam_iosched); 928851063e1SWarner Losh * since that forces the I/O scheduler to only schedule one at a time. 929851063e1SWarner Losh * On NVMe drives, this is a performance disaster. 930851063e1SWarner Losh */ 931baabaca3SWarner Losh goto out; 932baabaca3SWarner Losh } 933baabaca3SWarner Losh case BIO_FLUSH: 934baabaca3SWarner Losh nda_nvme_flush(softc, nvmeio); 935baabaca3SWarner Losh break; 936baabaca3SWarner Losh } 937baabaca3SWarner Losh start_ccb->ccb_h.ccb_state = NDA_CCB_BUFFER_IO; 938baabaca3SWarner Losh start_ccb->ccb_h.flags |= CAM_UNLOCKED; 939baabaca3SWarner Losh out: 940baabaca3SWarner Losh start_ccb->ccb_h.ccb_bp = bp; 941baabaca3SWarner Losh softc->outstanding_cmds++; 942baabaca3SWarner Losh softc->refcount++; 943baabaca3SWarner Losh cam_periph_unlock(periph); 944baabaca3SWarner Losh xpt_action(start_ccb); 945baabaca3SWarner Losh cam_periph_lock(periph); 946baabaca3SWarner Losh softc->refcount--; 947baabaca3SWarner Losh 948baabaca3SWarner Losh /* May have more work to do, so ensure we stay scheduled */ 949baabaca3SWarner Losh ndaschedule(periph); 950baabaca3SWarner Losh break; 951baabaca3SWarner Losh } 952baabaca3SWarner Losh } 953baabaca3SWarner Losh } 954baabaca3SWarner Losh 955baabaca3SWarner Losh static void 956baabaca3SWarner Losh ndadone(struct cam_periph *periph, union ccb *done_ccb) 957baabaca3SWarner Losh { 958baabaca3SWarner Losh struct nda_softc *softc; 959baabaca3SWarner Losh struct ccb_nvmeio *nvmeio = &done_ccb->nvmeio; 960baabaca3SWarner Losh struct cam_path *path; 961baabaca3SWarner Losh int state; 962baabaca3SWarner Losh 963baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 964baabaca3SWarner Losh path = done_ccb->ccb_h.path; 965baabaca3SWarner Losh 966baabaca3SWarner Losh CAM_DEBUG(path, CAM_DEBUG_TRACE, ("ndadone\n")); 967baabaca3SWarner Losh 968baabaca3SWarner Losh state = nvmeio->ccb_h.ccb_state & NDA_CCB_TYPE_MASK; 969baabaca3SWarner Losh switch (state) { 970baabaca3SWarner Losh case NDA_CCB_BUFFER_IO: 971baabaca3SWarner Losh case NDA_CCB_TRIM: 972baabaca3SWarner Losh { 973baabaca3SWarner Losh struct bio *bp; 974baabaca3SWarner Losh int error; 975baabaca3SWarner Losh 976baabaca3SWarner Losh cam_periph_lock(periph); 977baabaca3SWarner Losh bp = (struct bio *)done_ccb->ccb_h.ccb_bp; 978baabaca3SWarner Losh if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 979baabaca3SWarner Losh error = ndaerror(done_ccb, 0, 0); 980baabaca3SWarner Losh if (error == ERESTART) { 981baabaca3SWarner Losh /* A retry was scheduled, so just return. */ 982baabaca3SWarner Losh cam_periph_unlock(periph); 983baabaca3SWarner Losh return; 984baabaca3SWarner Losh } 985baabaca3SWarner Losh if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) 986baabaca3SWarner Losh cam_release_devq(path, 987baabaca3SWarner Losh /*relsim_flags*/0, 988baabaca3SWarner Losh /*reduction*/0, 989baabaca3SWarner Losh /*timeout*/0, 990baabaca3SWarner Losh /*getcount_only*/0); 991baabaca3SWarner Losh } else { 992baabaca3SWarner Losh if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) 993baabaca3SWarner Losh panic("REQ_CMP with QFRZN"); 994baabaca3SWarner Losh error = 0; 995baabaca3SWarner Losh } 996baabaca3SWarner Losh bp->bio_error = error; 997baabaca3SWarner Losh if (error != 0) { 998baabaca3SWarner Losh bp->bio_resid = bp->bio_bcount; 999baabaca3SWarner Losh bp->bio_flags |= BIO_ERROR; 1000baabaca3SWarner Losh } else { 1001baabaca3SWarner Losh bp->bio_resid = 0; 1002baabaca3SWarner Losh } 1003baabaca3SWarner Losh if (state == NDA_CCB_TRIM) 1004baabaca3SWarner Losh free(bp->bio_driver2, M_NVMEDA); 1005baabaca3SWarner Losh softc->outstanding_cmds--; 1006baabaca3SWarner Losh 10079754579bSWarner Losh /* 10089754579bSWarner Losh * We need to call cam_iosched before we call biodone so that we 10099754579bSWarner Losh * don't measure any activity that happens in the completion 10109754579bSWarner Losh * routine, which in the case of sendfile can be quite 10119754579bSWarner Losh * extensive. 10129754579bSWarner Losh */ 1013baabaca3SWarner Losh cam_iosched_bio_complete(softc->cam_iosched, bp, done_ccb); 1014baabaca3SWarner Losh xpt_release_ccb(done_ccb); 1015baabaca3SWarner Losh if (state == NDA_CCB_TRIM) { 1016baabaca3SWarner Losh #ifdef notyet 1017baabaca3SWarner Losh TAILQ_HEAD(, bio) queue; 1018baabaca3SWarner Losh struct bio *bp1; 1019baabaca3SWarner Losh 1020baabaca3SWarner Losh TAILQ_INIT(&queue); 1021baabaca3SWarner Losh TAILQ_CONCAT(&queue, &softc->trim_req.bps, bio_queue); 1022baabaca3SWarner Losh #endif 1023851063e1SWarner Losh /* 1024851063e1SWarner Losh * Since we can have multiple trims in flight, we don't 1025851063e1SWarner Losh * need to call this here. 1026851063e1SWarner Losh * cam_iosched_trim_done(softc->cam_iosched); 1027851063e1SWarner Losh */ 1028baabaca3SWarner Losh ndaschedule(periph); 1029baabaca3SWarner Losh cam_periph_unlock(periph); 1030baabaca3SWarner Losh #ifdef notyet 1031baabaca3SWarner Losh /* Not yet collapsing several BIO_DELETE requests into one TRIM */ 1032baabaca3SWarner Losh while ((bp1 = TAILQ_FIRST(&queue)) != NULL) { 1033baabaca3SWarner Losh TAILQ_REMOVE(&queue, bp1, bio_queue); 1034baabaca3SWarner Losh bp1->bio_error = error; 1035baabaca3SWarner Losh if (error != 0) { 1036baabaca3SWarner Losh bp1->bio_flags |= BIO_ERROR; 1037baabaca3SWarner Losh bp1->bio_resid = bp1->bio_bcount; 1038baabaca3SWarner Losh } else 1039baabaca3SWarner Losh bp1->bio_resid = 0; 1040baabaca3SWarner Losh biodone(bp1); 1041baabaca3SWarner Losh } 1042baabaca3SWarner Losh #else 1043baabaca3SWarner Losh biodone(bp); 1044baabaca3SWarner Losh #endif 1045baabaca3SWarner Losh } else { 1046baabaca3SWarner Losh ndaschedule(periph); 1047baabaca3SWarner Losh cam_periph_unlock(periph); 1048baabaca3SWarner Losh biodone(bp); 1049baabaca3SWarner Losh } 1050baabaca3SWarner Losh return; 1051baabaca3SWarner Losh } 1052baabaca3SWarner Losh case NDA_CCB_DUMP: 1053baabaca3SWarner Losh /* No-op. We're polling */ 1054baabaca3SWarner Losh return; 1055baabaca3SWarner Losh default: 1056baabaca3SWarner Losh break; 1057baabaca3SWarner Losh } 1058baabaca3SWarner Losh xpt_release_ccb(done_ccb); 1059baabaca3SWarner Losh } 1060baabaca3SWarner Losh 1061baabaca3SWarner Losh static int 1062baabaca3SWarner Losh ndaerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) 1063baabaca3SWarner Losh { 1064baabaca3SWarner Losh struct nda_softc *softc; 1065baabaca3SWarner Losh struct cam_periph *periph; 1066baabaca3SWarner Losh 1067baabaca3SWarner Losh periph = xpt_path_periph(ccb->ccb_h.path); 1068baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 1069baabaca3SWarner Losh 1070baabaca3SWarner Losh switch (ccb->ccb_h.status & CAM_STATUS_MASK) { 1071baabaca3SWarner Losh case CAM_CMD_TIMEOUT: 1072baabaca3SWarner Losh #ifdef CAM_IO_STATS 1073baabaca3SWarner Losh softc->timeouts++; 1074baabaca3SWarner Losh #endif 1075baabaca3SWarner Losh break; 1076baabaca3SWarner Losh case CAM_REQ_ABORTED: 1077baabaca3SWarner Losh case CAM_REQ_CMP_ERR: 1078baabaca3SWarner Losh case CAM_REQ_TERMIO: 1079baabaca3SWarner Losh case CAM_UNREC_HBA_ERROR: 1080baabaca3SWarner Losh case CAM_DATA_RUN_ERR: 1081baabaca3SWarner Losh case CAM_ATA_STATUS_ERROR: 1082baabaca3SWarner Losh #ifdef CAM_IO_STATS 1083baabaca3SWarner Losh softc->errors++; 1084baabaca3SWarner Losh #endif 1085baabaca3SWarner Losh break; 1086baabaca3SWarner Losh default: 1087baabaca3SWarner Losh break; 1088baabaca3SWarner Losh } 1089baabaca3SWarner Losh 1090baabaca3SWarner Losh return(cam_periph_error(ccb, cam_flags, sense_flags, NULL)); 1091baabaca3SWarner Losh } 1092baabaca3SWarner Losh 1093baabaca3SWarner Losh /* 1094baabaca3SWarner Losh * Step through all NDA peripheral drivers, and if the device is still open, 1095baabaca3SWarner Losh * sync the disk cache to physical media. 1096baabaca3SWarner Losh */ 1097baabaca3SWarner Losh static void 1098baabaca3SWarner Losh ndaflush(void) 1099baabaca3SWarner Losh { 1100baabaca3SWarner Losh struct cam_periph *periph; 1101baabaca3SWarner Losh struct nda_softc *softc; 1102baabaca3SWarner Losh union ccb *ccb; 1103baabaca3SWarner Losh int error; 1104baabaca3SWarner Losh 1105baabaca3SWarner Losh CAM_PERIPH_FOREACH(periph, &ndadriver) { 1106baabaca3SWarner Losh softc = (struct nda_softc *)periph->softc; 1107baabaca3SWarner Losh if (SCHEDULER_STOPPED()) { 1108baabaca3SWarner Losh /* If we paniced with the lock held, do not recurse. */ 1109baabaca3SWarner Losh if (!cam_periph_owned(periph) && 1110baabaca3SWarner Losh (softc->flags & NDA_FLAG_OPEN)) { 1111baabaca3SWarner Losh ndadump(softc->disk, NULL, 0, 0, 0); 1112baabaca3SWarner Losh } 1113baabaca3SWarner Losh continue; 1114baabaca3SWarner Losh } 1115baabaca3SWarner Losh cam_periph_lock(periph); 1116baabaca3SWarner Losh /* 1117baabaca3SWarner Losh * We only sync the cache if the drive is still open, and 1118baabaca3SWarner Losh * if the drive is capable of it.. 1119baabaca3SWarner Losh */ 1120baabaca3SWarner Losh if ((softc->flags & NDA_FLAG_OPEN) == 0) { 1121baabaca3SWarner Losh cam_periph_unlock(periph); 1122baabaca3SWarner Losh continue; 1123baabaca3SWarner Losh } 1124baabaca3SWarner Losh 1125baabaca3SWarner Losh ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL); 1126baabaca3SWarner Losh nda_nvme_flush(softc, &ccb->nvmeio); 1127baabaca3SWarner Losh error = cam_periph_runccb(ccb, ndaerror, /*cam_flags*/0, 1128baabaca3SWarner Losh /*sense_flags*/ SF_NO_RECOVERY | SF_NO_RETRY, 1129baabaca3SWarner Losh softc->disk->d_devstat); 1130baabaca3SWarner Losh if (error != 0) 1131baabaca3SWarner Losh xpt_print(periph->path, "Synchronize cache failed\n"); 1132baabaca3SWarner Losh xpt_release_ccb(ccb); 1133baabaca3SWarner Losh cam_periph_unlock(periph); 1134baabaca3SWarner Losh } 1135baabaca3SWarner Losh } 1136baabaca3SWarner Losh 1137baabaca3SWarner Losh static void 1138baabaca3SWarner Losh ndashutdown(void *arg, int howto) 1139baabaca3SWarner Losh { 1140baabaca3SWarner Losh 1141baabaca3SWarner Losh ndaflush(); 1142baabaca3SWarner Losh } 1143baabaca3SWarner Losh 1144baabaca3SWarner Losh static void 1145baabaca3SWarner Losh ndasuspend(void *arg) 1146baabaca3SWarner Losh { 1147baabaca3SWarner Losh 1148baabaca3SWarner Losh ndaflush(); 1149baabaca3SWarner Losh } 1150