2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-29 23:53:55 +08:00
linux-next/drivers/scsi/mvumi.c
Ming Lei 3c1a30df6d scsi: mvumi: use sg helper to iterate over scatterlist
Unlike the legacy I/O path, scsi-mq preallocates a large array to hold
the scatterlist for each request. This static allocation can consume
substantial amounts of memory on modern controllers which support a
large number of concurrently outstanding requests.

To facilitate a switch to a smaller static allocation combined with a
dynamic allocation for requests that need it, we need to make sure all
SCSI drivers handle chained scatterlists correctly.

Convert remaining drivers that directly dereference the scatterlist
array to using the iterator functions.

[mkp: clarified commit message and folded in build fix reported by zeroday]

Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Ewan D. Milne <emilne@redhat.com>
Signed-off-by: Ming Lei <ming.lei@redhat.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
2019-06-20 15:20:43 -04:00

2680 lines
70 KiB
C

/*
* Marvell UMI driver
*
* Copyright 2011 Marvell. <jyli@marvell.com>
*
* This file is licensed under GPLv2.
*
* 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; version 2 of the
* License.
*
* 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/blkdev.h>
#include <linux/io.h>
#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_transport.h>
#include <scsi/scsi_eh.h>
#include <linux/uaccess.h>
#include <linux/kthread.h>
#include "mvumi.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jyli@marvell.com");
MODULE_DESCRIPTION("Marvell UMI Driver");
static const struct pci_device_id mvumi_pci_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL_EXT, PCI_DEVICE_ID_MARVELL_MV9143) },
{ PCI_DEVICE(PCI_VENDOR_ID_MARVELL_EXT, PCI_DEVICE_ID_MARVELL_MV9580) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, mvumi_pci_table);
static void tag_init(struct mvumi_tag *st, unsigned short size)
{
unsigned short i;
BUG_ON(size != st->size);
st->top = size;
for (i = 0; i < size; i++)
st->stack[i] = size - 1 - i;
}
static unsigned short tag_get_one(struct mvumi_hba *mhba, struct mvumi_tag *st)
{
BUG_ON(st->top <= 0);
return st->stack[--st->top];
}
static void tag_release_one(struct mvumi_hba *mhba, struct mvumi_tag *st,
unsigned short tag)
{
BUG_ON(st->top >= st->size);
st->stack[st->top++] = tag;
}
static bool tag_is_empty(struct mvumi_tag *st)
{
if (st->top == 0)
return 1;
else
return 0;
}
static void mvumi_unmap_pci_addr(struct pci_dev *dev, void **addr_array)
{
int i;
for (i = 0; i < MAX_BASE_ADDRESS; i++)
if ((pci_resource_flags(dev, i) & IORESOURCE_MEM) &&
addr_array[i])
pci_iounmap(dev, addr_array[i]);
}
static int mvumi_map_pci_addr(struct pci_dev *dev, void **addr_array)
{
int i;
for (i = 0; i < MAX_BASE_ADDRESS; i++) {
if (pci_resource_flags(dev, i) & IORESOURCE_MEM) {
addr_array[i] = pci_iomap(dev, i, 0);
if (!addr_array[i]) {
dev_err(&dev->dev, "failed to map Bar[%d]\n",
i);
mvumi_unmap_pci_addr(dev, addr_array);
return -ENOMEM;
}
} else
addr_array[i] = NULL;
dev_dbg(&dev->dev, "Bar %d : %p.\n", i, addr_array[i]);
}
return 0;
}
static struct mvumi_res *mvumi_alloc_mem_resource(struct mvumi_hba *mhba,
enum resource_type type, unsigned int size)
{
struct mvumi_res *res = kzalloc(sizeof(*res), GFP_ATOMIC);
if (!res) {
dev_err(&mhba->pdev->dev,
"Failed to allocate memory for resource manager.\n");
return NULL;
}
switch (type) {
case RESOURCE_CACHED_MEMORY:
res->virt_addr = kzalloc(size, GFP_ATOMIC);
if (!res->virt_addr) {
dev_err(&mhba->pdev->dev,
"unable to allocate memory,size = %d.\n", size);
kfree(res);
return NULL;
}
break;
case RESOURCE_UNCACHED_MEMORY:
size = round_up(size, 8);
res->virt_addr = dma_alloc_coherent(&mhba->pdev->dev, size,
&res->bus_addr,
GFP_KERNEL);
if (!res->virt_addr) {
dev_err(&mhba->pdev->dev,
"unable to allocate consistent mem,"
"size = %d.\n", size);
kfree(res);
return NULL;
}
break;
default:
dev_err(&mhba->pdev->dev, "unknown resource type %d.\n", type);
kfree(res);
return NULL;
}
res->type = type;
res->size = size;
INIT_LIST_HEAD(&res->entry);
list_add_tail(&res->entry, &mhba->res_list);
return res;
}
static void mvumi_release_mem_resource(struct mvumi_hba *mhba)
{
struct mvumi_res *res, *tmp;
list_for_each_entry_safe(res, tmp, &mhba->res_list, entry) {
switch (res->type) {
case RESOURCE_UNCACHED_MEMORY:
dma_free_coherent(&mhba->pdev->dev, res->size,
res->virt_addr, res->bus_addr);
break;
case RESOURCE_CACHED_MEMORY:
kfree(res->virt_addr);
break;
default:
dev_err(&mhba->pdev->dev,
"unknown resource type %d\n", res->type);
break;
}
list_del(&res->entry);
kfree(res);
}
mhba->fw_flag &= ~MVUMI_FW_ALLOC;
}
/**
* mvumi_make_sgl - Prepares SGL
* @mhba: Adapter soft state
* @scmd: SCSI command from the mid-layer
* @sgl_p: SGL to be filled in
* @sg_count return the number of SG elements
*
* If successful, this function returns 0. otherwise, it returns -1.
*/
static int mvumi_make_sgl(struct mvumi_hba *mhba, struct scsi_cmnd *scmd,
void *sgl_p, unsigned char *sg_count)
{
struct scatterlist *sg;
struct mvumi_sgl *m_sg = (struct mvumi_sgl *) sgl_p;
unsigned int i;
unsigned int sgnum = scsi_sg_count(scmd);
dma_addr_t busaddr;
*sg_count = dma_map_sg(&mhba->pdev->dev, scsi_sglist(scmd), sgnum,
scmd->sc_data_direction);
if (*sg_count > mhba->max_sge) {
dev_err(&mhba->pdev->dev,
"sg count[0x%x] is bigger than max sg[0x%x].\n",
*sg_count, mhba->max_sge);
dma_unmap_sg(&mhba->pdev->dev, scsi_sglist(scmd), sgnum,
scmd->sc_data_direction);
return -1;
}
scsi_for_each_sg(scmd, sg, *sg_count, i) {
busaddr = sg_dma_address(sg);
m_sg->baseaddr_l = cpu_to_le32(lower_32_bits(busaddr));
m_sg->baseaddr_h = cpu_to_le32(upper_32_bits(busaddr));
m_sg->flags = 0;
sgd_setsz(mhba, m_sg, cpu_to_le32(sg_dma_len(sg)));
if ((i + 1) == *sg_count)
m_sg->flags |= 1U << mhba->eot_flag;
sgd_inc(mhba, m_sg);
}
return 0;
}
static int mvumi_internal_cmd_sgl(struct mvumi_hba *mhba, struct mvumi_cmd *cmd,
unsigned int size)
{
struct mvumi_sgl *m_sg;
void *virt_addr;
dma_addr_t phy_addr;
if (size == 0)
return 0;
virt_addr = dma_alloc_coherent(&mhba->pdev->dev, size, &phy_addr,
GFP_KERNEL);
if (!virt_addr)
return -1;
m_sg = (struct mvumi_sgl *) &cmd->frame->payload[0];
cmd->frame->sg_counts = 1;
cmd->data_buf = virt_addr;
m_sg->baseaddr_l = cpu_to_le32(lower_32_bits(phy_addr));
m_sg->baseaddr_h = cpu_to_le32(upper_32_bits(phy_addr));
m_sg->flags = 1U << mhba->eot_flag;
sgd_setsz(mhba, m_sg, cpu_to_le32(size));
return 0;
}
static struct mvumi_cmd *mvumi_create_internal_cmd(struct mvumi_hba *mhba,
unsigned int buf_size)
{
struct mvumi_cmd *cmd;
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
if (!cmd) {
dev_err(&mhba->pdev->dev, "failed to create a internal cmd\n");
return NULL;
}
INIT_LIST_HEAD(&cmd->queue_pointer);
cmd->frame = dma_alloc_coherent(&mhba->pdev->dev, mhba->ib_max_size,
&cmd->frame_phys, GFP_KERNEL);
if (!cmd->frame) {
dev_err(&mhba->pdev->dev, "failed to allocate memory for FW"
" frame,size = %d.\n", mhba->ib_max_size);
kfree(cmd);
return NULL;
}
if (buf_size) {
if (mvumi_internal_cmd_sgl(mhba, cmd, buf_size)) {
dev_err(&mhba->pdev->dev, "failed to allocate memory"
" for internal frame\n");
dma_free_coherent(&mhba->pdev->dev, mhba->ib_max_size,
cmd->frame, cmd->frame_phys);
kfree(cmd);
return NULL;
}
} else
cmd->frame->sg_counts = 0;
return cmd;
}
static void mvumi_delete_internal_cmd(struct mvumi_hba *mhba,
struct mvumi_cmd *cmd)
{
struct mvumi_sgl *m_sg;
unsigned int size;
dma_addr_t phy_addr;
if (cmd && cmd->frame) {
if (cmd->frame->sg_counts) {
m_sg = (struct mvumi_sgl *) &cmd->frame->payload[0];
sgd_getsz(mhba, m_sg, size);
phy_addr = (dma_addr_t) m_sg->baseaddr_l |
(dma_addr_t) ((m_sg->baseaddr_h << 16) << 16);
dma_free_coherent(&mhba->pdev->dev, size, cmd->data_buf,
phy_addr);
}
dma_free_coherent(&mhba->pdev->dev, mhba->ib_max_size,
cmd->frame, cmd->frame_phys);
kfree(cmd);
}
}
/**
* mvumi_get_cmd - Get a command from the free pool
* @mhba: Adapter soft state
*
* Returns a free command from the pool
*/
static struct mvumi_cmd *mvumi_get_cmd(struct mvumi_hba *mhba)
{
struct mvumi_cmd *cmd = NULL;
if (likely(!list_empty(&mhba->cmd_pool))) {
cmd = list_entry((&mhba->cmd_pool)->next,
struct mvumi_cmd, queue_pointer);
list_del_init(&cmd->queue_pointer);
} else
dev_warn(&mhba->pdev->dev, "command pool is empty!\n");
return cmd;
}
/**
* mvumi_return_cmd - Return a cmd to free command pool
* @mhba: Adapter soft state
* @cmd: Command packet to be returned to free command pool
*/
static inline void mvumi_return_cmd(struct mvumi_hba *mhba,
struct mvumi_cmd *cmd)
{
cmd->scmd = NULL;
list_add_tail(&cmd->queue_pointer, &mhba->cmd_pool);
}
/**
* mvumi_free_cmds - Free all the cmds in the free cmd pool
* @mhba: Adapter soft state
*/
static void mvumi_free_cmds(struct mvumi_hba *mhba)
{
struct mvumi_cmd *cmd;
while (!list_empty(&mhba->cmd_pool)) {
cmd = list_first_entry(&mhba->cmd_pool, struct mvumi_cmd,
queue_pointer);
list_del(&cmd->queue_pointer);
if (!(mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC))
kfree(cmd->frame);
kfree(cmd);
}
}
/**
* mvumi_alloc_cmds - Allocates the command packets
* @mhba: Adapter soft state
*
*/
static int mvumi_alloc_cmds(struct mvumi_hba *mhba)
{
int i;
struct mvumi_cmd *cmd;
for (i = 0; i < mhba->max_io; i++) {
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
if (!cmd)
goto err_exit;
INIT_LIST_HEAD(&cmd->queue_pointer);
list_add_tail(&cmd->queue_pointer, &mhba->cmd_pool);
if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC) {
cmd->frame = mhba->ib_frame + i * mhba->ib_max_size;
cmd->frame_phys = mhba->ib_frame_phys
+ i * mhba->ib_max_size;
} else
cmd->frame = kzalloc(mhba->ib_max_size, GFP_KERNEL);
if (!cmd->frame)
goto err_exit;
}
return 0;
err_exit:
dev_err(&mhba->pdev->dev,
"failed to allocate memory for cmd[0x%x].\n", i);
while (!list_empty(&mhba->cmd_pool)) {
cmd = list_first_entry(&mhba->cmd_pool, struct mvumi_cmd,
queue_pointer);
list_del(&cmd->queue_pointer);
if (!(mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC))
kfree(cmd->frame);
kfree(cmd);
}
return -ENOMEM;
}
static unsigned int mvumi_check_ib_list_9143(struct mvumi_hba *mhba)
{
unsigned int ib_rp_reg;
struct mvumi_hw_regs *regs = mhba->regs;
ib_rp_reg = ioread32(mhba->regs->inb_read_pointer);
if (unlikely(((ib_rp_reg & regs->cl_slot_num_mask) ==
(mhba->ib_cur_slot & regs->cl_slot_num_mask)) &&
((ib_rp_reg & regs->cl_pointer_toggle)
!= (mhba->ib_cur_slot & regs->cl_pointer_toggle)))) {
dev_warn(&mhba->pdev->dev, "no free slot to use.\n");
return 0;
}
if (atomic_read(&mhba->fw_outstanding) >= mhba->max_io) {
dev_warn(&mhba->pdev->dev, "firmware io overflow.\n");
return 0;
} else {
return mhba->max_io - atomic_read(&mhba->fw_outstanding);
}
}
static unsigned int mvumi_check_ib_list_9580(struct mvumi_hba *mhba)
{
unsigned int count;
if (atomic_read(&mhba->fw_outstanding) >= (mhba->max_io - 1))
return 0;
count = ioread32(mhba->ib_shadow);
if (count == 0xffff)
return 0;
return count;
}
static void mvumi_get_ib_list_entry(struct mvumi_hba *mhba, void **ib_entry)
{
unsigned int cur_ib_entry;
cur_ib_entry = mhba->ib_cur_slot & mhba->regs->cl_slot_num_mask;
cur_ib_entry++;
if (cur_ib_entry >= mhba->list_num_io) {
cur_ib_entry -= mhba->list_num_io;
mhba->ib_cur_slot ^= mhba->regs->cl_pointer_toggle;
}
mhba->ib_cur_slot &= ~mhba->regs->cl_slot_num_mask;
mhba->ib_cur_slot |= (cur_ib_entry & mhba->regs->cl_slot_num_mask);
if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC) {
*ib_entry = mhba->ib_list + cur_ib_entry *
sizeof(struct mvumi_dyn_list_entry);
} else {
*ib_entry = mhba->ib_list + cur_ib_entry * mhba->ib_max_size;
}
atomic_inc(&mhba->fw_outstanding);
}
static void mvumi_send_ib_list_entry(struct mvumi_hba *mhba)
{
iowrite32(0xffff, mhba->ib_shadow);
iowrite32(mhba->ib_cur_slot, mhba->regs->inb_write_pointer);
}
static char mvumi_check_ob_frame(struct mvumi_hba *mhba,
unsigned int cur_obf, struct mvumi_rsp_frame *p_outb_frame)
{
unsigned short tag, request_id;
udelay(1);
p_outb_frame = mhba->ob_list + cur_obf * mhba->ob_max_size;
request_id = p_outb_frame->request_id;
tag = p_outb_frame->tag;
if (tag > mhba->tag_pool.size) {
dev_err(&mhba->pdev->dev, "ob frame data error\n");
return -1;
}
if (mhba->tag_cmd[tag] == NULL) {
dev_err(&mhba->pdev->dev, "tag[0x%x] with NO command\n", tag);
return -1;
} else if (mhba->tag_cmd[tag]->request_id != request_id &&
mhba->request_id_enabled) {
dev_err(&mhba->pdev->dev, "request ID from FW:0x%x,"
"cmd request ID:0x%x\n", request_id,
mhba->tag_cmd[tag]->request_id);
return -1;
}
return 0;
}
static int mvumi_check_ob_list_9143(struct mvumi_hba *mhba,
unsigned int *cur_obf, unsigned int *assign_obf_end)
{
unsigned int ob_write, ob_write_shadow;
struct mvumi_hw_regs *regs = mhba->regs;
do {
ob_write = ioread32(regs->outb_copy_pointer);
ob_write_shadow = ioread32(mhba->ob_shadow);
} while ((ob_write & regs->cl_slot_num_mask) != ob_write_shadow);
*cur_obf = mhba->ob_cur_slot & mhba->regs->cl_slot_num_mask;
*assign_obf_end = ob_write & mhba->regs->cl_slot_num_mask;
if ((ob_write & regs->cl_pointer_toggle) !=
(mhba->ob_cur_slot & regs->cl_pointer_toggle)) {
*assign_obf_end += mhba->list_num_io;
}
return 0;
}
static int mvumi_check_ob_list_9580(struct mvumi_hba *mhba,
unsigned int *cur_obf, unsigned int *assign_obf_end)
{
unsigned int ob_write;
struct mvumi_hw_regs *regs = mhba->regs;
ob_write = ioread32(regs->outb_read_pointer);
ob_write = ioread32(regs->outb_copy_pointer);
*cur_obf = mhba->ob_cur_slot & mhba->regs->cl_slot_num_mask;
*assign_obf_end = ob_write & mhba->regs->cl_slot_num_mask;
if (*assign_obf_end < *cur_obf)
*assign_obf_end += mhba->list_num_io;
else if (*assign_obf_end == *cur_obf)
return -1;
return 0;
}
static void mvumi_receive_ob_list_entry(struct mvumi_hba *mhba)
{
unsigned int cur_obf, assign_obf_end, i;
struct mvumi_ob_data *ob_data;
struct mvumi_rsp_frame *p_outb_frame;
struct mvumi_hw_regs *regs = mhba->regs;
if (mhba->instancet->check_ob_list(mhba, &cur_obf, &assign_obf_end))
return;
for (i = (assign_obf_end - cur_obf); i != 0; i--) {
cur_obf++;
if (cur_obf >= mhba->list_num_io) {
cur_obf -= mhba->list_num_io;
mhba->ob_cur_slot ^= regs->cl_pointer_toggle;
}
p_outb_frame = mhba->ob_list + cur_obf * mhba->ob_max_size;
/* Copy pointer may point to entry in outbound list
* before entry has valid data
*/
if (unlikely(p_outb_frame->tag > mhba->tag_pool.size ||
mhba->tag_cmd[p_outb_frame->tag] == NULL ||
p_outb_frame->request_id !=
mhba->tag_cmd[p_outb_frame->tag]->request_id))
if (mvumi_check_ob_frame(mhba, cur_obf, p_outb_frame))
continue;
if (!list_empty(&mhba->ob_data_list)) {
ob_data = (struct mvumi_ob_data *)
list_first_entry(&mhba->ob_data_list,
struct mvumi_ob_data, list);
list_del_init(&ob_data->list);
} else {
ob_data = NULL;
if (cur_obf == 0) {
cur_obf = mhba->list_num_io - 1;
mhba->ob_cur_slot ^= regs->cl_pointer_toggle;
} else
cur_obf -= 1;
break;
}
memcpy(ob_data->data, p_outb_frame, mhba->ob_max_size);
p_outb_frame->tag = 0xff;
list_add_tail(&ob_data->list, &mhba->free_ob_list);
}
mhba->ob_cur_slot &= ~regs->cl_slot_num_mask;
mhba->ob_cur_slot |= (cur_obf & regs->cl_slot_num_mask);
iowrite32(mhba->ob_cur_slot, regs->outb_read_pointer);
}
static void mvumi_reset(struct mvumi_hba *mhba)
{
struct mvumi_hw_regs *regs = mhba->regs;
iowrite32(0, regs->enpointa_mask_reg);
if (ioread32(regs->arm_to_pciea_msg1) != HANDSHAKE_DONESTATE)
return;
iowrite32(DRBL_SOFT_RESET, regs->pciea_to_arm_drbl_reg);
}
static unsigned char mvumi_start(struct mvumi_hba *mhba);
static int mvumi_wait_for_outstanding(struct mvumi_hba *mhba)
{
mhba->fw_state = FW_STATE_ABORT;
mvumi_reset(mhba);
if (mvumi_start(mhba))
return FAILED;
else
return SUCCESS;
}
static int mvumi_wait_for_fw(struct mvumi_hba *mhba)
{
struct mvumi_hw_regs *regs = mhba->regs;
u32 tmp;
unsigned long before;
before = jiffies;
iowrite32(0, regs->enpointa_mask_reg);
tmp = ioread32(regs->arm_to_pciea_msg1);
while (tmp != HANDSHAKE_READYSTATE) {
iowrite32(DRBL_MU_RESET, regs->pciea_to_arm_drbl_reg);
if (time_after(jiffies, before + FW_MAX_DELAY * HZ)) {
dev_err(&mhba->pdev->dev,
"FW reset failed [0x%x].\n", tmp);
return FAILED;
}
msleep(500);
rmb();
tmp = ioread32(regs->arm_to_pciea_msg1);
}
return SUCCESS;
}
static void mvumi_backup_bar_addr(struct mvumi_hba *mhba)
{
unsigned char i;
for (i = 0; i < MAX_BASE_ADDRESS; i++) {
pci_read_config_dword(mhba->pdev, 0x10 + i * 4,
&mhba->pci_base[i]);
}
}
static void mvumi_restore_bar_addr(struct mvumi_hba *mhba)
{
unsigned char i;
for (i = 0; i < MAX_BASE_ADDRESS; i++) {
if (mhba->pci_base[i])
pci_write_config_dword(mhba->pdev, 0x10 + i * 4,
mhba->pci_base[i]);
}
}
static int mvumi_pci_set_master(struct pci_dev *pdev)
{
int ret = 0;
pci_set_master(pdev);
if (IS_DMA64) {
if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)))
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
} else
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
return ret;
}
static int mvumi_reset_host_9580(struct mvumi_hba *mhba)
{
mhba->fw_state = FW_STATE_ABORT;
iowrite32(0, mhba->regs->reset_enable);
iowrite32(0xf, mhba->regs->reset_request);
iowrite32(0x10, mhba->regs->reset_enable);
iowrite32(0x10, mhba->regs->reset_request);
msleep(100);
pci_disable_device(mhba->pdev);
if (pci_enable_device(mhba->pdev)) {
dev_err(&mhba->pdev->dev, "enable device failed\n");
return FAILED;
}
if (mvumi_pci_set_master(mhba->pdev)) {
dev_err(&mhba->pdev->dev, "set master failed\n");
return FAILED;
}
mvumi_restore_bar_addr(mhba);
if (mvumi_wait_for_fw(mhba) == FAILED)
return FAILED;
return mvumi_wait_for_outstanding(mhba);
}
static int mvumi_reset_host_9143(struct mvumi_hba *mhba)
{
return mvumi_wait_for_outstanding(mhba);
}
static int mvumi_host_reset(struct scsi_cmnd *scmd)
{
struct mvumi_hba *mhba;
mhba = (struct mvumi_hba *) scmd->device->host->hostdata;
scmd_printk(KERN_NOTICE, scmd, "RESET -%u cmd=%x retries=%x\n",
scmd->request->tag, scmd->cmnd[0], scmd->retries);
return mhba->instancet->reset_host(mhba);
}
static int mvumi_issue_blocked_cmd(struct mvumi_hba *mhba,
struct mvumi_cmd *cmd)
{
unsigned long flags;
cmd->cmd_status = REQ_STATUS_PENDING;
if (atomic_read(&cmd->sync_cmd)) {
dev_err(&mhba->pdev->dev,
"last blocked cmd not finished, sync_cmd = %d\n",
atomic_read(&cmd->sync_cmd));
BUG_ON(1);
return -1;
}
atomic_inc(&cmd->sync_cmd);
spin_lock_irqsave(mhba->shost->host_lock, flags);
mhba->instancet->fire_cmd(mhba, cmd);
spin_unlock_irqrestore(mhba->shost->host_lock, flags);
wait_event_timeout(mhba->int_cmd_wait_q,
(cmd->cmd_status != REQ_STATUS_PENDING),
MVUMI_INTERNAL_CMD_WAIT_TIME * HZ);
/* command timeout */
if (atomic_read(&cmd->sync_cmd)) {
spin_lock_irqsave(mhba->shost->host_lock, flags);
atomic_dec(&cmd->sync_cmd);
if (mhba->tag_cmd[cmd->frame->tag]) {
mhba->tag_cmd[cmd->frame->tag] = NULL;
dev_warn(&mhba->pdev->dev, "TIMEOUT:release tag [%d]\n",
cmd->frame->tag);
tag_release_one(mhba, &mhba->tag_pool, cmd->frame->tag);
}
if (!list_empty(&cmd->queue_pointer)) {
dev_warn(&mhba->pdev->dev,
"TIMEOUT:A internal command doesn't send!\n");
list_del_init(&cmd->queue_pointer);
} else
atomic_dec(&mhba->fw_outstanding);
spin_unlock_irqrestore(mhba->shost->host_lock, flags);
}
return 0;
}
static void mvumi_release_fw(struct mvumi_hba *mhba)
{
mvumi_free_cmds(mhba);
mvumi_release_mem_resource(mhba);
mvumi_unmap_pci_addr(mhba->pdev, mhba->base_addr);
dma_free_coherent(&mhba->pdev->dev, HSP_MAX_SIZE,
mhba->handshake_page, mhba->handshake_page_phys);
kfree(mhba->regs);
pci_release_regions(mhba->pdev);
}
static unsigned char mvumi_flush_cache(struct mvumi_hba *mhba)
{
struct mvumi_cmd *cmd;
struct mvumi_msg_frame *frame;
unsigned char device_id, retry = 0;
unsigned char bitcount = sizeof(unsigned char) * 8;
for (device_id = 0; device_id < mhba->max_target_id; device_id++) {
if (!(mhba->target_map[device_id / bitcount] &
(1 << (device_id % bitcount))))
continue;
get_cmd: cmd = mvumi_create_internal_cmd(mhba, 0);
if (!cmd) {
if (retry++ >= 5) {
dev_err(&mhba->pdev->dev, "failed to get memory"
" for internal flush cache cmd for "
"device %d", device_id);
retry = 0;
continue;
} else
goto get_cmd;
}
cmd->scmd = NULL;
cmd->cmd_status = REQ_STATUS_PENDING;
atomic_set(&cmd->sync_cmd, 0);
frame = cmd->frame;
frame->req_function = CL_FUN_SCSI_CMD;
frame->device_id = device_id;
frame->cmd_flag = CMD_FLAG_NON_DATA;
frame->data_transfer_length = 0;
frame->cdb_length = MAX_COMMAND_SIZE;
memset(frame->cdb, 0, MAX_COMMAND_SIZE);
frame->cdb[0] = SCSI_CMD_MARVELL_SPECIFIC;
frame->cdb[1] = CDB_CORE_MODULE;
frame->cdb[2] = CDB_CORE_SHUTDOWN;
mvumi_issue_blocked_cmd(mhba, cmd);
if (cmd->cmd_status != SAM_STAT_GOOD) {
dev_err(&mhba->pdev->dev,
"device %d flush cache failed, status=0x%x.\n",
device_id, cmd->cmd_status);
}
mvumi_delete_internal_cmd(mhba, cmd);
}
return 0;
}
static unsigned char
mvumi_calculate_checksum(struct mvumi_hs_header *p_header,
unsigned short len)
{
unsigned char *ptr;
unsigned char ret = 0, i;
ptr = (unsigned char *) p_header->frame_content;
for (i = 0; i < len; i++) {
ret ^= *ptr;
ptr++;
}
return ret;
}
static void mvumi_hs_build_page(struct mvumi_hba *mhba,
struct mvumi_hs_header *hs_header)
{
struct mvumi_hs_page2 *hs_page2;
struct mvumi_hs_page4 *hs_page4;
struct mvumi_hs_page3 *hs_page3;
u64 time;
u64 local_time;
switch (hs_header->page_code) {
case HS_PAGE_HOST_INFO:
hs_page2 = (struct mvumi_hs_page2 *) hs_header;
hs_header->frame_length = sizeof(*hs_page2) - 4;
memset(hs_header->frame_content, 0, hs_header->frame_length);
hs_page2->host_type = 3; /* 3 mean linux*/
if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC)
hs_page2->host_cap = 0x08;/* host dynamic source mode */
hs_page2->host_ver.ver_major = VER_MAJOR;
hs_page2->host_ver.ver_minor = VER_MINOR;
hs_page2->host_ver.ver_oem = VER_OEM;
hs_page2->host_ver.ver_build = VER_BUILD;
hs_page2->system_io_bus = 0;
hs_page2->slot_number = 0;
hs_page2->intr_level = 0;
hs_page2->intr_vector = 0;
time = ktime_get_real_seconds();
local_time = (time - (sys_tz.tz_minuteswest * 60));
hs_page2->seconds_since1970 = local_time;
hs_header->checksum = mvumi_calculate_checksum(hs_header,
hs_header->frame_length);
break;
case HS_PAGE_FIRM_CTL:
hs_page3 = (struct mvumi_hs_page3 *) hs_header;
hs_header->frame_length = sizeof(*hs_page3) - 4;
memset(hs_header->frame_content, 0, hs_header->frame_length);
hs_header->checksum = mvumi_calculate_checksum(hs_header,
hs_header->frame_length);
break;
case HS_PAGE_CL_INFO:
hs_page4 = (struct mvumi_hs_page4 *) hs_header;
hs_header->frame_length = sizeof(*hs_page4) - 4;
memset(hs_header->frame_content, 0, hs_header->frame_length);
hs_page4->ib_baseaddr_l = lower_32_bits(mhba->ib_list_phys);
hs_page4->ib_baseaddr_h = upper_32_bits(mhba->ib_list_phys);
hs_page4->ob_baseaddr_l = lower_32_bits(mhba->ob_list_phys);
hs_page4->ob_baseaddr_h = upper_32_bits(mhba->ob_list_phys);
hs_page4->ib_entry_size = mhba->ib_max_size_setting;
hs_page4->ob_entry_size = mhba->ob_max_size_setting;
if (mhba->hba_capability
& HS_CAPABILITY_NEW_PAGE_IO_DEPTH_DEF) {
hs_page4->ob_depth = find_first_bit((unsigned long *)
&mhba->list_num_io,
BITS_PER_LONG);
hs_page4->ib_depth = find_first_bit((unsigned long *)
&mhba->list_num_io,
BITS_PER_LONG);
} else {
hs_page4->ob_depth = (u8) mhba->list_num_io;
hs_page4->ib_depth = (u8) mhba->list_num_io;
}
hs_header->checksum = mvumi_calculate_checksum(hs_header,
hs_header->frame_length);
break;
default:
dev_err(&mhba->pdev->dev, "cannot build page, code[0x%x]\n",
hs_header->page_code);
break;
}
}
/**
* mvumi_init_data - Initialize requested date for FW
* @mhba: Adapter soft state
*/
static int mvumi_init_data(struct mvumi_hba *mhba)
{
struct mvumi_ob_data *ob_pool;
struct mvumi_res *res_mgnt;
unsigned int tmp_size, offset, i;
void *virmem, *v;
dma_addr_t p;
if (mhba->fw_flag & MVUMI_FW_ALLOC)
return 0;
tmp_size = mhba->ib_max_size * mhba->max_io;
if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC)
tmp_size += sizeof(struct mvumi_dyn_list_entry) * mhba->max_io;
tmp_size += 128 + mhba->ob_max_size * mhba->max_io;
tmp_size += 8 + sizeof(u32)*2 + 16;
res_mgnt = mvumi_alloc_mem_resource(mhba,
RESOURCE_UNCACHED_MEMORY, tmp_size);
if (!res_mgnt) {
dev_err(&mhba->pdev->dev,
"failed to allocate memory for inbound list\n");
goto fail_alloc_dma_buf;
}
p = res_mgnt->bus_addr;
v = res_mgnt->virt_addr;
/* ib_list */
offset = round_up(p, 128) - p;
p += offset;
v += offset;
mhba->ib_list = v;
mhba->ib_list_phys = p;
if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC) {
v += sizeof(struct mvumi_dyn_list_entry) * mhba->max_io;
p += sizeof(struct mvumi_dyn_list_entry) * mhba->max_io;
mhba->ib_frame = v;
mhba->ib_frame_phys = p;
}
v += mhba->ib_max_size * mhba->max_io;
p += mhba->ib_max_size * mhba->max_io;
/* ib shadow */
offset = round_up(p, 8) - p;
p += offset;
v += offset;
mhba->ib_shadow = v;
mhba->ib_shadow_phys = p;
p += sizeof(u32)*2;
v += sizeof(u32)*2;
/* ob shadow */
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9580) {
offset = round_up(p, 8) - p;
p += offset;
v += offset;
mhba->ob_shadow = v;
mhba->ob_shadow_phys = p;
p += 8;
v += 8;
} else {
offset = round_up(p, 4) - p;
p += offset;
v += offset;
mhba->ob_shadow = v;
mhba->ob_shadow_phys = p;
p += 4;
v += 4;
}
/* ob list */
offset = round_up(p, 128) - p;
p += offset;
v += offset;
mhba->ob_list = v;
mhba->ob_list_phys = p;
/* ob data pool */
tmp_size = mhba->max_io * (mhba->ob_max_size + sizeof(*ob_pool));
tmp_size = round_up(tmp_size, 8);
res_mgnt = mvumi_alloc_mem_resource(mhba,
RESOURCE_CACHED_MEMORY, tmp_size);
if (!res_mgnt) {
dev_err(&mhba->pdev->dev,
"failed to allocate memory for outbound data buffer\n");
goto fail_alloc_dma_buf;
}
virmem = res_mgnt->virt_addr;
for (i = mhba->max_io; i != 0; i--) {
ob_pool = (struct mvumi_ob_data *) virmem;
list_add_tail(&ob_pool->list, &mhba->ob_data_list);
virmem += mhba->ob_max_size + sizeof(*ob_pool);
}
tmp_size = sizeof(unsigned short) * mhba->max_io +
sizeof(struct mvumi_cmd *) * mhba->max_io;
tmp_size += round_up(mhba->max_target_id, sizeof(unsigned char) * 8) /
(sizeof(unsigned char) * 8);
res_mgnt = mvumi_alloc_mem_resource(mhba,
RESOURCE_CACHED_MEMORY, tmp_size);
if (!res_mgnt) {
dev_err(&mhba->pdev->dev,
"failed to allocate memory for tag and target map\n");
goto fail_alloc_dma_buf;
}
virmem = res_mgnt->virt_addr;
mhba->tag_pool.stack = virmem;
mhba->tag_pool.size = mhba->max_io;
tag_init(&mhba->tag_pool, mhba->max_io);
virmem += sizeof(unsigned short) * mhba->max_io;
mhba->tag_cmd = virmem;
virmem += sizeof(struct mvumi_cmd *) * mhba->max_io;
mhba->target_map = virmem;
mhba->fw_flag |= MVUMI_FW_ALLOC;
return 0;
fail_alloc_dma_buf:
mvumi_release_mem_resource(mhba);
return -1;
}
static int mvumi_hs_process_page(struct mvumi_hba *mhba,
struct mvumi_hs_header *hs_header)
{
struct mvumi_hs_page1 *hs_page1;
unsigned char page_checksum;
page_checksum = mvumi_calculate_checksum(hs_header,
hs_header->frame_length);
if (page_checksum != hs_header->checksum) {
dev_err(&mhba->pdev->dev, "checksum error\n");
return -1;
}
switch (hs_header->page_code) {
case HS_PAGE_FIRM_CAP:
hs_page1 = (struct mvumi_hs_page1 *) hs_header;
mhba->max_io = hs_page1->max_io_support;
mhba->list_num_io = hs_page1->cl_inout_list_depth;
mhba->max_transfer_size = hs_page1->max_transfer_size;
mhba->max_target_id = hs_page1->max_devices_support;
mhba->hba_capability = hs_page1->capability;
mhba->ib_max_size_setting = hs_page1->cl_in_max_entry_size;
mhba->ib_max_size = (1 << hs_page1->cl_in_max_entry_size) << 2;
mhba->ob_max_size_setting = hs_page1->cl_out_max_entry_size;
mhba->ob_max_size = (1 << hs_page1->cl_out_max_entry_size) << 2;
dev_dbg(&mhba->pdev->dev, "FW version:%d\n",
hs_page1->fw_ver.ver_build);
if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_COMPACT_SG)
mhba->eot_flag = 22;
else
mhba->eot_flag = 27;
if (mhba->hba_capability & HS_CAPABILITY_NEW_PAGE_IO_DEPTH_DEF)
mhba->list_num_io = 1 << hs_page1->cl_inout_list_depth;
break;
default:
dev_err(&mhba->pdev->dev, "handshake: page code error\n");
return -1;
}
return 0;
}
/**
* mvumi_handshake - Move the FW to READY state
* @mhba: Adapter soft state
*
* During the initialization, FW passes can potentially be in any one of
* several possible states. If the FW in operational, waiting-for-handshake
* states, driver must take steps to bring it to ready state. Otherwise, it
* has to wait for the ready state.
*/
static int mvumi_handshake(struct mvumi_hba *mhba)
{
unsigned int hs_state, tmp, hs_fun;
struct mvumi_hs_header *hs_header;
struct mvumi_hw_regs *regs = mhba->regs;
if (mhba->fw_state == FW_STATE_STARTING)
hs_state = HS_S_START;
else {
tmp = ioread32(regs->arm_to_pciea_msg0);
hs_state = HS_GET_STATE(tmp);
dev_dbg(&mhba->pdev->dev, "handshake state[0x%x].\n", hs_state);
if (HS_GET_STATUS(tmp) != HS_STATUS_OK) {
mhba->fw_state = FW_STATE_STARTING;
return -1;
}
}
hs_fun = 0;
switch (hs_state) {
case HS_S_START:
mhba->fw_state = FW_STATE_HANDSHAKING;
HS_SET_STATUS(hs_fun, HS_STATUS_OK);
HS_SET_STATE(hs_fun, HS_S_RESET);
iowrite32(HANDSHAKE_SIGNATURE, regs->pciea_to_arm_msg1);
iowrite32(hs_fun, regs->pciea_to_arm_msg0);
iowrite32(DRBL_HANDSHAKE, regs->pciea_to_arm_drbl_reg);
break;
case HS_S_RESET:
iowrite32(lower_32_bits(mhba->handshake_page_phys),
regs->pciea_to_arm_msg1);
iowrite32(upper_32_bits(mhba->handshake_page_phys),
regs->arm_to_pciea_msg1);
HS_SET_STATUS(hs_fun, HS_STATUS_OK);
HS_SET_STATE(hs_fun, HS_S_PAGE_ADDR);
iowrite32(hs_fun, regs->pciea_to_arm_msg0);
iowrite32(DRBL_HANDSHAKE, regs->pciea_to_arm_drbl_reg);
break;
case HS_S_PAGE_ADDR:
case HS_S_QUERY_PAGE:
case HS_S_SEND_PAGE:
hs_header = (struct mvumi_hs_header *) mhba->handshake_page;
if (hs_header->page_code == HS_PAGE_FIRM_CAP) {
mhba->hba_total_pages =
((struct mvumi_hs_page1 *) hs_header)->total_pages;
if (mhba->hba_total_pages == 0)
mhba->hba_total_pages = HS_PAGE_TOTAL-1;
}
if (hs_state == HS_S_QUERY_PAGE) {
if (mvumi_hs_process_page(mhba, hs_header)) {
HS_SET_STATE(hs_fun, HS_S_ABORT);
return -1;
}
if (mvumi_init_data(mhba)) {
HS_SET_STATE(hs_fun, HS_S_ABORT);
return -1;
}
} else if (hs_state == HS_S_PAGE_ADDR) {
hs_header->page_code = 0;
mhba->hba_total_pages = HS_PAGE_TOTAL-1;
}
if ((hs_header->page_code + 1) <= mhba->hba_total_pages) {
hs_header->page_code++;
if (hs_header->page_code != HS_PAGE_FIRM_CAP) {
mvumi_hs_build_page(mhba, hs_header);
HS_SET_STATE(hs_fun, HS_S_SEND_PAGE);
} else
HS_SET_STATE(hs_fun, HS_S_QUERY_PAGE);
} else
HS_SET_STATE(hs_fun, HS_S_END);
HS_SET_STATUS(hs_fun, HS_STATUS_OK);
iowrite32(hs_fun, regs->pciea_to_arm_msg0);
iowrite32(DRBL_HANDSHAKE, regs->pciea_to_arm_drbl_reg);
break;
case HS_S_END:
/* Set communication list ISR */
tmp = ioread32(regs->enpointa_mask_reg);
tmp |= regs->int_comaout | regs->int_comaerr;
iowrite32(tmp, regs->enpointa_mask_reg);
iowrite32(mhba->list_num_io, mhba->ib_shadow);
/* Set InBound List Available count shadow */
iowrite32(lower_32_bits(mhba->ib_shadow_phys),
regs->inb_aval_count_basel);
iowrite32(upper_32_bits(mhba->ib_shadow_phys),
regs->inb_aval_count_baseh);
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9143) {
/* Set OutBound List Available count shadow */
iowrite32((mhba->list_num_io-1) |
regs->cl_pointer_toggle,
mhba->ob_shadow);
iowrite32(lower_32_bits(mhba->ob_shadow_phys),
regs->outb_copy_basel);
iowrite32(upper_32_bits(mhba->ob_shadow_phys),
regs->outb_copy_baseh);
}
mhba->ib_cur_slot = (mhba->list_num_io - 1) |
regs->cl_pointer_toggle;
mhba->ob_cur_slot = (mhba->list_num_io - 1) |
regs->cl_pointer_toggle;
mhba->fw_state = FW_STATE_STARTED;
break;
default:
dev_err(&mhba->pdev->dev, "unknown handshake state [0x%x].\n",
hs_state);
return -1;
}
return 0;
}
static unsigned char mvumi_handshake_event(struct mvumi_hba *mhba)
{
unsigned int isr_status;
unsigned long before;
before = jiffies;
mvumi_handshake(mhba);
do {
isr_status = mhba->instancet->read_fw_status_reg(mhba);
if (mhba->fw_state == FW_STATE_STARTED)
return 0;
if (time_after(jiffies, before + FW_MAX_DELAY * HZ)) {
dev_err(&mhba->pdev->dev,
"no handshake response at state 0x%x.\n",
mhba->fw_state);
dev_err(&mhba->pdev->dev,
"isr : global=0x%x,status=0x%x.\n",
mhba->global_isr, isr_status);
return -1;
}
rmb();
usleep_range(1000, 2000);
} while (!(isr_status & DRBL_HANDSHAKE_ISR));
return 0;
}
static unsigned char mvumi_check_handshake(struct mvumi_hba *mhba)
{
unsigned int tmp;
unsigned long before;
before = jiffies;
tmp = ioread32(mhba->regs->arm_to_pciea_msg1);
while ((tmp != HANDSHAKE_READYSTATE) && (tmp != HANDSHAKE_DONESTATE)) {
if (tmp != HANDSHAKE_READYSTATE)
iowrite32(DRBL_MU_RESET,
mhba->regs->pciea_to_arm_drbl_reg);
if (time_after(jiffies, before + FW_MAX_DELAY * HZ)) {
dev_err(&mhba->pdev->dev,
"invalid signature [0x%x].\n", tmp);
return -1;
}
usleep_range(1000, 2000);
rmb();
tmp = ioread32(mhba->regs->arm_to_pciea_msg1);
}
mhba->fw_state = FW_STATE_STARTING;
dev_dbg(&mhba->pdev->dev, "start firmware handshake...\n");
do {
if (mvumi_handshake_event(mhba)) {
dev_err(&mhba->pdev->dev,
"handshake failed at state 0x%x.\n",
mhba->fw_state);
return -1;
}
} while (mhba->fw_state != FW_STATE_STARTED);
dev_dbg(&mhba->pdev->dev, "firmware handshake done\n");
return 0;
}
static unsigned char mvumi_start(struct mvumi_hba *mhba)
{
unsigned int tmp;
struct mvumi_hw_regs *regs = mhba->regs;
/* clear Door bell */
tmp = ioread32(regs->arm_to_pciea_drbl_reg);
iowrite32(tmp, regs->arm_to_pciea_drbl_reg);
iowrite32(regs->int_drbl_int_mask, regs->arm_to_pciea_mask_reg);
tmp = ioread32(regs->enpointa_mask_reg) | regs->int_dl_cpu2pciea;
iowrite32(tmp, regs->enpointa_mask_reg);
msleep(100);
if (mvumi_check_handshake(mhba))
return -1;
return 0;
}
/**
* mvumi_complete_cmd - Completes a command
* @mhba: Adapter soft state
* @cmd: Command to be completed
*/
static void mvumi_complete_cmd(struct mvumi_hba *mhba, struct mvumi_cmd *cmd,
struct mvumi_rsp_frame *ob_frame)
{
struct scsi_cmnd *scmd = cmd->scmd;
cmd->scmd->SCp.ptr = NULL;
scmd->result = ob_frame->req_status;
switch (ob_frame->req_status) {
case SAM_STAT_GOOD:
scmd->result |= DID_OK << 16;
break;
case SAM_STAT_BUSY:
scmd->result |= DID_BUS_BUSY << 16;
break;
case SAM_STAT_CHECK_CONDITION:
scmd->result |= (DID_OK << 16);
if (ob_frame->rsp_flag & CL_RSP_FLAG_SENSEDATA) {
memcpy(cmd->scmd->sense_buffer, ob_frame->payload,
sizeof(struct mvumi_sense_data));
scmd->result |= (DRIVER_SENSE << 24);
}
break;
default:
scmd->result |= (DRIVER_INVALID << 24) | (DID_ABORT << 16);
break;
}
if (scsi_bufflen(scmd))
dma_unmap_sg(&mhba->pdev->dev, scsi_sglist(scmd),
scsi_sg_count(scmd),
scmd->sc_data_direction);
cmd->scmd->scsi_done(scmd);
mvumi_return_cmd(mhba, cmd);
}
static void mvumi_complete_internal_cmd(struct mvumi_hba *mhba,
struct mvumi_cmd *cmd,
struct mvumi_rsp_frame *ob_frame)
{
if (atomic_read(&cmd->sync_cmd)) {
cmd->cmd_status = ob_frame->req_status;
if ((ob_frame->req_status == SAM_STAT_CHECK_CONDITION) &&
(ob_frame->rsp_flag & CL_RSP_FLAG_SENSEDATA) &&
cmd->data_buf) {
memcpy(cmd->data_buf, ob_frame->payload,
sizeof(struct mvumi_sense_data));
}
atomic_dec(&cmd->sync_cmd);
wake_up(&mhba->int_cmd_wait_q);
}
}
static void mvumi_show_event(struct mvumi_hba *mhba,
struct mvumi_driver_event *ptr)
{
unsigned int i;
dev_warn(&mhba->pdev->dev,
"Event[0x%x] id[0x%x] severity[0x%x] device id[0x%x]\n",
ptr->sequence_no, ptr->event_id, ptr->severity, ptr->device_id);
if (ptr->param_count) {
printk(KERN_WARNING "Event param(len 0x%x): ",
ptr->param_count);
for (i = 0; i < ptr->param_count; i++)
printk(KERN_WARNING "0x%x ", ptr->params[i]);
printk(KERN_WARNING "\n");
}
if (ptr->sense_data_length) {
printk(KERN_WARNING "Event sense data(len 0x%x): ",
ptr->sense_data_length);
for (i = 0; i < ptr->sense_data_length; i++)
printk(KERN_WARNING "0x%x ", ptr->sense_data[i]);
printk(KERN_WARNING "\n");
}
}
static int mvumi_handle_hotplug(struct mvumi_hba *mhba, u16 devid, int status)
{
struct scsi_device *sdev;
int ret = -1;
if (status == DEVICE_OFFLINE) {
sdev = scsi_device_lookup(mhba->shost, 0, devid, 0);
if (sdev) {
dev_dbg(&mhba->pdev->dev, "remove disk %d-%d-%d.\n", 0,
sdev->id, 0);
scsi_remove_device(sdev);
scsi_device_put(sdev);
ret = 0;
} else
dev_err(&mhba->pdev->dev, " no disk[%d] to remove\n",
devid);
} else if (status == DEVICE_ONLINE) {
sdev = scsi_device_lookup(mhba->shost, 0, devid, 0);
if (!sdev) {
scsi_add_device(mhba->shost, 0, devid, 0);
dev_dbg(&mhba->pdev->dev, " add disk %d-%d-%d.\n", 0,
devid, 0);
ret = 0;
} else {
dev_err(&mhba->pdev->dev, " don't add disk %d-%d-%d.\n",
0, devid, 0);
scsi_device_put(sdev);
}
}
return ret;
}
static u64 mvumi_inquiry(struct mvumi_hba *mhba,
unsigned int id, struct mvumi_cmd *cmd)
{
struct mvumi_msg_frame *frame;
u64 wwid = 0;
int cmd_alloc = 0;
int data_buf_len = 64;
if (!cmd) {
cmd = mvumi_create_internal_cmd(mhba, data_buf_len);
if (cmd)
cmd_alloc = 1;
else
return 0;
} else {
memset(cmd->data_buf, 0, data_buf_len);
}
cmd->scmd = NULL;
cmd->cmd_status = REQ_STATUS_PENDING;
atomic_set(&cmd->sync_cmd, 0);
frame = cmd->frame;
frame->device_id = (u16) id;
frame->cmd_flag = CMD_FLAG_DATA_IN;
frame->req_function = CL_FUN_SCSI_CMD;
frame->cdb_length = 6;
frame->data_transfer_length = MVUMI_INQUIRY_LENGTH;
memset(frame->cdb, 0, frame->cdb_length);
frame->cdb[0] = INQUIRY;
frame->cdb[4] = frame->data_transfer_length;
mvumi_issue_blocked_cmd(mhba, cmd);
if (cmd->cmd_status == SAM_STAT_GOOD) {
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9143)
wwid = id + 1;
else
memcpy((void *)&wwid,
(cmd->data_buf + MVUMI_INQUIRY_UUID_OFF),
MVUMI_INQUIRY_UUID_LEN);
dev_dbg(&mhba->pdev->dev,
"inquiry device(0:%d:0) wwid(%llx)\n", id, wwid);
} else {
wwid = 0;
}
if (cmd_alloc)
mvumi_delete_internal_cmd(mhba, cmd);
return wwid;
}
static void mvumi_detach_devices(struct mvumi_hba *mhba)
{
struct mvumi_device *mv_dev = NULL , *dev_next;
struct scsi_device *sdev = NULL;
mutex_lock(&mhba->device_lock);
/* detach Hard Disk */
list_for_each_entry_safe(mv_dev, dev_next,
&mhba->shost_dev_list, list) {
mvumi_handle_hotplug(mhba, mv_dev->id, DEVICE_OFFLINE);
list_del_init(&mv_dev->list);
dev_dbg(&mhba->pdev->dev, "release device(0:%d:0) wwid(%llx)\n",
mv_dev->id, mv_dev->wwid);
kfree(mv_dev);
}
list_for_each_entry_safe(mv_dev, dev_next, &mhba->mhba_dev_list, list) {
list_del_init(&mv_dev->list);
dev_dbg(&mhba->pdev->dev, "release device(0:%d:0) wwid(%llx)\n",
mv_dev->id, mv_dev->wwid);
kfree(mv_dev);
}
/* detach virtual device */
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9580)
sdev = scsi_device_lookup(mhba->shost, 0,
mhba->max_target_id - 1, 0);
if (sdev) {
scsi_remove_device(sdev);
scsi_device_put(sdev);
}
mutex_unlock(&mhba->device_lock);
}
static void mvumi_rescan_devices(struct mvumi_hba *mhba, int id)
{
struct scsi_device *sdev;
sdev = scsi_device_lookup(mhba->shost, 0, id, 0);
if (sdev) {
scsi_rescan_device(&sdev->sdev_gendev);
scsi_device_put(sdev);
}
}
static int mvumi_match_devices(struct mvumi_hba *mhba, int id, u64 wwid)
{
struct mvumi_device *mv_dev = NULL;
list_for_each_entry(mv_dev, &mhba->shost_dev_list, list) {
if (mv_dev->wwid == wwid) {
if (mv_dev->id != id) {
dev_err(&mhba->pdev->dev,
"%s has same wwid[%llx] ,"
" but different id[%d %d]\n",
__func__, mv_dev->wwid, mv_dev->id, id);
return -1;
} else {
if (mhba->pdev->device ==
PCI_DEVICE_ID_MARVELL_MV9143)
mvumi_rescan_devices(mhba, id);
return 1;
}
}
}
return 0;
}
static void mvumi_remove_devices(struct mvumi_hba *mhba, int id)
{
struct mvumi_device *mv_dev = NULL, *dev_next;
list_for_each_entry_safe(mv_dev, dev_next,
&mhba->shost_dev_list, list) {
if (mv_dev->id == id) {
dev_dbg(&mhba->pdev->dev,
"detach device(0:%d:0) wwid(%llx) from HOST\n",
mv_dev->id, mv_dev->wwid);
mvumi_handle_hotplug(mhba, mv_dev->id, DEVICE_OFFLINE);
list_del_init(&mv_dev->list);
kfree(mv_dev);
}
}
}
static int mvumi_probe_devices(struct mvumi_hba *mhba)
{
int id, maxid;
u64 wwid = 0;
struct mvumi_device *mv_dev = NULL;
struct mvumi_cmd *cmd = NULL;
int found = 0;
cmd = mvumi_create_internal_cmd(mhba, 64);
if (!cmd)
return -1;
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9143)
maxid = mhba->max_target_id;
else
maxid = mhba->max_target_id - 1;
for (id = 0; id < maxid; id++) {
wwid = mvumi_inquiry(mhba, id, cmd);
if (!wwid) {
/* device no response, remove it */
mvumi_remove_devices(mhba, id);
} else {
/* device response, add it */
found = mvumi_match_devices(mhba, id, wwid);
if (!found) {
mvumi_remove_devices(mhba, id);
mv_dev = kzalloc(sizeof(struct mvumi_device),
GFP_KERNEL);
if (!mv_dev) {
dev_err(&mhba->pdev->dev,
"%s alloc mv_dev failed\n",
__func__);
continue;
}
mv_dev->id = id;
mv_dev->wwid = wwid;
mv_dev->sdev = NULL;
INIT_LIST_HEAD(&mv_dev->list);
list_add_tail(&mv_dev->list,
&mhba->mhba_dev_list);
dev_dbg(&mhba->pdev->dev,
"probe a new device(0:%d:0)"
" wwid(%llx)\n", id, mv_dev->wwid);
} else if (found == -1)
return -1;
else
continue;
}
}
if (cmd)
mvumi_delete_internal_cmd(mhba, cmd);
return 0;
}
static int mvumi_rescan_bus(void *data)
{
int ret = 0;
struct mvumi_hba *mhba = (struct mvumi_hba *) data;
struct mvumi_device *mv_dev = NULL , *dev_next;
while (!kthread_should_stop()) {
set_current_state(TASK_INTERRUPTIBLE);
if (!atomic_read(&mhba->pnp_count))
schedule();
msleep(1000);
atomic_set(&mhba->pnp_count, 0);
__set_current_state(TASK_RUNNING);
mutex_lock(&mhba->device_lock);
ret = mvumi_probe_devices(mhba);
if (!ret) {
list_for_each_entry_safe(mv_dev, dev_next,
&mhba->mhba_dev_list, list) {
if (mvumi_handle_hotplug(mhba, mv_dev->id,
DEVICE_ONLINE)) {
dev_err(&mhba->pdev->dev,
"%s add device(0:%d:0) failed"
"wwid(%llx) has exist\n",
__func__,
mv_dev->id, mv_dev->wwid);
list_del_init(&mv_dev->list);
kfree(mv_dev);
} else {
list_move_tail(&mv_dev->list,
&mhba->shost_dev_list);
}
}
}
mutex_unlock(&mhba->device_lock);
}
return 0;
}
static void mvumi_proc_msg(struct mvumi_hba *mhba,
struct mvumi_hotplug_event *param)
{
u16 size = param->size;
const unsigned long *ar_bitmap;
const unsigned long *re_bitmap;
int index;
if (mhba->fw_flag & MVUMI_FW_ATTACH) {
index = -1;
ar_bitmap = (const unsigned long *) param->bitmap;
re_bitmap = (const unsigned long *) &param->bitmap[size >> 3];
mutex_lock(&mhba->sas_discovery_mutex);
do {
index = find_next_zero_bit(ar_bitmap, size, index + 1);
if (index >= size)
break;
mvumi_handle_hotplug(mhba, index, DEVICE_ONLINE);
} while (1);
index = -1;
do {
index = find_next_zero_bit(re_bitmap, size, index + 1);
if (index >= size)
break;
mvumi_handle_hotplug(mhba, index, DEVICE_OFFLINE);
} while (1);
mutex_unlock(&mhba->sas_discovery_mutex);
}
}
static void mvumi_notification(struct mvumi_hba *mhba, u8 msg, void *buffer)
{
if (msg == APICDB1_EVENT_GETEVENT) {
int i, count;
struct mvumi_driver_event *param = NULL;
struct mvumi_event_req *er = buffer;
count = er->count;
if (count > MAX_EVENTS_RETURNED) {
dev_err(&mhba->pdev->dev, "event count[0x%x] is bigger"
" than max event count[0x%x].\n",
count, MAX_EVENTS_RETURNED);
return;
}
for (i = 0; i < count; i++) {
param = &er->events[i];
mvumi_show_event(mhba, param);
}
} else if (msg == APICDB1_HOST_GETEVENT) {
mvumi_proc_msg(mhba, buffer);
}
}
static int mvumi_get_event(struct mvumi_hba *mhba, unsigned char msg)
{
struct mvumi_cmd *cmd;
struct mvumi_msg_frame *frame;
cmd = mvumi_create_internal_cmd(mhba, 512);
if (!cmd)
return -1;
cmd->scmd = NULL;
cmd->cmd_status = REQ_STATUS_PENDING;
atomic_set(&cmd->sync_cmd, 0);
frame = cmd->frame;
frame->device_id = 0;
frame->cmd_flag = CMD_FLAG_DATA_IN;
frame->req_function = CL_FUN_SCSI_CMD;
frame->cdb_length = MAX_COMMAND_SIZE;
frame->data_transfer_length = sizeof(struct mvumi_event_req);
memset(frame->cdb, 0, MAX_COMMAND_SIZE);
frame->cdb[0] = APICDB0_EVENT;
frame->cdb[1] = msg;
mvumi_issue_blocked_cmd(mhba, cmd);
if (cmd->cmd_status != SAM_STAT_GOOD)
dev_err(&mhba->pdev->dev, "get event failed, status=0x%x.\n",
cmd->cmd_status);
else
mvumi_notification(mhba, cmd->frame->cdb[1], cmd->data_buf);
mvumi_delete_internal_cmd(mhba, cmd);
return 0;
}
static void mvumi_scan_events(struct work_struct *work)
{
struct mvumi_events_wq *mu_ev =
container_of(work, struct mvumi_events_wq, work_q);
mvumi_get_event(mu_ev->mhba, mu_ev->event);
kfree(mu_ev);
}
static void mvumi_launch_events(struct mvumi_hba *mhba, u32 isr_status)
{
struct mvumi_events_wq *mu_ev;
while (isr_status & (DRBL_BUS_CHANGE | DRBL_EVENT_NOTIFY)) {
if (isr_status & DRBL_BUS_CHANGE) {
atomic_inc(&mhba->pnp_count);
wake_up_process(mhba->dm_thread);
isr_status &= ~(DRBL_BUS_CHANGE);
continue;
}
mu_ev = kzalloc(sizeof(*mu_ev), GFP_ATOMIC);
if (mu_ev) {
INIT_WORK(&mu_ev->work_q, mvumi_scan_events);
mu_ev->mhba = mhba;
mu_ev->event = APICDB1_EVENT_GETEVENT;
isr_status &= ~(DRBL_EVENT_NOTIFY);
mu_ev->param = NULL;
schedule_work(&mu_ev->work_q);
}
}
}
static void mvumi_handle_clob(struct mvumi_hba *mhba)
{
struct mvumi_rsp_frame *ob_frame;
struct mvumi_cmd *cmd;
struct mvumi_ob_data *pool;
while (!list_empty(&mhba->free_ob_list)) {
pool = list_first_entry(&mhba->free_ob_list,
struct mvumi_ob_data, list);
list_del_init(&pool->list);
list_add_tail(&pool->list, &mhba->ob_data_list);
ob_frame = (struct mvumi_rsp_frame *) &pool->data[0];
cmd = mhba->tag_cmd[ob_frame->tag];
atomic_dec(&mhba->fw_outstanding);
mhba->tag_cmd[ob_frame->tag] = NULL;
tag_release_one(mhba, &mhba->tag_pool, ob_frame->tag);
if (cmd->scmd)
mvumi_complete_cmd(mhba, cmd, ob_frame);
else
mvumi_complete_internal_cmd(mhba, cmd, ob_frame);
}
mhba->instancet->fire_cmd(mhba, NULL);
}
static irqreturn_t mvumi_isr_handler(int irq, void *devp)
{
struct mvumi_hba *mhba = (struct mvumi_hba *) devp;
unsigned long flags;
spin_lock_irqsave(mhba->shost->host_lock, flags);
if (unlikely(mhba->instancet->clear_intr(mhba) || !mhba->global_isr)) {
spin_unlock_irqrestore(mhba->shost->host_lock, flags);
return IRQ_NONE;
}
if (mhba->global_isr & mhba->regs->int_dl_cpu2pciea) {
if (mhba->isr_status & (DRBL_BUS_CHANGE | DRBL_EVENT_NOTIFY))
mvumi_launch_events(mhba, mhba->isr_status);
if (mhba->isr_status & DRBL_HANDSHAKE_ISR) {
dev_warn(&mhba->pdev->dev, "enter handshake again!\n");
mvumi_handshake(mhba);
}
}
if (mhba->global_isr & mhba->regs->int_comaout)
mvumi_receive_ob_list_entry(mhba);
mhba->global_isr = 0;
mhba->isr_status = 0;
if (mhba->fw_state == FW_STATE_STARTED)
mvumi_handle_clob(mhba);
spin_unlock_irqrestore(mhba->shost->host_lock, flags);
return IRQ_HANDLED;
}
static enum mvumi_qc_result mvumi_send_command(struct mvumi_hba *mhba,
struct mvumi_cmd *cmd)
{
void *ib_entry;
struct mvumi_msg_frame *ib_frame;
unsigned int frame_len;
ib_frame = cmd->frame;
if (unlikely(mhba->fw_state != FW_STATE_STARTED)) {
dev_dbg(&mhba->pdev->dev, "firmware not ready.\n");
return MV_QUEUE_COMMAND_RESULT_NO_RESOURCE;
}
if (tag_is_empty(&mhba->tag_pool)) {
dev_dbg(&mhba->pdev->dev, "no free tag.\n");
return MV_QUEUE_COMMAND_RESULT_NO_RESOURCE;
}
mvumi_get_ib_list_entry(mhba, &ib_entry);
cmd->frame->tag = tag_get_one(mhba, &mhba->tag_pool);
cmd->frame->request_id = mhba->io_seq++;
cmd->request_id = cmd->frame->request_id;
mhba->tag_cmd[cmd->frame->tag] = cmd;
frame_len = sizeof(*ib_frame) - 4 +
ib_frame->sg_counts * sizeof(struct mvumi_sgl);
if (mhba->hba_capability & HS_CAPABILITY_SUPPORT_DYN_SRC) {
struct mvumi_dyn_list_entry *dle;
dle = ib_entry;
dle->src_low_addr =
cpu_to_le32(lower_32_bits(cmd->frame_phys));
dle->src_high_addr =
cpu_to_le32(upper_32_bits(cmd->frame_phys));
dle->if_length = (frame_len >> 2) & 0xFFF;
} else {
memcpy(ib_entry, ib_frame, frame_len);
}
return MV_QUEUE_COMMAND_RESULT_SENT;
}
static void mvumi_fire_cmd(struct mvumi_hba *mhba, struct mvumi_cmd *cmd)
{
unsigned short num_of_cl_sent = 0;
unsigned int count;
enum mvumi_qc_result result;
if (cmd)
list_add_tail(&cmd->queue_pointer, &mhba->waiting_req_list);
count = mhba->instancet->check_ib_list(mhba);
if (list_empty(&mhba->waiting_req_list) || !count)
return;
do {
cmd = list_first_entry(&mhba->waiting_req_list,
struct mvumi_cmd, queue_pointer);
list_del_init(&cmd->queue_pointer);
result = mvumi_send_command(mhba, cmd);
switch (result) {
case MV_QUEUE_COMMAND_RESULT_SENT:
num_of_cl_sent++;
break;
case MV_QUEUE_COMMAND_RESULT_NO_RESOURCE:
list_add(&cmd->queue_pointer, &mhba->waiting_req_list);
if (num_of_cl_sent > 0)
mvumi_send_ib_list_entry(mhba);
return;
}
} while (!list_empty(&mhba->waiting_req_list) && count--);
if (num_of_cl_sent > 0)
mvumi_send_ib_list_entry(mhba);
}
/**
* mvumi_enable_intr - Enables interrupts
* @mhba: Adapter soft state
*/
static void mvumi_enable_intr(struct mvumi_hba *mhba)
{
unsigned int mask;
struct mvumi_hw_regs *regs = mhba->regs;
iowrite32(regs->int_drbl_int_mask, regs->arm_to_pciea_mask_reg);
mask = ioread32(regs->enpointa_mask_reg);
mask |= regs->int_dl_cpu2pciea | regs->int_comaout | regs->int_comaerr;
iowrite32(mask, regs->enpointa_mask_reg);
}
/**
* mvumi_disable_intr -Disables interrupt
* @mhba: Adapter soft state
*/
static void mvumi_disable_intr(struct mvumi_hba *mhba)
{
unsigned int mask;
struct mvumi_hw_regs *regs = mhba->regs;
iowrite32(0, regs->arm_to_pciea_mask_reg);
mask = ioread32(regs->enpointa_mask_reg);
mask &= ~(regs->int_dl_cpu2pciea | regs->int_comaout |
regs->int_comaerr);
iowrite32(mask, regs->enpointa_mask_reg);
}
static int mvumi_clear_intr(void *extend)
{
struct mvumi_hba *mhba = (struct mvumi_hba *) extend;
unsigned int status, isr_status = 0, tmp = 0;
struct mvumi_hw_regs *regs = mhba->regs;
status = ioread32(regs->main_int_cause_reg);
if (!(status & regs->int_mu) || status == 0xFFFFFFFF)
return 1;
if (unlikely(status & regs->int_comaerr)) {
tmp = ioread32(regs->outb_isr_cause);
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9580) {
if (tmp & regs->clic_out_err) {
iowrite32(tmp & regs->clic_out_err,
regs->outb_isr_cause);
}
} else {
if (tmp & (regs->clic_in_err | regs->clic_out_err))
iowrite32(tmp & (regs->clic_in_err |
regs->clic_out_err),
regs->outb_isr_cause);
}
status ^= mhba->regs->int_comaerr;
/* inbound or outbound parity error, command will timeout */
}
if (status & regs->int_comaout) {
tmp = ioread32(regs->outb_isr_cause);
if (tmp & regs->clic_irq)
iowrite32(tmp & regs->clic_irq, regs->outb_isr_cause);
}
if (status & regs->int_dl_cpu2pciea) {
isr_status = ioread32(regs->arm_to_pciea_drbl_reg);
if (isr_status)
iowrite32(isr_status, regs->arm_to_pciea_drbl_reg);
}
mhba->global_isr = status;
mhba->isr_status = isr_status;
return 0;
}
/**
* mvumi_read_fw_status_reg - returns the current FW status value
* @mhba: Adapter soft state
*/
static unsigned int mvumi_read_fw_status_reg(struct mvumi_hba *mhba)
{
unsigned int status;
status = ioread32(mhba->regs->arm_to_pciea_drbl_reg);
if (status)
iowrite32(status, mhba->regs->arm_to_pciea_drbl_reg);
return status;
}
static struct mvumi_instance_template mvumi_instance_9143 = {
.fire_cmd = mvumi_fire_cmd,
.enable_intr = mvumi_enable_intr,
.disable_intr = mvumi_disable_intr,
.clear_intr = mvumi_clear_intr,
.read_fw_status_reg = mvumi_read_fw_status_reg,
.check_ib_list = mvumi_check_ib_list_9143,
.check_ob_list = mvumi_check_ob_list_9143,
.reset_host = mvumi_reset_host_9143,
};
static struct mvumi_instance_template mvumi_instance_9580 = {
.fire_cmd = mvumi_fire_cmd,
.enable_intr = mvumi_enable_intr,
.disable_intr = mvumi_disable_intr,
.clear_intr = mvumi_clear_intr,
.read_fw_status_reg = mvumi_read_fw_status_reg,
.check_ib_list = mvumi_check_ib_list_9580,
.check_ob_list = mvumi_check_ob_list_9580,
.reset_host = mvumi_reset_host_9580,
};
static int mvumi_slave_configure(struct scsi_device *sdev)
{
struct mvumi_hba *mhba;
unsigned char bitcount = sizeof(unsigned char) * 8;
mhba = (struct mvumi_hba *) sdev->host->hostdata;
if (sdev->id >= mhba->max_target_id)
return -EINVAL;
mhba->target_map[sdev->id / bitcount] |= (1 << (sdev->id % bitcount));
return 0;
}
/**
* mvumi_build_frame - Prepares a direct cdb (DCDB) command
* @mhba: Adapter soft state
* @scmd: SCSI command
* @cmd: Command to be prepared in
*
* This function prepares CDB commands. These are typcially pass-through
* commands to the devices.
*/
static unsigned char mvumi_build_frame(struct mvumi_hba *mhba,
struct scsi_cmnd *scmd, struct mvumi_cmd *cmd)
{
struct mvumi_msg_frame *pframe;
cmd->scmd = scmd;
cmd->cmd_status = REQ_STATUS_PENDING;
pframe = cmd->frame;
pframe->device_id = ((unsigned short) scmd->device->id) |
(((unsigned short) scmd->device->lun) << 8);
pframe->cmd_flag = 0;
switch (scmd->sc_data_direction) {
case DMA_NONE:
pframe->cmd_flag |= CMD_FLAG_NON_DATA;
break;
case DMA_FROM_DEVICE:
pframe->cmd_flag |= CMD_FLAG_DATA_IN;
break;
case DMA_TO_DEVICE:
pframe->cmd_flag |= CMD_FLAG_DATA_OUT;
break;
case DMA_BIDIRECTIONAL:
default:
dev_warn(&mhba->pdev->dev, "unexpected data direction[%d] "
"cmd[0x%x]\n", scmd->sc_data_direction, scmd->cmnd[0]);
goto error;
}
pframe->cdb_length = scmd->cmd_len;
memcpy(pframe->cdb, scmd->cmnd, pframe->cdb_length);
pframe->req_function = CL_FUN_SCSI_CMD;
if (scsi_bufflen(scmd)) {
if (mvumi_make_sgl(mhba, scmd, &pframe->payload[0],
&pframe->sg_counts))
goto error;
pframe->data_transfer_length = scsi_bufflen(scmd);
} else {
pframe->sg_counts = 0;
pframe->data_transfer_length = 0;
}
return 0;
error:
scmd->result = (DID_OK << 16) | (DRIVER_SENSE << 24) |
SAM_STAT_CHECK_CONDITION;
scsi_build_sense_buffer(0, scmd->sense_buffer, ILLEGAL_REQUEST, 0x24,
0);
return -1;
}
/**
* mvumi_queue_command - Queue entry point
* @scmd: SCSI command to be queued
* @done: Callback entry point
*/
static int mvumi_queue_command(struct Scsi_Host *shost,
struct scsi_cmnd *scmd)
{
struct mvumi_cmd *cmd;
struct mvumi_hba *mhba;
unsigned long irq_flags;
spin_lock_irqsave(shost->host_lock, irq_flags);
mhba = (struct mvumi_hba *) shost->hostdata;
scmd->result = 0;
cmd = mvumi_get_cmd(mhba);
if (unlikely(!cmd)) {
spin_unlock_irqrestore(shost->host_lock, irq_flags);
return SCSI_MLQUEUE_HOST_BUSY;
}
if (unlikely(mvumi_build_frame(mhba, scmd, cmd)))
goto out_return_cmd;
cmd->scmd = scmd;
scmd->SCp.ptr = (char *) cmd;
mhba->instancet->fire_cmd(mhba, cmd);
spin_unlock_irqrestore(shost->host_lock, irq_flags);
return 0;
out_return_cmd:
mvumi_return_cmd(mhba, cmd);
scmd->scsi_done(scmd);
spin_unlock_irqrestore(shost->host_lock, irq_flags);
return 0;
}
static enum blk_eh_timer_return mvumi_timed_out(struct scsi_cmnd *scmd)
{
struct mvumi_cmd *cmd = (struct mvumi_cmd *) scmd->SCp.ptr;
struct Scsi_Host *host = scmd->device->host;
struct mvumi_hba *mhba = shost_priv(host);
unsigned long flags;
spin_lock_irqsave(mhba->shost->host_lock, flags);
if (mhba->tag_cmd[cmd->frame->tag]) {
mhba->tag_cmd[cmd->frame->tag] = NULL;
tag_release_one(mhba, &mhba->tag_pool, cmd->frame->tag);
}
if (!list_empty(&cmd->queue_pointer))
list_del_init(&cmd->queue_pointer);
else
atomic_dec(&mhba->fw_outstanding);
scmd->result = (DRIVER_INVALID << 24) | (DID_ABORT << 16);
scmd->SCp.ptr = NULL;
if (scsi_bufflen(scmd)) {
dma_unmap_sg(&mhba->pdev->dev, scsi_sglist(scmd),
scsi_sg_count(scmd),
scmd->sc_data_direction);
}
mvumi_return_cmd(mhba, cmd);
spin_unlock_irqrestore(mhba->shost->host_lock, flags);
return BLK_EH_DONE;
}
static int
mvumi_bios_param(struct scsi_device *sdev, struct block_device *bdev,
sector_t capacity, int geom[])
{
int heads, sectors;
sector_t cylinders;
unsigned long tmp;
heads = 64;
sectors = 32;
tmp = heads * sectors;
cylinders = capacity;
sector_div(cylinders, tmp);
if (capacity >= 0x200000) {
heads = 255;
sectors = 63;
tmp = heads * sectors;
cylinders = capacity;
sector_div(cylinders, tmp);
}
geom[0] = heads;
geom[1] = sectors;
geom[2] = cylinders;
return 0;
}
static struct scsi_host_template mvumi_template = {
.module = THIS_MODULE,
.name = "Marvell Storage Controller",
.slave_configure = mvumi_slave_configure,
.queuecommand = mvumi_queue_command,
.eh_timed_out = mvumi_timed_out,
.eh_host_reset_handler = mvumi_host_reset,
.bios_param = mvumi_bios_param,
.dma_boundary = PAGE_SIZE - 1,
.this_id = -1,
};
static int mvumi_cfg_hw_reg(struct mvumi_hba *mhba)
{
void *base = NULL;
struct mvumi_hw_regs *regs;
switch (mhba->pdev->device) {
case PCI_DEVICE_ID_MARVELL_MV9143:
mhba->mmio = mhba->base_addr[0];
base = mhba->mmio;
if (!mhba->regs) {
mhba->regs = kzalloc(sizeof(*regs), GFP_KERNEL);
if (mhba->regs == NULL)
return -ENOMEM;
}
regs = mhba->regs;
/* For Arm */
regs->ctrl_sts_reg = base + 0x20104;
regs->rstoutn_mask_reg = base + 0x20108;
regs->sys_soft_rst_reg = base + 0x2010C;
regs->main_int_cause_reg = base + 0x20200;
regs->enpointa_mask_reg = base + 0x2020C;
regs->rstoutn_en_reg = base + 0xF1400;
/* For Doorbell */
regs->pciea_to_arm_drbl_reg = base + 0x20400;
regs->arm_to_pciea_drbl_reg = base + 0x20408;
regs->arm_to_pciea_mask_reg = base + 0x2040C;
regs->pciea_to_arm_msg0 = base + 0x20430;
regs->pciea_to_arm_msg1 = base + 0x20434;
regs->arm_to_pciea_msg0 = base + 0x20438;
regs->arm_to_pciea_msg1 = base + 0x2043C;
/* For Message Unit */
regs->inb_aval_count_basel = base + 0x508;
regs->inb_aval_count_baseh = base + 0x50C;
regs->inb_write_pointer = base + 0x518;
regs->inb_read_pointer = base + 0x51C;
regs->outb_coal_cfg = base + 0x568;
regs->outb_copy_basel = base + 0x5B0;
regs->outb_copy_baseh = base + 0x5B4;
regs->outb_copy_pointer = base + 0x544;
regs->outb_read_pointer = base + 0x548;
regs->outb_isr_cause = base + 0x560;
regs->outb_coal_cfg = base + 0x568;
/* Bit setting for HW */
regs->int_comaout = 1 << 8;
regs->int_comaerr = 1 << 6;
regs->int_dl_cpu2pciea = 1 << 1;
regs->cl_pointer_toggle = 1 << 12;
regs->clic_irq = 1 << 1;
regs->clic_in_err = 1 << 8;
regs->clic_out_err = 1 << 12;
regs->cl_slot_num_mask = 0xFFF;
regs->int_drbl_int_mask = 0x3FFFFFFF;
regs->int_mu = regs->int_dl_cpu2pciea | regs->int_comaout |
regs->int_comaerr;
break;
case PCI_DEVICE_ID_MARVELL_MV9580:
mhba->mmio = mhba->base_addr[2];
base = mhba->mmio;
if (!mhba->regs) {
mhba->regs = kzalloc(sizeof(*regs), GFP_KERNEL);
if (mhba->regs == NULL)
return -ENOMEM;
}
regs = mhba->regs;
/* For Arm */
regs->ctrl_sts_reg = base + 0x20104;
regs->rstoutn_mask_reg = base + 0x1010C;
regs->sys_soft_rst_reg = base + 0x10108;
regs->main_int_cause_reg = base + 0x10200;
regs->enpointa_mask_reg = base + 0x1020C;
regs->rstoutn_en_reg = base + 0xF1400;
/* For Doorbell */
regs->pciea_to_arm_drbl_reg = base + 0x10460;
regs->arm_to_pciea_drbl_reg = base + 0x10480;
regs->arm_to_pciea_mask_reg = base + 0x10484;
regs->pciea_to_arm_msg0 = base + 0x10400;
regs->pciea_to_arm_msg1 = base + 0x10404;
regs->arm_to_pciea_msg0 = base + 0x10420;
regs->arm_to_pciea_msg1 = base + 0x10424;
/* For reset*/
regs->reset_request = base + 0x10108;
regs->reset_enable = base + 0x1010c;
/* For Message Unit */
regs->inb_aval_count_basel = base + 0x4008;
regs->inb_aval_count_baseh = base + 0x400C;
regs->inb_write_pointer = base + 0x4018;
regs->inb_read_pointer = base + 0x401C;
regs->outb_copy_basel = base + 0x4058;
regs->outb_copy_baseh = base + 0x405C;
regs->outb_copy_pointer = base + 0x406C;
regs->outb_read_pointer = base + 0x4070;
regs->outb_coal_cfg = base + 0x4080;
regs->outb_isr_cause = base + 0x4088;
/* Bit setting for HW */
regs->int_comaout = 1 << 4;
regs->int_dl_cpu2pciea = 1 << 12;
regs->int_comaerr = 1 << 29;
regs->cl_pointer_toggle = 1 << 14;
regs->cl_slot_num_mask = 0x3FFF;
regs->clic_irq = 1 << 0;
regs->clic_out_err = 1 << 1;
regs->int_drbl_int_mask = 0x3FFFFFFF;
regs->int_mu = regs->int_dl_cpu2pciea | regs->int_comaout;
break;
default:
return -1;
break;
}
return 0;
}
/**
* mvumi_init_fw - Initializes the FW
* @mhba: Adapter soft state
*
* This is the main function for initializing firmware.
*/
static int mvumi_init_fw(struct mvumi_hba *mhba)
{
int ret = 0;
if (pci_request_regions(mhba->pdev, MV_DRIVER_NAME)) {
dev_err(&mhba->pdev->dev, "IO memory region busy!\n");
return -EBUSY;
}
ret = mvumi_map_pci_addr(mhba->pdev, mhba->base_addr);
if (ret)
goto fail_ioremap;
switch (mhba->pdev->device) {
case PCI_DEVICE_ID_MARVELL_MV9143:
mhba->instancet = &mvumi_instance_9143;
mhba->io_seq = 0;
mhba->max_sge = MVUMI_MAX_SG_ENTRY;
mhba->request_id_enabled = 1;
break;
case PCI_DEVICE_ID_MARVELL_MV9580:
mhba->instancet = &mvumi_instance_9580;
mhba->io_seq = 0;
mhba->max_sge = MVUMI_MAX_SG_ENTRY;
break;
default:
dev_err(&mhba->pdev->dev, "device 0x%x not supported!\n",
mhba->pdev->device);
mhba->instancet = NULL;
ret = -EINVAL;
goto fail_alloc_mem;
}
dev_dbg(&mhba->pdev->dev, "device id : %04X is found.\n",
mhba->pdev->device);
ret = mvumi_cfg_hw_reg(mhba);
if (ret) {
dev_err(&mhba->pdev->dev,
"failed to allocate memory for reg\n");
ret = -ENOMEM;
goto fail_alloc_mem;
}
mhba->handshake_page = dma_alloc_coherent(&mhba->pdev->dev,
HSP_MAX_SIZE, &mhba->handshake_page_phys, GFP_KERNEL);
if (!mhba->handshake_page) {
dev_err(&mhba->pdev->dev,
"failed to allocate memory for handshake\n");
ret = -ENOMEM;
goto fail_alloc_page;
}
if (mvumi_start(mhba)) {
ret = -EINVAL;
goto fail_ready_state;
}
ret = mvumi_alloc_cmds(mhba);
if (ret)
goto fail_ready_state;
return 0;
fail_ready_state:
mvumi_release_mem_resource(mhba);
dma_free_coherent(&mhba->pdev->dev, HSP_MAX_SIZE,
mhba->handshake_page, mhba->handshake_page_phys);
fail_alloc_page:
kfree(mhba->regs);
fail_alloc_mem:
mvumi_unmap_pci_addr(mhba->pdev, mhba->base_addr);
fail_ioremap:
pci_release_regions(mhba->pdev);
return ret;
}
/**
* mvumi_io_attach - Attaches this driver to SCSI mid-layer
* @mhba: Adapter soft state
*/
static int mvumi_io_attach(struct mvumi_hba *mhba)
{
struct Scsi_Host *host = mhba->shost;
struct scsi_device *sdev = NULL;
int ret;
unsigned int max_sg = (mhba->ib_max_size + 4 -
sizeof(struct mvumi_msg_frame)) / sizeof(struct mvumi_sgl);
host->irq = mhba->pdev->irq;
host->unique_id = mhba->unique_id;
host->can_queue = (mhba->max_io - 1) ? (mhba->max_io - 1) : 1;
host->sg_tablesize = mhba->max_sge > max_sg ? max_sg : mhba->max_sge;
host->max_sectors = mhba->max_transfer_size / 512;
host->cmd_per_lun = (mhba->max_io - 1) ? (mhba->max_io - 1) : 1;
host->max_id = mhba->max_target_id;
host->max_cmd_len = MAX_COMMAND_SIZE;
ret = scsi_add_host(host, &mhba->pdev->dev);
if (ret) {
dev_err(&mhba->pdev->dev, "scsi_add_host failed\n");
return ret;
}
mhba->fw_flag |= MVUMI_FW_ATTACH;
mutex_lock(&mhba->sas_discovery_mutex);
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9580)
ret = scsi_add_device(host, 0, mhba->max_target_id - 1, 0);
else
ret = 0;
if (ret) {
dev_err(&mhba->pdev->dev, "add virtual device failed\n");
mutex_unlock(&mhba->sas_discovery_mutex);
goto fail_add_device;
}
mhba->dm_thread = kthread_create(mvumi_rescan_bus,
mhba, "mvumi_scanthread");
if (IS_ERR(mhba->dm_thread)) {
dev_err(&mhba->pdev->dev,
"failed to create device scan thread\n");
mutex_unlock(&mhba->sas_discovery_mutex);
goto fail_create_thread;
}
atomic_set(&mhba->pnp_count, 1);
wake_up_process(mhba->dm_thread);
mutex_unlock(&mhba->sas_discovery_mutex);
return 0;
fail_create_thread:
if (mhba->pdev->device == PCI_DEVICE_ID_MARVELL_MV9580)
sdev = scsi_device_lookup(mhba->shost, 0,
mhba->max_target_id - 1, 0);
if (sdev) {
scsi_remove_device(sdev);
scsi_device_put(sdev);
}
fail_add_device:
scsi_remove_host(mhba->shost);
return ret;
}
/**
* mvumi_probe_one - PCI hotplug entry point
* @pdev: PCI device structure
* @id: PCI ids of supported hotplugged adapter
*/
static int mvumi_probe_one(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct Scsi_Host *host;
struct mvumi_hba *mhba;
int ret;
dev_dbg(&pdev->dev, " %#4.04x:%#4.04x:%#4.04x:%#4.04x: ",
pdev->vendor, pdev->device, pdev->subsystem_vendor,
pdev->subsystem_device);
ret = pci_enable_device(pdev);
if (ret)
return ret;
ret = mvumi_pci_set_master(pdev);
if (ret)
goto fail_set_dma_mask;
host = scsi_host_alloc(&mvumi_template, sizeof(*mhba));
if (!host) {
dev_err(&pdev->dev, "scsi_host_alloc failed\n");
ret = -ENOMEM;
goto fail_alloc_instance;
}
mhba = shost_priv(host);
INIT_LIST_HEAD(&mhba->cmd_pool);
INIT_LIST_HEAD(&mhba->ob_data_list);
INIT_LIST_HEAD(&mhba->free_ob_list);
INIT_LIST_HEAD(&mhba->res_list);
INIT_LIST_HEAD(&mhba->waiting_req_list);
mutex_init(&mhba->device_lock);
INIT_LIST_HEAD(&mhba->mhba_dev_list);
INIT_LIST_HEAD(&mhba->shost_dev_list);
atomic_set(&mhba->fw_outstanding, 0);
init_waitqueue_head(&mhba->int_cmd_wait_q);
mutex_init(&mhba->sas_discovery_mutex);
mhba->pdev = pdev;
mhba->shost = host;
mhba->unique_id = pdev->bus->number << 8 | pdev->devfn;
ret = mvumi_init_fw(mhba);
if (ret)
goto fail_init_fw;
ret = request_irq(mhba->pdev->irq, mvumi_isr_handler, IRQF_SHARED,
"mvumi", mhba);
if (ret) {
dev_err(&pdev->dev, "failed to register IRQ\n");
goto fail_init_irq;
}
mhba->instancet->enable_intr(mhba);
pci_set_drvdata(pdev, mhba);
ret = mvumi_io_attach(mhba);
if (ret)
goto fail_io_attach;
mvumi_backup_bar_addr(mhba);
dev_dbg(&pdev->dev, "probe mvumi driver successfully.\n");
return 0;
fail_io_attach:
mhba->instancet->disable_intr(mhba);
free_irq(mhba->pdev->irq, mhba);
fail_init_irq:
mvumi_release_fw(mhba);
fail_init_fw:
scsi_host_put(host);
fail_alloc_instance:
fail_set_dma_mask:
pci_disable_device(pdev);
return ret;
}
static void mvumi_detach_one(struct pci_dev *pdev)
{
struct Scsi_Host *host;
struct mvumi_hba *mhba;
mhba = pci_get_drvdata(pdev);
if (mhba->dm_thread) {
kthread_stop(mhba->dm_thread);
mhba->dm_thread = NULL;
}
mvumi_detach_devices(mhba);
host = mhba->shost;
scsi_remove_host(mhba->shost);
mvumi_flush_cache(mhba);
mhba->instancet->disable_intr(mhba);
free_irq(mhba->pdev->irq, mhba);
mvumi_release_fw(mhba);
scsi_host_put(host);
pci_disable_device(pdev);
dev_dbg(&pdev->dev, "driver is removed!\n");
}
/**
* mvumi_shutdown - Shutdown entry point
* @device: Generic device structure
*/
static void mvumi_shutdown(struct pci_dev *pdev)
{
struct mvumi_hba *mhba = pci_get_drvdata(pdev);
mvumi_flush_cache(mhba);
}
static int __maybe_unused mvumi_suspend(struct pci_dev *pdev, pm_message_t state)
{
struct mvumi_hba *mhba = NULL;
mhba = pci_get_drvdata(pdev);
mvumi_flush_cache(mhba);
pci_set_drvdata(pdev, mhba);
mhba->instancet->disable_intr(mhba);
free_irq(mhba->pdev->irq, mhba);
mvumi_unmap_pci_addr(pdev, mhba->base_addr);
pci_release_regions(pdev);
pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, pci_choose_state(pdev, state));
return 0;
}
static int __maybe_unused mvumi_resume(struct pci_dev *pdev)
{
int ret;
struct mvumi_hba *mhba = NULL;
mhba = pci_get_drvdata(pdev);
pci_set_power_state(pdev, PCI_D0);
pci_enable_wake(pdev, PCI_D0, 0);
pci_restore_state(pdev);
ret = pci_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "enable device failed\n");
return ret;
}
ret = mvumi_pci_set_master(pdev);
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
if (ret)
goto fail;
ret = pci_request_regions(mhba->pdev, MV_DRIVER_NAME);
if (ret)
goto fail;
ret = mvumi_map_pci_addr(mhba->pdev, mhba->base_addr);
if (ret)
goto release_regions;
if (mvumi_cfg_hw_reg(mhba)) {
ret = -EINVAL;
goto unmap_pci_addr;
}
mhba->mmio = mhba->base_addr[0];
mvumi_reset(mhba);
if (mvumi_start(mhba)) {
ret = -EINVAL;
goto unmap_pci_addr;
}
ret = request_irq(mhba->pdev->irq, mvumi_isr_handler, IRQF_SHARED,
"mvumi", mhba);
if (ret) {
dev_err(&pdev->dev, "failed to register IRQ\n");
goto unmap_pci_addr;
}
mhba->instancet->enable_intr(mhba);
return 0;
unmap_pci_addr:
mvumi_unmap_pci_addr(pdev, mhba->base_addr);
release_regions:
pci_release_regions(pdev);
fail:
pci_disable_device(pdev);
return ret;
}
static struct pci_driver mvumi_pci_driver = {
.name = MV_DRIVER_NAME,
.id_table = mvumi_pci_table,
.probe = mvumi_probe_one,
.remove = mvumi_detach_one,
.shutdown = mvumi_shutdown,
#ifdef CONFIG_PM
.suspend = mvumi_suspend,
.resume = mvumi_resume,
#endif
};
module_pci_driver(mvumi_pci_driver);