/* cxgb3i_iscsi.c: Chelsio S3xx iSCSI driver.
*
* Copyright (c) 2008 Chelsio Communications, Inc.
* Copyright (c) 2008 Mike Christie
* Copyright (c) 2008 Red Hat, Inc. All rights reserved.
*
* 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.
*
* Written by: Karen Xie (kxie@chelsio.com)
*/
#include <linux/inet.h>
#include <linux/slab.h>
#include <linux/crypto.h>
#include <linux/if_vlan.h>
#include <net/dst.h>
#include <net/tcp.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_eh.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi.h>
#include <scsi/iscsi_proto.h>
#include <scsi/libiscsi.h>
#include <scsi/scsi_transport_iscsi.h>
#include "cxgb3i.h"
#include "cxgb3i_pdu.h"
#ifdef __DEBUG_CXGB3I_TAG__
#define cxgb3i_tag_debug cxgb3i_log_debug
#else
#define cxgb3i_tag_debug(fmt...)
#endif
#ifdef __DEBUG_CXGB3I_API__
#define cxgb3i_api_debug cxgb3i_log_debug
#else
#define cxgb3i_api_debug(fmt...)
#endif
/*
* align pdu size to multiple of 512 for better performance
*/
#define align_pdu_size(n) do { n = (n) & (~511); } while (0)
static struct scsi_transport_template *cxgb3i_scsi_transport;
static struct scsi_host_template cxgb3i_host_template;
static struct iscsi_transport cxgb3i_iscsi_transport;
static unsigned char sw_tag_idx_bits;
static unsigned char sw_tag_age_bits;
static LIST_HEAD(cxgb3i_snic_list);
static DEFINE_RWLOCK(cxgb3i_snic_rwlock);
/**
* cxgb3i_adpater_find_by_tdev - find the cxgb3i_adapter structure via t3cdev
* @tdev: t3cdev pointer
*/
struct cxgb3i_adapter *cxgb3i_adapter_find_by_tdev(struct t3cdev *tdev)
{
struct cxgb3i_adapter *snic;
read_lock(&cxgb3i_snic_rwlock);
list_for_each_entry(snic, &cxgb3i_snic_list, list_head) {
if (snic->tdev == tdev) {
read_unlock(&cxgb3i_snic_rwlock);
return snic;
}
}
read_unlock(&cxgb3i_snic_rwlock);
return NULL;
}
static inline int adapter_update(struct cxgb3i_adapter *snic)
{
cxgb3i_log_info("snic 0x%p, t3dev 0x%p, updating.\n",
snic, snic->tdev);
return cxgb3i_adapter_ddp_info(snic->tdev, &snic->tag_format,
&snic->tx_max_size,
&snic->rx_max_size);
}
static int adapter_add(struct cxgb3i_adapter *snic)
{
struct t3cdev *t3dev = snic->tdev;
struct adapter *adapter = tdev2adap(t3dev);
int i, err;
snic->pdev = adapter->pdev;
snic->tag_format.sw_bits = sw_tag_idx_bits + sw_tag_age_bits;
err = cxgb3i_adapter_ddp_info(t3dev, &snic->tag_format,
&snic->tx_max_size,
&snic->rx_max_size);
if (err < 0)
return err;
for_each_port(adapter, i) {
snic->hba[i] = cxgb3i_hba_host_add(snic, adapter->port[i]);
if (!snic->hba[i])
return -EINVAL;
}
snic->hba_cnt = adapter->params.nports;
/* add to the list */
write_lock(&cxgb3i_snic_rwlock);
list_add_tail(&snic->list_head, &cxgb3i_snic_list);
write_unlock(&cxgb3i_snic_rwlock);
cxgb3i_log_info("t3dev 0x%p open, snic 0x%p, %u scsi hosts added.\n",
t3dev, snic, snic->hba_cnt);
return 0;
}
/**
* cxgb3i_adapter_open - init a s3 adapter structure and any h/w settings
* @t3dev: t3cdev adapter
*/
void cxgb3i_adapter_open(struct t3cdev *t3dev)
{
struct cxgb3i_adapter *snic = cxgb3i_adapter_find_by_tdev(t3dev);
int err;
if (snic)
err = adapter_update(snic);
else {
snic = kzalloc(sizeof(*snic), GFP_KERNEL);
if (snic) {
spin_lock_init(&snic->lock);
snic->tdev = t3dev;
err = adapter_add(snic);
} else
err = -ENOMEM;
}
if (err < 0) {
cxgb3i_log_info("snic 0x%p, f 0x%x, t3dev 0x%p open, err %d.\n",
snic, snic ? snic->flags : 0, t3dev, err);
if (snic) {
snic->flags &= ~CXGB3I_ADAPTER_FLAG_RESET;
cxgb3i_adapter_close(t3dev);
}
}
}
/**
* cxgb3i_adapter_close - release the resources held and cleanup h/w settings
* @t3dev: t3cdev adapter
*/
void cxgb3i_adapter_close(struct t3cdev *t3dev)
{
struct cxgb3i_adapter *snic = cxgb3i_adapter_find_by_tdev(t3dev);
int i;
if (!snic || snic->flags & CXGB3I_ADAPTER_FLAG_RESET) {
cxgb3i_log_info("t3dev 0x%p close, snic 0x%p, f 0x%x.\n",
t3dev, snic, snic ? snic->flags : 0);