| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright 2018 Nexenta Systems, Inc. All rights reserved. |
| * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. |
| */ |
| |
| /* |
| * AHCI (Advanced Host Controller Interface) SATA HBA Driver |
| * |
| * Power Management Support |
| * ------------------------ |
| * |
| * At the moment, the ahci driver only implements suspend/resume to |
| * support Suspend to RAM on X86 feature. Device power management isn't |
| * implemented, link power management is disabled, and hot plug isn't |
| * allowed during the period from suspend to resume. |
| * |
| * For s/r support, the ahci driver only need to implement DDI_SUSPEND |
| * and DDI_RESUME entries, and don't need to take care of new requests |
| * sent down after suspend because the target driver (sd) has already |
| * handled these conditions, and blocked these requests. For the detailed |
| * information, please check with sdopen, sdclose and sdioctl routines. |
| * |
| */ |
| |
| #include <sys/note.h> |
| #include <sys/scsi/scsi.h> |
| #include <sys/pci.h> |
| #include <sys/disp.h> |
| #include <sys/sata/sata_hba.h> |
| #include <sys/sata/adapters/ahci/ahcireg.h> |
| #include <sys/sata/adapters/ahci/ahcivar.h> |
| |
| /* |
| * FMA header files |
| */ |
| #include <sys/ddifm.h> |
| #include <sys/fm/protocol.h> |
| #include <sys/fm/util.h> |
| #include <sys/fm/io/ddi.h> |
| |
| /* |
| * This is the string displayed by modinfo, etc. |
| */ |
| static char ahci_ident[] = "ahci driver"; |
| |
| /* |
| * Function prototypes for driver entry points |
| */ |
| static int ahci_attach(dev_info_t *, ddi_attach_cmd_t); |
| static int ahci_detach(dev_info_t *, ddi_detach_cmd_t); |
| static int ahci_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); |
| static int ahci_quiesce(dev_info_t *); |
| |
| /* |
| * Function prototypes for SATA Framework interfaces |
| */ |
| static int ahci_register_sata_hba_tran(ahci_ctl_t *, uint32_t); |
| static int ahci_unregister_sata_hba_tran(ahci_ctl_t *); |
| |
| static int ahci_tran_probe_port(dev_info_t *, sata_device_t *); |
| static int ahci_tran_start(dev_info_t *, sata_pkt_t *spkt); |
| static int ahci_tran_abort(dev_info_t *, sata_pkt_t *, int); |
| static int ahci_tran_reset_dport(dev_info_t *, sata_device_t *); |
| static int ahci_tran_hotplug_port_activate(dev_info_t *, sata_device_t *); |
| static int ahci_tran_hotplug_port_deactivate(dev_info_t *, sata_device_t *); |
| #if defined(__lock_lint) |
| static int ahci_selftest(dev_info_t *, sata_device_t *); |
| #endif |
| |
| /* |
| * FMA Prototypes |
| */ |
| static void ahci_fm_init(ahci_ctl_t *); |
| static void ahci_fm_fini(ahci_ctl_t *); |
| static int ahci_fm_error_cb(dev_info_t *, ddi_fm_error_t *, const void*); |
| int ahci_check_acc_handle(ddi_acc_handle_t); |
| int ahci_check_dma_handle(ddi_dma_handle_t); |
| void ahci_fm_ereport(ahci_ctl_t *, char *); |
| static int ahci_check_all_handle(ahci_ctl_t *); |
| static int ahci_check_ctl_handle(ahci_ctl_t *); |
| static int ahci_check_port_handle(ahci_ctl_t *, int); |
| static int ahci_check_slot_handle(ahci_port_t *, int); |
| |
| /* |
| * Local function prototypes |
| */ |
| static int ahci_setup_port_base_addresses(ahci_ctl_t *, ahci_port_t *); |
| static int ahci_alloc_ports_state(ahci_ctl_t *); |
| static void ahci_dealloc_ports_state(ahci_ctl_t *); |
| static int ahci_alloc_port_state(ahci_ctl_t *, uint8_t); |
| static void ahci_dealloc_port_state(ahci_ctl_t *, uint8_t); |
| static int ahci_alloc_rcvd_fis(ahci_ctl_t *, ahci_port_t *); |
| static void ahci_dealloc_rcvd_fis(ahci_port_t *); |
| static int ahci_alloc_cmd_list(ahci_ctl_t *, ahci_port_t *); |
| static void ahci_dealloc_cmd_list(ahci_ctl_t *, ahci_port_t *); |
| static int ahci_alloc_cmd_tables(ahci_ctl_t *, ahci_port_t *); |
| static void ahci_dealloc_cmd_tables(ahci_ctl_t *, ahci_port_t *); |
| static void ahci_alloc_pmult(ahci_ctl_t *, ahci_port_t *); |
| static void ahci_dealloc_pmult(ahci_ctl_t *, ahci_port_t *); |
| |
| static int ahci_initialize_controller(ahci_ctl_t *); |
| static void ahci_uninitialize_controller(ahci_ctl_t *); |
| static int ahci_initialize_port(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *); |
| static int ahci_config_space_init(ahci_ctl_t *); |
| static void ahci_staggered_spin_up(ahci_ctl_t *, uint8_t); |
| |
| static void ahci_drain_ports_taskq(ahci_ctl_t *); |
| static int ahci_rdwr_pmult(ahci_ctl_t *, ahci_addr_t *, uint8_t, uint32_t *, |
| uint8_t); |
| static int ahci_read_pmult(ahci_ctl_t *, ahci_addr_t *, uint8_t, uint32_t *); |
| static int ahci_write_pmult(ahci_ctl_t *, ahci_addr_t *, uint8_t, uint32_t); |
| static int ahci_update_pmult_pscr(ahci_ctl_t *, ahci_addr_t *, |
| sata_device_t *); |
| static int ahci_update_pmult_gscr(ahci_ctl_t *, ahci_addr_t *, |
| sata_pmult_gscr_t *); |
| static int ahci_initialize_pmult(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *, |
| sata_device_t *); |
| static int ahci_initialize_pmport(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *); |
| static int ahci_probe_pmult(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *); |
| static int ahci_probe_pmport(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *, |
| sata_device_t *); |
| |
| static void ahci_disable_interface_pm(ahci_ctl_t *, uint8_t); |
| static int ahci_start_port(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static void ahci_find_dev_signature(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *); |
| static void ahci_update_sata_registers(ahci_ctl_t *, uint8_t, sata_device_t *); |
| static int ahci_deliver_satapkt(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *, sata_pkt_t *); |
| static int ahci_do_sync_start(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *, sata_pkt_t *); |
| static int ahci_claim_free_slot(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *, int); |
| static void ahci_copy_err_cnxt(sata_cmd_t *, ahci_fis_d2h_register_t *); |
| static void ahci_copy_ncq_err_page(sata_cmd_t *, |
| struct sata_ncq_error_recovery_page *); |
| static void ahci_copy_out_regs(sata_cmd_t *, ahci_fis_d2h_register_t *); |
| static void ahci_add_doneq(ahci_port_t *, sata_pkt_t *, int); |
| static void ahci_flush_doneq(ahci_port_t *); |
| |
| static int ahci_software_reset(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *); |
| static int ahci_hba_reset(ahci_ctl_t *); |
| static int ahci_port_reset(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *); |
| static int ahci_pmport_reset(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *); |
| static void ahci_reject_all_abort_pkts(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static int ahci_reset_device_reject_pkts(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *); |
| static int ahci_reset_pmdevice_reject_pkts(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *); |
| static int ahci_reset_port_reject_pkts(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *); |
| static int ahci_reset_hba_reject_pkts(ahci_ctl_t *); |
| static int ahci_put_port_into_notrunning_state(ahci_ctl_t *, ahci_port_t *, |
| uint8_t); |
| static int ahci_restart_port_wait_till_ready(ahci_ctl_t *, ahci_port_t *, |
| uint8_t, int, int *); |
| static void ahci_mop_commands(ahci_ctl_t *, ahci_port_t *, uint32_t, |
| uint32_t, uint32_t, uint32_t, uint32_t); |
| static uint32_t ahci_get_rdlogext_data(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static void ahci_get_rqsense_data(ahci_ctl_t *, ahci_port_t *, |
| uint8_t, sata_pkt_t *); |
| static void ahci_fatal_error_recovery_handler(ahci_ctl_t *, ahci_port_t *, |
| ahci_addr_t *, uint32_t); |
| static void ahci_pmult_error_recovery_handler(ahci_ctl_t *, ahci_port_t *, |
| uint8_t, uint32_t); |
| static void ahci_timeout_pkts(ahci_ctl_t *, ahci_port_t *, |
| uint8_t, uint32_t); |
| static void ahci_events_handler(void *); |
| static void ahci_watchdog_handler(ahci_ctl_t *); |
| |
| static uint_t ahci_intr(caddr_t, caddr_t); |
| static void ahci_port_intr(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static int ahci_add_intrs(ahci_ctl_t *, int); |
| static void ahci_rem_intrs(ahci_ctl_t *); |
| static void ahci_enable_all_intrs(ahci_ctl_t *); |
| static void ahci_disable_all_intrs(ahci_ctl_t *); |
| static void ahci_enable_port_intrs(ahci_ctl_t *, uint8_t); |
| static void ahci_disable_port_intrs(ahci_ctl_t *, uint8_t); |
| |
| static int ahci_intr_cmd_cmplt(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static int ahci_intr_set_device_bits(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static int ahci_intr_ncq_events(ahci_ctl_t *, ahci_port_t *, ahci_addr_t *); |
| static int ahci_intr_pmult_sntf_events(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static int ahci_intr_port_connect_change(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static int ahci_intr_device_mechanical_presence_status(ahci_ctl_t *, |
| ahci_port_t *, uint8_t); |
| static int ahci_intr_phyrdy_change(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| static int ahci_intr_non_fatal_error(ahci_ctl_t *, ahci_port_t *, |
| uint8_t, uint32_t); |
| static int ahci_intr_fatal_error(ahci_ctl_t *, ahci_port_t *, |
| uint8_t, uint32_t); |
| static int ahci_intr_cold_port_detect(ahci_ctl_t *, ahci_port_t *, uint8_t); |
| |
| static void ahci_get_ahci_addr(ahci_ctl_t *, sata_device_t *, ahci_addr_t *); |
| static int ahci_get_num_implemented_ports(uint32_t); |
| static void ahci_log_fatal_error_message(ahci_ctl_t *, uint8_t, uint32_t); |
| static void ahci_dump_commands(ahci_ctl_t *, uint8_t, uint32_t); |
| static void ahci_log_serror_message(ahci_ctl_t *, uint8_t, uint32_t, int); |
| #if AHCI_DEBUG |
| static void ahci_log(ahci_ctl_t *, uint_t, char *, ...); |
| #endif |
| |
| |
| /* |
| * DMA attributes for the data buffer |
| * |
| * dma_attr_addr_hi will be changed to 0xffffffffull if the HBA |
| * does not support 64-bit addressing |
| */ |
| static ddi_dma_attr_t buffer_dma_attr = { |
| DMA_ATTR_V0, /* dma_attr_version */ |
| 0x0ull, /* dma_attr_addr_lo: lowest bus address */ |
| 0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */ |
| 0x3fffffull, /* dma_attr_count_max i.e. for one cookie */ |
| 0x2ull, /* dma_attr_align: word aligned */ |
| 1, /* dma_attr_burstsizes */ |
| 1, /* dma_attr_minxfer */ |
| 0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */ |
| 0xffffffffull, /* dma_attr_seg */ |
| AHCI_PRDT_NUMBER, /* dma_attr_sgllen */ |
| 512, /* dma_attr_granular */ |
| 0, /* dma_attr_flags */ |
| }; |
| |
| /* |
| * DMA attributes for the rcvd FIS |
| * |
| * dma_attr_addr_hi will be changed to 0xffffffffull if the HBA |
| * does not support 64-bit addressing |
| */ |
| static ddi_dma_attr_t rcvd_fis_dma_attr = { |
| DMA_ATTR_V0, /* dma_attr_version */ |
| 0x0ull, /* dma_attr_addr_lo: lowest bus address */ |
| 0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */ |
| 0xffffffffull, /* dma_attr_count_max i.e. for one cookie */ |
| 0x100ull, /* dma_attr_align: 256-byte aligned */ |
| 1, /* dma_attr_burstsizes */ |
| 1, /* dma_attr_minxfer */ |
| 0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */ |
| 0xffffffffull, /* dma_attr_seg */ |
| 1, /* dma_attr_sgllen */ |
| 1, /* dma_attr_granular */ |
| 0, /* dma_attr_flags */ |
| }; |
| |
| /* |
| * DMA attributes for the command list |
| * |
| * dma_attr_addr_hi will be changed to 0xffffffffull if the HBA |
| * does not support 64-bit addressing |
| */ |
| static ddi_dma_attr_t cmd_list_dma_attr = { |
| DMA_ATTR_V0, /* dma_attr_version */ |
| 0x0ull, /* dma_attr_addr_lo: lowest bus address */ |
| 0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */ |
| 0xffffffffull, /* dma_attr_count_max i.e. for one cookie */ |
| 0x400ull, /* dma_attr_align: 1K-byte aligned */ |
| 1, /* dma_attr_burstsizes */ |
| 1, /* dma_attr_minxfer */ |
| 0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */ |
| 0xffffffffull, /* dma_attr_seg */ |
| 1, /* dma_attr_sgllen */ |
| 1, /* dma_attr_granular */ |
| 0, /* dma_attr_flags */ |
| }; |
| |
| /* |
| * DMA attributes for cmd tables |
| * |
| * dma_attr_addr_hi will be changed to 0xffffffffull if the HBA |
| * does not support 64-bit addressing |
| */ |
| static ddi_dma_attr_t cmd_table_dma_attr = { |
| DMA_ATTR_V0, /* dma_attr_version */ |
| 0x0ull, /* dma_attr_addr_lo: lowest bus address */ |
| 0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */ |
| 0xffffffffull, /* dma_attr_count_max i.e. for one cookie */ |
| 0x80ull, /* dma_attr_align: 128-byte aligned */ |
| 1, /* dma_attr_burstsizes */ |
| 1, /* dma_attr_minxfer */ |
| 0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */ |
| 0xffffffffull, /* dma_attr_seg */ |
| 1, /* dma_attr_sgllen */ |
| 1, /* dma_attr_granular */ |
| 0, /* dma_attr_flags */ |
| }; |
| |
| |
| /* Device access attributes */ |
| static ddi_device_acc_attr_t accattr = { |
| DDI_DEVICE_ATTR_V1, |
| DDI_STRUCTURE_LE_ACC, |
| DDI_STRICTORDER_ACC, |
| DDI_DEFAULT_ACC |
| }; |
| |
| |
| static struct dev_ops ahcictl_dev_ops = { |
| DEVO_REV, /* devo_rev */ |
| 0, /* refcnt */ |
| ahci_getinfo, /* info */ |
| nulldev, /* identify */ |
| nulldev, /* probe */ |
| ahci_attach, /* attach */ |
| ahci_detach, /* detach */ |
| nodev, /* no reset */ |
| (struct cb_ops *)0, /* driver operations */ |
| NULL, /* bus operations */ |
| NULL, /* power */ |
| ahci_quiesce, /* quiesce */ |
| }; |
| |
| static sata_tran_hotplug_ops_t ahci_tran_hotplug_ops = { |
| SATA_TRAN_HOTPLUG_OPS_REV_1, |
| ahci_tran_hotplug_port_activate, |
| ahci_tran_hotplug_port_deactivate |
| }; |
| |
| extern struct mod_ops mod_driverops; |
| |
| static struct modldrv modldrv = { |
| &mod_driverops, /* driverops */ |
| ahci_ident, /* short description */ |
| &ahcictl_dev_ops, /* driver ops */ |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, |
| &modldrv, |
| NULL |
| }; |
| |
| /* The following variables are watchdog handler related */ |
| static clock_t ahci_watchdog_timeout = 5; /* 5 seconds */ |
| static clock_t ahci_watchdog_tick; |
| |
| /* |
| * This static variable indicates the size of command table, |
| * and it's changeable with prdt number, which ahci_dma_prdt_number |
| * indicates. |
| */ |
| static size_t ahci_cmd_table_size; |
| |
| /* |
| * The below global variables are tunable via /etc/system |
| * |
| * ahci_dma_prdt_number |
| * ahci_msi_enabled |
| * ahci_buf_64bit_dma |
| * ahci_commu_64bit_dma |
| */ |
| |
| /* The number of Physical Region Descriptor Table(PRDT) in Command Table */ |
| int ahci_dma_prdt_number = AHCI_PRDT_NUMBER; |
| |
| /* AHCI MSI is tunable */ |
| boolean_t ahci_msi_enabled = B_TRUE; |
| |
| /* |
| * 64-bit dma addressing for data buffer is tunable |
| * |
| * The variable controls only the below value: |
| * DBAU (upper 32-bits physical address of data block) |
| */ |
| boolean_t ahci_buf_64bit_dma = B_TRUE; |
| |
| /* |
| * 64-bit dma addressing for communication system descriptors is tunable |
| * |
| * The variable controls the below three values: |
| * |
| * PxCLBU (upper 32-bits for the command list base physical address) |
| * PxFBU (upper 32-bits for the received FIS base physical address) |
| * CTBAU (upper 32-bits of command table base) |
| */ |
| boolean_t ahci_commu_64bit_dma = B_TRUE; |
| |
| /* |
| * By default, 64-bit dma for data buffer will be disabled for AMD/ATI SB600 |
| * chipset. If the users want to have a try with 64-bit dma, please change |
| * the below variable value to enable it. |
| */ |
| boolean_t sb600_buf_64bit_dma_disable = B_TRUE; |
| |
| /* |
| * By default, 64-bit dma for command buffer will be disabled for AMD/ATI |
| * SB600/700/710/750/800. If the users want to have a try with 64-bit dma, |
| * please change the below value to enable it. |
| */ |
| boolean_t sbxxx_commu_64bit_dma_disable = B_TRUE; |
| |
| |
| /* |
| * End of global tunable variable definition |
| */ |
| |
| #if AHCI_DEBUG |
| uint32_t ahci_debug_flags = 0; |
| #else |
| uint32_t ahci_debug_flags = (AHCIDBG_ERRS|AHCIDBG_TIMEOUT); |
| #endif |
| |
| |
| #if AHCI_DEBUG |
| /* The following is needed for ahci_log() */ |
| static kmutex_t ahci_log_mutex; |
| static char ahci_log_buf[512]; |
| #endif |
| |
| /* Opaque state pointer initialized by ddi_soft_state_init() */ |
| static void *ahci_statep = NULL; |
| |
| /* |
| * ahci module initialization. |
| */ |
| int |
| _init(void) |
| { |
| int ret; |
| |
| ret = ddi_soft_state_init(&ahci_statep, sizeof (ahci_ctl_t), 0); |
| if (ret != 0) { |
| goto err_out; |
| } |
| |
| #if AHCI_DEBUG |
| mutex_init(&ahci_log_mutex, NULL, MUTEX_DRIVER, NULL); |
| #endif |
| |
| if ((ret = sata_hba_init(&modlinkage)) != 0) { |
| #if AHCI_DEBUG |
| mutex_destroy(&ahci_log_mutex); |
| #endif |
| ddi_soft_state_fini(&ahci_statep); |
| goto err_out; |
| } |
| |
| /* watchdog tick */ |
| ahci_watchdog_tick = drv_usectohz( |
| (clock_t)ahci_watchdog_timeout * 1000000); |
| |
| ret = mod_install(&modlinkage); |
| if (ret != 0) { |
| sata_hba_fini(&modlinkage); |
| #if AHCI_DEBUG |
| mutex_destroy(&ahci_log_mutex); |
| #endif |
| ddi_soft_state_fini(&ahci_statep); |
| goto err_out; |
| } |
| |
| return (ret); |
| |
| err_out: |
| cmn_err(CE_WARN, "!ahci: Module init failed"); |
| return (ret); |
| } |
| |
| /* |
| * ahci module uninitialize. |
| */ |
| int |
| _fini(void) |
| { |
| int ret; |
| |
| ret = mod_remove(&modlinkage); |
| if (ret != 0) { |
| return (ret); |
| } |
| |
| /* Remove the resources allocated in _init(). */ |
| sata_hba_fini(&modlinkage); |
| #if AHCI_DEBUG |
| mutex_destroy(&ahci_log_mutex); |
| #endif |
| ddi_soft_state_fini(&ahci_statep); |
| |
| return (ret); |
| } |
| |
| /* |
| * _info entry point |
| */ |
| int |
| _info(struct modinfo *modinfop) |
| { |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| |
| /* |
| * The attach entry point for dev_ops. |
| */ |
| static int |
| ahci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) |
| { |
| ahci_ctl_t *ahci_ctlp = NULL; |
| int instance = ddi_get_instance(dip); |
| int status; |
| int attach_state; |
| uint32_t cap_status, ahci_version; |
| uint32_t ghc_control; |
| int intr_types; |
| int i; |
| pci_regspec_t *regs; |
| int regs_length; |
| int rnumber; |
| #if AHCI_DEBUG |
| int speed; |
| #endif |
| |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_ENTRY, ahci_ctlp, "ahci_attach enter", |
| NULL); |
| |
| switch (cmd) { |
| case DDI_ATTACH: |
| break; |
| |
| case DDI_RESUME: |
| |
| /* |
| * During DDI_RESUME, the hardware state of the device |
| * (power may have been removed from the device) must be |
| * restored, allow pending requests to continue, and |
| * service new requests. |
| */ |
| ahci_ctlp = ddi_get_soft_state(ahci_statep, instance); |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| |
| /* |
| * GHC.AE must be set to 1 before any other AHCI register |
| * is accessed |
| */ |
| ghc_control = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_GHC(ahci_ctlp)); |
| ghc_control |= AHCI_HBA_GHC_AE; |
| ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_GHC(ahci_ctlp), ghc_control); |
| |
| /* Restart watch thread */ |
| if (ahci_ctlp->ahcictl_timeout_id == 0) |
| ahci_ctlp->ahcictl_timeout_id = timeout( |
| (void (*)(void *))ahci_watchdog_handler, |
| (caddr_t)ahci_ctlp, ahci_watchdog_tick); |
| |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| |
| /* |
| * Re-initialize the controller and enable the interrupts and |
| * restart all the ports. |
| * |
| * Note that so far we don't support hot-plug during |
| * suspend/resume. |
| */ |
| if (ahci_initialize_controller(ahci_ctlp) != AHCI_SUCCESS) { |
| AHCIDBG(AHCIDBG_ERRS|AHCIDBG_PM, ahci_ctlp, |
| "Failed to initialize the controller " |
| "during DDI_RESUME", NULL); |
| return (DDI_FAILURE); |
| } |
| |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| ahci_ctlp->ahcictl_flags &= ~AHCI_SUSPEND; |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| |
| return (DDI_SUCCESS); |
| |
| default: |
| return (DDI_FAILURE); |
| } |
| |
| attach_state = AHCI_ATTACH_STATE_NONE; |
| |
| /* Allocate soft state */ |
| status = ddi_soft_state_zalloc(ahci_statep, instance); |
| if (status != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: Cannot allocate soft state", |
| instance); |
| goto err_out; |
| } |
| |
| ahci_ctlp = ddi_get_soft_state(ahci_statep, instance); |
| ahci_ctlp->ahcictl_flags |= AHCI_ATTACH; |
| ahci_ctlp->ahcictl_dip = dip; |
| |
| /* Initialize the cport/port mapping */ |
| for (i = 0; i < AHCI_MAX_PORTS; i++) { |
| ahci_ctlp->ahcictl_port_to_cport[i] = 0xff; |
| ahci_ctlp->ahcictl_cport_to_port[i] = 0xff; |
| } |
| |
| attach_state |= AHCI_ATTACH_STATE_STATEP_ALLOC; |
| |
| /* Initialize FMA properties */ |
| ahci_fm_init(ahci_ctlp); |
| |
| attach_state |= AHCI_ATTACH_STATE_FMA; |
| |
| /* |
| * Now map the AHCI base address; which includes global |
| * registers and port control registers |
| * |
| * According to the spec, the AHCI Base Address is BAR5, |
| * but BAR0-BAR4 are optional, so we need to check which |
| * rnumber is used for BAR5. |
| */ |
| |
| /* |
| * search through DDI "reg" property for the AHCI register set |
| */ |
| if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, |
| DDI_PROP_DONTPASS, "reg", (int **)®s, |
| (uint_t *)®s_length) != DDI_PROP_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: Cannot lookup reg property", |
| instance); |
| goto err_out; |
| } |
| |
| /* AHCI Base Address is located at 0x24 offset */ |
| for (rnumber = 0; rnumber < regs_length; ++rnumber) { |
| if ((regs[rnumber].pci_phys_hi & PCI_REG_REG_M) |
| == AHCI_PCI_RNUM) |
| break; |
| } |
| |
| ddi_prop_free(regs); |
| |
| if (rnumber == regs_length) { |
| cmn_err(CE_WARN, "!ahci%d: Cannot find AHCI register set", |
| instance); |
| goto err_out; |
| } |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, "rnumber = %d", rnumber); |
| |
| status = ddi_regs_map_setup(dip, |
| rnumber, |
| (caddr_t *)&ahci_ctlp->ahcictl_ahci_addr, |
| 0, |
| 0, |
| &accattr, |
| &ahci_ctlp->ahcictl_ahci_acc_handle); |
| if (status != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: Cannot map register space", |
| instance); |
| goto err_out; |
| } |
| |
| attach_state |= AHCI_ATTACH_STATE_REG_MAP; |
| |
| /* |
| * GHC.AE must be set to 1 before any other AHCI register |
| * is accessed |
| */ |
| ghc_control = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_GHC(ahci_ctlp)); |
| ghc_control |= AHCI_HBA_GHC_AE; |
| ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_GHC(ahci_ctlp), ghc_control); |
| |
| /* Get the AHCI version information */ |
| ahci_version = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_VS(ahci_ctlp)); |
| |
| cmn_err(CE_NOTE, "!ahci%d: hba AHCI version = %x.%x", instance, |
| (ahci_version & 0xffff0000) >> 16, |
| ((ahci_version & 0x0000ff00) >> 4 | |
| (ahci_version & 0x000000ff))); |
| |
| /* We don't support controllers whose versions are lower than 1.0 */ |
| if (!(ahci_version & 0xffff0000)) { |
| cmn_err(CE_WARN, "ahci%d: Don't support AHCI HBA with lower " |
| "than version 1.0", instance); |
| goto err_out; |
| } |
| |
| /* Get the HBA capabilities information */ |
| cap_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_CAP(ahci_ctlp)); |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, "hba capabilities = 0x%x", |
| cap_status); |
| |
| /* CAP2 (HBA Capabilities Extended) is available since AHCI spec 1.2 */ |
| if (ahci_version >= 0x00010200) { |
| uint32_t cap2_status; |
| |
| /* Get the HBA capabilities extended information */ |
| cap2_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_CAP2(ahci_ctlp)); |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, |
| "hba capabilities extended = 0x%x", cap2_status); |
| } |
| |
| #if AHCI_DEBUG |
| /* Get the interface speed supported by the HBA */ |
| speed = (cap_status & AHCI_HBA_CAP_ISS) >> AHCI_HBA_CAP_ISS_SHIFT; |
| if (speed == 0x01) { |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, |
| "hba interface speed support: Gen 1 (1.5Gbps)", NULL); |
| } else if (speed == 0x10) { |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, |
| "hba interface speed support: Gen 2 (3 Gbps)", NULL); |
| } else if (speed == 0x11) { |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, |
| "hba interface speed support: Gen 3 (6 Gbps)", NULL); |
| } |
| #endif |
| |
| /* Get the number of command slots supported by the HBA */ |
| ahci_ctlp->ahcictl_num_cmd_slots = |
| ((cap_status & AHCI_HBA_CAP_NCS) >> |
| AHCI_HBA_CAP_NCS_SHIFT) + 1; |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, "hba number of cmd slots: %d", |
| ahci_ctlp->ahcictl_num_cmd_slots); |
| |
| /* Get the bit map which indicates ports implemented by the HBA */ |
| ahci_ctlp->ahcictl_ports_implemented = |
| ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_GLOBAL_PI(ahci_ctlp)); |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, "hba implementation of ports: 0x%x", |
| ahci_ctlp->ahcictl_ports_implemented); |
| |
| /* Max port number implemented */ |
| ahci_ctlp->ahcictl_num_ports = |
| ddi_fls(ahci_ctlp->ahcictl_ports_implemented); |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, "hba number of ports: %d", |
| (cap_status & AHCI_HBA_CAP_NP) + 1); |
| |
| /* Get the number of implemented ports by the HBA */ |
| ahci_ctlp->ahcictl_num_implemented_ports = |
| ahci_get_num_implemented_ports( |
| ahci_ctlp->ahcictl_ports_implemented); |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, |
| "hba number of implemented ports: %d", |
| ahci_ctlp->ahcictl_num_implemented_ports); |
| |
| /* Check whether HBA supports 64bit DMA addressing */ |
| if (!(cap_status & AHCI_HBA_CAP_S64A)) { |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_BUF_32BIT_DMA; |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_COMMU_32BIT_DMA; |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, |
| "hba does not support 64-bit addressing", NULL); |
| } |
| |
| /* Checking for the support of Port Multiplier */ |
| if (cap_status & AHCI_HBA_CAP_SPM) { |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_PMULT_CBSS; |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_PMULT, ahci_ctlp, |
| "hba supports port multiplier (CBSS)", NULL); |
| |
| /* Support FIS-based switching ? */ |
| if (cap_status & AHCI_HBA_CAP_FBSS) { |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_PMULT_FBSS; |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_PMULT, ahci_ctlp, |
| "hba supports FIS-based switching (FBSS)", NULL); |
| } |
| } |
| |
| /* Checking for Support Command List Override */ |
| if (cap_status & AHCI_HBA_CAP_SCLO) { |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_SCLO; |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_PMULT, ahci_ctlp, |
| "hba supports command list override.", NULL); |
| } |
| |
| /* Checking for Asynchronous Notification */ |
| if (cap_status & AHCI_HBA_CAP_SSNTF) { |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_SNTF; |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_PMULT, ahci_ctlp, |
| "hba supports asynchronous notification.", NULL); |
| } |
| |
| if (pci_config_setup(dip, &ahci_ctlp->ahcictl_pci_conf_handle) |
| != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: Cannot set up pci configure space", |
| instance); |
| goto err_out; |
| } |
| |
| attach_state |= AHCI_ATTACH_STATE_PCICFG_SETUP; |
| |
| /* |
| * Check the pci configuration space, and set caps. We also |
| * handle the hardware defect in this function. |
| * |
| * For example, force ATI SB600 to use 32-bit dma addressing |
| * since it doesn't support 64-bit dma though its CAP register |
| * declares it support. |
| */ |
| if (ahci_config_space_init(ahci_ctlp) == AHCI_FAILURE) { |
| cmn_err(CE_WARN, "!ahci%d: ahci_config_space_init failed", |
| instance); |
| goto err_out; |
| } |
| |
| /* |
| * Disable the whole controller interrupts before adding |
| * interrupt handlers(s). |
| */ |
| ahci_disable_all_intrs(ahci_ctlp); |
| |
| /* Get supported interrupt types */ |
| if (ddi_intr_get_supported_types(dip, &intr_types) != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: ddi_intr_get_supported_types failed", |
| instance); |
| goto err_out; |
| } |
| |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_INTR, ahci_ctlp, |
| "ddi_intr_get_supported_types() returned: 0x%x", |
| intr_types); |
| |
| if (ahci_msi_enabled && (intr_types & DDI_INTR_TYPE_MSI)) { |
| /* |
| * Try MSI first, but fall back to FIXED if failed |
| */ |
| if (ahci_add_intrs(ahci_ctlp, DDI_INTR_TYPE_MSI) == |
| DDI_SUCCESS) { |
| ahci_ctlp->ahcictl_intr_type = DDI_INTR_TYPE_MSI; |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_INTR, ahci_ctlp, |
| "Using MSI interrupt type", NULL); |
| goto intr_done; |
| } |
| |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_INTR, ahci_ctlp, |
| "MSI registration failed, " |
| "trying FIXED interrupts", NULL); |
| } |
| |
| if (intr_types & DDI_INTR_TYPE_FIXED) { |
| if (ahci_add_intrs(ahci_ctlp, DDI_INTR_TYPE_FIXED) == |
| DDI_SUCCESS) { |
| ahci_ctlp->ahcictl_intr_type = DDI_INTR_TYPE_FIXED; |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_INTR, ahci_ctlp, |
| "Using FIXED interrupt type", NULL); |
| goto intr_done; |
| } |
| |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_INTR, ahci_ctlp, |
| "FIXED interrupt registration failed", NULL); |
| } |
| |
| cmn_err(CE_WARN, "!ahci%d: Interrupt registration failed", instance); |
| |
| goto err_out; |
| |
| intr_done: |
| |
| attach_state |= AHCI_ATTACH_STATE_INTR_ADDED; |
| |
| /* Initialize the controller mutex */ |
| mutex_init(&ahci_ctlp->ahcictl_mutex, NULL, MUTEX_DRIVER, |
| (void *)(uintptr_t)ahci_ctlp->ahcictl_intr_pri); |
| |
| attach_state |= AHCI_ATTACH_STATE_MUTEX_INIT; |
| |
| if (ahci_dma_prdt_number < AHCI_MIN_PRDT_NUMBER) { |
| ahci_dma_prdt_number = AHCI_MIN_PRDT_NUMBER; |
| } else if (ahci_dma_prdt_number > AHCI_MAX_PRDT_NUMBER) { |
| ahci_dma_prdt_number = AHCI_MAX_PRDT_NUMBER; |
| } |
| |
| ahci_cmd_table_size = (sizeof (ahci_cmd_table_t) + |
| (ahci_dma_prdt_number - AHCI_PRDT_NUMBER) * |
| sizeof (ahci_prdt_item_t)); |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, |
| "ahci_attach: ahci_dma_prdt_number set by user is 0x%x," |
| " ahci_cmd_table_size is 0x%x", |
| ahci_dma_prdt_number, ahci_cmd_table_size); |
| |
| if (ahci_dma_prdt_number != AHCI_PRDT_NUMBER) |
| ahci_ctlp->ahcictl_buffer_dma_attr.dma_attr_sgllen = |
| ahci_dma_prdt_number; |
| |
| ahci_ctlp->ahcictl_buffer_dma_attr = buffer_dma_attr; |
| ahci_ctlp->ahcictl_rcvd_fis_dma_attr = rcvd_fis_dma_attr; |
| ahci_ctlp->ahcictl_cmd_list_dma_attr = cmd_list_dma_attr; |
| ahci_ctlp->ahcictl_cmd_table_dma_attr = cmd_table_dma_attr; |
| |
| /* |
| * enable 64bit dma for data buffer for SB600 if |
| * sb600_buf_64bit_dma_disable is B_FALSE |
| */ |
| if ((ahci_buf_64bit_dma == B_FALSE) || |
| ((ahci_ctlp->ahcictl_cap & AHCI_CAP_BUF_32BIT_DMA) && |
| !(sb600_buf_64bit_dma_disable == B_FALSE && |
| ahci_ctlp->ahcictl_venid == 0x1002 && |
| ahci_ctlp->ahcictl_devid == 0x4380))) { |
| ahci_ctlp->ahcictl_buffer_dma_attr.dma_attr_addr_hi = |
| 0xffffffffull; |
| } |
| |
| /* |
| * enable 64bit dma for command buffer for SB600/700/710/800 |
| * if sbxxx_commu_64bit_dma_disable is B_FALSE |
| */ |
| if ((ahci_commu_64bit_dma == B_FALSE) || |
| ((ahci_ctlp->ahcictl_cap & AHCI_CAP_COMMU_32BIT_DMA) && |
| !(sbxxx_commu_64bit_dma_disable == B_FALSE && |
| ahci_ctlp->ahcictl_venid == 0x1002 && |
| (ahci_ctlp->ahcictl_devid == 0x4380 || |
| ahci_ctlp->ahcictl_devid == 0x4391)))) { |
| ahci_ctlp->ahcictl_rcvd_fis_dma_attr.dma_attr_addr_hi = |
| 0xffffffffull; |
| ahci_ctlp->ahcictl_cmd_list_dma_attr.dma_attr_addr_hi = |
| 0xffffffffull; |
| ahci_ctlp->ahcictl_cmd_table_dma_attr.dma_attr_addr_hi = |
| 0xffffffffull; |
| } |
| |
| /* Allocate the ports structure */ |
| status = ahci_alloc_ports_state(ahci_ctlp); |
| if (status != AHCI_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: Cannot allocate ports structure", |
| instance); |
| goto err_out; |
| } |
| |
| attach_state |= AHCI_ATTACH_STATE_PORT_ALLOC; |
| |
| /* |
| * Initialize the controller and ports. |
| */ |
| status = ahci_initialize_controller(ahci_ctlp); |
| if (status != AHCI_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: HBA initialization failed", |
| instance); |
| goto err_out; |
| } |
| |
| attach_state |= AHCI_ATTACH_STATE_HW_INIT; |
| |
| /* Start one thread to check packet timeouts */ |
| ahci_ctlp->ahcictl_timeout_id = timeout( |
| (void (*)(void *))ahci_watchdog_handler, |
| (caddr_t)ahci_ctlp, ahci_watchdog_tick); |
| |
| attach_state |= AHCI_ATTACH_STATE_TIMEOUT_ENABLED; |
| |
| if (ahci_register_sata_hba_tran(ahci_ctlp, cap_status)) { |
| cmn_err(CE_WARN, "!ahci%d: sata hba tran registration failed", |
| instance); |
| goto err_out; |
| } |
| |
| /* Check all handles at the end of the attach operation. */ |
| if (ahci_check_all_handle(ahci_ctlp) != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "!ahci%d: invalid dma/acc handles", |
| instance); |
| goto err_out; |
| } |
| |
| ahci_ctlp->ahcictl_flags &= ~AHCI_ATTACH; |
| |
| AHCIDBG(AHCIDBG_INIT, ahci_ctlp, "ahci_attach success!", NULL); |
| |
| return (DDI_SUCCESS); |
| |
| err_out: |
| /* FMA message */ |
| ahci_fm_ereport(ahci_ctlp, DDI_FM_DEVICE_NO_RESPONSE); |
| ddi_fm_service_impact(ahci_ctlp->ahcictl_dip, DDI_SERVICE_LOST); |
| |
| if (attach_state & AHCI_ATTACH_STATE_TIMEOUT_ENABLED) { |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| (void) untimeout(ahci_ctlp->ahcictl_timeout_id); |
| ahci_ctlp->ahcictl_timeout_id = 0; |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_HW_INIT) { |
| ahci_uninitialize_controller(ahci_ctlp); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_PORT_ALLOC) { |
| ahci_dealloc_ports_state(ahci_ctlp); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_MUTEX_INIT) { |
| mutex_destroy(&ahci_ctlp->ahcictl_mutex); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_INTR_ADDED) { |
| ahci_rem_intrs(ahci_ctlp); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_PCICFG_SETUP) { |
| pci_config_teardown(&ahci_ctlp->ahcictl_pci_conf_handle); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_REG_MAP) { |
| ddi_regs_map_free(&ahci_ctlp->ahcictl_ahci_acc_handle); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_FMA) { |
| ahci_fm_fini(ahci_ctlp); |
| } |
| |
| if (attach_state & AHCI_ATTACH_STATE_STATEP_ALLOC) { |
| ddi_soft_state_free(ahci_statep, instance); |
| } |
| |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * The detach entry point for dev_ops. |
| */ |
| static int |
| ahci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) |
| { |
| ahci_ctl_t *ahci_ctlp; |
| int instance; |
| int ret; |
| |
| instance = ddi_get_instance(dip); |
| ahci_ctlp = ddi_get_soft_state(ahci_statep, instance); |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, "ahci_detach enter", NULL); |
| |
| switch (cmd) { |
| case DDI_DETACH: |
| |
| /* disable the interrupts for an uninterrupted detach */ |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| ahci_disable_all_intrs(ahci_ctlp); |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| |
| /* unregister from the sata framework. */ |
| ret = ahci_unregister_sata_hba_tran(ahci_ctlp); |
| if (ret != AHCI_SUCCESS) { |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| ahci_enable_all_intrs(ahci_ctlp); |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| return (DDI_FAILURE); |
| } |
| |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| |
| /* stop the watchdog handler */ |
| (void) untimeout(ahci_ctlp->ahcictl_timeout_id); |
| ahci_ctlp->ahcictl_timeout_id = 0; |
| |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| |
| /* uninitialize the controller */ |
| ahci_uninitialize_controller(ahci_ctlp); |
| |
| /* remove the interrupts */ |
| ahci_rem_intrs(ahci_ctlp); |
| |
| /* deallocate the ports structures */ |
| ahci_dealloc_ports_state(ahci_ctlp); |
| |
| /* destroy mutex */ |
| mutex_destroy(&ahci_ctlp->ahcictl_mutex); |
| |
| /* teardown the pci config */ |
| pci_config_teardown(&ahci_ctlp->ahcictl_pci_conf_handle); |
| |
| /* remove the reg maps. */ |
| ddi_regs_map_free(&ahci_ctlp->ahcictl_ahci_acc_handle); |
| |
| /* release fma resource */ |
| ahci_fm_fini(ahci_ctlp); |
| |
| /* free the soft state. */ |
| ddi_soft_state_free(ahci_statep, instance); |
| |
| return (DDI_SUCCESS); |
| |
| case DDI_SUSPEND: |
| |
| /* |
| * The steps associated with suspension must include putting |
| * the underlying device into a quiescent state so that it |
| * will not generate interrupts or modify or access memory. |
| */ |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| if (ahci_ctlp->ahcictl_flags & AHCI_SUSPEND) { |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| return (DDI_SUCCESS); |
| } |
| |
| ahci_ctlp->ahcictl_flags |= AHCI_SUSPEND; |
| |
| /* stop the watchdog handler */ |
| if (ahci_ctlp->ahcictl_timeout_id) { |
| (void) untimeout(ahci_ctlp->ahcictl_timeout_id); |
| ahci_ctlp->ahcictl_timeout_id = 0; |
| } |
| |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| |
| /* |
| * drain the taskq |
| */ |
| ahci_drain_ports_taskq(ahci_ctlp); |
| |
| /* |
| * Disable the interrupts and stop all the ports. |
| */ |
| ahci_uninitialize_controller(ahci_ctlp); |
| |
| return (DDI_SUCCESS); |
| |
| default: |
| return (DDI_FAILURE); |
| } |
| } |
| |
| /* |
| * The info entry point for dev_ops. |
| * |
| */ |
| static int |
| ahci_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, |
| void *arg, void **result) |
| { |
| #ifndef __lock_lint |
| _NOTE(ARGUNUSED(dip)) |
| #endif /* __lock_lint */ |
| |
| ahci_ctl_t *ahci_ctlp; |
| int instance; |
| dev_t dev; |
| |
| dev = (dev_t)arg; |
| instance = getminor(dev); |
| |
| switch (infocmd) { |
| case DDI_INFO_DEVT2DEVINFO: |
| ahci_ctlp = ddi_get_soft_state(ahci_statep, instance); |
| if (ahci_ctlp != NULL) { |
| *result = ahci_ctlp->ahcictl_dip; |
| return (DDI_SUCCESS); |
| } else { |
| *result = NULL; |
| return (DDI_FAILURE); |
| } |
| case DDI_INFO_DEVT2INSTANCE: |
| *(int *)result = instance; |
| break; |
| default: |
| break; |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * Registers the ahci with sata framework. |
| */ |
| static int |
| ahci_register_sata_hba_tran(ahci_ctl_t *ahci_ctlp, uint32_t cap_status) |
| { |
| struct sata_hba_tran *sata_hba_tran; |
| |
| AHCIDBG(AHCIDBG_INIT|AHCIDBG_ENTRY, ahci_ctlp, |
| "ahci_register_sata_hba_tran enter", NULL); |
| |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| |
| /* Allocate memory for the sata_hba_tran */ |
| sata_hba_tran = kmem_zalloc(sizeof (sata_hba_tran_t), KM_SLEEP); |
| |
| sata_hba_tran->sata_tran_hba_rev = SATA_TRAN_HBA_REV; |
| sata_hba_tran->sata_tran_hba_dip = ahci_ctlp->ahcictl_dip; |
| sata_hba_tran->sata_tran_hba_dma_attr = |
| &ahci_ctlp->ahcictl_buffer_dma_attr; |
| |
| /* Report the number of implemented ports */ |
| sata_hba_tran->sata_tran_hba_num_cports = |
| ahci_ctlp->ahcictl_num_implemented_ports; |
| |
| /* Support ATAPI device */ |
| sata_hba_tran->sata_tran_hba_features_support = SATA_CTLF_ATAPI; |
| |
| /* Get the data transfer capability for PIO command by the HBA */ |
| if (cap_status & AHCI_HBA_CAP_PMD) { |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_PIO_MDRQ; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, "HBA supports multiple " |
| "DRQ block data transfer for PIO command protocol", NULL); |
| } |
| |
| /* |
| * According to the AHCI spec, the ATA/ATAPI-7 queued feature set |
| * is not supported by AHCI (including the READ QUEUED (EXT), WRITE |
| * QUEUED (EXT), and SERVICE commands). Queued operations are |
| * supported in AHCI using the READ FPDMA QUEUED and WRITE FPDMA |
| * QUEUED commands when the HBA and device support native command |
| * queuing(NCQ). |
| * |
| * SATA_CTLF_NCQ will be set to sata_tran_hba_features_support if the |
| * CAP register of the HBA indicates NCQ is supported. |
| * |
| * SATA_CTLF_NCQ cannot be set if AHCI_CAP_NO_MCMDLIST_NONQUEUE is |
| * set because the previous register content of PxCI can be re-written |
| * in the register write. |
| */ |
| if ((cap_status & AHCI_HBA_CAP_SNCQ) && |
| !(ahci_ctlp->ahcictl_cap & AHCI_CAP_NO_MCMDLIST_NONQUEUE)) { |
| sata_hba_tran->sata_tran_hba_features_support |= SATA_CTLF_NCQ; |
| ahci_ctlp->ahcictl_cap |= AHCI_CAP_NCQ; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, "HBA supports Native " |
| "Command Queuing", NULL); |
| } |
| |
| /* Support port multiplier? */ |
| if (cap_status & AHCI_HBA_CAP_SPM) { |
| sata_hba_tran->sata_tran_hba_features_support |= |
| SATA_CTLF_PORT_MULTIPLIER; |
| |
| /* Support FIS-based switching for port multiplier? */ |
| if (cap_status & AHCI_HBA_CAP_FBSS) { |
| sata_hba_tran->sata_tran_hba_features_support |= |
| SATA_CTLF_PMULT_FBS; |
| } |
| } |
| |
| /* Report the number of command slots */ |
| sata_hba_tran->sata_tran_hba_qdepth = ahci_ctlp->ahcictl_num_cmd_slots; |
| |
| sata_hba_tran->sata_tran_probe_port = ahci_tran_probe_port; |
| sata_hba_tran->sata_tran_start = ahci_tran_start; |
| sata_hba_tran->sata_tran_abort = ahci_tran_abort; |
| sata_hba_tran->sata_tran_reset_dport = ahci_tran_reset_dport; |
| sata_hba_tran->sata_tran_hotplug_ops = &ahci_tran_hotplug_ops; |
| #ifdef __lock_lint |
| sata_hba_tran->sata_tran_selftest = ahci_selftest; |
| #endif |
| /* |
| * When SATA framework adds support for pwrmgt the |
| * pwrmgt_ops needs to be updated |
| */ |
| sata_hba_tran->sata_tran_pwrmgt_ops = NULL; |
| sata_hba_tran->sata_tran_ioctl = NULL; |
| |
| ahci_ctlp->ahcictl_sata_hba_tran = sata_hba_tran; |
| |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| |
| /* Attach it to SATA framework */ |
| if (sata_hba_attach(ahci_ctlp->ahcictl_dip, sata_hba_tran, DDI_ATTACH) |
| != DDI_SUCCESS) { |
| kmem_free((void *)sata_hba_tran, sizeof (sata_hba_tran_t)); |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| ahci_ctlp->ahcictl_sata_hba_tran = NULL; |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| return (AHCI_FAILURE); |
| } |
| |
| return (AHCI_SUCCESS); |
| } |
| |
| /* |
| * Unregisters the ahci with sata framework. |
| */ |
| static int |
| ahci_unregister_sata_hba_tran(ahci_ctl_t *ahci_ctlp) |
| { |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, |
| "ahci_unregister_sata_hba_tran enter", NULL); |
| |
| /* Detach from the SATA framework. */ |
| if (sata_hba_detach(ahci_ctlp->ahcictl_dip, DDI_DETACH) != |
| DDI_SUCCESS) { |
| return (AHCI_FAILURE); |
| } |
| |
| /* Deallocate sata_hba_tran. */ |
| kmem_free((void *)ahci_ctlp->ahcictl_sata_hba_tran, |
| sizeof (sata_hba_tran_t)); |
| |
| mutex_enter(&ahci_ctlp->ahcictl_mutex); |
| ahci_ctlp->ahcictl_sata_hba_tran = NULL; |
| mutex_exit(&ahci_ctlp->ahcictl_mutex); |
| |
| return (AHCI_SUCCESS); |
| } |
| |
| #define SET_PORTSTR(str, addrp) \ |
| if (AHCI_ADDR_IS_PORT(addrp)) \ |
| (void) sprintf((str), "%d", (addrp)->aa_port); \ |
| else if (AHCI_ADDR_IS_PMULT(addrp)) \ |
| (void) sprintf((str), "%d (pmult)", (addrp)->aa_port); \ |
| else \ |
| (void) sprintf((str), "%d:%d", (addrp)->aa_port, \ |
| (addrp)->aa_pmport); |
| |
| /* |
| * ahci_tran_probe_port is called by SATA framework. It returns port state, |
| * port status registers and an attached device type via sata_device |
| * structure. |
| * |
| * We return the cached information from a previous hardware probe. The |
| * actual hardware probing itself was done either from within |
| * ahci_initialize_controller() during the driver attach or from a phy |
| * ready change interrupt handler. |
| */ |
| static int |
| ahci_tran_probe_port(dev_info_t *dip, sata_device_t *sd) |
| { |
| ahci_ctl_t *ahci_ctlp; |
| ahci_port_t *ahci_portp; |
| ahci_addr_t addr, pmult_addr; |
| uint8_t cport = sd->satadev_addr.cport; |
| char portstr[10]; |
| uint8_t device_type; |
| uint32_t port_state; |
| uint8_t port; |
| int rval = SATA_SUCCESS, rval_init; |
| |
| ahci_ctlp = ddi_get_soft_state(ahci_statep, ddi_get_instance(dip)); |
| port = ahci_ctlp->ahcictl_cport_to_port[cport]; |
| |
| ahci_portp = ahci_ctlp->ahcictl_ports[port]; |
| |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| |
| ahci_get_ahci_addr(ahci_ctlp, sd, &addr); |
| ASSERT(AHCI_ADDR_IS_VALID(&addr)); |
| SET_PORTSTR(portstr, &addr); |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, |
| "ahci_tran_probe_port enter: port %s", portstr); |
| |
| if ((AHCI_ADDR_IS_PMULT(&addr) || AHCI_ADDR_IS_PMPORT(&addr)) && |
| (ahci_portp->ahciport_device_type != SATA_DTYPE_PMULT || |
| ahci_portp->ahciport_pmult_info == NULL)) { |
| /* port mutliplier is removed. */ |
| AHCIDBG(AHCIDBG_PMULT, ahci_ctlp, |
| "ahci_tran_probe_port: " |
| "pmult is removed from port %s", portstr); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_FAILURE); |
| } |
| |
| /* |
| * The sata_device may refer to |
| * 1. A controller port. |
| * A controller port should be ready here. |
| * 2. A port multiplier. |
| * SATA_ADDR_PMULT_SPEC - if it is not initialized yet, initialize |
| * it and register the port multiplier to the framework. |
| * SATA_ADDR_PMULT - check the status of all its device ports. |
| * 3. A port multiplier port. |
| * If it has not been initialized, initialized it. |
| * |
| * A port multiplier or a port multiplier port may require some |
| * initialization because we cannot do these time-consuming jobs in an |
| * interrupt context. |
| */ |
| if (sd->satadev_addr.qual & SATA_ADDR_PMULT_SPEC) { |
| AHCI_ADDR_SET_PMULT(&pmult_addr, port); |
| /* Initialize registers on a port multiplier */ |
| rval_init = ahci_initialize_pmult(ahci_ctlp, |
| ahci_portp, &pmult_addr, sd); |
| if (rval_init != AHCI_SUCCESS) { |
| AHCIDBG(AHCIDBG_PMULT, ahci_ctlp, |
| "ahci_tran_probe_port: " |
| "pmult initialization failed.", NULL); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_FAILURE); |
| } |
| } else if (sd->satadev_addr.qual & SATA_ADDR_PMULT) { |
| /* Check pmports hotplug events */ |
| (void) ahci_probe_pmult(ahci_ctlp, ahci_portp, &addr); |
| } else if (sd->satadev_addr.qual & (SATA_ADDR_PMPORT | |
| SATA_ADDR_DPMPORT)) { |
| if (ahci_probe_pmport(ahci_ctlp, ahci_portp, |
| &addr, sd) != AHCI_SUCCESS) { |
| rval = SATA_FAILURE; |
| goto out; |
| } |
| } |
| |
| /* Update port state and device type */ |
| port_state = AHCIPORT_GET_STATE(ahci_portp, &addr); |
| |
| switch (port_state) { |
| |
| case SATA_PSTATE_FAILED: |
| sd->satadev_state = SATA_PSTATE_FAILED; |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_probe_port: port %s PORT FAILED", portstr); |
| goto out; |
| |
| case SATA_PSTATE_SHUTDOWN: |
| sd->satadev_state = SATA_PSTATE_SHUTDOWN; |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_probe_port: port %s PORT SHUTDOWN", portstr); |
| goto out; |
| |
| case SATA_PSTATE_PWROFF: |
| sd->satadev_state = SATA_PSTATE_PWROFF; |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_probe_port: port %s PORT PWROFF", portstr); |
| goto out; |
| |
| case SATA_PSTATE_PWRON: |
| sd->satadev_state = SATA_PSTATE_PWRON; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_probe_port: port %s PORT PWRON", portstr); |
| break; |
| |
| default: |
| sd->satadev_state = port_state; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_probe_port: port %s PORT NORMAL %x", |
| portstr, port_state); |
| break; |
| } |
| |
| device_type = AHCIPORT_GET_DEV_TYPE(ahci_portp, &addr); |
| |
| switch (device_type) { |
| |
| case SATA_DTYPE_ATADISK: |
| sd->satadev_type = SATA_DTYPE_ATADISK; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_probe_port: port %s DISK found", portstr); |
| break; |
| |
| case SATA_DTYPE_ATAPI: |
| /* |
| * HBA driver only knows it's an ATAPI device, and don't know |
| * it's CD/DVD, tape or ATAPI disk because the ATAPI device |
| * type need to be determined by checking IDENTIFY PACKET |
| * DEVICE data |
| */ |
| sd->satadev_type = SATA_DTYPE_ATAPI; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_probe_port: port %s ATAPI found", portstr); |
| break; |
| |
| case SATA_DTYPE_PMULT: |
| ASSERT(AHCI_ADDR_IS_PORT(&addr) || AHCI_ADDR_IS_PMULT(&addr)); |
| sd->satadev_type = SATA_DTYPE_PMULT; |
| |
| /* Update the number of pmports. */ |
| ASSERT(ahci_portp->ahciport_pmult_info != NULL); |
| sd->satadev_add_info = ahci_portp-> |
| ahciport_pmult_info->ahcipmi_num_dev_ports; |
| |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_probe_port: port %s Port Multiplier found", |
| portstr); |
| break; |
| |
| case SATA_DTYPE_UNKNOWN: |
| sd->satadev_type = SATA_DTYPE_UNKNOWN; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_probe_port: port %s Unknown device found", |
| portstr); |
| break; |
| |
| default: |
| /* we don't support any other device types */ |
| sd->satadev_type = SATA_DTYPE_NONE; |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_probe_port: port %s No device found", portstr); |
| break; |
| } |
| |
| out: |
| /* Register update only fails while probing a pmult/pmport */ |
| if (AHCI_ADDR_IS_PORT(&addr) || AHCI_ADDR_IS_PMULT(&addr)) { |
| ahci_update_sata_registers(ahci_ctlp, port, sd); |
| } else if (AHCI_ADDR_IS_PMPORT(&addr)) { |
| if (port_state & SATA_STATE_READY) |
| if (ahci_update_pmult_pscr(ahci_ctlp, |
| &addr, sd) != AHCI_SUCCESS) |
| rval = SATA_FAILURE; |
| } |
| |
| /* Check handles for the sata registers access */ |
| if ((ahci_check_ctl_handle(ahci_ctlp) != DDI_SUCCESS) || |
| (ahci_check_port_handle(ahci_ctlp, port) != DDI_SUCCESS)) { |
| ddi_fm_service_impact(ahci_ctlp->ahcictl_dip, |
| DDI_SERVICE_UNAFFECTED); |
| rval = SATA_FAILURE; |
| } |
| |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (rval); |
| } |
| |
| /* |
| * There are four operation modes in sata framework: |
| * SATA_OPMODE_INTERRUPTS |
| * SATA_OPMODE_POLLING |
| * SATA_OPMODE_ASYNCH |
| * SATA_OPMODE_SYNCH |
| * |
| * Their combined meanings as following: |
| * |
| * SATA_OPMODE_SYNCH |
| * The command has to be completed before sata_tran_start functions returns. |
| * Either interrupts or polling could be used - it's up to the driver. |
| * Mode used currently for internal, sata-module initiated operations. |
| * |
| * SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS |
| * It is the same as the one above. |
| * |
| * SATA_OPMODE_SYNCH | SATA_OPMODE_POLLING |
| * The command has to be completed before sata_tran_start function returns. |
| * No interrupt used, polling only. This should be the mode used for scsi |
| * packets with FLAG_NOINTR. |
| * |
| * SATA_OPMODE_ASYNCH | SATA_OPMODE_INTERRUPTS |
| * The command may be queued (callback function specified). Interrupts could |
| * be used. It's normal operation mode. |
| */ |
| /* |
| * Called by sata framework to transport a sata packet down stream. |
| */ |
| static int |
| ahci_tran_start(dev_info_t *dip, sata_pkt_t *spkt) |
| { |
| ahci_ctl_t *ahci_ctlp; |
| ahci_port_t *ahci_portp; |
| ahci_addr_t addr; |
| uint8_t cport = spkt->satapkt_device.satadev_addr.cport; |
| uint8_t port; |
| char portstr[10]; |
| |
| ahci_ctlp = ddi_get_soft_state(ahci_statep, ddi_get_instance(dip)); |
| port = ahci_ctlp->ahcictl_cport_to_port[cport]; |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, |
| "ahci_tran_start enter: cport %d satapkt 0x%p", |
| cport, (void *)spkt); |
| |
| ahci_portp = ahci_ctlp->ahcictl_ports[port]; |
| |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| ahci_get_ahci_addr(ahci_ctlp, &spkt->satapkt_device, &addr); |
| SET_PORTSTR(portstr, &addr); |
| |
| /* Sanity check */ |
| if (AHCI_ADDR_IS_PMPORT(&addr)) { |
| if (ahci_portp->ahciport_device_type != SATA_DTYPE_PMULT || |
| ahci_portp->ahciport_pmult_info == NULL) { |
| |
| spkt->satapkt_reason = SATA_PKT_PORT_ERROR; |
| spkt->satapkt_device.satadev_type = SATA_DTYPE_NONE; |
| spkt->satapkt_device.satadev_state = SATA_STATE_UNKNOWN; |
| ahci_update_sata_registers(ahci_ctlp, port, |
| &spkt->satapkt_device); |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning PORT_ERROR while " |
| "pmult removed: port: %s", portstr); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_PORT_ERROR); |
| } |
| |
| if (!(AHCIPORT_GET_STATE(ahci_portp, &addr) & |
| SATA_STATE_READY)) { |
| if (!ddi_in_panic() || |
| ahci_initialize_pmport(ahci_ctlp, |
| ahci_portp, &addr) != AHCI_SUCCESS) { |
| spkt->satapkt_reason = SATA_PKT_PORT_ERROR; |
| spkt->satapkt_device.satadev_type = |
| AHCIPORT_GET_DEV_TYPE(ahci_portp, &addr); |
| spkt->satapkt_device.satadev_state = |
| AHCIPORT_GET_STATE(ahci_portp, &addr); |
| ahci_update_sata_registers(ahci_ctlp, port, |
| &spkt->satapkt_device); |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning PORT_ERROR " |
| "while sub-link is not initialized " |
| "at port: %s", portstr); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_PORT_ERROR); |
| } |
| } |
| } |
| |
| if (AHCIPORT_GET_STATE(ahci_portp, &addr) & SATA_PSTATE_FAILED || |
| AHCIPORT_GET_STATE(ahci_portp, &addr) & SATA_PSTATE_SHUTDOWN|| |
| AHCIPORT_GET_STATE(ahci_portp, &addr) & SATA_PSTATE_PWROFF) { |
| /* |
| * In case the target driver would send the packet before |
| * sata framework can have the opportunity to process those |
| * event reports. |
| */ |
| spkt->satapkt_reason = SATA_PKT_PORT_ERROR; |
| spkt->satapkt_device.satadev_state = |
| ahci_portp->ahciport_port_state; |
| ahci_update_sata_registers(ahci_ctlp, port, |
| &spkt->satapkt_device); |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning PORT_ERROR while " |
| "port in FAILED/SHUTDOWN/PWROFF state: " |
| "port: %s", portstr); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_PORT_ERROR); |
| } |
| |
| if (AHCIPORT_GET_DEV_TYPE(ahci_portp, &addr) == SATA_DTYPE_NONE) { |
| /* |
| * ahci_intr_phyrdy_change() may have rendered it to |
| * SATA_DTYPE_NONE. |
| */ |
| spkt->satapkt_reason = SATA_PKT_PORT_ERROR; |
| spkt->satapkt_device.satadev_type = SATA_DTYPE_NONE; |
| spkt->satapkt_device.satadev_state = |
| ahci_portp->ahciport_port_state; |
| ahci_update_sata_registers(ahci_ctlp, port, |
| &spkt->satapkt_device); |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning PORT_ERROR while " |
| "no device attached: port: %s", portstr); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_PORT_ERROR); |
| } |
| |
| /* R/W PMULT command will occupy the whole HBA port */ |
| if (RDWR_PMULT_CMD_IN_PROGRESS(ahci_portp)) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning BUSY while " |
| "executing READ/WRITE PORT-MULT command: " |
| "port: %s", portstr); |
| spkt->satapkt_reason = SATA_PKT_BUSY; |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_BUSY); |
| } |
| |
| if (ahci_portp->ahciport_flags & AHCI_PORT_FLAG_HOTPLUG) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning BUSY while " |
| "hot-plug in progress: port: %s", portstr); |
| spkt->satapkt_reason = SATA_PKT_BUSY; |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_BUSY); |
| } |
| |
| /* |
| * SATA HBA driver should remember that a device was reset and it |
| * is supposed to reject any packets which do not specify either |
| * SATA_IGNORE_DEV_RESET_STATE or SATA_CLEAR_DEV_RESET_STATE. |
| * |
| * This is to prevent a race condition when a device was arbitrarily |
| * reset by the HBA driver (and lost it's setting) and a target |
| * driver sending some commands to a device before the sata framework |
| * has a chance to restore the device setting (such as cache enable/ |
| * disable or other resettable stuff). |
| */ |
| /* |
| * It is unnecessary to use specific flags to indicate |
| * reset_in_progress for a pmport. While mopping, all command will be |
| * mopped so that the entire HBA port is being dealt as a single |
| * object. |
| */ |
| if (spkt->satapkt_cmd.satacmd_flags.sata_clear_dev_reset) { |
| ahci_portp->ahciport_reset_in_progress = 0; |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start [CLEAR] the " |
| "reset_in_progress for port: %d", port); |
| } |
| |
| if (ahci_portp->ahciport_reset_in_progress && |
| ! spkt->satapkt_cmd.satacmd_flags.sata_ignore_dev_reset && |
| ! ddi_in_panic()) { |
| spkt->satapkt_reason = SATA_PKT_BUSY; |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning BUSY while " |
| "reset in progress: port: %d", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_BUSY); |
| } |
| |
| #ifdef AHCI_DEBUG |
| if (spkt->satapkt_cmd.satacmd_flags.sata_ignore_dev_reset) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start: packet 0x%p [PASSTHRU] at port %d", |
| spkt, port); |
| } |
| #endif |
| |
| if (ahci_portp->ahciport_flags & AHCI_PORT_FLAG_MOPPING) { |
| spkt->satapkt_reason = SATA_PKT_BUSY; |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning BUSY while " |
| "mopping in progress: port: %d", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_BUSY); |
| } |
| |
| if (ahci_check_ctl_handle(ahci_ctlp) != DDI_SUCCESS) { |
| ddi_fm_service_impact(ahci_ctlp->ahcictl_dip, |
| DDI_SERVICE_UNAFFECTED); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_BUSY); |
| } |
| |
| if (spkt->satapkt_op_mode & |
| (SATA_OPMODE_SYNCH | SATA_OPMODE_POLLING)) { |
| /* |
| * If a SYNC command to be executed in interrupt context, |
| * bounce it back to sata module. |
| */ |
| if (!(spkt->satapkt_op_mode & SATA_OPMODE_POLLING) && |
| servicing_interrupt()) { |
| spkt->satapkt_reason = SATA_PKT_BUSY; |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_start returning BUSY while " |
| "sending SYNC mode under interrupt context: " |
| "port : %d", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_BUSY); |
| } |
| |
| /* We need to do the sync start now */ |
| if (ahci_do_sync_start(ahci_ctlp, ahci_portp, &addr, |
| spkt) == AHCI_FAILURE) { |
| goto fail_out; |
| } |
| } else { |
| /* Async start, using interrupt */ |
| if (ahci_deliver_satapkt(ahci_ctlp, ahci_portp, &addr, spkt) |
| == AHCI_FAILURE) { |
| spkt->satapkt_reason = SATA_PKT_QUEUE_FULL; |
| goto fail_out; |
| } |
| } |
| |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, "ahci_tran_start " |
| "sata tran accepted: port %s", portstr); |
| |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_ACCEPTED); |
| |
| fail_out: |
| /* |
| * Failed to deliver packet to the controller. |
| * Check if it's caused by invalid handles. |
| */ |
| if (ahci_check_ctl_handle(ahci_ctlp) != DDI_SUCCESS || |
| ahci_check_port_handle(ahci_ctlp, port) != DDI_SUCCESS) { |
| spkt->satapkt_device.satadev_type = |
| AHCIPORT_GET_DEV_TYPE(ahci_portp, &addr); |
| spkt->satapkt_device.satadev_state = |
| AHCIPORT_GET_STATE(ahci_portp, &addr); |
| spkt->satapkt_reason = SATA_PKT_DEV_ERROR; |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_PORT_ERROR); |
| } |
| |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, "ahci_tran_start " |
| "return QUEUE_FULL: port %d", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_TRAN_QUEUE_FULL); |
| } |
| |
| /* |
| * SATA_OPMODE_SYNCH flag is set |
| * |
| * If SATA_OPMODE_POLLING flag is set, then we must poll the command |
| * without interrupt, otherwise we can still use the interrupt. |
| */ |
| static int |
| ahci_do_sync_start(ahci_ctl_t *ahci_ctlp, ahci_port_t *ahci_portp, |
| ahci_addr_t *addrp, sata_pkt_t *spkt) |
| { |
| int pkt_timeout_ticks; |
| uint32_t timeout_tags; |
| int rval; |
| int instance = ddi_get_instance(ahci_ctlp->ahcictl_dip); |
| uint8_t port = addrp->aa_port; |
| |
| ASSERT(MUTEX_HELD(&ahci_portp->ahciport_mutex)); |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, "ahci_do_sync_start enter: " |
| "port %d:%d spkt 0x%p", port, addrp->aa_pmport, spkt); |
| |
| if (spkt->satapkt_op_mode & SATA_OPMODE_POLLING) { |
| ahci_portp->ahciport_flags |= AHCI_PORT_FLAG_POLLING; |
| if ((rval = ahci_deliver_satapkt(ahci_ctlp, ahci_portp, |
| addrp, spkt)) == AHCI_FAILURE) { |
| ahci_portp->ahciport_flags &= ~AHCI_PORT_FLAG_POLLING; |
| return (rval); |
| } |
| |
| pkt_timeout_ticks = |
| drv_usectohz((clock_t)spkt->satapkt_time * 1000000); |
| |
| while (spkt->satapkt_reason == SATA_PKT_BUSY) { |
| /* Simulate the interrupt */ |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| ahci_port_intr(ahci_ctlp, ahci_portp, port); |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| |
| if (spkt->satapkt_reason != SATA_PKT_BUSY) |
| break; |
| |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| drv_usecwait(AHCI_1MS_USECS); |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| |
| pkt_timeout_ticks -= AHCI_1MS_TICKS; |
| if (pkt_timeout_ticks < 0) { |
| cmn_err(CE_WARN, "!ahci%d: ahci_do_sync_start " |
| "port %d satapkt 0x%p timed out\n", |
| instance, port, (void *)spkt); |
| timeout_tags = (0x1 << rval); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| ahci_timeout_pkts(ahci_ctlp, ahci_portp, |
| port, timeout_tags); |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| } |
| } |
| |
| ahci_portp->ahciport_flags &= ~AHCI_PORT_FLAG_POLLING; |
| return (AHCI_SUCCESS); |
| |
| } else { |
| if ((rval = ahci_deliver_satapkt(ahci_ctlp, ahci_portp, |
| addrp, spkt)) == AHCI_FAILURE) |
| return (rval); |
| |
| #if AHCI_DEBUG |
| /* |
| * Note that the driver always uses the slot 0 to deliver |
| * REQUEST SENSE or READ LOG EXT command |
| */ |
| if (ERR_RETRI_CMD_IN_PROGRESS(ahci_portp)) |
| ASSERT(rval == 0); |
| #endif |
| |
| while (spkt->satapkt_reason == SATA_PKT_BUSY) |
| cv_wait(&ahci_portp->ahciport_cv, |
| &ahci_portp->ahciport_mutex); |
| |
| return (AHCI_SUCCESS); |
| } |
| } |
| |
| /* |
| * Searches for and claims a free command slot. |
| * |
| * Returns value: |
| * |
| * AHCI_FAILURE returned only if |
| * 1. No empty slot left |
| * 2. Non-queued command requested while queued command(s) is outstanding |
| * 3. Queued command requested while non-queued command(s) is outstanding |
| * 4. HBA doesn't support multiple-use of command list while already a |
| * non-queued command is oustanding |
| * 5. Queued command requested while some queued command(s) has been |
| * outstanding on a different port multiplier port. (AHCI spec 1.2, |
| * 9.1.2) |
| * |
| * claimed slot number returned if succeeded |
| * |
| * NOTE: it will always return slot 0 for following commands to simplify the |
| * algorithm. |
| * 1. REQUEST SENSE or READ LOG EXT command during error recovery process |
| * 2. READ/WRITE PORTMULT command |
| */ |
| static int |
| ahci_claim_free_slot(ahci_ctl_t *ahci_ctlp, ahci_port_t *ahci_portp, |
| ahci_addr_t *addrp, int command_type) |
| { |
| uint32_t port_cmd_issue; |
| uint32_t free_slots; |
| int slot; |
| |
| ASSERT(MUTEX_HELD(&ahci_portp->ahciport_mutex)); |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, "ahci_claim_free_slot enter " |
| "ahciport_pending_tags = 0x%x " |
| "ahciport_pending_ncq_tags = 0x%x", |
| ahci_portp->ahciport_pending_tags, |
| ahci_portp->ahciport_pending_ncq_tags); |
| |
| /* |
| * According to the AHCI spec, system software is responsible to |
| * ensure that queued and non-queued commands are not mixed in |
| * the command list. |
| */ |
| if (command_type == AHCI_NON_NCQ_CMD) { |
| /* Non-NCQ command request */ |
| if (NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| AHCIDBG(AHCIDBG_INFO|AHCIDBG_NCQ, ahci_ctlp, |
| "ahci_claim_free_slot: there is still pending " |
| "queued command(s) in the command list, " |
| "so no available slot for the non-queued " |
| "command", NULL); |
| return (AHCI_FAILURE); |
| } |
| if (RDWR_PMULT_CMD_IN_PROGRESS(ahci_portp)) { |
| AHCIDBG(AHCIDBG_INFO|AHCIDBG_PMULT, ahci_ctlp, |
| "ahci_claim_free_slot: there is still pending " |
| "read/write port-mult command(s) in command list, " |
| "so no available slot for the non-queued command", |
| NULL); |
| return (AHCI_FAILURE); |
| } |
| if ((ahci_ctlp->ahcictl_cap & AHCI_CAP_NO_MCMDLIST_NONQUEUE) && |
| NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_claim_free_slot: HBA cannot support multiple-" |
| "use of the command list for non-queued commands", |
| NULL); |
| return (AHCI_FAILURE); |
| } |
| free_slots = (~ahci_portp->ahciport_pending_tags) & |
| AHCI_SLOT_MASK(ahci_ctlp); |
| } else if (command_type == AHCI_NCQ_CMD) { |
| /* NCQ command request */ |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| AHCIDBG(AHCIDBG_INFO|AHCIDBG_NCQ, ahci_ctlp, |
| "ahci_claim_free_slot: there is still pending " |
| "non-queued command(s) in the command list, " |
| "so no available slot for the queued command", |
| NULL); |
| return (AHCI_FAILURE); |
| } |
| |
| /* |
| * NCQ commands cannot be sent to different port multiplier |
| * ports in Command-Based Switching mode |
| */ |
| /* |
| * NOTE: In Command-Based Switching mode, AHCI controller |
| * usually reports a 'Handshake Error' when multiple NCQ |
| * commands are outstanding simultaneously. |
| */ |
| if (AHCIPORT_DEV_TYPE(ahci_portp, addrp) == SATA_DTYPE_PMULT) { |
| ASSERT(ahci_portp->ahciport_pmult_info != NULL); |
| if (!(ahci_ctlp->ahcictl_cap & AHCI_CAP_PMULT_FBSS) && |
| NCQ_CMD_IN_PROGRESS(ahci_portp) && |
| AHCIPORT_NCQ_PMPORT(ahci_portp) != |
| addrp->aa_pmport) { |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_claim_free_slot: there is still " |
| "pending queued command(s) in the " |
| "command list for another Port Multiplier " |
| "port, so no available slot.", NULL); |
| return (AHCI_FAILURE); |
| } |
| } |
| |
| free_slots = (~ahci_portp->ahciport_pending_ncq_tags) & |
| AHCI_NCQ_SLOT_MASK(ahci_portp); |
| } else if (command_type == AHCI_ERR_RETRI_CMD) { |
| /* Error retrieval command request */ |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_claim_free_slot: slot 0 is allocated for REQUEST " |
| "SENSE or READ LOG EXT command", NULL); |
| slot = 0; |
| goto out; |
| } else if (command_type == AHCI_RDWR_PMULT_CMD) { |
| /* |
| * An extra check on PxCI. Sometimes PxCI bits may not be |
| * cleared during hot-plug or error recovery process. |
| */ |
| port_cmd_issue = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxCI(ahci_ctlp, addrp->aa_port)); |
| |
| if (port_cmd_issue != 0) { |
| AHCIDBG(AHCIDBG_INFO|AHCIDBG_PMULT, ahci_ctlp, |
| "ahci_claim_free_slot: there is still pending " |
| "command(s) in command list (0x%x/0x%x, PxCI %x)," |
| "so no available slot for R/W PMULT command.", |
| NON_NCQ_CMD_IN_PROGRESS(ahci_portp), |
| NCQ_CMD_IN_PROGRESS(ahci_portp), |
| port_cmd_issue); |
| return (AHCI_FAILURE); |
| } |
| |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_claim_free_slot: slot 0 is allocated for " |
| "READ/WRITE PORTMULT command", NULL); |
| slot = 0; |
| goto out; |
| } |
| |
| slot = ddi_ffs(free_slots) - 1; |
| if (slot == -1) { |
| AHCIDBG(AHCIDBG_VERBOSE, ahci_ctlp, |
| "ahci_claim_free_slot: no empty slots", NULL); |
| return (AHCI_FAILURE); |
| } |
| |
| /* |
| * According to the AHCI spec, to allow a simple mechanism for the |
| * HBA to map command list slots to queue entries, software must |
| * match the tag number it uses to the slot it is placing the command |
| * in. For example, if a queued command is placed in slot 5, the tag |
| * for that command must be 5. |
| */ |
| if (command_type == AHCI_NCQ_CMD) { |
| ahci_portp->ahciport_pending_ncq_tags |= (0x1 << slot); |
| if (AHCI_ADDR_IS_PMPORT(addrp)) { |
| ASSERT(ahci_portp->ahciport_pmult_info != NULL); |
| AHCIPORT_NCQ_PMPORT(ahci_portp) = addrp->aa_pmport; |
| } |
| } |
| |
| ahci_portp->ahciport_pending_tags |= (0x1 << slot); |
| |
| out: |
| AHCIDBG(AHCIDBG_VERBOSE, ahci_ctlp, |
| "ahci_claim_free_slot: found slot: 0x%x", slot); |
| |
| return (slot); |
| } |
| |
| /* |
| * Builds the Command Table for the sata packet and delivers it to controller. |
| * |
| * Returns: |
| * slot number if we can obtain a slot successfully |
| * otherwise, return AHCI_FAILURE |
| */ |
| static int |
| ahci_deliver_satapkt(ahci_ctl_t *ahci_ctlp, ahci_port_t *ahci_portp, |
| ahci_addr_t *addrp, sata_pkt_t *spkt) |
| { |
| int cmd_slot; |
| sata_cmd_t *scmd; |
| ahci_fis_h2d_register_t *h2d_register_fisp; |
| ahci_cmd_table_t *cmd_table; |
| ahci_cmd_header_t *cmd_header; |
| int ncookies; |
| int i; |
| int command_type = AHCI_NON_NCQ_CMD; |
| int ncq_qdepth; |
| int instance = ddi_get_instance(ahci_ctlp->ahcictl_dip); |
| uint8_t port, pmport; |
| #if AHCI_DEBUG |
| uint32_t *ptr; |
| uint8_t *ptr2; |
| #endif |
| |
| ASSERT(MUTEX_HELD(&ahci_portp->ahciport_mutex)); |
| |
| port = addrp->aa_port; |
| pmport = addrp->aa_pmport; |
| |
| spkt->satapkt_reason = SATA_PKT_BUSY; |
| |
| scmd = &spkt->satapkt_cmd; |
| |
| /* Check if the command is a NCQ command */ |
| if (scmd->satacmd_cmd_reg == SATAC_READ_FPDMA_QUEUED || |
| scmd->satacmd_cmd_reg == SATAC_WRITE_FPDMA_QUEUED) { |
| command_type = AHCI_NCQ_CMD; |
| |
| /* |
| * When NCQ is support, system software must determine the |
| * maximum tag allowed by the device and the HBA, and it |
| * must use a value not beyond of the lower bound of the two. |
| * |
| * Sata module is going to calculate the qdepth and send |
| * down to HBA driver via sata_cmd. |
| */ |
| ncq_qdepth = scmd->satacmd_flags.sata_max_queue_depth + 1; |
| |
| /* |
| * At the moment, the driver doesn't support the dynamic |
| * setting of the maximum ncq depth, and the value can be |
| * set either during the attach or after hot-plug insertion. |
| */ |
| if (ahci_portp->ahciport_max_ncq_tags == 0) { |
| ahci_portp->ahciport_max_ncq_tags = ncq_qdepth; |
| AHCIDBG(AHCIDBG_NCQ, ahci_ctlp, |
| "ahci_deliver_satapkt: port %d the max tags for " |
| "NCQ command is %d", port, ncq_qdepth); |
| } else { |
| if (ncq_qdepth != ahci_portp->ahciport_max_ncq_tags) { |
| cmn_err(CE_WARN, "!ahci%d: ahci_deliver_satapkt" |
| " port %d the max tag for NCQ command is " |
| "requested to change from %d to %d, at the" |
| " moment the driver doesn't support the " |
| "dynamic change so it's going to " |
| "still use the previous tag value", |
| instance, port, |
| ahci_portp->ahciport_max_ncq_tags, |
| ncq_qdepth); |
| } |
| } |
| } |
| |
| /* Check if the command is an error retrieval command */ |
| if (ERR_RETRI_CMD_IN_PROGRESS(ahci_portp)) |
| command_type = AHCI_ERR_RETRI_CMD; |
| |
| /* Check if the command is an read/write pmult command */ |
| if (RDWR_PMULT_CMD_IN_PROGRESS(ahci_portp)) |
| command_type = AHCI_RDWR_PMULT_CMD; |
| |
| /* Check if there is an empty command slot */ |
| cmd_slot = ahci_claim_free_slot(ahci_ctlp, ahci_portp, |
| addrp, command_type); |
| if (cmd_slot == AHCI_FAILURE) { |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, "no free command slot", NULL); |
| return (AHCI_FAILURE); |
| } |
| |
| AHCIDBG(AHCIDBG_ENTRY|AHCIDBG_INFO, ahci_ctlp, |
| "ahci_deliver_satapkt enter: cmd_reg: 0x%x, cmd_slot: 0x%x, " |
| "port: %d, satapkt: 0x%p", scmd->satacmd_cmd_reg, |
| cmd_slot, port, (void *)spkt); |
| |
| cmd_table = ahci_portp->ahciport_cmd_tables[cmd_slot]; |
| bzero((void *)cmd_table, ahci_cmd_table_size); |
| |
| /* For data transfer operations, it is the H2D Register FIS */ |
| h2d_register_fisp = |
| &(cmd_table->ahcict_command_fis.ahcifc_fis.ahcifc_h2d_register); |
| |
| SET_FIS_TYPE(h2d_register_fisp, AHCI_H2D_REGISTER_FIS_TYPE); |
| |
| /* |
| * PMP field only make sense when target is a port multiplier or a |
| * device behind a port multiplier. Otherwise should set it to 0. |
| */ |
| if (AHCI_ADDR_IS_PMULT(addrp) || AHCI_ADDR_IS_PMPORT(addrp)) |
| SET_FIS_PMP(h2d_register_fisp, pmport); |
| |
| SET_FIS_CDMDEVCTL(h2d_register_fisp, 1); |
| SET_FIS_COMMAND(h2d_register_fisp, scmd->satacmd_cmd_reg); |
| SET_FIS_FEATURES(h2d_register_fisp, scmd->satacmd_features_reg); |
| SET_FIS_SECTOR_COUNT(h2d_register_fisp, scmd->satacmd_sec_count_lsb); |
| |
| switch (scmd->satacmd_addr_type) { |
| |
| case 0: |
| /* |
| * satacmd_addr_type will be 0 for the commands below: |
| * ATAPI command |
| * SATAC_IDLE_IM |
| * SATAC_STANDBY_IM |
| * SATAC_DOWNLOAD_MICROCODE |
| * SATAC_FLUSH_CACHE |
| * SATAC_SET_FEATURES |
| * SATAC_SMART |
| * SATAC_ID_PACKET_DEVICE |
| * SATAC_ID_DEVICE |
| * SATAC_READ_PORTMULT |
| * SATAC_WRITE_PORTMULT |
| */ |
| /* FALLTHRU */ |
| |
| case ATA_ADDR_LBA: |
| /* FALLTHRU */ |
| |
| case ATA_ADDR_LBA28: |
| /* LBA[7:0] */ |
| SET_FIS_SECTOR(h2d_register_fisp, scmd->satacmd_lba_low_lsb); |
| |
| /* LBA[15:8] */ |
| SET_FIS_CYL_LOW(h2d_register_fisp, scmd->satacmd_lba_mid_lsb); |
| |
| /* LBA[23:16] */ |
| SET_FIS_CYL_HI(h2d_register_fisp, scmd->satacmd_lba_high_lsb); |
| |
| /* LBA [27:24] (also called dev_head) */ |
| SET_FIS_DEV_HEAD(h2d_register_fisp, scmd->satacmd_device_reg); |
| |
| break; |
| |
| case ATA_ADDR_LBA48: |
| /* LBA[7:0] */ |
| SET_FIS_SECTOR(h2d_register_fisp, scmd->satacmd_lba_low_lsb); |
| |
| /* LBA[15:8] */ |
| SET_FIS_CYL_LOW(h2d_register_fisp, scmd->satacmd_lba_mid_lsb); |
| |
| /* LBA[23:16] */ |
| SET_FIS_CYL_HI(h2d_register_fisp, scmd->satacmd_lba_high_lsb); |
| |
| /* LBA [31:24] */ |
| SET_FIS_SECTOR_EXP(h2d_register_fisp, |
| scmd->satacmd_lba_low_msb); |
| |
| /* LBA [39:32] */ |
| SET_FIS_CYL_LOW_EXP(h2d_register_fisp, |
| scmd->satacmd_lba_mid_msb); |
| |
| /* LBA [47:40] */ |
| SET_FIS_CYL_HI_EXP(h2d_register_fisp, |
| scmd->satacmd_lba_high_msb); |
| |
| /* Set dev_head */ |
| SET_FIS_DEV_HEAD(h2d_register_fisp, |
| scmd->satacmd_device_reg); |
| |
| /* Set the extended sector count and features */ |
| SET_FIS_SECTOR_COUNT_EXP(h2d_register_fisp, |
| scmd->satacmd_sec_count_msb); |
| SET_FIS_FEATURES_EXP(h2d_register_fisp, |
| scmd->satacmd_features_reg_ext); |
| break; |
| } |
| |
| /* |
| * For NCQ command (READ/WRITE FPDMA QUEUED), sector count 7:0 is |
| * filled into features field, and sector count 8:15 is filled into |
| * features (exp) field. The hba driver doesn't need to anything |
| * special with regard to this, since sata framework has already |
| * done so. |
| * |
| * However the driver needs to make sure TAG is filled into sector |
| * field. |
| */ |
| if (command_type == AHCI_NCQ_CMD) { |
| SET_FIS_SECTOR_COUNT(h2d_register_fisp, |
| (cmd_slot << SATA_TAG_QUEUING_SHIFT)); |
| } |
| |
| ncookies = scmd->satacmd_num_dma_cookies; |
| AHCIDBG(AHCIDBG_PRDT, ahci_ctlp, |
| "ncookies = 0x%x, ahci_dma_prdt_number = 0x%x", |
| ncookies, ahci_dma_prdt_number); |
| |
| ASSERT(ncookies <= ahci_dma_prdt_number); |
| ahci_portp->ahciport_prd_bytecounts[cmd_slot] = 0; |
| |
| /* *** now fill the scatter gather list ******* */ |
| for (i = 0; i < ncookies; i++) { |
| cmd_table->ahcict_prdt[i].ahcipi_data_base_addr = |
| scmd->satacmd_dma_cookie_list[i]._dmu._dmac_la[0]; |
| cmd_table->ahcict_prdt[i].ahcipi_data_base_addr_upper = |
| scmd->satacmd_dma_cookie_list[i]._dmu._dmac_la[1]; |
| cmd_table->ahcict_prdt[i].ahcipi_descr_info = |
| scmd->satacmd_dma_cookie_list[i].dmac_size - 1; |
| ahci_portp->ahciport_prd_bytecounts[cmd_slot] += |
| scmd->satacmd_dma_cookie_list[i].dmac_size; |
| } |
| |
| AHCIDBG(AHCIDBG_PRDT, ahci_ctlp, |
| "ahciport_prd_bytecounts 0x%x for cmd_slot 0x%x", |
| ahci_portp->ahciport_prd_bytecounts[cmd_slot], cmd_slot); |
| |
| /* The ACMD field is filled in for ATAPI command */ |
| if (scmd->satacmd_cmd_reg == SATAC_PACKET) { |
| bcopy(scmd->satacmd_acdb, cmd_table->ahcict_atapi_cmd, |
| SATA_ATAPI_MAX_CDB_LEN); |
| } |
| |
| /* Set Command Header in Command List */ |
| cmd_header = &ahci_portp->ahciport_cmd_list[cmd_slot]; |
| BZERO_DESCR_INFO(cmd_header); |
| BZERO_PRD_BYTE_COUNT(cmd_header); |
| |
| /* Set the number of entries in the PRD table */ |
| SET_PRD_TABLE_LENGTH(cmd_header, ncookies); |
| |
| /* Set the length of the command in the CFIS area */ |
| SET_COMMAND_FIS_LENGTH(cmd_header, AHCI_H2D_REGISTER_FIS_LENGTH); |
| |
| /* |
| * PMP field only make sense when target is a port multiplier or a |
| * device behind a port multiplier. Otherwise should set it to 0. |
| */ |
| if (AHCI_ADDR_IS_PMULT(addrp) || AHCI_ADDR_IS_PMPORT(addrp)) |
| SET_PORT_MULTI_PORT(cmd_header, pmport); |
| |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, "command data direction is " |
| "sata_data_direction = 0x%x", |
| scmd->satacmd_flags.sata_data_direction); |
| |
| /* Set A bit if it is an ATAPI command */ |
| if (scmd->satacmd_cmd_reg == SATAC_PACKET) |
| SET_ATAPI(cmd_header, AHCI_CMDHEAD_ATAPI); |
| |
| /* Set W bit if data is going to the device */ |
| if (scmd->satacmd_flags.sata_data_direction == SATA_DIR_WRITE) |
| SET_WRITE(cmd_header, AHCI_CMDHEAD_DATA_WRITE); |
| |
| /* |
| * Set the prefetchable bit - this bit is only valid if the PRDTL |
| * field is non-zero or the ATAPI 'A' bit is set in the command |
| * header. This bit cannot be set when using native command |
| * queuing commands or when using FIS-based switching with a Port |
| * multiplier. |
| */ |
| if (command_type != AHCI_NCQ_CMD) |
| SET_PREFETCHABLE(cmd_header, AHCI_CMDHEAD_PREFETCHABLE); |
| |
| /* |
| * Now remember the sata packet in ahciport_slot_pkts[]. |
| * Error retrieval command and r/w port multiplier command will |
| * be stored specifically for each port. |
| */ |
| if (!ERR_RETRI_CMD_IN_PROGRESS(ahci_portp) && |
| !RDWR_PMULT_CMD_IN_PROGRESS(ahci_portp)) |
| ahci_portp->ahciport_slot_pkts[cmd_slot] = spkt; |
| |
| /* |
| * Keep the timeout value |
| */ |
| ahci_portp->ahciport_slot_timeout[cmd_slot] = spkt->satapkt_time; |
| |
| /* |
| * If the intial timout is less than 1 tick, then make it longer by |
| * 1 tick to avoid immediate timeout |
| */ |
| if (ahci_portp->ahciport_slot_timeout[cmd_slot] <= |
| ahci_watchdog_timeout) |
| ahci_portp->ahciport_slot_timeout[cmd_slot] += |
| ahci_watchdog_timeout; |
| |
| #if AHCI_DEBUG |
| if (ahci_debug_flags & AHCIDBG_ATACMD && |
| scmd->satacmd_cmd_reg != SATAC_PACKET || |
| ahci_debug_flags & AHCIDBG_ATAPICMD && |
| scmd->satacmd_cmd_reg == SATAC_PACKET) { |
| |
| /* Dump the command header and table */ |
| ahci_log(ahci_ctlp, CE_WARN, "\n"); |
| ahci_log(ahci_ctlp, CE_WARN, "Command header&table for spkt " |
| "0x%p cmd_reg 0x%x port %d", spkt, |
| scmd->satacmd_cmd_reg, port); |
| ptr = (uint32_t *)cmd_header; |
| ahci_log(ahci_ctlp, CE_WARN, |
| " Command Header:%8x %8x %8x %8x", |
| ptr[0], ptr[1], ptr[2], ptr[3]); |
| |
| /* Dump the H2D register FIS */ |
| ptr = (uint32_t *)h2d_register_fisp; |
| ahci_log(ahci_ctlp, CE_WARN, |
| " Command FIS: %8x %8x %8x %8x", |
| ptr[0], ptr[1], ptr[2], ptr[3]); |
| |
| /* Dump the ACMD register FIS */ |
| ptr2 = (uint8_t *)&(cmd_table->ahcict_atapi_cmd); |
| for (i = 0; i < SATA_ATAPI_MAX_CDB_LEN/8; i++) |
| if (ahci_debug_flags & AHCIDBG_ATAPICMD) |
| ahci_log(ahci_ctlp, CE_WARN, |
| " ATAPI command: %2x %2x %2x %2x " |
| "%2x %2x %2x %2x", |
| ptr2[8 * i], ptr2[8 * i + 1], |
| ptr2[8 * i + 2], ptr2[8 * i + 3], |
| ptr2[8 * i + 4], ptr2[8 * i + 5], |
| ptr2[8 * i + 6], ptr2[8 * i + 7]); |
| |
| /* Dump the PRDT */ |
| for (i = 0; i < ncookies; i++) { |
| ptr = (uint32_t *)&(cmd_table->ahcict_prdt[i]); |
| ahci_log(ahci_ctlp, CE_WARN, |
| " Cookie %d: %8x %8x %8x %8x", |
| i, ptr[0], ptr[1], ptr[2], ptr[3]); |
| } |
| } |
| #endif |
| |
| (void) ddi_dma_sync( |
| ahci_portp->ahciport_cmd_tables_dma_handle[cmd_slot], |
| 0, |
| ahci_cmd_table_size, |
| DDI_DMA_SYNC_FORDEV); |
| |
| (void) ddi_dma_sync(ahci_portp->ahciport_cmd_list_dma_handle, |
| cmd_slot * sizeof (ahci_cmd_header_t), |
| sizeof (ahci_cmd_header_t), |
| DDI_DMA_SYNC_FORDEV); |
| |
| if ((ahci_check_dma_handle(ahci_portp-> |
| ahciport_cmd_tables_dma_handle[cmd_slot]) != DDI_FM_OK) || |
| ahci_check_dma_handle(ahci_portp-> |
| ahciport_cmd_list_dma_handle) != DDI_FM_OK) { |
| ddi_fm_service_impact(ahci_ctlp->ahcictl_dip, |
| DDI_SERVICE_UNAFFECTED); |
| return (AHCI_FAILURE); |
| } |
| |
| /* Set the corresponding bit in the PxSACT.DS for queued command */ |
| if (command_type == AHCI_NCQ_CMD) { |
| ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxSACT(ahci_ctlp, port), |
| (0x1 << cmd_slot)); |
| } |
| |
| /* Indicate to the HBA that a command is active. */ |
| ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxCI(ahci_ctlp, port), |
| (0x1 << cmd_slot)); |
| |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, "ahci_deliver_satapkt " |
| "exit: port %d", port); |
| |
| /* Make sure the command is started by the PxSACT/PxCI */ |
| if (ahci_check_acc_handle(ahci_ctlp-> |
| ahcictl_ahci_acc_handle) != DDI_FM_OK) { |
| ddi_fm_service_impact(ahci_ctlp->ahcictl_dip, |
| DDI_SERVICE_UNAFFECTED); |
| return (AHCI_FAILURE); |
| } |
| |
| return (cmd_slot); |
| } |
| |
| /* |
| * Called by the sata framework to abort the previously sent packet(s). |
| * |
| * Reset device to abort commands. |
| */ |
| static int |
| ahci_tran_abort(dev_info_t *dip, sata_pkt_t *spkt, int flag) |
| { |
| ahci_ctl_t *ahci_ctlp; |
| ahci_port_t *ahci_portp; |
| uint32_t slot_status = 0; |
| uint32_t aborted_tags = 0; |
| uint32_t finished_tags = 0; |
| uint8_t cport = spkt->satapkt_device.satadev_addr.cport; |
| uint8_t port; |
| int tmp_slot; |
| int instance = ddi_get_instance(dip); |
| |
| ahci_ctlp = ddi_get_soft_state(ahci_statep, instance); |
| port = ahci_ctlp->ahcictl_cport_to_port[cport]; |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, |
| "ahci_tran_abort enter: port %d", port); |
| |
| ahci_portp = ahci_ctlp->ahcictl_ports[port]; |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| |
| /* |
| * If AHCI_PORT_FLAG_MOPPING flag is set, it means all the pending |
| * commands are being mopped, therefore there is nothing else to do |
| */ |
| if (ahci_portp->ahciport_flags & AHCI_PORT_FLAG_MOPPING) { |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_abort: port %d is in " |
| "mopping process, so just return directly ", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_SUCCESS); |
| } |
| |
| /* |
| * If AHCI_PORT_FLAG_RDWR_PMULT flag is set, it means a R/W PMULT |
| * command is being executed so no other commands is outstanding, |
| * nothing to do. |
| */ |
| if (ahci_portp->ahciport_flags & AHCI_PORT_FLAG_RDWR_PMULT) { |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "ahci_tran_abort: port %d is reading/writing " |
| "port multiplier, so just return directly ", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_SUCCESS); |
| } |
| |
| if (ahci_portp->ahciport_port_state & SATA_PSTATE_FAILED | |
| ahci_portp->ahciport_port_state & SATA_PSTATE_SHUTDOWN | |
| ahci_portp->ahciport_port_state & SATA_PSTATE_PWROFF) { |
| /* |
| * In case the targer driver would send the request before |
| * sata framework can have the opportunity to process those |
| * event reports. |
| */ |
| spkt->satapkt_reason = SATA_PKT_PORT_ERROR; |
| spkt->satapkt_device.satadev_state = |
| ahci_portp->ahciport_port_state; |
| ahci_update_sata_registers(ahci_ctlp, port, |
| &spkt->satapkt_device); |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_abort returning SATA_FAILURE while " |
| "port in FAILED/SHUTDOWN/PWROFF state: " |
| "port: %d", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_FAILURE); |
| } |
| |
| if (ahci_portp->ahciport_device_type == SATA_DTYPE_NONE) { |
| /* |
| * ahci_intr_phyrdy_change() may have rendered it to |
| * AHCI_PORT_TYPE_NODEV. |
| */ |
| spkt->satapkt_reason = SATA_PKT_PORT_ERROR; |
| spkt->satapkt_device.satadev_type = SATA_DTYPE_NONE; |
| spkt->satapkt_device.satadev_state = |
| ahci_portp->ahciport_port_state; |
| ahci_update_sata_registers(ahci_ctlp, port, |
| &spkt->satapkt_device); |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_tran_abort returning SATA_FAILURE while " |
| "no device attached: port: %d", port); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_FAILURE); |
| } |
| |
| if (flag == SATA_ABORT_ALL_PACKETS) { |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| aborted_tags = ahci_portp->ahciport_pending_tags; |
| else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| aborted_tags = ahci_portp->ahciport_pending_ncq_tags; |
| |
| cmn_err(CE_NOTE, "!ahci%d: ahci port %d abort all packets", |
| instance, port); |
| } else { |
| aborted_tags = 0xffffffff; |
| /* |
| * Aborting one specific packet, first search the |
| * ahciport_slot_pkts[] list for matching spkt. |
| */ |
| for (tmp_slot = 0; |
| tmp_slot < ahci_ctlp->ahcictl_num_cmd_slots; tmp_slot++) { |
| if (ahci_portp->ahciport_slot_pkts[tmp_slot] == spkt) { |
| aborted_tags = (0x1 << tmp_slot); |
| break; |
| } |
| } |
| |
| if (aborted_tags == 0xffffffff) { |
| /* request packet is not on the pending list */ |
| AHCIDBG(AHCIDBG_INFO, ahci_ctlp, |
| "Cannot find the aborting pkt 0x%p on the " |
| "pending list", (void *)spkt); |
| ahci_update_sata_registers(ahci_ctlp, port, |
| &spkt->satapkt_device); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| return (SATA_FAILURE); |
| } |
| cmn_err(CE_NOTE, "!ahci%d: ahci port %d abort satapkt 0x%p", |
| instance, port, (void *)spkt); |
| } |
| |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxCI(ahci_ctlp, port)); |
| else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxSACT(ahci_ctlp, port)); |
| |
| ahci_portp->ahciport_flags |= AHCI_PORT_FLAG_MOPPING; |
| ahci_portp->ahciport_mop_in_progress++; |
| |
| /* |
| * To abort the packet(s), first we are trying to clear PxCMD.ST |
| * to stop the port, and if the port can be stopped |
| * successfully with PxTFD.STS.BSY and PxTFD.STS.DRQ cleared to '0', |
| * then we just send back the aborted packet(s) with ABORTED flag |
| * and then restart the port by setting PxCMD.ST and PxCMD.FRE. |
| * If PxTFD.STS.BSY or PxTFD.STS.DRQ is set to '1', then we |
| * perform a COMRESET. |
| */ |
| (void) ahci_restart_port_wait_till_ready(ahci_ctlp, |
| ahci_portp, port, NULL, NULL); |
| |
| /* |
| * Compute which have finished and which need to be retried. |
| * |
| * The finished tags are ahciport_pending_tags/ahciport_pending_ncq_tags |
| * minus the slot_status. The aborted_tags has to be deducted by |
| * finished_tags since we can't possibly abort a tag which had finished |
| * already. |
| */ |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| finished_tags = ahci_portp->ahciport_pending_tags & |
| ~slot_status & AHCI_SLOT_MASK(ahci_ctlp); |
| else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| finished_tags = ahci_portp->ahciport_pending_ncq_tags & |
| ~slot_status & AHCI_NCQ_SLOT_MASK(ahci_portp); |
| |
| aborted_tags &= ~finished_tags; |
| |
| ahci_mop_commands(ahci_ctlp, |
| ahci_portp, |
| slot_status, |
| 0, /* failed tags */ |
| 0, /* timeout tags */ |
| aborted_tags, |
| 0); /* reset tags */ |
| |
| ahci_update_sata_registers(ahci_ctlp, port, &spkt->satapkt_device); |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| |
| return (SATA_SUCCESS); |
| } |
| |
| /* |
| * Used to do device reset and reject all the pending packets on a device |
| * during the reset operation. |
| * |
| * NOTE: ONLY called by ahci_tran_reset_dport |
| */ |
| static int |
| ahci_reset_device_reject_pkts(ahci_ctl_t *ahci_ctlp, |
| ahci_port_t *ahci_portp, ahci_addr_t *addrp) |
| { |
| uint32_t slot_status = 0; |
| uint32_t reset_tags = 0; |
| uint32_t finished_tags = 0; |
| uint8_t port = addrp->aa_port; |
| sata_device_t sdevice; |
| int ret; |
| |
| ASSERT(MUTEX_HELD(&ahci_portp->ahciport_mutex)); |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, |
| "ahci_reset_device_reject_pkts on port: %d", port); |
| |
| /* |
| * If AHCI_PORT_FLAG_MOPPING flag is set, it means all the pending |
| * commands are being mopped, therefore there is nothing else to do |
| */ |
| if (ahci_portp->ahciport_flags & AHCI_PORT_FLAG_MOPPING) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_reset_device_reject_pkts: port %d is in " |
| "mopping process, so return directly ", port); |
| return (SATA_SUCCESS); |
| } |
| |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxCI(ahci_ctlp, port)); |
| reset_tags = slot_status & AHCI_SLOT_MASK(ahci_ctlp); |
| } else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxSACT(ahci_ctlp, port)); |
| reset_tags = slot_status & AHCI_NCQ_SLOT_MASK(ahci_portp); |
| } |
| |
| if (ahci_software_reset(ahci_ctlp, ahci_portp, addrp) |
| != AHCI_SUCCESS) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "Try to do a port reset after software " |
| "reset failed", port); |
| ret = ahci_port_reset(ahci_ctlp, ahci_portp, addrp); |
| if (ret != AHCI_SUCCESS) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_reset_device_reject_pkts: port %d " |
| "failed", port); |
| return (SATA_FAILURE); |
| } |
| } |
| /* Set the reset in progress flag */ |
| ahci_portp->ahciport_reset_in_progress = 1; |
| |
| ahci_portp->ahciport_flags |= AHCI_PORT_FLAG_MOPPING; |
| ahci_portp->ahciport_mop_in_progress++; |
| |
| /* Indicate to the framework that a reset has happened */ |
| bzero((void *)&sdevice, sizeof (sata_device_t)); |
| sdevice.satadev_addr.cport = ahci_ctlp->ahcictl_port_to_cport[port]; |
| sdevice.satadev_addr.pmport = 0; |
| sdevice.satadev_addr.qual = SATA_ADDR_DCPORT; |
| sdevice.satadev_state = SATA_DSTATE_RESET | |
| SATA_DSTATE_PWR_ACTIVE; |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| sata_hba_event_notify( |
| ahci_ctlp->ahcictl_sata_hba_tran->sata_tran_hba_dip, |
| &sdevice, |
| SATA_EVNT_DEVICE_RESET); |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| |
| AHCIDBG(AHCIDBG_EVENT, ahci_ctlp, |
| "port %d sending event up: SATA_EVNT_DEVICE_RESET", port); |
| |
| /* Next try to mop the pending commands */ |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| finished_tags = ahci_portp->ahciport_pending_tags & |
| ~slot_status & AHCI_SLOT_MASK(ahci_ctlp); |
| else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| finished_tags = ahci_portp->ahciport_pending_ncq_tags & |
| ~slot_status & AHCI_NCQ_SLOT_MASK(ahci_portp); |
| |
| reset_tags &= ~finished_tags; |
| |
| ahci_mop_commands(ahci_ctlp, |
| ahci_portp, |
| slot_status, |
| 0, /* failed tags */ |
| 0, /* timeout tags */ |
| 0, /* aborted tags */ |
| reset_tags); /* reset tags */ |
| |
| return (SATA_SUCCESS); |
| } |
| |
| /* |
| * Used to do device reset and reject all the pending packets on a device |
| * during the reset operation. |
| * |
| * NOTE: ONLY called by ahci_tran_reset_dport |
| */ |
| static int |
| ahci_reset_pmdevice_reject_pkts(ahci_ctl_t *ahci_ctlp, |
| ahci_port_t *ahci_portp, ahci_addr_t *addrp) |
| { |
| uint32_t finished_tags = 0, reset_tags = 0, slot_status = 0; |
| uint8_t port = addrp->aa_port; |
| uint8_t pmport = addrp->aa_pmport; |
| sata_device_t sdevice; |
| |
| ASSERT(MUTEX_HELD(&ahci_portp->ahciport_mutex)); |
| |
| AHCIDBG(AHCIDBG_ENTRY|AHCIDBG_PMULT, ahci_ctlp, |
| "ahci_reset_pmdevice_reject_pkts at port %d:%d", port, pmport); |
| |
| if (ahci_portp->ahciport_flags & AHCI_PORT_FLAG_MOPPING) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_reset_pmdevice_reject_pkts: port %d is in " |
| "mopping process, so return directly ", port); |
| return (SATA_SUCCESS); |
| } |
| |
| /* Checking for outstanding commands */ |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxCI(ahci_ctlp, port)); |
| reset_tags = slot_status & AHCI_SLOT_MASK(ahci_ctlp); |
| } else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxSACT(ahci_ctlp, port)); |
| reset_tags = slot_status & AHCI_NCQ_SLOT_MASK(ahci_portp); |
| } |
| |
| /* Issue SOFTWARE reset command. */ |
| if (ahci_software_reset(ahci_ctlp, ahci_portp, addrp) |
| != AHCI_SUCCESS) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "Try to do a port reset after software " |
| "reset failed", port); |
| return (SATA_FAILURE); |
| } |
| |
| /* Set the reset in progress flag */ |
| ahci_portp->ahciport_reset_in_progress = 1; |
| |
| ahci_portp->ahciport_flags |= AHCI_PORT_FLAG_MOPPING; |
| ahci_portp->ahciport_mop_in_progress++; |
| |
| /* Indicate to the framework that a reset has happened */ |
| bzero((void *)&sdevice, sizeof (sata_device_t)); |
| sdevice.satadev_addr.cport = ahci_ctlp->ahcictl_port_to_cport[port]; |
| sdevice.satadev_addr.pmport = pmport; |
| if (AHCI_ADDR_IS_PMULT(addrp)) |
| sdevice.satadev_addr.qual = SATA_ADDR_PMULT; |
| else |
| sdevice.satadev_addr.qual = SATA_ADDR_DPMPORT; |
| sdevice.satadev_state = SATA_DSTATE_RESET | |
| SATA_DSTATE_PWR_ACTIVE; |
| mutex_exit(&ahci_portp->ahciport_mutex); |
| sata_hba_event_notify( |
| ahci_ctlp->ahcictl_sata_hba_tran->sata_tran_hba_dip, |
| &sdevice, |
| SATA_EVNT_DEVICE_RESET); |
| mutex_enter(&ahci_portp->ahciport_mutex); |
| |
| AHCIDBG(AHCIDBG_EVENT, ahci_ctlp, |
| "port %d:%d sending event up: SATA_EVNT_DEVICE_RESET", |
| port, pmport); |
| |
| /* Next try to mop the pending commands */ |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| finished_tags = ahci_portp->ahciport_pending_tags & |
| ~slot_status & AHCI_SLOT_MASK(ahci_ctlp); |
| else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) |
| finished_tags = ahci_portp->ahciport_pending_ncq_tags & |
| ~slot_status & AHCI_NCQ_SLOT_MASK(ahci_portp); |
| reset_tags &= ~finished_tags; |
| |
| AHCIDBG(AHCIDBG_EVENT|AHCIDBG_PMULT, ahci_ctlp, |
| "reset_tags = %x, finished_tags = %x, slot_status = %x", |
| reset_tags, finished_tags, slot_status); |
| |
| /* |
| * NOTE: Because PxCI be only erased by unset PxCMD.ST bit, so even we |
| * try to reset a single device behind a port multiplier will |
| * terminate all the commands on that HBA port. We need mop these |
| * commands as well. |
| */ |
| ahci_mop_commands(ahci_ctlp, |
| ahci_portp, |
| slot_status, |
| 0, /* failed tags */ |
| 0, /* timeout tags */ |
| 0, /* aborted tags */ |
| reset_tags); /* reset tags */ |
| |
| return (SATA_SUCCESS); |
| } |
| |
| /* |
| * Used to do port reset and reject all the pending packets on a port during |
| * the reset operation. |
| */ |
| static int |
| ahci_reset_port_reject_pkts(ahci_ctl_t *ahci_ctlp, |
| ahci_port_t *ahci_portp, ahci_addr_t *addrp) |
| { |
| uint32_t slot_status = 0; |
| uint32_t reset_tags = 0; |
| uint32_t finished_tags = 0; |
| uint8_t port = addrp->aa_port; |
| |
| ASSERT(MUTEX_HELD(&ahci_portp->ahciport_mutex)); |
| |
| AHCIDBG(AHCIDBG_ENTRY, ahci_ctlp, |
| "ahci_reset_port_reject_pkts at port: %d", port); |
| |
| /* |
| * If AHCI_PORT_FLAG_MOPPING flag is set, it means all the pending |
| * commands are being mopped, therefore there is nothing else to do |
| */ |
| if (ahci_portp->ahciport_flags & AHCI_PORT_FLAG_MOPPING) { |
| AHCIDBG(AHCIDBG_ERRS, ahci_ctlp, |
| "ahci_reset_port_reject_pkts: port %d is in " |
| "mopping process, so return directly ", port); |
| return (SATA_SUCCESS); |
| } |
| |
| ahci_portp->ahciport_flags |= AHCI_PORT_FLAG_MOPPING; |
| ahci_portp->ahciport_mop_in_progress++; |
| |
| if (NON_NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxCI(ahci_ctlp, port)); |
| reset_tags = slot_status & AHCI_SLOT_MASK(ahci_ctlp); |
| } else if (NCQ_CMD_IN_PROGRESS(ahci_portp)) { |
| slot_status = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle, |
| (uint32_t *)AHCI_PORT_PxSACT(ahci_ctlp, port)); |
| reset_tags = slot_status & AHCI_NCQ_SLOT_MASK(ahci_portp); |
| } |
| |
| if (ahci_restart_port_wait_till_ready(ahci_ctlp, |
| ahci_portp, port, AHCI_PORT_RESET|AHCI_RESET_NO_EVENTS_UP, |
| NULL) != AHCI_SUCCESS) { |
| |
| /* Clear mop flag */ |
| |