]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - arch/powerpc/platforms/iseries/vio.c
[POWERPC] iSeries: Move detection of virtual tapes
[linux-2.6-omap-h63xx.git] / arch / powerpc / platforms / iseries / vio.c
index c27a66876c2cd042bf32c7f0fff36d652ec63a1f..a4cc990a26a0ac02eda8e7377fff866cda546dfb 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 <asm/vio.h>
-#include <asm/iommu.h>
-#include <asm/tce.h>
-#include <asm/abs_addr.h>
-#include <asm/page.h>
+#include <asm/firmware.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>
 
-struct device *iSeries_vio_dev = &vio_bus_device.dev;
-EXPORT_SYMBOL(iSeries_vio_dev);
+#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
 
-static struct iommu_table veth_iommu_table;
-static struct iommu_table vio_iommu_table;
+struct vio_waitevent {
+       struct completion       com;
+       int                     rc;
+       u16                     sub_result;
+};
+
+struct vio_resource {
+       char    rsrcname[10];
+       char    type[4];
+       char    model[3];
+};
 
-static void __init iommu_vio_init(void)
+static struct property * __init new_property(const char *name, int length,
+               const void *value)
 {
-       struct iommu_table *t;
-       struct iommu_table_cb cb;
-       unsigned long cbp;
-       unsigned long itc_entries;
-
-       cb.itc_busno = 255;    /* Bus 255 is the virtual bus */
-       cb.itc_virtbus = 0xff; /* Ask for virtual bus */
-
-       cbp = virt_to_abs(&cb);
-       HvCallXm_getTceTableParms(cbp);
-
-       itc_entries = cb.itc_size * PAGE_SIZE / sizeof(union tce_entry);
-       veth_iommu_table.it_size        = itc_entries / 2;
-       veth_iommu_table.it_busno       = cb.itc_busno;
-       veth_iommu_table.it_offset      = cb.itc_offset;
-       veth_iommu_table.it_index       = cb.itc_index;
-       veth_iommu_table.it_type        = TCE_VB;
-       veth_iommu_table.it_blocksize   = 1;
-
-       t = iommu_init_table(&veth_iommu_table);
-
-       if (!t)
-               printk("Virtual Bus VETH TCE table failed.\n");
-
-       vio_iommu_table.it_size         = itc_entries - veth_iommu_table.it_size;
-       vio_iommu_table.it_busno        = cb.itc_busno;
-       vio_iommu_table.it_offset       = cb.itc_offset +
-                                         veth_iommu_table.it_size;
-       vio_iommu_table.it_index        = cb.itc_index;
-       vio_iommu_table.it_type         = TCE_VB;
-       vio_iommu_table.it_blocksize    = 1;
-
-       t = iommu_init_table(&vio_iommu_table);
-
-       if (!t)
-               printk("Virtual Bus VIO TCE table failed.\n");
+       struct property *np = kzalloc(sizeof(*np) + strlen(name) + 1 + length,
+                       GFP_KERNEL);
+
+       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 * __init 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;
+       np->full_name = kmalloc(strlen(path) + 1, GFP_KERNEL);
+       if (!np->full_name) {
+               kfree(np);
                return NULL;
-       memset(viodev, 0, sizeof(struct vio_dev));
+       }
+       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 __init 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);
+}
+
+static int __init add_string_property(struct device_node *np, const char *name,
+               const char *value)
+{
+       struct property *nprop = new_property(name, strlen(value) + 1, value);
+
+       if (!nprop)
+               return 0;
+       prom_add_property(np, nprop);
+       return 1;
 }
 
-void __init probe_bus_iseries(void)
+static int __init add_raw_property(struct device_node *np, const char *name,
+               int length, const void *value)
 {
-       HvLpIndexMap vlan_map;
-       struct vio_dev *viodev;
-       int i;
-
-       /* there is only one of each of these */
-       vio_register_device_iseries("viocons", 0);
-       vio_register_device_iseries("vscsi", 0);
-
-       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;
+       struct property *nprop = new_property(name, length, value);
+
+       if (!nprop)
+               return 0;
+       prom_add_property(np, nprop);
+       return 1;
+}
+
+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);
+               }
        }
-       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);
 }
 
-/**
- * vio_match_device_iseries: - Tell if a iSeries VIO device matches a
- *     vio_device_id
- */
-static int vio_match_device_iseries(const struct vio_device_id *id,
-               const struct vio_dev *dev)
+static void __init get_viocd_info(struct device_node *vio_root)
 {
-       return strncmp(dev->type, id->type, strlen(id->type)) == 0;
+       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++) {
+               struct device_node *np;
+               char name[64];
+               u32 reg = FIRST_VIOCD + unit;
+
+               snprintf(name, sizeof(name), "/vdevice/viocd@%08x", reg);
+               np = new_node(name, vio_root);
+               if (!np)
+                       goto hv_free;
+               if (!add_string_property(np, "name", "viocd") ||
+                       !add_string_property(np, "device_type", "block") ||
+                       !add_string_property(np, "compatible",
+                               "IBM,iSeries-viocd") ||
+                       !add_raw_property(np, "reg", sizeof(reg), &reg) ||
+                       !add_raw_property(np, "linux,unit_address",
+                               sizeof(unit), &unit) ||
+                       !add_raw_property(np, "linux,vio_rsrcname",
+                               sizeof(unitinfo[unit].rsrcname),
+                               unitinfo[unit].rsrcname) ||
+                       !add_raw_property(np, "linux,vio_type",
+                               sizeof(unitinfo[unit].type),
+                               unitinfo[unit].type) ||
+                       !add_raw_property(np, "linux,vio_model",
+                               sizeof(unitinfo[unit].model),
+                               unitinfo[unit].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 (vio_root->pde) {
+                       struct proc_dir_entry *ent;
+
+                       ent = proc_mkdir(strrchr(np->full_name, '/') + 1,
+                                       vio_root->pde);
+                       if (ent)
+                               proc_device_tree_add_node(np, ent);
+               }
+#endif
+               continue;
+
+ node_free:
+               free_node(np);
+               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);
 }
 
-static struct vio_bus_ops vio_bus_ops_iseries = {
-       .match = vio_match_device_iseries,
-};
+/* Handle interrupt events for tape */
+static void __init handle_tape_event(struct HvLpEvent *event)
+{
+       struct vio_waitevent *we;
+       struct viotapelpevent *tevent = (struct viotapelpevent *)event;
 
-/**
- * vio_bus_init_iseries: - Initialize the iSeries virtual IO bus
- */
-static int __init vio_bus_init_iseries(void)
+       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)
 {
-       int err;
-
-       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();
+       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;
        }
-       return err;
+
+       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++) {
+               struct device_node *np;
+               char name[64];
+               u32 reg = FIRST_VIOTAPE + unit;
+
+               snprintf(name, sizeof(name), "/vdevice/viotape@%08x", reg);
+               np = new_node(name, vio_root);
+               if (!np)
+                       goto hv_free;
+               if (!add_string_property(np, "name", "viotape") ||
+                       !add_string_property(np, "device_type", "byte") ||
+                       !add_string_property(np, "compatible",
+                               "IBM,iSeries-viotape") ||
+                       !add_raw_property(np, "reg", sizeof(reg), &reg) ||
+                       !add_raw_property(np, "linux,unit_address",
+                               sizeof(unit), &unit) ||
+                       !add_raw_property(np, "linux,vio_rsrcname",
+                               sizeof(unitinfo[unit].rsrcname),
+                               unitinfo[unit].rsrcname) ||
+                       !add_raw_property(np, "linux,vio_type",
+                               sizeof(unitinfo[unit].type),
+                               unitinfo[unit].type) ||
+                       !add_raw_property(np, "linux,vio_model",
+                               sizeof(unitinfo[unit].model),
+                               unitinfo[unit].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 (vio_root->pde) {
+                       struct proc_dir_entry *ent;
+
+                       ent = proc_mkdir(strrchr(np->full_name, '/') + 1,
+                                       vio_root->pde);
+                       if (ent)
+                               proc_device_tree_add_node(np, ent);
+               }
+#endif
+               continue;
+
+ node_free:
+               free_node(np);
+               break;
+       }
+
+ hv_free:
+       iseries_hv_free(len, unitinfo, unitinfo_dmaaddr);
+ clear_handler:
+       vio_clearHandler(viomajorsubtype_tape);
+       viopath_close(viopath_hostLp, viomajorsubtype_tape, 2);
 }
 
-__initcall(vio_bus_init_iseries);
+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_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);