#! /bin/sh -e ## ## All lines beginning with `## DP:' are a description of the patch. ## DP: Description: Enables IBM eServer i/pSeries Virtual SCSI Target Driver ## DP: Description: Needed for i/pSeries with logical partitions (LPAR). ## DP: Patch author: Dave Boutcher (boutcher@us.ibm.com) ## DP: Upstream status: unknown, sent to me by Cajus Pollmeier. diff -aurN a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig --- a/drivers/scsi/Kconfig 2005-06-17 15:48:29.000000000 -0400 +++ b/drivers/scsi/Kconfig 2005-06-18 12:02:58.000000000 -0400 @@ -813,6 +813,14 @@ To compile this driver as a module, choose M here: the module will be called ibmvscsic. +config SCSI_IBMVSCSIS + tristate "IBM Virtual SCSI Server support" + depends on PPC_PSERIES + help + This is the IBM Virtual SCSI Server + To compile this driver as a module, choose M here: the + module will be called ibmvscsis. + config SCSI_INITIO tristate "Initio 9100U(W) support" depends on PCI && SCSI diff -aurN a/drivers/scsi/ibmvscsi/Makefile b/drivers/scsi/ibmvscsi/Makefile --- a/drivers/scsi/ibmvscsi/Makefile 2005-06-17 15:48:29.000000000 -0400 +++ b/drivers/scsi/ibmvscsi/Makefile 2005-06-18 12:02:58.000000000 -0400 @@ -3,3 +3,5 @@ ibmvscsic-y += ibmvscsi.o ibmvscsic-$(CONFIG_PPC_ISERIES) += iseries_vscsi.o ibmvscsic-$(CONFIG_PPC_PSERIES) += rpa_vscsi.o + +obj-$(CONFIG_SCSI_IBMVSCSIS) += ibmvscsis.o diff -aurN a/drivers/scsi/ibmvscsi/ibmvscsis.c b/drivers/scsi/ibmvscsi/ibmvscsis.c --- a/drivers/scsi/ibmvscsi/ibmvscsis.c 1969-12-31 19:00:00.000000000 -0500 +++ b/drivers/scsi/ibmvscsi/ibmvscsis.c 2005-06-18 12:02:58.000000000 -0400 @@ -0,0 +1,2818 @@ +/**************************************************************************/ +/* -*- -linux- -*- */ +/* IBM eServer i/pSeries Virtual SCSI Target Driver */ +/* Copyright (C) 2003 Dave Boutcher (boutcher@us.ibm.com) IBM Corp. */ +/* */ +/* 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. */ +/* */ +/* 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 */ +/* */ +/* This module contains the eServer virtual SCSI target code. The driver */ +/* takes SRP requests from the virtual SCSI client (the linux version is */ +/* int ibmvscsi.c, but there can be other clients, like AIX or OF) and */ +/* passes them on to real devices in this system. */ +/* */ +/* The basic hierarchy (and somewhat the organization of this file) is */ +/* that SCSI CDBs are in SRPs are in CRQs. */ +/* */ +/**************************************************************************/ +/* + TODO: + - Support redirecting SRP SCSI requests to a real SCSI driver +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../scsi.h" +#include "viosrp.h" + +#define IBMVSCSIS_VERSION "1.2" + +MODULE_DESCRIPTION("IBM Virtual SCSI Target"); +MODULE_AUTHOR("Dave Boutcher"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(IBMVSCSIS_VERSION); + +static int ibmvscsis_debug = 0; + +/* These are fixed and come from the device tree...we + * just store them here to save getting them every time. + */ +static char system_id[64] = ""; +static char partition_name[97] = "UNKNOWN"; +static unsigned int partition_number = -1; + +/* + * Quick macro to enable/disable interrupts + * TODO: move to vio.h to be common with ibmvscsi.c + */ +#define h_vio_signal(ua, mode) \ + plpar_hcall_norets(H_VIO_SIGNAL, ua, mode) + +/* + * These are indexes into the following table, and have to match!!! + */ +#define SENSE_SUCCESS 0 +#define SENSE_ABORT 1 +#define SENSE_INVALID_ID 2 +#define SENSE_DEVICE_FAULT 3 +#define SENSE_DEVICE_BUSY 4 +#define SENSE_UNIT_OFFLINE 5 +#define SENSE_INVALID_CMD 6 +#define SENSE_INTERMEDIATE 7 +#define SENSE_WRITE_PROT 8 +#define SENSE_INVALID_FIELD 9 + +#define TARGET_MAX_NAME_LEN 128 + +static unsigned char ibmvscsis_sense_data[][3] = { +/* + * Sense key lookup table + * Format: SenseKey,AdditionalSenseCode,AdditionalSenseCodeQualifier + * Adapted from 3w-xxxx.h + */ + {0x00, 0x00, 0x00}, /* Success */ + {0x0b, 0x00, 0x00}, /* Aborted command */ + {0x0b, 0x14, 0x00}, /* ID not found */ + {0x04, 0x00, 0x00}, /* Device fault */ + {0x0b, 0x00, 0x00}, /* Device busy */ + {0x02, 0x04, 0x00}, /* Unit offline */ + {0x05, 0x20, 0x00}, /* Invalid Command */ + {0x10, 0x00, 0x00}, /* Intermediate */ + {0x07, 0x27, 0x00}, /* Write Protected */ + {0x05, 0x24, 0x00}, /* Invalid field */ +}; + +/* + * SCSI defined structure for inquiry data + * TODO: Seral number is currently messed up if you do + * scsiinfo. I'm not sure why and I think it comes out of + * here + */ +struct inquiry_data { + u8 qual_type; + u8 rmb_reserve; + u8 version; + u8 aerc_naca_hisup_format; + u8 addl_len; + u8 sccs_reserved; + u8 bque_encserv_vs_multip_mchngr_reserved; + u8 reladr_reserved_linked_cmdqueue_vs; + char vendor[8]; + char product[16]; + char revision[4]; + char vendor_specific[20]; + char reserved1[2]; + char version_descriptor[16]; + char reserved2[22]; + char unique[158]; +}; + +extern int vio_num_address_cells; + +/* + * an RPA command/response transport queue. This is our structure + * that points to the actual queue. feel free to modify this structure + * as needed + */ +struct crq_queue { + struct viosrp_crq *msgs; + int size, cur; + dma_addr_t msg_token; + spinlock_t lock; +}; + +/* + * This structure tracks our fundamental unit of work. Whenever + * an SRP Information Unit (IU) arrives, we track all the good stuff + * here + */ +struct iu_entry { + union viosrp_iu *iu; + struct server_adapter *adapter; + struct list_head next; + dma_addr_t iu_token; + int aborted; + struct { + dma_addr_t remote_token; + char *data_buffer; + dma_addr_t data_token; + long data_len; + struct vdev *vd; + char in_use:1; + char diunder:1; + char diover:1; + char dounder:1; + char doover:1; + char write:1; + char linked:1; + int data_out_residual_count; + int data_in_residual_count; + int ioerr; + } req; +}; + +/* + * a pool of ius for use + */ +struct iu_pool { + spinlock_t lock; + struct list_head iu_entries; + struct iu_entry *list; + union viosrp_iu *iu_storage; + dma_addr_t iu_token; + u32 size; +}; + +/* + * Represents a single device that someone told us about + * that we treat as a LUN + */ +struct vdev { + struct list_head list; + char type; /* 'B' for block, 'S' for SCSI */ + atomic_t refcount; + int disabled; + u64 lun; + struct kobject kobj; + struct { + char device_name[TARGET_MAX_NAME_LEN]; + struct block_device *bdev; + long blksize; + long lastlba; + int ro; + } b; +}; + +/* + * Represents a bus. target #'s in SCSI are 6 bits long, + * so you can have 64 targets per bus + */ +#define TARGETS_PER_BUS (64) +#define BUS_PER_ADAPTER (8) +struct vbus { + struct vdev *vdev[TARGETS_PER_BUS]; + atomic_t num_targets; + struct kobject kobj; + int bus_num; +}; + +/* + * Buffer cache + */ +struct dma_buffer { + dma_addr_t token; + char *addr; + size_t len; +}; +#define DMA_BUFFER_CACHE_SIZE (16) +#define DMA_BUFFER_INIT_COUNT (4) +#define DMA_BUFFER_INIT_LEN (PAGE_SIZE*16) + +/* all driver data associated with a host adapter */ +struct server_adapter { + struct device *dev; + struct vio_dev *dma_dev; + struct crq_queue queue; + struct work_struct crq_task; + struct tasklet_struct endio_tasklet; + struct iu_pool pool; + spinlock_t lock; + struct bio *bio_done; + struct bio *bio_donetail; + struct list_head inflight; + struct vbus *vbus[8]; + int nvdevs; + char name[32]; + unsigned long liobn; + unsigned long riobn; + + atomic_t num_buses; + struct kobject stats_kobj; + + /* This ugly expression allocates a bit array of + * in-use flags large enough for the number of buffers + */ + unsigned long dma_buffer_use[(DMA_BUFFER_CACHE_SIZE + + sizeof(unsigned long) - 1) + / sizeof(unsigned long)]; + struct dma_buffer dma_buffer[DMA_BUFFER_CACHE_SIZE]; + + /* Statistics only */ + atomic_t iu_count; /* number allocated */ + atomic_t bio_count; /* number allocated */ + atomic_t crq_processed; + atomic_t interrupts; + atomic_t read_processed; + atomic_t write_processed; + atomic_t buffers_allocated; + atomic_t errors; +}; + +/* + * Forward declarations + */ +static long send_rsp(struct iu_entry *iue, int status); + +/* + * The following are lifted from usb.h + */ +#define DEBUG 1 +#ifdef DEBUG +#define dbg(format, arg...) if (ibmvscsis_debug) printk(KERN_WARNING __FILE__ ": " format , ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif +#define err(format, arg...) printk(KERN_ERR "ibmvscsis: " format , ## arg) +#define info(format, arg...) printk(KERN_INFO "ibmvscsis: " format , ## arg) +#define warn(format, arg...) printk(KERN_WARNING "ibmvscsis: " format , ## arg) + +/* ============================================================== + * Utility Routines + * ============================================================== + */ +/* + * return an 8 byte lun given a bus, target, lun. + * Today this only supports single level luns. Should we add a level or a + * 64 bit LUN as input to support multi-level luns? + */ +u64 make_lun(unsigned int bus, unsigned int target, unsigned int lun) +{ + u16 result = (0x8000 | + ((target & 0x003f) << 8) | + ((bus & 0x0007) << 5) | (lun & 0x001f)); + return ((u64) result) << 48; +} + +/* + * Given an 8 byte LUN, return the first level bus/target/lun. + * Today this doesn't support multi-level LUNs + */ +#define GETBUS(x) ((int)((((u64)(x)) >> 53) & 0x0007)) +#define GETTARGET(x) ((int)((((u64)(x)) >> 56) & 0x003f)) +#define GETLUN(x) ((int)((((u64)(x)) >> 48) & 0x001f)) + +static u8 getcontrolbyte(u8 * cdb) +{ + return cdb[COMMAND_SIZE(cdb[0]) - 1]; +} + +static u8 getlink(struct iu_entry *iue) +{ + return (getcontrolbyte(iue->iu->srp.cmd.cdb) & 0x01); +} + +/* + * Given an SRP, figure out the data in length + */ +static int did_len(struct srp_cmd *cmd) +{ + struct memory_descriptor *md; + struct indirect_descriptor *id; + int offset = cmd->additional_cdb_len * 4; + + switch (cmd->data_out_format) { + case SRP_NO_BUFFER: + offset += 0; + break; + case SRP_DIRECT_BUFFER: + offset += sizeof(struct memory_descriptor); + break; + case SRP_INDIRECT_BUFFER: + offset += sizeof(struct indirect_descriptor) + + + ((cmd->data_out_count - + 1) * sizeof(struct memory_descriptor)); + break; + default: + err("client error. Invalid data_out_format %d\n", + cmd->data_out_format); + return 0; + } + + switch (cmd->data_in_format) { + case SRP_NO_BUFFER: + return 0; + case SRP_DIRECT_BUFFER: + md = (struct memory_descriptor *)(cmd->additional_data + + offset); + return md->length; + case SRP_INDIRECT_BUFFER: + id = (struct indirect_descriptor *)(cmd->additional_data + + offset); + return id->total_length; + default: + err("client error. Invalid data_in_format %d\n", + cmd->data_in_format); + return 0; + } +} + +/* + * We keep a pool of IUs, this routine builds the pool. The pool is + * per-adapter. The size of the pool is negotiated as part of the SRP + * login, where we negotiate the number of requests (IUs) the client + * can send us. This routine is not synchronized. + */ +static int initialize_iu_pool(struct server_adapter *adapter, int size) +{ + struct iu_pool *pool = &adapter->pool; + int i; + + pool->size = size; + pool->lock = SPIN_LOCK_UNLOCKED; + INIT_LIST_HEAD(&pool->iu_entries); + + pool->list = kmalloc(pool->size * sizeof(*pool->list), GFP_KERNEL); + if (!pool->list) { + err("Error: Cannot allocate memory for IU list\n"); + return -ENOMEM; + } + memset(pool->list, 0x00, pool->size * sizeof(*pool->list)); + + pool->iu_storage = + dma_alloc_coherent(adapter->dev, + pool->size * sizeof(*pool->iu_storage), + &pool->iu_token, 0); + if (!pool->iu_storage) { + err("Error: Cannot allocate memory for IU pool\n"); + kfree(pool->list); + return -ENOMEM; + } + + for (i = 0; i < pool->size; ++i) { + pool->list[i].iu = pool->iu_storage + i; + pool->list[i].iu_token = + pool->iu_token + sizeof(*pool->iu_storage) * i; + pool->list[i].adapter = adapter; + list_add_tail(&pool->list[i].next, &pool->iu_entries); + } + + return 0; +} + +/* + * Free the pool we allocated in initialize_iu_pool + */ +static void release_iu_pool(struct server_adapter *adapter) +{ + struct iu_pool *pool = &adapter->pool; + int i, in_use = 0; + for (i = 0; i < pool->size; ++i) + if (pool->list[i].req.in_use) + ++in_use; + if (in_use) + err("Releasing event pool with %d events still in use?\n", + in_use); + kfree(pool->list); + dma_free_coherent(adapter->dev, pool->size * sizeof(*pool->iu_storage), + pool->iu_storage, pool->iu_token); +} + +/* + * Get an IU from the pool. Return NULL of the pool is empty. This + * routine is syncronized by a lock. The routine sets all the important + * fields to 0 + */ +static struct iu_entry *get_iu(struct server_adapter *adapter) +{ + struct iu_entry *e; + unsigned long flags; + + spin_lock_irqsave(&adapter->pool.lock, flags); + if (!list_empty(&adapter->pool.iu_entries)) { + e = list_entry(adapter->pool.iu_entries.next, struct iu_entry, + next); + list_del(adapter->pool.iu_entries.next); + + if (e->req.in_use) { + err("Found in-use iue in free pool!"); + } + + memset(&e->req, 0x00, sizeof(e->req)); + + e->req.in_use = 1; + } else { + e = NULL; + } + + spin_unlock_irqrestore(&adapter->pool.lock, flags); + atomic_inc(&adapter->iu_count); + return e; +} + +/* + * Return an IU to the pool. This routine is synchronized + */ +static void free_iu(struct iu_entry *iue) +{ + unsigned long flags; + if (iue->req.vd) { + atomic_dec(&iue->req.vd->refcount); + } + + spin_lock_irqsave(&iue->adapter->pool.lock, flags); + if (iue->req.in_use == 0) { + warn("Internal error, freeing iue twice!\n"); + } else { + iue->req.in_use = 0; + list_add_tail(&iue->next, &iue->adapter->pool.iu_entries); + } + spin_unlock_irqrestore(&iue->adapter->pool.lock, flags); + atomic_dec(&iue->adapter->iu_count); +} + +/* + * Get a CRQ from the inter-partition queue. + */ +static struct viosrp_crq *crq_queue_next_crq(struct crq_queue *queue) +{ + struct viosrp_crq *crq; + unsigned long flags; + + spin_lock_irqsave(&queue->lock, flags); + crq = &queue->msgs[queue->cur]; + if (crq->valid & 0x80) { + if (++queue->cur == queue->size) + queue->cur = 0; + } else + crq = NULL; + spin_unlock_irqrestore(&queue->lock, flags); + + return crq; +} + +/* + * Make the RDMA hypervisor call. There should be a better way to do this + * than inline assembler. + * TODO: Fix the inline assembler + */ +static long h_copy_rdma(long length, + unsigned long sliobn, unsigned long slioba, + unsigned long dliobn, unsigned long dlioba) +{ + long lpar_rc = 0; + __asm__ __volatile__(" li 3,0x110 \n\t" + " mr 4, %1 \n\t" + " mr 5, %2 \n\t" + " mr 6, %3 \n\t" + " mr 7, %4 \n\t" + " mr 8, %5 \n\t" + " .long 0x44000022 \n\t" + " mr %0, 3 \n\t":"=&r"(lpar_rc) + :"r"(length), "r"(sliobn), "r"(slioba), + "r"(dliobn), "r"(dlioba) + :"r0", "r3", "r4", "r5", "r6", "r7", "r8", "cr0", + "cr1", "ctr", "xer", "memory"); + return lpar_rc; +} + +/* + * Send an SRP to another partition using the CRQ. + */ +static int send_srp(struct iu_entry *iue, u64 length) +{ + long rc, rc1; + union { + struct viosrp_crq cooked; + u64 raw[2]; + } crq; + + /* First copy the SRP */ + rc = h_copy_rdma(length, + iue->adapter->liobn, + iue->iu_token, + iue->adapter->riobn, iue->req.remote_token); + + if (rc) { + err("Error %ld transferring data to client\n", rc); + } + + crq.cooked.valid = 0x80; + crq.cooked.format = VIOSRP_SRP_FORMAT; + crq.cooked.reserved = 0x00; + crq.cooked.timeout = 0x00; + crq.cooked.IU_length = length; + crq.cooked.IU_data_ptr = iue->iu->srp.generic.tag; + + if (rc == 0) { + crq.cooked.status = 0x99; /* TODO: is this right? */ + } else { + crq.cooked.status = 0x00; + } + + rc1 = + plpar_hcall_norets(H_SEND_CRQ, iue->adapter->dma_dev->unit_address, + crq.raw[0], crq.raw[1]); + + if (rc1) { + err("Error %ld sending response to client\n", rc1); + return rc1; + } + + return rc; +} + +/* + * Send data to a single SRP memory descriptor + * Returns amount of data sent, or negative value on error + */ +static long send_md_data(dma_addr_t stoken, int len, + struct memory_descriptor *md, + struct server_adapter *adapter) +{ + int tosend; + long rc; + + if (len < md->length) + tosend = len; + else + tosend = md->length; + + rc = h_copy_rdma(tosend, + adapter->liobn, + stoken, adapter->riobn, md->virtual_address); + + if (rc != H_Success) { + err(" Error %ld transferring data to client\n", rc); + return -1; + } + + return tosend; +} + +/* + * Send data to the SRP data_in buffers + * Returns amount of data sent, or negative value on error + */ +static long send_cmd_data(dma_addr_t stoken, int len, struct iu_entry *iue) +{ + struct srp_cmd *cmd = &iue->iu->srp.cmd; + struct memory_descriptor *md; + struct indirect_descriptor *id; + int offset = 0; + int total_length = 0; + int i; + int thislen; + int bytes; + int sentlen = 0; + + offset = cmd->additional_cdb_len * 4; + + switch (cmd->data_out_format) { + case SRP_NO_BUFFER: + offset += 0; + break; + case SRP_DIRECT_BUFFER: + offset += sizeof(struct memory_descriptor); + break; + case SRP_INDIRECT_BUFFER: + offset += sizeof(struct indirect_descriptor) + + + ((cmd->data_out_count - + 1) * sizeof(struct memory_descriptor)); + break; + default: + err("client error: Invalid data_out_format %d\n", + cmd->data_out_format); + return 0; + } + + switch (cmd->data_in_format) { + case SRP_NO_BUFFER: + return 0; + case SRP_DIRECT_BUFFER: + md = (struct memory_descriptor *)(cmd->additional_data + + offset); + sentlen = send_md_data(stoken, len, md, iue->adapter); + len -= sentlen; + if (len) { + iue->req.diover = 1; + iue->req.data_in_residual_count = len; + } + return sentlen; + } + + if (cmd->data_in_format != SRP_INDIRECT_BUFFER) { + err("client error Invalid data_in_format %d\n", + cmd->data_in_format); + return 0; + } + + id = (struct indirect_descriptor *)(cmd->additional_data + offset); + + total_length = id->total_length; + + /* Work through the partial memory descriptor list */ + for (i = 0; ((i < cmd->data_in_count) && (len)); i++) { + if (len > id->list[i].length) { + thislen = id->list[i].length; + } else { + thislen = len; + } + + bytes = + send_md_data(stoken + sentlen, thislen, id->list + i, + iue->adapter); + if (bytes < 0) + return bytes; + + if (bytes != thislen) { + warn("Error: Tried to send %d, sent %d\n", thislen, + bytes); + } + + sentlen += bytes; + total_length -= bytes; + len -= bytes; + } + + if (len) { + iue->req.diover = 1; + iue->req.data_in_residual_count = len; + } + + return sentlen; +} + +/* + * Get data from the other partition from a single SRP memory descriptor + * Returns amount of data sent, or negative value on error + */ +static long get_md_data(dma_addr_t ttoken, int len, + struct memory_descriptor *md, + struct server_adapter *adapter) +{ + int toget; + long rc; + + if (len < md->length) + toget = len; + else + toget = md->length; + + rc = h_copy_rdma(toget, + adapter->riobn, + md->virtual_address, adapter->liobn, ttoken); + + if (rc != H_Success) { + err("Error %ld transferring data to client\n", rc); + return -1; + } + + return toget; +} + +/* + * Get data from an SRP data in area. + * Returns amount of data sent, or negative value on error + */ +static long get_cmd_data(dma_addr_t stoken, int len, struct iu_entry *iue) +{ + struct srp_cmd *cmd = &iue->iu->srp.cmd; + struct memory_descriptor *md; + struct indirect_descriptor *id; + int offset = 0; + int total_length = 0; + int i; + int thislen; + int bytes; + int sentlen = 0; + + offset = cmd->additional_cdb_len * 4; + + switch (cmd->data_out_format) { + case SRP_NO_BUFFER: + return 0; + break; + case SRP_DIRECT_BUFFER: + md = (struct memory_descriptor *)(cmd->additional_data + + offset); + return get_md_data(stoken, len, md, iue->adapter); + break; + } + + if (cmd->data_out_format != SRP_INDIRECT_BUFFER) { + err("client error: Invalid data_out_format %d\n", + cmd->data_out_format); + return 0; + } + + id = (struct indirect_descriptor *)(cmd->additional_data + offset); + + total_length = id->total_length; + + /* Work through the partial memory descriptor list */ + for (i = 0; ((i < cmd->data_out_count) && (len)); i++) { + if (len > id->list[i].length) { + thislen = id->list[i].length; + } else { + thislen = len; + } + + bytes = + get_md_data(stoken + sentlen, thislen, id->list + i, + iue->adapter); + if (bytes < 0) + return bytes; + + if (bytes != thislen) { + err("Partial data sent to client (%d/%d)\n", bytes, thislen); + } + + sentlen += bytes; + total_length -= bytes; + len -= bytes; + } + + return sentlen; +} + +/* + * Get some data buffers to start. This doesn't lock the adapter structure! + */ +static void init_data_buffer(struct server_adapter *adapter) +{ + int i; + + for (i = 0; i < DMA_BUFFER_INIT_COUNT; i++) { + if (adapter->dma_buffer[i].addr == NULL) { + adapter->dma_buffer[i].addr = (char *) + dma_alloc_coherent(adapter->dev, + DMA_BUFFER_INIT_LEN, + &adapter->dma_buffer[i].token, + 0); + adapter->dma_buffer[i].len = DMA_BUFFER_INIT_LEN; + dbg("data buf %p token %8.8x, len %ld\n", + adapter->dma_buffer[i].addr, + adapter->dma_buffer[i].token, + adapter->dma_buffer[i].len); + atomic_inc(&adapter->buffers_allocated); + } + } + + return; +} + +/* + * Get a memory buffer that includes a mapped TCE. + */ +static void get_data_buffer(char **buffer, dma_addr_t * data_token, size_t len, + struct server_adapter *adapter) +{ + int i; + + for (i = 0; i < DMA_BUFFER_CACHE_SIZE; i++) { + if ((adapter->dma_buffer[i].addr) && + (adapter->dma_buffer[i].len >= len) && + (!test_and_set_bit(i, adapter->dma_buffer_use))) { + *buffer = adapter->dma_buffer[i].addr; + *data_token = adapter->dma_buffer[i].token; + return; + } + } + + /* Couldn't get a buffer! Try and get a new one */ + *buffer = (char *)dma_alloc_coherent(adapter->dev, len, data_token, 0); + atomic_inc(&adapter->buffers_allocated); + dbg("get: %p, %8.8x, %ld\n", *buffer, *data_token, len); + return; +} + +/* + * Free a memory buffer that includes a mapped TCE. + */ +static void free_data_buffer(char *buffer, dma_addr_t data_token, size_t len, + struct server_adapter *adapter) +{ + int i; + + /* First see if this buffer is already in the cache */ + for (i = 0; i < DMA_BUFFER_CACHE_SIZE; i++) { + if (adapter->dma_buffer[i].addr == buffer) { + if (adapter->dma_buffer[i].token != data_token) { + err("Inconsistent data buffer pool info!\n"); + } + if (!test_and_clear_bit(i, adapter->dma_buffer_use)) { + err("Freeing data buffer twice!\n"); + } + return; + } + } + + /* See if there is an empty slot in our list */ + for (i = 0; i < DMA_BUFFER_CACHE_SIZE; i++) { + if (!test_and_set_bit(i, adapter->dma_buffer_use)) { + if (adapter->dma_buffer[i].addr == NULL) { + adapter->dma_buffer[i].addr = buffer; + adapter->dma_buffer[i].token = data_token; + adapter->dma_buffer[i].len = len; + clear_bit(i, adapter->dma_buffer_use); + return; + } else { + clear_bit(i, adapter->dma_buffer_use); + } + } + } + + /* Now see if there is a smaller buffer we should throw out */ + for (i = 0; i < DMA_BUFFER_CACHE_SIZE; i++) { + if (!test_and_set_bit(i, adapter->dma_buffer_use)) { + if (adapter->dma_buffer[i].len < len) { + dbg("fre1: %p, %8.8x, %ld\n", + adapter->dma_buffer[i].addr, + adapter->dma_buffer[i].token, + adapter->dma_buffer[i].len); + + dma_free_coherent(adapter->dev, + adapter->dma_buffer[i].len, + adapter->dma_buffer[i].addr, + adapter->dma_buffer[i].token); + + atomic_dec(&adapter->buffers_allocated); + + adapter->dma_buffer[i].addr = buffer; + adapter->dma_buffer[i].token = data_token; + adapter->dma_buffer[i].len = len; + clear_bit(i, adapter->dma_buffer_use); + return; + } else { + clear_bit(i, adapter->dma_buffer_use); + } + } + } + + /* No space to cache this. Give it back to the kernel */ + dbg("fre2: %p, %8.8x, %ld\n", buffer, data_token, len); + dma_free_coherent(adapter->dev, len, buffer, data_token); + atomic_dec(&adapter->buffers_allocated); +} + +/* + * Release all the data buffers + */ +static void release_data_buffer(struct server_adapter *adapter) +{ + int i; + int free_in_use = 0; + + for (i = 0; i < DMA_BUFFER_INIT_COUNT; i++) { + if (adapter->dma_buffer[i].addr != NULL) { + if (test_bit(i, adapter->dma_buffer_use)) { + free_in_use++; + } + dma_free_coherent(adapter->dev, + adapter->dma_buffer[i].len, + adapter->dma_buffer[i].addr, + adapter->dma_buffer[i].token); + + atomic_dec(&adapter->buffers_allocated); + } + } + + if (free_in_use) { + err("Freeing %d in-use data buffers\n", free_in_use); + } + return; +} + +/* + * the routine that gets called on end_io of our bios. We basically + * schedule the processing to be done in our task, since we don't want + * do things like RDMA in someone else's interrupt handler + * + * Each iu request may result in multiple bio requests. only proceed + * when all the bio requests have done. + */ +static int ibmvscsis_end_io(struct bio *bio, unsigned int nbytes, int error) +{ + struct iu_entry *iue = (struct iu_entry *)bio->bi_private; + struct server_adapter *adapter = iue->adapter; + unsigned long flags; + + if (bio->bi_size) + return 1; + + if (!test_bit(BIO_UPTODATE, &bio->bi_flags)) { + iue->req.ioerr = 1; + }; + + /* Add the bio to the done queue */ + spin_lock_irqsave(&adapter->lock, flags); + if (adapter->bio_donetail) { + adapter->bio_donetail->bi_next = bio; + adapter->bio_donetail = bio; + } else + adapter->bio_done = adapter->bio_donetail = bio; + bio->bi_next = NULL; + spin_unlock_irqrestore(&adapter->lock, flags); + + /* Schedule the task */ + tasklet_schedule(&adapter->endio_tasklet); + + return 0; +} + +/* + * Find the vdev structure from the LUN field in an SRP IUE + * Note that this routine bumps a refcount field in the vdev. + * Normally this is done when free_iu is called. + */ +static struct vdev *find_device(struct iu_entry *iue) +{ + u16 *lun = (u16 *) & iue->iu->srp.cmd.lun; + u32 bus = (lun[0] & 0x00E0) >> 5; + u32 target = (lun[0] & 0x3F00) >> 8; + u32 slun = (lun[0] & 0x001F); + struct vdev *vd; + unsigned long flags; + + /* If asking for a lun other than 0, return nope */ + if (slun) { + return NULL; + } + + /* Only from SRP CMD */ + if (iue->iu->srp.generic.type != SRP_CMD_TYPE) + return NULL; + + /* if not a recognized LUN format, return NULL */ + if ((lun[0] & 0xC000) != 0x8000) + return NULL; + + spin_lock_irqsave(&iue->adapter->lock, flags); + if (iue->adapter->vbus[bus] == NULL) { + spin_unlock_irqrestore(&iue->adapter->lock, flags); + return NULL; + } + + vd = iue->adapter->vbus[bus]->vdev[target]; + + if ((vd == NULL) || (vd->disabled)) { + spin_unlock_irqrestore(&iue->adapter->lock, flags); + return NULL; + } + + if (vd) { + atomic_inc(&vd->refcount); + } + spin_unlock_irqrestore(&iue->adapter->lock, flags); + + return vd; +} + +/* + * Process BH buffer completions. When the end_io routine gets called + * we queue the bio on an internal queue and start a task to process them + */ +static void endio_task(unsigned long data) +{ + struct server_adapter *adapter = (struct server_adapter *)data; + struct iu_entry *iue= NULL; + struct bio *bio; + int bytes; + unsigned long flags; + + do { + spin_lock_irqsave(&adapter->lock, flags); + if ((bio = adapter->bio_done)) { + if (bio == adapter->bio_donetail) + adapter->bio_donetail = NULL; + adapter->bio_done = bio->bi_next; + bio->bi_next = NULL; + } + if (bio) { + /* Remove this iue from the in-flight list */ + iue = (struct iu_entry *)bio->bi_private; + if (!iue->req.in_use) { + err("Internal error! freed iue in bio!!!\n"); + spin_unlock_irqrestore(&adapter->lock, flags); + return; + } + + list_del(&iue->next); + } + + spin_unlock_irqrestore(&adapter->lock, flags); + + if (bio) { + /* Send back the SRP and data if this request was NOT + * aborted + */ + if (!iue->aborted) { + + if (!iue->req.ioerr) { + /* return data if this was a read */ + if (!iue->req.write) { + bytes = + send_cmd_data(iue->req. + data_token, + iue->req. + data_len, + iue); + if (bytes != iue->req.data_len) { + err("Error sending data " + "on response " + "(tried %d, sent %d\n", + bio->bi_size, bytes); + send_rsp(iue, + SENSE_ABORT); + } else { + send_rsp(iue, + SENSE_SUCCESS); + } + } else { + send_rsp(iue, SENSE_SUCCESS); + } + } else { + err("Block operation failed\n"); + print_command(iue->iu->srp.cmd.cdb); + send_rsp(iue, SENSE_DEVICE_FAULT); + } + } + + spin_lock_irqsave(&adapter->lock, flags); + free_data_buffer(iue->req.data_buffer, + iue->req.data_token, iue->req.data_len, + adapter); + spin_unlock_irqrestore(&adapter->lock, flags); + + free_iu(iue); + + bio_put(bio); + atomic_dec(&adapter->bio_count); + } + } while (bio); +} + +/* ============================================================== + * SCSI Command Emulation Routines + * ============================================================== + */ + +/* + * Process an inquiry SCSI Command + */ +static void process_inquiry(struct iu_entry *iue) +{ + struct inquiry_data *id; + dma_addr_t data_token; + u8 *raw_id; + int bytes; + + id = (struct inquiry_data *)dma_alloc_coherent(iue->adapter->dev, + sizeof(*id), + &data_token, 0); + raw_id = (u8 *)id; + memset(id, 0x00, sizeof(*id)); + + /* If we have a valid device */ + if (iue->req.vd) { + /* Standard inquiry page */ + if ((iue->iu->srp.cmd.cdb[1] == 0x00) && + (iue->iu->srp.cmd.cdb[2] == 0x00)) { + dbg(" inquiry returning device\n"); + id->qual_type = 0x00; /* Direct Access */ + id->rmb_reserve = 0x00; /* TODO: CD is removable */ + id->version = 0x84; /* ISO/IE */ + id->aerc_naca_hisup_format = 0x22;/* naca & fmt 0x02 */ + id->addl_len = sizeof(*id) - 4; + id->bque_encserv_vs_multip_mchngr_reserved = 0x00; + id->reladr_reserved_linked_cmdqueue_vs = 0x02;/*CMDQ*/ + memcpy(id->vendor, "IBM ", 8); + memcpy(id->product, "VSCSI blkdev ", 16); + memcpy(id->revision, "0001", 4); + snprintf(id->unique,sizeof(id->unique), + "IBM-VSCSI-%s-P%d-%x-%d-%d-%d\n", + system_id, + partition_number, + iue->adapter->dma_dev->unit_address, + GETBUS(iue->req.vd->lun), + GETTARGET(iue->req.vd->lun), + GETLUN(iue->req.vd->lun)); + } else if ((iue->iu->srp.cmd.cdb[1] == 0x01) && + (iue->iu->srp.cmd.cdb[2] == 0x00)) { + /* Supported VPD pages */ + raw_id[0] = 0x00; /* qualifier & type */ + raw_id[1] = 0x80; /* page */ + raw_id[2] = 0x00; /* reserved */ + raw_id[3] = 0x03; /* length */ + raw_id[4] = 0x00; /* page 0 */ + raw_id[5] = 0x80; /* serial number page */ + } else if ((iue->iu->srp.cmd.cdb[1] == 0x01) && + (iue->iu->srp.cmd.cdb[2] == 0x80)) { + /* serial number page */ + raw_id[0] = 0x00; /* qualifier & type */ + raw_id[1] = 0x80; /* page */ + raw_id[2] = 0x00; /* reserved */ + snprintf((char *)(raw_id+4), + sizeof(*id)-4, + "IBM-VSCSI-%s-P%d-%x-%d-%d-%d\n", + system_id, + partition_number, + iue->adapter->dma_dev->unit_address, + GETBUS(iue->req.vd->lun), + GETTARGET(iue->req.vd->lun), + GETLUN(iue->req.vd->lun)); + raw_id[3] = strlen((char *)raw_id+4); + } else { + /* Some unsupported data */ + send_rsp(iue, SENSE_INVALID_FIELD); + free_iu(iue); + return; + } + } else { + dbg(" inquiry returning no device\n"); + id->qual_type = 0x7F; /* Not supported, no device */ + } + + bytes = send_cmd_data(data_token, sizeof(*id), iue); + + dma_free_coherent(iue->adapter->dev, sizeof(*id), id, data_token); + + if (bytes < 0) { + send_rsp(iue, SENSE_DEVICE_FAULT); + } else { + send_rsp(iue, SENSE_SUCCESS); + } + + free_iu(iue); +} + +/* + * Handle an I/O. Called by WRITE6, WRITE10, etc + */ +static void process_rw(char *cmd, int rw, struct iu_entry *iue, long lba, + long len) +{ + char *buffer; + struct bio *bio; + int bytes; + int num_biovec; + int cur_biovec; + long flags; + + dbg("%s %16.16lx[%d:%d:%d][%s] lba %ld len %ld reladr %d link %d\n", + cmd, + iue->iu->srp.cmd.lun, + GETBUS(iue->iu->srp.cmd.lun), + GETTARGET(iue->iu->srp.cmd.lun), + GETLUN(iue->iu->srp.cmd.lun), + iue->req.vd->b.device_name, + lba, + len / iue->req.vd->b.blksize, + iue->iu->srp.cmd.cdb[1] & 0x01, iue->req.linked); + + if (rw == WRITE) { + atomic_inc(&iue->adapter->write_processed); + } else if (rw == READ) { + atomic_inc(&iue->adapter->read_processed); + } else { + err("Major internal error...rw not read or write\n"); + send_rsp(iue, SENSE_DEVICE_FAULT); + + free_iu(iue); + return; + } + + if (len == 0) { + warn("Zero length I/O\n"); + send_rsp(iue, SENSE_INVALID_CMD); + + free_iu(iue); + return; + } + + /* Writing to a read-only device */ + if ((rw == WRITE) && (iue->req.vd->b.ro)) { + warn("WRITE to read-only device\n"); + send_rsp(iue, SENSE_WRITE_PROT); + + free_iu(iue); + return; + } + + get_data_buffer(&buffer, &iue->req.data_token, len, iue->adapter); + iue->req.data_buffer = buffer; + iue->req.data_len = len; + if (buffer == NULL) { + err("Not able to get a data buffer (%lu pages)\n", + len / PAGE_SIZE); + send_rsp(iue, SENSE_DEVICE_FAULT); + + free_iu(iue); + return; + } + + /* if reladr */ + if (iue->iu->srp.cmd.cdb[1] & 0x01) { + lba = lba + iue->req.vd->b.lastlba; + } + + /* If this command is linked, Keep this lba */ + if (iue->req.linked) { + iue->req.vd->b.lastlba = lba; + } else { + iue->req.vd->b.lastlba = 0; + } + + if (rw == WRITE) { + iue->req.write = 1; + /* Get the data */ + bytes = get_cmd_data(iue->req.data_token, len, iue); + if (bytes != len) { + err("Error transferring data\n"); + send_rsp(iue, SENSE_DEVICE_FAULT); + + free_iu(iue); + return; + } + } + + num_biovec = (len - 1) / PAGE_CACHE_SIZE + 1; + + bio = bio_alloc(GFP_ATOMIC, num_biovec); + if (!bio) { + /* Ouch. couldn't get a bio. Mark this I/O as + * in error, then decrement the outstanding bio. + * If there are still outstanding bio, they will send + * the error and free the IU. If there are none, we + * should do it here + */ + iue->req.ioerr = 1; + err("Not able to allocate a bio\n"); + send_rsp(iue, SENSE_DEVICE_FAULT); + free_iu(iue); + return; + } + + iue->aborted = 0; + spin_lock_irqsave(&iue->adapter->lock, flags); + list_add_tail(&iue->next, &iue->adapter->inflight); + spin_unlock_irqrestore(&iue->adapter->lock, flags); + + atomic_inc(&iue->adapter->bio_count); + bio->bi_size = len; + bio->bi_bdev = iue->req.vd->b.bdev; + bio->bi_sector = lba; + bio->bi_end_io = &ibmvscsis_end_io; + bio->bi_private = iue; + bio->bi_rw = (rw == WRITE) ? 1 : 0; + bio->bi_phys_segments = 1; + bio->bi_hw_segments = 1; + + /* This all assumes that the buffers we get are page-aligned */ + for (cur_biovec = 0; cur_biovec < num_biovec; cur_biovec++) { + long thislen; + + if (len > PAGE_CACHE_SIZE) { + thislen = PAGE_CACHE_SIZE; + } else { + thislen = len; + } + + bio->bi_io_vec[cur_biovec].bv_page = virt_to_page(buffer); + bio->bi_io_vec[cur_biovec].bv_len = thislen; + bio->bi_io_vec[cur_biovec].bv_offset = + (unsigned long)buffer & PAGE_OFFSET_MASK; + bio->bi_vcnt++; + + len -= thislen; + buffer += thislen; + } + generic_make_request(bio); +} + +/* + * Process a READ6 + */ +static void processRead6(struct iu_entry *iue) +{ + long lba = (*((u32 *) (iue->iu->srp.cmd.cdb))) & 0x001FFFFF; + long len = iue->iu->srp.cmd.cdb[4]; + + /* Length of 0 indicates 256 */ + if (len == 0) { + len = 256; + } + + len = len * iue->req.vd->b.blksize; + + process_rw("Read6", READ, iue, lba, len); +} + +/* + * Process a READ10 + */ +static void processRead10(struct iu_entry *iue) +{ + long lba = *((u32 *) (iue->iu->srp.cmd.cdb + 2)); + long len = + *((u16 *) (iue->iu->srp.cmd.cdb + 7)) * iue->req.vd->b.blksize; + + process_rw("Read10", READ, iue, lba, len); +} + +/* + * Process a READ10 + */ +static void processRead12(struct iu_entry *iue) +{ + long lba = *((u32 *) (iue->iu->srp.cmd.cdb + 2)); + long len = + *((u32 *) (iue->iu->srp.cmd.cdb + 6)) * iue->req.vd->b.blksize; + + process_rw("Read12", READ, iue, lba, len); +} + +static void processWrite6(struct iu_entry *iue) +{ + long lba = (*((u32 *) (iue->iu->srp.cmd.cdb))) & 0x001FFFFF; + long len = iue->iu->srp.cmd.cdb[4]; + + /* Length of 0 indicates 256 */ + if (len == 0) { + len = 256; + } + + len = len * iue->req.vd->b.blksize; + + process_rw("Write6", WRITE, iue, lba, len); +} + +static void processWrite10(struct iu_entry *iue) +{ + long lba = *((u32 *) (iue->iu->srp.cmd.cdb + 2)); + long len = + *((u16 *) (iue->iu->srp.cmd.cdb + 7)) * iue->req.vd->b.blksize; + + process_rw("Write10", WRITE, iue, lba, len); +} + +static void processWrite12(struct iu_entry *iue) +{ + long lba = *((u32 *) (iue->iu->srp.cmd.cdb + 2)); + long len = + *((u32 *) (iue->iu->srp.cmd.cdb + 6)) * iue->req.vd->b.blksize; + + process_rw("Write12", WRITE, iue, lba, len); +} + +/* + * Handle Read Capacity + */ +static void processReadCapacity(struct iu_entry *iue) +{ + struct ReadCapacityData { + u32 blocks; + u32 blocksize; + } *cap; + dma_addr_t data_token; + int bytes; + + cap = (struct ReadCapacityData *)dma_alloc_coherent(iue->adapter->dev, + sizeof(*cap), + &data_token, 0); + + /* return block size and last valid block */ + cap->blocksize = iue->req.vd->b.blksize; + cap->blocks = iue->req.vd->b.bdev->bd_inode->i_size + / iue->req.vd->b.blksize + - 1; + + info("Reporting capacity as %u block of size %u\n", cap->blocks, + cap->blocksize); + + bytes = send_cmd_data(data_token, sizeof(*cap), iue); + + dma_free_coherent(iue->adapter->dev, sizeof(*cap), cap, data_token); + + if (bytes != sizeof(*cap)) { + err("Error sending read capacity data. bytes %d, wanted %ld\n", + bytes, sizeof(*cap)); + } + + send_rsp(iue, SENSE_SUCCESS); + + free_iu(iue); +} + +/* + * Process Mode Sense + * TODO: I know scsiinfo asks for a bunch of mode pages not implemented here. + * Also, we need to act differently for virtual disk and virtual CD + */ +#define MODE_SENSE_BUFFER_SIZE (512) +static void processModeSense(struct iu_entry *iue) +{ + dma_addr_t data_token; + int bytes; + + u8 *mode = (u8 *) dma_alloc_coherent(iue->adapter->dev, + MODE_SENSE_BUFFER_SIZE, + &data_token, 0); + /* which page */ + switch (iue->iu->srp.cmd.cdb[2]) { + case 0: + case 0x3f: + mode[1] = 0x00; /* Default medium */ + if (iue->req.vd->b.ro) { + mode[2] = 0x80; /* device specific */ + } else { + mode[2] = 0x00; /* device specific */ + } + /* note the DPOFUA bit is set to zero! */ + mode[3] = 0x08; /* block descriptor length */ + *((u32 *) & mode[4]) = iue->req.vd->b.bdev->bd_inode->i_size / + iue->req.vd->b.blksize; + *((u32 *) & mode[8]) = iue->req.vd->b.blksize; + bytes = mode[0] = 12; /* length */ + break; + + case 0x08: /* Cache page */ + /* length should be 4 */ + if (iue->iu->srp.cmd.cdb[4] != 4 + && iue->iu->srp.cmd.cdb[4] != 0x20) { + send_rsp(iue, SENSE_INVALID_CMD); + dma_free_coherent(iue->adapter->dev, + MODE_SENSE_BUFFER_SIZE, + mode, data_token); + free_iu(iue); + return; + } + + mode[1] = 0x00; /* Default medium */ + if (iue->req.vd->b.ro) { + mode[2] = 0x80; /* device specific */ + } else { + mode[2] = 0x00; /* device specific */ + } + /* note the DPOFUA bit is set to zero! */ + mode[3] = 0x08; /* block descriptor length */ + *((u32 *) & mode[4]) = iue->req.vd->b.bdev->bd_inode->i_size / + iue->req.vd->b.blksize; + *((u32 *) & mode[8]) = iue->req.vd->b.blksize; + + /* Cache page */ + mode[12] = 0x08; /* page */ + mode[13] = 0x12; /* page length */ + mode[14] = 0x01; /* no cache (0x04 for read/write cache) */ + + bytes = mode[0] = 12 + mode[13]; /* length */ + break; + default: + warn("Request for unknown mode page %d\n", + iue->iu->srp.cmd.cdb[2]); + send_rsp(iue, SENSE_INVALID_CMD); + dma_free_coherent(iue->adapter->dev, + MODE_SENSE_BUFFER_SIZE, mode, data_token); + free_iu(iue); + return; + } + + bytes = send_cmd_data(data_token, bytes, iue); + + dma_free_coherent(iue->adapter->dev, + MODE_SENSE_BUFFER_SIZE, mode, data_token); + + send_rsp(iue, SENSE_SUCCESS); + + free_iu(iue); + return; +} + +/* + * Report LUNS command. + */ +static void processReportLUNs(struct iu_entry *iue) +{ + int listsize = did_len(&iue->iu->srp.cmd); + dma_addr_t data_token; + int index = 2; /* Start after the two entries (length and LUN0) */ + int bus; + int target; + int bytes; + unsigned long flags; + + u64 *lunlist = (u64 *) dma_alloc_coherent(iue->adapter->dev, + listsize, + &data_token, 0); + + memset(lunlist, 0x00, listsize); + + /* work out list size in units of u64 */ + listsize = listsize / 8; + + if (listsize < 1) { + send_rsp(iue, SENSE_INVALID_CMD); + free_iu(iue); + } + + spin_lock_irqsave(&iue->adapter->lock, flags); + + /* send lunlist of size 1 when requesting lun is not all zeros */ + if (iue->iu->srp.cmd.lun != 0x0LL) { + *lunlist = ((u64) 1 * 8) << 32; + goto send_lunlist; + } + + /* return the total number of luns plus LUN0 in bytes */ + *lunlist = (((u64) ((iue->adapter->nvdevs + 1) * 8)) << 32); + + dbg("reporting %d luns\n", iue->adapter->nvdevs + 1); + /* loop through the bus */ + for (bus = 0; bus < BUS_PER_ADAPTER; bus++) { + /* If this bus exists */ + if (iue->adapter->vbus[bus]) { + /* loop through the targets */ + for (target = 0; target < TARGETS_PER_BUS; target++) { + /* If the target exists */ + if (iue->adapter->vbus[bus]->vdev[target]) { + if ((index < listsize) && + (!iue->adapter->vbus[bus]-> + vdev[target]->disabled)) { + lunlist[index++] = + iue->adapter->vbus[bus]-> + vdev[target]->lun; + dbg(" lun %16.16lx\n", + iue->adapter->vbus[bus]-> + vdev[target]->lun); + } + } + } + } + } + + send_lunlist: + spin_unlock_irqrestore(&iue->adapter->lock, flags); + + bytes = send_cmd_data(data_token, (index * 8), iue); + + dma_free_coherent(iue->adapter->dev, listsize * 8, lunlist, data_token); + + if (bytes != (index * 8)) { + err("Error sending report luns data. bytes %d, wanted %d\n", + bytes, index * 4); + send_rsp(iue, SENSE_ABORT); + } else { + send_rsp(iue, SENSE_SUCCESS); + } + + free_iu(iue); + return; +} + +/* + * Process an IU. + * + * Note that THIS routine is responsible for returning the IU from the pool + * The current assumption is that all the process routines called from here + * are, in turn, responsible for freeing the IU + */ +static void process_cmd(struct iu_entry *iue) +{ + union viosrp_iu *iu = iue->iu; + + iue->req.vd = find_device(iue); + + if ((iue->req.vd == NULL) && + (iu->srp.cmd.cdb[0] != REPORT_LUNS) && + (iu->srp.cmd.cdb[0] != INQUIRY)) { + dbg("Cmd %2.2x for unknown LUN %16.16lx\n", + iu->srp.cmd.cdb[0], iue->iu->srp.cmd.lun); + send_rsp(iue, SENSE_INVALID_ID); + free_iu(iue); + return; + } + + iue->req.linked = getlink(iue); + + switch (iu->srp.cmd.cdb[0]) { + case READ_6: + processRead6(iue); + break; + case READ_10: + processRead10(iue); + break; + case READ_12: + processRead12(iue); + break; + case WRITE_6: + processWrite6(iue); + break; + case WRITE_10: + processWrite10(iue); + break; + case WRITE_12: + processWrite12(iue); + break; + case REPORT_LUNS: + dbg("REPORT LUNS lun %16.16lx\n", iue->iu->srp.cmd.lun); + processReportLUNs(iue); + break; + case INQUIRY: + dbg("INQUIRY lun %16.16lx\n", iue->iu->srp.cmd.lun); + process_inquiry(iue); + break; + case READ_CAPACITY: + dbg("READ CAPACITY lun %16.16lx\n", iue->iu->srp.cmd.lun); + processReadCapacity(iue); + break; + case MODE_SENSE: + dbg("MODE SENSE lun %16.16lx\n", iue->iu->srp.cmd.lun); + processModeSense(iue); + break; + case TEST_UNIT_READY: + /* we already know the device exists */ + dbg("TEST UNIT READY lun %16.16lx\n", iue->iu->srp.cmd.lun); + send_rsp(iue, SENSE_SUCCESS); + free_iu(iue); + break; + case START_STOP: + /* just respond OK */ + dbg("START_STOP lun %16.16lx\n", iue->iu->srp.cmd.lun); + send_rsp(iue, SENSE_SUCCESS); + free_iu(iue); + break; + default: + warn("Unsupported SCSI Command 0x%2.2x\n", iu->srp.cmd.cdb[0]); + send_rsp(iue, SENSE_INVALID_CMD); + free_iu(iue); + } +} + +u16 send_adapter_info(struct iu_entry *iue, + dma_addr_t remote_buffer, u16 length) +{ + dma_addr_t data_token; + struct mad_adapter_info_data *info = + (struct mad_adapter_info_data *)dma_alloc_coherent(iue->adapter-> + dev, + sizeof(*info), + &data_token, 0); + + dbg("in send_adapter_info\n "); + if ((info) && (!dma_mapping_error(data_token))) { + int rc; + memset(info, 0x00, sizeof(*info)); + + dbg("building adapter_info\n "); + strcpy(info->srp_version, "1.6a"); + strncpy(info->partition_name, partition_name, + sizeof(info->partition_name)); + info->partition_number = partition_number; + info->mad_version = 1; + info->os_type = 3; + + rc = h_copy_rdma(sizeof(*info), + iue->adapter->liobn, + data_token, + iue->adapter->riobn, + remote_buffer); + + dma_free_coherent(iue->adapter->dev, + sizeof(*info), info, data_token); + + if (rc != H_Success) { + err("Error sending adapter info rc %d\n",rc); + return 1; + } + } else { + dbg("bad dma_alloc_cohereint in adapter_info\n "); + return 1; + } + return 0; + +} + +/* ============================================================== + * SRP Processing Routines + * ============================================================== + */ +/* + * Process an incoming SRP Login request + */ +static void process_login(struct iu_entry *iue) +{ + union viosrp_iu *iu = iue->iu; + u64 tag = iu->srp.generic.tag; + + /* TODO handle case that requested size is wrong and buffer format is wrong */ + memset(iu, 0x00, sizeof(struct srp_login_rsp)); + iu->srp.login_rsp.type = SRP_LOGIN_RSP_TYPE; + iu->srp.login_rsp.request_limit_delta = iue->adapter->pool.size; + iu->srp.login_rsp.tag = tag; + iu->srp.login_rsp.max_initiator_to_target_iulen = sizeof(union srp_iu); + iu->srp.login_rsp.max_target_to_initiator_iulen = sizeof(union srp_iu); + iu->srp.login_rsp.supported_buffer_formats = 0x0006; /* direct and indirect */ + iu->srp.login_rsp.multi_channel_result = 0x00; /* TODO fix if we were already logged in */ + + send_srp(iue, sizeof(iu->srp.login_rsp)); +} + +/* + * Send an SRP response that includes sense data + */ +static long send_rsp(struct iu_entry *iue, int status) +{ + u8 *sense = iue->iu->srp.rsp.sense_and_response_data; + u64 tag = iue->iu->srp.generic.tag; + union viosrp_iu *iu = iue->iu; + + if (status != SENSE_SUCCESS) { + atomic_inc(&iue->adapter->errors); + } + + /* If the linked bit is on and status is good */ + if ((iue->req.linked) && (status == SENSE_SUCCESS)) { + status = SENSE_INTERMEDIATE; + } + + memset(iu, 0x00, sizeof(struct srp_rsp)); + iu->srp.rsp.type = SRP_RSP_TYPE; + iu->srp.rsp.request_limit_delta = 1; + iu->srp.rsp.tag = tag; + + iu->srp.rsp.diunder = iue->req.diunder; + iu->srp.rsp.diover = iue->req.diover; + iu->srp.rsp.dounder = iue->req.dounder; + iu->srp.rsp.doover = iue->req.doover; + + iu->srp.rsp.data_in_residual_count = iue->req.data_in_residual_count; + iu->srp.rsp.data_out_residual_count = iue->req.data_out_residual_count; + + iu->srp.rsp.rspvalid = 0; + + iu->srp.rsp.response_data_list_length = 0; + + if (status) { + iu->srp.rsp.status = SAM_STAT_CHECK_CONDITION; + iu->srp.rsp.snsvalid = 1; + iu->srp.rsp.sense_data_list_length = 18; /* TODO be smarter about this */ + + /* Valid bit and 'current errors' */ + sense[0] = (0x1 << 7 | 0x70); + + /* Sense key */ + sense[2] = ibmvscsis_sense_data[status][0]; + + /* Additional sense length */ + sense[7] = 0xa; /* 10 bytes */ + + /* Additional sense code */ + sense[12] = ibmvscsis_sense_data[status][1]; + + /* Additional sense code qualifier */ + sense[13] = ibmvscsis_sense_data[status][2]; + } else { + iu->srp.rsp.status = 0; + } + + send_srp(iue, sizeof(iu->srp.rsp)); + + return 0; +} + +static void process_device_reset(struct iu_entry *iue) +{ + struct iu_entry *tmp_iue; + unsigned long flags; + union viosrp_iu *iu = iue->iu; + + info("device reset for lun %16.16lx\n", iu->srp.tsk_mgmt.lun); + + spin_lock_irqsave(&iue->adapter->lock, flags); + + list_for_each_entry(tmp_iue, &iue->adapter->inflight, next) { + if (iu->srp.tsk_mgmt.lun == tmp_iue->iu->srp.cmd.lun) { + { + tmp_iue->aborted = 1; + } + } + + } + + spin_unlock_irqrestore(&iue->adapter->lock, flags); + send_rsp(iue, SENSE_SUCCESS); +} + +static void process_abort(struct iu_entry *iue) +{ + struct iu_entry *tmp_iue; + unsigned long flags; + union viosrp_iu *iu = iue->iu; + + info("aborting task with tag %16.16lx, lun %16.16lx\n", + iu->srp.tsk_mgmt.managed_task_tag, iu->srp.tsk_mgmt.lun); + + spin_lock_irqsave(&iue->adapter->lock, flags); + + list_for_each_entry(tmp_iue, &iue->adapter->inflight, next) { + if (tmp_iue->iu->srp.cmd.tag == + iu->srp.tsk_mgmt.managed_task_tag) { + { + tmp_iue->aborted = 1; + info("abort successful\n"); + spin_unlock_irqrestore(&iue->adapter->lock, + flags); + send_rsp(iue, SENSE_SUCCESS); + return; + } + } + } + info("unable to abort cmd\n"); + + spin_unlock_irqrestore(&iue->adapter->lock, flags); + send_rsp(iue, SENSE_INVALID_ID); +} + +static void process_tsk_mgmt(struct iu_entry *iue) +{ + union viosrp_iu *iu = iue->iu; + + if (iu->srp.tsk_mgmt.task_mgmt_flags == 0x01) { + process_abort(iue); + } else if (iu->srp.tsk_mgmt.task_mgmt_flags == 0x08) { + process_device_reset(iue); + } else { + send_rsp(iue, SENSE_INVALID_CMD); + } +} + +static void process_iu(struct viosrp_crq *crq, struct server_adapter *adapter) +{ + struct iu_entry *iue = get_iu(adapter); + union viosrp_iu *iu; + int queued = 0; + long rc; + + if (iue == NULL) { + /* TODO Yikes! */ + warn("Error getting IU from pool, other side exceeded limit\n"); + return; + } + + iue->req.remote_token = crq->IU_data_ptr; + + rc = h_copy_rdma(crq->IU_length, + iue->adapter->riobn, + iue->req.remote_token, adapter->liobn, iue->iu_token); + + iu = iue->iu; + + if (rc) { + err("Error %ld transferring data to client\n", rc); + } + + if (crq->format == VIOSRP_MAD_FORMAT) { + switch (iu->mad.empty_iu.common.type) { + case VIOSRP_EMPTY_IU_TYPE: + warn("Unsupported EMPTY MAD IU\n"); + break; + case VIOSRP_ERROR_LOG_TYPE: + warn("Unsupported ERROR LOG MAD IU\n"); + iu->mad.error_log.common.status = 1; + send_srp(iue, sizeof(iu->mad.error_log)); + break; + case VIOSRP_ADAPTER_INFO_TYPE: + iu->mad.adapter_info.common.status = + send_adapter_info(iue, + iu->mad.adapter_info.buffer, + iu->mad.adapter_info.common. + length); + + send_srp(iue, sizeof(iu->mad.adapter_info)); + break; + case VIOSRP_HOST_CONFIG_TYPE: + iu->mad.host_config.common.status = 1; + send_srp(iue, sizeof(iu->mad.host_config)); + break; + default: + warn("Unsupported MAD type %d\n", iu->srp.generic.type); + } + } else { + switch (iu->srp.generic.type) { + case SRP_LOGIN_REQ_TYPE: + dbg("SRP LOGIN\n"); + process_login(iue); + break; + case SRP_LOGIN_RSP_TYPE: + warn("Unsupported LOGIN_RSP SRP IU\n"); + break; + case SRP_I_LOGOUT_TYPE: + warn("Unsupported I_LOGOUT SRP IU\n"); + break; + case SRP_T_LOGOUT_TYPE: + warn("Unsupported T_LOGOUT SRP IU\n"); + break; + case SRP_TSK_MGMT_TYPE: + process_tsk_mgmt(iue); + break; + case SRP_CMD_TYPE: + process_cmd(iue); + queued = 1; + break; + case SRP_RSP_TYPE: + warn("Unsupported RSP SRP IU\n"); + break; + case SRP_CRED_REQ_TYPE: + warn("Unsupported CRED_REQ SRP IU\n"); + break; + case SRP_CRED_RSP_TYPE: + warn("Unsupported CRED_RSP SRP IU\n"); + break; + case SRP_AER_REQ_TYPE: + warn("Unsupported AER_REQ SRP IU\n"); + break; + case SRP_AER_RSP_TYPE: + warn("Unsupported AER_RSP SRP IU\n"); + break; + default: + warn("Unsupported SRP type %d\n", iu->srp.generic.type); + } + } + + /* + * If no one has queued the IU for further work, free it + * Note that this is kind of an ugly design based on setting + * this variable up above in cases where the routine we call + * is responsible for freeing the IU + */ + if (!queued) + free_iu(iue); +} + +/* ============================================================== + * CRQ Processing Routines + * ============================================================== + */ + +/* + * Handle a CRQ event + */ +static void handle_crq(struct viosrp_crq *crq, struct server_adapter *adapter) +{ + switch (crq->valid) { + case 0xC0: /* initialization */ + switch (crq->format) { + case 0x01: + info("Client just initialized\n"); + plpar_hcall_norets(H_SEND_CRQ, + adapter->dma_dev->unit_address, + 0xC002000000000000, 0); + break; + case 0x02: + info("Client initialization complete\n"); + break; + default: + err("Client error: Unknwn msg format %d\n", + crq->format); + } + return; + case 0xFF: /* transport event */ + info("Client closed\n"); + return; + case 0x80: /* real payload */ + { + switch (crq->format) { + case VIOSRP_SRP_FORMAT: + case VIOSRP_MAD_FORMAT: + process_iu(crq, adapter); + break; + case VIOSRP_OS400_FORMAT: + warn("Unsupported OS400 format CRQ\n"); + break; + + case VIOSRP_AIX_FORMAT: + warn("Unsupported AIX format CRQ\n"); + break; + + case VIOSRP_LINUX_FORMAT: + warn("Unsupported LINUX format CRQ\n"); + break; + + case VIOSRP_INLINE_FORMAT: + warn("Unsupported _INLINE_ format CRQ\n"); + break; + + default: + err("Client error: Unsupported msg format %d\n", + crq->format); + } + } + break; + default: + err("Client error: unknown message type 0x%02x!?\n", + crq->valid); + return; + } + +} + +/* + * Task to handle CRQs and completions + */ +static void crq_task(void *data) +{ + struct server_adapter *adapter = (struct server_adapter *)data; + struct viosrp_crq *crq; + long rc; + int done = 0; + + while (!done) { + + /* Loop through and process CRQs */ + while ((crq = crq_queue_next_crq(&adapter->queue)) != NULL) { + atomic_inc(&adapter->crq_processed); + handle_crq(crq, adapter); + crq->valid = 0x00; + } + + rc = h_vio_signal(adapter->dma_dev->unit_address, 1); + if (rc != 0) { + err("Error %ld enabling interrupts!!!\n", rc); + } + if ((crq = crq_queue_next_crq(&adapter->queue)) != NULL) { + rc = h_vio_signal(adapter->dma_dev->unit_address, 0); + if (rc != 0) { + err("Error %ld enabling interrupts!!!\n", rc); + } + handle_crq(crq, adapter); + crq->valid = 0x00; + } else { + done = 1; + } + } +} + +/* + * Handle the interrupt that occurs when something is placed on our CRQ + */ +static irqreturn_t handle_interrupt(int irq, void *dev_instance, + struct pt_regs *regs) +{ + struct server_adapter *adapter = (struct server_adapter *)dev_instance; + long rc; + + rc = h_vio_signal(adapter->dma_dev->unit_address, 0); + if (rc != 0) { + err(" Error %ld disabling interrupts!!!\n", rc); + } + + atomic_inc(&adapter->interrupts); + + kblockd_schedule_work(&adapter->crq_task); + + return IRQ_HANDLED; +} + +/* + * Initialize our CRQ + * return zero on success, non-zero on failure + */ +static int initialize_crq_queue(struct crq_queue *queue, + struct server_adapter *adapter) +{ + int rc; + + queue->msgs = (struct viosrp_crq *)get_zeroed_page(GFP_KERNEL); + if (!queue->msgs) + goto malloc_failed; + queue->size = PAGE_SIZE / sizeof(*queue->msgs); + + queue->msg_token = dma_map_single(adapter->dev, queue->msgs, + queue->size * sizeof(*queue->msgs), + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(queue->msg_token)) + goto map_failed; + + rc = plpar_hcall_norets(H_REG_CRQ, adapter->dma_dev->unit_address, + queue->msg_token, PAGE_SIZE); + + if ((rc != 0) && (rc != 2)) { + err("Error 0x%x opening virtual adapter\n", rc); + goto reg_crq_failed; + } + + if (request_irq + (adapter->dma_dev->irq, &handle_interrupt, SA_INTERRUPT, + "ibmvscsis", adapter) != 0) + goto req_irq_failed; + + rc = h_vio_signal(adapter->dma_dev->unit_address, 1); + if (rc != 0) { + err("Error %d enabling interrupts!!!\n", rc); + goto req_irq_failed; + } + + plpar_hcall_norets(H_SEND_CRQ, adapter->dma_dev->unit_address, + 0xC001000000000000, 0); + + queue->cur = 0; + queue->lock = SPIN_LOCK_UNLOCKED; + + return 0; + + req_irq_failed: + do { + rc = plpar_hcall_norets(H_FREE_CRQ, adapter->dma_dev->unit_address); + } while ((rc == H_Busy) || (H_isLongBusy(rc))); + + reg_crq_failed: + dma_unmap_single(adapter->dev, queue->msg_token, + queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL); + map_failed: + free_page((unsigned long)queue->msgs); + malloc_failed: + return -1; +} + +/* + * Release the CRQ + */ +static void release_crq_queue(struct crq_queue *queue, + struct server_adapter *adapter) +{ + int rc; + + info("releasing adapter\n"); + free_irq(adapter->dma_dev->irq, adapter); + do { + rc = plpar_hcall_norets(H_FREE_CRQ, adapter->dma_dev->unit_address); + } while ((rc == H_Busy) || (H_isLongBusy(rc))); + dma_unmap_single(adapter->dev, queue->msg_token, + queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL); + free_page((unsigned long)queue->msgs); +} + +/* ============================================================== + * Module Management + * ============================================================== + */ +/* + * Add a block device as a SCSI LUN + */ +static int activate_block_device(struct vdev *vdev) +{ + struct block_device *bdev; + char *name = vdev->b.device_name; + int ro = vdev->b.ro; + + bdev = open_bdev_excl(name, ro, activate_block_device); + if (IS_ERR(bdev)) + return PTR_ERR(bdev);; + + vdev->b.bdev = bdev; + vdev->disabled = 0; + + info("Activating block device %s as %sLUN 0x%lx\n", + name, ro ? "read only " : "", vdev->lun); + + return 0; +} + +static void deactivate_block_device(struct vdev *vdev) +{ + info("Deactivating block device, LUN 0x%lx\n", vdev->lun); + + /* Wait while any users of this device finish. Note there should + * be no new users, since we have marked this disabled + * + * We just poll here, since we are blocking write + */ + while (atomic_read(&vdev->refcount)) { + schedule_timeout(HZ / 4); /* 1/4 second */ + } + + vdev->disabled = 1; + close_bdev_excl(vdev->b.bdev); +} + + +#define ATTR(_type, _name, _mode) \ +struct attribute vscsi_##_type##_##_name##_attr = { \ +.name = __stringify(_name), .mode = _mode, .owner = THIS_MODULE \ +}; + +static struct kobj_type ktype_vscsi_target; +static struct kobj_type ktype_vscsi_bus; +static struct kobj_type ktype_vscsi_stats; + +static void set_num_targets(struct vbus* vbus, long value) +{ + struct device *dev = + container_of(vbus->kobj.parent, struct device , kobj); + struct server_adapter *adapter = (struct server_adapter *)to_vio_dev(dev)->driver_data; + int cur_num_targets = atomic_read(&vbus->num_targets); + unsigned long flags; + + spin_lock_irqsave(&adapter->lock, flags); + + if (cur_num_targets < value) { //growing + int i; + for (i = cur_num_targets; i < value; i++) { + vbus->vdev[i] = (struct vdev *) + kmalloc(sizeof(struct vdev), GFP_KERNEL); + if (!vbus->vdev[i]) { + spin_unlock_irqrestore(&adapter->lock, flags); + err("Couldn't allocate target memory %d\n", i); + return; + } + memset(vbus->vdev[i], 0x00, sizeof(struct vdev)); + + vbus->vdev[i]->lun = make_lun(vbus->bus_num, i, 0); + vbus->vdev[i]->b.blksize = 512; + vbus->vdev[i]->disabled = 1; + + vbus->vdev[i]->kobj.parent = &vbus->kobj; + sprintf(vbus->vdev[i]->kobj.name, "target%d", i); + vbus->vdev[i]->kobj.ktype = &ktype_vscsi_target; + kobject_register(&vbus->vdev[i]->kobj); + adapter->nvdevs++; + atomic_inc(&vbus->num_targets); + } + } else { //shrinking + int i; + for (i = cur_num_targets - 1; i >= value; i--) + { + if (!vbus->vdev[i]->disabled) { + spin_unlock_irqrestore(&adapter->lock, flags); + err("Can't remove active target %d\n", i); + return; + } + + kobject_unregister(&vbus->vdev[i]->kobj); + + kfree(vbus->vdev[i]); + + adapter->nvdevs--; + atomic_dec(&vbus->num_targets); + } + } + spin_unlock_irqrestore(&adapter->lock, flags); +} + +static void set_num_buses(struct device *dev, long value) +{ + struct server_adapter *adapter = (struct server_adapter *)to_vio_dev(dev)->driver_data; + int cur_num_buses = atomic_read(&adapter->num_buses); + unsigned long flags= 0L; + + + if (cur_num_buses < value) { // growing + int i; + for (i = cur_num_buses; i < value; i++) { + adapter->vbus[i] = (struct vbus *) + kmalloc(sizeof(struct vbus), GFP_KERNEL); + if (!adapter->vbus[i]) { + spin_unlock_irqrestore(&adapter->lock, flags); + err("Couldn't allocate bus %d memory\n", i); + return; + } + memset(adapter->vbus[i], 0x00, sizeof(struct vbus)); + + spin_lock_irqsave(&adapter->lock, flags); + + adapter->vbus[i]->bus_num = i; + + adapter->vbus[i]->kobj.parent = &dev->kobj; + sprintf(adapter->vbus[i]->kobj.name, "bus%d", i); + adapter->vbus[i]->kobj.ktype = &ktype_vscsi_bus; + kobject_register(&adapter->vbus[i]->kobj); + + atomic_inc(&adapter->num_buses); + spin_unlock_irqrestore(&adapter->lock, flags); + + set_num_targets(adapter->vbus[i], 1); + } + + } else if (cur_num_buses > value) { //shrinking + int i, j, active_target; + for (i = cur_num_buses - 1; i >= value; i--) { + active_target = -1; + for (j = 0; j < TARGETS_PER_BUS; j++) { + if (adapter->vbus[i]->vdev[j] && + !adapter->vbus[i]->vdev[j]->disabled) { + active_target = j; + break; + } + } + if (active_target != -1) { + err("Can't remove bus%d, target%d active\n", + i, active_target); + return ; + } + + set_num_targets(adapter->vbus[i], 0); + + spin_lock_irqsave(&adapter->lock, flags); + atomic_dec(&adapter->num_buses); + kobject_unregister(&adapter->vbus[i]->kobj); + kfree(adapter->vbus[i]); + adapter->vbus[i] = NULL; + spin_unlock_irqrestore(&adapter->lock, flags); + } + } +} + + +/* Target sysfs stuff */ +static ATTR(target, type, 0644); +static ATTR(target, device, 0644); +static ATTR(target, active, 0644); +static ATTR(target, ro, 0644); + +static ssize_t vscsi_target_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct vdev *vdev = container_of(kobj, struct vdev, kobj); + struct device *dev = container_of(kobj->parent->parent, struct device, kobj); + struct server_adapter *adapter = (struct server_adapter *)to_vio_dev(dev)->driver_data; + unsigned long flags; + ssize_t returned= (ssize_t)0; + + spin_lock_irqsave(&adapter->lock, flags); + + if (attr == &vscsi_target_type_attr) + returned = sprintf(buf, "%c\n", vdev->type); + else if (attr == &vscsi_target_device_attr) + returned = sprintf(buf, "%s\n", vdev->b.device_name); + else if (attr == &vscsi_target_active_attr) + returned = sprintf(buf, "%d\n", !vdev->disabled); + else if (attr == &vscsi_target_ro_attr) + returned = sprintf(buf, "%d\n", vdev->b.ro); + else { + spin_unlock_irqrestore(&adapter->lock, flags); + BUG(); + } + + spin_unlock_irqrestore(&adapter->lock, flags); + + return returned; +} + +static ssize_t vscsi_target_store(struct kobject * kobj, struct attribute * attr, const char * buf, size_t count) +{ + struct vdev *vdev = container_of(kobj, struct vdev, kobj); + struct device *dev = container_of(kobj->parent->parent, struct device, kobj); + struct server_adapter *adapter = (struct server_adapter *)to_vio_dev(dev)->driver_data; + long flags; + long value = simple_strtol(buf, NULL, 10); + + if (attr != &vscsi_target_active_attr && !vdev->disabled) { + err("Error: Can't modify properties while target is active.\n"); + return -EPERM; + } + + if (attr == &vscsi_target_type_attr) { + if (buf[0] == 'B' || buf[0] == 'b') + vdev->type = 'B'; + else if (buf[0] == 'S' || buf[0] == 's') { + // TODO + err ("SCSI mode not supported yet\n"); + return -EINVAL; + } else + return -EINVAL; + } else if (attr == &vscsi_target_device_attr) { + int i; + spin_lock_irqsave(&adapter->lock, flags); + i = strlcpy(vdev->b.device_name, buf, TARGET_MAX_NAME_LEN); + for (; i >= 0; i--) + if (vdev->b.device_name[i] == '\n') + vdev->b.device_name[i] = '\0'; + spin_unlock_irqrestore(&adapter->lock, flags); + } else if (attr == &vscsi_target_active_attr) { + if (value) { + int rc; + if (!vdev->disabled) { + warn("Warning: Target was already active\n"); + return -EINVAL; + } + if (vdev->type == '\0') { + err("Error: Type not specified\n"); + return -EPERM; + } + rc = activate_block_device(vdev); + if (rc) { + err("Error opening block device=%d\n", rc); + return rc; + } + } else { + if (!vdev->disabled) + deactivate_block_device(vdev); + } + } else if (attr == &vscsi_target_ro_attr) + vdev->b.ro = value > 0 ? 1 : 0; + else + BUG(); + + return count; +} + +static struct attribute * vscsi_target_attrs[] = { + &vscsi_target_type_attr, + &vscsi_target_device_attr, + &vscsi_target_active_attr, + &vscsi_target_ro_attr, + NULL, +}; + +static struct sysfs_ops vscsi_target_ops = { + .show = vscsi_target_show, + .store = vscsi_target_store, +}; + +static struct kobj_type ktype_vscsi_target = { + .release = NULL, + .sysfs_ops = &vscsi_target_ops, + .default_attrs = vscsi_target_attrs, +}; + + + +/* Bus sysfs stuff */ +static ssize_t vscsi_bus_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct vbus *vbus = container_of(kobj, struct vbus, kobj); + return sprintf(buf, "%d\n", atomic_read(&vbus->num_targets)); +} + +static ssize_t vscsi_bus_store(struct kobject * kobj, struct attribute * attr, +const char * buf, size_t count) +{ + struct vbus *vbus = container_of(kobj, struct vbus, kobj); + long value = simple_strtol(buf, NULL, 10); + + if (value < 0 || value > TARGETS_PER_BUS) + return -EINVAL; + + set_num_targets(vbus, value); + + return count; +} + + +static ATTR(bus, num_targets, 0644); + +static struct attribute * vscsi_bus_attrs[] = { + &vscsi_bus_num_targets_attr, + NULL, +}; + +static struct sysfs_ops vscsi_bus_ops = { + .show = vscsi_bus_show, + .store = vscsi_bus_store, +}; + +static struct kobj_type ktype_vscsi_bus = { + .release = NULL, + .sysfs_ops = &vscsi_bus_ops, + .default_attrs = vscsi_bus_attrs, +}; + + +/* Device attributes */ +static ssize_t vscsi_dev_bus_show(struct device * dev, char * buf) +{ + struct server_adapter *adapter = (struct server_adapter *)to_vio_dev(dev)->driver_data; + + return sprintf(buf, "%d\n", atomic_read(&adapter->num_buses)); +} + +static ssize_t vscsi_dev_bus_store(struct device * dev, const char * buf, size_t count) +{ + long value = simple_strtol(buf, NULL, 10); + + if (value < 0 || value > BUS_PER_ADAPTER) + return -EINVAL; + + set_num_buses(dev, value); + return count; +} + +static DEVICE_ATTR(num_buses, 0644, vscsi_dev_bus_show, vscsi_dev_bus_store); + + +/* Stats kobj stuff */ + +static ATTR(stats, interrupts, 0444); +static ATTR(stats, read_ops, 0444); +static ATTR(stats, write_ops, 0444); +static ATTR(stats, crq_msgs, 0444); +static ATTR(stats, iu_allocs, 0444); +static ATTR(stats, bio_allocs, 0444); +static ATTR(stats, buf_allocs, 0444); +static ATTR(stats, errors, 0444); + +static struct attribute * vscsi_stats_attrs[] = { + &vscsi_stats_interrupts_attr, + &vscsi_stats_read_ops_attr, + &vscsi_stats_write_ops_attr, + &vscsi_stats_crq_msgs_attr, + &vscsi_stats_iu_allocs_attr, + &vscsi_stats_bio_allocs_attr, + &vscsi_stats_buf_allocs_attr, + &vscsi_stats_errors_attr, + NULL, +}; + + +static ssize_t vscsi_stats_show(struct kobject * kobj, struct attribute * attr, char * buf) +{ + struct server_adapter *adapter= container_of(kobj, struct server_adapter, stats_kobj); + if (attr == &vscsi_stats_interrupts_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->interrupts)); + if (attr == &vscsi_stats_read_ops_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->read_processed)); + if (attr == &vscsi_stats_write_ops_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->write_processed)); + if (attr == &vscsi_stats_crq_msgs_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->crq_processed)); + if (attr == &vscsi_stats_iu_allocs_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->iu_count)); + if (attr == &vscsi_stats_bio_allocs_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->bio_count)); + if (attr == &vscsi_stats_buf_allocs_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->buffers_allocated)); + if (attr == &vscsi_stats_errors_attr) + return sprintf(buf, "%d\n", + atomic_read(&adapter->errors)); + + BUG(); + return 0; +} + +static struct sysfs_ops vscsi_stats_ops = { + .show = vscsi_stats_show, + .store = NULL, +}; + +static struct kobj_type ktype_vscsi_stats = { + .release = NULL, + .sysfs_ops = &vscsi_stats_ops, + .default_attrs = vscsi_stats_attrs, +}; + + +static int ibmvscsis_probe(struct vio_dev *dev, const struct vio_device_id *id) +{ + struct server_adapter *adapter; + int rc; + unsigned int *dma_window; + unsigned int dma_window_property_size; + + adapter = kmalloc(sizeof(*adapter), GFP_KERNEL); + if (!adapter) { + err("couldn't allocate adapter memory\n"); + return -1; + } + memset(adapter, 0x00, sizeof(*adapter)); + adapter->dma_dev = dev; + adapter->dev = &dev->dev; + dev->driver_data = adapter; + sprintf(adapter->name, "%x", dev->unit_address); + adapter->lock = SPIN_LOCK_UNLOCKED; + + dma_window = + (unsigned int *)vio_get_attribute(dev, "ibm,my-dma-window", + &dma_window_property_size); + if (!dma_window) { + warn("Couldn't find ibm,my-dma-window property\n"); + } + + adapter->liobn = dma_window[0]; + /* RPA docs say that #address-cells is always 1 for virtual + devices, but some older boxes' OF returns 2. This should + be removed by GA, unless there is legacy OFs that still + have 2 or 3 for #address-cells */ + /*adapter->riobn = dma_window[2+vio_num_address_cells]; */ + + /* This is just an ugly kludge. Remove as soon as the OF for all + machines actually follow the spec and encodes the offset field + as phys-encode (that is, #address-cells wide) */ + if (dma_window_property_size == 24) { + adapter->riobn = dma_window[3]; + } else if (dma_window_property_size == 40) { + adapter->riobn = dma_window[5]; + } else { + warn("Invalid size of ibm,my-dma-window=%i\n", + dma_window_property_size); + } + + INIT_WORK(&adapter->crq_task, crq_task, adapter); + + tasklet_init(&adapter->endio_tasklet, + endio_task, (unsigned long)adapter); + + INIT_LIST_HEAD(&adapter->inflight); + + /* Initialize the buffer cache */ + init_data_buffer(adapter); + + /* Arbitrarily support 16 IUs right now */ + rc = initialize_iu_pool(adapter, 16); + if (rc) { + kfree(adapter); + return rc; + } + + rc = initialize_crq_queue(&adapter->queue, adapter); + if (rc != 0) { + kfree(adapter); + return rc; + } + + set_num_buses(&dev->dev, 1); + device_create_file(&dev->dev, &dev_attr_num_buses); + + adapter->stats_kobj.parent = &dev->dev.kobj; + strcpy(adapter->stats_kobj.name, "stats"); + adapter->stats_kobj.ktype = & ktype_vscsi_stats; + kobject_register(&adapter->stats_kobj); + + return 0; +} + +static int ibmvscsis_remove(struct vio_dev *dev) +{ + int bus; + int target; + unsigned long flags; + struct server_adapter *adapter = + (struct server_adapter *)dev->driver_data; + + spin_lock_irqsave(&adapter->lock, flags); + + /* + * Loop through the bus + */ + for (bus = 0; bus < BUS_PER_ADAPTER; bus++) { + /* If this bus exists */ + if (adapter->vbus[bus]) { + /* loop through the targets */ + for (target = 0; target < TARGETS_PER_BUS; target++) { + /* If the target exists */ + if (adapter->vbus[bus]->vdev[target] && + !adapter->vbus[bus]->vdev[target] + ->disabled) { + deactivate_block_device(adapter-> + vbus[bus]->vdev[target]); + } + } + spin_unlock_irqrestore(&adapter->lock, flags); + set_num_targets(adapter->vbus[bus], 0); + spin_lock_irqsave(&adapter->lock, flags); + } + } + + spin_unlock_irqrestore(&adapter->lock, flags); + set_num_buses(adapter->dev, 0); + release_crq_queue(&adapter->queue, adapter); + + release_iu_pool(adapter); + + release_data_buffer(adapter); + + kobject_unregister(&adapter->stats_kobj); + device_remove_file(&dev->dev, &dev_attr_num_buses); + + kfree(adapter); + + return 0; +} + +static struct vio_device_id ibmvscsis_device_table[] __devinitdata = { + {"v-scsi-host", "IBM,v-scsi-host"}, + {0,} +}; + +MODULE_DEVICE_TABLE(vio, ibmvscsis_device_table); + +static struct vio_driver ibmvscsis_driver = { + .name = "ibmvscsis", + .id_table = ibmvscsis_device_table, + .probe = ibmvscsis_probe, + .remove = ibmvscsis_remove, +}; + +static int mod_init(void) +{ + struct device_node *rootdn; + char *ppartition_name; + char *psystem_id; + char *pmodel; + unsigned int *p_number_ptr; + int rc; + + /* Retrieve information about this partition */ + rootdn = find_path_device("/"); + if (rootdn) { + pmodel = get_property(rootdn, "model", NULL); + psystem_id = get_property(rootdn, "system-id", NULL); + if (pmodel && psystem_id) + snprintf(system_id,sizeof(system_id), + "%s-%s", + pmodel, psystem_id); + ppartition_name = + get_property(rootdn, "ibm,partition-name", NULL); + if (ppartition_name) + strncpy(partition_name, ppartition_name, + sizeof(partition_name)); + p_number_ptr = + (unsigned int *)get_property(rootdn, "ibm,partition-no", + NULL); + if (p_number_ptr) + partition_number = *p_number_ptr; + } + + info("initialized version "IBMVSCSIS_VERSION"\n"); + + rc = vio_register_driver(&ibmvscsis_driver); + + if (rc) { + warn("rc %d from vio_register_driver\n", rc); + } + + return rc; +} + +static void mod_exit(void) +{ + info("terminated\n"); + + vio_unregister_driver(&ibmvscsis_driver); +} + +module_init(mod_init); +module_exit(mod_exit); diff -aurN a/include/asm-ppc64/vio.h b/include/asm-ppc64/vio.h --- a/include/asm-ppc64/vio.h 2005-06-17 15:48:29.000000000 -0400 +++ b/include/asm-ppc64/vio.h 2005-06-18 12:02:58.000000000 -0400 @@ -91,6 +91,7 @@ char *type; uint32_t unit_address; unsigned int irq; + void *driver_data; struct device dev; };