Logo Search packages:      
Sourcecode: linux version File versions  Download package

vio.c

/*
 * Legacy iSeries specific vio initialisation
 * that needs to be built in (not a module).
 *
 * © 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 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/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/iseries/vio.h>
#include <asm/iseries/iommu.h>
#include <asm/iseries/hv_types.h>
#include <asm/iseries/hv_lp_event.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

00050 struct vio_waitevent {
      struct completion com;
      int               rc;
      u16               sub_result;
};

00056 struct vio_resource {
      char  rsrcname[10];
      char  type[4];
      char  model[3];
};

static struct property *new_property(const char *name, int length,
            const void *value)
{
      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;
}

static void free_property(struct property *np)
{
      kfree(np);
}

static struct device_node *new_node(const char *path,
            struct device_node *parent)
{
      struct device_node *np = kzalloc(sizeof(*np), GFP_KERNEL);

      if (!np)
            return NULL;
      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;
}

static void free_node(struct device_node *np)
{
      struct property *next;
      struct property *prop;

      next = np->properties;
      while (next) {
            prop = next;
            next = prop->next;
            free_property(prop);
      }
      of_node_put(np->parent);
      kfree(np->full_name);
      kfree(np);
}

static int 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;
}

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];

      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);
      }
#endif
      return np;

 node_free:
      free_node(np);
      return NULL;
}

/*
 * This is here so that we can dynamically add viodasd
 * devices without exposing all the above infrastructure.
 */
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)
{
      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 void __init probe_disk(struct device_node *vio_root, u32 unit)
{
      HvLpEvent_Rc hvrc;
      struct vio_waitevent we;
      u16 flags = 0;

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 rc;
      u32 unit;

      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;
      }

      /* 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);
}

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;
      int ret = -ENODEV;

      if (!firmware_has_feature(FW_FEATURE_ISERIES))
            goto out;

      iommu_vio_init();

      vio_root = of_find_node_by_path("/vdevice");
      if (!vio_root)
            goto out;

      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);

      ret = 0;

 put_node:
      of_node_put(vio_root);
 out:
      return ret;
}
arch_initcall(iseries_vio_init);

Generated by  Doxygen 1.6.0   Back to index