mirror of
https://github.com/torvalds/linux.git
synced 2024-12-15 15:41:58 +00:00
5f2d8c3650
Drivers should do only device-specific jobs. But in general, drivers using legacy PCI PM framework for .suspend()/.resume() have to manage many PCI PM-related tasks themselves which can be done by PCI Core itself. This brings extra load on the driver and it directly calls PCI helper functions to handle them. Switch to the new generic framework by updating function signatures and define a "struct dev_pm_ops" variable to bind PM callbacks. Also, remove unnecessary calls to the PCI Helper functions along with the legacy .suspend & .resume bindings. Link: https://lore.kernel.org/r/20201102164730.324035-12-vaibhavgupta40@gmail.com Signed-off-by: Vaibhav Gupta <vaibhavgupta40@gmail.com> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
1700 lines
44 KiB
C
1700 lines
44 KiB
C
/*
|
|
* linux/drivers/scsi/esas2r/esas2r_init.c
|
|
* For use with ATTO ExpressSAS R6xx SAS/SATA RAID controllers
|
|
*
|
|
* Copyright (c) 2001-2013 ATTO Technology, Inc.
|
|
* (mailto:linuxdrivers@attotech.com)mpt3sas/mpt3sas_trigger_diag.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* NO WARRANTY
|
|
* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
|
|
* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
|
|
* solely responsible for determining the appropriateness of using and
|
|
* distributing the Program and assumes all risks associated with its
|
|
* exercise of rights under this Agreement, including but not limited to
|
|
* the risks and costs of program errors, damage to or loss of data,
|
|
* programs or equipment, and unavailability or interruption of operations.
|
|
*
|
|
* DISCLAIMER OF LIABILITY
|
|
* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
|
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
|
|
* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
* USA.
|
|
*/
|
|
|
|
#include "esas2r.h"
|
|
|
|
static bool esas2r_initmem_alloc(struct esas2r_adapter *a,
|
|
struct esas2r_mem_desc *mem_desc,
|
|
u32 align)
|
|
{
|
|
mem_desc->esas2r_param = mem_desc->size + align;
|
|
mem_desc->virt_addr = NULL;
|
|
mem_desc->phys_addr = 0;
|
|
mem_desc->esas2r_data = dma_alloc_coherent(&a->pcid->dev,
|
|
(size_t)mem_desc->
|
|
esas2r_param,
|
|
(dma_addr_t *)&mem_desc->
|
|
phys_addr,
|
|
GFP_KERNEL);
|
|
|
|
if (mem_desc->esas2r_data == NULL) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"failed to allocate %lu bytes of consistent memory!",
|
|
(long
|
|
unsigned
|
|
int)mem_desc->esas2r_param);
|
|
return false;
|
|
}
|
|
|
|
mem_desc->virt_addr = PTR_ALIGN(mem_desc->esas2r_data, align);
|
|
mem_desc->phys_addr = ALIGN(mem_desc->phys_addr, align);
|
|
memset(mem_desc->virt_addr, 0, mem_desc->size);
|
|
return true;
|
|
}
|
|
|
|
static void esas2r_initmem_free(struct esas2r_adapter *a,
|
|
struct esas2r_mem_desc *mem_desc)
|
|
{
|
|
if (mem_desc->virt_addr == NULL)
|
|
return;
|
|
|
|
/*
|
|
* Careful! phys_addr and virt_addr may have been adjusted from the
|
|
* original allocation in order to return the desired alignment. That
|
|
* means we have to use the original address (in esas2r_data) and size
|
|
* (esas2r_param) and calculate the original physical address based on
|
|
* the difference between the requested and actual allocation size.
|
|
*/
|
|
if (mem_desc->phys_addr) {
|
|
int unalign = ((u8 *)mem_desc->virt_addr) -
|
|
((u8 *)mem_desc->esas2r_data);
|
|
|
|
dma_free_coherent(&a->pcid->dev,
|
|
(size_t)mem_desc->esas2r_param,
|
|
mem_desc->esas2r_data,
|
|
(dma_addr_t)(mem_desc->phys_addr - unalign));
|
|
} else {
|
|
kfree(mem_desc->esas2r_data);
|
|
}
|
|
|
|
mem_desc->virt_addr = NULL;
|
|
}
|
|
|
|
static bool alloc_vda_req(struct esas2r_adapter *a,
|
|
struct esas2r_request *rq)
|
|
{
|
|
struct esas2r_mem_desc *memdesc = kzalloc(
|
|
sizeof(struct esas2r_mem_desc), GFP_KERNEL);
|
|
|
|
if (memdesc == NULL) {
|
|
esas2r_hdebug("could not alloc mem for vda request memdesc\n");
|
|
return false;
|
|
}
|
|
|
|
memdesc->size = sizeof(union atto_vda_req) +
|
|
ESAS2R_DATA_BUF_LEN;
|
|
|
|
if (!esas2r_initmem_alloc(a, memdesc, 256)) {
|
|
esas2r_hdebug("could not alloc mem for vda request\n");
|
|
kfree(memdesc);
|
|
return false;
|
|
}
|
|
|
|
a->num_vrqs++;
|
|
list_add(&memdesc->next_desc, &a->vrq_mds_head);
|
|
|
|
rq->vrq_md = memdesc;
|
|
rq->vrq = (union atto_vda_req *)memdesc->virt_addr;
|
|
rq->vrq->scsi.handle = a->num_vrqs;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void esas2r_unmap_regions(struct esas2r_adapter *a)
|
|
{
|
|
if (a->regs)
|
|
iounmap((void __iomem *)a->regs);
|
|
|
|
a->regs = NULL;
|
|
|
|
pci_release_region(a->pcid, 2);
|
|
|
|
if (a->data_window)
|
|
iounmap((void __iomem *)a->data_window);
|
|
|
|
a->data_window = NULL;
|
|
|
|
pci_release_region(a->pcid, 0);
|
|
}
|
|
|
|
static int esas2r_map_regions(struct esas2r_adapter *a)
|
|
{
|
|
int error;
|
|
|
|
a->regs = NULL;
|
|
a->data_window = NULL;
|
|
|
|
error = pci_request_region(a->pcid, 2, a->name);
|
|
if (error != 0) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"pci_request_region(2) failed, error %d",
|
|
error);
|
|
|
|
return error;
|
|
}
|
|
|
|
a->regs = (void __force *)ioremap(pci_resource_start(a->pcid, 2),
|
|
pci_resource_len(a->pcid, 2));
|
|
if (a->regs == NULL) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"ioremap failed for regs mem region\n");
|
|
pci_release_region(a->pcid, 2);
|
|
return -EFAULT;
|
|
}
|
|
|
|
error = pci_request_region(a->pcid, 0, a->name);
|
|
if (error != 0) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"pci_request_region(2) failed, error %d",
|
|
error);
|
|
esas2r_unmap_regions(a);
|
|
return error;
|
|
}
|
|
|
|
a->data_window = (void __force *)ioremap(pci_resource_start(a->pcid,
|
|
0),
|
|
pci_resource_len(a->pcid, 0));
|
|
if (a->data_window == NULL) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"ioremap failed for data_window mem region\n");
|
|
esas2r_unmap_regions(a);
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void esas2r_setup_interrupts(struct esas2r_adapter *a, int intr_mode)
|
|
{
|
|
int i;
|
|
|
|
/* Set up interrupt mode based on the requested value */
|
|
switch (intr_mode) {
|
|
case INTR_MODE_LEGACY:
|
|
use_legacy_interrupts:
|
|
a->intr_mode = INTR_MODE_LEGACY;
|
|
break;
|
|
|
|
case INTR_MODE_MSI:
|
|
i = pci_enable_msi(a->pcid);
|
|
if (i != 0) {
|
|
esas2r_log(ESAS2R_LOG_WARN,
|
|
"failed to enable MSI for adapter %d, "
|
|
"falling back to legacy interrupts "
|
|
"(err=%d)", a->index,
|
|
i);
|
|
goto use_legacy_interrupts;
|
|
}
|
|
a->intr_mode = INTR_MODE_MSI;
|
|
set_bit(AF2_MSI_ENABLED, &a->flags2);
|
|
break;
|
|
|
|
|
|
default:
|
|
esas2r_log(ESAS2R_LOG_WARN,
|
|
"unknown interrupt_mode %d requested, "
|
|
"falling back to legacy interrupt",
|
|
interrupt_mode);
|
|
goto use_legacy_interrupts;
|
|
}
|
|
}
|
|
|
|
static void esas2r_claim_interrupts(struct esas2r_adapter *a)
|
|
{
|
|
unsigned long flags = 0;
|
|
|
|
if (a->intr_mode == INTR_MODE_LEGACY)
|
|
flags |= IRQF_SHARED;
|
|
|
|
esas2r_log(ESAS2R_LOG_INFO,
|
|
"esas2r_claim_interrupts irq=%d (%p, %s, %lx)",
|
|
a->pcid->irq, a, a->name, flags);
|
|
|
|
if (request_irq(a->pcid->irq,
|
|
(a->intr_mode ==
|
|
INTR_MODE_LEGACY) ? esas2r_interrupt :
|
|
esas2r_msi_interrupt,
|
|
flags,
|
|
a->name,
|
|
a)) {
|
|
esas2r_log(ESAS2R_LOG_CRIT, "unable to request IRQ %02X",
|
|
a->pcid->irq);
|
|
return;
|
|
}
|
|
|
|
set_bit(AF2_IRQ_CLAIMED, &a->flags2);
|
|
esas2r_log(ESAS2R_LOG_INFO,
|
|
"claimed IRQ %d flags: 0x%lx",
|
|
a->pcid->irq, flags);
|
|
}
|
|
|
|
int esas2r_init_adapter(struct Scsi_Host *host, struct pci_dev *pcid,
|
|
int index)
|
|
{
|
|
struct esas2r_adapter *a;
|
|
u64 bus_addr = 0;
|
|
int i;
|
|
void *next_uncached;
|
|
struct esas2r_request *first_request, *last_request;
|
|
bool dma64 = false;
|
|
|
|
if (index >= MAX_ADAPTERS) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"tried to init invalid adapter index %u!",
|
|
index);
|
|
return 0;
|
|
}
|
|
|
|
if (esas2r_adapters[index]) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"tried to init existing adapter index %u!",
|
|
index);
|
|
return 0;
|
|
}
|
|
|
|
a = (struct esas2r_adapter *)host->hostdata;
|
|
memset(a, 0, sizeof(struct esas2r_adapter));
|
|
a->pcid = pcid;
|
|
a->host = host;
|
|
|
|
if (sizeof(dma_addr_t) > 4 &&
|
|
dma_get_required_mask(&pcid->dev) > DMA_BIT_MASK(32) &&
|
|
!dma_set_mask_and_coherent(&pcid->dev, DMA_BIT_MASK(64)))
|
|
dma64 = true;
|
|
|
|
if (!dma64 && dma_set_mask_and_coherent(&pcid->dev, DMA_BIT_MASK(32))) {
|
|
esas2r_log(ESAS2R_LOG_CRIT, "failed to set DMA mask");
|
|
esas2r_kill_adapter(index);
|
|
return 0;
|
|
}
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO, &pcid->dev,
|
|
"%s-bit PCI addressing enabled\n", dma64 ? "64" : "32");
|
|
|
|
esas2r_adapters[index] = a;
|
|
sprintf(a->name, ESAS2R_DRVR_NAME "_%02d", index);
|
|
esas2r_debug("new adapter %p, name %s", a, a->name);
|
|
spin_lock_init(&a->request_lock);
|
|
spin_lock_init(&a->fw_event_lock);
|
|
mutex_init(&a->fm_api_mutex);
|
|
mutex_init(&a->fs_api_mutex);
|
|
sema_init(&a->nvram_semaphore, 1);
|
|
|
|
esas2r_fw_event_off(a);
|
|
snprintf(a->fw_event_q_name, ESAS2R_KOBJ_NAME_LEN, "esas2r/%d",
|
|
a->index);
|
|
a->fw_event_q = create_singlethread_workqueue(a->fw_event_q_name);
|
|
|
|
init_waitqueue_head(&a->buffered_ioctl_waiter);
|
|
init_waitqueue_head(&a->nvram_waiter);
|
|
init_waitqueue_head(&a->fm_api_waiter);
|
|
init_waitqueue_head(&a->fs_api_waiter);
|
|
init_waitqueue_head(&a->vda_waiter);
|
|
|
|
INIT_LIST_HEAD(&a->general_req.req_list);
|
|
INIT_LIST_HEAD(&a->active_list);
|
|
INIT_LIST_HEAD(&a->defer_list);
|
|
INIT_LIST_HEAD(&a->free_sg_list_head);
|
|
INIT_LIST_HEAD(&a->avail_request);
|
|
INIT_LIST_HEAD(&a->vrq_mds_head);
|
|
INIT_LIST_HEAD(&a->fw_event_list);
|
|
|
|
first_request = (struct esas2r_request *)((u8 *)(a + 1));
|
|
|
|
for (last_request = first_request, i = 1; i < num_requests;
|
|
last_request++, i++) {
|
|
INIT_LIST_HEAD(&last_request->req_list);
|
|
list_add_tail(&last_request->comp_list, &a->avail_request);
|
|
if (!alloc_vda_req(a, last_request)) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"failed to allocate a VDA request!");
|
|
esas2r_kill_adapter(index);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
esas2r_debug("requests: %p to %p (%d, %d)", first_request,
|
|
last_request,
|
|
sizeof(*first_request),
|
|
num_requests);
|
|
|
|
if (esas2r_map_regions(a) != 0) {
|
|
esas2r_log(ESAS2R_LOG_CRIT, "could not map PCI regions!");
|
|
esas2r_kill_adapter(index);
|
|
return 0;
|
|
}
|
|
|
|
a->index = index;
|
|
|
|
/* interrupts will be disabled until we are done with init */
|
|
atomic_inc(&a->dis_ints_cnt);
|
|
atomic_inc(&a->disable_cnt);
|
|
set_bit(AF_CHPRST_PENDING, &a->flags);
|
|
set_bit(AF_DISC_PENDING, &a->flags);
|
|
set_bit(AF_FIRST_INIT, &a->flags);
|
|
set_bit(AF_LEGACY_SGE_MODE, &a->flags);
|
|
|
|
a->init_msg = ESAS2R_INIT_MSG_START;
|
|
a->max_vdareq_size = 128;
|
|
a->build_sgl = esas2r_build_sg_list_sge;
|
|
|
|
esas2r_setup_interrupts(a, interrupt_mode);
|
|
|
|
a->uncached_size = esas2r_get_uncached_size(a);
|
|
a->uncached = dma_alloc_coherent(&pcid->dev,
|
|
(size_t)a->uncached_size,
|
|
(dma_addr_t *)&bus_addr,
|
|
GFP_KERNEL);
|
|
if (a->uncached == NULL) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"failed to allocate %d bytes of consistent memory!",
|
|
a->uncached_size);
|
|
esas2r_kill_adapter(index);
|
|
return 0;
|
|
}
|
|
|
|
a->uncached_phys = bus_addr;
|
|
|
|
esas2r_debug("%d bytes uncached memory allocated @ %p (%x:%x)",
|
|
a->uncached_size,
|
|
a->uncached,
|
|
upper_32_bits(bus_addr),
|
|
lower_32_bits(bus_addr));
|
|
memset(a->uncached, 0, a->uncached_size);
|
|
next_uncached = a->uncached;
|
|
|
|
if (!esas2r_init_adapter_struct(a,
|
|
&next_uncached)) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"failed to initialize adapter structure (2)!");
|
|
esas2r_kill_adapter(index);
|
|
return 0;
|
|
}
|
|
|
|
tasklet_init(&a->tasklet,
|
|
esas2r_adapter_tasklet,
|
|
(unsigned long)a);
|
|
|
|
/*
|
|
* Disable chip interrupts to prevent spurious interrupts
|
|
* until we claim the IRQ.
|
|
*/
|
|
esas2r_disable_chip_interrupts(a);
|
|
esas2r_check_adapter(a);
|
|
|
|
if (!esas2r_init_adapter_hw(a, true)) {
|
|
esas2r_log(ESAS2R_LOG_CRIT, "failed to initialize hardware!");
|
|
} else {
|
|
esas2r_debug("esas2r_init_adapter ok");
|
|
}
|
|
|
|
esas2r_claim_interrupts(a);
|
|
|
|
if (test_bit(AF2_IRQ_CLAIMED, &a->flags2))
|
|
esas2r_enable_chip_interrupts(a);
|
|
|
|
set_bit(AF2_INIT_DONE, &a->flags2);
|
|
if (!test_bit(AF_DEGRADED_MODE, &a->flags))
|
|
esas2r_kickoff_timer(a);
|
|
esas2r_debug("esas2r_init_adapter done for %p (%d)",
|
|
a, a->disable_cnt);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void esas2r_adapter_power_down(struct esas2r_adapter *a,
|
|
int power_management)
|
|
{
|
|
struct esas2r_mem_desc *memdesc, *next;
|
|
|
|
if ((test_bit(AF2_INIT_DONE, &a->flags2))
|
|
&& (!test_bit(AF_DEGRADED_MODE, &a->flags))) {
|
|
if (!power_management) {
|
|
del_timer_sync(&a->timer);
|
|
tasklet_kill(&a->tasklet);
|
|
}
|
|
esas2r_power_down(a);
|
|
|
|
/*
|
|
* There are versions of firmware that do not handle the sync
|
|
* cache command correctly. Stall here to ensure that the
|
|
* cache is lazily flushed.
|
|
*/
|
|
mdelay(500);
|
|
esas2r_debug("chip halted");
|
|
}
|
|
|
|
/* Remove sysfs binary files */
|
|
if (a->sysfs_fw_created) {
|
|
sysfs_remove_bin_file(&a->host->shost_dev.kobj, &bin_attr_fw);
|
|
a->sysfs_fw_created = 0;
|
|
}
|
|
|
|
if (a->sysfs_fs_created) {
|
|
sysfs_remove_bin_file(&a->host->shost_dev.kobj, &bin_attr_fs);
|
|
a->sysfs_fs_created = 0;
|
|
}
|
|
|
|
if (a->sysfs_vda_created) {
|
|
sysfs_remove_bin_file(&a->host->shost_dev.kobj, &bin_attr_vda);
|
|
a->sysfs_vda_created = 0;
|
|
}
|
|
|
|
if (a->sysfs_hw_created) {
|
|
sysfs_remove_bin_file(&a->host->shost_dev.kobj, &bin_attr_hw);
|
|
a->sysfs_hw_created = 0;
|
|
}
|
|
|
|
if (a->sysfs_live_nvram_created) {
|
|
sysfs_remove_bin_file(&a->host->shost_dev.kobj,
|
|
&bin_attr_live_nvram);
|
|
a->sysfs_live_nvram_created = 0;
|
|
}
|
|
|
|
if (a->sysfs_default_nvram_created) {
|
|
sysfs_remove_bin_file(&a->host->shost_dev.kobj,
|
|
&bin_attr_default_nvram);
|
|
a->sysfs_default_nvram_created = 0;
|
|
}
|
|
|
|
/* Clean up interrupts */
|
|
if (test_bit(AF2_IRQ_CLAIMED, &a->flags2)) {
|
|
esas2r_log_dev(ESAS2R_LOG_INFO,
|
|
&(a->pcid->dev),
|
|
"free_irq(%d) called", a->pcid->irq);
|
|
|
|
free_irq(a->pcid->irq, a);
|
|
esas2r_debug("IRQ released");
|
|
clear_bit(AF2_IRQ_CLAIMED, &a->flags2);
|
|
}
|
|
|
|
if (test_bit(AF2_MSI_ENABLED, &a->flags2)) {
|
|
pci_disable_msi(a->pcid);
|
|
clear_bit(AF2_MSI_ENABLED, &a->flags2);
|
|
esas2r_debug("MSI disabled");
|
|
}
|
|
|
|
if (a->inbound_list_md.virt_addr)
|
|
esas2r_initmem_free(a, &a->inbound_list_md);
|
|
|
|
if (a->outbound_list_md.virt_addr)
|
|
esas2r_initmem_free(a, &a->outbound_list_md);
|
|
|
|
list_for_each_entry_safe(memdesc, next, &a->free_sg_list_head,
|
|
next_desc) {
|
|
esas2r_initmem_free(a, memdesc);
|
|
}
|
|
|
|
/* Following frees everything allocated via alloc_vda_req */
|
|
list_for_each_entry_safe(memdesc, next, &a->vrq_mds_head, next_desc) {
|
|
esas2r_initmem_free(a, memdesc);
|
|
list_del(&memdesc->next_desc);
|
|
kfree(memdesc);
|
|
}
|
|
|
|
kfree(a->first_ae_req);
|
|
a->first_ae_req = NULL;
|
|
|
|
kfree(a->sg_list_mds);
|
|
a->sg_list_mds = NULL;
|
|
|
|
kfree(a->req_table);
|
|
a->req_table = NULL;
|
|
|
|
if (a->regs) {
|
|
esas2r_unmap_regions(a);
|
|
a->regs = NULL;
|
|
a->data_window = NULL;
|
|
esas2r_debug("regions unmapped");
|
|
}
|
|
}
|
|
|
|
/* Release/free allocated resources for specified adapters. */
|
|
void esas2r_kill_adapter(int i)
|
|
{
|
|
struct esas2r_adapter *a = esas2r_adapters[i];
|
|
|
|
if (a) {
|
|
unsigned long flags;
|
|
struct workqueue_struct *wq;
|
|
esas2r_debug("killing adapter %p [%d] ", a, i);
|
|
esas2r_fw_event_off(a);
|
|
esas2r_adapter_power_down(a, 0);
|
|
if (esas2r_buffered_ioctl &&
|
|
(a->pcid == esas2r_buffered_ioctl_pcid)) {
|
|
dma_free_coherent(&a->pcid->dev,
|
|
(size_t)esas2r_buffered_ioctl_size,
|
|
esas2r_buffered_ioctl,
|
|
esas2r_buffered_ioctl_addr);
|
|
esas2r_buffered_ioctl = NULL;
|
|
}
|
|
|
|
if (a->vda_buffer) {
|
|
dma_free_coherent(&a->pcid->dev,
|
|
(size_t)VDA_MAX_BUFFER_SIZE,
|
|
a->vda_buffer,
|
|
(dma_addr_t)a->ppvda_buffer);
|
|
a->vda_buffer = NULL;
|
|
}
|
|
if (a->fs_api_buffer) {
|
|
dma_free_coherent(&a->pcid->dev,
|
|
(size_t)a->fs_api_buffer_size,
|
|
a->fs_api_buffer,
|
|
(dma_addr_t)a->ppfs_api_buffer);
|
|
a->fs_api_buffer = NULL;
|
|
}
|
|
|
|
kfree(a->local_atto_ioctl);
|
|
a->local_atto_ioctl = NULL;
|
|
|
|
spin_lock_irqsave(&a->fw_event_lock, flags);
|
|
wq = a->fw_event_q;
|
|
a->fw_event_q = NULL;
|
|
spin_unlock_irqrestore(&a->fw_event_lock, flags);
|
|
if (wq)
|
|
destroy_workqueue(wq);
|
|
|
|
if (a->uncached) {
|
|
dma_free_coherent(&a->pcid->dev,
|
|
(size_t)a->uncached_size,
|
|
a->uncached,
|
|
(dma_addr_t)a->uncached_phys);
|
|
a->uncached = NULL;
|
|
esas2r_debug("uncached area freed");
|
|
}
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO,
|
|
&(a->pcid->dev),
|
|
"pci_disable_device() called. msix_enabled: %d "
|
|
"msi_enabled: %d irq: %d pin: %d",
|
|
a->pcid->msix_enabled,
|
|
a->pcid->msi_enabled,
|
|
a->pcid->irq,
|
|
a->pcid->pin);
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO,
|
|
&(a->pcid->dev),
|
|
"before pci_disable_device() enable_cnt: %d",
|
|
a->pcid->enable_cnt.counter);
|
|
|
|
pci_disable_device(a->pcid);
|
|
esas2r_log_dev(ESAS2R_LOG_INFO,
|
|
&(a->pcid->dev),
|
|
"after pci_disable_device() enable_cnt: %d",
|
|
a->pcid->enable_cnt.counter);
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO,
|
|
&(a->pcid->dev),
|
|
"pci_set_drv_data(%p, NULL) called",
|
|
a->pcid);
|
|
|
|
pci_set_drvdata(a->pcid, NULL);
|
|
esas2r_adapters[i] = NULL;
|
|
|
|
if (test_bit(AF2_INIT_DONE, &a->flags2)) {
|
|
clear_bit(AF2_INIT_DONE, &a->flags2);
|
|
|
|
set_bit(AF_DEGRADED_MODE, &a->flags);
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO,
|
|
&(a->host->shost_gendev),
|
|
"scsi_remove_host() called");
|
|
|
|
scsi_remove_host(a->host);
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO,
|
|
&(a->host->shost_gendev),
|
|
"scsi_host_put() called");
|
|
|
|
scsi_host_put(a->host);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int __maybe_unused esas2r_suspend(struct device *dev)
|
|
{
|
|
struct Scsi_Host *host = dev_get_drvdata(dev);
|
|
struct esas2r_adapter *a = (struct esas2r_adapter *)host->hostdata;
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO, dev, "suspending adapter()");
|
|
if (!a)
|
|
return -ENODEV;
|
|
|
|
esas2r_adapter_power_down(a, 1);
|
|
esas2r_log_dev(ESAS2R_LOG_INFO, dev, "esas2r_suspend(): 0");
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused esas2r_resume(struct device *dev)
|
|
{
|
|
struct Scsi_Host *host = dev_get_drvdata(dev);
|
|
struct esas2r_adapter *a = (struct esas2r_adapter *)host->hostdata;
|
|
int rez = 0;
|
|
|
|
esas2r_log_dev(ESAS2R_LOG_INFO, dev, "resuming adapter()");
|
|
|
|
if (!a) {
|
|
rez = -ENODEV;
|
|
goto error_exit;
|
|
}
|
|
|
|
if (esas2r_map_regions(a) != 0) {
|
|
esas2r_log(ESAS2R_LOG_CRIT, "could not re-map PCI regions!");
|
|
rez = -ENOMEM;
|
|
goto error_exit;
|
|
}
|
|
|
|
/* Set up interupt mode */
|
|
esas2r_setup_interrupts(a, a->intr_mode);
|
|
|
|
/*
|
|
* Disable chip interrupts to prevent spurious interrupts until we
|
|
* claim the IRQ.
|
|
*/
|
|
esas2r_disable_chip_interrupts(a);
|
|
if (!esas2r_power_up(a, true)) {
|
|
esas2r_debug("yikes, esas2r_power_up failed");
|
|
rez = -ENOMEM;
|
|
goto error_exit;
|
|
}
|
|
|
|
esas2r_claim_interrupts(a);
|
|
|
|
if (test_bit(AF2_IRQ_CLAIMED, &a->flags2)) {
|
|
/*
|
|
* Now that system interrupt(s) are claimed, we can enable
|
|
* chip interrupts.
|
|
*/
|
|
esas2r_enable_chip_interrupts(a);
|
|
esas2r_kickoff_timer(a);
|
|
} else {
|
|
esas2r_debug("yikes, unable to claim IRQ");
|
|
esas2r_log(ESAS2R_LOG_CRIT, "could not re-claim IRQ!");
|
|
rez = -ENOMEM;
|
|
goto error_exit;
|
|
}
|
|
|
|
error_exit:
|
|
esas2r_log_dev(ESAS2R_LOG_CRIT, dev, "esas2r_resume(): %d",
|
|
rez);
|
|
return rez;
|
|
}
|
|
|
|
SIMPLE_DEV_PM_OPS(esas2r_pm_ops, esas2r_suspend, esas2r_resume);
|
|
|
|
bool esas2r_set_degraded_mode(struct esas2r_adapter *a, char *error_str)
|
|
{
|
|
set_bit(AF_DEGRADED_MODE, &a->flags);
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"setting adapter to degraded mode: %s\n", error_str);
|
|
return false;
|
|
}
|
|
|
|
u32 esas2r_get_uncached_size(struct esas2r_adapter *a)
|
|
{
|
|
return sizeof(struct esas2r_sas_nvram)
|
|
+ ALIGN(ESAS2R_DISC_BUF_LEN, 8)
|
|
+ ALIGN(sizeof(u32), 8) /* outbound list copy pointer */
|
|
+ 8
|
|
+ (num_sg_lists * (u16)sgl_page_size)
|
|
+ ALIGN((num_requests + num_ae_requests + 1 +
|
|
ESAS2R_LIST_EXTRA) *
|
|
sizeof(struct esas2r_inbound_list_source_entry),
|
|
8)
|
|
+ ALIGN((num_requests + num_ae_requests + 1 +
|
|
ESAS2R_LIST_EXTRA) *
|
|
sizeof(struct atto_vda_ob_rsp), 8)
|
|
+ 256; /* VDA request and buffer align */
|
|
}
|
|
|
|
static void esas2r_init_pci_cfg_space(struct esas2r_adapter *a)
|
|
{
|
|
if (pci_is_pcie(a->pcid)) {
|
|
u16 devcontrol;
|
|
|
|
pcie_capability_read_word(a->pcid, PCI_EXP_DEVCTL, &devcontrol);
|
|
|
|
if ((devcontrol & PCI_EXP_DEVCTL_READRQ) >
|
|
PCI_EXP_DEVCTL_READRQ_512B) {
|
|
esas2r_log(ESAS2R_LOG_INFO,
|
|
"max read request size > 512B");
|
|
|
|
devcontrol &= ~PCI_EXP_DEVCTL_READRQ;
|
|
devcontrol |= PCI_EXP_DEVCTL_READRQ_512B;
|
|
pcie_capability_write_word(a->pcid, PCI_EXP_DEVCTL,
|
|
devcontrol);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine the organization of the uncached data area and
|
|
* finish initializing the adapter structure
|
|
*/
|
|
bool esas2r_init_adapter_struct(struct esas2r_adapter *a,
|
|
void **uncached_area)
|
|
{
|
|
u32 i;
|
|
u8 *high;
|
|
struct esas2r_inbound_list_source_entry *element;
|
|
struct esas2r_request *rq;
|
|
struct esas2r_mem_desc *sgl;
|
|
|
|
spin_lock_init(&a->sg_list_lock);
|
|
spin_lock_init(&a->mem_lock);
|
|
spin_lock_init(&a->queue_lock);
|
|
|
|
a->targetdb_end = &a->targetdb[ESAS2R_MAX_TARGETS];
|
|
|
|
if (!alloc_vda_req(a, &a->general_req)) {
|
|
esas2r_hdebug(
|
|
"failed to allocate a VDA request for the general req!");
|
|
return false;
|
|
}
|
|
|
|
/* allocate requests for asynchronous events */
|
|
a->first_ae_req =
|
|
kcalloc(num_ae_requests, sizeof(struct esas2r_request),
|
|
GFP_KERNEL);
|
|
|
|
if (a->first_ae_req == NULL) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"failed to allocate memory for asynchronous events");
|
|
return false;
|
|
}
|
|
|
|
/* allocate the S/G list memory descriptors */
|
|
a->sg_list_mds = kcalloc(num_sg_lists, sizeof(struct esas2r_mem_desc),
|
|
GFP_KERNEL);
|
|
|
|
if (a->sg_list_mds == NULL) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"failed to allocate memory for s/g list descriptors");
|
|
return false;
|
|
}
|
|
|
|
/* allocate the request table */
|
|
a->req_table =
|
|
kcalloc(num_requests + num_ae_requests + 1,
|
|
sizeof(struct esas2r_request *),
|
|
GFP_KERNEL);
|
|
|
|
if (a->req_table == NULL) {
|
|
esas2r_log(ESAS2R_LOG_CRIT,
|
|
"failed to allocate memory for the request table");
|
|
return false;
|
|
}
|
|
|
|
/* initialize PCI configuration space */
|
|
esas2r_init_pci_cfg_space(a);
|
|
|
|
/*
|
|
* the thunder_stream boards all have a serial flash part that has a
|
|
* different base address on the AHB bus.
|
|
*/
|
|
if ((a->pcid->subsystem_vendor == ATTO_VENDOR_ID)
|
|
&& (a->pcid->subsystem_device & ATTO_SSDID_TBT))
|
|
a->flags2 |= AF2_THUNDERBOLT;
|
|
|
|
if (test_bit(AF2_THUNDERBOLT, &a->flags2))
|
|
a->flags2 |= AF2_SERIAL_FLASH;
|
|
|
|
if (a->pcid->subsystem_device == ATTO_TLSH_1068)
|
|
a->flags2 |= AF2_THUNDERLINK;
|
|
|
|
/* Uncached Area */
|
|
high = (u8 *)*uncached_area;
|
|
|
|
/* initialize the scatter/gather table pages */
|
|
|
|
for (i = 0, sgl = a->sg_list_mds; i < num_sg_lists; i++, sgl++) {
|
|
sgl->size = sgl_page_size;
|
|
|
|
list_add_tail(&sgl->next_desc, &a->free_sg_list_head);
|
|
|
|
if (!esas2r_initmem_alloc(a, sgl, ESAS2R_SGL_ALIGN)) {
|
|
/* Allow the driver to load if the minimum count met. */
|
|
if (i < NUM_SGL_MIN)
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* compute the size of the lists */
|
|
a->list_size = num_requests + ESAS2R_LIST_EXTRA;
|
|
|
|
/* allocate the inbound list */
|
|
a->inbound_list_md.size = a->list_size *
|
|
sizeof(struct
|
|
esas2r_inbound_list_source_entry);
|
|
|
|
if (!esas2r_initmem_alloc(a, &a->inbound_list_md, ESAS2R_LIST_ALIGN)) {
|
|
esas2r_hdebug("failed to allocate IB list");
|
|
return false;
|
|
}
|
|
|
|
/* allocate the outbound list */
|
|
a->outbound_list_md.size = a->list_size *
|
|
sizeof(struct atto_vda_ob_rsp);
|
|
|
|
if (!esas2r_initmem_alloc(a, &a->outbound_list_md,
|
|
ESAS2R_LIST_ALIGN)) {
|
|
esas2r_hdebug("failed to allocate IB list");
|
|
return false;
|
|
}
|
|
|
|
/* allocate the NVRAM structure */
|
|
a->nvram = (struct esas2r_sas_nvram *)high;
|
|
high += sizeof(struct esas2r_sas_nvram);
|
|
|
|
/* allocate the discovery buffer */
|
|
a->disc_buffer = high;
|
|
high += ESAS2R_DISC_BUF_LEN;
|
|
high = PTR_ALIGN(high, 8);
|
|
|
|
/* allocate the outbound list copy pointer */
|
|
a->outbound_copy = (u32 volatile *)high;
|
|
high += sizeof(u32);
|
|
|
|
if (!test_bit(AF_NVR_VALID, &a->flags))
|
|
esas2r_nvram_set_defaults(a);
|
|
|
|
/* update the caller's uncached memory area pointer */
|
|
*uncached_area = (void *)high;
|
|
|
|
/* initialize the allocated memory */
|
|
if (test_bit(AF_FIRST_INIT, &a->flags)) {
|
|
esas2r_targ_db_initialize(a);
|
|
|
|
/* prime parts of the inbound list */
|
|
element =
|
|
(struct esas2r_inbound_list_source_entry *)a->
|
|
inbound_list_md.
|
|
virt_addr;
|
|
|
|
for (i = 0; i < a->list_size; i++) {
|
|
element->address = 0;
|
|
element->reserved = 0;
|
|
element->length = cpu_to_le32(HWILSE_INTERFACE_F0
|
|
| (sizeof(union
|
|
atto_vda_req)
|
|
/
|
|
sizeof(u32)));
|
|
element++;
|
|
}
|
|
|
|
/* init the AE requests */
|
|
for (rq = a->first_ae_req, i = 0; i < num_ae_requests; rq++,
|
|
i++) {
|
|
INIT_LIST_HEAD(&rq->req_list);
|
|
if (!alloc_vda_req(a, rq)) {
|
|
esas2r_hdebug(
|
|
"failed to allocate a VDA request!");
|
|
return false;
|
|
}
|
|
|
|
esas2r_rq_init_request(rq, a);
|
|
|
|
/* override the completion function */
|
|
rq->comp_cb = esas2r_ae_complete;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* This code will verify that the chip is operational. */
|
|
bool esas2r_check_adapter(struct esas2r_adapter *a)
|
|
{
|
|
u32 starttime;
|
|
u32 doorbell;
|
|
u64 ppaddr;
|
|
u32 dw;
|
|
|
|
/*
|
|
* if the chip reset detected flag is set, we can bypass a bunch of
|
|
* stuff.
|
|
*/
|
|
if (test_bit(AF_CHPRST_DETECTED, &a->flags))
|
|
goto skip_chip_reset;
|
|
|
|
/*
|
|
* BEFORE WE DO ANYTHING, disable the chip interrupts! the boot driver
|
|
* may have left them enabled or we may be recovering from a fault.
|
|
*/
|
|
esas2r_write_register_dword(a, MU_INT_MASK_OUT, ESAS2R_INT_DIS_MASK);
|
|
esas2r_flush_register_dword(a, MU_INT_MASK_OUT);
|
|
|
|
/*
|
|
* wait for the firmware to become ready by forcing an interrupt and
|
|
* waiting for a response.
|
|
*/
|
|
starttime = jiffies_to_msecs(jiffies);
|
|
|
|
while (true) {
|
|
esas2r_force_interrupt(a);
|
|
doorbell = esas2r_read_register_dword(a, MU_DOORBELL_OUT);
|
|
if (doorbell == 0xFFFFFFFF) {
|
|
/*
|
|
* Give the firmware up to two seconds to enable
|
|
* register access after a reset.
|
|
*/
|
|
if ((jiffies_to_msecs(jiffies) - starttime) > 2000)
|
|
return esas2r_set_degraded_mode(a,
|
|
"unable to access registers");
|
|
} else if (doorbell & DRBL_FORCE_INT) {
|
|
u32 ver = (doorbell & DRBL_FW_VER_MSK);
|
|
|
|
/*
|
|
* This driver supports version 0 and version 1 of
|
|
* the API
|
|
*/
|
|
esas2r_write_register_dword(a, MU_DOORBELL_OUT,
|
|
doorbell);
|
|
|
|
if (ver == DRBL_FW_VER_0) {
|
|
set_bit(AF_LEGACY_SGE_MODE, &a->flags);
|
|
|
|
a->max_vdareq_size = 128;
|
|
a->build_sgl = esas2r_build_sg_list_sge;
|
|
} else if (ver == DRBL_FW_VER_1) {
|
|
clear_bit(AF_LEGACY_SGE_MODE, &a->flags);
|
|
|
|
a->max_vdareq_size = 1024;
|
|
a->build_sgl = esas2r_build_sg_list_prd;
|
|
} else {
|
|
return esas2r_set_degraded_mode(a,
|
|
"unknown firmware version");
|
|
}
|
|
break;
|
|
}
|
|
|
|
schedule_timeout_interruptible(msecs_to_jiffies(100));
|
|
|
|
if ((jiffies_to_msecs(jiffies) - starttime) > 180000) {
|
|
esas2r_hdebug("FW ready TMO");
|
|
esas2r_bugon();
|
|
|
|
return esas2r_set_degraded_mode(a,
|
|
"firmware start has timed out");
|
|
}
|
|
}
|
|
|
|
/* purge any asynchronous events since we will repost them later */
|
|
esas2r_write_register_dword(a, MU_DOORBELL_IN, DRBL_MSG_IFC_DOWN);
|
|
starttime = jiffies_to_msecs(jiffies);
|
|
|
|
while (true) {
|
|
doorbell = esas2r_read_register_dword(a, MU_DOORBELL_OUT);
|
|
if (doorbell & DRBL_MSG_IFC_DOWN) {
|
|
esas2r_write_register_dword(a, MU_DOORBELL_OUT,
|
|
doorbell);
|
|
break;
|
|
}
|
|
|
|
schedule_timeout_interruptible(msecs_to_jiffies(50));
|
|
|
|
if ((jiffies_to_msecs(jiffies) - starttime) > 3000) {
|
|
esas2r_hdebug("timeout waiting for interface down");
|
|
break;
|
|
}
|
|
}
|
|
skip_chip_reset:
|
|
/*
|
|
* first things first, before we go changing any of these registers
|
|
* disable the communication lists.
|
|
*/
|
|
dw = esas2r_read_register_dword(a, MU_IN_LIST_CONFIG);
|
|
dw &= ~MU_ILC_ENABLE;
|
|
esas2r_write_register_dword(a, MU_IN_LIST_CONFIG, dw);
|
|
dw = esas2r_read_register_dword(a, MU_OUT_LIST_CONFIG);
|
|
dw &= ~MU_OLC_ENABLE;
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_CONFIG, dw);
|
|
|
|
/* configure the communication list addresses */
|
|
ppaddr = a->inbound_list_md.phys_addr;
|
|
esas2r_write_register_dword(a, MU_IN_LIST_ADDR_LO,
|
|
lower_32_bits(ppaddr));
|
|
esas2r_write_register_dword(a, MU_IN_LIST_ADDR_HI,
|
|
upper_32_bits(ppaddr));
|
|
ppaddr = a->outbound_list_md.phys_addr;
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_ADDR_LO,
|
|
lower_32_bits(ppaddr));
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_ADDR_HI,
|
|
upper_32_bits(ppaddr));
|
|
ppaddr = a->uncached_phys +
|
|
((u8 *)a->outbound_copy - a->uncached);
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_COPY_PTR_LO,
|
|
lower_32_bits(ppaddr));
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_COPY_PTR_HI,
|
|
upper_32_bits(ppaddr));
|
|
|
|
/* reset the read and write pointers */
|
|
*a->outbound_copy =
|
|
a->last_write =
|
|
a->last_read = a->list_size - 1;
|
|
set_bit(AF_COMM_LIST_TOGGLE, &a->flags);
|
|
esas2r_write_register_dword(a, MU_IN_LIST_WRITE, MU_ILW_TOGGLE |
|
|
a->last_write);
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_COPY, MU_OLC_TOGGLE |
|
|
a->last_write);
|
|
esas2r_write_register_dword(a, MU_IN_LIST_READ, MU_ILR_TOGGLE |
|
|
a->last_write);
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_WRITE,
|
|
MU_OLW_TOGGLE | a->last_write);
|
|
|
|
/* configure the interface select fields */
|
|
dw = esas2r_read_register_dword(a, MU_IN_LIST_IFC_CONFIG);
|
|
dw &= ~(MU_ILIC_LIST | MU_ILIC_DEST);
|
|
esas2r_write_register_dword(a, MU_IN_LIST_IFC_CONFIG,
|
|
(dw | MU_ILIC_LIST_F0 | MU_ILIC_DEST_DDR));
|
|
dw = esas2r_read_register_dword(a, MU_OUT_LIST_IFC_CONFIG);
|
|
dw &= ~(MU_OLIC_LIST | MU_OLIC_SOURCE);
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_IFC_CONFIG,
|
|
(dw | MU_OLIC_LIST_F0 |
|
|
MU_OLIC_SOURCE_DDR));
|
|
|
|
/* finish configuring the communication lists */
|
|
dw = esas2r_read_register_dword(a, MU_IN_LIST_CONFIG);
|
|
dw &= ~(MU_ILC_ENTRY_MASK | MU_ILC_NUMBER_MASK);
|
|
dw |= MU_ILC_ENTRY_4_DW | MU_ILC_DYNAMIC_SRC
|
|
| (a->list_size << MU_ILC_NUMBER_SHIFT);
|
|
esas2r_write_register_dword(a, MU_IN_LIST_CONFIG, dw);
|
|
dw = esas2r_read_register_dword(a, MU_OUT_LIST_CONFIG);
|
|
dw &= ~(MU_OLC_ENTRY_MASK | MU_OLC_NUMBER_MASK);
|
|
dw |= MU_OLC_ENTRY_4_DW | (a->list_size << MU_OLC_NUMBER_SHIFT);
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_CONFIG, dw);
|
|
|
|
/*
|
|
* notify the firmware that we're done setting up the communication
|
|
* list registers. wait here until the firmware is done configuring
|
|
* its lists. it will signal that it is done by enabling the lists.
|
|
*/
|
|
esas2r_write_register_dword(a, MU_DOORBELL_IN, DRBL_MSG_IFC_INIT);
|
|
starttime = jiffies_to_msecs(jiffies);
|
|
|
|
while (true) {
|
|
doorbell = esas2r_read_register_dword(a, MU_DOORBELL_OUT);
|
|
if (doorbell & DRBL_MSG_IFC_INIT) {
|
|
esas2r_write_register_dword(a, MU_DOORBELL_OUT,
|
|
doorbell);
|
|
break;
|
|
}
|
|
|
|
schedule_timeout_interruptible(msecs_to_jiffies(100));
|
|
|
|
if ((jiffies_to_msecs(jiffies) - starttime) > 3000) {
|
|
esas2r_hdebug(
|
|
"timeout waiting for communication list init");
|
|
esas2r_bugon();
|
|
return esas2r_set_degraded_mode(a,
|
|
"timeout waiting for communication list init");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* flag whether the firmware supports the power down doorbell. we
|
|
* determine this by reading the inbound doorbell enable mask.
|
|
*/
|
|
doorbell = esas2r_read_register_dword(a, MU_DOORBELL_IN_ENB);
|
|
if (doorbell & DRBL_POWER_DOWN)
|
|
set_bit(AF2_VDA_POWER_DOWN, &a->flags2);
|
|
else
|
|
clear_bit(AF2_VDA_POWER_DOWN, &a->flags2);
|
|
|
|
/*
|
|
* enable assertion of outbound queue and doorbell interrupts in the
|
|
* main interrupt cause register.
|
|
*/
|
|
esas2r_write_register_dword(a, MU_OUT_LIST_INT_MASK, MU_OLIS_MASK);
|
|
esas2r_write_register_dword(a, MU_DOORBELL_OUT_ENB, DRBL_ENB_MASK);
|
|
return true;
|
|
}
|
|
|
|
/* Process the initialization message just completed and format the next one. */
|
|
static bool esas2r_format_init_msg(struct esas2r_adapter *a,
|
|
struct esas2r_request *rq)
|
|
{
|
|
u32 msg = a->init_msg;
|
|
struct atto_vda_cfg_init *ci;
|
|
|
|
a->init_msg = 0;
|
|
|
|
switch (msg) {
|
|
case ESAS2R_INIT_MSG_START:
|
|
case ESAS2R_INIT_MSG_REINIT:
|
|
{
|
|
esas2r_hdebug("CFG init");
|
|
esas2r_build_cfg_req(a,
|
|
rq,
|
|
VDA_CFG_INIT,
|
|
0,
|
|
NULL);
|
|
ci = (struct atto_vda_cfg_init *)&rq->vrq->cfg.data.init;
|
|
ci->sgl_page_size = cpu_to_le32(sgl_page_size);
|
|
/* firmware interface overflows in y2106 */
|
|
ci->epoch_time = cpu_to_le32(ktime_get_real_seconds());
|
|
rq->flags |= RF_FAILURE_OK;
|
|
a->init_msg = ESAS2R_INIT_MSG_INIT;
|
|
break;
|
|
}
|
|
|
|
case ESAS2R_INIT_MSG_INIT:
|
|
if (rq->req_stat == RS_SUCCESS) {
|
|
u32 major;
|
|
u32 minor;
|
|
u16 fw_release;
|
|
|
|
a->fw_version = le16_to_cpu(
|
|
rq->func_rsp.cfg_rsp.vda_version);
|
|
a->fw_build = rq->func_rsp.cfg_rsp.fw_build;
|
|
fw_release = le16_to_cpu(
|
|
rq->func_rsp.cfg_rsp.fw_release);
|
|
major = LOBYTE(fw_release);
|
|
minor = HIBYTE(fw_release);
|
|
a->fw_version += (major << 16) + (minor << 24);
|
|
} else {
|
|
esas2r_hdebug("FAILED");
|
|
}
|
|
|
|
/*
|
|
* the 2.71 and earlier releases of R6xx firmware did not error
|
|
* unsupported config requests correctly.
|
|
*/
|
|
|
|
if ((test_bit(AF2_THUNDERBOLT, &a->flags2))
|
|
|| (be32_to_cpu(a->fw_version) > 0x00524702)) {
|
|
esas2r_hdebug("CFG get init");
|
|
esas2r_build_cfg_req(a,
|
|
rq,
|
|
VDA_CFG_GET_INIT2,
|
|
sizeof(struct atto_vda_cfg_init),
|
|
NULL);
|
|
|
|
rq->vrq->cfg.sg_list_offset = offsetof(
|
|
struct atto_vda_cfg_req,
|
|
data.sge);
|
|
rq->vrq->cfg.data.prde.ctl_len =
|
|
cpu_to_le32(sizeof(struct atto_vda_cfg_init));
|
|
rq->vrq->cfg.data.prde.address = cpu_to_le64(
|
|
rq->vrq_md->phys_addr +
|
|
sizeof(union atto_vda_req));
|
|
rq->flags |= RF_FAILURE_OK;
|
|
a->init_msg = ESAS2R_INIT_MSG_GET_INIT;
|
|
break;
|
|
}
|
|
fallthrough;
|
|
|
|
case ESAS2R_INIT_MSG_GET_INIT:
|
|
if (msg == ESAS2R_INIT_MSG_GET_INIT) {
|
|
ci = (struct atto_vda_cfg_init *)rq->data_buf;
|
|
if (rq->req_stat == RS_SUCCESS) {
|
|
a->num_targets_backend =
|
|
le32_to_cpu(ci->num_targets_backend);
|
|
a->ioctl_tunnel =
|
|
le32_to_cpu(ci->ioctl_tunnel);
|
|
} else {
|
|
esas2r_hdebug("FAILED");
|
|
}
|
|
}
|
|
fallthrough;
|
|
|
|
default:
|
|
rq->req_stat = RS_SUCCESS;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Perform initialization messages via the request queue. Messages are
|
|
* performed with interrupts disabled.
|
|
*/
|
|
bool esas2r_init_msgs(struct esas2r_adapter *a)
|
|
{
|
|
bool success = true;
|
|
struct esas2r_request *rq = &a->general_req;
|
|
|
|
esas2r_rq_init_request(rq, a);
|
|
rq->comp_cb = esas2r_dummy_complete;
|
|
|
|
if (a->init_msg == 0)
|
|
a->init_msg = ESAS2R_INIT_MSG_REINIT;
|
|
|
|
while (a->init_msg) {
|
|
if (esas2r_format_init_msg(a, rq)) {
|
|
unsigned long flags;
|
|
while (true) {
|
|
spin_lock_irqsave(&a->queue_lock, flags);
|
|
esas2r_start_vda_request(a, rq);
|
|
spin_unlock_irqrestore(&a->queue_lock, flags);
|
|
esas2r_wait_request(a, rq);
|
|
if (rq->req_stat != RS_PENDING)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rq->req_stat == RS_SUCCESS
|
|
|| ((rq->flags & RF_FAILURE_OK)
|
|
&& rq->req_stat != RS_TIMEOUT))
|
|
continue;
|
|
|
|
esas2r_log(ESAS2R_LOG_CRIT, "init message %x failed (%x, %x)",
|
|
a->init_msg, rq->req_stat, rq->flags);
|
|
a->init_msg = ESAS2R_INIT_MSG_START;
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
esas2r_rq_destroy_request(rq, a);
|
|
return success;
|
|
}
|
|
|
|
/* Initialize the adapter chip */
|
|
bool esas2r_init_adapter_hw(struct esas2r_adapter *a, bool init_poll)
|
|
{
|
|
bool rslt = false;
|
|
struct esas2r_request *rq;
|
|
u32 i;
|
|
|
|
if (test_bit(AF_DEGRADED_MODE, &a->flags))
|
|
goto exit;
|
|
|
|
if (!test_bit(AF_NVR_VALID, &a->flags)) {
|
|
if (!esas2r_nvram_read_direct(a))
|
|
esas2r_log(ESAS2R_LOG_WARN,
|
|
"invalid/missing NVRAM parameters");
|
|
}
|
|
|
|
if (!esas2r_init_msgs(a)) {
|
|
esas2r_set_degraded_mode(a, "init messages failed");
|
|
goto exit;
|
|
}
|
|
|
|
/* The firmware is ready. */
|
|
clear_bit(AF_DEGRADED_MODE, &a->flags);
|
|
clear_bit(AF_CHPRST_PENDING, &a->flags);
|
|
|
|
/* Post all the async event requests */
|
|
for (i = 0, rq = a->first_ae_req; i < num_ae_requests; i++, rq++)
|
|
esas2r_start_ae_request(a, rq);
|
|
|
|
if (!a->flash_rev[0])
|
|
esas2r_read_flash_rev(a);
|
|
|
|
if (!a->image_type[0])
|
|
esas2r_read_image_type(a);
|
|
|
|
if (a->fw_version == 0)
|
|
a->fw_rev[0] = 0;
|
|
else
|
|
sprintf(a->fw_rev, "%1d.%02d",
|
|
(int)LOBYTE(HIWORD(a->fw_version)),
|
|
(int)HIBYTE(HIWORD(a->fw_version)));
|
|
|
|
esas2r_hdebug("firmware revision: %s", a->fw_rev);
|
|
|
|
if (test_bit(AF_CHPRST_DETECTED, &a->flags)
|
|
&& (test_bit(AF_FIRST_INIT, &a->flags))) {
|
|
esas2r_enable_chip_interrupts(a);
|
|
return true;
|
|
}
|
|
|
|
/* initialize discovery */
|
|
esas2r_disc_initialize(a);
|
|
|
|
/*
|
|
* wait for the device wait time to expire here if requested. this is
|
|
* usually requested during initial driver load and possibly when
|
|
* resuming from a low power state. deferred device waiting will use
|
|
* interrupts. chip reset recovery always defers device waiting to
|
|
* avoid being in a TASKLET too long.
|
|
*/
|
|
if (init_poll) {
|
|
u32 currtime = a->disc_start_time;
|
|
u32 nexttick = 100;
|
|
u32 deltatime;
|
|
|
|
/*
|
|
* Block Tasklets from getting scheduled and indicate this is
|
|
* polled discovery.
|
|
*/
|
|
set_bit(AF_TASKLET_SCHEDULED, &a->flags);
|
|
set_bit(AF_DISC_POLLED, &a->flags);
|
|
|
|
/*
|
|
* Temporarily bring the disable count to zero to enable
|
|
* deferred processing. Note that the count is already zero
|
|
* after the first initialization.
|
|
*/
|
|
if (test_bit(AF_FIRST_INIT, &a->flags))
|
|
atomic_dec(&a->disable_cnt);
|
|
|
|
while (test_bit(AF_DISC_PENDING, &a->flags)) {
|
|
schedule_timeout_interruptible(msecs_to_jiffies(100));
|
|
|
|
/*
|
|
* Determine the need for a timer tick based on the
|
|
* delta time between this and the last iteration of
|
|
* this loop. We don't use the absolute time because
|
|
* then we would have to worry about when nexttick
|
|
* wraps and currtime hasn't yet.
|
|
*/
|
|
deltatime = jiffies_to_msecs(jiffies) - currtime;
|
|
currtime += deltatime;
|
|
|
|
/*
|
|
* Process any waiting discovery as long as the chip is
|
|
* up. If a chip reset happens during initial polling,
|
|
* we have to make sure the timer tick processes the
|
|
* doorbell indicating the firmware is ready.
|
|
*/
|
|
if (!test_bit(AF_CHPRST_PENDING, &a->flags))
|
|
esas2r_disc_check_for_work(a);
|
|
|
|
/* Simulate a timer tick. */
|
|
if (nexttick <= deltatime) {
|
|
|
|
/* Time for a timer tick */
|
|
nexttick += 100;
|
|
esas2r_timer_tick(a);
|
|
}
|
|
|
|
if (nexttick > deltatime)
|
|
nexttick -= deltatime;
|
|
|
|
/* Do any deferred processing */
|
|
if (esas2r_is_tasklet_pending(a))
|
|
esas2r_do_tasklet_tasks(a);
|
|
|
|
}
|
|
|
|
if (test_bit(AF_FIRST_INIT, &a->flags))
|
|
atomic_inc(&a->disable_cnt);
|
|
|
|
clear_bit(AF_DISC_POLLED, &a->flags);
|
|
clear_bit(AF_TASKLET_SCHEDULED, &a->flags);
|
|
}
|
|
|
|
|
|
esas2r_targ_db_report_changes(a);
|
|
|
|
/*
|
|
* For cases where (a) the initialization messages processing may
|
|
* handle an interrupt for a port event and a discovery is waiting, but
|
|
* we are not waiting for devices, or (b) the device wait time has been
|
|
* exhausted but there is still discovery pending, start any leftover
|
|
* discovery in interrupt driven mode.
|
|
*/
|
|
esas2r_disc_start_waiting(a);
|
|
|
|
/* Enable chip interrupts */
|
|
a->int_mask = ESAS2R_INT_STS_MASK;
|
|
esas2r_enable_chip_interrupts(a);
|
|
esas2r_enable_heartbeat(a);
|
|
rslt = true;
|
|
|
|
exit:
|
|
/*
|
|
* Regardless of whether initialization was successful, certain things
|
|
* need to get done before we exit.
|
|
*/
|
|
|
|
if (test_bit(AF_CHPRST_DETECTED, &a->flags) &&
|
|
test_bit(AF_FIRST_INIT, &a->flags)) {
|
|
/*
|
|
* Reinitialization was performed during the first
|
|
* initialization. Only clear the chip reset flag so the
|
|
* original device polling is not cancelled.
|
|
*/
|
|
if (!rslt)
|
|
clear_bit(AF_CHPRST_PENDING, &a->flags);
|
|
} else {
|
|
/* First initialization or a subsequent re-init is complete. */
|
|
if (!rslt) {
|
|
clear_bit(AF_CHPRST_PENDING, &a->flags);
|
|
clear_bit(AF_DISC_PENDING, &a->flags);
|
|
}
|
|
|
|
|
|
/* Enable deferred processing after the first initialization. */
|
|
if (test_bit(AF_FIRST_INIT, &a->flags)) {
|
|
clear_bit(AF_FIRST_INIT, &a->flags);
|
|
|
|
if (atomic_dec_return(&a->disable_cnt) == 0)
|
|
esas2r_do_deferred_processes(a);
|
|
}
|
|
}
|
|
|
|
return rslt;
|
|
}
|
|
|
|
void esas2r_reset_adapter(struct esas2r_adapter *a)
|
|
{
|
|
set_bit(AF_OS_RESET, &a->flags);
|
|
esas2r_local_reset_adapter(a);
|
|
esas2r_schedule_tasklet(a);
|
|
}
|
|
|
|
void esas2r_reset_chip(struct esas2r_adapter *a)
|
|
{
|
|
if (!esas2r_is_adapter_present(a))
|
|
return;
|
|
|
|
/*
|
|
* Before we reset the chip, save off the VDA core dump. The VDA core
|
|
* dump is located in the upper 512KB of the onchip SRAM. Make sure
|
|
* to not overwrite a previous crash that was saved.
|
|
*/
|
|
if (test_bit(AF2_COREDUMP_AVAIL, &a->flags2) &&
|
|
!test_bit(AF2_COREDUMP_SAVED, &a->flags2)) {
|
|
esas2r_read_mem_block(a,
|
|
a->fw_coredump_buff,
|
|
MW_DATA_ADDR_SRAM + 0x80000,
|
|
ESAS2R_FWCOREDUMP_SZ);
|
|
|
|
set_bit(AF2_COREDUMP_SAVED, &a->flags2);
|
|
}
|
|
|
|
clear_bit(AF2_COREDUMP_AVAIL, &a->flags2);
|
|
|
|
/* Reset the chip */
|
|
if (a->pcid->revision == MVR_FREY_B2)
|
|
esas2r_write_register_dword(a, MU_CTL_STATUS_IN_B2,
|
|
MU_CTL_IN_FULL_RST2);
|
|
else
|
|
esas2r_write_register_dword(a, MU_CTL_STATUS_IN,
|
|
MU_CTL_IN_FULL_RST);
|
|
|
|
|
|
/* Stall a little while to let the reset condition clear */
|
|
mdelay(10);
|
|
}
|
|
|
|
static void esas2r_power_down_notify_firmware(struct esas2r_adapter *a)
|
|
{
|
|
u32 starttime;
|
|
u32 doorbell;
|
|
|
|
esas2r_write_register_dword(a, MU_DOORBELL_IN, DRBL_POWER_DOWN);
|
|
starttime = jiffies_to_msecs(jiffies);
|
|
|
|
while (true) {
|
|
doorbell = esas2r_read_register_dword(a, MU_DOORBELL_OUT);
|
|
if (doorbell & DRBL_POWER_DOWN) {
|
|
esas2r_write_register_dword(a, MU_DOORBELL_OUT,
|
|
doorbell);
|
|
break;
|
|
}
|
|
|
|
schedule_timeout_interruptible(msecs_to_jiffies(100));
|
|
|
|
if ((jiffies_to_msecs(jiffies) - starttime) > 30000) {
|
|
esas2r_hdebug("Timeout waiting for power down");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Perform power management processing including managing device states, adapter
|
|
* states, interrupts, and I/O.
|
|
*/
|
|
void esas2r_power_down(struct esas2r_adapter *a)
|
|
{
|
|
set_bit(AF_POWER_MGT, &a->flags);
|
|
set_bit(AF_POWER_DOWN, &a->flags);
|
|
|
|
if (!test_bit(AF_DEGRADED_MODE, &a->flags)) {
|
|
u32 starttime;
|
|
u32 doorbell;
|
|
|
|
/*
|
|
* We are currently running OK and will be reinitializing later.
|
|
* increment the disable count to coordinate with
|
|
* esas2r_init_adapter. We don't have to do this in degraded
|
|
* mode since we never enabled interrupts in the first place.
|
|
*/
|
|
esas2r_disable_chip_interrupts(a);
|
|
esas2r_disable_heartbeat(a);
|
|
|
|
/* wait for any VDA activity to clear before continuing */
|
|
esas2r_write_register_dword(a, MU_DOORBELL_IN,
|
|
DRBL_MSG_IFC_DOWN);
|
|
starttime = jiffies_to_msecs(jiffies);
|
|
|
|
while (true) {
|
|
doorbell =
|
|
esas2r_read_register_dword(a, MU_DOORBELL_OUT);
|
|
if (doorbell & DRBL_MSG_IFC_DOWN) {
|
|
esas2r_write_register_dword(a, MU_DOORBELL_OUT,
|
|
doorbell);
|
|
break;
|
|
}
|
|
|
|
schedule_timeout_interruptible(msecs_to_jiffies(100));
|
|
|
|
if ((jiffies_to_msecs(jiffies) - starttime) > 3000) {
|
|
esas2r_hdebug(
|
|
"timeout waiting for interface down");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For versions of firmware that support it tell them the driver
|
|
* is powering down.
|
|
*/
|
|
if (test_bit(AF2_VDA_POWER_DOWN, &a->flags2))
|
|
esas2r_power_down_notify_firmware(a);
|
|
}
|
|
|
|
/* Suspend I/O processing. */
|
|
set_bit(AF_OS_RESET, &a->flags);
|
|
set_bit(AF_DISC_PENDING, &a->flags);
|
|
set_bit(AF_CHPRST_PENDING, &a->flags);
|
|
|
|
esas2r_process_adapter_reset(a);
|
|
|
|
/* Remove devices now that I/O is cleaned up. */
|
|
a->prev_dev_cnt = esas2r_targ_db_get_tgt_cnt(a);
|
|
esas2r_targ_db_remove_all(a, false);
|
|
}
|
|
|
|
/*
|
|
* Perform power management processing including managing device states, adapter
|
|
* states, interrupts, and I/O.
|
|
*/
|
|
bool esas2r_power_up(struct esas2r_adapter *a, bool init_poll)
|
|
{
|
|
bool ret;
|
|
|
|
clear_bit(AF_POWER_DOWN, &a->flags);
|
|
esas2r_init_pci_cfg_space(a);
|
|
set_bit(AF_FIRST_INIT, &a->flags);
|
|
atomic_inc(&a->disable_cnt);
|
|
|
|
/* reinitialize the adapter */
|
|
ret = esas2r_check_adapter(a);
|
|
if (!esas2r_init_adapter_hw(a, init_poll))
|
|
ret = false;
|
|
|
|
/* send the reset asynchronous event */
|
|
esas2r_send_reset_ae(a, true);
|
|
|
|
/* clear this flag after initialization. */
|
|
clear_bit(AF_POWER_MGT, &a->flags);
|
|
return ret;
|
|
}
|
|
|
|
bool esas2r_is_adapter_present(struct esas2r_adapter *a)
|
|
{
|
|
if (test_bit(AF_NOT_PRESENT, &a->flags))
|
|
return false;
|
|
|
|
if (esas2r_read_register_dword(a, MU_DOORBELL_OUT) == 0xFFFFFFFF) {
|
|
set_bit(AF_NOT_PRESENT, &a->flags);
|
|
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const char *esas2r_get_model_name(struct esas2r_adapter *a)
|
|
{
|
|
switch (a->pcid->subsystem_device) {
|
|
case ATTO_ESAS_R680:
|
|
return "ATTO ExpressSAS R680";
|
|
|
|
case ATTO_ESAS_R608:
|
|
return "ATTO ExpressSAS R608";
|
|
|
|
case ATTO_ESAS_R60F:
|
|
return "ATTO ExpressSAS R60F";
|
|
|
|
case ATTO_ESAS_R6F0:
|
|
return "ATTO ExpressSAS R6F0";
|
|
|
|
case ATTO_ESAS_R644:
|
|
return "ATTO ExpressSAS R644";
|
|
|
|
case ATTO_ESAS_R648:
|
|
return "ATTO ExpressSAS R648";
|
|
|
|
case ATTO_TSSC_3808:
|
|
return "ATTO ThunderStream SC 3808D";
|
|
|
|
case ATTO_TSSC_3808E:
|
|
return "ATTO ThunderStream SC 3808E";
|
|
|
|
case ATTO_TLSH_1068:
|
|
return "ATTO ThunderLink SH 1068";
|
|
}
|
|
|
|
return "ATTO SAS Controller";
|
|
}
|
|
|
|
const char *esas2r_get_model_name_short(struct esas2r_adapter *a)
|
|
{
|
|
switch (a->pcid->subsystem_device) {
|
|
case ATTO_ESAS_R680:
|
|
return "R680";
|
|
|
|
case ATTO_ESAS_R608:
|
|
return "R608";
|
|
|
|
case ATTO_ESAS_R60F:
|
|
return "R60F";
|
|
|
|
case ATTO_ESAS_R6F0:
|
|
return "R6F0";
|
|
|
|
case ATTO_ESAS_R644:
|
|
return "R644";
|
|
|
|
case ATTO_ESAS_R648:
|
|
return "R648";
|
|
|
|
case ATTO_TSSC_3808:
|
|
return "SC 3808D";
|
|
|
|
case ATTO_TSSC_3808E:
|
|
return "SC 3808E";
|
|
|
|
case ATTO_TLSH_1068:
|
|
return "SH 1068";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|