/* * 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 struct vio_waitevent { struct completion com; int rc; u16 sub_result; }; 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), ®) || !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);