Path: blob/master/arch/powerpc/platforms/iseries/vio.c
10820 views
/*1* Legacy iSeries specific vio initialisation2* that needs to be built in (not a module).3*4* © Copyright 2007 IBM Corporation5* Author: Stephen Rothwell6* Some parts collected from various other files7*8* This program is free software; you can redistribute it and/or9* modify it under the terms of the GNU General Public License as10* published by the Free Software Foundation; either version 2 of the11* License, or (at your option) any later version.12*13* This program is distributed in the hope that it will be useful, but14* WITHOUT ANY WARRANTY; without even the implied warranty of15* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU16* General Public License for more details.17*18* You should have received a copy of the GNU General Public License19* along with this program; if not, write to the Free Software Foundation,20* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA21*/22#include <linux/of.h>23#include <linux/init.h>24#include <linux/slab.h>25#include <linux/completion.h>26#include <linux/proc_fs.h>27#include <linux/module.h>2829#include <asm/firmware.h>30#include <asm/vio.h>31#include <asm/iseries/vio.h>32#include <asm/iseries/iommu.h>33#include <asm/iseries/hv_types.h>34#include <asm/iseries/hv_lp_event.h>3536#define FIRST_VTY 037#define NUM_VTYS 138#define FIRST_VSCSI (FIRST_VTY + NUM_VTYS)39#define NUM_VSCSIS 140#define FIRST_VLAN (FIRST_VSCSI + NUM_VSCSIS)41#define NUM_VLANS HVMAXARCHITECTEDVIRTUALLANS42#define FIRST_VIODASD (FIRST_VLAN + NUM_VLANS)43#define NUM_VIODASDS HVMAXARCHITECTEDVIRTUALDISKS44#define FIRST_VIOCD (FIRST_VIODASD + NUM_VIODASDS)45#define NUM_VIOCDS HVMAXARCHITECTEDVIRTUALCDROMS46#define FIRST_VIOTAPE (FIRST_VIOCD + NUM_VIOCDS)47#define NUM_VIOTAPES HVMAXARCHITECTEDVIRTUALTAPES4849struct vio_waitevent {50struct completion com;51int rc;52u16 sub_result;53};5455struct vio_resource {56char rsrcname[10];57char type[4];58char model[3];59};6061static struct property *new_property(const char *name, int length,62const void *value)63{64struct property *np = kzalloc(sizeof(*np) + strlen(name) + 1 + length,65GFP_KERNEL);6667if (!np)68return NULL;69np->name = (char *)(np + 1);70np->value = np->name + strlen(name) + 1;71strcpy(np->name, name);72memcpy(np->value, value, length);73np->length = length;74return np;75}7677static void free_property(struct property *np)78{79kfree(np);80}8182static struct device_node *new_node(const char *path,83struct device_node *parent)84{85struct device_node *np = kzalloc(sizeof(*np), GFP_KERNEL);8687if (!np)88return NULL;89np->full_name = kstrdup(path, GFP_KERNEL);90if (!np->full_name) {91kfree(np);92return NULL;93}94of_node_set_flag(np, OF_DYNAMIC);95kref_init(&np->kref);96np->parent = of_node_get(parent);97return np;98}99100static void free_node(struct device_node *np)101{102struct property *next;103struct property *prop;104105next = np->properties;106while (next) {107prop = next;108next = prop->next;109free_property(prop);110}111of_node_put(np->parent);112kfree(np->full_name);113kfree(np);114}115116static int add_string_property(struct device_node *np, const char *name,117const char *value)118{119struct property *nprop = new_property(name, strlen(value) + 1, value);120121if (!nprop)122return 0;123prom_add_property(np, nprop);124return 1;125}126127static int add_raw_property(struct device_node *np, const char *name,128int length, const void *value)129{130struct property *nprop = new_property(name, length, value);131132if (!nprop)133return 0;134prom_add_property(np, nprop);135return 1;136}137138static struct device_node *do_device_node(struct device_node *parent,139const char *name, u32 reg, u32 unit, const char *type,140const char *compat, struct vio_resource *res)141{142struct device_node *np;143char path[32];144145snprintf(path, sizeof(path), "/vdevice/%s@%08x", name, reg);146np = new_node(path, parent);147if (!np)148return NULL;149if (!add_string_property(np, "name", name) ||150!add_string_property(np, "device_type", type) ||151!add_string_property(np, "compatible", compat) ||152!add_raw_property(np, "reg", sizeof(reg), ®) ||153!add_raw_property(np, "linux,unit_address",154sizeof(unit), &unit)) {155goto node_free;156}157if (res) {158if (!add_raw_property(np, "linux,vio_rsrcname",159sizeof(res->rsrcname), res->rsrcname) ||160!add_raw_property(np, "linux,vio_type",161sizeof(res->type), res->type) ||162!add_raw_property(np, "linux,vio_model",163sizeof(res->model), res->model))164goto node_free;165}166np->name = of_get_property(np, "name", NULL);167np->type = of_get_property(np, "device_type", NULL);168of_attach_node(np);169#ifdef CONFIG_PROC_DEVICETREE170if (parent->pde) {171struct proc_dir_entry *ent;172173ent = proc_mkdir(strrchr(np->full_name, '/') + 1, parent->pde);174if (ent)175proc_device_tree_add_node(np, ent);176}177#endif178return np;179180node_free:181free_node(np);182return NULL;183}184185/*186* This is here so that we can dynamically add viodasd187* devices without exposing all the above infrastructure.188*/189struct vio_dev *vio_create_viodasd(u32 unit)190{191struct device_node *vio_root;192struct device_node *np;193struct vio_dev *vdev = NULL;194195vio_root = of_find_node_by_path("/vdevice");196if (!vio_root)197return NULL;198np = do_device_node(vio_root, "viodasd", FIRST_VIODASD + unit, unit,199"block", "IBM,iSeries-viodasd", NULL);200of_node_put(vio_root);201if (np) {202vdev = vio_register_device_node(np);203if (!vdev)204free_node(np);205}206return vdev;207}208EXPORT_SYMBOL_GPL(vio_create_viodasd);209210static void __init handle_block_event(struct HvLpEvent *event)211{212struct vioblocklpevent *bevent = (struct vioblocklpevent *)event;213struct vio_waitevent *pwe;214215if (event == NULL)216/* Notification that a partition went away! */217return;218/* First, we should NEVER get an int here...only acks */219if (hvlpevent_is_int(event)) {220printk(KERN_WARNING "handle_viod_request: "221"Yikes! got an int in viodasd event handler!\n");222if (hvlpevent_need_ack(event)) {223event->xRc = HvLpEvent_Rc_InvalidSubtype;224HvCallEvent_ackLpEvent(event);225}226return;227}228229switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {230case vioblockopen:231/*232* Handle a response to an open request. We get all the233* disk information in the response, so update it. The234* correlation token contains a pointer to a waitevent235* structure that has a completion in it. update the236* return code in the waitevent structure and post the237* completion to wake up the guy who sent the request238*/239pwe = (struct vio_waitevent *)event->xCorrelationToken;240pwe->rc = event->xRc;241pwe->sub_result = bevent->sub_result;242complete(&pwe->com);243break;244case vioblockclose:245break;246default:247printk(KERN_WARNING "handle_viod_request: unexpected subtype!");248if (hvlpevent_need_ack(event)) {249event->xRc = HvLpEvent_Rc_InvalidSubtype;250HvCallEvent_ackLpEvent(event);251}252}253}254255static void __init probe_disk(struct device_node *vio_root, u32 unit)256{257HvLpEvent_Rc hvrc;258struct vio_waitevent we;259u16 flags = 0;260261retry:262init_completion(&we.com);263264/* Send the open event to OS/400 */265hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,266HvLpEvent_Type_VirtualIo,267viomajorsubtype_blockio | vioblockopen,268HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,269viopath_sourceinst(viopath_hostLp),270viopath_targetinst(viopath_hostLp),271(u64)(unsigned long)&we, VIOVERSION << 16,272((u64)unit << 48) | ((u64)flags<< 32),2730, 0, 0);274if (hvrc != 0) {275printk(KERN_WARNING "probe_disk: bad rc on HV open %d\n",276(int)hvrc);277return;278}279280wait_for_completion(&we.com);281282if (we.rc != 0) {283if (flags != 0)284return;285/* try again with read only flag set */286flags = vioblockflags_ro;287goto retry;288}289290/* Send the close event to OS/400. We DON'T expect a response */291hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,292HvLpEvent_Type_VirtualIo,293viomajorsubtype_blockio | vioblockclose,294HvLpEvent_AckInd_NoAck, HvLpEvent_AckType_ImmediateAck,295viopath_sourceinst(viopath_hostLp),296viopath_targetinst(viopath_hostLp),2970, VIOVERSION << 16,298((u64)unit << 48) | ((u64)flags << 32),2990, 0, 0);300if (hvrc != 0) {301printk(KERN_WARNING "probe_disk: "302"bad rc sending event to OS/400 %d\n", (int)hvrc);303return;304}305306do_device_node(vio_root, "viodasd", FIRST_VIODASD + unit, unit,307"block", "IBM,iSeries-viodasd", NULL);308}309310static void __init get_viodasd_info(struct device_node *vio_root)311{312int rc;313u32 unit;314315rc = viopath_open(viopath_hostLp, viomajorsubtype_blockio, 2);316if (rc) {317printk(KERN_WARNING "get_viodasd_info: "318"error opening path to host partition %d\n",319viopath_hostLp);320return;321}322323/* Initialize our request handler */324vio_setHandler(viomajorsubtype_blockio, handle_block_event);325326for (unit = 0; unit < HVMAXARCHITECTEDVIRTUALDISKS; unit++)327probe_disk(vio_root, unit);328329vio_clearHandler(viomajorsubtype_blockio);330viopath_close(viopath_hostLp, viomajorsubtype_blockio, 2);331}332333static void __init handle_cd_event(struct HvLpEvent *event)334{335struct viocdlpevent *bevent;336struct vio_waitevent *pwe;337338if (!event)339/* Notification that a partition went away! */340return;341342/* First, we should NEVER get an int here...only acks */343if (hvlpevent_is_int(event)) {344printk(KERN_WARNING "handle_cd_event: got an unexpected int\n");345if (hvlpevent_need_ack(event)) {346event->xRc = HvLpEvent_Rc_InvalidSubtype;347HvCallEvent_ackLpEvent(event);348}349return;350}351352bevent = (struct viocdlpevent *)event;353354switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {355case viocdgetinfo:356pwe = (struct vio_waitevent *)event->xCorrelationToken;357pwe->rc = event->xRc;358pwe->sub_result = bevent->sub_result;359complete(&pwe->com);360break;361362default:363printk(KERN_WARNING "handle_cd_event: "364"message with unexpected subtype %0x04X!\n",365event->xSubtype & VIOMINOR_SUBTYPE_MASK);366if (hvlpevent_need_ack(event)) {367event->xRc = HvLpEvent_Rc_InvalidSubtype;368HvCallEvent_ackLpEvent(event);369}370}371}372373static void __init get_viocd_info(struct device_node *vio_root)374{375HvLpEvent_Rc hvrc;376u32 unit;377struct vio_waitevent we;378struct vio_resource *unitinfo;379dma_addr_t unitinfo_dmaaddr;380int ret;381382ret = viopath_open(viopath_hostLp, viomajorsubtype_cdio, 2);383if (ret) {384printk(KERN_WARNING385"get_viocd_info: error opening path to host partition %d\n",386viopath_hostLp);387return;388}389390/* Initialize our request handler */391vio_setHandler(viomajorsubtype_cdio, handle_cd_event);392393unitinfo = iseries_hv_alloc(394sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS,395&unitinfo_dmaaddr, GFP_ATOMIC);396if (!unitinfo) {397printk(KERN_WARNING398"get_viocd_info: error allocating unitinfo\n");399goto clear_handler;400}401402memset(unitinfo, 0, sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS);403404init_completion(&we.com);405406hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,407HvLpEvent_Type_VirtualIo,408viomajorsubtype_cdio | viocdgetinfo,409HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,410viopath_sourceinst(viopath_hostLp),411viopath_targetinst(viopath_hostLp),412(u64)&we, VIOVERSION << 16, unitinfo_dmaaddr, 0,413sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS, 0);414if (hvrc != HvLpEvent_Rc_Good) {415printk(KERN_WARNING416"get_viocd_info: cdrom error sending event. rc %d\n",417(int)hvrc);418goto hv_free;419}420421wait_for_completion(&we.com);422423if (we.rc) {424printk(KERN_WARNING "get_viocd_info: bad rc %d:0x%04X\n",425we.rc, we.sub_result);426goto hv_free;427}428429for (unit = 0; (unit < HVMAXARCHITECTEDVIRTUALCDROMS) &&430unitinfo[unit].rsrcname[0]; unit++) {431if (!do_device_node(vio_root, "viocd", FIRST_VIOCD + unit, unit,432"block", "IBM,iSeries-viocd", &unitinfo[unit]))433break;434}435436hv_free:437iseries_hv_free(sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALCDROMS,438unitinfo, unitinfo_dmaaddr);439clear_handler:440vio_clearHandler(viomajorsubtype_cdio);441viopath_close(viopath_hostLp, viomajorsubtype_cdio, 2);442}443444/* Handle interrupt events for tape */445static void __init handle_tape_event(struct HvLpEvent *event)446{447struct vio_waitevent *we;448struct viotapelpevent *tevent = (struct viotapelpevent *)event;449450if (event == NULL)451/* Notification that a partition went away! */452return;453454we = (struct vio_waitevent *)event->xCorrelationToken;455switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {456case viotapegetinfo:457we->rc = tevent->sub_type_result;458complete(&we->com);459break;460default:461printk(KERN_WARNING "handle_tape_event: weird ack\n");462}463}464465static void __init get_viotape_info(struct device_node *vio_root)466{467HvLpEvent_Rc hvrc;468u32 unit;469struct vio_resource *unitinfo;470dma_addr_t unitinfo_dmaaddr;471size_t len = sizeof(*unitinfo) * HVMAXARCHITECTEDVIRTUALTAPES;472struct vio_waitevent we;473int ret;474475init_completion(&we.com);476477ret = viopath_open(viopath_hostLp, viomajorsubtype_tape, 2);478if (ret) {479printk(KERN_WARNING "get_viotape_info: "480"error on viopath_open to hostlp %d\n", ret);481return;482}483484vio_setHandler(viomajorsubtype_tape, handle_tape_event);485486unitinfo = iseries_hv_alloc(len, &unitinfo_dmaaddr, GFP_ATOMIC);487if (!unitinfo)488goto clear_handler;489490memset(unitinfo, 0, len);491492hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,493HvLpEvent_Type_VirtualIo,494viomajorsubtype_tape | viotapegetinfo,495HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,496viopath_sourceinst(viopath_hostLp),497viopath_targetinst(viopath_hostLp),498(u64)(unsigned long)&we, VIOVERSION << 16,499unitinfo_dmaaddr, len, 0, 0);500if (hvrc != HvLpEvent_Rc_Good) {501printk(KERN_WARNING "get_viotape_info: hv error on op %d\n",502(int)hvrc);503goto hv_free;504}505506wait_for_completion(&we.com);507508for (unit = 0; (unit < HVMAXARCHITECTEDVIRTUALTAPES) &&509unitinfo[unit].rsrcname[0]; unit++) {510if (!do_device_node(vio_root, "viotape", FIRST_VIOTAPE + unit,511unit, "byte", "IBM,iSeries-viotape",512&unitinfo[unit]))513break;514}515516hv_free:517iseries_hv_free(len, unitinfo, unitinfo_dmaaddr);518clear_handler:519vio_clearHandler(viomajorsubtype_tape);520viopath_close(viopath_hostLp, viomajorsubtype_tape, 2);521}522523static int __init iseries_vio_init(void)524{525struct device_node *vio_root;526int ret = -ENODEV;527528if (!firmware_has_feature(FW_FEATURE_ISERIES))529goto out;530531iommu_vio_init();532533vio_root = of_find_node_by_path("/vdevice");534if (!vio_root)535goto out;536537if (viopath_hostLp == HvLpIndexInvalid) {538vio_set_hostlp();539/* If we don't have a host, bail out */540if (viopath_hostLp == HvLpIndexInvalid)541goto put_node;542}543544get_viodasd_info(vio_root);545get_viocd_info(vio_root);546get_viotape_info(vio_root);547548ret = 0;549550put_node:551of_node_put(vio_root);552out:553return ret;554}555arch_initcall(iseries_vio_init);556557558