diff options
Diffstat (limited to 'sys/cam/ata/ata_da.c')
-rw-r--r-- | sys/cam/ata/ata_da.c | 472 |
1 files changed, 386 insertions, 86 deletions
diff --git a/sys/cam/ata/ata_da.c b/sys/cam/ata/ata_da.c index 38cf46e..05134d7 100644 --- a/sys/cam/ata/ata_da.c +++ b/sys/cam/ata/ata_da.c @@ -59,6 +59,7 @@ __FBSDID("$FreeBSD$"); #include <cam/cam_periph.h> #include <cam/cam_xpt_periph.h> #include <cam/cam_sim.h> +#include <cam/cam_iosched.h> #include <cam/ata/ata_all.h> @@ -68,6 +69,8 @@ __FBSDID("$FreeBSD$"); #define ATA_MAX_28BIT_LBA 268435455UL +extern int iosched_debug; + typedef enum { ADA_STATE_RAHEAD, ADA_STATE_WCACHE, @@ -87,17 +90,21 @@ typedef enum { ADA_FLAG_CAN_CFA = 0x0400, ADA_FLAG_CAN_POWERMGT = 0x0800, ADA_FLAG_CAN_DMA48 = 0x1000, - ADA_FLAG_DIRTY = 0x2000 + ADA_FLAG_DIRTY = 0x2000, + ADA_FLAG_CAN_NCQ_TRIM = 0x4000, /* CAN_TRIM also set */ + ADA_FLAG_PIM_CAN_NCQ_TRIM = 0x8000 } ada_flags; typedef enum { ADA_Q_NONE = 0x00, ADA_Q_4K = 0x01, + ADA_Q_NCQ_TRIM_BROKEN = 0x02, } ada_quirks; #define ADA_Q_BIT_STRING \ "\020" \ - "\0014K" + "\0014K" \ + "\002NCQ_TRIM_BROKEN" typedef enum { ADA_CCB_RAHEAD = 0x01, @@ -112,6 +119,23 @@ typedef enum { #define ccb_state ppriv_field0 #define ccb_bp ppriv_ptr1 +typedef enum { + ADA_DELETE_NONE, + ADA_DELETE_DISABLE, + ADA_DELETE_CFA_ERASE, + ADA_DELETE_DSM_TRIM, + ADA_DELETE_NCQ_DSM_TRIM, + ADA_DELETE_MIN = ADA_DELETE_CFA_ERASE, + ADA_DELETE_MAX = ADA_DELETE_NCQ_DSM_TRIM, +} ada_delete_methods; + +static const char *ada_delete_method_names[] = + { "NONE", "DISABLE", "CFA_ERASE", "DSM_TRIM", "NCQ_DSM_TRIM" }; +#if 0 +static const char *ada_delete_method_desc[] = + { "NONE", "DISABLED", "CFA Erase", "DSM Trim", "DSM Trim via NCQ" }; +#endif + struct disk_params { u_int8_t heads; u_int8_t secs_per_track; @@ -128,18 +152,18 @@ struct trim_request { }; struct ada_softc { - struct bio_queue_head bio_queue; - struct bio_queue_head trim_queue; + struct cam_iosched_softc *cam_iosched; int outstanding_cmds; /* Number of active commands */ int refcount; /* Active xpt_action() calls */ ada_state state; ada_flags flags; ada_quirks quirks; - int sort_io_queue; + ada_delete_methods delete_method; int trim_max_ranges; - int trim_running; int read_ahead; int write_cache; + int unmappedio; + int rotating; #ifdef ADA_TEST_FAILURE int force_read_error; int force_write_error; @@ -153,6 +177,13 @@ struct ada_softc { struct sysctl_oid *sysctl_tree; struct callout sendordered_c; struct trim_request trim_req; +#ifdef CAM_IO_STATS + struct sysctl_ctx_list sysctl_stats_ctx; + struct sysctl_oid *sysctl_stats_tree; + u_int timeouts; + u_int errors; + u_int invalidations; +#endif }; struct ada_quirk_entry { @@ -330,6 +361,38 @@ static struct ada_quirk_entry ada_quirk_table[] = }, { /* + * Crucial M500 SSDs MU07 firmware + * NCQ Trim works + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Crucial CT*M500*", "MU07" }, + /*quirks*/0 + }, + { + /* + * Crucial M500 SSDs all other firmware + * NCQ Trim doesn't work + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Crucial CT*M500*", "*" }, + /*quirks*/ADA_Q_NCQ_TRIM_BROKEN + }, + { + /* + * Crucial M550 SSDs + * NCQ Trim doesn't work, but only on MU01 firmware + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Crucial CT*M550*", "MU01" }, + /*quirks*/ADA_Q_NCQ_TRIM_BROKEN + }, + { + /* + * Crucial MX100 SSDs + * NCQ Trim doesn't work, but only on MU01 firmware + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Crucial CT*MX100*", "MU01" }, + /*quirks*/ADA_Q_NCQ_TRIM_BROKEN + }, + { + /* * Crucial RealSSD C300 SSDs * 4k optimised */ @@ -338,6 +401,14 @@ static struct ada_quirk_entry ada_quirk_table[] = }, { /* + * FCCT M500 SSDs + * NCQ Trim doesn't work + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "FCCT*M500*", "*" }, + /*quirks*/ADA_Q_NCQ_TRIM_BROKEN + }, + { + /* * Intel 320 Series SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ @@ -402,6 +473,30 @@ static struct ada_quirk_entry ada_quirk_table[] = }, { /* + * Micron M500 SSDs firmware MU07 + * NCQ Trim works? + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Micron M500*", "MU07" }, + /*quirks*/0 + }, + { + /* + * Micron M500 SSDs all other firmware + * NCQ Trim doesn't work + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Micron M500*", "*" }, + /*quirks*/ADA_Q_NCQ_TRIM_BROKEN + }, + { + /* + * Micron M5[15]0 SSDs + * NCQ Trim doesn't work, but only MU01 firmware + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "Micron M5[15]0*", "MU01" }, + /*quirks*/ADA_Q_NCQ_TRIM_BROKEN + }, + { + /* * OCZ Agility 2 SSDs * 4k optimised & trim only works in 4k requests + 4k aligned */ @@ -451,25 +546,33 @@ static struct ada_quirk_entry ada_quirk_table[] = { /* * Samsung 830 Series SSDs - * 4k optimised + * 4k optimised, NCQ TRIM Broken (normal TRIM is fine) */ { T_DIRECT, SIP_MEDIA_FIXED, "*", "SAMSUNG SSD 830 Series*", "*" }, - /*quirks*/ADA_Q_4K + /*quirks*/ADA_Q_4K | ADA_Q_NCQ_TRIM_BROKEN }, { /* * Samsung 840 SSDs - * 4k optimised + * 4k optimised, NCQ TRIM Broken (normal TRIM is fine) */ { T_DIRECT, SIP_MEDIA_FIXED, "*", "Samsung SSD 840*", "*" }, - /*quirks*/ADA_Q_4K + /*quirks*/ADA_Q_4K | ADA_Q_NCQ_TRIM_BROKEN }, { /* * Samsung 850 SSDs - * 4k optimised + * 4k optimised, NCQ TRIM broken (normal TRIM fine) */ { T_DIRECT, SIP_MEDIA_FIXED, "*", "Samsung SSD 850*", "*" }, + /*quirks*/ADA_Q_4K | ADA_Q_NCQ_TRIM_BROKEN + }, + { + /* + * Samsung SM863 Series SSDs (MZ7KM*) + * 4k optimised, NCQ believed to be working + */ + { T_DIRECT, SIP_MEDIA_FIXED, "*", "SAMSUNG MZ7KM*", "*" }, /*quirks*/ADA_Q_4K }, { @@ -477,11 +580,11 @@ static struct ada_quirk_entry ada_quirk_table[] = * Samsung 843T Series SSDs (MZ7WD*) * Samsung PM851 Series SSDs (MZ7TE*) * Samsung PM853T Series SSDs (MZ7GE*) - * Samsung SM863 Series SSDs (MZ7KM*) - * 4k optimised + * 4k optimised, NCQ believed to be broken since these are + * appear to be built with the same controllers as the 840/850. */ { T_DIRECT, SIP_MEDIA_FIXED, "*", "SAMSUNG MZ7*", "*" }, - /*quirks*/ADA_Q_4K + /*quirks*/ADA_Q_4K | ADA_Q_NCQ_TRIM_BROKEN }, { /* @@ -566,8 +669,6 @@ static void adaresume(void *arg); softc->read_ahead : ada_read_ahead) #define ADA_WC (softc->write_cache >= 0 ? \ softc->write_cache : ada_write_cache) -#define ADA_SIO (softc->sort_io_queue >= 0 ? \ - softc->sort_io_queue : cam_sort_io_queues) /* * Most platforms map firmware geometry to actual, but some don't. If @@ -624,6 +725,8 @@ static struct periph_driver adadriver = TAILQ_HEAD_INITIALIZER(adadriver.units), /* generation */ 0 }; +static int adadeletemethodsysctl(SYSCTL_HANDLER_ARGS); + PERIPHDRIVER_DECLARE(ada, adadriver); static int @@ -719,11 +822,7 @@ adaschedule(struct cam_periph *periph) if (softc->state != ADA_STATE_NORMAL) return; - /* Check if we have more work to do. */ - if (bioq_first(&softc->bio_queue) || - (!softc->trim_running && bioq_first(&softc->trim_queue))) { - xpt_schedule(periph, CAM_PRIORITY_NORMAL); - } + cam_iosched_schedule(softc->cam_iosched, periph); } /* @@ -756,14 +855,7 @@ adastrategy(struct bio *bp) /* * Place it in the queue of disk activities for this disk */ - if (bp->bio_cmd == BIO_DELETE) { - bioq_disksort(&softc->trim_queue, bp); - } else { - if (ADA_SIO) - bioq_disksort(&softc->bio_queue, bp); - else - bioq_insert_tail(&softc->bio_queue, bp); - } + cam_iosched_queue_work(softc->cam_iosched, bp); /* * Schedule ourselves for performing the work. @@ -836,6 +928,11 @@ adadump(void *arg, void *virtual, vm_offset_t physical, off_t offset, size_t len if (softc->flags & ADA_FLAG_CAN_FLUSHCACHE) { xpt_setup_ccb(&ccb.ccb_h, periph->path, CAM_PRIORITY_NORMAL); + /* + * Tell the drive to flush its internal cache. if we + * can't flush in 5s we have big problems. No need to + * wait the default 60s to detect problems. + */ ccb.ccb_h.ccb_state = ADA_CCB_DUMP; cam_fill_ataio(&ccb.ataio, 0, @@ -844,7 +941,7 @@ adadump(void *arg, void *virtual, vm_offset_t physical, off_t offset, size_t len 0, NULL, 0, - ada_default_timeout*1000); + 5*1000); if (softc->flags & ADA_FLAG_CAN_48BIT) ata_48bit_cmd(&ccb.ataio, ATA_FLUSHCACHE48, 0, 0, 0); @@ -918,14 +1015,16 @@ adaoninvalidate(struct cam_periph *periph) * De-register any async callbacks. */ xpt_register_async(0, adaasync, periph, periph->path); +#ifdef CAM_IO_STATS + softc->invalidations++; +#endif /* * Return all queued I/O with ENXIO. * XXX Handle any transactions queued to the card * with XPT_ABORT_CCB. */ - bioq_flush(&softc->bio_queue, NULL, ENXIO); - bioq_flush(&softc->trim_queue, NULL, ENXIO); + cam_iosched_flush(softc->cam_iosched, NULL, ENXIO); disk_gone(softc->disk); } @@ -939,12 +1038,20 @@ adacleanup(struct cam_periph *periph) cam_periph_unlock(periph); + cam_iosched_fini(softc->cam_iosched); + /* * If we can't free the sysctl tree, oh well... */ - if ((softc->flags & ADA_FLAG_SCTX_INIT) != 0 - && sysctl_ctx_free(&softc->sysctl_ctx) != 0) { - xpt_print(periph->path, "can't remove sysctl context\n"); + if ((softc->flags & ADA_FLAG_SCTX_INIT) != 0) { +#ifdef CAM_IO_STATS + if (sysctl_ctx_free(&softc->sysctl_stats_ctx) != 0) + xpt_print(periph->path, + "can't remove sysctl stats context\n"); +#endif + if (sysctl_ctx_free(&softc->sysctl_ctx) != 0) + xpt_print(periph->path, + "can't remove sysctl context\n"); } disk_destroy(softc->disk); @@ -954,6 +1061,20 @@ adacleanup(struct cam_periph *periph) } static void +adasetdeletemethod(struct ada_softc *softc) +{ + + if (softc->flags & ADA_FLAG_CAN_NCQ_TRIM) + softc->delete_method = ADA_DELETE_NCQ_DSM_TRIM; + else if (softc->flags & ADA_FLAG_CAN_TRIM) + softc->delete_method = ADA_DELETE_DSM_TRIM; + else if ((softc->flags & ADA_FLAG_CAN_CFA) && !(softc->flags & ADA_FLAG_CAN_48BIT)) + softc->delete_method = ADA_DELETE_CFA_ERASE; + else + softc->delete_method = ADA_DELETE_NONE; +} + +static void adaasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { @@ -1018,11 +1139,26 @@ adaasync(void *callback_arg, u_int32_t code, softc->flags |= ADA_FLAG_CAN_NCQ; else softc->flags &= ~ADA_FLAG_CAN_NCQ; + if ((cgd.ident_data.support_dsm & ATA_SUPPORT_DSM_TRIM) && - (cgd.inq_flags & SID_DMA)) + (cgd.inq_flags & SID_DMA)) { softc->flags |= ADA_FLAG_CAN_TRIM; - else - softc->flags &= ~ADA_FLAG_CAN_TRIM; + /* + * If we can do RCVSND_FPDMA_QUEUED commands, we may be able to do + * NCQ trims, if we support trims at all. We also need support from + * the sim do do things properly. Perhaps we should look at log 13 + * dword 0 bit 0 and dword 1 bit 0 are set too... + */ + if ((softc->quirks & ADA_Q_NCQ_TRIM_BROKEN) == 0 && + (softc->flags & ADA_FLAG_PIM_CAN_NCQ_TRIM) != 0 && + (cgd.ident_data.satacapabilities2 & ATA_SUPPORT_RCVSND_FPDMA_QUEUED) != 0 && + (softc->flags & ADA_FLAG_CAN_TRIM) != 0) + softc->flags |= ADA_FLAG_CAN_NCQ_TRIM; + else + softc->flags &= ~ADA_FLAG_CAN_NCQ_TRIM; + } else + softc->flags &= ~(ADA_FLAG_CAN_TRIM | ADA_FLAG_CAN_NCQ_TRIM); + adasetdeletemethod(softc); cam_periph_async(periph, code, path, arg); break; @@ -1100,6 +1236,10 @@ adasysctlinit(void *context, int pending) return; } + SYSCTL_ADD_PROC(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "delete_method", CTLTYPE_STRING | CTLFLAG_RW, + softc, 0, adadeletemethodsysctl, "A", + "BIO_DELETE execution method"); SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "read_ahead", CTLFLAG_RW | CTLFLAG_MPSAFE, &softc->read_ahead, 0, "Enable disk read ahead."); @@ -1107,9 +1247,11 @@ adasysctlinit(void *context, int pending) OID_AUTO, "write_cache", CTLFLAG_RW | CTLFLAG_MPSAFE, &softc->write_cache, 0, "Enable disk write cache."); SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), - OID_AUTO, "sort_io_queue", CTLFLAG_RW | CTLFLAG_MPSAFE, - &softc->sort_io_queue, 0, - "Sort IO queue to try and optimise disk access patterns"); + OID_AUTO, "unmapped_io", CTLFLAG_RD | CTLFLAG_MPSAFE, + &softc->unmappedio, 0, "Unmapped I/O leaf"); + SYSCTL_ADD_INT(&softc->sysctl_ctx, SYSCTL_CHILDREN(softc->sysctl_tree), + OID_AUTO, "rotating", CTLFLAG_RD | CTLFLAG_MPSAFE, + &softc->rotating, 0, "Rotating media"); #ifdef ADA_TEST_FAILURE /* * Add a 'door bell' sysctl which allows one to set it from userland @@ -1129,6 +1271,31 @@ adasysctlinit(void *context, int pending) &softc->periodic_read_error, 0, "Force a read error every N reads (don't set too low)."); #endif + +#ifdef CAM_IO_STATS + softc->sysctl_stats_tree = SYSCTL_ADD_NODE(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_tree), OID_AUTO, "stats", + CTLFLAG_RD, 0, "Statistics"); + SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_stats_tree), + OID_AUTO, "timeouts", CTLFLAG_RD | CTLFLAG_MPSAFE, + &softc->timeouts, 0, + "Device timeouts reported by the SIM"); + SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_stats_tree), + OID_AUTO, "errors", CTLFLAG_RD | CTLFLAG_MPSAFE, + &softc->errors, 0, + "Transport errors reported by the SIM."); + SYSCTL_ADD_INT(&softc->sysctl_stats_ctx, + SYSCTL_CHILDREN(softc->sysctl_stats_tree), + OID_AUTO, "pack_invalidations", CTLFLAG_RD | CTLFLAG_MPSAFE, + &softc->invalidations, 0, + "Device pack invalidations."); +#endif + + cam_iosched_sysctl_init(softc->cam_iosched, &softc->sysctl_ctx, + softc->sysctl_tree); + cam_periph_release(periph); } @@ -1148,6 +1315,43 @@ adagetattr(struct bio *bp) return ret; } +static int +adadeletemethodsysctl(SYSCTL_HANDLER_ARGS) +{ + char buf[16]; + const char *p; + struct ada_softc *softc; + int i, error, value, methods; + + softc = (struct ada_softc *)arg1; + + value = softc->delete_method; + if (value < 0 || value > ADA_DELETE_MAX) + p = "UNKNOWN"; + else + p = ada_delete_method_names[value]; + strncpy(buf, p, sizeof(buf)); + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + if (error != 0 || req->newptr == NULL) + return (error); + methods = 1 << ADA_DELETE_DISABLE; + if ((softc->flags & ADA_FLAG_CAN_CFA) && + !(softc->flags & ADA_FLAG_CAN_48BIT)) + methods |= 1 << ADA_DELETE_CFA_ERASE; + if (softc->flags & ADA_FLAG_CAN_TRIM) + methods |= 1 << ADA_DELETE_DSM_TRIM; + if (softc->flags & ADA_FLAG_CAN_NCQ_TRIM) + methods |= 1 << ADA_DELETE_NCQ_DSM_TRIM; + for (i = 0; i <= ADA_DELETE_MAX; i++) { + if (!(methods & (1 << i)) || + strcmp(buf, ada_delete_method_names[i]) != 0) + continue; + softc->delete_method = i; + return (0); + } + return (EINVAL); +} + static cam_status adaregister(struct cam_periph *periph, void *arg) { @@ -1175,8 +1379,11 @@ adaregister(struct cam_periph *periph, void *arg) return(CAM_REQ_CMP_ERR); } - bioq_init(&softc->bio_queue); - bioq_init(&softc->trim_queue); + if (cam_iosched_init(&softc->cam_iosched, periph) != 0) { + printf("adaregister: Unable to probe new device. " + "Unable to allocate iosched memory\n"); + return(CAM_REQ_CMP_ERR); + } if ((cgd->ident_data.capabilities1 & ATA_SUPPORT_DMA) && (cgd->inq_flags & SID_DMA)) @@ -1206,6 +1413,8 @@ adaregister(struct cam_periph *periph, void *arg) if (cgd->ident_data.support.command2 & ATA_SUPPORT_CFA) softc->flags |= ADA_FLAG_CAN_CFA; + adasetdeletemethod(softc); + periph->softc = softc; /* @@ -1246,10 +1455,12 @@ adaregister(struct cam_periph *periph, void *arg) "kern.cam.ada.%d.write_cache", periph->unit_number); TUNABLE_INT_FETCH(announce_buf, &softc->write_cache); /* Disable queue sorting for non-rotational media by default. */ - if (cgd->ident_data.media_rotation_rate == ATA_RATE_NON_ROTATING) - softc->sort_io_queue = 0; - else - softc->sort_io_queue = -1; + if (cgd->ident_data.media_rotation_rate == ATA_RATE_NON_ROTATING) { + softc->rotating = 0; + } else { + softc->rotating = 1; + } + cam_iosched_set_sort_queue(softc->cam_iosched, softc->rotating ? -1 : 0); adagetparams(periph, cgd); softc->disk = disk_alloc(); softc->disk->d_rotation_rate = cgd->ident_data.media_rotation_rate; @@ -1292,8 +1503,23 @@ adaregister(struct cam_periph *periph, void *arg) softc->disk->d_delmaxsize = 256 * softc->params.secsize; } else softc->disk->d_delmaxsize = maxio; - if ((cpi.hba_misc & PIM_UNMAPPED) != 0) + if ((cpi.hba_misc & PIM_UNMAPPED) != 0) { softc->disk->d_flags |= DISKFLAG_UNMAPPED_BIO; + softc->unmappedio = 1; + } + /* + * If we can do RCVSND_FPDMA_QUEUED commands, we may be able to do + * NCQ trims, if we support trims at all. We also need support from + * the sim do do things properly. Perhaps we should look at log 13 + * dword 0 bit 0 and dword 1 bit 0 are set too... + */ + if (cpi.hba_misc & PIM_NCQ_KLUDGE) + softc->flags |= ADA_FLAG_PIM_CAN_NCQ_TRIM; + if ((softc->quirks & ADA_Q_NCQ_TRIM_BROKEN) == 0 && + (softc->flags & ADA_FLAG_PIM_CAN_NCQ_TRIM) != 0 && + (cgd->ident_data.satacapabilities2 & ATA_SUPPORT_RCVSND_FPDMA_QUEUED) != 0 && + (softc->flags & ADA_FLAG_CAN_TRIM) != 0) + softc->flags |= ADA_FLAG_CAN_NCQ_TRIM; strlcpy(softc->disk->d_descr, cgd->ident_data.model, MIN(sizeof(softc->disk->d_descr), sizeof(cgd->ident_data.model))); strlcpy(softc->disk->d_ident, cgd->ident_data.serial, @@ -1320,6 +1546,7 @@ adaregister(struct cam_periph *periph, void *arg) softc->disk->d_fwsectors = softc->params.secs_per_track; softc->disk->d_fwheads = softc->params.heads; ata_disk_firmware_geom_adjust(softc->disk); + adasetdeletemethod(softc); /* * Acquire a reference to the periph before we register with GEOM. @@ -1389,10 +1616,9 @@ adaregister(struct cam_periph *periph, void *arg) return(CAM_REQ_CMP); } -static void -ada_dsmtrim(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) +static int +ada_dsmtrim_req_create(struct ada_softc *softc, struct bio *bp, struct trim_request *req) { - struct trim_request *req = &softc->trim_req; uint64_t lastlba = (uint64_t)-1; int c, lastcount = 0, off, ranges = 0; @@ -1402,8 +1628,6 @@ ada_dsmtrim(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) uint64_t lba = bp->bio_pblkno; int count = bp->bio_bcount / softc->params.secsize; - bioq_remove(&softc->trim_queue, bp); - /* Try to extend the previous range. */ if (lba == lastlba) { c = min(count, ATA_DSM_RANGE_MAX - lastcount); @@ -1439,12 +1663,27 @@ ada_dsmtrim(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) } lastlba = lba; TAILQ_INSERT_TAIL(&req->bps, bp, bio_queue); - bp = bioq_first(&softc->trim_queue); - if (bp == NULL || - bp->bio_bcount / softc->params.secsize > - (softc->trim_max_ranges - ranges) * ATA_DSM_RANGE_MAX) + + bp = cam_iosched_next_trim(softc->cam_iosched); + if (bp == NULL) + break; + if (bp->bio_bcount / softc->params.secsize > + (softc->trim_max_ranges - ranges) * ATA_DSM_RANGE_MAX) { + cam_iosched_put_back_trim(softc->cam_iosched, bp); break; + } } while (1); + + return (ranges); +} + +static void +ada_dsmtrim(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) +{ + struct trim_request *req = &softc->trim_req; + int ranges; + + ranges = ada_dsmtrim_req_create(softc, bp, req); cam_fill_ataio(ataio, ada_retry_count, adadone, @@ -1460,6 +1699,30 @@ ada_dsmtrim(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) } static void +ada_ncq_dsmtrim(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) +{ + struct trim_request *req = &softc->trim_req; + int ranges; + + ranges = ada_dsmtrim_req_create(softc, bp, req); + cam_fill_ataio(ataio, + ada_retry_count, + adadone, + CAM_DIR_OUT, + 0, + req->data, + ((ranges + ATA_DSM_BLK_RANGES - 1) / + ATA_DSM_BLK_RANGES) * ATA_DSM_BLK_SIZE, + ada_default_timeout * 1000); + ata_ncq_cmd(ataio, + ATA_SEND_FPDMA_QUEUED, + 0, + (ranges + ATA_DSM_BLK_RANGES - 1) / ATA_DSM_BLK_RANGES); + ataio->cmd.sector_count_exp = ATA_SFPDMA_DSM; + ataio->cmd.flags |= CAM_ATAIO_AUX_HACK; +} + +static void ada_cfaerase(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) { struct trim_request *req = &softc->trim_req; @@ -1468,7 +1731,6 @@ ada_cfaerase(struct ada_softc *softc, struct bio *bp, struct ccb_ataio *ataio) bzero(req, sizeof(*req)); TAILQ_INIT(&req->bps); - bioq_remove(&softc->trim_queue, bp); TAILQ_INSERT_TAIL(&req->bps, bp, bio_queue); cam_fill_ataio(ataio, @@ -1499,37 +1761,14 @@ adastart(struct cam_periph *periph, union ccb *start_ccb) struct bio *bp; u_int8_t tag_code; - /* Run TRIM if not running yet. */ - if (!softc->trim_running && - (bp = bioq_first(&softc->trim_queue)) != 0) { - if (softc->flags & ADA_FLAG_CAN_TRIM) { - ada_dsmtrim(softc, bp, ataio); - } else if ((softc->flags & ADA_FLAG_CAN_CFA) && - !(softc->flags & ADA_FLAG_CAN_48BIT)) { - ada_cfaerase(softc, bp, ataio); - } else { - /* This can happen if DMA was disabled. */ - bioq_remove(&softc->trim_queue, bp); - biofinish(bp, NULL, EOPNOTSUPP); - xpt_release_ccb(start_ccb); - adaschedule(periph); - return; - } - softc->trim_running = 1; - start_ccb->ccb_h.ccb_state = ADA_CCB_TRIM; - start_ccb->ccb_h.flags |= CAM_UNLOCKED; - goto out; - } - /* Run regular command. */ - bp = bioq_first(&softc->bio_queue); + bp = cam_iosched_next_bio(softc->cam_iosched); if (bp == NULL) { xpt_release_ccb(start_ccb); break; } - bioq_remove(&softc->bio_queue, bp); - if ((bp->bio_flags & BIO_ORDERED) != 0 - || (softc->flags & ADA_FLAG_NEED_OTAG) != 0) { + if ((bp->bio_flags & BIO_ORDERED) != 0 || + (bp->bio_cmd != BIO_DELETE && (softc->flags & ADA_FLAG_NEED_OTAG) != 0)) { softc->flags &= ~ADA_FLAG_NEED_OTAG; softc->flags |= ADA_FLAG_WAS_OTAG; tag_code = 0; @@ -1659,6 +1898,27 @@ adastart(struct cam_periph *periph, union ccb *start_ccb) } break; } + case BIO_DELETE: + switch (softc->delete_method) { + case ADA_DELETE_NCQ_DSM_TRIM: + ada_ncq_dsmtrim(softc, bp, ataio); + break; + case ADA_DELETE_DSM_TRIM: + ada_dsmtrim(softc, bp, ataio); + break; + case ADA_DELETE_CFA_ERASE: + ada_cfaerase(softc, bp, ataio); + break; + default: + biofinish(bp, NULL, EOPNOTSUPP); + xpt_release_ccb(start_ccb); + adaschedule(periph); + return; + } + start_ccb->ccb_h.ccb_state = ADA_CCB_TRIM; + start_ccb->ccb_h.flags |= CAM_UNLOCKED; + cam_iosched_submit_trim(softc->cam_iosched); + goto out; case BIO_FLUSH: cam_fill_ataio(ataio, 1, @@ -1742,6 +2002,7 @@ adadone(struct cam_periph *periph, union ccb *done_ccb) int error; cam_periph_lock(periph); + bp = (struct bio *)done_ccb->ccb_h.ccb_bp; if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { error = adaerror(done_ccb, 0, 0); if (error == ERESTART) { @@ -1755,12 +2016,25 @@ adadone(struct cam_periph *periph, union ccb *done_ccb) /*reduction*/0, /*timeout*/0, /*getcount_only*/0); + /* + * If we get an error on an NCQ DSM TRIM, fall back + * to a non-NCQ DSM TRIM forever. Please note that if + * CAN_NCQ_TRIM is set, CAN_TRIM is necessarily set too. + * However, for this one trim, we treat it as advisory + * and return success up the stack. + */ + if (state == ADA_CCB_TRIM && + error != 0 && + (softc->flags & ADA_FLAG_CAN_NCQ_TRIM) != 0) { + softc->flags &= ~ADA_FLAG_CAN_NCQ_TRIM; + error = 0; + adasetdeletemethod(softc); + } } else { if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) panic("REQ_CMP with QFRZN"); error = 0; } - bp = (struct bio *)done_ccb->ccb_h.ccb_bp; bp->bio_error = error; if (error != 0) { bp->bio_resid = bp->bio_bcount; @@ -1776,6 +2050,8 @@ adadone(struct cam_periph *periph, union ccb *done_ccb) softc->outstanding_cmds--; if (softc->outstanding_cmds == 0) softc->flags |= ADA_FLAG_WAS_OTAG; + + cam_iosched_bio_complete(softc->cam_iosched, bp, done_ccb); xpt_release_ccb(done_ccb); if (state == ADA_CCB_TRIM) { TAILQ_HEAD(, bio) queue; @@ -1793,7 +2069,7 @@ adadone(struct cam_periph *periph, union ccb *done_ccb) * daschedule again so that we don't stall if there are * no other I/Os pending apart from BIO_DELETEs. */ - softc->trim_running = 0; + cam_iosched_trim_done(softc->cam_iosched); adaschedule(periph); cam_periph_unlock(periph); while ((bp1 = TAILQ_FIRST(&queue)) != NULL) { @@ -1807,6 +2083,7 @@ adadone(struct cam_periph *periph, union ccb *done_ccb) biodone(bp1); } } else { + adaschedule(periph); cam_periph_unlock(periph); biodone(bp); } @@ -1898,6 +2175,29 @@ out: static int adaerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { +#ifdef CAM_IO_STATS + struct ada_softc *softc; + struct cam_periph *periph; + + periph = xpt_path_periph(ccb->ccb_h.path); + softc = (struct ada_softc *)periph->softc; + + switch (ccb->ccb_h.status & CAM_STATUS_MASK) { + case CAM_CMD_TIMEOUT: + softc->timeouts++; + break; + case CAM_REQ_ABORTED: + case CAM_REQ_CMP_ERR: + case CAM_REQ_TERMIO: + case CAM_UNREC_HBA_ERROR: + case CAM_DATA_RUN_ERR: + case CAM_ATA_STATUS_ERROR: + softc->errors++; + break; + default: + break; + } +#endif return(cam_periph_error(ccb, cam_flags, sense_flags, NULL)); } |