Date: Mon, 17 Jul 2017 06:28:34 +0000 (UTC) From: Xin LI <delphij@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-10@freebsd.org Subject: svn commit: r321067 - in stable/10: share/man/man4 sys/dev/arcmsr Message-ID: <201707170628.v6H6SYvw089263@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: delphij Date: Mon Jul 17 06:28:34 2017 New Revision: 321067 URL: https://svnweb.freebsd.org/changeset/base/321067 Log: MFC r320986: Update arcmsr(4) to 1.40.00.00 in order to add support of ARC-1884 SATA RAID controllers. Many thanks to Areca for continuing to support FreeBSD. Submitted by: 黃清隆 <ching2048 areca com tw> Modified: stable/10/share/man/man4/arcmsr.4 stable/10/sys/dev/arcmsr/arcmsr.c stable/10/sys/dev/arcmsr/arcmsr.h Directory Properties: stable/10/ (props changed) Modified: stable/10/share/man/man4/arcmsr.4 ============================================================================== --- stable/10/share/man/man4/arcmsr.4 Mon Jul 17 06:14:48 2017 (r321066) +++ stable/10/share/man/man4/arcmsr.4 Mon Jul 17 06:28:34 2017 (r321067) @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd December 4, 2015 +.Dd July 14, 2017 .Dt ARCMSR 4 .Os .Sh NAME @@ -147,6 +147,8 @@ ARC-1880 ARC-1882 .It ARC-1883 +.It +ARC-1884 .El .Sh FILES .Bl -tag -width ".Pa /dev/arcmsr?" -compact Modified: stable/10/sys/dev/arcmsr/arcmsr.c ============================================================================== --- stable/10/sys/dev/arcmsr/arcmsr.c Mon Jul 17 06:14:48 2017 (r321066) +++ stable/10/sys/dev/arcmsr/arcmsr.c Mon Jul 17 06:28:34 2017 (r321067) @@ -77,6 +77,7 @@ ** 1.20.00.28 09/13/2013 Ching Huang Removed recursive mutex in arcmsr_abort_dr_ccbs ** 1.20.00.29 12/18/2013 Ching Huang Change simq allocation number, support ARC1883 ** 1.30.00.00 11/30/2015 Ching Huang Added support ARC1203 +** 1.40.00.00 07/11/2017 Ching Huang Added support ARC1884 ****************************************************************************************** */ @@ -148,7 +149,7 @@ __FBSDID("$FreeBSD$"); #define arcmsr_callout_init(a) callout_init(a); #endif -#define ARCMSR_DRIVER_VERSION "arcmsr version 1.30.00.00 2015-11-30" +#define ARCMSR_DRIVER_VERSION "arcmsr version 1.40.00.00 2017-07-11" #include <dev/arcmsr/arcmsr.h> /* ************************************************************************** @@ -185,6 +186,8 @@ static void arcmsr_rescanLun_cb(struct cam_periph *per static void arcmsr_polling_devmap(void *arg); static void arcmsr_srb_timeout(void *arg); static void arcmsr_hbd_postqueue_isr(struct AdapterControlBlock *acb); +static void arcmsr_hbe_postqueue_isr(struct AdapterControlBlock *acb); +void arcmsr_teardown_intr(device_t dev, struct AdapterControlBlock *acb); #ifdef ARCMSR_DEBUG1 static void arcmsr_dump_data(struct AdapterControlBlock *acb); #endif @@ -376,6 +379,12 @@ static u_int32_t arcmsr_disable_allintr( struct Adapte CHIP_REG_WRITE32(HBD_MessageUnit, 0, pcief0_int_enable, ARCMSR_HBDMU_ALL_INT_DISABLE); } break; + case ACB_ADAPTER_TYPE_E: { + /* disable all outbound interrupt */ + intmask_org = CHIP_REG_READ32(HBC_MessageUnit, 0, host_int_mask) ; /* disable outbound message0 int */ + CHIP_REG_WRITE32(HBE_MessageUnit, 0, host_int_mask, intmask_org | ARCMSR_HBEMU_ALL_INTMASKENABLE); + } + break; } return (intmask_org); } @@ -418,6 +427,13 @@ static void arcmsr_enable_allintr( struct AdapterContr acb->outbound_int_enable = mask; } break; + case ACB_ADAPTER_TYPE_E: { + /* enable outbound Post Queue, outbound doorbell Interrupt */ + mask = ~(ARCMSR_HBEMU_OUTBOUND_DOORBELL_ISR | ARCMSR_HBEMU_OUTBOUND_POSTQUEUE_ISR); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, host_int_mask, intmask_org & mask); + acb->outbound_int_enable = ~(intmask_org & mask) & 0x0000000f; + } + break; } } /* @@ -503,6 +519,28 @@ static u_int8_t arcmsr_hbd_wait_msgint_ready(struct Ad return (FALSE); } /* +********************************************************************** +********************************************************************** +*/ +static u_int8_t arcmsr_hbe_wait_msgint_ready(struct AdapterControlBlock *acb) +{ + u_int32_t Index, read_doorbell; + u_int8_t Retries = 0x00; + + do { + for(Index=0; Index < 100; Index++) { + read_doorbell = CHIP_REG_READ32(HBE_MessageUnit, 0, iobound_doorbell); + if((read_doorbell ^ acb->in_doorbell) & ARCMSR_HBEMU_IOP2DRV_MESSAGE_CMD_DONE) { + CHIP_REG_WRITE32(HBE_MessageUnit, 0, host_int_status, 0);/*clear interrupt*/ + acb->in_doorbell = read_doorbell; + return TRUE; + } + UDELAY(10000); + }/*max 1 seconds*/ + }while(Retries++ < 20);/*max 20 sec*/ + return (FALSE); +} +/* ************************************************************************ ************************************************************************ */ @@ -576,6 +614,25 @@ static void arcmsr_flush_hbd_cache(struct AdapterContr ************************************************************************ ************************************************************************ */ +static void arcmsr_flush_hbe_cache(struct AdapterControlBlock *acb) +{ + int retry_count = 30;/* enlarge wait flush adapter cache time: 10 minute */ + + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_FLUSH_CACHE); + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_MESSAGE_CMD_DONE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + do { + if(arcmsr_hbe_wait_msgint_ready(acb)) { + break; + } else { + retry_count--; + } + }while(retry_count != 0); +} +/* +************************************************************************ +************************************************************************ +*/ static void arcmsr_flush_adapter_cache(struct AdapterControlBlock *acb) { switch (acb->adapter_type) { @@ -595,6 +652,10 @@ static void arcmsr_flush_adapter_cache(struct AdapterC arcmsr_flush_hbd_cache(acb); } break; + case ACB_ADAPTER_TYPE_E: { + arcmsr_flush_hbe_cache(acb); + } + break; } } /* @@ -715,6 +776,19 @@ static void arcmsr_abort_hbd_allcmd(struct AdapterCont ********************************************************************* ********************************************************************* */ +static void arcmsr_abort_hbe_allcmd(struct AdapterControlBlock *acb) +{ + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_ABORT_CMD); + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_MESSAGE_CMD_DONE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + if(!arcmsr_hbe_wait_msgint_ready(acb)) { + printf("arcmsr%d: wait 'abort all outstanding command' timeout \n", acb->pci_unit); + } +} +/* +********************************************************************* +********************************************************************* +*/ static void arcmsr_abort_allcmd(struct AdapterControlBlock *acb) { switch (acb->adapter_type) { @@ -734,6 +808,10 @@ static void arcmsr_abort_allcmd(struct AdapterControlB arcmsr_abort_hbd_allcmd(acb); } break; + case ACB_ADAPTER_TYPE_E: { + arcmsr_abort_hbe_allcmd(acb); + } + break; } } /* @@ -836,6 +914,9 @@ static void arcmsr_drain_donequeue(struct AdapterContr case ACB_ADAPTER_TYPE_D: srb = (struct CommandControlBlock *)(acb->vir2phy_offset+(flag_srb & 0xFFFFFFE0)); /*frame must be 32 bytes aligned*/ break; + case ACB_ADAPTER_TYPE_E: + srb = acb->psrb_pool[flag_srb]; + break; case ACB_ADAPTER_TYPE_A: case ACB_ADAPTER_TYPE_B: default: @@ -938,6 +1019,10 @@ static void arcmsr_done4abort_postqueue(struct Adapter arcmsr_hbd_postqueue_isr(acb); } break; + case ACB_ADAPTER_TYPE_E: { + arcmsr_hbe_postqueue_isr(acb); + } + break; } } /* @@ -963,9 +1048,9 @@ static void arcmsr_iop_reset(struct AdapterControlBloc srb->srb_state = ARCMSR_SRB_ABORTED; srb->pccb->ccb_h.status |= CAM_REQ_ABORTED; arcmsr_srb_complete(srb, 1); - printf("arcmsr%d: scsi id=%d lun=%d srb='%p' aborted\n" + printf("arcmsr%d: scsi id=%d lun=%jx srb='%p' aborted\n" , acb->pci_unit, srb->pccb->ccb_h.target_id - , srb->pccb->ccb_h.target_lun, srb); + , (uintmax_t)srb->pccb->ccb_h.target_lun, srb); } } /* enable all outbound interrupt */ @@ -1149,6 +1234,15 @@ static void arcmsr_post_srb(struct AdapterControlBlock ARCMSR_LOCK_RELEASE(&acb->postDone_lock); } break; + case ACB_ADAPTER_TYPE_E: { + u_int32_t ccb_post_stamp, arc_cdb_size; + + arc_cdb_size = (srb->arc_cdb_size > 0x300) ? 0x300 : srb->arc_cdb_size; + ccb_post_stamp = (srb->smid | ((arc_cdb_size-1) >> 6)); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_queueport_high, 0); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_queueport_low, ccb_post_stamp); + } + break; } } /* @@ -1184,6 +1278,12 @@ static struct QBUFFER *arcmsr_get_iop_rqbuffer( struct qbuffer = (struct QBUFFER *)&phbdmu->phbdmu->message_rbuffer; } break; + case ACB_ADAPTER_TYPE_E: { + struct HBE_MessageUnit *phbcmu = (struct HBE_MessageUnit *)acb->pmu; + + qbuffer = (struct QBUFFER *)&phbcmu->message_rbuffer; + } + break; } return(qbuffer); } @@ -1220,6 +1320,12 @@ static struct QBUFFER *arcmsr_get_iop_wqbuffer( struct qbuffer = (struct QBUFFER *)&phbdmu->phbdmu->message_wbuffer; } break; + case ACB_ADAPTER_TYPE_E: { + struct HBE_MessageUnit *phbcmu = (struct HBE_MessageUnit *)acb->pmu; + + qbuffer = (struct QBUFFER *)&phbcmu->message_wbuffer; + } + break; } return(qbuffer); } @@ -1251,6 +1357,12 @@ static void arcmsr_iop_message_read(struct AdapterCont CHIP_REG_WRITE32(HBD_MessageUnit, 0, inbound_doorbell, ARCMSR_HBDMU_DRV2IOP_DATA_OUT_READ); } break; + case ACB_ADAPTER_TYPE_E: { + /* let IOP know data has been read */ + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_DATA_READ_OK; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + } + break; } } /* @@ -1293,6 +1405,15 @@ static void arcmsr_iop_message_wrote(struct AdapterCon CHIP_REG_WRITE32(HBD_MessageUnit, 0, inbound_doorbell, ARCMSR_HBDMU_DRV2IOP_DATA_IN_READY); } break; + case ACB_ADAPTER_TYPE_E: { + /* + ** push inbound doorbell tell iop, driver data write ok + ** and wait reply on next hwinterrupt for next Qbuffer post + */ + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_DATA_WRITE_OK; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + } + break; } } /* @@ -1352,6 +1473,20 @@ static void arcmsr_stop_hbd_bgrb(struct AdapterControl ************************************************************************ ************************************************************************ */ +static void arcmsr_stop_hbe_bgrb(struct AdapterControlBlock *acb) +{ + acb->acb_flags &= ~ACB_F_MSG_START_BGRB; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_STOP_BGRB); + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_MESSAGE_CMD_DONE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + if(!arcmsr_hbe_wait_msgint_ready(acb)) { + printf("arcmsr%d: wait 'stop adapter background rebulid' timeout \n", acb->pci_unit); + } +} +/* +************************************************************************ +************************************************************************ +*/ static void arcmsr_stop_adapter_bgrb(struct AdapterControlBlock *acb) { switch (acb->adapter_type) { @@ -1371,6 +1506,10 @@ static void arcmsr_stop_adapter_bgrb(struct AdapterCon arcmsr_stop_hbd_bgrb(acb); } break; + case ACB_ADAPTER_TYPE_E: { + arcmsr_stop_hbe_bgrb(acb); + } + break; } } /* @@ -1398,8 +1537,8 @@ static u_int32_t arcmsr_Read_iop_rqbuffer_data_D(struc struct QBUFFER *prbuffer) { u_int8_t *pQbuffer; - u_int8_t *buf1 = 0; - u_int32_t *iop_data, *buf2 = 0; + u_int8_t *buf1 = NULL; + u_int32_t *iop_data, *buf2 = NULL; u_int32_t iop_len, data_len; iop_data = (u_int32_t *)prbuffer->data; @@ -1446,7 +1585,8 @@ static u_int32_t arcmsr_Read_iop_rqbuffer_data(struct u_int8_t *iop_data; u_int32_t iop_len; - if(acb->adapter_type & (ACB_ADAPTER_TYPE_C | ACB_ADAPTER_TYPE_D)) { + if((acb->adapter_type == ACB_ADAPTER_TYPE_C) || (acb->adapter_type == ACB_ADAPTER_TYPE_D) || + (acb->adapter_type == ACB_ADAPTER_TYPE_E)) { return(arcmsr_Read_iop_rqbuffer_data_D(acb, prbuffer)); } iop_data = (u_int8_t *)prbuffer->data; @@ -1494,8 +1634,8 @@ static void arcmsr_Write_data_2iop_wqbuffer_D(struct A { u_int8_t *pQbuffer; struct QBUFFER *pwbuffer; - u_int8_t *buf1 = 0; - u_int32_t *iop_data, *buf2 = 0; + u_int8_t *buf1 = NULL; + u_int32_t *iop_data, *buf2 = NULL; u_int32_t allxfer_len = 0, data_len; if(acb->acb_flags & ACB_F_MESSAGE_WQBUFFER_READ) { @@ -1541,7 +1681,8 @@ static void arcmsr_Write_data_2iop_wqbuffer(struct Ada u_int8_t *iop_data; int32_t allxfer_len=0; - if(acb->adapter_type & (ACB_ADAPTER_TYPE_C | ACB_ADAPTER_TYPE_D)) { + if((acb->adapter_type == ACB_ADAPTER_TYPE_C) || (acb->adapter_type == ACB_ADAPTER_TYPE_D) || + (acb->adapter_type == ACB_ADAPTER_TYPE_E)) { arcmsr_Write_data_2iop_wqbuffer_D(acb); return; } @@ -1694,6 +1835,14 @@ static void arcmsr_dr_handle(struct AdapterControlBloc devicemap += 4; } break; + case ACB_ADAPTER_TYPE_E: + devicemap = offsetof(struct HBE_MessageUnit, msgcode_rwbuffer[ARCMSR_FW_DEVMAP_OFFSET]); + for (target = 0; target < 4; target++) + { + deviceMapCurrent[target]=bus_space_read_4(acb->btag[0], acb->bhandle[0], devicemap); + devicemap += 4; + } + break; } if(acb->acb_flags & ACB_F_BUS_HANG_ON) @@ -1792,6 +1941,18 @@ static void arcmsr_hbd_message_isr(struct AdapterContr ************************************************************************** ************************************************************************** */ +static void arcmsr_hbe_message_isr(struct AdapterControlBlock *acb) { + u_int32_t outbound_message; + + CHIP_REG_WRITE32(HBE_MessageUnit, 0, host_int_status, 0); + outbound_message = CHIP_REG_READ32(HBE_MessageUnit, 0, msgcode_rwbuffer[0]); + if (outbound_message == ARCMSR_SIGNATURE_GET_CONFIG) + arcmsr_dr_handle( acb ); +} +/* +************************************************************************** +************************************************************************** +*/ static void arcmsr_hba_doorbell_isr(struct AdapterControlBlock *acb) { u_int32_t doorbell_status; @@ -1876,6 +2037,35 @@ static void arcmsr_hbd_doorbell_isr(struct AdapterCont ************************************************************************** ************************************************************************** */ +static void arcmsr_hbe_doorbell_isr(struct AdapterControlBlock *acb) +{ + u_int32_t doorbell_status, in_doorbell; + + /* + ******************************************************************* + ** Maybe here we need to check wrqbuffer_lock is lock or not + ** DOORBELL: din! don! + ** check if there are any mail need to pack from firmware + ******************************************************************* + */ + in_doorbell = CHIP_REG_READ32(HBE_MessageUnit, 0, iobound_doorbell); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, host_int_status, 0); /* clear doorbell interrupt */ + doorbell_status = in_doorbell ^ acb->in_doorbell; + if(doorbell_status & ARCMSR_HBEMU_IOP2DRV_DATA_WRITE_OK) { + arcmsr_iop2drv_data_wrote_handle(acb); + } + if(doorbell_status & ARCMSR_HBEMU_IOP2DRV_DATA_READ_OK) { + arcmsr_iop2drv_data_read_handle(acb); + } + if(doorbell_status & ARCMSR_HBEMU_IOP2DRV_MESSAGE_CMD_DONE) { + arcmsr_hbe_message_isr(acb); /* messenger of "driver to iop commands" */ + } + acb->in_doorbell = in_doorbell; +} +/* +************************************************************************** +************************************************************************** +*/ static void arcmsr_hba_postqueue_isr(struct AdapterControlBlock *acb) { u_int32_t flag_srb; @@ -2013,6 +2203,34 @@ static void arcmsr_hbd_postqueue_isr(struct AdapterCon CHIP_REG_READ32(HBD_MessageUnit, 0, outboundlist_interrupt_cause); /*Dummy ioread32 to force pci flush */ } /* +************************************************************************** +************************************************************************** +*/ +static void arcmsr_hbe_postqueue_isr(struct AdapterControlBlock *acb) +{ + u_int16_t error; + uint32_t doneq_index; + uint16_t cmdSMID; + + /* + ***************************************************************************** + ** areca cdb command done + ***************************************************************************** + */ + bus_dmamap_sync(acb->srb_dmat, acb->srb_dmamap, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + doneq_index = acb->doneq_index; + while ((CHIP_REG_READ32(HBE_MessageUnit, 0, reply_post_producer_index) & 0xFFFF) != doneq_index) { + cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID; + error = (acb->pCompletionQ[doneq_index].cmdFlag & ARCMSR_SRBREPLY_FLAG_ERROR_MODE1) ? TRUE : FALSE; + arcmsr_drain_donequeue(acb, (u_int32_t)cmdSMID, error); + doneq_index++; + if (doneq_index >= acb->completionQ_entry) + doneq_index = 0; + } + acb->doneq_index = doneq_index; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, reply_post_consumer_index, doneq_index); +} +/* ********************************************************************** ********************************************************************** */ @@ -2143,6 +2361,37 @@ static void arcmsr_handle_hbd_isr( struct AdapterContr // CHIP_REG_READ32(HBD_MessageUnit, 0, pcief0_int_enable); } /* +********************************************************************** +********************************************************************** +*/ +static void arcmsr_handle_hbe_isr( struct AdapterControlBlock *acb) +{ + u_int32_t host_interrupt_status; + /* + ********************************************* + ** check outbound intstatus + ********************************************* + */ + host_interrupt_status = CHIP_REG_READ32(HBE_MessageUnit, 0, host_int_status) & + (ARCMSR_HBEMU_OUTBOUND_POSTQUEUE_ISR | + ARCMSR_HBEMU_OUTBOUND_DOORBELL_ISR); + if(!host_interrupt_status) { + /*it must be share irq*/ + return; + } + do { + /* MU doorbell interrupts*/ + if(host_interrupt_status & ARCMSR_HBEMU_OUTBOUND_DOORBELL_ISR) { + arcmsr_hbe_doorbell_isr(acb); + } + /* MU post queue interrupts*/ + if(host_interrupt_status & ARCMSR_HBEMU_OUTBOUND_POSTQUEUE_ISR) { + arcmsr_hbe_postqueue_isr(acb); + } + host_interrupt_status = CHIP_REG_READ32(HBE_MessageUnit, 0, host_int_status); + } while (host_interrupt_status & (ARCMSR_HBEMU_OUTBOUND_POSTQUEUE_ISR | ARCMSR_HBEMU_OUTBOUND_DOORBELL_ISR)); +} +/* ****************************************************************************** ****************************************************************************** */ @@ -2161,6 +2410,9 @@ static void arcmsr_interrupt(struct AdapterControlBloc case ACB_ADAPTER_TYPE_D: arcmsr_handle_hbd_isr(acb); break; + case ACB_ADAPTER_TYPE_E: + arcmsr_handle_hbe_isr(acb); + break; default: printf("arcmsr%d: interrupt service," " unknown adapter type =%d\n", acb->pci_unit, acb->adapter_type); @@ -2205,6 +2457,12 @@ static void arcmsr_polling_devmap(void *arg) case ACB_ADAPTER_TYPE_D: CHIP_REG_WRITE32(HBD_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_GET_CONFIG); break; + + case ACB_ADAPTER_TYPE_E: + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_GET_CONFIG); + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_MESSAGE_CMD_DONE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + break; } if((acb->acb_flags & ACB_F_SCSISTOPADAPTER) == 0) @@ -2754,10 +3012,10 @@ static u_int8_t arcmsr_seek_cmd2abort(union ccb *abort if(srb->srb_state == ARCMSR_SRB_START) { if(srb->pccb == abortccb) { srb->srb_state = ARCMSR_SRB_ABORTED; - printf("arcmsr%d:scsi id=%d lun=%d abort srb '%p'" + printf("arcmsr%d:scsi id=%d lun=%jx abort srb '%p'" "outstanding command \n" , acb->pci_unit, abortccb->ccb_h.target_id - , abortccb->ccb_h.target_lun, srb); + , (uintmax_t)abortccb->ccb_h.target_lun, srb); arcmsr_polling_srbdone(acb, srb); /* enable outbound Post Queue, outbound doorbell Interrupt */ arcmsr_enable_allintr(acb, intmask_org); @@ -2883,12 +3141,6 @@ static void arcmsr_action(struct cam_sim *psim, union } break; } - case XPT_TARGET_IO: { - /* target mode not yet support vendor specific commands. */ - pccb->ccb_h.status |= CAM_REQ_CMP; - xpt_done(pccb); - break; - } case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &pccb->cpi; @@ -2913,6 +3165,7 @@ static void arcmsr_action(struct cam_sim *psim, union else cpi->base_transfer_speed = 300000; if((acb->vendor_device_id == PCIDevVenIDARC1880) || + (acb->vendor_device_id == PCIDevVenIDARC1884) || (acb->vendor_device_id == PCIDevVenIDARC1680) || (acb->vendor_device_id == PCIDevVenIDARC1214)) { @@ -2938,7 +3191,6 @@ static void arcmsr_action(struct cam_sim *psim, union pabort_ccb = pccb->cab.abort_ccb; switch (pabort_ccb->ccb_h.func_code) { case XPT_ACCEPT_TARGET_IO: - case XPT_IMMED_NOTIFY: case XPT_CONT_TARGET_IO: if(arcmsr_seek_cmd2abort(pabort_ccb)==TRUE) { pabort_ccb->ccb_h.status |= CAM_REQ_ABORTED; @@ -2998,6 +3250,7 @@ static void arcmsr_action(struct cam_sim *psim, union cts->protocol = PROTO_SCSI; if((acb->vendor_device_id == PCIDevVenIDARC1880) || + (acb->vendor_device_id == PCIDevVenIDARC1884) || (acb->vendor_device_id == PCIDevVenIDARC1680) || (acb->vendor_device_id == PCIDevVenIDARC1214)) { @@ -3157,6 +3410,20 @@ static void arcmsr_start_hbd_bgrb(struct AdapterContro ********************************************************************** ********************************************************************** */ +static void arcmsr_start_hbe_bgrb(struct AdapterControlBlock *acb) +{ + acb->acb_flags |= ACB_F_MSG_START_BGRB; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_START_BGRB); + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_MESSAGE_CMD_DONE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + if(!arcmsr_hbe_wait_msgint_ready(acb)) { + printf("arcmsr%d: wait 'start adapter background rebulid' timeout \n", acb->pci_unit); + } +} +/* +********************************************************************** +********************************************************************** +*/ static void arcmsr_start_adapter_bgrb(struct AdapterControlBlock *acb) { switch (acb->adapter_type) { @@ -3172,6 +3439,9 @@ static void arcmsr_start_adapter_bgrb(struct AdapterCo case ACB_ADAPTER_TYPE_D: arcmsr_start_hbd_bgrb(acb); break; + case ACB_ADAPTER_TYPE_E: + arcmsr_start_hbe_bgrb(acb); + break; } } /* @@ -3210,11 +3480,11 @@ polling_ccb_retry: poll_srb_done = (srb == poll_srb) ? 1:0; if((srb->acb != acb) || (srb->srb_state != ARCMSR_SRB_START)) { if(srb->srb_state == ARCMSR_SRB_ABORTED) { - printf("arcmsr%d: scsi id=%d lun=%d srb='%p'" + printf("arcmsr%d: scsi id=%d lun=%jx srb='%p'" "poll command abort successfully \n" , acb->pci_unit , srb->pccb->ccb_h.target_id - , srb->pccb->ccb_h.target_lun, srb); + , (uintmax_t)srb->pccb->ccb_h.target_lun, srb); srb->pccb->ccb_h.status |= CAM_REQ_ABORTED; arcmsr_srb_complete(srb, 1); continue; @@ -3269,11 +3539,11 @@ polling_ccb_retry: poll_srb_done = (srb == poll_srb) ? 1:0; if((srb->acb != acb) || (srb->srb_state != ARCMSR_SRB_START)) { if(srb->srb_state == ARCMSR_SRB_ABORTED) { - printf("arcmsr%d: scsi id=%d lun=%d srb='%p'" + printf("arcmsr%d: scsi id=%d lun=%jx srb='%p'" "poll command abort successfully \n" , acb->pci_unit , srb->pccb->ccb_h.target_id - , srb->pccb->ccb_h.target_lun, srb); + , (uintmax_t)srb->pccb->ccb_h.target_lun, srb); srb->pccb->ccb_h.status |= CAM_REQ_ABORTED; arcmsr_srb_complete(srb, 1); continue; @@ -3324,8 +3594,8 @@ polling_ccb_retry: poll_srb_done = (srb == poll_srb) ? 1:0; if((srb->acb != acb) || (srb->srb_state != ARCMSR_SRB_START)) { if(srb->srb_state == ARCMSR_SRB_ABORTED) { - printf("arcmsr%d: scsi id=%d lun=%d srb='%p'poll command abort successfully \n" - , acb->pci_unit, srb->pccb->ccb_h.target_id, srb->pccb->ccb_h.target_lun, srb); + printf("arcmsr%d: scsi id=%d lun=%jx srb='%p'poll command abort successfully \n" + , acb->pci_unit, srb->pccb->ccb_h.target_id, (uintmax_t)srb->pccb->ccb_h.target_lun, srb); srb->pccb->ccb_h.status |= CAM_REQ_ABORTED; arcmsr_srb_complete(srb, 1); continue; @@ -3380,8 +3650,8 @@ polling_ccb_retry: poll_srb_done = (srb == poll_srb) ? 1:0; if((srb->acb != acb) || (srb->srb_state != ARCMSR_SRB_START)) { if(srb->srb_state == ARCMSR_SRB_ABORTED) { - printf("arcmsr%d: scsi id=%d lun=%d srb='%p'poll command abort successfully \n" - , acb->pci_unit, srb->pccb->ccb_h.target_id, srb->pccb->ccb_h.target_lun, srb); + printf("arcmsr%d: scsi id=%d lun=%jx srb='%p'poll command abort successfully \n" + , acb->pci_unit, srb->pccb->ccb_h.target_id, (uintmax_t)srb->pccb->ccb_h.target_lun, srb); srb->pccb->ccb_h.status |= CAM_REQ_ABORTED; arcmsr_srb_complete(srb, 1); continue; @@ -3395,8 +3665,63 @@ polling_ccb_retry: } /* ********************************************************************** +** ********************************************************************** */ +static void arcmsr_polling_hbe_srbdone(struct AdapterControlBlock *acb, struct CommandControlBlock *poll_srb) +{ + struct CommandControlBlock *srb; + u_int32_t poll_srb_done=0, poll_count=0, doneq_index; + u_int16_t error, cmdSMID; + +polling_ccb_retry: + poll_count++; + bus_dmamap_sync(acb->srb_dmat, acb->srb_dmamap, BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE); + while(1) { + doneq_index = acb->doneq_index; + if((CHIP_REG_READ32(HBE_MessageUnit, 0, reply_post_producer_index) & 0xFFFF) == doneq_index) { + if(poll_srb_done) { + break;/*chip FIFO no ccb for completion already*/ + } else { + UDELAY(25000); + if ((poll_count > 100) && (poll_srb != NULL)) { + break; + } + if (acb->srboutstandingcount == 0) { + break; + } + goto polling_ccb_retry; + } + } + cmdSMID = acb->pCompletionQ[doneq_index].cmdSMID; + doneq_index++; + if (doneq_index >= acb->completionQ_entry) + doneq_index = 0; + acb->doneq_index = doneq_index; + srb = acb->psrb_pool[cmdSMID]; + error = (acb->pCompletionQ[doneq_index].cmdFlag & ARCMSR_SRBREPLY_FLAG_ERROR_MODE1) ? TRUE : FALSE; + if (poll_srb != NULL) + poll_srb_done = (srb == poll_srb) ? 1:0; + if((srb->acb != acb) || (srb->srb_state != ARCMSR_SRB_START)) { + if(srb->srb_state == ARCMSR_SRB_ABORTED) { + printf("arcmsr%d: scsi id=%d lun=%jx srb='%p'poll command abort successfully \n" + , acb->pci_unit, srb->pccb->ccb_h.target_id, (uintmax_t)srb->pccb->ccb_h.target_lun, srb); + srb->pccb->ccb_h.status |= CAM_REQ_ABORTED; + arcmsr_srb_complete(srb, 1); + continue; + } + printf("arcmsr%d: polling get an illegal srb command done srb='%p'srboutstandingcount=%d \n" + , acb->pci_unit, srb, acb->srboutstandingcount); + continue; + } + arcmsr_report_srb_state(acb, srb, error); + } /*drain reply FIFO*/ + CHIP_REG_WRITE32(HBE_MessageUnit, 0, reply_post_producer_index, doneq_index); +} +/* +********************************************************************** +********************************************************************** +*/ static void arcmsr_polling_srbdone(struct AdapterControlBlock *acb, struct CommandControlBlock *poll_srb) { switch (acb->adapter_type) { @@ -3416,6 +3741,10 @@ static void arcmsr_polling_srbdone(struct AdapterContr arcmsr_polling_hbd_srbdone(acb, poll_srb); } break; + case ACB_ADAPTER_TYPE_E: { + arcmsr_polling_hbe_srbdone(acb, poll_srb); + } + break; } } /* @@ -3622,6 +3951,58 @@ static void arcmsr_get_hbd_config(struct AdapterContro ********************************************************************** ********************************************************************** */ +static void arcmsr_get_hbe_config(struct AdapterControlBlock *acb) +{ + char *acb_firm_model = acb->firm_model; + char *acb_firm_version = acb->firm_version; + char *acb_device_map = acb->device_map; + size_t iop_firm_model = offsetof(struct HBE_MessageUnit,msgcode_rwbuffer[ARCMSR_FW_MODEL_OFFSET]); /*firm_model,15,60-67*/ + size_t iop_firm_version = offsetof(struct HBE_MessageUnit,msgcode_rwbuffer[ARCMSR_FW_VERS_OFFSET]); /*firm_version,17,68-83*/ + size_t iop_device_map = offsetof(struct HBE_MessageUnit,msgcode_rwbuffer[ARCMSR_FW_DEVMAP_OFFSET]); + int i; + + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_GET_CONFIG); + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_MESSAGE_CMD_DONE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + if(!arcmsr_hbe_wait_msgint_ready(acb)) { + printf("arcmsr%d: wait 'get adapter firmware miscellaneous data' timeout \n", acb->pci_unit); + } + + i = 0; + while(i < 8) { + *acb_firm_model = bus_space_read_1(acb->btag[0], acb->bhandle[0], iop_firm_model+i); + /* 8 bytes firm_model, 15, 60-67*/ + acb_firm_model++; + i++; + } + i = 0; + while(i < 16) { + *acb_firm_version = bus_space_read_1(acb->btag[0], acb->bhandle[0], iop_firm_version+i); + /* 16 bytes firm_version, 17, 68-83*/ + acb_firm_version++; + i++; + } + i = 0; + while(i < 16) { + *acb_device_map = bus_space_read_1(acb->btag[0], acb->bhandle[0], iop_device_map+i); + acb_device_map++; + i++; + } + printf("Areca RAID adapter%d: %s F/W version %s \n", acb->pci_unit, acb->firm_model, acb->firm_version); + acb->firm_request_len = CHIP_REG_READ32(HBE_MessageUnit, 0, msgcode_rwbuffer[1]); /*firm_request_len, 1, 04-07*/ + acb->firm_numbers_queue = CHIP_REG_READ32(HBE_MessageUnit, 0, msgcode_rwbuffer[2]); /*firm_numbers_queue, 2, 08-11*/ + acb->firm_sdram_size = CHIP_REG_READ32(HBE_MessageUnit, 0, msgcode_rwbuffer[3]); /*firm_sdram_size, 3, 12-15*/ + acb->firm_ide_channels = CHIP_REG_READ32(HBE_MessageUnit, 0, msgcode_rwbuffer[4]); /*firm_ide_channels, 4, 16-19*/ + acb->firm_cfg_version = CHIP_REG_READ32(HBE_MessageUnit, 0, msgcode_rwbuffer[ARCMSR_FW_CFGVER_OFFSET]); /*firm_cfg_version, 25, */ + if(acb->firm_numbers_queue > ARCMSR_MAX_OUTSTANDING_CMD) + acb->maxOutstanding = ARCMSR_MAX_OUTSTANDING_CMD - 1; + else + acb->maxOutstanding = acb->firm_numbers_queue - 1; +} +/* +********************************************************************** +********************************************************************** +*/ static void arcmsr_get_firmware_spec(struct AdapterControlBlock *acb) { switch (acb->adapter_type) { @@ -3641,6 +4022,10 @@ static void arcmsr_get_firmware_spec(struct AdapterCon arcmsr_get_hbd_config(acb); } break; + case ACB_ADAPTER_TYPE_E: { + arcmsr_get_hbe_config(acb); + } + break; } } /* @@ -3702,6 +4087,18 @@ static void arcmsr_wait_firmware_ready( struct Adapter } } break; + case ACB_ADAPTER_TYPE_E: { + while ((CHIP_REG_READ32(HBE_MessageUnit, 0, outbound_msgaddr1) & ARCMSR_HBEMU_MESSAGE_FIRMWARE_OK) == 0) + { + if (timeout++ > 4000) /* (4000*15)/1000 = 60 sec */ + { + printf( "arcmsr%d:timed out waiting for firmware ready\n", acb->pci_unit); + return; + } + UDELAY(15000); /* wait 15 milli-seconds */ + } + } + break; } } /* @@ -3745,6 +4142,14 @@ static void arcmsr_clear_doorbell_queue_buffer( struct } break; + case ACB_ADAPTER_TYPE_E: { + /* empty doorbell Qbuffer if door bell ringed */ + acb->in_doorbell = CHIP_REG_READ32(HBE_MessageUnit, 0, iobound_doorbell); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, host_int_status, 0); /*clear doorbell interrupt */ + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_DATA_READ_OK; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + } + break; } } /* @@ -3851,6 +4256,27 @@ static u_int32_t arcmsr_iop_confirm(struct AdapterCont } } break; + case ACB_ADAPTER_TYPE_E: { + u_int32_t cdb_phyaddr_lo32; + cdb_phyaddr_lo32 = srb_phyaddr_lo32 + offsetof(struct CommandControlBlock, arcmsr_cdb); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[0], ARCMSR_SIGNATURE_SET_CONFIG); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[1], ARCMSR_SIGNATURE_1884); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[2], cdb_phyaddr_lo32); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[3], srb_phyaddr_hi32); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[4], SRB_SIZE); + cdb_phyaddr_lo32 = srb_phyaddr_lo32 + ARCMSR_SRBS_POOL_SIZE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[5], cdb_phyaddr_lo32); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[6], srb_phyaddr_hi32); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, msgcode_rwbuffer[7], COMPLETION_Q_POOL_SIZE); + CHIP_REG_WRITE32(HBE_MessageUnit, 0, inbound_msgaddr0, ARCMSR_INBOUND_MESG0_SET_CONFIG); + acb->out_doorbell ^= ARCMSR_HBEMU_DRV2IOP_MESSAGE_CMD_DONE; + CHIP_REG_WRITE32(HBE_MessageUnit, 0, iobound_doorbell, acb->out_doorbell); + if(!arcmsr_hbe_wait_msgint_ready(acb)) { + printf( "arcmsr%d: 'set srb high part physical address' timeout \n", acb->pci_unit); + return FALSE; + } + } + break; } return (TRUE); } @@ -3860,21 +4286,14 @@ static u_int32_t arcmsr_iop_confirm(struct AdapterCont */ static void arcmsr_enable_eoi_mode(struct AdapterControlBlock *acb) { - switch (acb->adapter_type) + if (acb->adapter_type == ACB_ADAPTER_TYPE_B) { - case ACB_ADAPTER_TYPE_A: - case ACB_ADAPTER_TYPE_C: - case ACB_ADAPTER_TYPE_D: - break; - case ACB_ADAPTER_TYPE_B: { - struct HBB_MessageUnit *phbbmu = (struct HBB_MessageUnit *)acb->pmu; - WRITE_CHIP_REG32(0, phbbmu->drv2iop_doorbell, ARCMSR_MESSAGE_ACTIVE_EOI_MODE); - if(!arcmsr_hbb_wait_msgint_ready(acb)) { - printf( "arcmsr%d: 'iop enable eoi mode' timeout \n", acb->pci_unit); - return; - } + struct HBB_MessageUnit *phbbmu = (struct HBB_MessageUnit *)acb->pmu; + WRITE_CHIP_REG32(0, phbbmu->drv2iop_doorbell, ARCMSR_MESSAGE_ACTIVE_EOI_MODE); + if(!arcmsr_hbb_wait_msgint_ready(acb)) { + printf( "arcmsr%d: 'iop enable eoi mode' timeout \n", acb->pci_unit); + return; } - break; } } /* @@ -3920,7 +4339,8 @@ static void arcmsr_map_free_srb(void *arg, bus_dma_seg " srb dmamap bus_dmamap_create error\n", acb->pci_unit); return; } - if((acb->adapter_type == ACB_ADAPTER_TYPE_C) || (acb->adapter_type == ACB_ADAPTER_TYPE_D)) + if((acb->adapter_type == ACB_ADAPTER_TYPE_C) || (acb->adapter_type == ACB_ADAPTER_TYPE_D) + || (acb->adapter_type == ACB_ADAPTER_TYPE_E)) { srb_tmp->cdb_phyaddr_low = srb_phyaddr; srb_tmp->cdb_phyaddr_high = (u_int32_t)((srb_phyaddr >> 16) >> 16); @@ -3928,10 +4348,12 @@ static void arcmsr_map_free_srb(void *arg, bus_dma_seg else srb_tmp->cdb_phyaddr_low = srb_phyaddr >> 5; srb_tmp->acb = acb; + srb_tmp->smid = i << 16; acb->srbworkingQ[i] = acb->psrb_pool[i] = srb_tmp; srb_phyaddr = srb_phyaddr + SRB_SIZE; srb_tmp = (struct CommandControlBlock *)((unsigned long)srb_tmp + SRB_SIZE); } + acb->pCompletionQ = (pCompletion_Q)srb_tmp; acb->vir2phy_offset = (unsigned long)srb_tmp - (unsigned long)srb_phyaddr; } /* @@ -3999,6 +4421,12 @@ static u_int32_t arcmsr_initialize(device_t dev) max_coherent_size = ARCMSR_SRBS_POOL_SIZE; } break; + case PCIDevVenIDARC1884: + acb->adapter_type = ACB_ADAPTER_TYPE_E; + acb->adapter_bus_speed = ACB_BUS_SPEED_12G; + max_coherent_size = ARCMSR_SRBS_POOL_SIZE + COMPLETION_Q_POOL_SIZE; + acb->completionQ_entry = COMPLETION_Q_POOL_SIZE / sizeof(struct deliver_completeQ); + break; case PCIDevVenIDARC1214: { acb->adapter_type = ACB_ADAPTER_TYPE_D; acb->adapter_bus_speed = ACB_BUS_SPEED_6G; @@ -4147,141 +4575,177 @@ static u_int32_t arcmsr_initialize(device_t dev) pci_write_config(dev, PCIR_COMMAND, pci_command, 2); switch(acb->adapter_type) { case ACB_ADAPTER_TYPE_A: { - u_int32_t rid0 = PCIR_BAR(0); - vm_offset_t mem_base0; + u_int32_t rid0 = PCIR_BAR(0); + vm_offset_t mem_base0; - acb->sys_res_arcmsr[0] = bus_alloc_resource_any(dev,SYS_RES_MEMORY, &rid0, RF_ACTIVE); - if(acb->sys_res_arcmsr[0] == NULL) { - arcmsr_free_resource(acb); - printf("arcmsr%d: bus_alloc_resource failure!\n", device_get_unit(dev)); - return ENOMEM; - } - if(rman_get_start(acb->sys_res_arcmsr[0]) <= 0) { - arcmsr_free_resource(acb); - printf("arcmsr%d: rman_get_start failure!\n", device_get_unit(dev)); - return ENXIO; - } - mem_base0 = (vm_offset_t) rman_get_virtual(acb->sys_res_arcmsr[0]); - if(mem_base0 == 0) { - arcmsr_free_resource(acb); - printf("arcmsr%d: rman_get_virtual failure!\n", device_get_unit(dev)); - return ENXIO; - } - acb->btag[0] = rman_get_bustag(acb->sys_res_arcmsr[0]); - acb->bhandle[0] = rman_get_bushandle(acb->sys_res_arcmsr[0]); - acb->pmu = (struct MessageUnit_UNION *)mem_base0; + acb->sys_res_arcmsr[0] = bus_alloc_resource_any(dev,SYS_RES_MEMORY, &rid0, RF_ACTIVE); + if(acb->sys_res_arcmsr[0] == NULL) { + arcmsr_free_resource(acb); + printf("arcmsr%d: bus_alloc_resource failure!\n", device_get_unit(dev)); + return ENOMEM; } + if(rman_get_start(acb->sys_res_arcmsr[0]) <= 0) { + arcmsr_free_resource(acb); + printf("arcmsr%d: rman_get_start failure!\n", device_get_unit(dev)); + return ENXIO; + } + mem_base0 = (vm_offset_t) rman_get_virtual(acb->sys_res_arcmsr[0]); + if(mem_base0 == 0) { + arcmsr_free_resource(acb); + printf("arcmsr%d: rman_get_virtual failure!\n", device_get_unit(dev)); + return ENXIO; + } + acb->btag[0] = rman_get_bustag(acb->sys_res_arcmsr[0]); + acb->bhandle[0] = rman_get_bushandle(acb->sys_res_arcmsr[0]); + acb->pmu = (struct MessageUnit_UNION *)mem_base0; + acb->rid = 0; + } break; case ACB_ADAPTER_TYPE_B: { - struct HBB_MessageUnit *phbbmu; - struct CommandControlBlock *freesrb; - u_int32_t rid[]={ PCIR_BAR(0), PCIR_BAR(2) }; - vm_offset_t mem_base[]={0,0}; - u_long size; - if (vendor_dev_id == PCIDevVenIDARC1203) - size = sizeof(struct HBB_DOORBELL_1203); - else - size = sizeof(struct HBB_DOORBELL); - for(i=0; i < 2; i++) { - if(i == 0) { - acb->sys_res_arcmsr[i] = bus_alloc_resource_any(dev,SYS_RES_MEMORY, &rid[i], - RF_ACTIVE); - } else { - acb->sys_res_arcmsr[i] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid[i], - RF_ACTIVE); - } - if(acb->sys_res_arcmsr[i] == NULL) { - arcmsr_free_resource(acb); - printf("arcmsr%d: bus_alloc_resource %d failure!\n", device_get_unit(dev), i); - return ENOMEM; - } - if(rman_get_start(acb->sys_res_arcmsr[i]) <= 0) { - arcmsr_free_resource(acb); - printf("arcmsr%d: rman_get_start %d failure!\n", device_get_unit(dev), i); - return ENXIO; - } - mem_base[i] = (vm_offset_t) rman_get_virtual(acb->sys_res_arcmsr[i]); - if(mem_base[i] == 0) { *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201707170628.v6H6SYvw089263>