2869 lines
75 KiB
Diff
2869 lines
75 KiB
Diff
#! /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 <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/pagemap.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/blkdev.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/bio.h>
|
|
+
|
|
+#include <asm/hvcall.h>
|
|
+#include <asm/vio.h>
|
|
+#include <asm/iommu.h>
|
|
+
|
|
+#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;
|
|
};
|