]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - arch/powerpc/platforms/iseries/vio.c
[POWERPC] iSeries: Move viodasd probing
[linux-2.6-omap-h63xx.git] / arch / powerpc / platforms / iseries / vio.c
index ad36ab0639f0496df1f1c1cbbe155c2b26d421f1..910b00b4703e47e237d0b56f63eefd2085e70347 100644 (file)
 /*
- * IBM PowerPC iSeries Virtual I/O Infrastructure Support.
+ * Legacy iSeries specific vio initialisation
+ * that needs to be built in (not a module).
  *
- *    Copyright (c) 2005 Stephen Rothwell, IBM Corp.
+ * Â© Copyright 2007 IBM Corporation
+ *     Author: Stephen Rothwell
+ *     Some parts collected from various other files
  *
- *      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 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
  */
-#include <linux/types.h>
-#include <linux/device.h>
+#include <linux/of.h>
 #include <linux/init.h>
+#include <linux/gfp.h>
+#include <linux/completion.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
 
+#include <asm/firmware.h>
 #include <asm/vio.h>
-#include <asm/iommu.h>
-#include <asm/tce.h>
-#include <asm/abs_addr.h>
-#include <asm/page.h>
 #include <asm/iseries/vio.h>
+#include <asm/iseries/iommu.h>
 #include <asm/iseries/hv_types.h>
-#include <asm/iseries/hv_lp_config.h>
-#include <asm/iseries/hv_call_xm.h>
+#include <asm/iseries/hv_lp_event.h>
 
-#include "iommu.h"
+#define FIRST_VTY      0
+#define NUM_VTYS       1
+#define FIRST_VSCSI    (FIRST_VTY + NUM_VTYS)
+#define NUM_VSCSIS     1
+#define FIRST_VLAN     (FIRST_VSCSI + NUM_VSCSIS)
+#define NUM_VLANS      HVMAXARCHITECTEDVIRTUALLANS
+#define FIRST_VIODASD  (FIRST_VLAN + NUM_VLANS)
+#define NUM_VIODASDS   HVMAXARCHITECTEDVIRTUALDISKS
+#define FIRST_VIOCD    (FIRST_VIODASD + NUM_VIODASDS)
+#define NUM_VIOCDS     HVMAXARCHITECTEDVIRTUALCDROMS
+#define FIRST_VIOTAPE  (FIRST_VIOCD + NUM_VIOCDS)
+#define NUM_VIOTAPES   HVMAXARCHITECTEDVIRTUALTAPES
 
-struct device *iSeries_vio_dev = &vio_bus_device.dev;
-EXPORT_SYMBOL(iSeries_vio_dev);
+struct vio_waitevent {
+       struct completion       com;
+       int                     rc;
+       u16                     sub_result;
+};
 
-static struct iommu_table veth_iommu_table;
-static struct iommu_table vio_iommu_table;
+struct vio_resource {
+       char    rsrcname[10];
+       char    type[4];
+       char    model[3];
+};
 
-static void __init iommu_vio_init(void)
+static struct property *new_property(const char *name, int length,
+               const void *value)
 {
-       iommu_table_getparms_iSeries(255, 0, 0xff, &veth_iommu_table);
-       veth_iommu_table.it_size /= 2;
-       vio_iommu_table = veth_iommu_table;
-       vio_iommu_table.it_offset += veth_iommu_table.it_size;
+       struct property *np = kzalloc(sizeof(*np) + strlen(name) + 1 + length,
+                       GFP_KERNEL);
 
-       if (!iommu_init_table(&veth_iommu_table))
-               printk("Virtual Bus VETH TCE table failed.\n");
-       if (!iommu_init_table(&vio_iommu_table))
-               printk("Virtual Bus VIO TCE table failed.\n");
+       if (!np)
+               return NULL;
+       np->name = (char *)(np + 1);
+       np->value = np->name + strlen(name) + 1;
+       strcpy(np->name, name);
+       memcpy(np->value, value, length);
+       np->length = length;
+       return np;
 }
 
-/**
- * vio_register_device_iseries: - Register a new iSeries vio device.
- * @voidev:    The device to register.
- */
-static struct vio_dev *__init vio_register_device_iseries(char *type,
-               uint32_t unit_num)
+static void __init free_property(struct property *np)
+{
+       kfree(np);
+}
+
+static struct device_node *new_node(const char *path,
+               struct device_node *parent)
 {
-       struct vio_dev *viodev;
+       struct device_node *np = kzalloc(sizeof(*np), GFP_KERNEL);
 
-       /* allocate a vio_dev for this device */
-       viodev = kmalloc(sizeof(struct vio_dev), GFP_KERNEL);
-       if (!viodev)
+       if (!np)
                return NULL;
-       memset(viodev, 0, sizeof(struct vio_dev));
+       np->full_name = kmalloc(strlen(path) + 1, GFP_KERNEL);
+       if (!np->full_name) {
+               kfree(np);
+               return NULL;
+       }
+       strcpy(np->full_name, path);
+       of_node_set_flag(np, OF_DYNAMIC);
+       kref_init(&np->kref);
+       np->parent = of_node_get(parent);
+       return np;
+}
 
-       snprintf(viodev->dev.bus_id, BUS_ID_SIZE, "%s%d", type, unit_num);
+static void free_node(struct device_node *np)
+{
+       struct property *next;
+       struct property *prop;
 
-       viodev->name = viodev->dev.bus_id;
-       viodev->type = type;
-       viodev->unit_address = unit_num;
-       viodev->iommu_table = &vio_iommu_table;
-       if (vio_register_device(viodev) == NULL) {
-               kfree(viodev);
-               return NULL;
+       next = np->properties;
+       while (next) {
+               prop = next;
+               next = prop->next;
+               free_property(prop);
        }
-       return viodev;
+       of_node_put(np->parent);
+       kfree(np->full_name);
+       kfree(np);
 }
 
-void __init probe_bus_iseries(void)
+static int add_string_property(struct device_node *np, const char *name,
+               const char *value)
 {
-       HvLpIndexMap vlan_map;
-       struct vio_dev *viodev;
-       int i;
+       struct property *nprop = new_property(name, strlen(value) + 1, value);
 
-       /* there is only one of each of these */
-       vio_register_device_iseries("viocons", 0);
-       vio_register_device_iseries("vscsi", 0);
+       if (!nprop)
+               return 0;
+       prom_add_property(np, nprop);
+       return 1;
+}
+
+static int add_raw_property(struct device_node *np, const char *name,
+               int length, const void *value)
+{
+       struct property *nprop = new_property(name, length, value);
+
+       if (!nprop)
+               return 0;
+       prom_add_property(np, nprop);
+       return 1;
+}
+
+static struct device_node *do_device_node(struct device_node *parent,
+               const char *name, u32 reg, u32 unit, const char *type,
+               const char *compat, struct vio_resource *res)
+{
+       struct device_node *np;
+       char path[32];
 
-       vlan_map = HvLpConfig_getVirtualLanIndexMap();
-       for (i = 0; i < HVMAXARCHITECTEDVIRTUALLANS; i++) {
-               if ((vlan_map & (0x8000 >> i)) == 0)
-                       continue;
-               viodev = vio_register_device_iseries("vlan", i);
-               /* veth is special and has it own iommu_table */
-               viodev->iommu_table = &veth_iommu_table;
+       snprintf(path, sizeof(path), "/vdevice/%s@%08x", name, reg);
+       np = new_node(path, parent);
+       if (!np)
+               return NULL;
+       if (!add_string_property(np, "name", name) ||
+               !add_string_property(np, "device_type", type) ||
+               !add_string_property(np, "compatible", compat) ||
+               !add_raw_property(np, "reg", sizeof(reg), &reg) ||
+               !add_raw_property(np, "linux,unit_address",
+                       sizeof(unit), &unit)) {
+               goto node_free;
+       }
+       if (res) {
+               if (!add_raw_property(np, "linux,vio_rsrcname",
+                               sizeof(res->rsrcname), res->rsrcname) ||
+                       !add_raw_property(np, "linux,vio_type",
+                               sizeof(res->type), res->type) ||
+                       !add_raw_property(np, "linux,vio_model",
+                               sizeof(res->model), res->model))
+                       goto node_free;
+       }
+       np->name = of_get_property(np, "name", NULL);
+       np->type = of_get_property(np, "device_type", NULL);
+       of_attach_node(np);
+#ifdef CONFIG_PROC_DEVICETREE
+       if (parent->pde) {
+               struct proc_dir_entry *ent;
+
+               ent = proc_mkdir(strrchr(np->full_name, '/') + 1, parent->pde);
+               if (ent)
+                       proc_device_tree_add_node(np, ent);
        }
-       for (i = 0; i < HVMAXARCHITECTEDVIRTUALDISKS; i++)
-               vio_register_device_iseries("viodasd", i);
-       for (i = 0; i < HVMAXARCHITECTEDVIRTUALCDROMS; i++)
-               vio_register_device_iseries("viocd", i);
-       for (i = 0; i < HVMAXARCHITECTEDVIRTUALTAPES; i++)
-               vio_register_device_iseries("viotape", i);
+#endif
+       return np;
+
+ node_free:
+       free_node(np);
+       return NULL;
 }
 
-/**
- * vio_match_device_iseries: - Tell if a iSeries VIO device matches a
- *     vio_device_id
+/*
+ * This is here so that we can dynamically add viodasd
+ * devices without exposing all the above infrastructure.
  */
-static int vio_match_device_iseries(const struct vio_device_id *id,
-               const struct vio_dev *dev)
+struct vio_dev *vio_create_viodasd(u32 unit)
+{
+       struct device_node *vio_root;
+       struct device_node *np;
+       struct vio_dev *vdev = NULL;
+
+       vio_root = of_find_node_by_path("/vdevice");
+       if (!vio_root)
+               return NULL;
+       np = do_device_node(vio_root, "viodasd", FIRST_VIODASD + unit, unit,
+                       "block", "IBM,iSeries-viodasd", NULL);
+       of_node_put(vio_root);
+       if (np) {
+               vdev = vio_register_device_node(np);
+               if (!vdev)
+                       free_node(np);
+       }
+       return vdev;
+}
+EXPORT_SYMBOL_GPL(vio_create_viodasd);
+
+static void __init handle_block_event(struct HvLpEvent *event)
 {
-       return strncmp(dev->type, id->type, strlen(id->type)) == 0;
+       struct vioblocklpevent *bevent = (struct vioblocklpevent *)event;
+       struct vio_waitevent *pwe;
+
+       if (event == NULL)
+               /* Notification that a partition went away! */
+               return;
+       /* First, we should NEVER get an int here...only acks */
+       if (hvlpevent_is_int(event)) {
+               printk(KERN_WARNING "handle_viod_request: "
+                      "Yikes! got an int in viodasd event handler!\n");
+               if (hvlpevent_need_ack(event)) {
+                       event->xRc = HvLpEvent_Rc_InvalidSubtype;
+                       HvCallEvent_ackLpEvent(event);
+               }
+               return;
+       }
+
+       switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {
+       case vioblockopen:
+               /*
+                * Handle a response to an open request.  We get all the
+                * disk information in the response, so update it.  The
+                * correlation token contains a pointer to a waitevent
+                * structure that has a completion in it.  update the
+                * return code in the waitevent structure and post the
+                * completion to wake up the guy who sent the request
+                */
+               pwe = (struct vio_waitevent *)event->xCorrelationToken;
+               pwe->rc = event->xRc;
+               pwe->sub_result = bevent->sub_result;
+               complete(&pwe->com);
+               break;
+       case vioblockclose:
+               break;
+       default:
+               printk(KERN_WARNING "handle_viod_request: unexpected subtype!");
+               if (hvlpevent_need_ack(event)) {
+                       event->xRc = HvLpEvent_Rc_InvalidSubtype;
+                       HvCallEvent_ackLpEvent(event);
+               }
+       }
 }
 
-static struct vio_bus_ops vio_bus_ops_iseries = {
-       .match = vio_match_device_iseries,
-};
+static void __init probe_disk(struct device_node *vio_root, u32 unit)
+{
+       HvLpEvent_Rc hvrc;
+       struct vio_waitevent we;
+       u16 flags = 0;
 
-/**
- * vio_bus_init_iseries: - Initialize the iSeries virtual IO bus
- */
-static int __init vio_bus_init_iseries(void)
+retry:
+       init_completion(&we.com);
+
+       /* Send the open event to OS/400 */
+       hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+                       HvLpEvent_Type_VirtualIo,
+                       viomajorsubtype_blockio | vioblockopen,
+                       HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+                       viopath_sourceinst(viopath_hostLp),
+                       viopath_targetinst(viopath_hostLp),
+                       (u64)(unsigned long)&we, VIOVERSION << 16,
+                       ((u64)unit << 48) | ((u64)flags<< 32),
+                       0, 0, 0);
+       if (hvrc != 0) {
+               printk(KERN_WARNING "probe_disk: bad rc on HV open %d\n",
+                       (int)hvrc);
+               return;
+       }
+
+       wait_for_completion(&we.com);
+
+       if (we.rc != 0) {
+               if (flags != 0)
+                       return;
+               /* try again with read only flag set */
+               flags = vioblockflags_ro;
+               goto retry;
+       }
+
+       /* Send the close event to OS/400.  We DON'T expect a response */
+       hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+                       HvLpEvent_Type_VirtualIo,
+                       viomajorsubtype_blockio | vioblockclose,
+                       HvLpEvent_AckInd_NoAck, HvLpEvent_AckType_ImmediateAck,
+                       viopath_sourceinst(viopath_hostLp),
+                       viopath_targetinst(viopath_hostLp),
+                       0, VIOVERSION << 16,
+                       ((u64)unit << 48) | ((u64)flags << 32),
+                       0, 0, 0);
+       if (hvrc != 0) {
+               printk(KERN_WARNING "probe_disk: "
+                      "bad rc sending event to OS/400 %d\n", (int)hvrc);
+               return;
+       }
+
+       do_device_node(vio_root, "viodasd", FIRST_VIODASD + unit, unit,
+                       "block", "IBM,iSeries-viodasd", NULL);
+}
+
+static void __init get_viodasd_info(struct device_node *vio_root)
 {
-       int err;
+       int rc;
+       u32 unit;
 
-       err = vio_bus_init(&vio_bus_ops_iseries);
-       if (err == 0) {
-               iommu_vio_init();
-               vio_bus_device.iommu_table = &vio_iommu_table;
-               iSeries_vio_dev = &vio_bus_device.dev;
-               probe_bus_iseries();
+       rc = viopath_open(viopath_hostLp, viomajorsubtype_blockio, 2);
+       if (rc) {
+               printk(KERN_WARNING "get_viodasd_info: "
+                      "error opening path to host partition %d\n",
+                      viopath_hostLp);
+               return;
        }
-       return err;
+
+       /* Initialize our request handler */
+       vio_setHandler(viomajorsubtype_blockio, handle_block_event);
+
+       for (unit = 0; unit < HVMAXARCHITECTEDVIRTUALDISKS; unit++)
+               probe_disk(vio_root, unit);
+
+       vio_clearHandler(viomajorsubtype_blockio);
+       viopath_close(viopath_hostLp, viomajorsubtype_blockio, 2);
 }
 
-__initcall(vio_bus_init_iseries);
+static void __init handle_cd_event(struct HvLpEvent *event)
+{
+       struct viocdlpevent *bevent;
+       struct vio_waitevent *pwe;
+
+       if (!event)
+               /* Notification that a partition went away! */
+               return;
+
+       /* First, we should NEVER get an int here...only acks */
+       if (hvlpevent_is_int(event)) {
+               printk(KERN_WARNING "handle_cd_event: got an unexpected int\n");
+               if (hvlpevent_need_ack(event)) {
+                       event->xRc = HvLpEvent_Rc_InvalidSubtype;
+                       HvCallEvent_ackLpEvent(event);
+               }
+               return;
+       }
+
+       bevent = (struct viocdlpevent *)event;
+
+       switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {
+       case viocdgetinfo:
+               pwe = (struct vio_waitevent *)event->xCorrelationToken;
+               pwe->rc = event->xRc;
+               pwe->sub_result = bevent->sub_result;
+               complete(&pwe->com);
+               break;
+
+       default:
+               printk(KERN_WARNING "handle_cd_event: "
+                       "message with unexpected subtype %0x04X!\n",
+                       event->xSubtype & VIOMINOR_SUBTYPE_MASK);
+               if (hvlpevent_need_ack(event)) {
+                       event->xRc = HvLpEvent_Rc_InvalidSubtype;
+                       HvCallEvent_ackLpEvent(event);
+               }
+       }
+}
+
+static void __init get_viocd_info(struct device_node *vio_root)
+{
+       HvLpEvent_Rc hvrc;
+       u32 unit;
+       struct vio_waitevent we;
+       struct vio_resource *unitinfo;
+       dma_addr_t unitinfo_dmaaddr;
+       int ret;
+
+       ret = viopath_open(viopath_hostLp, viomajorsubtype_cdio, 2);
+       if (ret) {
+               printk(KERN_WARNING
+                       "get_viocd_info: error opening path to host partition %d\n",
+                       viopath_hostLp);
+               return;
+       }
+
+       /* Initialize our request handler */
+       vio_setHandler(viomajorsubtype_cdio, handle_cd_event);
+
+       unitinfo = iseries_hv_alloc(
+                       sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS,
+                       &unitinfo_dmaaddr, GFP_ATOMIC);
+       if (!unitinfo) {
+               printk(KERN_WARNING
+                       "get_viocd_info: error allocating unitinfo\n");
+               goto clear_handler;
+       }
+
+       memset(unitinfo, 0, sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS);
+
+       init_completion(&we.com);
+
+       hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+                       HvLpEvent_Type_VirtualIo,
+                       viomajorsubtype_cdio | viocdgetinfo,
+                       HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+                       viopath_sourceinst(viopath_hostLp),
+                       viopath_targetinst(viopath_hostLp),
+                       (u64)&we, VIOVERSION << 16, unitinfo_dmaaddr, 0,
+                       sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS, 0);
+       if (hvrc != HvLpEvent_Rc_Good) {
+               printk(KERN_WARNING
+                       "get_viocd_info: cdrom error sending event. rc %d\n",
+                       (int)hvrc);
+               goto hv_free;
+       }
+
+       wait_for_completion(&we.com);
+
+       if (we.rc) {
+               printk(KERN_WARNING "get_viocd_info: bad rc %d:0x%04X\n",
+                       we.rc, we.sub_result);
+               goto hv_free;
+       }
+
+       for (unit = 0; (unit < HVMAXARCHITECTEDVIRTUALCDROMS) &&
+                       unitinfo[unit].rsrcname[0]; unit++) {
+               if (!do_device_node(vio_root, "viocd", FIRST_VIOCD + unit, unit,
+                               "block", "IBM,iSeries-viocd", &unitinfo[unit]))
+                       break;
+       }
+
+ hv_free:
+       iseries_hv_free(sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS,
+                       unitinfo, unitinfo_dmaaddr);
+ clear_handler:
+       vio_clearHandler(viomajorsubtype_cdio);
+       viopath_close(viopath_hostLp, viomajorsubtype_cdio, 2);
+}
+
+/* Handle interrupt events for tape */
+static void __init handle_tape_event(struct HvLpEvent *event)
+{
+       struct vio_waitevent *we;
+       struct viotapelpevent *tevent = (struct viotapelpevent *)event;
+
+       if (event == NULL)
+               /* Notification that a partition went away! */
+               return;
+
+       we = (struct vio_waitevent *)event->xCorrelationToken;
+       switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {
+       case viotapegetinfo:
+               we->rc = tevent->sub_type_result;
+               complete(&we->com);
+               break;
+       default:
+               printk(KERN_WARNING "handle_tape_event: weird ack\n");
+       }
+}
+
+static void __init get_viotape_info(struct device_node *vio_root)
+{
+       HvLpEvent_Rc hvrc;
+       u32 unit;
+       struct vio_resource *unitinfo;
+       dma_addr_t unitinfo_dmaaddr;
+       size_t len = sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALTAPES;
+       struct vio_waitevent we;
+       int ret;
+
+       ret = viopath_open(viopath_hostLp, viomajorsubtype_tape, 2);
+       if (ret) {
+               printk(KERN_WARNING "get_viotape_info: "
+                       "error on viopath_open to hostlp %d\n", ret);
+               return;
+       }
+
+       vio_setHandler(viomajorsubtype_tape, handle_tape_event);
+
+       unitinfo = iseries_hv_alloc(len, &unitinfo_dmaaddr, GFP_ATOMIC);
+       if (!unitinfo)
+               goto clear_handler;
+
+       memset(unitinfo, 0, len);
+
+       hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+                       HvLpEvent_Type_VirtualIo,
+                       viomajorsubtype_tape | viotapegetinfo,
+                       HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+                       viopath_sourceinst(viopath_hostLp),
+                       viopath_targetinst(viopath_hostLp),
+                       (u64)(unsigned long)&we, VIOVERSION << 16,
+                       unitinfo_dmaaddr, len, 0, 0);
+       if (hvrc != HvLpEvent_Rc_Good) {
+               printk(KERN_WARNING "get_viotape_info: hv error on op %d\n",
+                               (int)hvrc);
+               goto hv_free;
+       }
+
+       wait_for_completion(&we.com);
+
+       for (unit = 0; (unit < HVMAXARCHITECTEDVIRTUALTAPES) &&
+                       unitinfo[unit].rsrcname[0]; unit++) {
+               if (!do_device_node(vio_root, "viotape", FIRST_VIOTAPE + unit,
+                               unit, "byte", "IBM,iSeries-viotape",
+                               &unitinfo[unit]))
+                       break;
+       }
+
+ hv_free:
+       iseries_hv_free(len, unitinfo, unitinfo_dmaaddr);
+ clear_handler:
+       vio_clearHandler(viomajorsubtype_tape);
+       viopath_close(viopath_hostLp, viomajorsubtype_tape, 2);
+}
+
+static int __init iseries_vio_init(void)
+{
+       struct device_node *vio_root;
+
+       if (!firmware_has_feature(FW_FEATURE_ISERIES))
+               return -ENODEV;
+
+       iommu_vio_init();
+
+       vio_root = of_find_node_by_path("/vdevice");
+       if (!vio_root)
+               return -ENODEV;
+
+       if (viopath_hostLp == HvLpIndexInvalid) {
+               vio_set_hostlp();
+               /* If we don't have a host, bail out */
+               if (viopath_hostLp == HvLpIndexInvalid)
+                       goto put_node;
+       }
+
+       get_viodasd_info(vio_root);
+       get_viocd_info(vio_root);
+       get_viotape_info(vio_root);
+
+       return 0;
+
+ put_node:
+       of_node_put(vio_root);
+       return -ENODEV;
+}
+arch_initcall(iseries_vio_init);