diff options
Diffstat (limited to 'arch/powerpc/platforms/iseries')
37 files changed, 8408 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/iseries/Kconfig b/arch/powerpc/platforms/iseries/Kconfig new file mode 100644 index 00000000..b57cda3a --- /dev/null +++ b/arch/powerpc/platforms/iseries/Kconfig @@ -0,0 +1,38 @@ +config PPC_ISERIES + bool "IBM Legacy iSeries" + depends on PPC64 && PPC_BOOK3S + select PPC_SMP_MUXED_IPI + select PPC_INDIRECT_PIO + select PPC_INDIRECT_MMIO + select PPC_PCI_CHOICE if EXPERT + +menu "iSeries device drivers" + depends on PPC_ISERIES + +config VIODASD + tristate "iSeries Virtual I/O disk support" + depends on BLOCK + select VIOPATH + help + If you are running on an iSeries system and you want to use + virtual disks created and managed by OS/400, say Y. + +config VIOCD + tristate "iSeries Virtual I/O CD support" + depends on BLOCK + select VIOPATH + help + If you are running Linux on an IBM iSeries system and you want to + read a CD drive owned by OS/400, say Y here. + +config VIOTAPE + tristate "iSeries Virtual Tape Support" + select VIOPATH + help + If you are running Linux on an iSeries system and you want Linux + to read and/or write a tape drive owned by OS/400, say Y here. + +endmenu + +config VIOPATH + bool diff --git a/arch/powerpc/platforms/iseries/Makefile b/arch/powerpc/platforms/iseries/Makefile new file mode 100644 index 00000000..a7602b11 --- /dev/null +++ b/arch/powerpc/platforms/iseries/Makefile @@ -0,0 +1,9 @@ +ccflags-y := -mno-minimal-toc + +obj-y += exception.o +obj-y += hvlog.o hvlpconfig.o lpardata.o setup.o dt.o mf.o lpevents.o \ + hvcall.o proc.o htab.o iommu.o misc.o irq.o +obj-$(CONFIG_PCI) += pci.o +obj-$(CONFIG_SMP) += smp.o +obj-$(CONFIG_VIOPATH) += viopath.o vio.o +obj-$(CONFIG_MODULES) += ksyms.o diff --git a/arch/powerpc/platforms/iseries/call_hpt.h b/arch/powerpc/platforms/iseries/call_hpt.h new file mode 100644 index 00000000..8d95fe4b --- /dev/null +++ b/arch/powerpc/platforms/iseries/call_hpt.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ +#ifndef _PLATFORMS_ISERIES_CALL_HPT_H +#define _PLATFORMS_ISERIES_CALL_HPT_H + +/* + * This file contains the "hypervisor call" interface which is used to + * drive the hypervisor from the OS. + */ + +#include <asm/iseries/hv_call_sc.h> +#include <asm/iseries/hv_types.h> +#include <asm/mmu.h> + +#define HvCallHptGetHptAddress HvCallHpt + 0 +#define HvCallHptGetHptPages HvCallHpt + 1 +#define HvCallHptSetPp HvCallHpt + 5 +#define HvCallHptSetSwBits HvCallHpt + 6 +#define HvCallHptUpdate HvCallHpt + 7 +#define HvCallHptInvalidateNoSyncICache HvCallHpt + 8 +#define HvCallHptGet HvCallHpt + 11 +#define HvCallHptFindNextValid HvCallHpt + 12 +#define HvCallHptFindValid HvCallHpt + 13 +#define HvCallHptAddValidate HvCallHpt + 16 +#define HvCallHptInvalidateSetSwBitsGet HvCallHpt + 18 + + +static inline u64 HvCallHpt_getHptAddress(void) +{ + return HvCall0(HvCallHptGetHptAddress); +} + +static inline u64 HvCallHpt_getHptPages(void) +{ + return HvCall0(HvCallHptGetHptPages); +} + +static inline void HvCallHpt_setPp(u32 hpteIndex, u8 value) +{ + HvCall2(HvCallHptSetPp, hpteIndex, value); +} + +static inline void HvCallHpt_setSwBits(u32 hpteIndex, u8 bitson, u8 bitsoff) +{ + HvCall3(HvCallHptSetSwBits, hpteIndex, bitson, bitsoff); +} + +static inline void HvCallHpt_invalidateNoSyncICache(u32 hpteIndex) +{ + HvCall1(HvCallHptInvalidateNoSyncICache, hpteIndex); +} + +static inline u64 HvCallHpt_invalidateSetSwBitsGet(u32 hpteIndex, u8 bitson, + u8 bitsoff) +{ + u64 compressedStatus; + + compressedStatus = HvCall4(HvCallHptInvalidateSetSwBitsGet, + hpteIndex, bitson, bitsoff, 1); + HvCall1(HvCallHptInvalidateNoSyncICache, hpteIndex); + return compressedStatus; +} + +static inline u64 HvCallHpt_findValid(struct hash_pte *hpte, u64 vpn) +{ + return HvCall3Ret16(HvCallHptFindValid, hpte, vpn, 0, 0); +} + +static inline u64 HvCallHpt_findNextValid(struct hash_pte *hpte, u32 hpteIndex, + u8 bitson, u8 bitsoff) +{ + return HvCall3Ret16(HvCallHptFindNextValid, hpte, hpteIndex, + bitson, bitsoff); +} + +static inline void HvCallHpt_get(struct hash_pte *hpte, u32 hpteIndex) +{ + HvCall2Ret16(HvCallHptGet, hpte, hpteIndex, 0); +} + +static inline void HvCallHpt_addValidate(u32 hpteIndex, u32 hBit, + struct hash_pte *hpte) +{ + HvCall4(HvCallHptAddValidate, hpteIndex, hBit, hpte->v, hpte->r); +} + +#endif /* _PLATFORMS_ISERIES_CALL_HPT_H */ diff --git a/arch/powerpc/platforms/iseries/call_pci.h b/arch/powerpc/platforms/iseries/call_pci.h new file mode 100644 index 00000000..dbdf6985 --- /dev/null +++ b/arch/powerpc/platforms/iseries/call_pci.h @@ -0,0 +1,309 @@ +/* + * Provides the Hypervisor PCI calls for iSeries Linux Parition. + * Copyright (C) 2001 <Wayne G Holm> <IBM Corporation> + * + * 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 + * + * Change Activity: + * Created, Jan 9, 2001 + */ + +#ifndef _PLATFORMS_ISERIES_CALL_PCI_H +#define _PLATFORMS_ISERIES_CALL_PCI_H + +#include <asm/iseries/hv_call_sc.h> +#include <asm/iseries/hv_types.h> + +/* + * DSA == Direct Select Address + * this struct must be 64 bits in total + */ +struct HvCallPci_DsaAddr { + u16 busNumber; /* PHB index? */ + u8 subBusNumber; /* PCI bus number? */ + u8 deviceId; /* device and function? */ + u8 barNumber; + u8 reserved[3]; +}; + +union HvDsaMap { + u64 DsaAddr; + struct HvCallPci_DsaAddr Dsa; +}; + +struct HvCallPci_LoadReturn { + u64 rc; + u64 value; +}; + +enum HvCallPci_DeviceType { + HvCallPci_NodeDevice = 1, + HvCallPci_SpDevice = 2, + HvCallPci_IopDevice = 3, + HvCallPci_BridgeDevice = 4, + HvCallPci_MultiFunctionDevice = 5, + HvCallPci_IoaDevice = 6 +}; + + +struct HvCallPci_DeviceInfo { + u32 deviceType; /* See DeviceType enum for values */ +}; + +struct HvCallPci_BusUnitInfo { + u32 sizeReturned; /* length of data returned */ + u32 deviceType; /* see DeviceType enum for values */ +}; + +struct HvCallPci_BridgeInfo { + struct HvCallPci_BusUnitInfo busUnitInfo; /* Generic bus unit info */ + u8 subBusNumber; /* Bus number of secondary bus */ + u8 maxAgents; /* Max idsels on secondary bus */ + u8 maxSubBusNumber; /* Max Sub Bus */ + u8 logicalSlotNumber; /* Logical Slot Number for IOA */ +}; + + +/* + * Maximum BusUnitInfo buffer size. Provided for clients so + * they can allocate a buffer big enough for any type of bus + * unit. Increase as needed. + */ +enum {HvCallPci_MaxBusUnitInfoSize = 128}; + +struct HvCallPci_BarParms { + u64 vaddr; + u64 raddr; + u64 size; + u64 protectStart; + u64 protectEnd; + u64 relocationOffset; + u64 pciAddress; + u64 reserved[3]; +}; + +enum HvCallPci_VpdType { + HvCallPci_BusVpd = 1, + HvCallPci_BusAdapterVpd = 2 +}; + +#define HvCallPciConfigLoad8 HvCallPci + 0 +#define HvCallPciConfigLoad16 HvCallPci + 1 +#define HvCallPciConfigLoad32 HvCallPci + 2 +#define HvCallPciConfigStore8 HvCallPci + 3 +#define HvCallPciConfigStore16 HvCallPci + 4 +#define HvCallPciConfigStore32 HvCallPci + 5 +#define HvCallPciEoi HvCallPci + 16 +#define HvCallPciGetBarParms HvCallPci + 18 +#define HvCallPciMaskFisr HvCallPci + 20 +#define HvCallPciUnmaskFisr HvCallPci + 21 +#define HvCallPciSetSlotReset HvCallPci + 25 +#define HvCallPciGetDeviceInfo HvCallPci + 27 +#define HvCallPciGetCardVpd HvCallPci + 28 +#define HvCallPciBarLoad8 HvCallPci + 40 +#define HvCallPciBarLoad16 HvCallPci + 41 +#define HvCallPciBarLoad32 HvCallPci + 42 +#define HvCallPciBarLoad64 HvCallPci + 43 +#define HvCallPciBarStore8 HvCallPci + 44 +#define HvCallPciBarStore16 HvCallPci + 45 +#define HvCallPciBarStore32 HvCallPci + 46 +#define HvCallPciBarStore64 HvCallPci + 47 +#define HvCallPciMaskInterrupts HvCallPci + 48 +#define HvCallPciUnmaskInterrupts HvCallPci + 49 +#define HvCallPciGetBusUnitInfo HvCallPci + 50 + +static inline u64 HvCallPci_configLoad16(u16 busNumber, u8 subBusNumber, + u8 deviceId, u32 offset, u16 *value) +{ + struct HvCallPci_DsaAddr dsa; + struct HvCallPci_LoadReturn retVal; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumber; + dsa.subBusNumber = subBusNumber; + dsa.deviceId = deviceId; + + HvCall3Ret16(HvCallPciConfigLoad16, &retVal, *(u64 *)&dsa, offset, 0); + + *value = retVal.value; + + return retVal.rc; +} + +static inline u64 HvCallPci_configLoad32(u16 busNumber, u8 subBusNumber, + u8 deviceId, u32 offset, u32 *value) +{ + struct HvCallPci_DsaAddr dsa; + struct HvCallPci_LoadReturn retVal; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumber; + dsa.subBusNumber = subBusNumber; + dsa.deviceId = deviceId; + + HvCall3Ret16(HvCallPciConfigLoad32, &retVal, *(u64 *)&dsa, offset, 0); + + *value = retVal.value; + + return retVal.rc; +} + +static inline u64 HvCallPci_configStore8(u16 busNumber, u8 subBusNumber, + u8 deviceId, u32 offset, u8 value) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumber; + dsa.subBusNumber = subBusNumber; + dsa.deviceId = deviceId; + + return HvCall4(HvCallPciConfigStore8, *(u64 *)&dsa, offset, value, 0); +} + +static inline u64 HvCallPci_eoi(u16 busNumberParm, u8 subBusParm, + u8 deviceIdParm) +{ + struct HvCallPci_DsaAddr dsa; + struct HvCallPci_LoadReturn retVal; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceIdParm; + + HvCall1Ret16(HvCallPciEoi, &retVal, *(u64*)&dsa); + + return retVal.rc; +} + +static inline u64 HvCallPci_getBarParms(u16 busNumberParm, u8 subBusParm, + u8 deviceIdParm, u8 barNumberParm, u64 parms, u32 sizeofParms) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceIdParm; + dsa.barNumber = barNumberParm; + + return HvCall3(HvCallPciGetBarParms, *(u64*)&dsa, parms, sizeofParms); +} + +static inline u64 HvCallPci_maskFisr(u16 busNumberParm, u8 subBusParm, + u8 deviceIdParm, u64 fisrMask) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceIdParm; + + return HvCall2(HvCallPciMaskFisr, *(u64*)&dsa, fisrMask); +} + +static inline u64 HvCallPci_unmaskFisr(u16 busNumberParm, u8 subBusParm, + u8 deviceIdParm, u64 fisrMask) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceIdParm; + + return HvCall2(HvCallPciUnmaskFisr, *(u64*)&dsa, fisrMask); +} + +static inline u64 HvCallPci_getDeviceInfo(u16 busNumberParm, u8 subBusParm, + u8 deviceNumberParm, u64 parms, u32 sizeofParms) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceNumberParm << 4; + + return HvCall3(HvCallPciGetDeviceInfo, *(u64*)&dsa, parms, sizeofParms); +} + +static inline u64 HvCallPci_maskInterrupts(u16 busNumberParm, u8 subBusParm, + u8 deviceIdParm, u64 interruptMask) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceIdParm; + + return HvCall2(HvCallPciMaskInterrupts, *(u64*)&dsa, interruptMask); +} + +static inline u64 HvCallPci_unmaskInterrupts(u16 busNumberParm, u8 subBusParm, + u8 deviceIdParm, u64 interruptMask) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceIdParm; + + return HvCall2(HvCallPciUnmaskInterrupts, *(u64*)&dsa, interruptMask); +} + +static inline u64 HvCallPci_getBusUnitInfo(u16 busNumberParm, u8 subBusParm, + u8 deviceIdParm, u64 parms, u32 sizeofParms) +{ + struct HvCallPci_DsaAddr dsa; + + *((u64*)&dsa) = 0; + + dsa.busNumber = busNumberParm; + dsa.subBusNumber = subBusParm; + dsa.deviceId = deviceIdParm; + + return HvCall3(HvCallPciGetBusUnitInfo, *(u64*)&dsa, parms, + sizeofParms); +} + +static inline int HvCallPci_getBusVpd(u16 busNumParm, u64 destParm, + u16 sizeParm) +{ + u64 xRc = HvCall4(HvCallPciGetCardVpd, busNumParm, destParm, + sizeParm, HvCallPci_BusVpd); + if (xRc == -1) + return -1; + else + return xRc & 0xFFFF; +} + +#endif /* _PLATFORMS_ISERIES_CALL_PCI_H */ diff --git a/arch/powerpc/platforms/iseries/call_sm.h b/arch/powerpc/platforms/iseries/call_sm.h new file mode 100644 index 00000000..c7e25161 --- /dev/null +++ b/arch/powerpc/platforms/iseries/call_sm.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ +#ifndef _ISERIES_CALL_SM_H +#define _ISERIES_CALL_SM_H + +/* + * This file contains the "hypervisor call" interface which is used to + * drive the hypervisor from the OS. + */ + +#include <asm/iseries/hv_call_sc.h> +#include <asm/iseries/hv_types.h> + +#define HvCallSmGet64BitsOfAccessMap HvCallSm + 11 + +static inline u64 HvCallSm_get64BitsOfAccessMap(HvLpIndex lpIndex, + u64 indexIntoBitMap) +{ + return HvCall2(HvCallSmGet64BitsOfAccessMap, lpIndex, indexIntoBitMap); +} + +#endif /* _ISERIES_CALL_SM_H */ diff --git a/arch/powerpc/platforms/iseries/dt.c b/arch/powerpc/platforms/iseries/dt.c new file mode 100644 index 00000000..f0491cc2 --- /dev/null +++ b/arch/powerpc/platforms/iseries/dt.c @@ -0,0 +1,643 @@ +/* + * Copyright (C) 2005-2006 Michael Ellerman, IBM Corporation + * Copyright (C) 2000-2004, IBM Corporation + * + * Description: + * This file contains all the routines to build a flattened device + * tree for a legacy iSeries machine. + * + * 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. + */ + +#undef DEBUG + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/pci_regs.h> +#include <linux/pci_ids.h> +#include <linux/threads.h> +#include <linux/bitops.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/if_ether.h> /* ETH_ALEN */ + +#include <asm/machdep.h> +#include <asm/prom.h> +#include <asm/lppaca.h> +#include <asm/cputable.h> +#include <asm/abs_addr.h> +#include <asm/system.h> +#include <asm/iseries/hv_types.h> +#include <asm/iseries/hv_lp_config.h> +#include <asm/iseries/hv_call_xm.h> +#include <asm/udbg.h> + +#include "processor_vpd.h" +#include "call_hpt.h" +#include "call_pci.h" +#include "pci.h" +#include "it_exp_vpd_panel.h" +#include "naca.h" + +#ifdef DEBUG +#define DBG(fmt...) udbg_printf(fmt) +#else +#define DBG(fmt...) +#endif + +/* + * These are created by the linker script at the start and end + * of the section containing all the strings marked with the DS macro. + */ +extern char __dt_strings_start[]; +extern char __dt_strings_end[]; + +#define DS(s) ({ \ + static const char __s[] __attribute__((section(".dt_strings"))) = s; \ + __s; \ +}) + +struct iseries_flat_dt { + struct boot_param_header header; + u64 reserve_map[2]; +}; + +static void * __initdata dt_data; + +/* + * Putting these strings here keeps them out of the .dt_strings section + * that we capture for the strings blob of the flattened device tree. + */ +static char __initdata device_type_cpu[] = "cpu"; +static char __initdata device_type_memory[] = "memory"; +static char __initdata device_type_serial[] = "serial"; +static char __initdata device_type_network[] = "network"; +static char __initdata device_type_pci[] = "pci"; +static char __initdata device_type_vdevice[] = "vdevice"; +static char __initdata device_type_vscsi[] = "vscsi"; + + +/* EBCDIC to ASCII conversion routines */ + +static unsigned char __init e2a(unsigned char x) +{ + switch (x) { + case 0x81 ... 0x89: + return x - 0x81 + 'a'; + case 0x91 ... 0x99: + return x - 0x91 + 'j'; + case 0xA2 ... 0xA9: + return x - 0xA2 + 's'; + case 0xC1 ... 0xC9: + return x - 0xC1 + 'A'; + case 0xD1 ... 0xD9: + return x - 0xD1 + 'J'; + case 0xE2 ... 0xE9: + return x - 0xE2 + 'S'; + case 0xF0 ... 0xF9: + return x - 0xF0 + '0'; + } + return ' '; +} + +static unsigned char * __init strne2a(unsigned char *dest, + const unsigned char *src, size_t n) +{ + int i; + + n = strnlen(src, n); + + for (i = 0; i < n; i++) + dest[i] = e2a(src[i]); + + return dest; +} + +static struct iseries_flat_dt * __init dt_init(void) +{ + struct iseries_flat_dt *dt; + unsigned long str_len; + + str_len = __dt_strings_end - __dt_strings_start; + dt = (struct iseries_flat_dt *)ALIGN(klimit, 8); + dt->header.off_mem_rsvmap = + offsetof(struct iseries_flat_dt, reserve_map); + dt->header.off_dt_strings = ALIGN(sizeof(*dt), 8); + dt->header.off_dt_struct = dt->header.off_dt_strings + + ALIGN(str_len, 8); + dt_data = (void *)((unsigned long)dt + dt->header.off_dt_struct); + dt->header.dt_strings_size = str_len; + + /* There is no notion of hardware cpu id on iSeries */ + dt->header.boot_cpuid_phys = smp_processor_id(); + + memcpy((char *)dt + dt->header.off_dt_strings, __dt_strings_start, + str_len); + + dt->header.magic = OF_DT_HEADER; + dt->header.version = 0x10; + dt->header.last_comp_version = 0x10; + + dt->reserve_map[0] = 0; + dt->reserve_map[1] = 0; + + return dt; +} + +static void __init dt_push_u32(struct iseries_flat_dt *dt, u32 value) +{ + *((u32 *)dt_data) = value; + dt_data += sizeof(u32); +} + +#ifdef notyet +static void __init dt_push_u64(struct iseries_flat_dt *dt, u64 value) +{ + *((u64 *)dt_data) = value; + dt_data += sizeof(u64); +} +#endif + +static void __init dt_push_bytes(struct iseries_flat_dt *dt, const char *data, + int len) +{ + memcpy(dt_data, data, len); + dt_data += ALIGN(len, 4); +} + +static void __init dt_start_node(struct iseries_flat_dt *dt, const char *name) +{ + dt_push_u32(dt, OF_DT_BEGIN_NODE); + dt_push_bytes(dt, name, strlen(name) + 1); +} + +#define dt_end_node(dt) dt_push_u32(dt, OF_DT_END_NODE) + +static void __init __dt_prop(struct iseries_flat_dt *dt, const char *name, + const void *data, int len) +{ + unsigned long offset; + + dt_push_u32(dt, OF_DT_PROP); + + /* Length of the data */ + dt_push_u32(dt, len); + + offset = name - __dt_strings_start; + + /* The offset of the properties name in the string blob. */ + dt_push_u32(dt, (u32)offset); + + /* The actual data. */ + dt_push_bytes(dt, data, len); +} +#define dt_prop(dt, name, data, len) __dt_prop((dt), DS(name), (data), (len)) + +#define dt_prop_str(dt, name, data) \ + dt_prop((dt), name, (data), strlen((data)) + 1); /* + 1 for NULL */ + +static void __init __dt_prop_u32(struct iseries_flat_dt *dt, const char *name, + u32 data) +{ + __dt_prop(dt, name, &data, sizeof(u32)); +} +#define dt_prop_u32(dt, name, data) __dt_prop_u32((dt), DS(name), (data)) + +static void __init __maybe_unused __dt_prop_u64(struct iseries_flat_dt *dt, + const char *name, u64 data) +{ + __dt_prop(dt, name, &data, sizeof(u64)); +} +#define dt_prop_u64(dt, name, data) __dt_prop_u64((dt), DS(name), (data)) + +#define dt_prop_u64_list(dt, name, data, n) \ + dt_prop((dt), name, (data), sizeof(u64) * (n)) + +#define dt_prop_u32_list(dt, name, data, n) \ + dt_prop((dt), name, (data), sizeof(u32) * (n)) + +#define dt_prop_empty(dt, name) dt_prop((dt), name, NULL, 0) + +static void __init dt_cpus(struct iseries_flat_dt *dt) +{ + unsigned char buf[32]; + unsigned char *p; + unsigned int i, index; + struct IoHriProcessorVpd *d; + u32 pft_size[2]; + + /* yuck */ + snprintf(buf, 32, "PowerPC,%s", cur_cpu_spec->cpu_name); + p = strchr(buf, ' '); + if (!p) p = buf + strlen(buf); + + dt_start_node(dt, "cpus"); + dt_prop_u32(dt, "#address-cells", 1); + dt_prop_u32(dt, "#size-cells", 0); + + pft_size[0] = 0; /* NUMA CEC cookie, 0 for non NUMA */ + pft_size[1] = __ilog2(HvCallHpt_getHptPages() * HW_PAGE_SIZE); + + for (i = 0; i < NR_LPPACAS; i++) { + if (lppaca[i].dyn_proc_status >= 2) + continue; + + snprintf(p, 32 - (p - buf), "@%d", i); + dt_start_node(dt, buf); + + dt_prop_str(dt, "device_type", device_type_cpu); + + index = lppaca[i].dyn_hv_phys_proc_index; + d = &xIoHriProcessorVpd[index]; + + dt_prop_u32(dt, "i-cache-size", d->xInstCacheSize * 1024); + dt_prop_u32(dt, "i-cache-line-size", d->xInstCacheOperandSize); + + dt_prop_u32(dt, "d-cache-size", d->xDataL1CacheSizeKB * 1024); + dt_prop_u32(dt, "d-cache-line-size", d->xDataCacheOperandSize); + + /* magic conversions to Hz copied from old code */ + dt_prop_u32(dt, "clock-frequency", + ((1UL << 34) * 1000000) / d->xProcFreq); + dt_prop_u32(dt, "timebase-frequency", + ((1UL << 32) * 1000000) / d->xTimeBaseFreq); + + dt_prop_u32(dt, "reg", i); + + dt_prop_u32_list(dt, "ibm,pft-size", pft_size, 2); + + dt_end_node(dt); + } + + dt_end_node(dt); +} + +static void __init dt_model(struct iseries_flat_dt *dt) +{ + char buf[16] = "IBM,"; + + /* N.B. lparcfg.c knows about the "IBM," prefixes ... */ + /* "IBM," + mfgId[2:3] + systemSerial[1:5] */ + strne2a(buf + 4, xItExtVpdPanel.mfgID + 2, 2); + strne2a(buf + 6, xItExtVpdPanel.systemSerial + 1, 5); + buf[11] = '\0'; + dt_prop_str(dt, "system-id", buf); + + /* "IBM," + machineType[0:4] */ + strne2a(buf + 4, xItExtVpdPanel.machineType, 4); + buf[8] = '\0'; + dt_prop_str(dt, "model", buf); + + dt_prop_str(dt, "compatible", "IBM,iSeries"); + dt_prop_u32(dt, "ibm,partition-no", HvLpConfig_getLpIndex()); +} + +static void __init dt_initrd(struct iseries_flat_dt *dt) +{ +#ifdef CONFIG_BLK_DEV_INITRD + if (naca.xRamDisk) { + dt_prop_u64(dt, "linux,initrd-start", (u64)naca.xRamDisk); + dt_prop_u64(dt, "linux,initrd-end", + (u64)naca.xRamDisk + naca.xRamDiskSize * HW_PAGE_SIZE); + } +#endif +} + +static void __init dt_do_vdevice(struct iseries_flat_dt *dt, + const char *name, u32 reg, int unit, + const char *type, const char *compat, int end) +{ + char buf[32]; + + snprintf(buf, 32, "%s@%08x", name, reg + ((unit >= 0) ? unit : 0)); + dt_start_node(dt, buf); + dt_prop_str(dt, "device_type", type); + if (compat) + dt_prop_str(dt, "compatible", compat); + dt_prop_u32(dt, "reg", reg + ((unit >= 0) ? unit : 0)); + if (unit >= 0) + dt_prop_u32(dt, "linux,unit_address", unit); + if (end) + dt_end_node(dt); +} + +static void __init dt_vdevices(struct iseries_flat_dt *dt) +{ + u32 reg = 0; + HvLpIndexMap vlan_map; + int i; + + dt_start_node(dt, "vdevice"); + dt_prop_str(dt, "device_type", device_type_vdevice); + dt_prop_str(dt, "compatible", "IBM,iSeries-vdevice"); + dt_prop_u32(dt, "#address-cells", 1); + dt_prop_u32(dt, "#size-cells", 0); + + dt_do_vdevice(dt, "vty", reg, -1, device_type_serial, + "IBM,iSeries-vty", 1); + reg++; + + dt_do_vdevice(dt, "v-scsi", reg, -1, device_type_vscsi, + "IBM,v-scsi", 1); + reg++; + + vlan_map = HvLpConfig_getVirtualLanIndexMap(); + for (i = 0; i < HVMAXARCHITECTEDVIRTUALLANS; i++) { + unsigned char mac_addr[ETH_ALEN]; + + if ((vlan_map & (0x8000 >> i)) == 0) + continue; + dt_do_vdevice(dt, "l-lan", reg, i, device_type_network, + "IBM,iSeries-l-lan", 0); + mac_addr[0] = 0x02; + mac_addr[1] = 0x01; + mac_addr[2] = 0xff; + mac_addr[3] = i; + mac_addr[4] = 0xff; + mac_addr[5] = HvLpConfig_getLpIndex_outline(); + dt_prop(dt, "local-mac-address", (char *)mac_addr, ETH_ALEN); + dt_prop(dt, "mac-address", (char *)mac_addr, ETH_ALEN); + dt_prop_u32(dt, "max-frame-size", 9000); + dt_prop_u32(dt, "address-bits", 48); + + dt_end_node(dt); + } + + dt_end_node(dt); +} + +struct pci_class_name { + u16 code; + const char *name; + const char *type; +}; + +static struct pci_class_name __initdata pci_class_name[] = { + { PCI_CLASS_NETWORK_ETHERNET, "ethernet", device_type_network }, +}; + +static struct pci_class_name * __init dt_find_pci_class_name(u16 class_code) +{ + struct pci_class_name *cp; + + for (cp = pci_class_name; + cp < &pci_class_name[ARRAY_SIZE(pci_class_name)]; cp++) + if (cp->code == class_code) + return cp; + return NULL; +} + +/* + * This assumes that the node slot is always on the primary bus! + */ +static void __init scan_bridge_slot(struct iseries_flat_dt *dt, + HvBusNumber bus, struct HvCallPci_BridgeInfo *bridge_info) +{ + HvSubBusNumber sub_bus = bridge_info->subBusNumber; + u16 vendor_id; + u16 device_id; + u32 class_id; + int err; + char buf[32]; + u32 reg[5]; + int id_sel = ISERIES_GET_DEVICE_FROM_SUBBUS(sub_bus); + int function = ISERIES_GET_FUNCTION_FROM_SUBBUS(sub_bus); + HvAgentId eads_id_sel = ISERIES_PCI_AGENTID(id_sel, function); + u8 devfn; + struct pci_class_name *cp; + + /* + * Connect all functions of any device found. + */ + for (id_sel = 1; id_sel <= bridge_info->maxAgents; id_sel++) { + for (function = 0; function < 8; function++) { + HvAgentId agent_id = ISERIES_PCI_AGENTID(id_sel, + function); + err = HvCallXm_connectBusUnit(bus, sub_bus, + agent_id, 0); + if (err) { + if (err != 0x302) + DBG("connectBusUnit(%x, %x, %x) %x\n", + bus, sub_bus, agent_id, err); + continue; + } + + err = HvCallPci_configLoad16(bus, sub_bus, agent_id, + PCI_VENDOR_ID, &vendor_id); + if (err) { + DBG("ReadVendor(%x, %x, %x) %x\n", + bus, sub_bus, agent_id, err); + continue; + } + err = HvCallPci_configLoad16(bus, sub_bus, agent_id, + PCI_DEVICE_ID, &device_id); + if (err) { + DBG("ReadDevice(%x, %x, %x) %x\n", + bus, sub_bus, agent_id, err); + continue; + } + err = HvCallPci_configLoad32(bus, sub_bus, agent_id, + PCI_CLASS_REVISION , &class_id); + if (err) { + DBG("ReadClass(%x, %x, %x) %x\n", + bus, sub_bus, agent_id, err); + continue; + } + + devfn = PCI_DEVFN(ISERIES_ENCODE_DEVICE(eads_id_sel), + function); + cp = dt_find_pci_class_name(class_id >> 16); + if (cp && cp->name) + strncpy(buf, cp->name, sizeof(buf) - 1); + else + snprintf(buf, sizeof(buf), "pci%x,%x", + vendor_id, device_id); + buf[sizeof(buf) - 1] = '\0'; + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "@%x", PCI_SLOT(devfn)); + buf[sizeof(buf) - 1] = '\0'; + if (function != 0) + snprintf(buf + strlen(buf), + sizeof(buf) - strlen(buf), + ",%x", function); + dt_start_node(dt, buf); + reg[0] = (bus << 16) | (devfn << 8); + reg[1] = 0; + reg[2] = 0; + reg[3] = 0; + reg[4] = 0; + dt_prop_u32_list(dt, "reg", reg, 5); + if (cp && (cp->type || cp->name)) + dt_prop_str(dt, "device_type", + cp->type ? cp->type : cp->name); + dt_prop_u32(dt, "vendor-id", vendor_id); + dt_prop_u32(dt, "device-id", device_id); + dt_prop_u32(dt, "class-code", class_id >> 8); + dt_prop_u32(dt, "revision-id", class_id & 0xff); + dt_prop_u32(dt, "linux,subbus", sub_bus); + dt_prop_u32(dt, "linux,agent-id", agent_id); + dt_prop_u32(dt, "linux,logical-slot-number", + bridge_info->logicalSlotNumber); + dt_end_node(dt); + + } + } +} + +static void __init scan_bridge(struct iseries_flat_dt *dt, HvBusNumber bus, + HvSubBusNumber sub_bus, int id_sel) +{ + struct HvCallPci_BridgeInfo bridge_info; + HvAgentId agent_id; + int function; + int ret; + + /* Note: hvSubBus and irq is always be 0 at this level! */ + for (function = 0; function < 8; ++function) { + agent_id = ISERIES_PCI_AGENTID(id_sel, function); + ret = HvCallXm_connectBusUnit(bus, sub_bus, agent_id, 0); + if (ret != 0) { + if (ret != 0xb) + DBG("connectBusUnit(%x, %x, %x) %x\n", + bus, sub_bus, agent_id, ret); + continue; + } + DBG("found device at bus %d idsel %d func %d (AgentId %x)\n", + bus, id_sel, function, agent_id); + ret = HvCallPci_getBusUnitInfo(bus, sub_bus, agent_id, + iseries_hv_addr(&bridge_info), + sizeof(struct HvCallPci_BridgeInfo)); + if (ret != 0) + continue; + DBG("bridge info: type %x subbus %x " + "maxAgents %x maxsubbus %x logslot %x\n", + bridge_info.busUnitInfo.deviceType, + bridge_info.subBusNumber, + bridge_info.maxAgents, + bridge_info.maxSubBusNumber, + bridge_info.logicalSlotNumber); + if (bridge_info.busUnitInfo.deviceType == + HvCallPci_BridgeDevice) + scan_bridge_slot(dt, bus, &bridge_info); + else + DBG("PCI: Invalid Bridge Configuration(0x%02X)", + bridge_info.busUnitInfo.deviceType); + } +} + +static void __init scan_phb(struct iseries_flat_dt *dt, HvBusNumber bus) +{ + struct HvCallPci_DeviceInfo dev_info; + const HvSubBusNumber sub_bus = 0; /* EADs is always 0. */ + int err; + int id_sel; + const int max_agents = 8; + + /* + * Probe for EADs Bridges + */ + for (id_sel = 1; id_sel < max_agents; ++id_sel) { + err = HvCallPci_getDeviceInfo(bus, sub_bus, id_sel, + iseries_hv_addr(&dev_info), + sizeof(struct HvCallPci_DeviceInfo)); + if (err) { + if (err != 0x302) + DBG("getDeviceInfo(%x, %x, %x) %x\n", + bus, sub_bus, id_sel, err); + continue; + } + if (dev_info.deviceType != HvCallPci_NodeDevice) { + DBG("PCI: Invalid System Configuration" + "(0x%02X) for bus 0x%02x id 0x%02x.\n", + dev_info.deviceType, bus, id_sel); + continue; + } + scan_bridge(dt, bus, sub_bus, id_sel); + } +} + +static void __init dt_pci_devices(struct iseries_flat_dt *dt) +{ + HvBusNumber bus; + char buf[32]; + u32 buses[2]; + int phb_num = 0; + + /* Check all possible buses. */ + for (bus = 0; bus < 256; bus++) { + int err = HvCallXm_testBus(bus); + + if (err) { + /* + * Check for Unexpected Return code, a clue that + * something has gone wrong. + */ + if (err != 0x0301) + DBG("Unexpected Return on Probe(0x%02X) " + "0x%04X\n", bus, err); + continue; + } + DBG("bus %d appears to exist\n", bus); + snprintf(buf, 32, "pci@%d", phb_num); + dt_start_node(dt, buf); + dt_prop_str(dt, "device_type", device_type_pci); + dt_prop_str(dt, "compatible", "IBM,iSeries-Logical-PHB"); + dt_prop_u32(dt, "#address-cells", 3); + dt_prop_u32(dt, "#size-cells", 2); + buses[0] = buses[1] = bus; + dt_prop_u32_list(dt, "bus-range", buses, 2); + scan_phb(dt, bus); + dt_end_node(dt); + phb_num++; + } +} + +static void dt_finish(struct iseries_flat_dt *dt) +{ + dt_push_u32(dt, OF_DT_END); + dt->header.totalsize = (unsigned long)dt_data - (unsigned long)dt; + klimit = ALIGN((unsigned long)dt_data, 8); +} + +void * __init build_flat_dt(unsigned long phys_mem_size) +{ + struct iseries_flat_dt *iseries_dt; + u64 tmp[2]; + + iseries_dt = dt_init(); + + dt_start_node(iseries_dt, ""); + + dt_prop_u32(iseries_dt, "#address-cells", 2); + dt_prop_u32(iseries_dt, "#size-cells", 2); + dt_model(iseries_dt); + + /* /memory */ + dt_start_node(iseries_dt, "memory@0"); + dt_prop_str(iseries_dt, "device_type", device_type_memory); + tmp[0] = 0; + tmp[1] = phys_mem_size; + dt_prop_u64_list(iseries_dt, "reg", tmp, 2); + dt_end_node(iseries_dt); + + /* /chosen */ + dt_start_node(iseries_dt, "chosen"); + dt_prop_str(iseries_dt, "bootargs", cmd_line); + dt_initrd(iseries_dt); + dt_end_node(iseries_dt); + + dt_cpus(iseries_dt); + + dt_vdevices(iseries_dt); + dt_pci_devices(iseries_dt); + + dt_end_node(iseries_dt); + + dt_finish(iseries_dt); + + return iseries_dt; +} diff --git a/arch/powerpc/platforms/iseries/exception.S b/arch/powerpc/platforms/iseries/exception.S new file mode 100644 index 00000000..29c02f36 --- /dev/null +++ b/arch/powerpc/platforms/iseries/exception.S @@ -0,0 +1,311 @@ +/* + * Low level routines for legacy iSeries support. + * + * Extracted from head_64.S + * + * PowerPC version + * Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org) + * + * Rewritten by Cort Dougan (cort@cs.nmt.edu) for PReP + * Copyright (C) 1996 Cort Dougan <cort@cs.nmt.edu> + * Adapted for Power Macintosh by Paul Mackerras. + * Low-level exception handlers and MMU support + * rewritten by Paul Mackerras. + * Copyright (C) 1996 Paul Mackerras. + * + * Adapted for 64bit PowerPC by Dave Engebretsen, Peter Bergner, and + * Mike Corrigan {engebret|bergner|mikejc}@us.ibm.com + * + * This file contains the low-level support and setup for the + * PowerPC-64 platform, including trap and interrupt dispatch. + * + * 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. + */ + +#include <asm/reg.h> +#include <asm/ppc_asm.h> +#include <asm/asm-offsets.h> +#include <asm/thread_info.h> +#include <asm/ptrace.h> +#include <asm/cputable.h> +#include <asm/mmu.h> + +#include "exception.h" + + .text + + .globl system_reset_iSeries +system_reset_iSeries: + bl .relative_toc + mfspr r13,SPRN_SPRG3 /* Get alpaca address */ + LOAD_REG_ADDR(r23, alpaca) + li r0,ALPACA_SIZE + sub r23,r13,r23 + divdu r24,r23,r0 /* r24 has cpu number */ + cmpwi 0,r24,0 /* Are we processor 0? */ + bne 1f + LOAD_REG_ADDR(r13, boot_paca) + mtspr SPRN_SPRG_PACA,r13 /* Save it away for the future */ + mfmsr r23 + ori r23,r23,MSR_RI + mtmsrd r23 /* RI on */ + b .__start_initialization_iSeries /* Start up the first processor */ +1: mfspr r4,SPRN_CTRLF + li r5,CTRL_RUNLATCH /* Turn off the run light */ + andc r4,r4,r5 + mtspr SPRN_CTRLT,r4 + +/* Spin on __secondary_hold_spinloop until it is updated by the boot cpu. */ +/* In the UP case we'll yield() later, and we will not access the paca anyway */ +#ifdef CONFIG_SMP +iSeries_secondary_wait_paca: + HMT_LOW + LOAD_REG_ADDR(r23, __secondary_hold_spinloop) + ld r23,0(r23) + + cmpdi 0,r23,0 + bne 2f /* go on when the master is ready */ + + /* Keep poking the Hypervisor until we're released */ + /* 8002 is a call to HvCallCfg::getLps, a harmless Hypervisor function */ + lis r3,0x8002 + rldicr r3,r3,32,15 /* r0 = (r3 << 32) & 0xffff000000000000 */ + li r0,-1 /* r0=-1 indicates a Hypervisor call */ + sc /* Invoke the hypervisor via a system call */ + b iSeries_secondary_wait_paca + +2: + HMT_MEDIUM + sync + + LOAD_REG_ADDR(r3, nr_cpu_ids) /* get number of pacas allocated */ + lwz r3,0(r3) /* nr_cpus= or NR_CPUS can limit */ + cmpld 0,r24,r3 /* is our cpu number allocated? */ + bge iSeries_secondary_yield /* no, yield forever */ + + /* Load our paca now that it's been allocated */ + LOAD_REG_ADDR(r13, paca) + ld r13,0(r13) + mulli r0,r24,PACA_SIZE + add r13,r13,r0 + mtspr SPRN_SPRG_PACA,r13 /* Save it away for the future */ + mfmsr r23 + ori r23,r23,MSR_RI + mtmsrd r23 /* RI on */ + +iSeries_secondary_smp_loop: + lbz r23,PACAPROCSTART(r13) /* Test if this processor + * should start */ + cmpwi 0,r23,0 + bne 3f /* go on when we are told */ + + HMT_LOW + /* Let the Hypervisor know we are alive */ + /* 8002 is a call to HvCallCfg::getLps, a harmless Hypervisor function */ + lis r3,0x8002 + rldicr r3,r3,32,15 /* r0 = (r3 << 32) & 0xffff000000000000 */ + li r0,-1 /* r0=-1 indicates a Hypervisor call */ + sc /* Invoke the hypervisor via a system call */ + mfspr r13,SPRN_SPRG_PACA /* Put r13 back ???? */ + b iSeries_secondary_smp_loop /* wait for signal to start */ + +3: + HMT_MEDIUM + sync + LOAD_REG_ADDR(r3,current_set) + sldi r28,r24,3 /* get current_set[cpu#] */ + ldx r3,r3,r28 + addi r1,r3,THREAD_SIZE + subi r1,r1,STACK_FRAME_OVERHEAD + + b __secondary_start /* Loop until told to go */ +#endif /* CONFIG_SMP */ + +iSeries_secondary_yield: + /* Yield the processor. This is required for non-SMP kernels + which are running on multi-threaded machines. */ + HMT_LOW + lis r3,0x8000 + rldicr r3,r3,32,15 /* r3 = (r3 << 32) & 0xffff000000000000 */ + addi r3,r3,18 /* r3 = 0x8000000000000012 which is "yield" */ + li r4,0 /* "yield timed" */ + li r5,-1 /* "yield forever" */ + li r0,-1 /* r0=-1 indicates a Hypervisor call */ + sc /* Invoke the hypervisor via a system call */ + mfspr r13,SPRN_SPRG_PACA /* Put r13 back ???? */ + b iSeries_secondary_yield /* If SMP not configured, secondaries + * loop forever */ + +/*** ISeries-LPAR interrupt handlers ***/ + + STD_EXCEPTION_ISERIES(machine_check, PACA_EXMC) + + .globl data_access_iSeries +data_access_iSeries: + mtspr SPRN_SPRG_SCRATCH0,r13 +BEGIN_FTR_SECTION + mfspr r13,SPRN_SPRG_PACA + std r9,PACA_EXSLB+EX_R9(r13) + std r10,PACA_EXSLB+EX_R10(r13) + mfspr r10,SPRN_DAR + mfspr r9,SPRN_DSISR + srdi r10,r10,60 + rlwimi r10,r9,16,0x20 + mfcr r9 + cmpwi r10,0x2c + beq .do_stab_bolted_iSeries + ld r10,PACA_EXSLB+EX_R10(r13) + std r11,PACA_EXGEN+EX_R11(r13) + ld r11,PACA_EXSLB+EX_R9(r13) + std r12,PACA_EXGEN+EX_R12(r13) + mfspr r12,SPRN_SPRG_SCRATCH0 + std r10,PACA_EXGEN+EX_R10(r13) + std r11,PACA_EXGEN+EX_R9(r13) + std r12,PACA_EXGEN+EX_R13(r13) + EXCEPTION_PROLOG_ISERIES_1 +FTR_SECTION_ELSE + EXCEPTION_PROLOG_1(PACA_EXGEN) + EXCEPTION_PROLOG_ISERIES_1 +ALT_MMU_FTR_SECTION_END_IFCLR(MMU_FTR_SLB) + b data_access_common + +.do_stab_bolted_iSeries: + std r11,PACA_EXSLB+EX_R11(r13) + std r12,PACA_EXSLB+EX_R12(r13) + mfspr r10,SPRN_SPRG_SCRATCH0 + std r10,PACA_EXSLB+EX_R13(r13) + EXCEPTION_PROLOG_ISERIES_1 + b .do_stab_bolted + + .globl data_access_slb_iSeries +data_access_slb_iSeries: + mtspr SPRN_SPRG_SCRATCH0,r13 /* save r13 */ + mfspr r13,SPRN_SPRG_PACA /* get paca address into r13 */ + std r3,PACA_EXSLB+EX_R3(r13) + mfspr r3,SPRN_DAR + std r9,PACA_EXSLB+EX_R9(r13) + mfcr r9 +#ifdef __DISABLED__ + cmpdi r3,0 + bge slb_miss_user_iseries +#endif + std r10,PACA_EXSLB+EX_R10(r13) + std r11,PACA_EXSLB+EX_R11(r13) + std r12,PACA_EXSLB+EX_R12(r13) + mfspr r10,SPRN_SPRG_SCRATCH0 + std r10,PACA_EXSLB+EX_R13(r13) + ld r12,PACALPPACAPTR(r13) + ld r12,LPPACASRR1(r12) + b .slb_miss_realmode + + STD_EXCEPTION_ISERIES(instruction_access, PACA_EXGEN) + + .globl instruction_access_slb_iSeries +instruction_access_slb_iSeries: + mtspr SPRN_SPRG_SCRATCH0,r13 /* save r13 */ + mfspr r13,SPRN_SPRG_PACA /* get paca address into r13 */ + std r3,PACA_EXSLB+EX_R3(r13) + ld r3,PACALPPACAPTR(r13) + ld r3,LPPACASRR0(r3) /* get SRR0 value */ + std r9,PACA_EXSLB+EX_R9(r13) + mfcr r9 +#ifdef __DISABLED__ + cmpdi r3,0 + bge slb_miss_user_iseries +#endif + std r10,PACA_EXSLB+EX_R10(r13) + std r11,PACA_EXSLB+EX_R11(r13) + std r12,PACA_EXSLB+EX_R12(r13) + mfspr r10,SPRN_SPRG_SCRATCH0 + std r10,PACA_EXSLB+EX_R13(r13) + ld r12,PACALPPACAPTR(r13) + ld r12,LPPACASRR1(r12) + b .slb_miss_realmode + +#ifdef __DISABLED__ +slb_miss_user_iseries: + std r10,PACA_EXGEN+EX_R10(r13) + std r11,PACA_EXGEN+EX_R11(r13) + std r12,PACA_EXGEN+EX_R12(r13) + mfspr r10,SPRG_SCRATCH0 + ld r11,PACA_EXSLB+EX_R9(r13) + ld r12,PACA_EXSLB+EX_R3(r13) + std r10,PACA_EXGEN+EX_R13(r13) + std r11,PACA_EXGEN+EX_R9(r13) + std r12,PACA_EXGEN+EX_R3(r13) + EXCEPTION_PROLOG_ISERIES_1 + b slb_miss_user_common +#endif + + MASKABLE_EXCEPTION_ISERIES(hardware_interrupt) + STD_EXCEPTION_ISERIES(alignment, PACA_EXGEN) + STD_EXCEPTION_ISERIES(program_check, PACA_EXGEN) + STD_EXCEPTION_ISERIES(fp_unavailable, PACA_EXGEN) + MASKABLE_EXCEPTION_ISERIES(decrementer) + STD_EXCEPTION_ISERIES(trap_0a, PACA_EXGEN) + STD_EXCEPTION_ISERIES(trap_0b, PACA_EXGEN) + + .globl system_call_iSeries +system_call_iSeries: + mr r9,r13 + mfspr r13,SPRN_SPRG_PACA + EXCEPTION_PROLOG_ISERIES_1 + b system_call_common + + STD_EXCEPTION_ISERIES(single_step, PACA_EXGEN) + STD_EXCEPTION_ISERIES(trap_0e, PACA_EXGEN) + STD_EXCEPTION_ISERIES(performance_monitor, PACA_EXGEN) + +decrementer_iSeries_masked: + /* We may not have a valid TOC pointer in here. */ + li r11,1 + ld r12,PACALPPACAPTR(r13) + stb r11,LPPACADECRINT(r12) + li r12,-1 + clrldi r12,r12,33 /* set DEC to 0x7fffffff */ + mtspr SPRN_DEC,r12 + /* fall through */ + +hardware_interrupt_iSeries_masked: + mtcrf 0x80,r9 /* Restore regs */ + ld r12,PACALPPACAPTR(r13) + ld r11,LPPACASRR0(r12) + ld r12,LPPACASRR1(r12) + mtspr SPRN_SRR0,r11 + mtspr SPRN_SRR1,r12 + ld r9,PACA_EXGEN+EX_R9(r13) + ld r10,PACA_EXGEN+EX_R10(r13) + ld r11,PACA_EXGEN+EX_R11(r13) + ld r12,PACA_EXGEN+EX_R12(r13) + ld r13,PACA_EXGEN+EX_R13(r13) + rfid + b . /* prevent speculative execution */ + +_INIT_STATIC(__start_initialization_iSeries) + /* Clear out the BSS */ + LOAD_REG_ADDR(r11,__bss_stop) + LOAD_REG_ADDR(r8,__bss_start) + sub r11,r11,r8 /* bss size */ + addi r11,r11,7 /* round up to an even double word */ + rldicl. r11,r11,61,3 /* shift right by 3 */ + beq 4f + addi r8,r8,-8 + li r0,0 + mtctr r11 /* zero this many doublewords */ +3: stdu r0,8(r8) + bdnz 3b +4: + LOAD_REG_ADDR(r1,init_thread_union) + addi r1,r1,THREAD_SIZE + li r0,0 + stdu r0,-STACK_FRAME_OVERHEAD(r1) + + bl .iSeries_early_setup + bl .early_setup + + /* relocation is on at this point */ + + b .start_here_common diff --git a/arch/powerpc/platforms/iseries/exception.h b/arch/powerpc/platforms/iseries/exception.h new file mode 100644 index 00000000..bae3fba5 --- /dev/null +++ b/arch/powerpc/platforms/iseries/exception.h @@ -0,0 +1,58 @@ +#ifndef _ASM_POWERPC_ISERIES_EXCEPTION_H +#define _ASM_POWERPC_ISERIES_EXCEPTION_H +/* + * Extracted from head_64.S + * + * PowerPC version + * Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org) + * + * Rewritten by Cort Dougan (cort@cs.nmt.edu) for PReP + * Copyright (C) 1996 Cort Dougan <cort@cs.nmt.edu> + * Adapted for Power Macintosh by Paul Mackerras. + * Low-level exception handlers and MMU support + * rewritten by Paul Mackerras. + * Copyright (C) 1996 Paul Mackerras. + * + * Adapted for 64bit PowerPC by Dave Engebretsen, Peter Bergner, and + * Mike Corrigan {engebret|bergner|mikejc}@us.ibm.com + * + * This file contains the low-level support and setup for the + * PowerPC-64 platform, including trap and interrupt dispatch. + * + * 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. + */ +#include <asm/exception-64s.h> + +#define EXCEPTION_PROLOG_ISERIES_1 \ + mfmsr r10; \ + ld r12,PACALPPACAPTR(r13); \ + ld r11,LPPACASRR0(r12); \ + ld r12,LPPACASRR1(r12); \ + ori r10,r10,MSR_RI; \ + mtmsrd r10,1 + +#define STD_EXCEPTION_ISERIES(label, area) \ + .globl label##_iSeries; \ +label##_iSeries: \ + HMT_MEDIUM; \ + mtspr SPRN_SPRG_SCRATCH0,r13; /* save r13 */ \ + EXCEPTION_PROLOG_1(area); \ + EXCEPTION_PROLOG_ISERIES_1; \ + b label##_common + +#define MASKABLE_EXCEPTION_ISERIES(label) \ + .globl label##_iSeries; \ +label##_iSeries: \ + HMT_MEDIUM; \ + mtspr SPRN_SPRG_SCRATCH0,r13; /* save r13 */ \ + EXCEPTION_PROLOG_1(PACA_EXGEN); \ + lbz r10,PACASOFTIRQEN(r13); \ + cmpwi 0,r10,0; \ + beq- label##_iSeries_masked; \ + EXCEPTION_PROLOG_ISERIES_1; \ + b label##_common; \ + +#endif /* _ASM_POWERPC_ISERIES_EXCEPTION_H */ diff --git a/arch/powerpc/platforms/iseries/htab.c b/arch/powerpc/platforms/iseries/htab.c new file mode 100644 index 00000000..3ae66ab9 --- /dev/null +++ b/arch/powerpc/platforms/iseries/htab.c @@ -0,0 +1,257 @@ +/* + * iSeries hashtable management. + * Derived from pSeries_htab.c + * + * SMP scalability work: + * Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM + * + * 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. + */ +#include <asm/machdep.h> +#include <asm/pgtable.h> +#include <asm/mmu.h> +#include <asm/mmu_context.h> +#include <asm/abs_addr.h> +#include <linux/spinlock.h> + +#include "call_hpt.h" + +static spinlock_t iSeries_hlocks[64] __cacheline_aligned_in_smp; + +/* + * Very primitive algorithm for picking up a lock + */ +static inline void iSeries_hlock(unsigned long slot) +{ + if (slot & 0x8) + slot = ~slot; + spin_lock(&iSeries_hlocks[(slot >> 4) & 0x3f]); +} + +static inline void iSeries_hunlock(unsigned long slot) +{ + if (slot & 0x8) + slot = ~slot; + spin_unlock(&iSeries_hlocks[(slot >> 4) & 0x3f]); +} + +static long iSeries_hpte_insert(unsigned long hpte_group, unsigned long va, + unsigned long pa, unsigned long rflags, + unsigned long vflags, int psize, int ssize) +{ + long slot; + struct hash_pte lhpte; + int secondary = 0; + + BUG_ON(psize != MMU_PAGE_4K); + + /* + * The hypervisor tries both primary and secondary. + * If we are being called to insert in the secondary, + * it means we have already tried both primary and secondary, + * so we return failure immediately. + */ + if (vflags & HPTE_V_SECONDARY) + return -1; + + iSeries_hlock(hpte_group); + + slot = HvCallHpt_findValid(&lhpte, va >> HW_PAGE_SHIFT); + if (unlikely(lhpte.v & HPTE_V_VALID)) { + if (vflags & HPTE_V_BOLTED) { + HvCallHpt_setSwBits(slot, 0x10, 0); + HvCallHpt_setPp(slot, PP_RWXX); + iSeries_hunlock(hpte_group); + if (slot < 0) + return 0x8 | (slot & 7); + else + return slot & 7; + } + BUG(); + } + + if (slot == -1) { /* No available entry found in either group */ + iSeries_hunlock(hpte_group); + return -1; + } + + if (slot < 0) { /* MSB set means secondary group */ + vflags |= HPTE_V_SECONDARY; + secondary = 1; + slot &= 0x7fffffffffffffff; + } + + + lhpte.v = hpte_encode_v(va, MMU_PAGE_4K, MMU_SEGSIZE_256M) | + vflags | HPTE_V_VALID; + lhpte.r = hpte_encode_r(phys_to_abs(pa), MMU_PAGE_4K) | rflags; + + /* Now fill in the actual HPTE */ + HvCallHpt_addValidate(slot, secondary, &lhpte); + + iSeries_hunlock(hpte_group); + + return (secondary << 3) | (slot & 7); +} + +static unsigned long iSeries_hpte_getword0(unsigned long slot) +{ + struct hash_pte hpte; + + HvCallHpt_get(&hpte, slot); + return hpte.v; +} + +static long iSeries_hpte_remove(unsigned long hpte_group) +{ + unsigned long slot_offset; + int i; + unsigned long hpte_v; + + /* Pick a random slot to start at */ + slot_offset = mftb() & 0x7; + + iSeries_hlock(hpte_group); + + for (i = 0; i < HPTES_PER_GROUP; i++) { + hpte_v = iSeries_hpte_getword0(hpte_group + slot_offset); + + if (! (hpte_v & HPTE_V_BOLTED)) { + HvCallHpt_invalidateSetSwBitsGet(hpte_group + + slot_offset, 0, 0); + iSeries_hunlock(hpte_group); + return i; + } + + slot_offset++; + slot_offset &= 0x7; + } + + iSeries_hunlock(hpte_group); + + return -1; +} + +/* + * The HyperVisor expects the "flags" argument in this form: + * bits 0..59 : reserved + * bit 60 : N + * bits 61..63 : PP2,PP1,PP0 + */ +static long iSeries_hpte_updatepp(unsigned long slot, unsigned long newpp, + unsigned long va, int psize, int ssize, int local) +{ + struct hash_pte hpte; + unsigned long want_v; + + iSeries_hlock(slot); + + HvCallHpt_get(&hpte, slot); + want_v = hpte_encode_v(va, MMU_PAGE_4K, MMU_SEGSIZE_256M); + + if (HPTE_V_COMPARE(hpte.v, want_v) && (hpte.v & HPTE_V_VALID)) { + /* + * Hypervisor expects bits as NPPP, which is + * different from how they are mapped in our PP. + */ + HvCallHpt_setPp(slot, (newpp & 0x3) | ((newpp & 0x4) << 1)); + iSeries_hunlock(slot); + return 0; + } + iSeries_hunlock(slot); + + return -1; +} + +/* + * Functions used to find the PTE for a particular virtual address. + * Only used during boot when bolting pages. + * + * Input : vpn : virtual page number + * Output: PTE index within the page table of the entry + * -1 on failure + */ +static long iSeries_hpte_find(unsigned long vpn) +{ + struct hash_pte hpte; + long slot; + + /* + * The HvCallHpt_findValid interface is as follows: + * 0xffffffffffffffff : No entry found. + * 0x00000000xxxxxxxx : Entry found in primary group, slot x + * 0x80000000xxxxxxxx : Entry found in secondary group, slot x + */ + slot = HvCallHpt_findValid(&hpte, vpn); + if (hpte.v & HPTE_V_VALID) { + if (slot < 0) { + slot &= 0x7fffffffffffffff; + slot = -slot; + } + } else + slot = -1; + return slot; +} + +/* + * Update the page protection bits. Intended to be used to create + * guard pages for kernel data structures on pages which are bolted + * in the HPT. Assumes pages being operated on will not be stolen. + * Does not work on large pages. + * + * No need to lock here because we should be the only user. + */ +static void iSeries_hpte_updateboltedpp(unsigned long newpp, unsigned long ea, + int psize, int ssize) +{ + unsigned long vsid,va,vpn; + long slot; + + BUG_ON(psize != MMU_PAGE_4K); + + vsid = get_kernel_vsid(ea, MMU_SEGSIZE_256M); + va = (vsid << 28) | (ea & 0x0fffffff); + vpn = va >> HW_PAGE_SHIFT; + slot = iSeries_hpte_find(vpn); + if (slot == -1) + panic("updateboltedpp: Could not find page to bolt\n"); + HvCallHpt_setPp(slot, newpp); +} + +static void iSeries_hpte_invalidate(unsigned long slot, unsigned long va, + int psize, int ssize, int local) +{ + unsigned long hpte_v; + unsigned long avpn = va >> 23; + unsigned long flags; + + local_irq_save(flags); + + iSeries_hlock(slot); + + hpte_v = iSeries_hpte_getword0(slot); + + if ((HPTE_V_AVPN_VAL(hpte_v) == avpn) && (hpte_v & HPTE_V_VALID)) + HvCallHpt_invalidateSetSwBitsGet(slot, 0, 0); + + iSeries_hunlock(slot); + + local_irq_restore(flags); +} + +void __init hpte_init_iSeries(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(iSeries_hlocks); i++) + spin_lock_init(&iSeries_hlocks[i]); + + ppc_md.hpte_invalidate = iSeries_hpte_invalidate; + ppc_md.hpte_updatepp = iSeries_hpte_updatepp; + ppc_md.hpte_updateboltedpp = iSeries_hpte_updateboltedpp; + ppc_md.hpte_insert = iSeries_hpte_insert; + ppc_md.hpte_remove = iSeries_hpte_remove; +} diff --git a/arch/powerpc/platforms/iseries/hvcall.S b/arch/powerpc/platforms/iseries/hvcall.S new file mode 100644 index 00000000..07ae6ad5 --- /dev/null +++ b/arch/powerpc/platforms/iseries/hvcall.S @@ -0,0 +1,94 @@ +/* + * This file contains the code to perform calls to the + * iSeries LPAR hypervisor + * + * 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. + */ + +#include <asm/ppc_asm.h> +#include <asm/processor.h> +#include <asm/ptrace.h> /* XXX for STACK_FRAME_OVERHEAD */ + + .text + +/* + * Hypervisor call + * + * Invoke the iSeries hypervisor via the System Call instruction + * Parameters are passed to this routine in registers r3 - r10 + * + * r3 contains the HV function to be called + * r4-r10 contain the operands to the hypervisor function + * + */ + +_GLOBAL(HvCall) +_GLOBAL(HvCall0) +_GLOBAL(HvCall1) +_GLOBAL(HvCall2) +_GLOBAL(HvCall3) +_GLOBAL(HvCall4) +_GLOBAL(HvCall5) +_GLOBAL(HvCall6) +_GLOBAL(HvCall7) + + + mfcr r0 + std r0,-8(r1) + stdu r1,-(STACK_FRAME_OVERHEAD+16)(r1) + + /* r0 = 0xffffffffffffffff indicates a hypervisor call */ + + li r0,-1 + + /* Invoke the hypervisor */ + + sc + + ld r1,0(r1) + ld r0,-8(r1) + mtcrf 0xff,r0 + + /* return to caller, return value in r3 */ + + blr + +_GLOBAL(HvCall0Ret16) +_GLOBAL(HvCall1Ret16) +_GLOBAL(HvCall2Ret16) +_GLOBAL(HvCall3Ret16) +_GLOBAL(HvCall4Ret16) +_GLOBAL(HvCall5Ret16) +_GLOBAL(HvCall6Ret16) +_GLOBAL(HvCall7Ret16) + + mfcr r0 + std r0,-8(r1) + std r31,-16(r1) + stdu r1,-(STACK_FRAME_OVERHEAD+32)(r1) + + mr r31,r4 + li r0,-1 + mr r4,r5 + mr r5,r6 + mr r6,r7 + mr r7,r8 + mr r8,r9 + mr r9,r10 + + sc + + std r3,0(r31) + std r4,8(r31) + + mr r3,r5 + + ld r1,0(r1) + ld r0,-8(r1) + mtcrf 0xff,r0 + ld r31,-16(r1) + + blr diff --git a/arch/powerpc/platforms/iseries/hvlog.c b/arch/powerpc/platforms/iseries/hvlog.c new file mode 100644 index 00000000..f476d711 --- /dev/null +++ b/arch/powerpc/platforms/iseries/hvlog.c @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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. + */ + +#include <asm/page.h> +#include <asm/abs_addr.h> +#include <asm/iseries/hv_call.h> +#include <asm/iseries/hv_call_sc.h> +#include <asm/iseries/hv_types.h> + + +void HvCall_writeLogBuffer(const void *buffer, u64 len) +{ + struct HvLpBufferList hv_buf; + u64 left_this_page; + u64 cur = virt_to_abs(buffer); + + while (len) { + hv_buf.addr = cur; + left_this_page = ((cur & HW_PAGE_MASK) + HW_PAGE_SIZE) - cur; + if (left_this_page > len) + left_this_page = len; + hv_buf.len = left_this_page; + len -= left_this_page; + HvCall2(HvCallBaseWriteLogBuffer, + virt_to_abs(&hv_buf), + left_this_page); + cur = (cur & HW_PAGE_MASK) + HW_PAGE_SIZE; + } +} diff --git a/arch/powerpc/platforms/iseries/hvlpconfig.c b/arch/powerpc/platforms/iseries/hvlpconfig.c new file mode 100644 index 00000000..f0475f0b --- /dev/null +++ b/arch/powerpc/platforms/iseries/hvlpconfig.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2001 Kyle A. Lucke, IBM Corporation + * + * 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/module.h> +#include <asm/iseries/hv_lp_config.h> +#include "it_lp_naca.h" + +HvLpIndex HvLpConfig_getLpIndex_outline(void) +{ + return HvLpConfig_getLpIndex(); +} +EXPORT_SYMBOL(HvLpConfig_getLpIndex_outline); + +HvLpIndex HvLpConfig_getLpIndex(void) +{ + return itLpNaca.xLpIndex; +} +EXPORT_SYMBOL(HvLpConfig_getLpIndex); + +HvLpIndex HvLpConfig_getPrimaryLpIndex(void) +{ + return itLpNaca.xPrimaryLpIndex; +} +EXPORT_SYMBOL_GPL(HvLpConfig_getPrimaryLpIndex); diff --git a/arch/powerpc/platforms/iseries/iommu.c b/arch/powerpc/platforms/iseries/iommu.c new file mode 100644 index 00000000..d8b76335 --- /dev/null +++ b/arch/powerpc/platforms/iseries/iommu.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2001 Mike Corrigan & Dave Engebretsen, IBM Corporation + * + * Rewrite, cleanup: + * + * Copyright (C) 2004 Olof Johansson <olof@lixom.net>, IBM Corporation + * Copyright (C) 2006 Olof Johansson <olof@lixom.net> + * + * Dynamic DMA mapping support, iSeries-specific parts. + * + * + * 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/dma-mapping.h> +#include <linux/list.h> +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <asm/iommu.h> +#include <asm/vio.h> +#include <asm/tce.h> +#include <asm/machdep.h> +#include <asm/abs_addr.h> +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#include <asm/iseries/hv_call_xm.h> +#include <asm/iseries/hv_call_event.h> +#include <asm/iseries/iommu.h> + +static int tce_build_iSeries(struct iommu_table *tbl, long index, long npages, + unsigned long uaddr, enum dma_data_direction direction, + struct dma_attrs *attrs) +{ + u64 rc; + u64 tce, rpn; + + while (npages--) { + rpn = virt_to_abs(uaddr) >> TCE_SHIFT; + tce = (rpn & TCE_RPN_MASK) << TCE_RPN_SHIFT; + + if (tbl->it_type == TCE_VB) { + /* Virtual Bus */ + tce |= TCE_VALID|TCE_ALLIO; + if (direction != DMA_TO_DEVICE) + tce |= TCE_VB_WRITE; + } else { + /* PCI Bus */ + tce |= TCE_PCI_READ; /* Read allowed */ + if (direction != DMA_TO_DEVICE) + tce |= TCE_PCI_WRITE; + } + + rc = HvCallXm_setTce((u64)tbl->it_index, (u64)index, tce); + if (rc) + panic("PCI_DMA: HvCallXm_setTce failed, Rc: 0x%llx\n", + rc); + index++; + uaddr += TCE_PAGE_SIZE; + } + return 0; +} + +static void tce_free_iSeries(struct iommu_table *tbl, long index, long npages) +{ + u64 rc; + + while (npages--) { + rc = HvCallXm_setTce((u64)tbl->it_index, (u64)index, 0); + if (rc) + panic("PCI_DMA: HvCallXm_setTce failed, Rc: 0x%llx\n", + rc); + index++; + } +} + +/* + * Structure passed to HvCallXm_getTceTableParms + */ +struct iommu_table_cb { + unsigned long itc_busno; /* Bus number for this tce table */ + unsigned long itc_start; /* Will be NULL for secondary */ + unsigned long itc_totalsize; /* Size (in pages) of whole table */ + unsigned long itc_offset; /* Index into real tce table of the + start of our section */ + unsigned long itc_size; /* Size (in pages) of our section */ + unsigned long itc_index; /* Index of this tce table */ + unsigned short itc_maxtables; /* Max num of tables for partition */ + unsigned char itc_virtbus; /* Flag to indicate virtual bus */ + unsigned char itc_slotno; /* IOA Tce Slot Index */ + unsigned char itc_rsvd[4]; +}; + +/* + * Call Hv with the architected data structure to get TCE table info. + * info. Put the returned data into the Linux representation of the + * TCE table data. + * The Hardware Tce table comes in three flavors. + * 1. TCE table shared between Buses. + * 2. TCE table per Bus. + * 3. TCE Table per IOA. + */ +void iommu_table_getparms_iSeries(unsigned long busno, + unsigned char slotno, + unsigned char virtbus, + struct iommu_table* tbl) +{ + struct iommu_table_cb *parms; + + parms = kzalloc(sizeof(*parms), GFP_KERNEL); + if (parms == NULL) + panic("PCI_DMA: TCE Table Allocation failed."); + + parms->itc_busno = busno; + parms->itc_slotno = slotno; + parms->itc_virtbus = virtbus; + + HvCallXm_getTceTableParms(iseries_hv_addr(parms)); + + if (parms->itc_size == 0) + panic("PCI_DMA: parms->size is zero, parms is 0x%p", parms); + + /* itc_size is in pages worth of table, it_size is in # of entries */ + tbl->it_size = (parms->itc_size * TCE_PAGE_SIZE) / TCE_ENTRY_SIZE; + tbl->it_busno = parms->itc_busno; + tbl->it_offset = parms->itc_offset; + tbl->it_index = parms->itc_index; + tbl->it_blocksize = 1; + tbl->it_type = virtbus ? TCE_VB : TCE_PCI; + + kfree(parms); +} + + +#ifdef CONFIG_PCI +/* + * This function compares the known tables to find an iommu_table + * that has already been built for hardware TCEs. + */ +static struct iommu_table *iommu_table_find(struct iommu_table * tbl) +{ + struct device_node *node; + + for (node = NULL; (node = of_find_all_nodes(node)); ) { + struct pci_dn *pdn = PCI_DN(node); + struct iommu_table *it; + + if (pdn == NULL) + continue; + it = pdn->iommu_table; + if ((it != NULL) && + (it->it_type == TCE_PCI) && + (it->it_offset == tbl->it_offset) && + (it->it_index == tbl->it_index) && + (it->it_size == tbl->it_size)) { + of_node_put(node); + return it; + } + } + return NULL; +} + + +static void pci_dma_dev_setup_iseries(struct pci_dev *pdev) +{ + struct iommu_table *tbl; + struct device_node *dn = pci_device_to_OF_node(pdev); + struct pci_dn *pdn = PCI_DN(dn); + const u32 *lsn = of_get_property(dn, "linux,logical-slot-number", NULL); + + BUG_ON(lsn == NULL); + + tbl = kzalloc(sizeof(struct iommu_table), GFP_KERNEL); + + iommu_table_getparms_iSeries(pdn->busno, *lsn, 0, tbl); + + /* Look for existing tce table */ + pdn->iommu_table = iommu_table_find(tbl); + if (pdn->iommu_table == NULL) + pdn->iommu_table = iommu_init_table(tbl, -1); + else + kfree(tbl); + set_iommu_table_base(&pdev->dev, pdn->iommu_table); +} +#else +#define pci_dma_dev_setup_iseries NULL +#endif + +static struct iommu_table veth_iommu_table; +static struct iommu_table vio_iommu_table; + +void *iseries_hv_alloc(size_t size, dma_addr_t *dma_handle, gfp_t flag) +{ + return iommu_alloc_coherent(NULL, &vio_iommu_table, size, dma_handle, + DMA_BIT_MASK(32), flag, -1); +} +EXPORT_SYMBOL_GPL(iseries_hv_alloc); + +void iseries_hv_free(size_t size, void *vaddr, dma_addr_t dma_handle) +{ + iommu_free_coherent(&vio_iommu_table, size, vaddr, dma_handle); +} +EXPORT_SYMBOL_GPL(iseries_hv_free); + +dma_addr_t iseries_hv_map(void *vaddr, size_t size, + enum dma_data_direction direction) +{ + return iommu_map_page(NULL, &vio_iommu_table, virt_to_page(vaddr), + (unsigned long)vaddr % PAGE_SIZE, size, + DMA_BIT_MASK(32), direction, NULL); +} + +void iseries_hv_unmap(dma_addr_t dma_handle, size_t size, + enum dma_data_direction direction) +{ + iommu_unmap_page(&vio_iommu_table, dma_handle, size, direction, NULL); +} + +void __init iommu_vio_init(void) +{ + 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; + + if (!iommu_init_table(&veth_iommu_table, -1)) + printk("Virtual Bus VETH TCE table failed.\n"); + if (!iommu_init_table(&vio_iommu_table, -1)) + printk("Virtual Bus VIO TCE table failed.\n"); +} + +struct iommu_table *vio_build_iommu_table_iseries(struct vio_dev *dev) +{ + if (strcmp(dev->type, "network") == 0) + return &veth_iommu_table; + return &vio_iommu_table; +} + +void iommu_init_early_iSeries(void) +{ + ppc_md.tce_build = tce_build_iSeries; + ppc_md.tce_free = tce_free_iSeries; + + ppc_md.pci_dma_dev_setup = pci_dma_dev_setup_iseries; + set_pci_dma_ops(&dma_iommu_ops); +} diff --git a/arch/powerpc/platforms/iseries/ipl_parms.h b/arch/powerpc/platforms/iseries/ipl_parms.h new file mode 100644 index 00000000..83e4ca42 --- /dev/null +++ b/arch/powerpc/platforms/iseries/ipl_parms.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ +#ifndef _ISERIES_IPL_PARMS_H +#define _ISERIES_IPL_PARMS_H + +/* + * This struct maps the IPL Parameters DMA'd from the SP. + * + * Warning: + * This data must map in exactly 64 bytes and match the architecture for + * the IPL parms + */ + +#include <asm/types.h> + +struct ItIplParmsReal { + u8 xFormat; // Defines format of IplParms x00-x00 + u8 xRsvd01:6; // Reserved x01-x01 + u8 xAlternateSearch:1; // Alternate search indicator ... + u8 xUaSupplied:1; // UA Supplied on programmed IPL... + u8 xLsUaFormat; // Format byte for UA x02-x02 + u8 xRsvd02; // Reserved x03-x03 + u32 xLsUa; // LS UA x04-x07 + u32 xUnusedLsLid; // First OS LID to load x08-x0B + u16 xLsBusNumber; // LS Bus Number x0C-x0D + u8 xLsCardAdr; // LS Card Address x0E-x0E + u8 xLsBoardAdr; // LS Board Address x0F-x0F + u32 xRsvd03; // Reserved x10-x13 + u8 xSpcnPresent:1; // SPCN present x14-x14 + u8 xCpmPresent:1; // CPM present ... + u8 xRsvd04:6; // Reserved ... + u8 xRsvd05:4; // Reserved x15-x15 + u8 xKeyLock:4; // Keylock setting ... + u8 xRsvd06:6; // Reserved x16-x16 + u8 xIplMode:2; // Ipl mode (A|B|C|D) ... + u8 xHwIplType; // Fast v slow v slow EC HW IPL x17-x17 + u16 xCpmEnabledIpl:1; // CPM in effect when IPL initiatedx18-x19 + u16 xPowerOnResetIpl:1; // Indicate POR condition ... + u16 xMainStorePreserved:1; // Main Storage is preserved ... + u16 xRsvd07:13; // Reserved ... + u16 xIplSource:16; // Ipl source x1A-x1B + u8 xIplReason:8; // Reason for this IPL x1C-x1C + u8 xRsvd08; // Reserved x1D-x1D + u16 xRsvd09; // Reserved x1E-x1F + u16 xSysBoxType; // System Box Type x20-x21 + u16 xSysProcType; // System Processor Type x22-x23 + u32 xRsvd10; // Reserved x24-x27 + u64 xRsvd11; // Reserved x28-x2F + u64 xRsvd12; // Reserved x30-x37 + u64 xRsvd13; // Reserved x38-x3F +}; + +#endif /* _ISERIES_IPL_PARMS_H */ diff --git a/arch/powerpc/platforms/iseries/irq.c b/arch/powerpc/platforms/iseries/irq.c new file mode 100644 index 00000000..b2103453 --- /dev/null +++ b/arch/powerpc/platforms/iseries/irq.c @@ -0,0 +1,400 @@ +/* + * This module supports the iSeries PCI bus interrupt handling + * Copyright (C) 20yy <Robert L Holtorf> <IBM Corp> + * Copyright (C) 2004-2005 IBM Corporation + * + * 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 + * + * Change Activity: + * Created, December 13, 2000 by Wayne Holm + * End Change Activity + */ +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/threads.h> +#include <linux/smp.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/bootmem.h> +#include <linux/irq.h> +#include <linux/spinlock.h> + +#include <asm/paca.h> +#include <asm/iseries/hv_types.h> +#include <asm/iseries/hv_lp_event.h> +#include <asm/iseries/hv_call_xm.h> +#include <asm/iseries/it_lp_queue.h> + +#include "irq.h" +#include "pci.h" +#include "call_pci.h" + +#ifdef CONFIG_PCI + +enum pci_event_type { + pe_bus_created = 0, /* PHB has been created */ + pe_bus_error = 1, /* PHB has failed */ + pe_bus_failed = 2, /* Msg to Secondary, Primary failed bus */ + pe_node_failed = 4, /* Multi-adapter bridge has failed */ + pe_node_recovered = 5, /* Multi-adapter bridge has recovered */ + pe_bus_recovered = 12, /* PHB has been recovered */ + pe_unquiese_bus = 18, /* Secondary bus unqiescing */ + pe_bridge_error = 21, /* Bridge Error */ + pe_slot_interrupt = 22 /* Slot interrupt */ +}; + +struct pci_event { + struct HvLpEvent event; + union { + u64 __align; /* Align on an 8-byte boundary */ + struct { + u32 fisr; + HvBusNumber bus_number; + HvSubBusNumber sub_bus_number; + HvAgentId dev_id; + } slot; + struct { + HvBusNumber bus_number; + HvSubBusNumber sub_bus_number; + } bus; + struct { + HvBusNumber bus_number; + HvSubBusNumber sub_bus_number; + HvAgentId dev_id; + } node; + } data; +}; + +static DEFINE_SPINLOCK(pending_irqs_lock); +static int num_pending_irqs; +static int pending_irqs[NR_IRQS]; + +static void int_received(struct pci_event *event) +{ + int irq; + + switch (event->event.xSubtype) { + case pe_slot_interrupt: + irq = event->event.xCorrelationToken; + if (irq < NR_IRQS) { + spin_lock(&pending_irqs_lock); + pending_irqs[irq]++; + num_pending_irqs++; + spin_unlock(&pending_irqs_lock); + } else { + printk(KERN_WARNING "int_received: bad irq number %d\n", + irq); + HvCallPci_eoi(event->data.slot.bus_number, + event->data.slot.sub_bus_number, + event->data.slot.dev_id); + } + break; + /* Ignore error recovery events for now */ + case pe_bus_created: + printk(KERN_INFO "int_received: system bus %d created\n", + event->data.bus.bus_number); + break; + case pe_bus_error: + case pe_bus_failed: + printk(KERN_INFO "int_received: system bus %d failed\n", + event->data.bus.bus_number); + break; + case pe_bus_recovered: + case pe_unquiese_bus: + printk(KERN_INFO "int_received: system bus %d recovered\n", + event->data.bus.bus_number); + break; + case pe_node_failed: + case pe_bridge_error: + printk(KERN_INFO + "int_received: multi-adapter bridge %d/%d/%d failed\n", + event->data.node.bus_number, + event->data.node.sub_bus_number, + event->data.node.dev_id); + break; + case pe_node_recovered: + printk(KERN_INFO + "int_received: multi-adapter bridge %d/%d/%d recovered\n", + event->data.node.bus_number, + event->data.node.sub_bus_number, + event->data.node.dev_id); + break; + default: + printk(KERN_ERR + "int_received: unrecognized event subtype 0x%x\n", + event->event.xSubtype); + break; + } +} + +static void pci_event_handler(struct HvLpEvent *event) +{ + if (event && (event->xType == HvLpEvent_Type_PciIo)) { + if (hvlpevent_is_int(event)) + int_received((struct pci_event *)event); + else + printk(KERN_ERR + "pci_event_handler: unexpected ack received\n"); + } else if (event) + printk(KERN_ERR + "pci_event_handler: Unrecognized PCI event type 0x%x\n", + (int)event->xType); + else + printk(KERN_ERR "pci_event_handler: NULL event received\n"); +} + +#define REAL_IRQ_TO_SUBBUS(irq) (((irq) >> 14) & 0xff) +#define REAL_IRQ_TO_BUS(irq) ((((irq) >> 6) & 0xff) + 1) +#define REAL_IRQ_TO_IDSEL(irq) ((((irq) >> 3) & 7) + 1) +#define REAL_IRQ_TO_FUNC(irq) ((irq) & 7) + +/* + * This will be called by device drivers (via enable_IRQ) + * to enable INTA in the bridge interrupt status register. + */ +static void iseries_enable_IRQ(struct irq_data *d) +{ + u32 bus, dev_id, function, mask; + const u32 sub_bus = 0; + unsigned int rirq = (unsigned int)irqd_to_hwirq(d); + + /* The IRQ has already been locked by the caller */ + bus = REAL_IRQ_TO_BUS(rirq); + function = REAL_IRQ_TO_FUNC(rirq); + dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; + + /* Unmask secondary INTA */ + mask = 0x80000000; + HvCallPci_unmaskInterrupts(bus, sub_bus, dev_id, mask); +} + +/* This is called by iseries_activate_IRQs */ +static unsigned int iseries_startup_IRQ(struct irq_data *d) +{ + u32 bus, dev_id, function, mask; + const u32 sub_bus = 0; + unsigned int rirq = (unsigned int)irqd_to_hwirq(d); + + bus = REAL_IRQ_TO_BUS(rirq); + function = REAL_IRQ_TO_FUNC(rirq); + dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; + + /* Link the IRQ number to the bridge */ + HvCallXm_connectBusUnit(bus, sub_bus, dev_id, d->irq); + + /* Unmask bridge interrupts in the FISR */ + mask = 0x01010000 << function; + HvCallPci_unmaskFisr(bus, sub_bus, dev_id, mask); + iseries_enable_IRQ(d); + return 0; +} + +/* + * This is called out of iSeries_fixup to activate interrupt + * generation for usable slots + */ +void __init iSeries_activate_IRQs() +{ + int irq; + unsigned long flags; + + for_each_irq (irq) { + struct irq_desc *desc = irq_to_desc(irq); + struct irq_chip *chip; + + if (!desc) + continue; + + chip = irq_desc_get_chip(desc); + if (chip && chip->irq_startup) { + raw_spin_lock_irqsave(&desc->lock, flags); + chip->irq_startup(&desc->irq_data); + raw_spin_unlock_irqrestore(&desc->lock, flags); + } + } +} + +/* this is not called anywhere currently */ +static void iseries_shutdown_IRQ(struct irq_data *d) +{ + u32 bus, dev_id, function, mask; + const u32 sub_bus = 0; + unsigned int rirq = (unsigned int)irqd_to_hwirq(d); + + /* irq should be locked by the caller */ + bus = REAL_IRQ_TO_BUS(rirq); + function = REAL_IRQ_TO_FUNC(rirq); + dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; + + /* Invalidate the IRQ number in the bridge */ + HvCallXm_connectBusUnit(bus, sub_bus, dev_id, 0); + + /* Mask bridge interrupts in the FISR */ + mask = 0x01010000 << function; + HvCallPci_maskFisr(bus, sub_bus, dev_id, mask); +} + +/* + * This will be called by device drivers (via disable_IRQ) + * to disable INTA in the bridge interrupt status register. + */ +static void iseries_disable_IRQ(struct irq_data *d) +{ + u32 bus, dev_id, function, mask; + const u32 sub_bus = 0; + unsigned int rirq = (unsigned int)irqd_to_hwirq(d); + + /* The IRQ has already been locked by the caller */ + bus = REAL_IRQ_TO_BUS(rirq); + function = REAL_IRQ_TO_FUNC(rirq); + dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; + + /* Mask secondary INTA */ + mask = 0x80000000; + HvCallPci_maskInterrupts(bus, sub_bus, dev_id, mask); +} + +static void iseries_end_IRQ(struct irq_data *d) +{ + unsigned int rirq = (unsigned int)irqd_to_hwirq(d); + + HvCallPci_eoi(REAL_IRQ_TO_BUS(rirq), REAL_IRQ_TO_SUBBUS(rirq), + (REAL_IRQ_TO_IDSEL(rirq) << 4) + REAL_IRQ_TO_FUNC(rirq)); +} + +static struct irq_chip iseries_pic = { + .name = "iSeries", + .irq_startup = iseries_startup_IRQ, + .irq_shutdown = iseries_shutdown_IRQ, + .irq_unmask = iseries_enable_IRQ, + .irq_mask = iseries_disable_IRQ, + .irq_eoi = iseries_end_IRQ +}; + +/* + * This is called out of iSeries_scan_slot to allocate an IRQ for an EADS slot + * It calculates the irq value for the slot. + * Note that sub_bus is always 0 (at the moment at least). + */ +int __init iSeries_allocate_IRQ(HvBusNumber bus, + HvSubBusNumber sub_bus, u32 bsubbus) +{ + unsigned int realirq; + u8 idsel = ISERIES_GET_DEVICE_FROM_SUBBUS(bsubbus); + u8 function = ISERIES_GET_FUNCTION_FROM_SUBBUS(bsubbus); + + realirq = (((((sub_bus << 8) + (bus - 1)) << 3) + (idsel - 1)) << 3) + + function; + + return irq_create_mapping(NULL, realirq); +} + +#endif /* CONFIG_PCI */ + +/* + * Get the next pending IRQ. + */ +unsigned int iSeries_get_irq(void) +{ + int irq = NO_IRQ_IGNORE; + +#ifdef CONFIG_SMP + if (get_lppaca()->int_dword.fields.ipi_cnt) { + get_lppaca()->int_dword.fields.ipi_cnt = 0; + smp_ipi_demux(); + } +#endif /* CONFIG_SMP */ + if (hvlpevent_is_pending()) + process_hvlpevents(); + +#ifdef CONFIG_PCI + if (num_pending_irqs) { + spin_lock(&pending_irqs_lock); + for (irq = 0; irq < NR_IRQS; irq++) { + if (pending_irqs[irq]) { + pending_irqs[irq]--; + num_pending_irqs--; + break; + } + } + spin_unlock(&pending_irqs_lock); + if (irq >= NR_IRQS) + irq = NO_IRQ_IGNORE; + } +#endif + + return irq; +} + +#ifdef CONFIG_PCI + +static int iseries_irq_host_map(struct irq_host *h, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_and_handler(virq, &iseries_pic, handle_fasteoi_irq); + + return 0; +} + +static int iseries_irq_host_match(struct irq_host *h, struct device_node *np) +{ + /* Match all */ + return 1; +} + +static struct irq_host_ops iseries_irq_host_ops = { + .map = iseries_irq_host_map, + .match = iseries_irq_host_match, +}; + +/* + * This is called by init_IRQ. set in ppc_md.init_IRQ by iSeries_setup.c + * It must be called before the bus walk. + */ +void __init iSeries_init_IRQ(void) +{ + /* Register PCI event handler and open an event path */ + struct irq_host *host; + int ret; + + /* + * The Hypervisor only allows us up to 256 interrupt + * sources (the irq number is passed in a u8). + */ + irq_set_virq_count(256); + + /* Create irq host. No need for a revmap since HV will give us + * back our virtual irq number + */ + host = irq_alloc_host(NULL, IRQ_HOST_MAP_NOMAP, 0, + &iseries_irq_host_ops, 0); + BUG_ON(host == NULL); + irq_set_default_host(host); + + ret = HvLpEvent_registerHandler(HvLpEvent_Type_PciIo, + &pci_event_handler); + if (ret == 0) { + ret = HvLpEvent_openPath(HvLpEvent_Type_PciIo, 0); + if (ret != 0) + printk(KERN_ERR "iseries_init_IRQ: open event path " + "failed with rc 0x%x\n", ret); + } else + printk(KERN_ERR "iseries_init_IRQ: register handler " + "failed with rc 0x%x\n", ret); +} + +#endif /* CONFIG_PCI */ diff --git a/arch/powerpc/platforms/iseries/irq.h b/arch/powerpc/platforms/iseries/irq.h new file mode 100644 index 00000000..a1c23607 --- /dev/null +++ b/arch/powerpc/platforms/iseries/irq.h @@ -0,0 +1,13 @@ +#ifndef _ISERIES_IRQ_H +#define _ISERIES_IRQ_H + +#ifdef CONFIG_PCI +extern void iSeries_init_IRQ(void); +extern int iSeries_allocate_IRQ(HvBusNumber, HvSubBusNumber, u32); +extern void iSeries_activate_IRQs(void); +#else +#define iSeries_init_IRQ NULL +#endif +extern unsigned int iSeries_get_irq(void); + +#endif /* _ISERIES_IRQ_H */ diff --git a/arch/powerpc/platforms/iseries/it_exp_vpd_panel.h b/arch/powerpc/platforms/iseries/it_exp_vpd_panel.h new file mode 100644 index 00000000..6de9097b --- /dev/null +++ b/arch/powerpc/platforms/iseries/it_exp_vpd_panel.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2002 Dave Boutcher IBM Corporation + * + * 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 + */ +#ifndef _PLATFORMS_ISERIES_IT_EXT_VPD_PANEL_H +#define _PLATFORMS_ISERIES_IT_EXT_VPD_PANEL_H + +/* + * This struct maps the panel information + * + * Warning: + * This data must match the architecture for the panel information + */ + +#include <asm/types.h> + +struct ItExtVpdPanel { + /* Definition of the Extended Vpd On Panel Data Area */ + char systemSerial[8]; + char mfgID[4]; + char reserved1[24]; + char machineType[4]; + char systemID[6]; + char somUniqueCnt[4]; + char serialNumberCount; + char reserved2[7]; + u16 bbu3; + u16 bbu2; + u16 bbu1; + char xLocationLabel[8]; + u8 xRsvd1[6]; + u16 xFrameId; + u8 xRsvd2[48]; +}; + +extern struct ItExtVpdPanel xItExtVpdPanel; + +#endif /* _PLATFORMS_ISERIES_IT_EXT_VPD_PANEL_H */ diff --git a/arch/powerpc/platforms/iseries/it_lp_naca.h b/arch/powerpc/platforms/iseries/it_lp_naca.h new file mode 100644 index 00000000..cf6dcf6e --- /dev/null +++ b/arch/powerpc/platforms/iseries/it_lp_naca.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ +#ifndef _PLATFORMS_ISERIES_IT_LP_NACA_H +#define _PLATFORMS_ISERIES_IT_LP_NACA_H + +#include <linux/types.h> + +/* + * This control block contains the data that is shared between the + * hypervisor (PLIC) and the OS. + */ + +struct ItLpNaca { +// CACHE_LINE_1 0x0000 - 0x007F Contains read-only data + u32 xDesc; // Eye catcher x00-x03 + u16 xSize; // Size of this class x04-x05 + u16 xIntHdlrOffset; // Offset to IntHdlr array x06-x07 + u8 xMaxIntHdlrEntries; // Number of entries in array x08-x08 + u8 xPrimaryLpIndex; // LP Index of Primary x09-x09 + u8 xServiceLpIndex; // LP Ind of Service Focal Pointx0A-x0A + u8 xLpIndex; // LP Index x0B-x0B + u16 xMaxLpQueues; // Number of allocated queues x0C-x0D + u16 xLpQueueOffset; // Offset to start of LP queues x0E-x0F + u8 xPirEnvironMode; // Piranha or hardware x10-x10 + u8 xPirConsoleMode; // Piranha console indicator x11-x11 + u8 xPirDasdMode; // Piranha dasd indicator x12-x12 + u8 xRsvd1_0[5]; // Reserved for Piranha related x13-x17 + u8 flags; // flags, see below x18-x1F + u8 xSpVpdFormat; // VPD areas are in CSP format ... + u8 xIntProcRatio; // Ratio of int procs to procs ... + u8 xRsvd1_2[5]; // Reserved ... + u16 xRsvd1_3; // Reserved x20-x21 + u16 xPlicVrmIndex; // VRM index of PLIC x22-x23 + u16 xMinSupportedSlicVrmInd;// Min supported OS VRM index x24-x25 + u16 xMinCompatableSlicVrmInd;// Min compatible OS VRM index x26-x27 + u64 xLoadAreaAddr; // ER address of load area x28-x2F + u32 xLoadAreaChunks; // Chunks for the load area x30-x33 + u32 xPaseSysCallCRMask; // Mask used to test CR before x34-x37 + // doing an ASR switch on PASE + // system call. + u64 xSlicSegmentTablePtr; // Pointer to Slic seg table. x38-x3f + u8 xRsvd1_4[64]; // x40-x7F + +// CACHE_LINE_2 0x0080 - 0x00FF Contains local read-write data + u8 xRsvd2_0[128]; // Reserved x00-x7F + +// CACHE_LINE_3-6 0x0100 - 0x02FF Contains LP Queue indicators +// NB: Padding required to keep xInterruptHdlr at x300 which is required +// for v4r4 PLIC. + u8 xOldLpQueue[128]; // LP Queue needed for v4r4 100-17F + u8 xRsvd3_0[384]; // Reserved 180-2FF + +// CACHE_LINE_7-8 0x0300 - 0x03FF Contains the address of the OS interrupt +// handlers + u64 xInterruptHdlr[32]; // Interrupt handlers 300-x3FF +}; + +extern struct ItLpNaca itLpNaca; + +#define ITLPNACA_LPAR 0x80 /* Is LPAR installed on the system */ +#define ITLPNACA_PARTITIONED 0x40 /* Is the system partitioned */ +#define ITLPNACA_HWSYNCEDTBS 0x20 /* Hardware synced TBs */ +#define ITLPNACA_HMTINT 0x10 /* Utilize MHT for interrupts */ + +#endif /* _PLATFORMS_ISERIES_IT_LP_NACA_H */ diff --git a/arch/powerpc/platforms/iseries/ksyms.c b/arch/powerpc/platforms/iseries/ksyms.c new file mode 100644 index 00000000..2430848b --- /dev/null +++ b/arch/powerpc/platforms/iseries/ksyms.c @@ -0,0 +1,21 @@ +/* + * (C) 2001-2005 PPC 64 Team, IBM Corp + * + * 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. + */ +#include <linux/module.h> + +#include <asm/hw_irq.h> +#include <asm/iseries/hv_call_sc.h> + +EXPORT_SYMBOL(HvCall0); +EXPORT_SYMBOL(HvCall1); +EXPORT_SYMBOL(HvCall2); +EXPORT_SYMBOL(HvCall3); +EXPORT_SYMBOL(HvCall4); +EXPORT_SYMBOL(HvCall5); +EXPORT_SYMBOL(HvCall6); +EXPORT_SYMBOL(HvCall7); diff --git a/arch/powerpc/platforms/iseries/lpardata.c b/arch/powerpc/platforms/iseries/lpardata.c new file mode 100644 index 00000000..98bd2d37 --- /dev/null +++ b/arch/powerpc/platforms/iseries/lpardata.c @@ -0,0 +1,319 @@ +/* + * Copyright 2001 Mike Corrigan, IBM Corp + * + * 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. + */ +#include <linux/types.h> +#include <linux/threads.h> +#include <linux/module.h> +#include <linux/bitops.h> +#include <asm/processor.h> +#include <asm/ptrace.h> +#include <asm/abs_addr.h> +#include <asm/lppaca.h> +#include <asm/paca.h> +#include <asm/iseries/lpar_map.h> +#include <asm/iseries/it_lp_queue.h> +#include <asm/iseries/alpaca.h> + +#include "naca.h" +#include "vpd_areas.h" +#include "spcomm_area.h" +#include "ipl_parms.h" +#include "processor_vpd.h" +#include "release_data.h" +#include "it_exp_vpd_panel.h" +#include "it_lp_naca.h" + +/* The HvReleaseData is the root of the information shared between + * the hypervisor and Linux. + */ +const struct HvReleaseData hvReleaseData = { + .xDesc = 0xc8a5d9c4, /* "HvRD" ebcdic */ + .xSize = sizeof(struct HvReleaseData), + .xVpdAreasPtrOffset = offsetof(struct naca_struct, xItVpdAreas), + .xSlicNacaAddr = &naca, /* 64-bit Naca address */ + .xMsNucDataOffset = LPARMAP_PHYS, + .xFlags = HVREL_TAGSINACTIVE /* tags inactive */ + /* 64 bit */ + /* shared processors */ + /* HMT allowed */ + | 6, /* TEMP: This allows non-GA driver */ + .xVrmIndex = 4, /* We are v5r2m0 */ + .xMinSupportedPlicVrmIndex = 3, /* v5r1m0 */ + .xMinCompatablePlicVrmIndex = 3, /* v5r1m0 */ + .xVrmName = { 0xd3, 0x89, 0x95, 0xa4, /* "Linux 2.4.64" ebcdic */ + 0xa7, 0x40, 0xf2, 0x4b, + 0xf4, 0x4b, 0xf6, 0xf4 }, +}; + +/* + * The NACA. The first dword of the naca is required by the iSeries + * hypervisor to point to itVpdAreas. The hypervisor finds the NACA + * through the pointer in hvReleaseData. + */ +struct naca_struct naca = { + .xItVpdAreas = &itVpdAreas, + .xRamDisk = 0, + .xRamDiskSize = 0, +}; + +struct ItLpRegSave { + u32 xDesc; // Eye catcher "LpRS" ebcdic 000-003 + u16 xSize; // Size of this class 004-005 + u8 xInUse; // Area is live 006-007 + u8 xRsvd1[9]; // Reserved 007-00F + + u8 xFixedRegSave[352]; // Fixed Register Save Area 010-16F + u32 xCTRL; // Control Register 170-173 + u32 xDEC; // Decrementer 174-177 + u32 xFPSCR; // FP Status and Control Reg 178-17B + u32 xPVR; // Processor Version Number 17C-17F + + u64 xMMCR0; // Monitor Mode Control Reg 0 180-187 + u32 xPMC1; // Perf Monitor Counter 1 188-18B + u32 xPMC2; // Perf Monitor Counter 2 18C-18F + u32 xPMC3; // Perf Monitor Counter 3 190-193 + u32 xPMC4; // Perf Monitor Counter 4 194-197 + u32 xPIR; // Processor ID Reg 198-19B + + u32 xMMCR1; // Monitor Mode Control Reg 1 19C-19F + u32 xMMCRA; // Monitor Mode Control Reg A 1A0-1A3 + u32 xPMC5; // Perf Monitor Counter 5 1A4-1A7 + u32 xPMC6; // Perf Monitor Counter 6 1A8-1AB + u32 xPMC7; // Perf Monitor Counter 7 1AC-1AF + u32 xPMC8; // Perf Monitor Counter 8 1B0-1B3 + u32 xTSC; // Thread Switch Control 1B4-1B7 + u32 xTST; // Thread Switch Timeout 1B8-1BB + u32 xRsvd; // Reserved 1BC-1BF + + u64 xACCR; // Address Compare Control Reg 1C0-1C7 + u64 xIMR; // Instruction Match Register 1C8-1CF + u64 xSDR1; // Storage Description Reg 1 1D0-1D7 + u64 xSPRG0; // Special Purpose Reg General0 1D8-1DF + u64 xSPRG1; // Special Purpose Reg General1 1E0-1E7 + u64 xSPRG2; // Special Purpose Reg General2 1E8-1EF + u64 xSPRG3; // Special Purpose Reg General3 1F0-1F7 + u64 xTB; // Time Base Register 1F8-1FF + + u64 xFPR[32]; // Floating Point Registers 200-2FF + + u64 xMSR; // Machine State Register 300-307 + u64 xNIA; // Next Instruction Address 308-30F + + u64 xDABR; // Data Address Breakpoint Reg 310-317 + u64 xIABR; // Inst Address Breakpoint Reg 318-31F + + u64 xHID0; // HW Implementation Dependent0 320-327 + + u64 xHID4; // HW Implementation Dependent4 328-32F + u64 xSCOMd; // SCON Data Reg (SPRG4) 330-337 + u64 xSCOMc; // SCON Command Reg (SPRG5) 338-33F + u64 xSDAR; // Sample Data Address Register 340-347 + u64 xSIAR; // Sample Inst Address Register 348-34F + + u8 xRsvd3[176]; // Reserved 350-3FF +}; + +extern void system_reset_iSeries(void); +extern void machine_check_iSeries(void); +extern void data_access_iSeries(void); +extern void instruction_access_iSeries(void); +extern void hardware_interrupt_iSeries(void); +extern void alignment_iSeries(void); +extern void program_check_iSeries(void); +extern void fp_unavailable_iSeries(void); +extern void decrementer_iSeries(void); +extern void trap_0a_iSeries(void); +extern void trap_0b_iSeries(void); +extern void system_call_iSeries(void); +extern void single_step_iSeries(void); +extern void trap_0e_iSeries(void); +extern void performance_monitor_iSeries(void); +extern void data_access_slb_iSeries(void); +extern void instruction_access_slb_iSeries(void); + +struct ItLpNaca itLpNaca = { + .xDesc = 0xd397d581, /* "LpNa" ebcdic */ + .xSize = 0x0400, /* size of ItLpNaca */ + .xIntHdlrOffset = 0x0300, /* offset to int array */ + .xMaxIntHdlrEntries = 19, /* # ents */ + .xPrimaryLpIndex = 0, /* Part # of primary */ + .xServiceLpIndex = 0, /* Part # of serv */ + .xLpIndex = 0, /* Part # of me */ + .xMaxLpQueues = 0, /* # of LP queues */ + .xLpQueueOffset = 0x100, /* offset of start of LP queues */ + .xPirEnvironMode = 0, /* Piranha stuff */ + .xPirConsoleMode = 0, + .xPirDasdMode = 0, + .flags = 0, + .xSpVpdFormat = 0, + .xIntProcRatio = 0, + .xPlicVrmIndex = 0, /* VRM index of PLIC */ + .xMinSupportedSlicVrmInd = 0, /* min supported SLIC */ + .xMinCompatableSlicVrmInd = 0, /* min compat SLIC */ + .xLoadAreaAddr = 0, /* 64-bit addr of load area */ + .xLoadAreaChunks = 0, /* chunks for load area */ + .xPaseSysCallCRMask = 0, /* PASE mask */ + .xSlicSegmentTablePtr = 0, /* seg table */ + .xOldLpQueue = { 0 }, /* Old LP Queue */ + .xInterruptHdlr = { + (u64)system_reset_iSeries, /* 0x100 System Reset */ + (u64)machine_check_iSeries, /* 0x200 Machine Check */ + (u64)data_access_iSeries, /* 0x300 Data Access */ + (u64)instruction_access_iSeries, /* 0x400 Instruction Access */ + (u64)hardware_interrupt_iSeries, /* 0x500 External */ + (u64)alignment_iSeries, /* 0x600 Alignment */ + (u64)program_check_iSeries, /* 0x700 Program Check */ + (u64)fp_unavailable_iSeries, /* 0x800 FP Unavailable */ + (u64)decrementer_iSeries, /* 0x900 Decrementer */ + (u64)trap_0a_iSeries, /* 0xa00 Trap 0A */ + (u64)trap_0b_iSeries, /* 0xb00 Trap 0B */ + (u64)system_call_iSeries, /* 0xc00 System Call */ + (u64)single_step_iSeries, /* 0xd00 Single Step */ + (u64)trap_0e_iSeries, /* 0xe00 Trap 0E */ + (u64)performance_monitor_iSeries,/* 0xf00 Performance Monitor */ + 0, /* int 0x1000 */ + 0, /* int 0x1010 */ + 0, /* int 0x1020 CPU ctls */ + (u64)hardware_interrupt_iSeries, /* SC Ret Hdlr */ + (u64)data_access_slb_iSeries, /* 0x380 D-SLB */ + (u64)instruction_access_slb_iSeries /* 0x480 I-SLB */ + } +}; + +/* May be filled in by the hypervisor so cannot end up in the BSS */ +static struct ItIplParmsReal xItIplParmsReal __attribute__((__section__(".data"))); + +/* May be filled in by the hypervisor so cannot end up in the BSS */ +struct ItExtVpdPanel xItExtVpdPanel __attribute__((__section__(".data"))); + +#define maxPhysicalProcessors 32 + +struct IoHriProcessorVpd xIoHriProcessorVpd[maxPhysicalProcessors] = { + { + .xInstCacheOperandSize = 32, + .xDataCacheOperandSize = 32, + .xProcFreq = 50000000, + .xTimeBaseFreq = 50000000, + .xPVR = 0x3600 + } +}; + +/* Space for Main Store Vpd 27,200 bytes */ +/* May be filled in by the hypervisor so cannot end up in the BSS */ +u64 xMsVpd[3400] __attribute__((__section__(".data"))); + +/* Space for Recovery Log Buffer */ +/* May be filled in by the hypervisor so cannot end up in the BSS */ +static u64 xRecoveryLogBuffer[32] __attribute__((__section__(".data"))); + +static const struct SpCommArea xSpCommArea = { + .xDesc = 0xE2D7C3C2, + .xFormat = 1, +}; + +static const struct ItLpRegSave iseries_reg_save[] = { + [0 ... (NR_CPUS-1)] = { + .xDesc = 0xd397d9e2, /* "LpRS" */ + .xSize = sizeof(struct ItLpRegSave), + }, +}; + +#define ALPACA_INIT(number) \ +{ \ + .lppaca_ptr = &lppaca[number], \ + .reg_save_ptr = &iseries_reg_save[number], \ +} + +const struct alpaca alpaca[] = { + ALPACA_INIT( 0), +#if NR_CPUS > 1 + ALPACA_INIT( 1), ALPACA_INIT( 2), ALPACA_INIT( 3), +#if NR_CPUS > 4 + ALPACA_INIT( 4), ALPACA_INIT( 5), ALPACA_INIT( 6), ALPACA_INIT( 7), +#if NR_CPUS > 8 + ALPACA_INIT( 8), ALPACA_INIT( 9), ALPACA_INIT(10), ALPACA_INIT(11), + ALPACA_INIT(12), ALPACA_INIT(13), ALPACA_INIT(14), ALPACA_INIT(15), + ALPACA_INIT(16), ALPACA_INIT(17), ALPACA_INIT(18), ALPACA_INIT(19), + ALPACA_INIT(20), ALPACA_INIT(21), ALPACA_INIT(22), ALPACA_INIT(23), + ALPACA_INIT(24), ALPACA_INIT(25), ALPACA_INIT(26), ALPACA_INIT(27), + ALPACA_INIT(28), ALPACA_INIT(29), ALPACA_INIT(30), ALPACA_INIT(31), +#if NR_CPUS > 32 + ALPACA_INIT(32), ALPACA_INIT(33), ALPACA_INIT(34), ALPACA_INIT(35), + ALPACA_INIT(36), ALPACA_INIT(37), ALPACA_INIT(38), ALPACA_INIT(39), + ALPACA_INIT(40), ALPACA_INIT(41), ALPACA_INIT(42), ALPACA_INIT(43), + ALPACA_INIT(44), ALPACA_INIT(45), ALPACA_INIT(46), ALPACA_INIT(47), + ALPACA_INIT(48), ALPACA_INIT(49), ALPACA_INIT(50), ALPACA_INIT(51), + ALPACA_INIT(52), ALPACA_INIT(53), ALPACA_INIT(54), ALPACA_INIT(55), + ALPACA_INIT(56), ALPACA_INIT(57), ALPACA_INIT(58), ALPACA_INIT(59), + ALPACA_INIT(60), ALPACA_INIT(61), ALPACA_INIT(62), ALPACA_INIT(63), +#endif +#endif +#endif +#endif +}; + +/* The LparMap data is now located at offset 0x6000 in head.S + * It was put there so that the HvReleaseData could address it + * with a 32-bit offset as required by the iSeries hypervisor + * + * The Naca has a pointer to the ItVpdAreas. The hypervisor finds + * the Naca via the HvReleaseData area. The HvReleaseData has the + * offset into the Naca of the pointer to the ItVpdAreas. + */ +const struct ItVpdAreas itVpdAreas = { + .xSlicDesc = 0xc9a3e5c1, /* "ItVA" */ + .xSlicSize = sizeof(struct ItVpdAreas), + .xSlicVpdEntries = ItVpdMaxEntries, /* # VPD array entries */ + .xSlicDmaEntries = ItDmaMaxEntries, /* # DMA array entries */ + .xSlicMaxLogicalProcs = NR_CPUS * 2, /* Max logical procs */ + .xSlicMaxPhysicalProcs = maxPhysicalProcessors, /* Max physical procs */ + .xSlicDmaToksOffset = offsetof(struct ItVpdAreas, xPlicDmaToks), + .xSlicVpdAdrsOffset = offsetof(struct ItVpdAreas, xSlicVpdAdrs), + .xSlicDmaLensOffset = offsetof(struct ItVpdAreas, xPlicDmaLens), + .xSlicVpdLensOffset = offsetof(struct ItVpdAreas, xSlicVpdLens), + .xSlicMaxSlotLabels = 0, /* max slot labels */ + .xSlicMaxLpQueues = 1, /* max LP queues */ + .xPlicDmaLens = { 0 }, /* DMA lengths */ + .xPlicDmaToks = { 0 }, /* DMA tokens */ + .xSlicVpdLens = { /* VPD lengths */ + 0,0,0, /* 0 - 2 */ + sizeof(xItExtVpdPanel), /* 3 Extended VPD */ + sizeof(struct alpaca), /* 4 length of (fake) Paca */ + 0, /* 5 */ + sizeof(struct ItIplParmsReal),/* 6 length of IPL parms */ + 26992, /* 7 length of MS VPD */ + 0, /* 8 */ + sizeof(struct ItLpNaca),/* 9 length of LP Naca */ + 0, /* 10 */ + 256, /* 11 length of Recovery Log Buf */ + sizeof(struct SpCommArea), /* 12 length of SP Comm Area */ + 0,0,0, /* 13 - 15 */ + sizeof(struct IoHriProcessorVpd),/* 16 length of Proc Vpd */ + 0,0,0,0,0,0, /* 17 - 22 */ + sizeof(struct hvlpevent_queue), /* 23 length of Lp Queue */ + 0,0 /* 24 - 25 */ + }, + .xSlicVpdAdrs = { /* VPD addresses */ + 0,0,0, /* 0 - 2 */ + &xItExtVpdPanel, /* 3 Extended VPD */ + &alpaca[0], /* 4 first (fake) Paca */ + 0, /* 5 */ + &xItIplParmsReal, /* 6 IPL parms */ + &xMsVpd, /* 7 MS Vpd */ + 0, /* 8 */ + &itLpNaca, /* 9 LpNaca */ + 0, /* 10 */ + &xRecoveryLogBuffer, /* 11 Recovery Log Buffer */ + &xSpCommArea, /* 12 SP Comm Area */ + 0,0,0, /* 13 - 15 */ + &xIoHriProcessorVpd, /* 16 Proc Vpd */ + 0,0,0,0,0,0, /* 17 - 22 */ + &hvlpevent_queue, /* 23 Lp Queue */ + 0,0 + } +}; diff --git a/arch/powerpc/platforms/iseries/lpevents.c b/arch/powerpc/platforms/iseries/lpevents.c new file mode 100644 index 00000000..b0f8a857 --- /dev/null +++ b/arch/powerpc/platforms/iseries/lpevents.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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. + */ + +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/bootmem.h> +#include <linux/seq_file.h> +#include <linux/proc_fs.h> +#include <linux/module.h> + +#include <asm/system.h> +#include <asm/paca.h> +#include <asm/firmware.h> +#include <asm/iseries/it_lp_queue.h> +#include <asm/iseries/hv_lp_event.h> +#include <asm/iseries/hv_call_event.h> +#include "it_lp_naca.h" + +/* + * The LpQueue is used to pass event data from the hypervisor to + * the partition. This is where I/O interrupt events are communicated. + * + * It is written to by the hypervisor so cannot end up in the BSS. + */ +struct hvlpevent_queue hvlpevent_queue __attribute__((__section__(".data"))); + +DEFINE_PER_CPU(unsigned long[HvLpEvent_Type_NumTypes], hvlpevent_counts); + +static char *event_types[HvLpEvent_Type_NumTypes] = { + "Hypervisor", + "Machine Facilities", + "Session Manager", + "SPD I/O", + "Virtual Bus", + "PCI I/O", + "RIO I/O", + "Virtual Lan", + "Virtual I/O" +}; + +/* Array of LpEvent handler functions */ +static LpEventHandler lpEventHandler[HvLpEvent_Type_NumTypes]; +static unsigned lpEventHandlerPaths[HvLpEvent_Type_NumTypes]; + +static struct HvLpEvent * get_next_hvlpevent(void) +{ + struct HvLpEvent * event; + event = (struct HvLpEvent *)hvlpevent_queue.hq_current_event; + + if (hvlpevent_is_valid(event)) { + /* rmb() needed only for weakly consistent machines (regatta) */ + rmb(); + /* Set pointer to next potential event */ + hvlpevent_queue.hq_current_event += ((event->xSizeMinus1 + + IT_LP_EVENT_ALIGN) / IT_LP_EVENT_ALIGN) * + IT_LP_EVENT_ALIGN; + + /* Wrap to beginning if no room at end */ + if (hvlpevent_queue.hq_current_event > + hvlpevent_queue.hq_last_event) { + hvlpevent_queue.hq_current_event = + hvlpevent_queue.hq_event_stack; + } + } else { + event = NULL; + } + + return event; +} + +static unsigned long spread_lpevents = NR_CPUS; + +int hvlpevent_is_pending(void) +{ + struct HvLpEvent *next_event; + + if (smp_processor_id() >= spread_lpevents) + return 0; + + next_event = (struct HvLpEvent *)hvlpevent_queue.hq_current_event; + + return hvlpevent_is_valid(next_event) || + hvlpevent_queue.hq_overflow_pending; +} + +static void hvlpevent_clear_valid(struct HvLpEvent * event) +{ + /* Tell the Hypervisor that we're done with this event. + * Also clear bits within this event that might look like valid bits. + * ie. on 64-byte boundaries. + */ + struct HvLpEvent *tmp; + unsigned extra = ((event->xSizeMinus1 + IT_LP_EVENT_ALIGN) / + IT_LP_EVENT_ALIGN) - 1; + + switch (extra) { + case 3: + tmp = (struct HvLpEvent*)((char*)event + 3 * IT_LP_EVENT_ALIGN); + hvlpevent_invalidate(tmp); + case 2: + tmp = (struct HvLpEvent*)((char*)event + 2 * IT_LP_EVENT_ALIGN); + hvlpevent_invalidate(tmp); + case 1: + tmp = (struct HvLpEvent*)((char*)event + 1 * IT_LP_EVENT_ALIGN); + hvlpevent_invalidate(tmp); + } + + mb(); + + hvlpevent_invalidate(event); +} + +void process_hvlpevents(void) +{ + struct HvLpEvent * event; + + restart: + /* If we have recursed, just return */ + if (!spin_trylock(&hvlpevent_queue.hq_lock)) + return; + + for (;;) { + event = get_next_hvlpevent(); + if (event) { + /* Call appropriate handler here, passing + * a pointer to the LpEvent. The handler + * must make a copy of the LpEvent if it + * needs it in a bottom half. (perhaps for + * an ACK) + * + * Handlers are responsible for ACK processing + * + * The Hypervisor guarantees that LpEvents will + * only be delivered with types that we have + * registered for, so no type check is necessary + * here! + */ + if (event->xType < HvLpEvent_Type_NumTypes) + __get_cpu_var(hvlpevent_counts)[event->xType]++; + if (event->xType < HvLpEvent_Type_NumTypes && + lpEventHandler[event->xType]) + lpEventHandler[event->xType](event); + else { + u8 type = event->xType; + + /* + * Don't printk in the spinlock as printk + * may require ack events form the HV to send + * any characters there. + */ + hvlpevent_clear_valid(event); + spin_unlock(&hvlpevent_queue.hq_lock); + printk(KERN_INFO + "Unexpected Lp Event type=%d\n", type); + goto restart; + } + + hvlpevent_clear_valid(event); + } else if (hvlpevent_queue.hq_overflow_pending) + /* + * No more valid events. If overflow events are + * pending process them + */ + HvCallEvent_getOverflowLpEvents(hvlpevent_queue.hq_index); + else + break; + } + + spin_unlock(&hvlpevent_queue.hq_lock); +} + +static int set_spread_lpevents(char *str) +{ + unsigned long val = simple_strtoul(str, NULL, 0); + + /* + * The parameter is the number of processors to share in processing + * lp events. + */ + if (( val > 0) && (val <= NR_CPUS)) { + spread_lpevents = val; + printk("lpevent processing spread over %ld processors\n", val); + } else { + printk("invalid spread_lpevents %ld\n", val); + } + + return 1; +} +__setup("spread_lpevents=", set_spread_lpevents); + +void __init setup_hvlpevent_queue(void) +{ + void *eventStack; + + spin_lock_init(&hvlpevent_queue.hq_lock); + + /* Allocate a page for the Event Stack. */ + eventStack = alloc_bootmem_pages(IT_LP_EVENT_STACK_SIZE); + memset(eventStack, 0, IT_LP_EVENT_STACK_SIZE); + + /* Invoke the hypervisor to initialize the event stack */ + HvCallEvent_setLpEventStack(0, eventStack, IT_LP_EVENT_STACK_SIZE); + + hvlpevent_queue.hq_event_stack = eventStack; + hvlpevent_queue.hq_current_event = eventStack; + hvlpevent_queue.hq_last_event = (char *)eventStack + + (IT_LP_EVENT_STACK_SIZE - IT_LP_EVENT_MAX_SIZE); + hvlpevent_queue.hq_index = 0; +} + +/* Register a handler for an LpEvent type */ +int HvLpEvent_registerHandler(HvLpEvent_Type eventType, LpEventHandler handler) +{ + if (eventType < HvLpEvent_Type_NumTypes) { + lpEventHandler[eventType] = handler; + return 0; + } + return 1; +} +EXPORT_SYMBOL(HvLpEvent_registerHandler); + +int HvLpEvent_unregisterHandler(HvLpEvent_Type eventType) +{ + might_sleep(); + + if (eventType < HvLpEvent_Type_NumTypes) { + if (!lpEventHandlerPaths[eventType]) { + lpEventHandler[eventType] = NULL; + /* + * We now sleep until all other CPUs have scheduled. + * This ensures that the deletion is seen by all + * other CPUs, and that the deleted handler isn't + * still running on another CPU when we return. + */ + synchronize_sched(); + return 0; + } + } + return 1; +} +EXPORT_SYMBOL(HvLpEvent_unregisterHandler); + +/* + * lpIndex is the partition index of the target partition. + * needed only for VirtualIo, VirtualLan and SessionMgr. Zero + * indicates to use our partition index - for the other types. + */ +int HvLpEvent_openPath(HvLpEvent_Type eventType, HvLpIndex lpIndex) +{ + if ((eventType < HvLpEvent_Type_NumTypes) && + lpEventHandler[eventType]) { + if (lpIndex == 0) + lpIndex = itLpNaca.xLpIndex; + HvCallEvent_openLpEventPath(lpIndex, eventType); + ++lpEventHandlerPaths[eventType]; + return 0; + } + return 1; +} + +int HvLpEvent_closePath(HvLpEvent_Type eventType, HvLpIndex lpIndex) +{ + if ((eventType < HvLpEvent_Type_NumTypes) && + lpEventHandler[eventType] && + lpEventHandlerPaths[eventType]) { + if (lpIndex == 0) + lpIndex = itLpNaca.xLpIndex; + HvCallEvent_closeLpEventPath(lpIndex, eventType); + --lpEventHandlerPaths[eventType]; + return 0; + } + return 1; +} + +static int proc_lpevents_show(struct seq_file *m, void *v) +{ + int cpu, i; + unsigned long sum; + static unsigned long cpu_totals[NR_CPUS]; + + /* FIXME: do we care that there's no locking here? */ + sum = 0; + for_each_online_cpu(cpu) { + cpu_totals[cpu] = 0; + for (i = 0; i < HvLpEvent_Type_NumTypes; i++) { + cpu_totals[cpu] += per_cpu(hvlpevent_counts, cpu)[i]; + } + sum += cpu_totals[cpu]; + } + + seq_printf(m, "LpEventQueue 0\n"); + seq_printf(m, " events processed:\t%lu\n", sum); + + for (i = 0; i < HvLpEvent_Type_NumTypes; ++i) { + sum = 0; + for_each_online_cpu(cpu) { + sum += per_cpu(hvlpevent_counts, cpu)[i]; + } + + seq_printf(m, " %-20s %10lu\n", event_types[i], sum); + } + + seq_printf(m, "\n events processed by processor:\n"); + + for_each_online_cpu(cpu) { + seq_printf(m, " CPU%02d %10lu\n", cpu, cpu_totals[cpu]); + } + + return 0; +} + +static int proc_lpevents_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_lpevents_show, NULL); +} + +static const struct file_operations proc_lpevents_operations = { + .open = proc_lpevents_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init proc_lpevents_init(void) +{ + if (!firmware_has_feature(FW_FEATURE_ISERIES)) + return 0; + + proc_create("iSeries/lpevents", S_IFREG|S_IRUGO, NULL, + &proc_lpevents_operations); + return 0; +} +__initcall(proc_lpevents_init); + diff --git a/arch/powerpc/platforms/iseries/main_store.h b/arch/powerpc/platforms/iseries/main_store.h new file mode 100644 index 00000000..1a7a3f50 --- /dev/null +++ b/arch/powerpc/platforms/iseries/main_store.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ + +#ifndef _ISERIES_MAIN_STORE_H +#define _ISERIES_MAIN_STORE_H + +/* Main Store Vpd for Condor,iStar,sStar */ +struct IoHriMainStoreSegment4 { + u8 msArea0Exists:1; + u8 msArea1Exists:1; + u8 msArea2Exists:1; + u8 msArea3Exists:1; + u8 reserved1:4; + u8 reserved2; + + u8 msArea0Functional:1; + u8 msArea1Functional:1; + u8 msArea2Functional:1; + u8 msArea3Functional:1; + u8 reserved3:4; + u8 reserved4; + + u32 totalMainStore; + + u64 msArea0Ptr; + u64 msArea1Ptr; + u64 msArea2Ptr; + u64 msArea3Ptr; + + u32 cardProductionLevel; + + u32 msAdrHole; + + u8 msArea0HasRiserVpd:1; + u8 msArea1HasRiserVpd:1; + u8 msArea2HasRiserVpd:1; + u8 msArea3HasRiserVpd:1; + u8 reserved5:4; + u8 reserved6; + u16 reserved7; + + u8 reserved8[28]; + + u64 nonInterleavedBlocksStartAdr; + u64 nonInterleavedBlocksEndAdr; +}; + +/* Main Store VPD for Power4 */ +struct __attribute((packed)) IoHriMainStoreChipInfo1 { + u32 chipMfgID; + char chipECLevel[4]; +}; + +struct IoHriMainStoreVpdIdData { + char typeNumber[4]; + char modelNumber[4]; + char partNumber[12]; + char serialNumber[12]; +}; + +struct __attribute((packed)) IoHriMainStoreVpdFruData { + char fruLabel[8]; + u8 numberOfSlots; + u8 pluggingType; + u16 slotMapIndex; +}; + +struct __attribute((packed)) IoHriMainStoreAdrRangeBlock { + void *blockStart; + void *blockEnd; + u32 blockProcChipId; +}; + +#define MaxAreaAdrRangeBlocks 4 + +struct __attribute((packed)) IoHriMainStoreArea4 { + u32 msVpdFormat; + u8 containedVpdType; + u8 reserved1; + u16 reserved2; + + u64 msExists; + u64 msFunctional; + + u32 memorySize; + u32 procNodeId; + + u32 numAdrRangeBlocks; + struct IoHriMainStoreAdrRangeBlock xAdrRangeBlock[MaxAreaAdrRangeBlocks]; + + struct IoHriMainStoreChipInfo1 chipInfo0; + struct IoHriMainStoreChipInfo1 chipInfo1; + struct IoHriMainStoreChipInfo1 chipInfo2; + struct IoHriMainStoreChipInfo1 chipInfo3; + struct IoHriMainStoreChipInfo1 chipInfo4; + struct IoHriMainStoreChipInfo1 chipInfo5; + struct IoHriMainStoreChipInfo1 chipInfo6; + struct IoHriMainStoreChipInfo1 chipInfo7; + + void *msRamAreaArray; + u32 msRamAreaArrayNumEntries; + u32 msRamAreaArrayEntrySize; + + u32 numaDimmExists; + u32 numaDimmFunctional; + void *numaDimmArray; + u32 numaDimmArrayNumEntries; + u32 numaDimmArrayEntrySize; + + struct IoHriMainStoreVpdIdData idData; + + u64 powerData; + u64 cardAssemblyPartNum; + u64 chipSerialNum; + + u64 reserved3; + char reserved4[16]; + + struct IoHriMainStoreVpdFruData fruData; + + u8 vpdPortNum; + u8 reserved5; + u8 frameId; + u8 rackUnit; + char asciiKeywordVpd[256]; + u32 reserved6; +}; + + +struct IoHriMainStoreSegment5 { + u16 reserved1; + u8 reserved2; + u8 msVpdFormat; + + u32 totalMainStore; + u64 maxConfiguredMsAdr; + + struct IoHriMainStoreArea4 *msAreaArray; + u32 msAreaArrayNumEntries; + u32 msAreaArrayEntrySize; + + u32 msAreaExists; + u32 msAreaFunctional; + + u64 reserved3; +}; + +extern u64 xMsVpd[]; + +#endif /* _ISERIES_MAIN_STORE_H */ diff --git a/arch/powerpc/platforms/iseries/mf.c b/arch/powerpc/platforms/iseries/mf.c new file mode 100644 index 00000000..62dabe3c --- /dev/null +++ b/arch/powerpc/platforms/iseries/mf.c @@ -0,0 +1,1274 @@ +/* + * Copyright (C) 2001 Troy D. Armstrong IBM Corporation + * Copyright (C) 2004-2005 Stephen Rothwell IBM Corporation + * + * This modules exists as an interface between a Linux secondary partition + * running on an iSeries and the primary partition's Virtual Service + * Processor (VSP) object. The VSP has final authority over powering on/off + * all partitions in the iSeries. It also provides miscellaneous low-level + * machine facility type operations. + * + * + * 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/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/dma-mapping.h> +#include <linux/bcd.h> +#include <linux/rtc.h> +#include <linux/slab.h> + +#include <asm/time.h> +#include <asm/uaccess.h> +#include <asm/paca.h> +#include <asm/abs_addr.h> +#include <asm/firmware.h> +#include <asm/iseries/mf.h> +#include <asm/iseries/hv_lp_config.h> +#include <asm/iseries/hv_lp_event.h> +#include <asm/iseries/it_lp_queue.h> + +#include "setup.h" + +static int mf_initialized; + +/* + * This is the structure layout for the Machine Facilities LPAR event + * flows. + */ +struct vsp_cmd_data { + u64 token; + u16 cmd; + HvLpIndex lp_index; + u8 result_code; + u32 reserved; + union { + u64 state; /* GetStateOut */ + u64 ipl_type; /* GetIplTypeOut, Function02SelectIplTypeIn */ + u64 ipl_mode; /* GetIplModeOut, Function02SelectIplModeIn */ + u64 page[4]; /* GetSrcHistoryIn */ + u64 flag; /* GetAutoIplWhenPrimaryIplsOut, + SetAutoIplWhenPrimaryIplsIn, + WhiteButtonPowerOffIn, + Function08FastPowerOffIn, + IsSpcnRackPowerIncompleteOut */ + struct { + u64 token; + u64 address_type; + u64 side; + u32 length; + u32 offset; + } kern; /* SetKernelImageIn, GetKernelImageIn, + SetKernelCmdLineIn, GetKernelCmdLineIn */ + u32 length_out; /* GetKernelImageOut, GetKernelCmdLineOut */ + u8 reserved[80]; + } sub_data; +}; + +struct vsp_rsp_data { + struct completion com; + struct vsp_cmd_data *response; +}; + +struct alloc_data { + u16 size; + u16 type; + u32 count; + u16 reserved1; + u8 reserved2; + HvLpIndex target_lp; +}; + +struct ce_msg_data; + +typedef void (*ce_msg_comp_hdlr)(void *token, struct ce_msg_data *vsp_cmd_rsp); + +struct ce_msg_comp_data { + ce_msg_comp_hdlr handler; + void *token; +}; + +struct ce_msg_data { + u8 ce_msg[12]; + char reserved[4]; + struct ce_msg_comp_data *completion; +}; + +struct io_mf_lp_event { + struct HvLpEvent hp_lp_event; + u16 subtype_result_code; + u16 reserved1; + u32 reserved2; + union { + struct alloc_data alloc; + struct ce_msg_data ce_msg; + struct vsp_cmd_data vsp_cmd; + } data; +}; + +#define subtype_data(a, b, c, d) \ + (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +/* + * All outgoing event traffic is kept on a FIFO queue. The first + * pointer points to the one that is outstanding, and all new + * requests get stuck on the end. Also, we keep a certain number of + * preallocated pending events so that we can operate very early in + * the boot up sequence (before kmalloc is ready). + */ +struct pending_event { + struct pending_event *next; + struct io_mf_lp_event event; + MFCompleteHandler hdlr; + char dma_data[72]; + unsigned dma_data_length; + unsigned remote_address; +}; +static spinlock_t pending_event_spinlock; +static struct pending_event *pending_event_head; +static struct pending_event *pending_event_tail; +static struct pending_event *pending_event_avail; +#define PENDING_EVENT_PREALLOC_LEN 16 +static struct pending_event pending_event_prealloc[PENDING_EVENT_PREALLOC_LEN]; + +/* + * Put a pending event onto the available queue, so it can get reused. + * Attention! You must have the pending_event_spinlock before calling! + */ +static void free_pending_event(struct pending_event *ev) +{ + if (ev != NULL) { + ev->next = pending_event_avail; + pending_event_avail = ev; + } +} + +/* + * Enqueue the outbound event onto the stack. If the queue was + * empty to begin with, we must also issue it via the Hypervisor + * interface. There is a section of code below that will touch + * the first stack pointer without the protection of the pending_event_spinlock. + * This is OK, because we know that nobody else will be modifying + * the first pointer when we do this. + */ +static int signal_event(struct pending_event *ev) +{ + int rc = 0; + unsigned long flags; + int go = 1; + struct pending_event *ev1; + HvLpEvent_Rc hv_rc; + + /* enqueue the event */ + if (ev != NULL) { + ev->next = NULL; + spin_lock_irqsave(&pending_event_spinlock, flags); + if (pending_event_head == NULL) + pending_event_head = ev; + else { + go = 0; + pending_event_tail->next = ev; + } + pending_event_tail = ev; + spin_unlock_irqrestore(&pending_event_spinlock, flags); + } + + /* send the event */ + while (go) { + go = 0; + + /* any DMA data to send beforehand? */ + if (pending_event_head->dma_data_length > 0) + HvCallEvent_dmaToSp(pending_event_head->dma_data, + pending_event_head->remote_address, + pending_event_head->dma_data_length, + HvLpDma_Direction_LocalToRemote); + + hv_rc = HvCallEvent_signalLpEvent( + &pending_event_head->event.hp_lp_event); + if (hv_rc != HvLpEvent_Rc_Good) { + printk(KERN_ERR "mf.c: HvCallEvent_signalLpEvent() " + "failed with %d\n", (int)hv_rc); + + spin_lock_irqsave(&pending_event_spinlock, flags); + ev1 = pending_event_head; + pending_event_head = pending_event_head->next; + if (pending_event_head != NULL) + go = 1; + spin_unlock_irqrestore(&pending_event_spinlock, flags); + + if (ev1 == ev) + rc = -EIO; + else if (ev1->hdlr != NULL) + (*ev1->hdlr)((void *)ev1->event.hp_lp_event.xCorrelationToken, -EIO); + + spin_lock_irqsave(&pending_event_spinlock, flags); + free_pending_event(ev1); + spin_unlock_irqrestore(&pending_event_spinlock, flags); + } + } + + return rc; +} + +/* + * Allocate a new pending_event structure, and initialize it. + */ +static struct pending_event *new_pending_event(void) +{ + struct pending_event *ev = NULL; + HvLpIndex primary_lp = HvLpConfig_getPrimaryLpIndex(); + unsigned long flags; + struct HvLpEvent *hev; + + spin_lock_irqsave(&pending_event_spinlock, flags); + if (pending_event_avail != NULL) { + ev = pending_event_avail; + pending_event_avail = pending_event_avail->next; + } + spin_unlock_irqrestore(&pending_event_spinlock, flags); + if (ev == NULL) { + ev = kmalloc(sizeof(struct pending_event), GFP_ATOMIC); + if (ev == NULL) { + printk(KERN_ERR "mf.c: unable to kmalloc %ld bytes\n", + sizeof(struct pending_event)); + return NULL; + } + } + memset(ev, 0, sizeof(struct pending_event)); + hev = &ev->event.hp_lp_event; + hev->flags = HV_LP_EVENT_VALID | HV_LP_EVENT_DO_ACK | HV_LP_EVENT_INT; + hev->xType = HvLpEvent_Type_MachineFac; + hev->xSourceLp = HvLpConfig_getLpIndex(); + hev->xTargetLp = primary_lp; + hev->xSizeMinus1 = sizeof(ev->event) - 1; + hev->xRc = HvLpEvent_Rc_Good; + hev->xSourceInstanceId = HvCallEvent_getSourceLpInstanceId(primary_lp, + HvLpEvent_Type_MachineFac); + hev->xTargetInstanceId = HvCallEvent_getTargetLpInstanceId(primary_lp, + HvLpEvent_Type_MachineFac); + + return ev; +} + +static int __maybe_unused +signal_vsp_instruction(struct vsp_cmd_data *vsp_cmd) +{ + struct pending_event *ev = new_pending_event(); + int rc; + struct vsp_rsp_data response; + + if (ev == NULL) + return -ENOMEM; + + init_completion(&response.com); + response.response = vsp_cmd; + ev->event.hp_lp_event.xSubtype = 6; + ev->event.hp_lp_event.x.xSubtypeData = + subtype_data('M', 'F', 'V', 'I'); + ev->event.data.vsp_cmd.token = (u64)&response; + ev->event.data.vsp_cmd.cmd = vsp_cmd->cmd; + ev->event.data.vsp_cmd.lp_index = HvLpConfig_getLpIndex(); + ev->event.data.vsp_cmd.result_code = 0xFF; + ev->event.data.vsp_cmd.reserved = 0; + memcpy(&(ev->event.data.vsp_cmd.sub_data), + &(vsp_cmd->sub_data), sizeof(vsp_cmd->sub_data)); + mb(); + + rc = signal_event(ev); + if (rc == 0) + wait_for_completion(&response.com); + return rc; +} + + +/* + * Send a 12-byte CE message to the primary partition VSP object + */ +static int signal_ce_msg(char *ce_msg, struct ce_msg_comp_data *completion) +{ + struct pending_event *ev = new_pending_event(); + + if (ev == NULL) + return -ENOMEM; + + ev->event.hp_lp_event.xSubtype = 0; + ev->event.hp_lp_event.x.xSubtypeData = + subtype_data('M', 'F', 'C', 'E'); + memcpy(ev->event.data.ce_msg.ce_msg, ce_msg, 12); + ev->event.data.ce_msg.completion = completion; + return signal_event(ev); +} + +/* + * Send a 12-byte CE message (with no data) to the primary partition VSP object + */ +static int signal_ce_msg_simple(u8 ce_op, struct ce_msg_comp_data *completion) +{ + u8 ce_msg[12]; + + memset(ce_msg, 0, sizeof(ce_msg)); + ce_msg[3] = ce_op; + return signal_ce_msg(ce_msg, completion); +} + +/* + * Send a 12-byte CE message and DMA data to the primary partition VSP object + */ +static int dma_and_signal_ce_msg(char *ce_msg, + struct ce_msg_comp_data *completion, void *dma_data, + unsigned dma_data_length, unsigned remote_address) +{ + struct pending_event *ev = new_pending_event(); + + if (ev == NULL) + return -ENOMEM; + + ev->event.hp_lp_event.xSubtype = 0; + ev->event.hp_lp_event.x.xSubtypeData = + subtype_data('M', 'F', 'C', 'E'); + memcpy(ev->event.data.ce_msg.ce_msg, ce_msg, 12); + ev->event.data.ce_msg.completion = completion; + memcpy(ev->dma_data, dma_data, dma_data_length); + ev->dma_data_length = dma_data_length; + ev->remote_address = remote_address; + return signal_event(ev); +} + +/* + * Initiate a nice (hopefully) shutdown of Linux. We simply are + * going to try and send the init process a SIGINT signal. If + * this fails (why?), we'll simply force it off in a not-so-nice + * manner. + */ +static int shutdown(void) +{ + int rc = kill_cad_pid(SIGINT, 1); + + if (rc) { + printk(KERN_ALERT "mf.c: SIGINT to init failed (%d), " + "hard shutdown commencing\n", rc); + mf_power_off(); + } else + printk(KERN_INFO "mf.c: init has been successfully notified " + "to proceed with shutdown\n"); + return rc; +} + +/* + * The primary partition VSP object is sending us a new + * event flow. Handle it... + */ +static void handle_int(struct io_mf_lp_event *event) +{ + struct ce_msg_data *ce_msg_data; + struct ce_msg_data *pce_msg_data; + unsigned long flags; + struct pending_event *pev; + + /* ack the interrupt */ + event->hp_lp_event.xRc = HvLpEvent_Rc_Good; + HvCallEvent_ackLpEvent(&event->hp_lp_event); + + /* process interrupt */ + switch (event->hp_lp_event.xSubtype) { + case 0: /* CE message */ + ce_msg_data = &event->data.ce_msg; + switch (ce_msg_data->ce_msg[3]) { + case 0x5B: /* power control notification */ + if ((ce_msg_data->ce_msg[5] & 0x20) != 0) { + printk(KERN_INFO "mf.c: Commencing partition shutdown\n"); + if (shutdown() == 0) + signal_ce_msg_simple(0xDB, NULL); + } + break; + case 0xC0: /* get time */ + spin_lock_irqsave(&pending_event_spinlock, flags); + pev = pending_event_head; + if (pev != NULL) + pending_event_head = pending_event_head->next; + spin_unlock_irqrestore(&pending_event_spinlock, flags); + if (pev == NULL) + break; + pce_msg_data = &pev->event.data.ce_msg; + if (pce_msg_data->ce_msg[3] != 0x40) + break; + if (pce_msg_data->completion != NULL) { + ce_msg_comp_hdlr handler = + pce_msg_data->completion->handler; + void *token = pce_msg_data->completion->token; + + if (handler != NULL) + (*handler)(token, ce_msg_data); + } + spin_lock_irqsave(&pending_event_spinlock, flags); + free_pending_event(pev); + spin_unlock_irqrestore(&pending_event_spinlock, flags); + /* send next waiting event */ + if (pending_event_head != NULL) + signal_event(NULL); + break; + } + break; + case 1: /* IT sys shutdown */ + printk(KERN_INFO "mf.c: Commencing system shutdown\n"); + shutdown(); + break; + } +} + +/* + * The primary partition VSP object is acknowledging the receipt + * of a flow we sent to them. If there are other flows queued + * up, we must send another one now... + */ +static void handle_ack(struct io_mf_lp_event *event) +{ + unsigned long flags; + struct pending_event *two = NULL; + unsigned long free_it = 0; + struct ce_msg_data *ce_msg_data; + struct ce_msg_data *pce_msg_data; + struct vsp_rsp_data *rsp; + + /* handle current event */ + if (pending_event_head == NULL) { + printk(KERN_ERR "mf.c: stack empty for receiving ack\n"); + return; + } + + switch (event->hp_lp_event.xSubtype) { + case 0: /* CE msg */ + ce_msg_data = &event->data.ce_msg; + if (ce_msg_data->ce_msg[3] != 0x40) { + free_it = 1; + break; + } + if (ce_msg_data->ce_msg[2] == 0) + break; + free_it = 1; + pce_msg_data = &pending_event_head->event.data.ce_msg; + if (pce_msg_data->completion != NULL) { + ce_msg_comp_hdlr handler = + pce_msg_data->completion->handler; + void *token = pce_msg_data->completion->token; + + if (handler != NULL) + (*handler)(token, ce_msg_data); + } + break; + case 4: /* allocate */ + case 5: /* deallocate */ + if (pending_event_head->hdlr != NULL) + (*pending_event_head->hdlr)((void *)event->hp_lp_event.xCorrelationToken, event->data.alloc.count); + free_it = 1; + break; + case 6: + free_it = 1; + rsp = (struct vsp_rsp_data *)event->data.vsp_cmd.token; + if (rsp == NULL) { + printk(KERN_ERR "mf.c: no rsp\n"); + break; + } + if (rsp->response != NULL) + memcpy(rsp->response, &event->data.vsp_cmd, + sizeof(event->data.vsp_cmd)); + complete(&rsp->com); + break; + } + + /* remove from queue */ + spin_lock_irqsave(&pending_event_spinlock, flags); + if ((pending_event_head != NULL) && (free_it == 1)) { + struct pending_event *oldHead = pending_event_head; + + pending_event_head = pending_event_head->next; + two = pending_event_head; + free_pending_event(oldHead); + } + spin_unlock_irqrestore(&pending_event_spinlock, flags); + + /* send next waiting event */ + if (two != NULL) + signal_event(NULL); +} + +/* + * This is the generic event handler we are registering with + * the Hypervisor. Ensure the flows are for us, and then + * parse it enough to know if it is an interrupt or an + * acknowledge. + */ +static void hv_handler(struct HvLpEvent *event) +{ + if ((event != NULL) && (event->xType == HvLpEvent_Type_MachineFac)) { + if (hvlpevent_is_ack(event)) + handle_ack((struct io_mf_lp_event *)event); + else + handle_int((struct io_mf_lp_event *)event); + } else + printk(KERN_ERR "mf.c: alien event received\n"); +} + +/* + * Global kernel interface to allocate and seed events into the + * Hypervisor. + */ +void mf_allocate_lp_events(HvLpIndex target_lp, HvLpEvent_Type type, + unsigned size, unsigned count, MFCompleteHandler hdlr, + void *user_token) +{ + struct pending_event *ev = new_pending_event(); + int rc; + + if (ev == NULL) { + rc = -ENOMEM; + } else { + ev->event.hp_lp_event.xSubtype = 4; + ev->event.hp_lp_event.xCorrelationToken = (u64)user_token; + ev->event.hp_lp_event.x.xSubtypeData = + subtype_data('M', 'F', 'M', 'A'); + ev->event.data.alloc.target_lp = target_lp; + ev->event.data.alloc.type = type; + ev->event.data.alloc.size = size; + ev->event.data.alloc.count = count; + ev->hdlr = hdlr; + rc = signal_event(ev); + } + if ((rc != 0) && (hdlr != NULL)) + (*hdlr)(user_token, rc); +} +EXPORT_SYMBOL(mf_allocate_lp_events); + +/* + * Global kernel interface to unseed and deallocate events already in + * Hypervisor. + */ +void mf_deallocate_lp_events(HvLpIndex target_lp, HvLpEvent_Type type, + unsigned count, MFCompleteHandler hdlr, void *user_token) +{ + struct pending_event *ev = new_pending_event(); + int rc; + + if (ev == NULL) + rc = -ENOMEM; + else { + ev->event.hp_lp_event.xSubtype = 5; + ev->event.hp_lp_event.xCorrelationToken = (u64)user_token; + ev->event.hp_lp_event.x.xSubtypeData = + subtype_data('M', 'F', 'M', 'D'); + ev->event.data.alloc.target_lp = target_lp; + ev->event.data.alloc.type = type; + ev->event.data.alloc.count = count; + ev->hdlr = hdlr; + rc = signal_event(ev); + } + if ((rc != 0) && (hdlr != NULL)) + (*hdlr)(user_token, rc); +} +EXPORT_SYMBOL(mf_deallocate_lp_events); + +/* + * Global kernel interface to tell the VSP object in the primary + * partition to power this partition off. + */ +void mf_power_off(void) +{ + printk(KERN_INFO "mf.c: Down it goes...\n"); + signal_ce_msg_simple(0x4d, NULL); + for (;;) + ; +} + +/* + * Global kernel interface to tell the VSP object in the primary + * partition to reboot this partition. + */ +void mf_reboot(char *cmd) +{ + printk(KERN_INFO "mf.c: Preparing to bounce...\n"); + signal_ce_msg_simple(0x4e, NULL); + for (;;) + ; +} + +/* + * Display a single word SRC onto the VSP control panel. + */ +void mf_display_src(u32 word) +{ + u8 ce[12]; + + memset(ce, 0, sizeof(ce)); + ce[3] = 0x4a; + ce[7] = 0x01; + ce[8] = word >> 24; + ce[9] = word >> 16; + ce[10] = word >> 8; + ce[11] = word; + signal_ce_msg(ce, NULL); +} + +/* + * Display a single word SRC of the form "PROGXXXX" on the VSP control panel. + */ +static __init void mf_display_progress_src(u16 value) +{ + u8 ce[12]; + u8 src[72]; + + memcpy(ce, "\x00\x00\x04\x4A\x00\x00\x00\x48\x00\x00\x00\x00", 12); + memcpy(src, "\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00PROGxxxx ", + 72); + src[6] = value >> 8; + src[7] = value & 255; + src[44] = "0123456789ABCDEF"[(value >> 12) & 15]; + src[45] = "0123456789ABCDEF"[(value >> 8) & 15]; + src[46] = "0123456789ABCDEF"[(value >> 4) & 15]; + src[47] = "0123456789ABCDEF"[value & 15]; + dma_and_signal_ce_msg(ce, NULL, src, sizeof(src), 9 * 64 * 1024); +} + +/* + * Clear the VSP control panel. Used to "erase" an SRC that was + * previously displayed. + */ +static void mf_clear_src(void) +{ + signal_ce_msg_simple(0x4b, NULL); +} + +void __init mf_display_progress(u16 value) +{ + if (!mf_initialized) + return; + + if (0xFFFF == value) + mf_clear_src(); + else + mf_display_progress_src(value); +} + +/* + * Initialization code here. + */ +void __init mf_init(void) +{ + int i; + + spin_lock_init(&pending_event_spinlock); + + for (i = 0; i < PENDING_EVENT_PREALLOC_LEN; i++) + free_pending_event(&pending_event_prealloc[i]); + + HvLpEvent_registerHandler(HvLpEvent_Type_MachineFac, &hv_handler); + + /* virtual continue ack */ + signal_ce_msg_simple(0x57, NULL); + + mf_initialized = 1; + mb(); + + printk(KERN_NOTICE "mf.c: iSeries Linux LPAR Machine Facilities " + "initialized\n"); +} + +struct rtc_time_data { + struct completion com; + struct ce_msg_data ce_msg; + int rc; +}; + +static void get_rtc_time_complete(void *token, struct ce_msg_data *ce_msg) +{ + struct rtc_time_data *rtc = token; + + memcpy(&rtc->ce_msg, ce_msg, sizeof(rtc->ce_msg)); + rtc->rc = 0; + complete(&rtc->com); +} + +static int mf_set_rtc(struct rtc_time *tm) +{ + char ce_time[12]; + u8 day, mon, hour, min, sec, y1, y2; + unsigned year; + + year = 1900 + tm->tm_year; + y1 = year / 100; + y2 = year % 100; + + sec = tm->tm_sec; + min = tm->tm_min; + hour = tm->tm_hour; + day = tm->tm_mday; + mon = tm->tm_mon + 1; + + sec = bin2bcd(sec); + min = bin2bcd(min); + hour = bin2bcd(hour); + mon = bin2bcd(mon); + day = bin2bcd(day); + y1 = bin2bcd(y1); + y2 = bin2bcd(y2); + + memset(ce_time, 0, sizeof(ce_time)); + ce_time[3] = 0x41; + ce_time[4] = y1; + ce_time[5] = y2; + ce_time[6] = sec; + ce_time[7] = min; + ce_time[8] = hour; + ce_time[10] = day; + ce_time[11] = mon; + + return signal_ce_msg(ce_time, NULL); +} + +static int rtc_set_tm(int rc, u8 *ce_msg, struct rtc_time *tm) +{ + tm->tm_wday = 0; + tm->tm_yday = 0; + tm->tm_isdst = 0; + if (rc) { + tm->tm_sec = 0; + tm->tm_min = 0; + tm->tm_hour = 0; + tm->tm_mday = 15; + tm->tm_mon = 5; + tm->tm_year = 52; + return rc; + } + + if ((ce_msg[2] == 0xa9) || + (ce_msg[2] == 0xaf)) { + /* TOD clock is not set */ + tm->tm_sec = 1; + tm->tm_min = 1; + tm->tm_hour = 1; + tm->tm_mday = 10; + tm->tm_mon = 8; + tm->tm_year = 71; + mf_set_rtc(tm); + } + { + u8 year = ce_msg[5]; + u8 sec = ce_msg[6]; + u8 min = ce_msg[7]; + u8 hour = ce_msg[8]; + u8 day = ce_msg[10]; + u8 mon = ce_msg[11]; + + sec = bcd2bin(sec); + min = bcd2bin(min); + hour = bcd2bin(hour); + day = bcd2bin(day); + mon = bcd2bin(mon); + year = bcd2bin(year); + + if (year <= 69) + year += 100; + + tm->tm_sec = sec; + tm->tm_min = min; + tm->tm_hour = hour; + tm->tm_mday = day; + tm->tm_mon = mon; + tm->tm_year = year; + } + + return 0; +} + +static int mf_get_rtc(struct rtc_time *tm) +{ + struct ce_msg_comp_data ce_complete; + struct rtc_time_data rtc_data; + int rc; + + memset(&ce_complete, 0, sizeof(ce_complete)); + memset(&rtc_data, 0, sizeof(rtc_data)); + init_completion(&rtc_data.com); + ce_complete.handler = &get_rtc_time_complete; + ce_complete.token = &rtc_data; + rc = signal_ce_msg_simple(0x40, &ce_complete); + if (rc) + return rc; + wait_for_completion(&rtc_data.com); + return rtc_set_tm(rtc_data.rc, rtc_data.ce_msg.ce_msg, tm); +} + +struct boot_rtc_time_data { + int busy; + struct ce_msg_data ce_msg; + int rc; +}; + +static void get_boot_rtc_time_complete(void *token, struct ce_msg_data *ce_msg) +{ + struct boot_rtc_time_data *rtc = token; + + memcpy(&rtc->ce_msg, ce_msg, sizeof(rtc->ce_msg)); + rtc->rc = 0; + rtc->busy = 0; +} + +static int mf_get_boot_rtc(struct rtc_time *tm) +{ + struct ce_msg_comp_data ce_complete; + struct boot_rtc_time_data rtc_data; + int rc; + + memset(&ce_complete, 0, sizeof(ce_complete)); + memset(&rtc_data, 0, sizeof(rtc_data)); + rtc_data.busy = 1; + ce_complete.handler = &get_boot_rtc_time_complete; + ce_complete.token = &rtc_data; + rc = signal_ce_msg_simple(0x40, &ce_complete); + if (rc) + return rc; + /* We need to poll here as we are not yet taking interrupts */ + while (rtc_data.busy) { + if (hvlpevent_is_pending()) + process_hvlpevents(); + } + return rtc_set_tm(rtc_data.rc, rtc_data.ce_msg.ce_msg, tm); +} + +#ifdef CONFIG_PROC_FS +static int mf_cmdline_proc_show(struct seq_file *m, void *v) +{ + char *page, *p; + struct vsp_cmd_data vsp_cmd; + int rc; + dma_addr_t dma_addr; + + /* The HV appears to return no more than 256 bytes of command line */ + page = kmalloc(256, GFP_KERNEL); + if (!page) + return -ENOMEM; + + dma_addr = iseries_hv_map(page, 256, DMA_FROM_DEVICE); + if (dma_addr == DMA_ERROR_CODE) { + kfree(page); + return -ENOMEM; + } + memset(page, 0, 256); + memset(&vsp_cmd, 0, sizeof(vsp_cmd)); + vsp_cmd.cmd = 33; + vsp_cmd.sub_data.kern.token = dma_addr; + vsp_cmd.sub_data.kern.address_type = HvLpDma_AddressType_TceIndex; + vsp_cmd.sub_data.kern.side = (u64)m->private; + vsp_cmd.sub_data.kern.length = 256; + mb(); + rc = signal_vsp_instruction(&vsp_cmd); + iseries_hv_unmap(dma_addr, 256, DMA_FROM_DEVICE); + if (rc) { + kfree(page); + return rc; + } + if (vsp_cmd.result_code != 0) { + kfree(page); + return -ENOMEM; + } + p = page; + while (p - page < 256) { + if (*p == '\0' || *p == '\n') { + *p = '\n'; + break; + } + p++; + + } + seq_write(m, page, p - page); + kfree(page); + return 0; +} + +static int mf_cmdline_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, mf_cmdline_proc_show, PDE(inode)->data); +} + +#if 0 +static int mf_getVmlinuxChunk(char *buffer, int *size, int offset, u64 side) +{ + struct vsp_cmd_data vsp_cmd; + int rc; + int len = *size; + dma_addr_t dma_addr; + + dma_addr = iseries_hv_map(buffer, len, DMA_FROM_DEVICE); + memset(buffer, 0, len); + memset(&vsp_cmd, 0, sizeof(vsp_cmd)); + vsp_cmd.cmd = 32; + vsp_cmd.sub_data.kern.token = dma_addr; + vsp_cmd.sub_data.kern.address_type = HvLpDma_AddressType_TceIndex; + vsp_cmd.sub_data.kern.side = side; + vsp_cmd.sub_data.kern.offset = offset; + vsp_cmd.sub_data.kern.length = len; + mb(); + rc = signal_vsp_instruction(&vsp_cmd); + if (rc == 0) { + if (vsp_cmd.result_code == 0) + *size = vsp_cmd.sub_data.length_out; + else + rc = -ENOMEM; + } + + iseries_hv_unmap(dma_addr, len, DMA_FROM_DEVICE); + + return rc; +} + +static int proc_mf_dump_vmlinux(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int sizeToGet = count; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (mf_getVmlinuxChunk(page, &sizeToGet, off, (u64)data) == 0) { + if (sizeToGet != 0) { + *start = page + off; + return sizeToGet; + } + *eof = 1; + return 0; + } + *eof = 1; + return 0; +} +#endif + +static int mf_side_proc_show(struct seq_file *m, void *v) +{ + char mf_current_side = ' '; + struct vsp_cmd_data vsp_cmd; + + memset(&vsp_cmd, 0, sizeof(vsp_cmd)); + vsp_cmd.cmd = 2; + vsp_cmd.sub_data.ipl_type = 0; + mb(); + + if (signal_vsp_instruction(&vsp_cmd) == 0) { + if (vsp_cmd.result_code == 0) { + switch (vsp_cmd.sub_data.ipl_type) { + case 0: mf_current_side = 'A'; + break; + case 1: mf_current_side = 'B'; + break; + case 2: mf_current_side = 'C'; + break; + default: mf_current_side = 'D'; + break; + } + } + } + + seq_printf(m, "%c\n", mf_current_side); + return 0; +} + +static int mf_side_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, mf_side_proc_show, NULL); +} + +static ssize_t mf_side_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + char side; + u64 newSide; + struct vsp_cmd_data vsp_cmd; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (count == 0) + return 0; + + if (get_user(side, buffer)) + return -EFAULT; + + switch (side) { + case 'A': newSide = 0; + break; + case 'B': newSide = 1; + break; + case 'C': newSide = 2; + break; + case 'D': newSide = 3; + break; + default: + printk(KERN_ERR "mf_proc.c: proc_mf_change_side: invalid side\n"); + return -EINVAL; + } + + memset(&vsp_cmd, 0, sizeof(vsp_cmd)); + vsp_cmd.sub_data.ipl_type = newSide; + vsp_cmd.cmd = 10; + + (void)signal_vsp_instruction(&vsp_cmd); + + return count; +} + +static const struct file_operations mf_side_proc_fops = { + .owner = THIS_MODULE, + .open = mf_side_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = mf_side_proc_write, +}; + +static int mf_src_proc_show(struct seq_file *m, void *v) +{ + return 0; +} + +static int mf_src_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, mf_src_proc_show, NULL); +} + +static ssize_t mf_src_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + char stkbuf[10]; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if ((count < 4) && (count != 1)) { + printk(KERN_ERR "mf_proc: invalid src\n"); + return -EINVAL; + } + + if (count > (sizeof(stkbuf) - 1)) + count = sizeof(stkbuf) - 1; + if (copy_from_user(stkbuf, buffer, count)) + return -EFAULT; + + if ((count == 1) && (*stkbuf == '\0')) + mf_clear_src(); + else + mf_display_src(*(u32 *)stkbuf); + + return count; +} + +static const struct file_operations mf_src_proc_fops = { + .owner = THIS_MODULE, + .open = mf_src_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = mf_src_proc_write, +}; + +static ssize_t mf_cmdline_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + void *data = PDE(file->f_path.dentry->d_inode)->data; + struct vsp_cmd_data vsp_cmd; + dma_addr_t dma_addr; + char *page; + int ret = -EACCES; + + if (!capable(CAP_SYS_ADMIN)) + goto out; + + dma_addr = 0; + page = iseries_hv_alloc(count, &dma_addr, GFP_ATOMIC); + ret = -ENOMEM; + if (page == NULL) + goto out; + + ret = -EFAULT; + if (copy_from_user(page, buffer, count)) + goto out_free; + + memset(&vsp_cmd, 0, sizeof(vsp_cmd)); + vsp_cmd.cmd = 31; + vsp_cmd.sub_data.kern.token = dma_addr; + vsp_cmd.sub_data.kern.address_type = HvLpDma_AddressType_TceIndex; + vsp_cmd.sub_data.kern.side = (u64)data; + vsp_cmd.sub_data.kern.length = count; + mb(); + (void)signal_vsp_instruction(&vsp_cmd); + ret = count; + +out_free: + iseries_hv_free(count, page, dma_addr); +out: + return ret; +} + +static const struct file_operations mf_cmdline_proc_fops = { + .owner = THIS_MODULE, + .open = mf_cmdline_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = mf_cmdline_proc_write, +}; + +static ssize_t proc_mf_change_vmlinux(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct proc_dir_entry *dp = PDE(file->f_path.dentry->d_inode); + ssize_t rc; + dma_addr_t dma_addr; + char *page; + struct vsp_cmd_data vsp_cmd; + + rc = -EACCES; + if (!capable(CAP_SYS_ADMIN)) + goto out; + + dma_addr = 0; + page = iseries_hv_alloc(count, &dma_addr, GFP_ATOMIC); + rc = -ENOMEM; + if (page == NULL) { + printk(KERN_ERR "mf.c: couldn't allocate memory to set vmlinux chunk\n"); + goto out; + } + rc = -EFAULT; + if (copy_from_user(page, buf, count)) + goto out_free; + + memset(&vsp_cmd, 0, sizeof(vsp_cmd)); + vsp_cmd.cmd = 30; + vsp_cmd.sub_data.kern.token = dma_addr; + vsp_cmd.sub_data.kern.address_type = HvLpDma_AddressType_TceIndex; + vsp_cmd.sub_data.kern.side = (u64)dp->data; + vsp_cmd.sub_data.kern.offset = *ppos; + vsp_cmd.sub_data.kern.length = count; + mb(); + rc = signal_vsp_instruction(&vsp_cmd); + if (rc) + goto out_free; + rc = -ENOMEM; + if (vsp_cmd.result_code != 0) + goto out_free; + + *ppos += count; + rc = count; +out_free: + iseries_hv_free(count, page, dma_addr); +out: + return rc; +} + +static const struct file_operations proc_vmlinux_operations = { + .write = proc_mf_change_vmlinux, + .llseek = default_llseek, +}; + +static int __init mf_proc_init(void) +{ + struct proc_dir_entry *mf_proc_root; + struct proc_dir_entry *ent; + struct proc_dir_entry *mf; + char name[2]; + int i; + + if (!firmware_has_feature(FW_FEATURE_ISERIES)) + return 0; + + mf_proc_root = proc_mkdir("iSeries/mf", NULL); + if (!mf_proc_root) + return 1; + + name[1] = '\0'; + for (i = 0; i < 4; i++) { + name[0] = 'A' + i; + mf = proc_mkdir(name, mf_proc_root); + if (!mf) + return 1; + + ent = proc_create_data("cmdline", S_IRUSR|S_IWUSR, mf, + &mf_cmdline_proc_fops, (void *)(long)i); + if (!ent) + return 1; + + if (i == 3) /* no vmlinux entry for 'D' */ + continue; + + ent = proc_create_data("vmlinux", S_IFREG|S_IWUSR, mf, + &proc_vmlinux_operations, + (void *)(long)i); + if (!ent) + return 1; + } + + ent = proc_create("side", S_IFREG|S_IRUSR|S_IWUSR, mf_proc_root, + &mf_side_proc_fops); + if (!ent) + return 1; + + ent = proc_create("src", S_IFREG|S_IRUSR|S_IWUSR, mf_proc_root, + &mf_src_proc_fops); + if (!ent) + return 1; + + return 0; +} + +__initcall(mf_proc_init); + +#endif /* CONFIG_PROC_FS */ + +/* + * Get the RTC from the virtual service processor + * This requires flowing LpEvents to the primary partition + */ +void iSeries_get_rtc_time(struct rtc_time *rtc_tm) +{ + mf_get_rtc(rtc_tm); + rtc_tm->tm_mon--; +} + +/* + * Set the RTC in the virtual service processor + * This requires flowing LpEvents to the primary partition + */ +int iSeries_set_rtc_time(struct rtc_time *tm) +{ + mf_set_rtc(tm); + return 0; +} + +unsigned long iSeries_get_boot_time(void) +{ + struct rtc_time tm; + + mf_get_boot_rtc(&tm); + return mktime(tm.tm_year + 1900, tm.tm_mon, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} diff --git a/arch/powerpc/platforms/iseries/misc.S b/arch/powerpc/platforms/iseries/misc.S new file mode 100644 index 00000000..2c6ff0fd --- /dev/null +++ b/arch/powerpc/platforms/iseries/misc.S @@ -0,0 +1,26 @@ +/* + * This file contains miscellaneous low-level functions. + * Copyright (C) 1995-2005 IBM Corp + * + * Largely rewritten by Cort Dougan (cort@cs.nmt.edu) + * and Paul Mackerras. + * Adapted for iSeries by Mike Corrigan (mikejc@us.ibm.com) + * PPC64 updates by Dave Engebretsen (engebret@us.ibm.com) + * + * 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. + */ + +#include <asm/processor.h> +#include <asm/asm-offsets.h> +#include <asm/ppc_asm.h> + + .text + +/* Handle pending interrupts in interrupt context */ +_GLOBAL(iseries_handle_interrupts) + li r0,0x5555 + sc + blr diff --git a/arch/powerpc/platforms/iseries/naca.h b/arch/powerpc/platforms/iseries/naca.h new file mode 100644 index 00000000..f01708e1 --- /dev/null +++ b/arch/powerpc/platforms/iseries/naca.h @@ -0,0 +1,24 @@ +#ifndef _PLATFORMS_ISERIES_NACA_H +#define _PLATFORMS_ISERIES_NACA_H + +/* + * c 2001 PPC 64 Team, IBM Corp + * + * 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. + */ + +#include <asm/types.h> + +struct naca_struct { + /* Kernel only data - undefined for user space */ + const void *xItVpdAreas; /* VPD Data 0x00 */ + void *xRamDisk; /* iSeries ramdisk 0x08 */ + u64 xRamDiskSize; /* In pages 0x10 */ +}; + +extern struct naca_struct naca; + +#endif /* _PLATFORMS_ISERIES_NACA_H */ diff --git a/arch/powerpc/platforms/iseries/pci.c b/arch/powerpc/platforms/iseries/pci.c new file mode 100644 index 00000000..ab3962b0 --- /dev/null +++ b/arch/powerpc/platforms/iseries/pci.c @@ -0,0 +1,920 @@ +/* + * Copyright (C) 2001 Allan Trautman, IBM Corporation + * Copyright (C) 2005,2007 Stephen Rothwell, IBM Corp + * + * iSeries specific routines for PCI. + * + * Based on code from pci.c and iSeries_pci.c 32bit + * + * 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 + */ + +#undef DEBUG + +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/of.h> +#include <linux/ratelimit.h> + +#include <asm/types.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/pci-bridge.h> +#include <asm/iommu.h> +#include <asm/abs_addr.h> +#include <asm/firmware.h> + +#include <asm/iseries/hv_types.h> +#include <asm/iseries/hv_call_xm.h> +#include <asm/iseries/mf.h> +#include <asm/iseries/iommu.h> + +#include <asm/ppc-pci.h> + +#include "irq.h" +#include "pci.h" +#include "call_pci.h" + +#define PCI_RETRY_MAX 3 +static int limit_pci_retries = 1; /* Set Retry Error on. */ + +/* + * Table defines + * Each Entry size is 4 MB * 1024 Entries = 4GB I/O address space. + */ +#define IOMM_TABLE_MAX_ENTRIES 1024 +#define IOMM_TABLE_ENTRY_SIZE 0x0000000000400000UL +#define BASE_IO_MEMORY 0xE000000000000000UL +#define END_IO_MEMORY 0xEFFFFFFFFFFFFFFFUL + +static unsigned long max_io_memory = BASE_IO_MEMORY; +static long current_iomm_table_entry; + +/* + * Lookup Tables. + */ +static struct device_node *iomm_table[IOMM_TABLE_MAX_ENTRIES]; +static u64 ds_addr_table[IOMM_TABLE_MAX_ENTRIES]; + +static DEFINE_SPINLOCK(iomm_table_lock); + +/* + * Generate a Direct Select Address for the Hypervisor + */ +static inline u64 iseries_ds_addr(struct device_node *node) +{ + struct pci_dn *pdn = PCI_DN(node); + const u32 *sbp = of_get_property(node, "linux,subbus", NULL); + + return ((u64)pdn->busno << 48) + ((u64)(sbp ? *sbp : 0) << 40) + + ((u64)0x10 << 32); +} + +/* + * Size of Bus VPD data + */ +#define BUS_VPDSIZE 1024 + +/* + * Bus Vpd Tags + */ +#define VPD_END_OF_AREA 0x79 +#define VPD_ID_STRING 0x82 +#define VPD_VENDOR_AREA 0x84 + +/* + * Mfg Area Tags + */ +#define VPD_FRU_FRAME_ID 0x4649 /* "FI" */ +#define VPD_SLOT_MAP_FORMAT 0x4D46 /* "MF" */ +#define VPD_SLOT_MAP 0x534D /* "SM" */ + +/* + * Structures of the areas + */ +struct mfg_vpd_area { + u16 tag; + u8 length; + u8 data1; + u8 data2; +}; +#define MFG_ENTRY_SIZE 3 + +struct slot_map { + u8 agent; + u8 secondary_agent; + u8 phb; + char card_location[3]; + char parms[8]; + char reserved[2]; +}; +#define SLOT_ENTRY_SIZE 16 + +/* + * Parse the Slot Area + */ +static void __init iseries_parse_slot_area(struct slot_map *map, int len, + HvAgentId agent, u8 *phb, char card[4]) +{ + /* + * Parse Slot label until we find the one requested + */ + while (len > 0) { + if (map->agent == agent) { + /* + * If Phb wasn't found, grab the entry first one found. + */ + if (*phb == 0xff) + *phb = map->phb; + /* Found it, extract the data. */ + if (map->phb == *phb) { + memcpy(card, &map->card_location, 3); + card[3] = 0; + break; + } + } + /* Point to the next Slot */ + map = (struct slot_map *)((char *)map + SLOT_ENTRY_SIZE); + len -= SLOT_ENTRY_SIZE; + } +} + +/* + * Parse the Mfg Area + */ +static void __init iseries_parse_mfg_area(struct mfg_vpd_area *area, int len, + HvAgentId agent, u8 *phb, u8 *frame, char card[4]) +{ + u16 slot_map_fmt = 0; + + /* Parse Mfg Data */ + while (len > 0) { + int mfg_tag_len = area->length; + /* Frame ID (FI 4649020310 ) */ + if (area->tag == VPD_FRU_FRAME_ID) + *frame = area->data1; + /* Slot Map Format (MF 4D46020004 ) */ + else if (area->tag == VPD_SLOT_MAP_FORMAT) + slot_map_fmt = (area->data1 * 256) + + area->data2; + /* Slot Map (SM 534D90 */ + else if (area->tag == VPD_SLOT_MAP) { + struct slot_map *slot_map; + + if (slot_map_fmt == 0x1004) + slot_map = (struct slot_map *)((char *)area + + MFG_ENTRY_SIZE + 1); + else + slot_map = (struct slot_map *)((char *)area + + MFG_ENTRY_SIZE); + iseries_parse_slot_area(slot_map, mfg_tag_len, + agent, phb, card); + } + /* + * Point to the next Mfg Area + * Use defined size, sizeof give wrong answer + */ + area = (struct mfg_vpd_area *)((char *)area + mfg_tag_len + + MFG_ENTRY_SIZE); + len -= (mfg_tag_len + MFG_ENTRY_SIZE); + } +} + +/* + * Look for "BUS".. Data is not Null terminated. + * PHBID of 0xFF indicates PHB was not found in VPD Data. + */ +static u8 __init iseries_parse_phbid(u8 *area, int len) +{ + while (len > 0) { + if ((*area == 'B') && (*(area + 1) == 'U') + && (*(area + 2) == 'S')) { + area += 3; + while (*area == ' ') + area++; + return *area & 0x0F; + } + area++; + len--; + } + return 0xff; +} + +/* + * Parse out the VPD Areas + */ +static void __init iseries_parse_vpd(u8 *data, int data_len, + HvAgentId agent, u8 *frame, char card[4]) +{ + u8 phb = 0xff; + + while (data_len > 0) { + int len; + u8 tag = *data; + + if (tag == VPD_END_OF_AREA) + break; + len = *(data + 1) + (*(data + 2) * 256); + data += 3; + data_len -= 3; + if (tag == VPD_ID_STRING) + phb = iseries_parse_phbid(data, len); + else if (tag == VPD_VENDOR_AREA) + iseries_parse_mfg_area((struct mfg_vpd_area *)data, len, + agent, &phb, frame, card); + /* Point to next Area. */ + data += len; + data_len -= len; + } +} + +static int __init iseries_get_location_code(u16 bus, HvAgentId agent, + u8 *frame, char card[4]) +{ + int status = 0; + int bus_vpd_len = 0; + u8 *bus_vpd = kmalloc(BUS_VPDSIZE, GFP_KERNEL); + + if (bus_vpd == NULL) { + printk("PCI: Bus VPD Buffer allocation failure.\n"); + return 0; + } + bus_vpd_len = HvCallPci_getBusVpd(bus, iseries_hv_addr(bus_vpd), + BUS_VPDSIZE); + if (bus_vpd_len == 0) { + printk("PCI: Bus VPD Buffer zero length.\n"); + goto out_free; + } + /* printk("PCI: bus_vpd: %p, %d\n",bus_vpd, bus_vpd_len); */ + /* Make sure this is what I think it is */ + if (*bus_vpd != VPD_ID_STRING) { + printk("PCI: Bus VPD Buffer missing starting tag.\n"); + goto out_free; + } + iseries_parse_vpd(bus_vpd, bus_vpd_len, agent, frame, card); + status = 1; +out_free: + kfree(bus_vpd); + return status; +} + +/* + * Prints the device information. + * - Pass in pci_dev* pointer to the device. + * - Pass in the device count + * + * Format: + * PCI: Bus 0, Device 26, Vendor 0x12AE Frame 1, Card C10 Ethernet + * controller + */ +static void __init iseries_device_information(struct pci_dev *pdev, + u16 bus, HvSubBusNumber subbus) +{ + u8 frame = 0; + char card[4]; + HvAgentId agent; + + agent = ISERIES_PCI_AGENTID(ISERIES_GET_DEVICE_FROM_SUBBUS(subbus), + ISERIES_GET_FUNCTION_FROM_SUBBUS(subbus)); + + if (iseries_get_location_code(bus, agent, &frame, card)) { + printk(KERN_INFO "PCI: %s, Vendor %04X Frame%3d, " + "Card %4s 0x%04X\n", pci_name(pdev), pdev->vendor, + frame, card, (int)(pdev->class >> 8)); + } +} + +/* + * iomm_table_allocate_entry + * + * Adds pci_dev entry in address translation table + * + * - Allocates the number of entries required in table base on BAR + * size. + * - Allocates starting at BASE_IO_MEMORY and increases. + * - The size is round up to be a multiple of entry size. + * - CurrentIndex is incremented to keep track of the last entry. + * - Builds the resource entry for allocated BARs. + */ +static void __init iomm_table_allocate_entry(struct pci_dev *dev, int bar_num) +{ + struct resource *bar_res = &dev->resource[bar_num]; + long bar_size = pci_resource_len(dev, bar_num); + struct device_node *dn = pci_device_to_OF_node(dev); + + /* + * No space to allocate, quick exit, skip Allocation. + */ + if (bar_size == 0) + return; + /* + * Set Resource values. + */ + spin_lock(&iomm_table_lock); + bar_res->start = BASE_IO_MEMORY + + IOMM_TABLE_ENTRY_SIZE * current_iomm_table_entry; + bar_res->end = bar_res->start + bar_size - 1; + /* + * Allocate the number of table entries needed for BAR. + */ + while (bar_size > 0 ) { + iomm_table[current_iomm_table_entry] = dn; + ds_addr_table[current_iomm_table_entry] = + iseries_ds_addr(dn) | (bar_num << 24); + bar_size -= IOMM_TABLE_ENTRY_SIZE; + ++current_iomm_table_entry; + } + max_io_memory = BASE_IO_MEMORY + + IOMM_TABLE_ENTRY_SIZE * current_iomm_table_entry; + spin_unlock(&iomm_table_lock); +} + +/* + * allocate_device_bars + * + * - Allocates ALL pci_dev BAR's and updates the resources with the + * BAR value. BARS with zero length will have the resources + * The HvCallPci_getBarParms is used to get the size of the BAR + * space. It calls iomm_table_allocate_entry to allocate + * each entry. + * - Loops through The Bar resources(0 - 5) including the ROM + * is resource(6). + */ +static void __init allocate_device_bars(struct pci_dev *dev) +{ + int bar_num; + + for (bar_num = 0; bar_num <= PCI_ROM_RESOURCE; ++bar_num) + iomm_table_allocate_entry(dev, bar_num); +} + +/* + * Log error information to system console. + * Filter out the device not there errors. + * PCI: EADs Connect Failed 0x18.58.10 Rc: 0x00xx + * PCI: Read Vendor Failed 0x18.58.10 Rc: 0x00xx + * PCI: Connect Bus Unit Failed 0x18.58.10 Rc: 0x00xx + */ +static void pci_log_error(char *error, int bus, int subbus, + int agent, int hv_res) +{ + if (hv_res == 0x0302) + return; + printk(KERN_ERR "PCI: %s Failed: 0x%02X.%02X.%02X Rc: 0x%04X", + error, bus, subbus, agent, hv_res); +} + +/* + * Look down the chain to find the matching Device Device + */ +static struct device_node *find_device_node(int bus, int devfn) +{ + struct device_node *node; + + for (node = NULL; (node = of_find_all_nodes(node)); ) { + struct pci_dn *pdn = PCI_DN(node); + + if (pdn && (bus == pdn->busno) && (devfn == pdn->devfn)) + return node; + } + return NULL; +} + +/* + * iSeries_pcibios_fixup_resources + * + * Fixes up all resources for devices + */ +void __init iSeries_pcibios_fixup_resources(struct pci_dev *pdev) +{ + const u32 *agent; + const u32 *sub_bus; + unsigned char bus = pdev->bus->number; + struct device_node *node; + int i; + + node = pci_device_to_OF_node(pdev); + pr_debug("PCI: iSeries %s, pdev %p, node %p\n", + pci_name(pdev), pdev, node); + if (!node) { + printk("PCI: %s disabled, device tree entry not found !\n", + pci_name(pdev)); + for (i = 0; i <= PCI_ROM_RESOURCE; i++) + pdev->resource[i].flags = 0; + return; + } + sub_bus = of_get_property(node, "linux,subbus", NULL); + agent = of_get_property(node, "linux,agent-id", NULL); + if (agent && sub_bus) { + u8 irq = iSeries_allocate_IRQ(bus, 0, *sub_bus); + int err; + + err = HvCallXm_connectBusUnit(bus, *sub_bus, *agent, irq); + if (err) + pci_log_error("Connect Bus Unit", + bus, *sub_bus, *agent, err); + else { + err = HvCallPci_configStore8(bus, *sub_bus, + *agent, PCI_INTERRUPT_LINE, irq); + if (err) + pci_log_error("PciCfgStore Irq Failed!", + bus, *sub_bus, *agent, err); + else + pdev->irq = irq; + } + } + + allocate_device_bars(pdev); + if (likely(sub_bus)) + iseries_device_information(pdev, bus, *sub_bus); + else + printk(KERN_ERR "PCI: Device node %s has missing or invalid " + "linux,subbus property\n", node->full_name); +} + +/* + * iSeries_pci_final_fixup(void) + */ +void __init iSeries_pci_final_fixup(void) +{ + /* Fix up at the device node and pci_dev relationship */ + mf_display_src(0xC9000100); + iSeries_activate_IRQs(); + mf_display_src(0xC9000200); +} + +/* + * Config space read and write functions. + * For now at least, we look for the device node for the bus and devfn + * that we are asked to access. It may be possible to translate the devfn + * to a subbus and deviceid more directly. + */ +static u64 hv_cfg_read_func[4] = { + HvCallPciConfigLoad8, HvCallPciConfigLoad16, + HvCallPciConfigLoad32, HvCallPciConfigLoad32 +}; + +static u64 hv_cfg_write_func[4] = { + HvCallPciConfigStore8, HvCallPciConfigStore16, + HvCallPciConfigStore32, HvCallPciConfigStore32 +}; + +/* + * Read PCI config space + */ +static int iSeries_pci_read_config(struct pci_bus *bus, unsigned int devfn, + int offset, int size, u32 *val) +{ + struct device_node *node = find_device_node(bus->number, devfn); + u64 fn; + struct HvCallPci_LoadReturn ret; + + if (node == NULL) + return PCIBIOS_DEVICE_NOT_FOUND; + if (offset > 255) { + *val = ~0; + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + fn = hv_cfg_read_func[(size - 1) & 3]; + HvCall3Ret16(fn, &ret, iseries_ds_addr(node), offset, 0); + + if (ret.rc != 0) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; /* or something */ + } + + *val = ret.value; + return 0; +} + +/* + * Write PCI config space + */ + +static int iSeries_pci_write_config(struct pci_bus *bus, unsigned int devfn, + int offset, int size, u32 val) +{ + struct device_node *node = find_device_node(bus->number, devfn); + u64 fn; + u64 ret; + + if (node == NULL) + return PCIBIOS_DEVICE_NOT_FOUND; + if (offset > 255) + return PCIBIOS_BAD_REGISTER_NUMBER; + + fn = hv_cfg_write_func[(size - 1) & 3]; + ret = HvCall4(fn, iseries_ds_addr(node), offset, val, 0); + + if (ret != 0) + return PCIBIOS_DEVICE_NOT_FOUND; + + return 0; +} + +static struct pci_ops iSeries_pci_ops = { + .read = iSeries_pci_read_config, + .write = iSeries_pci_write_config +}; + +/* + * Check Return Code + * -> On Failure, print and log information. + * Increment Retry Count, if exceeds max, panic partition. + * + * PCI: Device 23.90 ReadL I/O Error( 0): 0x1234 + * PCI: Device 23.90 ReadL Retry( 1) + * PCI: Device 23.90 ReadL Retry Successful(1) + */ +static int check_return_code(char *type, struct device_node *dn, + int *retry, u64 ret) +{ + if (ret != 0) { + struct pci_dn *pdn = PCI_DN(dn); + + (*retry)++; + printk("PCI: %s: Device 0x%04X:%02X I/O Error(%2d): 0x%04X\n", + type, pdn->busno, pdn->devfn, + *retry, (int)ret); + /* + * Bump the retry and check for retry count exceeded. + * If, Exceeded, panic the system. + */ + if (((*retry) > PCI_RETRY_MAX) && + (limit_pci_retries > 0)) { + mf_display_src(0xB6000103); + panic_timeout = 0; + panic("PCI: Hardware I/O Error, SRC B6000103, " + "Automatic Reboot Disabled.\n"); + } + return -1; /* Retry Try */ + } + return 0; +} + +/* + * Translate the I/O Address into a device node, bar, and bar offset. + * Note: Make sure the passed variable end up on the stack to avoid + * the exposure of being device global. + */ +static inline struct device_node *xlate_iomm_address( + const volatile void __iomem *addr, + u64 *dsaptr, u64 *bar_offset, const char *func) +{ + unsigned long orig_addr; + unsigned long base_addr; + unsigned long ind; + struct device_node *dn; + + orig_addr = (unsigned long __force)addr; + if ((orig_addr < BASE_IO_MEMORY) || (orig_addr >= max_io_memory)) { + static DEFINE_RATELIMIT_STATE(ratelimit, 60 * HZ, 10); + + if (__ratelimit(&ratelimit)) + printk(KERN_ERR + "iSeries_%s: invalid access at IO address %p\n", + func, addr); + return NULL; + } + base_addr = orig_addr - BASE_IO_MEMORY; + ind = base_addr / IOMM_TABLE_ENTRY_SIZE; + dn = iomm_table[ind]; + + if (dn != NULL) { + *dsaptr = ds_addr_table[ind]; + *bar_offset = base_addr % IOMM_TABLE_ENTRY_SIZE; + } else + panic("PCI: Invalid PCI IO address detected!\n"); + return dn; +} + +/* + * Read MM I/O Instructions for the iSeries + * On MM I/O error, all ones are returned and iSeries_pci_IoError is cal + * else, data is returned in Big Endian format. + */ +static u8 iseries_readb(const volatile void __iomem *addr) +{ + u64 bar_offset; + u64 dsa; + int retry = 0; + struct HvCallPci_LoadReturn ret; + struct device_node *dn = + xlate_iomm_address(addr, &dsa, &bar_offset, "read_byte"); + + if (dn == NULL) + return 0xff; + do { + HvCall3Ret16(HvCallPciBarLoad8, &ret, dsa, bar_offset, 0); + } while (check_return_code("RDB", dn, &retry, ret.rc) != 0); + + return ret.value; +} + +static u16 iseries_readw_be(const volatile void __iomem *addr) +{ + u64 bar_offset; + u64 dsa; + int retry = 0; + struct HvCallPci_LoadReturn ret; + struct device_node *dn = + xlate_iomm_address(addr, &dsa, &bar_offset, "read_word"); + + if (dn == NULL) + return 0xffff; + do { + HvCall3Ret16(HvCallPciBarLoad16, &ret, dsa, + bar_offset, 0); + } while (check_return_code("RDW", dn, &retry, ret.rc) != 0); + + return ret.value; +} + +static u32 iseries_readl_be(const volatile void __iomem *addr) +{ + u64 bar_offset; + u64 dsa; + int retry = 0; + struct HvCallPci_LoadReturn ret; + struct device_node *dn = + xlate_iomm_address(addr, &dsa, &bar_offset, "read_long"); + + if (dn == NULL) + return 0xffffffff; + do { + HvCall3Ret16(HvCallPciBarLoad32, &ret, dsa, + bar_offset, 0); + } while (check_return_code("RDL", dn, &retry, ret.rc) != 0); + + return ret.value; +} + +/* + * Write MM I/O Instructions for the iSeries + * + */ +static void iseries_writeb(u8 data, volatile void __iomem *addr) +{ + u64 bar_offset; + u64 dsa; + int retry = 0; + u64 rc; + struct device_node *dn = + xlate_iomm_address(addr, &dsa, &bar_offset, "write_byte"); + + if (dn == NULL) + return; + do { + rc = HvCall4(HvCallPciBarStore8, dsa, bar_offset, data, 0); + } while (check_return_code("WWB", dn, &retry, rc) != 0); +} + +static void iseries_writew_be(u16 data, volatile void __iomem *addr) +{ + u64 bar_offset; + u64 dsa; + int retry = 0; + u64 rc; + struct device_node *dn = + xlate_iomm_address(addr, &dsa, &bar_offset, "write_word"); + + if (dn == NULL) + return; + do { + rc = HvCall4(HvCallPciBarStore16, dsa, bar_offset, data, 0); + } while (check_return_code("WWW", dn, &retry, rc) != 0); +} + +static void iseries_writel_be(u32 data, volatile void __iomem *addr) +{ + u64 bar_offset; + u64 dsa; + int retry = 0; + u64 rc; + struct device_node *dn = + xlate_iomm_address(addr, &dsa, &bar_offset, "write_long"); + + if (dn == NULL) + return; + do { + rc = HvCall4(HvCallPciBarStore32, dsa, bar_offset, data, 0); + } while (check_return_code("WWL", dn, &retry, rc) != 0); +} + +static u16 iseries_readw(const volatile void __iomem *addr) +{ + return le16_to_cpu(iseries_readw_be(addr)); +} + +static u32 iseries_readl(const volatile void __iomem *addr) +{ + return le32_to_cpu(iseries_readl_be(addr)); +} + +static void iseries_writew(u16 data, volatile void __iomem *addr) +{ + iseries_writew_be(cpu_to_le16(data), addr); +} + +static void iseries_writel(u32 data, volatile void __iomem *addr) +{ + iseries_writel(cpu_to_le32(data), addr); +} + +static void iseries_readsb(const volatile void __iomem *addr, void *buf, + unsigned long count) +{ + u8 *dst = buf; + while(count-- > 0) + *(dst++) = iseries_readb(addr); +} + +static void iseries_readsw(const volatile void __iomem *addr, void *buf, + unsigned long count) +{ + u16 *dst = buf; + while(count-- > 0) + *(dst++) = iseries_readw_be(addr); +} + +static void iseries_readsl(const volatile void __iomem *addr, void *buf, + unsigned long count) +{ + u32 *dst = buf; + while(count-- > 0) + *(dst++) = iseries_readl_be(addr); +} + +static void iseries_writesb(volatile void __iomem *addr, const void *buf, + unsigned long count) +{ + const u8 *src = buf; + while(count-- > 0) + iseries_writeb(*(src++), addr); +} + +static void iseries_writesw(volatile void __iomem *addr, const void *buf, + unsigned long count) +{ + const u16 *src = buf; + while(count-- > 0) + iseries_writew_be(*(src++), addr); +} + +static void iseries_writesl(volatile void __iomem *addr, const void *buf, + unsigned long count) +{ + const u32 *src = buf; + while(count-- > 0) + iseries_writel_be(*(src++), addr); +} + +static void iseries_memset_io(volatile void __iomem *addr, int c, + unsigned long n) +{ + volatile char __iomem *d = addr; + + while (n-- > 0) + iseries_writeb(c, d++); +} + +static void iseries_memcpy_fromio(void *dest, const volatile void __iomem *src, + unsigned long n) +{ + char *d = dest; + const volatile char __iomem *s = src; + + while (n-- > 0) + *d++ = iseries_readb(s++); +} + +static void iseries_memcpy_toio(volatile void __iomem *dest, const void *src, + unsigned long n) +{ + const char *s = src; + volatile char __iomem *d = dest; + + while (n-- > 0) + iseries_writeb(*s++, d++); +} + +/* We only set MMIO ops. The default PIO ops will be default + * to the MMIO ops + pci_io_base which is 0 on iSeries as + * expected so both should work. + * + * Note that we don't implement the readq/writeq versions as + * I don't know of an HV call for doing so. Thus, the default + * operation will be used instead, which will fault a the value + * return by iSeries for MMIO addresses always hits a non mapped + * area. This is as good as the BUG() we used to have there. + */ +static struct ppc_pci_io __initdata iseries_pci_io = { + .readb = iseries_readb, + .readw = iseries_readw, + .readl = iseries_readl, + .readw_be = iseries_readw_be, + .readl_be = iseries_readl_be, + .writeb = iseries_writeb, + .writew = iseries_writew, + .writel = iseries_writel, + .writew_be = iseries_writew_be, + .writel_be = iseries_writel_be, + .readsb = iseries_readsb, + .readsw = iseries_readsw, + .readsl = iseries_readsl, + .writesb = iseries_writesb, + .writesw = iseries_writesw, + .writesl = iseries_writesl, + .memset_io = iseries_memset_io, + .memcpy_fromio = iseries_memcpy_fromio, + .memcpy_toio = iseries_memcpy_toio, +}; + +/* + * iSeries_pcibios_init + * + * Description: + * This function checks for all possible system PCI host bridges that connect + * PCI buses. The system hypervisor is queried as to the guest partition + * ownership status. A pci_controller is built for any bus which is partially + * owned or fully owned by this guest partition. + */ +void __init iSeries_pcibios_init(void) +{ + struct pci_controller *phb; + struct device_node *root = of_find_node_by_path("/"); + struct device_node *node = NULL; + + /* Install IO hooks */ + ppc_pci_io = iseries_pci_io; + + pci_probe_only = 1; + + /* iSeries has no IO space in the common sense, it needs to set + * the IO base to 0 + */ + pci_io_base = 0; + + if (root == NULL) { + printk(KERN_CRIT "iSeries_pcibios_init: can't find root " + "of device tree\n"); + return; + } + while ((node = of_get_next_child(root, node)) != NULL) { + HvBusNumber bus; + const u32 *busp; + + if ((node->type == NULL) || (strcmp(node->type, "pci") != 0)) + continue; + + busp = of_get_property(node, "bus-range", NULL); + if (busp == NULL) + continue; + bus = *busp; + printk("bus %d appears to exist\n", bus); + phb = pcibios_alloc_controller(node); + if (phb == NULL) + continue; + /* All legacy iSeries PHBs are in domain zero */ + phb->global_number = 0; + + phb->first_busno = bus; + phb->last_busno = bus; + phb->ops = &iSeries_pci_ops; + phb->io_base_virt = (void __iomem *)_IO_BASE; + phb->io_resource.flags = IORESOURCE_IO; + phb->io_resource.start = BASE_IO_MEMORY; + phb->io_resource.end = END_IO_MEMORY; + phb->io_resource.name = "iSeries PCI IO"; + phb->mem_resources[0].flags = IORESOURCE_MEM; + phb->mem_resources[0].start = BASE_IO_MEMORY; + phb->mem_resources[0].end = END_IO_MEMORY; + phb->mem_resources[0].name = "Series PCI MEM"; + } + + of_node_put(root); + + pci_devs_phb_init(); +} + diff --git a/arch/powerpc/platforms/iseries/pci.h b/arch/powerpc/platforms/iseries/pci.h new file mode 100644 index 00000000..d9cf974c --- /dev/null +++ b/arch/powerpc/platforms/iseries/pci.h @@ -0,0 +1,58 @@ +#ifndef _PLATFORMS_ISERIES_PCI_H +#define _PLATFORMS_ISERIES_PCI_H + +/* + * Created by Allan Trautman on Tue Feb 20, 2001. + * + * Define some useful macros for the iSeries pci routines. + * Copyright (C) 2001 Allan H Trautman, IBM Corporation + * + * 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 + * + * Change Activity: + * Created Feb 20, 2001 + * Added device reset, March 22, 2001 + * Ported to ppc64, May 25, 2001 + * End Change Activity + */ + +/* + * Decodes Linux DevFn to iSeries DevFn, bridge device, or function. + * For Linux, see PCI_SLOT and PCI_FUNC in include/linux/pci.h + */ + +#define ISERIES_PCI_AGENTID(idsel, func) \ + (((idsel & 0x0F) << 4) | (func & 0x07)) +#define ISERIES_ENCODE_DEVICE(agentid) \ + ((0x10) | ((agentid & 0x20) >> 2) | (agentid & 0x07)) + +#define ISERIES_GET_DEVICE_FROM_SUBBUS(subbus) ((subbus >> 5) & 0x7) +#define ISERIES_GET_FUNCTION_FROM_SUBBUS(subbus) ((subbus >> 2) & 0x7) + +struct pci_dev; + +#ifdef CONFIG_PCI +extern void iSeries_pcibios_init(void); +extern void iSeries_pci_final_fixup(void); +extern void iSeries_pcibios_fixup_resources(struct pci_dev *dev); +#else +static inline void iSeries_pcibios_init(void) { } +static inline void iSeries_pci_final_fixup(void) { } +static inline void iSeries_pcibios_fixup_resources(struct pci_dev *dev) {} +#endif + +#endif /* _PLATFORMS_ISERIES_PCI_H */ diff --git a/arch/powerpc/platforms/iseries/proc.c b/arch/powerpc/platforms/iseries/proc.c new file mode 100644 index 00000000..06763682 --- /dev/null +++ b/arch/powerpc/platforms/iseries/proc.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2001 Kyle A. Lucke IBM Corporation + * Copyright (C) 2001 Mike Corrigan & Dave Engebretsen IBM Corporation + * + * 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/init.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/param.h> /* for HZ */ +#include <asm/paca.h> +#include <asm/processor.h> +#include <asm/time.h> +#include <asm/lppaca.h> +#include <asm/firmware.h> +#include <asm/iseries/hv_call_xm.h> + +#include "processor_vpd.h" +#include "main_store.h" + +static int __init iseries_proc_create(void) +{ + struct proc_dir_entry *e; + + if (!firmware_has_feature(FW_FEATURE_ISERIES)) + return 0; + + e = proc_mkdir("iSeries", 0); + if (!e) + return 1; + + return 0; +} +core_initcall(iseries_proc_create); + +static unsigned long startTitan = 0; +static unsigned long startTb = 0; + +static int proc_titantod_show(struct seq_file *m, void *v) +{ + unsigned long tb0, titan_tod; + + tb0 = get_tb(); + titan_tod = HvCallXm_loadTod(); + + seq_printf(m, "Titan\n" ); + seq_printf(m, " time base = %016lx\n", tb0); + seq_printf(m, " titan tod = %016lx\n", titan_tod); + seq_printf(m, " xProcFreq = %016x\n", + xIoHriProcessorVpd[0].xProcFreq); + seq_printf(m, " xTimeBaseFreq = %016x\n", + xIoHriProcessorVpd[0].xTimeBaseFreq); + seq_printf(m, " tb_ticks_per_jiffy = %lu\n", tb_ticks_per_jiffy); + seq_printf(m, " tb_ticks_per_usec = %lu\n", tb_ticks_per_usec); + + if (!startTitan) { + startTitan = titan_tod; + startTb = tb0; + } else { + unsigned long titan_usec = (titan_tod - startTitan) >> 12; + unsigned long tb_ticks = (tb0 - startTb); + unsigned long titan_jiffies = titan_usec / (1000000/HZ); + unsigned long titan_jiff_usec = titan_jiffies * (1000000/HZ); + unsigned long titan_jiff_rem_usec = + titan_usec - titan_jiff_usec; + unsigned long tb_jiffies = tb_ticks / tb_ticks_per_jiffy; + unsigned long tb_jiff_ticks = tb_jiffies * tb_ticks_per_jiffy; + unsigned long tb_jiff_rem_ticks = tb_ticks - tb_jiff_ticks; + unsigned long tb_jiff_rem_usec = + tb_jiff_rem_ticks / tb_ticks_per_usec; + unsigned long new_tb_ticks_per_jiffy = + (tb_ticks * (1000000/HZ))/titan_usec; + + seq_printf(m, " titan elapsed = %lu uSec\n", titan_usec); + seq_printf(m, " tb elapsed = %lu ticks\n", tb_ticks); + seq_printf(m, " titan jiffies = %lu.%04lu\n", titan_jiffies, + titan_jiff_rem_usec); + seq_printf(m, " tb jiffies = %lu.%04lu\n", tb_jiffies, + tb_jiff_rem_usec); + seq_printf(m, " new tb_ticks_per_jiffy = %lu\n", + new_tb_ticks_per_jiffy); + } + + return 0; +} + +static int proc_titantod_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_titantod_show, NULL); +} + +static const struct file_operations proc_titantod_operations = { + .open = proc_titantod_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init iseries_proc_init(void) +{ + if (!firmware_has_feature(FW_FEATURE_ISERIES)) + return 0; + + proc_create("iSeries/titanTod", S_IFREG|S_IRUGO, NULL, + &proc_titantod_operations); + return 0; +} +__initcall(iseries_proc_init); diff --git a/arch/powerpc/platforms/iseries/processor_vpd.h b/arch/powerpc/platforms/iseries/processor_vpd.h new file mode 100644 index 00000000..7ac5d0d0 --- /dev/null +++ b/arch/powerpc/platforms/iseries/processor_vpd.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ +#ifndef _ISERIES_PROCESSOR_VPD_H +#define _ISERIES_PROCESSOR_VPD_H + +#include <asm/types.h> + +/* + * This struct maps Processor Vpd that is DMAd to SLIC by CSP + */ +struct IoHriProcessorVpd { + u8 xFormat; // VPD format indicator x00-x00 + u8 xProcStatus:8; // Processor State x01-x01 + u8 xSecondaryThreadCount; // Secondary thread cnt x02-x02 + u8 xSrcType:1; // Src Type x03-x03 + u8 xSrcSoft:1; // Src stay soft ... + u8 xSrcParable:1; // Src parable ... + u8 xRsvd1:5; // Reserved ... + u16 xHvPhysicalProcIndex; // Hypervisor physical proc index04-x05 + u16 xRsvd2; // Reserved x06-x07 + u32 xHwNodeId; // Hardware node id x08-x0B + u32 xHwProcId; // Hardware processor id x0C-x0F + + u32 xTypeNum; // Card Type/CCIN number x10-x13 + u32 xModelNum; // Model/Feature number x14-x17 + u64 xSerialNum; // Serial number x18-x1F + char xPartNum[12]; // Book Part or FPU number x20-x2B + char xMfgID[4]; // Manufacturing ID x2C-x2F + + u32 xProcFreq; // Processor Frequency x30-x33 + u32 xTimeBaseFreq; // Time Base Frequency x34-x37 + + u32 xChipEcLevel; // Chip EC Levels x38-x3B + u32 xProcIdReg; // PIR SPR value x3C-x3F + u32 xPVR; // PVR value x40-x43 + u8 xRsvd3[12]; // Reserved x44-x4F + + u32 xInstCacheSize; // Instruction cache size in KB x50-x53 + u32 xInstBlockSize; // Instruction cache block size x54-x57 + u32 xDataCacheOperandSize; // Data cache operand size x58-x5B + u32 xInstCacheOperandSize; // Inst cache operand size x5C-x5F + + u32 xDataL1CacheSizeKB; // L1 data cache size in KB x60-x63 + u32 xDataL1CacheLineSize; // L1 data cache block size x64-x67 + u64 xRsvd4; // Reserved x68-x6F + + u32 xDataL2CacheSizeKB; // L2 data cache size in KB x70-x73 + u32 xDataL2CacheLineSize; // L2 data cache block size x74-x77 + u64 xRsvd5; // Reserved x78-x7F + + u32 xDataL3CacheSizeKB; // L3 data cache size in KB x80-x83 + u32 xDataL3CacheLineSize; // L3 data cache block size x84-x87 + u64 xRsvd6; // Reserved x88-x8F + + u64 xFruLabel; // Card Location Label x90-x97 + u8 xSlotsOnCard; // Slots on card (0=no slots) x98-x98 + u8 xPartLocFlag; // Location flag (0-pluggable 1-imbedded) x99-x99 + u16 xSlotMapIndex; // Index in slot map table x9A-x9B + u8 xSmartCardPortNo; // Smart card port number x9C-x9C + u8 xRsvd7; // Reserved x9D-x9D + u16 xFrameIdAndRackUnit; // Frame ID and rack unit adr x9E-x9F + + u8 xRsvd8[24]; // Reserved xA0-xB7 + + char xProcSrc[72]; // CSP format SRC xB8-xFF +}; + +extern struct IoHriProcessorVpd xIoHriProcessorVpd[]; + +#endif /* _ISERIES_PROCESSOR_VPD_H */ diff --git a/arch/powerpc/platforms/iseries/release_data.h b/arch/powerpc/platforms/iseries/release_data.h new file mode 100644 index 00000000..6ad7d843 --- /dev/null +++ b/arch/powerpc/platforms/iseries/release_data.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ +#ifndef _ISERIES_RELEASE_DATA_H +#define _ISERIES_RELEASE_DATA_H + +/* + * This control block contains the critical information about the + * release so that it can be changed in the future (ie, the virtual + * address of the OS's NACA). + */ +#include <asm/types.h> +#include "naca.h" + +/* + * When we IPL a secondary partition, we will check if if the + * secondary xMinPlicVrmIndex > the primary xVrmIndex. + * If it is then this tells PLIC that this secondary is not + * supported running on this "old" of a level of PLIC. + * + * Likewise, we will compare the primary xMinSlicVrmIndex to + * the secondary xVrmIndex. + * If the primary xMinSlicVrmDelta > secondary xVrmDelta then we + * know that this PLIC does not support running an OS "that old". + */ + +#define HVREL_TAGSINACTIVE 0x8000 +#define HVREL_32BIT 0x4000 +#define HVREL_NOSHAREDPROCS 0x2000 +#define HVREL_NOHMT 0x1000 + +struct HvReleaseData { + u32 xDesc; /* Descriptor "HvRD" ebcdic x00-x03 */ + u16 xSize; /* Size of this control block x04-x05 */ + u16 xVpdAreasPtrOffset; /* Offset in NACA of ItVpdAreas x06-x07 */ + struct naca_struct *xSlicNacaAddr; /* Virt addr of SLIC NACA x08-x0F */ + u32 xMsNucDataOffset; /* Offset of Linux Mapping Data x10-x13 */ + u32 xRsvd1; /* Reserved x14-x17 */ + u16 xFlags; + u16 xVrmIndex; /* VRM Index of OS image x1A-x1B */ + u16 xMinSupportedPlicVrmIndex; /* Min PLIC level (soft) x1C-x1D */ + u16 xMinCompatablePlicVrmIndex; /* Min PLIC levelP (hard) x1E-x1F */ + char xVrmName[12]; /* Displayable name x20-x2B */ + char xRsvd3[20]; /* Reserved x2C-x3F */ +}; + +extern const struct HvReleaseData hvReleaseData; + +#endif /* _ISERIES_RELEASE_DATA_H */ diff --git a/arch/powerpc/platforms/iseries/setup.c b/arch/powerpc/platforms/iseries/setup.c new file mode 100644 index 00000000..c25a0815 --- /dev/null +++ b/arch/powerpc/platforms/iseries/setup.c @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2000 Mike Corrigan <mikejc@us.ibm.com> + * Copyright (c) 1999-2000 Grant Erickson <grant@lcse.umn.edu> + * + * Description: + * Architecture- / platform-specific boot-time initialization code for + * the IBM iSeries LPAR. Adapted from original code by Grant Erickson and + * code by Gary Thomas, Cort Dougan <cort@fsmlabs.com>, and Dan Malek + * <dan@net4x.com>. + * + * 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. + */ + +#undef DEBUG + +#include <linux/init.h> +#include <linux/threads.h> +#include <linux/smp.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/seq_file.h> +#include <linux/kdev_t.h> +#include <linux/kexec.h> +#include <linux/major.h> +#include <linux/root_dev.h> +#include <linux/kernel.h> +#include <linux/hrtimer.h> +#include <linux/tick.h> + +#include <asm/processor.h> +#include <asm/machdep.h> +#include <asm/page.h> +#include <asm/mmu.h> +#include <asm/pgtable.h> +#include <asm/mmu_context.h> +#include <asm/cputable.h> +#include <asm/sections.h> +#include <asm/iommu.h> +#include <asm/firmware.h> +#include <asm/system.h> +#include <asm/time.h> +#include <asm/paca.h> +#include <asm/cache.h> +#include <asm/abs_addr.h> +#include <asm/iseries/hv_lp_config.h> +#include <asm/iseries/hv_call_event.h> +#include <asm/iseries/hv_call_xm.h> +#include <asm/iseries/it_lp_queue.h> +#include <asm/iseries/mf.h> +#include <asm/iseries/hv_lp_event.h> +#include <asm/iseries/lpar_map.h> +#include <asm/udbg.h> +#include <asm/irq.h> + +#include "naca.h" +#include "setup.h" +#include "irq.h" +#include "vpd_areas.h" +#include "processor_vpd.h" +#include "it_lp_naca.h" +#include "main_store.h" +#include "call_sm.h" +#include "call_hpt.h" +#include "pci.h" + +#ifdef DEBUG +#define DBG(fmt...) udbg_printf(fmt) +#else +#define DBG(fmt...) +#endif + +/* Function Prototypes */ +static unsigned long build_iSeries_Memory_Map(void); +static void iseries_shared_idle(void); +static void iseries_dedicated_idle(void); + + +struct MemoryBlock { + unsigned long absStart; + unsigned long absEnd; + unsigned long logicalStart; + unsigned long logicalEnd; +}; + +/* + * Process the main store vpd to determine where the holes in memory are + * and return the number of physical blocks and fill in the array of + * block data. + */ +static unsigned long iSeries_process_Condor_mainstore_vpd( + struct MemoryBlock *mb_array, unsigned long max_entries) +{ + unsigned long holeFirstChunk, holeSizeChunks; + unsigned long numMemoryBlocks = 1; + struct IoHriMainStoreSegment4 *msVpd = + (struct IoHriMainStoreSegment4 *)xMsVpd; + unsigned long holeStart = msVpd->nonInterleavedBlocksStartAdr; + unsigned long holeEnd = msVpd->nonInterleavedBlocksEndAdr; + unsigned long holeSize = holeEnd - holeStart; + + printk("Mainstore_VPD: Condor\n"); + /* + * Determine if absolute memory has any + * holes so that we can interpret the + * access map we get back from the hypervisor + * correctly. + */ + mb_array[0].logicalStart = 0; + mb_array[0].logicalEnd = 0x100000000UL; + mb_array[0].absStart = 0; + mb_array[0].absEnd = 0x100000000UL; + + if (holeSize) { + numMemoryBlocks = 2; + holeStart = holeStart & 0x000fffffffffffffUL; + holeStart = addr_to_chunk(holeStart); + holeFirstChunk = holeStart; + holeSize = addr_to_chunk(holeSize); + holeSizeChunks = holeSize; + printk( "Main store hole: start chunk = %0lx, size = %0lx chunks\n", + holeFirstChunk, holeSizeChunks ); + mb_array[0].logicalEnd = holeFirstChunk; + mb_array[0].absEnd = holeFirstChunk; + mb_array[1].logicalStart = holeFirstChunk; + mb_array[1].logicalEnd = 0x100000000UL - holeSizeChunks; + mb_array[1].absStart = holeFirstChunk + holeSizeChunks; + mb_array[1].absEnd = 0x100000000UL; + } + return numMemoryBlocks; +} + +#define MaxSegmentAreas 32 +#define MaxSegmentAdrRangeBlocks 128 +#define MaxAreaRangeBlocks 4 + +static unsigned long iSeries_process_Regatta_mainstore_vpd( + struct MemoryBlock *mb_array, unsigned long max_entries) +{ + struct IoHriMainStoreSegment5 *msVpdP = + (struct IoHriMainStoreSegment5 *)xMsVpd; + unsigned long numSegmentBlocks = 0; + u32 existsBits = msVpdP->msAreaExists; + unsigned long area_num; + + printk("Mainstore_VPD: Regatta\n"); + + for (area_num = 0; area_num < MaxSegmentAreas; ++area_num ) { + unsigned long numAreaBlocks; + struct IoHriMainStoreArea4 *currentArea; + + if (existsBits & 0x80000000) { + unsigned long block_num; + + currentArea = &msVpdP->msAreaArray[area_num]; + numAreaBlocks = currentArea->numAdrRangeBlocks; + printk("ms_vpd: processing area %2ld blocks=%ld", + area_num, numAreaBlocks); + for (block_num = 0; block_num < numAreaBlocks; + ++block_num ) { + /* Process an address range block */ + struct MemoryBlock tempBlock; + unsigned long i; + + tempBlock.absStart = + (unsigned long)currentArea->xAdrRangeBlock[block_num].blockStart; + tempBlock.absEnd = + (unsigned long)currentArea->xAdrRangeBlock[block_num].blockEnd; + tempBlock.logicalStart = 0; + tempBlock.logicalEnd = 0; + printk("\n block %ld absStart=%016lx absEnd=%016lx", + block_num, tempBlock.absStart, + tempBlock.absEnd); + + for (i = 0; i < numSegmentBlocks; ++i) { + if (mb_array[i].absStart == + tempBlock.absStart) + break; + } + if (i == numSegmentBlocks) { + if (numSegmentBlocks == max_entries) + panic("iSeries_process_mainstore_vpd: too many memory blocks"); + mb_array[numSegmentBlocks] = tempBlock; + ++numSegmentBlocks; + } else + printk(" (duplicate)"); + } + printk("\n"); + } + existsBits <<= 1; + } + /* Now sort the blocks found into ascending sequence */ + if (numSegmentBlocks > 1) { + unsigned long m, n; + + for (m = 0; m < numSegmentBlocks - 1; ++m) { + for (n = numSegmentBlocks - 1; m < n; --n) { + if (mb_array[n].absStart < + mb_array[n-1].absStart) { + struct MemoryBlock tempBlock; + + tempBlock = mb_array[n]; + mb_array[n] = mb_array[n-1]; + mb_array[n-1] = tempBlock; + } + } + } + } + /* + * Assign "logical" addresses to each block. These + * addresses correspond to the hypervisor "bitmap" space. + * Convert all addresses into units of 256K chunks. + */ + { + unsigned long i, nextBitmapAddress; + + printk("ms_vpd: %ld sorted memory blocks\n", numSegmentBlocks); + nextBitmapAddress = 0; + for (i = 0; i < numSegmentBlocks; ++i) { + unsigned long length = mb_array[i].absEnd - + mb_array[i].absStart; + + mb_array[i].logicalStart = nextBitmapAddress; + mb_array[i].logicalEnd = nextBitmapAddress + length; + nextBitmapAddress += length; + printk(" Bitmap range: %016lx - %016lx\n" + " Absolute range: %016lx - %016lx\n", + mb_array[i].logicalStart, + mb_array[i].logicalEnd, + mb_array[i].absStart, mb_array[i].absEnd); + mb_array[i].absStart = addr_to_chunk(mb_array[i].absStart & + 0x000fffffffffffffUL); + mb_array[i].absEnd = addr_to_chunk(mb_array[i].absEnd & + 0x000fffffffffffffUL); + mb_array[i].logicalStart = + addr_to_chunk(mb_array[i].logicalStart); + mb_array[i].logicalEnd = addr_to_chunk(mb_array[i].logicalEnd); + } + } + + return numSegmentBlocks; +} + +static unsigned long iSeries_process_mainstore_vpd(struct MemoryBlock *mb_array, + unsigned long max_entries) +{ + unsigned long i; + unsigned long mem_blocks = 0; + + if (mmu_has_feature(MMU_FTR_SLB)) + mem_blocks = iSeries_process_Regatta_mainstore_vpd(mb_array, + max_entries); + else + mem_blocks = iSeries_process_Condor_mainstore_vpd(mb_array, + max_entries); + + printk("Mainstore_VPD: numMemoryBlocks = %ld\n", mem_blocks); + for (i = 0; i < mem_blocks; ++i) { + printk("Mainstore_VPD: block %3ld logical chunks %016lx - %016lx\n" + " abs chunks %016lx - %016lx\n", + i, mb_array[i].logicalStart, mb_array[i].logicalEnd, + mb_array[i].absStart, mb_array[i].absEnd); + } + return mem_blocks; +} + +static void __init iSeries_get_cmdline(void) +{ + char *p, *q; + + /* copy the command line parameter from the primary VSP */ + HvCallEvent_dmaToSp(cmd_line, 2 * 64* 1024, 256, + HvLpDma_Direction_RemoteToLocal); + + p = cmd_line; + q = cmd_line + 255; + while(p < q) { + if (!*p || *p == '\n') + break; + ++p; + } + *p = 0; +} + +static void __init iSeries_init_early(void) +{ + DBG(" -> iSeries_init_early()\n"); + + /* Snapshot the timebase, for use in later recalibration */ + iSeries_time_init_early(); + + /* + * Initialize the DMA/TCE management + */ + iommu_init_early_iSeries(); + + /* Initialize machine-dependency vectors */ +#ifdef CONFIG_SMP + smp_init_iSeries(); +#endif + + /* Associate Lp Event Queue 0 with processor 0 */ + HvCallEvent_setLpEventQueueInterruptProc(0, 0); + + mf_init(); + + DBG(" <- iSeries_init_early()\n"); +} + +struct mschunks_map mschunks_map = { + /* XXX We don't use these, but Piranha might need them. */ + .chunk_size = MSCHUNKS_CHUNK_SIZE, + .chunk_shift = MSCHUNKS_CHUNK_SHIFT, + .chunk_mask = MSCHUNKS_OFFSET_MASK, +}; +EXPORT_SYMBOL(mschunks_map); + +static void mschunks_alloc(unsigned long num_chunks) +{ + klimit = _ALIGN(klimit, sizeof(u32)); + mschunks_map.mapping = (u32 *)klimit; + klimit += num_chunks * sizeof(u32); + mschunks_map.num_chunks = num_chunks; +} + +/* + * The iSeries may have very large memories ( > 128 GB ) and a partition + * may get memory in "chunks" that may be anywhere in the 2**52 real + * address space. The chunks are 256K in size. To map this to the + * memory model Linux expects, the AS/400 specific code builds a + * translation table to translate what Linux thinks are "physical" + * addresses to the actual real addresses. This allows us to make + * it appear to Linux that we have contiguous memory starting at + * physical address zero while in fact this could be far from the truth. + * To avoid confusion, I'll let the words physical and/or real address + * apply to the Linux addresses while I'll use "absolute address" to + * refer to the actual hardware real address. + * + * build_iSeries_Memory_Map gets information from the Hypervisor and + * looks at the Main Store VPD to determine the absolute addresses + * of the memory that has been assigned to our partition and builds + * a table used to translate Linux's physical addresses to these + * absolute addresses. Absolute addresses are needed when + * communicating with the hypervisor (e.g. to build HPT entries) + * + * Returns the physical memory size + */ + +static unsigned long __init build_iSeries_Memory_Map(void) +{ + u32 loadAreaFirstChunk, loadAreaLastChunk, loadAreaSize; + u32 nextPhysChunk; + u32 hptFirstChunk, hptLastChunk, hptSizeChunks, hptSizePages; + u32 totalChunks,moreChunks; + u32 currChunk, thisChunk, absChunk; + u32 currDword; + u32 chunkBit; + u64 map; + struct MemoryBlock mb[32]; + unsigned long numMemoryBlocks, curBlock; + + /* Chunk size on iSeries is 256K bytes */ + totalChunks = (u32)HvLpConfig_getMsChunks(); + mschunks_alloc(totalChunks); + + /* + * Get absolute address of our load area + * and map it to physical address 0 + * This guarantees that the loadarea ends up at physical 0 + * otherwise, it might not be returned by PLIC as the first + * chunks + */ + + loadAreaFirstChunk = (u32)addr_to_chunk(itLpNaca.xLoadAreaAddr); + loadAreaSize = itLpNaca.xLoadAreaChunks; + + /* + * Only add the pages already mapped here. + * Otherwise we might add the hpt pages + * The rest of the pages of the load area + * aren't in the HPT yet and can still + * be assigned an arbitrary physical address + */ + if ((loadAreaSize * 64) > HvPagesToMap) + loadAreaSize = HvPagesToMap / 64; + + loadAreaLastChunk = loadAreaFirstChunk + loadAreaSize - 1; + + /* + * TODO Do we need to do something if the HPT is in the 64MB load area? + * This would be required if the itLpNaca.xLoadAreaChunks includes + * the HPT size + */ + + printk("Mapping load area - physical addr = 0000000000000000\n" + " absolute addr = %016lx\n", + chunk_to_addr(loadAreaFirstChunk)); + printk("Load area size %dK\n", loadAreaSize * 256); + + for (nextPhysChunk = 0; nextPhysChunk < loadAreaSize; ++nextPhysChunk) + mschunks_map.mapping[nextPhysChunk] = + loadAreaFirstChunk + nextPhysChunk; + + /* + * Get absolute address of our HPT and remember it so + * we won't map it to any physical address + */ + hptFirstChunk = (u32)addr_to_chunk(HvCallHpt_getHptAddress()); + hptSizePages = (u32)HvCallHpt_getHptPages(); + hptSizeChunks = hptSizePages >> + (MSCHUNKS_CHUNK_SHIFT - HW_PAGE_SHIFT); + hptLastChunk = hptFirstChunk + hptSizeChunks - 1; + + printk("HPT absolute addr = %016lx, size = %dK\n", + chunk_to_addr(hptFirstChunk), hptSizeChunks * 256); + + /* + * Determine if absolute memory has any + * holes so that we can interpret the + * access map we get back from the hypervisor + * correctly. + */ + numMemoryBlocks = iSeries_process_mainstore_vpd(mb, 32); + + /* + * Process the main store access map from the hypervisor + * to build up our physical -> absolute translation table + */ + curBlock = 0; + currChunk = 0; + currDword = 0; + moreChunks = totalChunks; + + while (moreChunks) { + map = HvCallSm_get64BitsOfAccessMap(itLpNaca.xLpIndex, + currDword); + thisChunk = currChunk; + while (map) { + chunkBit = map >> 63; + map <<= 1; + if (chunkBit) { + --moreChunks; + while (thisChunk >= mb[curBlock].logicalEnd) { + ++curBlock; + if (curBlock >= numMemoryBlocks) + panic("out of memory blocks"); + } + if (thisChunk < mb[curBlock].logicalStart) + panic("memory block error"); + + absChunk = mb[curBlock].absStart + + (thisChunk - mb[curBlock].logicalStart); + if (((absChunk < hptFirstChunk) || + (absChunk > hptLastChunk)) && + ((absChunk < loadAreaFirstChunk) || + (absChunk > loadAreaLastChunk))) { + mschunks_map.mapping[nextPhysChunk] = + absChunk; + ++nextPhysChunk; + } + } + ++thisChunk; + } + ++currDword; + currChunk += 64; + } + + /* + * main store size (in chunks) is + * totalChunks - hptSizeChunks + * which should be equal to + * nextPhysChunk + */ + return chunk_to_addr(nextPhysChunk); +} + +/* + * Document me. + */ +static void __init iSeries_setup_arch(void) +{ + if (get_lppaca()->shared_proc) { + ppc_md.idle_loop = iseries_shared_idle; + printk(KERN_DEBUG "Using shared processor idle loop\n"); + } else { + ppc_md.idle_loop = iseries_dedicated_idle; + printk(KERN_DEBUG "Using dedicated idle loop\n"); + } + + /* Setup the Lp Event Queue */ + setup_hvlpevent_queue(); + + printk("Max logical processors = %d\n", + itVpdAreas.xSlicMaxLogicalProcs); + printk("Max physical processors = %d\n", + itVpdAreas.xSlicMaxPhysicalProcs); + + iSeries_pcibios_init(); +} + +static void iSeries_show_cpuinfo(struct seq_file *m) +{ + seq_printf(m, "machine\t\t: 64-bit iSeries Logical Partition\n"); +} + +static void __init iSeries_progress(char * st, unsigned short code) +{ + printk("Progress: [%04x] - %s\n", (unsigned)code, st); + mf_display_progress(code); +} + +static void __init iSeries_fixup_klimit(void) +{ + /* + * Change klimit to take into account any ram disk + * that may be included + */ + if (naca.xRamDisk) + klimit = KERNELBASE + (u64)naca.xRamDisk + + (naca.xRamDiskSize * HW_PAGE_SIZE); +} + +static int __init iSeries_src_init(void) +{ + /* clear the progress line */ + if (firmware_has_feature(FW_FEATURE_ISERIES)) + ppc_md.progress(" ", 0xffff); + return 0; +} + +late_initcall(iSeries_src_init); + +static inline void process_iSeries_events(void) +{ + asm volatile ("li 0,0x5555; sc" : : : "r0", "r3"); +} + +static void yield_shared_processor(void) +{ + unsigned long tb; + + HvCall_setEnabledInterrupts(HvCall_MaskIPI | + HvCall_MaskLpEvent | + HvCall_MaskLpProd | + HvCall_MaskTimeout); + + tb = get_tb(); + /* Compute future tb value when yield should expire */ + HvCall_yieldProcessor(HvCall_YieldTimed, tb+tb_ticks_per_jiffy); + + /* + * The decrementer stops during the yield. Force a fake decrementer + * here and let the timer_interrupt code sort out the actual time. + */ + get_lppaca()->int_dword.fields.decr_int = 1; + ppc64_runlatch_on(); + process_iSeries_events(); +} + +static void iseries_shared_idle(void) +{ + while (1) { + tick_nohz_stop_sched_tick(1); + while (!need_resched() && !hvlpevent_is_pending()) { + local_irq_disable(); + ppc64_runlatch_off(); + + /* Recheck with irqs off */ + if (!need_resched() && !hvlpevent_is_pending()) + yield_shared_processor(); + + HMT_medium(); + local_irq_enable(); + } + + ppc64_runlatch_on(); + tick_nohz_restart_sched_tick(); + + if (hvlpevent_is_pending()) + process_iSeries_events(); + + preempt_enable_no_resched(); + schedule(); + preempt_disable(); + } +} + +static void iseries_dedicated_idle(void) +{ + set_thread_flag(TIF_POLLING_NRFLAG); + + while (1) { + tick_nohz_stop_sched_tick(1); + if (!need_resched()) { + while (!need_resched()) { + ppc64_runlatch_off(); + HMT_low(); + + if (hvlpevent_is_pending()) { + HMT_medium(); + ppc64_runlatch_on(); + process_iSeries_events(); + } + } + + HMT_medium(); + } + + ppc64_runlatch_on(); + tick_nohz_restart_sched_tick(); + preempt_enable_no_resched(); + schedule(); + preempt_disable(); + } +} + +static void __iomem *iseries_ioremap(phys_addr_t address, unsigned long size, + unsigned long flags, void *caller) +{ + return (void __iomem *)address; +} + +static void iseries_iounmap(volatile void __iomem *token) +{ +} + +static int __init iseries_probe(void) +{ + unsigned long root = of_get_flat_dt_root(); + if (!of_flat_dt_is_compatible(root, "IBM,iSeries")) + return 0; + + hpte_init_iSeries(); + /* iSeries does not support 16M pages */ + cur_cpu_spec->mmu_features &= ~MMU_FTR_16M_PAGE; + + return 1; +} + +#ifdef CONFIG_KEXEC +static int iseries_kexec_prepare(struct kimage *image) +{ + return -ENOSYS; +} +#endif + +define_machine(iseries) { + .name = "iSeries", + .setup_arch = iSeries_setup_arch, + .show_cpuinfo = iSeries_show_cpuinfo, + .init_IRQ = iSeries_init_IRQ, + .get_irq = iSeries_get_irq, + .init_early = iSeries_init_early, + .pcibios_fixup = iSeries_pci_final_fixup, + .pcibios_fixup_resources= iSeries_pcibios_fixup_resources, + .restart = mf_reboot, + .power_off = mf_power_off, + .halt = mf_power_off, + .get_boot_time = iSeries_get_boot_time, + .set_rtc_time = iSeries_set_rtc_time, + .get_rtc_time = iSeries_get_rtc_time, + .calibrate_decr = generic_calibrate_decr, + .progress = iSeries_progress, + .probe = iseries_probe, + .ioremap = iseries_ioremap, + .iounmap = iseries_iounmap, +#ifdef CONFIG_KEXEC + .machine_kexec_prepare = iseries_kexec_prepare, +#endif + /* XXX Implement enable_pmcs for iSeries */ +}; + +void * __init iSeries_early_setup(void) +{ + unsigned long phys_mem_size; + + /* Identify CPU type. This is done again by the common code later + * on but calling this function multiple times is fine. + */ + identify_cpu(0, mfspr(SPRN_PVR)); + initialise_paca(&boot_paca, 0); + + powerpc_firmware_features |= FW_FEATURE_ISERIES; + powerpc_firmware_features |= FW_FEATURE_LPAR; + +#ifdef CONFIG_SMP + /* On iSeries we know we can never have more than 64 cpus */ + nr_cpu_ids = max(nr_cpu_ids, 64); +#endif + + iSeries_fixup_klimit(); + + /* + * Initialize the table which translate Linux physical addresses to + * AS/400 absolute addresses + */ + phys_mem_size = build_iSeries_Memory_Map(); + + iSeries_get_cmdline(); + + return (void *) __pa(build_flat_dt(phys_mem_size)); +} + +static void hvputc(char c) +{ + if (c == '\n') + hvputc('\r'); + + HvCall_writeLogBuffer(&c, 1); +} + +void __init udbg_init_iseries(void) +{ + udbg_putc = hvputc; +} diff --git a/arch/powerpc/platforms/iseries/setup.h b/arch/powerpc/platforms/iseries/setup.h new file mode 100644 index 00000000..729754bb --- /dev/null +++ b/arch/powerpc/platforms/iseries/setup.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2000 Mike Corrigan <mikejc@us.ibm.com> + * Copyright (c) 1999-2000 Grant Erickson <grant@lcse.umn.edu> + * + * Description: + * Architecture- / platform-specific boot-time initialization code for + * the IBM AS/400 LPAR. Adapted from original code by Grant Erickson and + * code by Gary Thomas, Cort Dougan <cort@cs.nmt.edu>, and Dan Malek + * <dan@netx4.com>. + * + * 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. + */ + +#ifndef __ISERIES_SETUP_H__ +#define __ISERIES_SETUP_H__ + +extern void *iSeries_early_setup(void); +extern unsigned long iSeries_get_boot_time(void); +extern int iSeries_set_rtc_time(struct rtc_time *tm); +extern void iSeries_get_rtc_time(struct rtc_time *tm); + +extern void *build_flat_dt(unsigned long phys_mem_size); + +#endif /* __ISERIES_SETUP_H__ */ diff --git a/arch/powerpc/platforms/iseries/smp.c b/arch/powerpc/platforms/iseries/smp.c new file mode 100644 index 00000000..e3265add --- /dev/null +++ b/arch/powerpc/platforms/iseries/smp.c @@ -0,0 +1,89 @@ +/* + * SMP support for iSeries machines. + * + * Dave Engebretsen, Peter Bergner, and + * Mike Corrigan {engebret|bergner|mikec}@us.ibm.com + * + * Plus various changes from other IBM teams... + * + * 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. + */ + +#undef DEBUG + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/interrupt.h> +#include <linux/kernel_stat.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/cache.h> +#include <linux/err.h> +#include <linux/sysdev.h> +#include <linux/cpu.h> + +#include <asm/ptrace.h> +#include <asm/atomic.h> +#include <asm/irq.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/io.h> +#include <asm/smp.h> +#include <asm/paca.h> +#include <asm/iseries/hv_call.h> +#include <asm/time.h> +#include <asm/machdep.h> +#include <asm/cputable.h> +#include <asm/system.h> + +static void smp_iSeries_cause_ipi(int cpu, unsigned long data) +{ + HvCall_sendIPI(&(paca[cpu])); +} + +static int smp_iSeries_probe(void) +{ + return cpumask_weight(cpu_possible_mask); +} + +static int smp_iSeries_kick_cpu(int nr) +{ + BUG_ON((nr < 0) || (nr >= NR_CPUS)); + + /* Verify that our partition has a processor nr */ + if (lppaca_of(nr).dyn_proc_status >= 2) + return -ENOENT; + + /* The processor is currently spinning, waiting + * for the cpu_start field to become non-zero + * After we set cpu_start, the processor will + * continue on to secondary_start in iSeries_head.S + */ + paca[nr].cpu_start = 1; + + return 0; +} + +static void __devinit smp_iSeries_setup_cpu(int nr) +{ +} + +static struct smp_ops_t iSeries_smp_ops = { + .message_pass = smp_muxed_ipi_message_pass, + .cause_ipi = smp_iSeries_cause_ipi, + .probe = smp_iSeries_probe, + .kick_cpu = smp_iSeries_kick_cpu, + .setup_cpu = smp_iSeries_setup_cpu, +}; + +/* This is called very early. */ +void __init smp_init_iSeries(void) +{ + smp_ops = &iSeries_smp_ops; +} diff --git a/arch/powerpc/platforms/iseries/spcomm_area.h b/arch/powerpc/platforms/iseries/spcomm_area.h new file mode 100644 index 00000000..598b7c14 --- /dev/null +++ b/arch/powerpc/platforms/iseries/spcomm_area.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ + +#ifndef _ISERIES_SPCOMM_AREA_H +#define _ISERIES_SPCOMM_AREA_H + + +struct SpCommArea { + u32 xDesc; // Descriptor (only in new formats) 000-003 + u8 xFormat; // Format (only in new formats) 004-004 + u8 xRsvd1[11]; // Reserved 005-00F + u64 xRawTbAtIplStart; // Raw HW TB value when IPL is started 010-017 + u64 xRawTodAtIplStart; // Raw HW TOD value when IPL is started 018-01F + u64 xBcdTimeAtIplStart; // BCD time when IPL is started 020-027 + u64 xBcdTimeAtOsStart; // BCD time when OS passed control 028-02F + u8 xRsvd2[80]; // Reserved 030-07F +}; + +#endif /* _ISERIES_SPCOMM_AREA_H */ diff --git a/arch/powerpc/platforms/iseries/vio.c b/arch/powerpc/platforms/iseries/vio.c new file mode 100644 index 00000000..b6db7cef --- /dev/null +++ b/arch/powerpc/platforms/iseries/vio.c @@ -0,0 +1,556 @@ +/* + * 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/slab.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 = kstrdup(path, GFP_KERNEL); + if (!np->full_name) { + kfree(np); + return NULL; + } + 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; + + init_completion(&we.com); + + 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); diff --git a/arch/powerpc/platforms/iseries/viopath.c b/arch/powerpc/platforms/iseries/viopath.c new file mode 100644 index 00000000..2376069c --- /dev/null +++ b/arch/powerpc/platforms/iseries/viopath.c @@ -0,0 +1,677 @@ +/* -*- linux-c -*- + * + * iSeries Virtual I/O Message Path code + * + * Authors: Dave Boutcher <boutcher@us.ibm.com> + * Ryan Arnold <ryanarn@us.ibm.com> + * Colin Devilbiss <devilbis@us.ibm.com> + * + * (C) Copyright 2000-2005 IBM Corporation + * + * This code is used by the iSeries virtual disk, cd, + * tape, and console to communicate with OS/400 in another + * partition. + * + * 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) anyu 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/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/vmalloc.h> +#include <linux/string.h> +#include <linux/proc_fs.h> +#include <linux/dma-mapping.h> +#include <linux/wait.h> +#include <linux/seq_file.h> +#include <linux/interrupt.h> +#include <linux/completion.h> + +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/prom.h> +#include <asm/firmware.h> +#include <asm/iseries/hv_types.h> +#include <asm/iseries/hv_lp_event.h> +#include <asm/iseries/hv_lp_config.h> +#include <asm/iseries/mf.h> +#include <asm/iseries/vio.h> + +/* Status of the path to each other partition in the system. + * This is overkill, since we will only ever establish connections + * to our hosting partition and the primary partition on the system. + * But this allows for other support in the future. + */ +static struct viopathStatus { + int isOpen; /* Did we open the path? */ + int isActive; /* Do we have a mon msg outstanding */ + int users[VIO_MAX_SUBTYPES]; + HvLpInstanceId mSourceInst; + HvLpInstanceId mTargetInst; + int numberAllocated; +} viopathStatus[HVMAXARCHITECTEDLPS]; + +static DEFINE_SPINLOCK(statuslock); + +/* + * For each kind of event we allocate a buffer that is + * guaranteed not to cross a page boundary + */ +static unsigned char event_buffer[VIO_MAX_SUBTYPES * 256] + __attribute__((__aligned__(4096))); +static atomic_t event_buffer_available[VIO_MAX_SUBTYPES]; +static int event_buffer_initialised; + +static void handleMonitorEvent(struct HvLpEvent *event); + +/* + * We use this structure to handle asynchronous responses. The caller + * blocks on the semaphore and the handler posts the semaphore. However, + * if system_state is not SYSTEM_RUNNING, then wait_atomic is used ... + */ +struct alloc_parms { + struct completion done; + int number; + atomic_t wait_atomic; + int used_wait_atomic; +}; + +/* Put a sequence number in each mon msg. The value is not + * important. Start at something other than 0 just for + * readability. wrapping this is ok. + */ +static u8 viomonseq = 22; + +/* Our hosting logical partition. We get this at startup + * time, and different modules access this variable directly. + */ +HvLpIndex viopath_hostLp = HvLpIndexInvalid; +EXPORT_SYMBOL(viopath_hostLp); +HvLpIndex viopath_ourLp = HvLpIndexInvalid; +EXPORT_SYMBOL(viopath_ourLp); + +/* For each kind of incoming event we set a pointer to a + * routine to call. + */ +static vio_event_handler_t *vio_handler[VIO_MAX_SUBTYPES]; + +#define VIOPATH_KERN_WARN KERN_WARNING "viopath: " +#define VIOPATH_KERN_INFO KERN_INFO "viopath: " + +static int proc_viopath_show(struct seq_file *m, void *v) +{ + char *buf; + u16 vlanMap; + dma_addr_t handle; + HvLpEvent_Rc hvrc; + DECLARE_COMPLETION_ONSTACK(done); + struct device_node *node; + const char *sysid; + + buf = kzalloc(HW_PAGE_SIZE, GFP_KERNEL); + if (!buf) + return 0; + + handle = iseries_hv_map(buf, HW_PAGE_SIZE, DMA_FROM_DEVICE); + + hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp, + HvLpEvent_Type_VirtualIo, + viomajorsubtype_config | vioconfigget, + HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck, + viopath_sourceinst(viopath_hostLp), + viopath_targetinst(viopath_hostLp), + (u64)(unsigned long)&done, VIOVERSION << 16, + ((u64)handle) << 32, HW_PAGE_SIZE, 0, 0); + + if (hvrc != HvLpEvent_Rc_Good) + printk(VIOPATH_KERN_WARN "hv error on op %d\n", (int)hvrc); + + wait_for_completion(&done); + + vlanMap = HvLpConfig_getVirtualLanIndexMap(); + + buf[HW_PAGE_SIZE-1] = '\0'; + seq_printf(m, "%s", buf); + + iseries_hv_unmap(handle, HW_PAGE_SIZE, DMA_FROM_DEVICE); + kfree(buf); + + seq_printf(m, "AVAILABLE_VETH=%x\n", vlanMap); + + node = of_find_node_by_path("/"); + sysid = NULL; + if (node != NULL) + sysid = of_get_property(node, "system-id", NULL); + + if (sysid == NULL) + seq_printf(m, "SRLNBR=<UNKNOWN>\n"); + else + /* Skip "IBM," on front of serial number, see dt.c */ + seq_printf(m, "SRLNBR=%s\n", sysid + 4); + + of_node_put(node); + + return 0; +} + +static int proc_viopath_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_viopath_show, NULL); +} + +static const struct file_operations proc_viopath_operations = { + .open = proc_viopath_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init vio_proc_init(void) +{ + if (!firmware_has_feature(FW_FEATURE_ISERIES)) + return 0; + + proc_create("iSeries/config", 0, NULL, &proc_viopath_operations); + return 0; +} +__initcall(vio_proc_init); + +/* See if a given LP is active. Allow for invalid lps to be passed in + * and just return invalid + */ +int viopath_isactive(HvLpIndex lp) +{ + if (lp == HvLpIndexInvalid) + return 0; + if (lp < HVMAXARCHITECTEDLPS) + return viopathStatus[lp].isActive; + else + return 0; +} +EXPORT_SYMBOL(viopath_isactive); + +/* + * We cache the source and target instance ids for each + * partition. + */ +HvLpInstanceId viopath_sourceinst(HvLpIndex lp) +{ + return viopathStatus[lp].mSourceInst; +} +EXPORT_SYMBOL(viopath_sourceinst); + +HvLpInstanceId viopath_targetinst(HvLpIndex lp) +{ + return viopathStatus[lp].mTargetInst; +} +EXPORT_SYMBOL(viopath_targetinst); + +/* + * Send a monitor message. This is a message with the acknowledge + * bit on that the other side will NOT explicitly acknowledge. When + * the other side goes down, the hypervisor will acknowledge any + * outstanding messages....so we will know when the other side dies. + */ +static void sendMonMsg(HvLpIndex remoteLp) +{ + HvLpEvent_Rc hvrc; + + viopathStatus[remoteLp].mSourceInst = + HvCallEvent_getSourceLpInstanceId(remoteLp, + HvLpEvent_Type_VirtualIo); + viopathStatus[remoteLp].mTargetInst = + HvCallEvent_getTargetLpInstanceId(remoteLp, + HvLpEvent_Type_VirtualIo); + + /* + * Deliberately ignore the return code here. if we call this + * more than once, we don't care. + */ + vio_setHandler(viomajorsubtype_monitor, handleMonitorEvent); + + hvrc = HvCallEvent_signalLpEventFast(remoteLp, HvLpEvent_Type_VirtualIo, + viomajorsubtype_monitor, HvLpEvent_AckInd_DoAck, + HvLpEvent_AckType_DeferredAck, + viopathStatus[remoteLp].mSourceInst, + viopathStatus[remoteLp].mTargetInst, + viomonseq++, 0, 0, 0, 0, 0); + + if (hvrc == HvLpEvent_Rc_Good) + viopathStatus[remoteLp].isActive = 1; + else { + printk(VIOPATH_KERN_WARN "could not connect to partition %d\n", + remoteLp); + viopathStatus[remoteLp].isActive = 0; + } +} + +static void handleMonitorEvent(struct HvLpEvent *event) +{ + HvLpIndex remoteLp; + int i; + + /* + * This handler is _also_ called as part of the loop + * at the end of this routine, so it must be able to + * ignore NULL events... + */ + if (!event) + return; + + /* + * First see if this is just a normal monitor message from the + * other partition + */ + if (hvlpevent_is_int(event)) { + remoteLp = event->xSourceLp; + if (!viopathStatus[remoteLp].isActive) + sendMonMsg(remoteLp); + return; + } + + /* + * This path is for an acknowledgement; the other partition + * died + */ + remoteLp = event->xTargetLp; + if ((event->xSourceInstanceId != viopathStatus[remoteLp].mSourceInst) || + (event->xTargetInstanceId != viopathStatus[remoteLp].mTargetInst)) { + printk(VIOPATH_KERN_WARN "ignoring ack....mismatched instances\n"); + return; + } + + printk(VIOPATH_KERN_WARN "partition %d ended\n", remoteLp); + + viopathStatus[remoteLp].isActive = 0; + + /* + * For each active handler, pass them a NULL + * message to indicate that the other partition + * died + */ + for (i = 0; i < VIO_MAX_SUBTYPES; i++) { + if (vio_handler[i] != NULL) + (*vio_handler[i])(NULL); + } +} + +int vio_setHandler(int subtype, vio_event_handler_t *beh) +{ + subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; + if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) + return -EINVAL; + if (vio_handler[subtype] != NULL) + return -EBUSY; + vio_handler[subtype] = beh; + return 0; +} +EXPORT_SYMBOL(vio_setHandler); + +int vio_clearHandler(int subtype) +{ + subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; + if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) + return -EINVAL; + if (vio_handler[subtype] == NULL) + return -EAGAIN; + vio_handler[subtype] = NULL; + return 0; +} +EXPORT_SYMBOL(vio_clearHandler); + +static void handleConfig(struct HvLpEvent *event) +{ + if (!event) + return; + if (hvlpevent_is_int(event)) { + printk(VIOPATH_KERN_WARN + "unexpected config request from partition %d", + event->xSourceLp); + + if (hvlpevent_need_ack(event)) { + event->xRc = HvLpEvent_Rc_InvalidSubtype; + HvCallEvent_ackLpEvent(event); + } + return; + } + + complete((struct completion *)event->xCorrelationToken); +} + +/* + * Initialization of the hosting partition + */ +void vio_set_hostlp(void) +{ + /* + * If this has already been set then we DON'T want to either change + * it or re-register the proc file system + */ + if (viopath_hostLp != HvLpIndexInvalid) + return; + + /* + * Figure out our hosting partition. This isn't allowed to change + * while we're active + */ + viopath_ourLp = HvLpConfig_getLpIndex(); + viopath_hostLp = HvLpConfig_getHostingLpIndex(viopath_ourLp); + + if (viopath_hostLp != HvLpIndexInvalid) + vio_setHandler(viomajorsubtype_config, handleConfig); +} +EXPORT_SYMBOL(vio_set_hostlp); + +static void vio_handleEvent(struct HvLpEvent *event) +{ + HvLpIndex remoteLp; + int subtype = (event->xSubtype & VIOMAJOR_SUBTYPE_MASK) + >> VIOMAJOR_SUBTYPE_SHIFT; + + if (hvlpevent_is_int(event)) { + remoteLp = event->xSourceLp; + /* + * The isActive is checked because if the hosting partition + * went down and came back up it would not be active but it + * would have different source and target instances, in which + * case we'd want to reset them. This case really protects + * against an unauthorized active partition sending interrupts + * or acks to this linux partition. + */ + if (viopathStatus[remoteLp].isActive + && (event->xSourceInstanceId != + viopathStatus[remoteLp].mTargetInst)) { + printk(VIOPATH_KERN_WARN + "message from invalid partition. " + "int msg rcvd, source inst (%d) doesn't match (%d)\n", + viopathStatus[remoteLp].mTargetInst, + event->xSourceInstanceId); + return; + } + + if (viopathStatus[remoteLp].isActive + && (event->xTargetInstanceId != + viopathStatus[remoteLp].mSourceInst)) { + printk(VIOPATH_KERN_WARN + "message from invalid partition. " + "int msg rcvd, target inst (%d) doesn't match (%d)\n", + viopathStatus[remoteLp].mSourceInst, + event->xTargetInstanceId); + return; + } + } else { + remoteLp = event->xTargetLp; + if (event->xSourceInstanceId != + viopathStatus[remoteLp].mSourceInst) { + printk(VIOPATH_KERN_WARN + "message from invalid partition. " + "ack msg rcvd, source inst (%d) doesn't match (%d)\n", + viopathStatus[remoteLp].mSourceInst, + event->xSourceInstanceId); + return; + } + + if (event->xTargetInstanceId != + viopathStatus[remoteLp].mTargetInst) { + printk(VIOPATH_KERN_WARN + "message from invalid partition. " + "viopath: ack msg rcvd, target inst (%d) doesn't match (%d)\n", + viopathStatus[remoteLp].mTargetInst, + event->xTargetInstanceId); + return; + } + } + + if (vio_handler[subtype] == NULL) { + printk(VIOPATH_KERN_WARN + "unexpected virtual io event subtype %d from partition %d\n", + event->xSubtype, remoteLp); + /* No handler. Ack if necessary */ + if (hvlpevent_is_int(event) && hvlpevent_need_ack(event)) { + event->xRc = HvLpEvent_Rc_InvalidSubtype; + HvCallEvent_ackLpEvent(event); + } + return; + } + + /* This innocuous little line is where all the real work happens */ + (*vio_handler[subtype])(event); +} + +static void viopath_donealloc(void *parm, int number) +{ + struct alloc_parms *parmsp = parm; + + parmsp->number = number; + if (parmsp->used_wait_atomic) + atomic_set(&parmsp->wait_atomic, 0); + else + complete(&parmsp->done); +} + +static int allocateEvents(HvLpIndex remoteLp, int numEvents) +{ + struct alloc_parms parms; + + if (system_state != SYSTEM_RUNNING) { + parms.used_wait_atomic = 1; + atomic_set(&parms.wait_atomic, 1); + } else { + parms.used_wait_atomic = 0; + init_completion(&parms.done); + } + mf_allocate_lp_events(remoteLp, HvLpEvent_Type_VirtualIo, 250, /* It would be nice to put a real number here! */ + numEvents, &viopath_donealloc, &parms); + if (system_state != SYSTEM_RUNNING) { + while (atomic_read(&parms.wait_atomic)) + mb(); + } else + wait_for_completion(&parms.done); + return parms.number; +} + +int viopath_open(HvLpIndex remoteLp, int subtype, int numReq) +{ + int i; + unsigned long flags; + int tempNumAllocated; + + if ((remoteLp >= HVMAXARCHITECTEDLPS) || (remoteLp == HvLpIndexInvalid)) + return -EINVAL; + + subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; + if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) + return -EINVAL; + + spin_lock_irqsave(&statuslock, flags); + + if (!event_buffer_initialised) { + for (i = 0; i < VIO_MAX_SUBTYPES; i++) + atomic_set(&event_buffer_available[i], 1); + event_buffer_initialised = 1; + } + + viopathStatus[remoteLp].users[subtype]++; + + if (!viopathStatus[remoteLp].isOpen) { + viopathStatus[remoteLp].isOpen = 1; + HvCallEvent_openLpEventPath(remoteLp, HvLpEvent_Type_VirtualIo); + + /* + * Don't hold the spinlock during an operation that + * can sleep. + */ + spin_unlock_irqrestore(&statuslock, flags); + tempNumAllocated = allocateEvents(remoteLp, 1); + spin_lock_irqsave(&statuslock, flags); + + viopathStatus[remoteLp].numberAllocated += tempNumAllocated; + + if (viopathStatus[remoteLp].numberAllocated == 0) { + HvCallEvent_closeLpEventPath(remoteLp, + HvLpEvent_Type_VirtualIo); + + spin_unlock_irqrestore(&statuslock, flags); + return -ENOMEM; + } + + viopathStatus[remoteLp].mSourceInst = + HvCallEvent_getSourceLpInstanceId(remoteLp, + HvLpEvent_Type_VirtualIo); + viopathStatus[remoteLp].mTargetInst = + HvCallEvent_getTargetLpInstanceId(remoteLp, + HvLpEvent_Type_VirtualIo); + HvLpEvent_registerHandler(HvLpEvent_Type_VirtualIo, + &vio_handleEvent); + sendMonMsg(remoteLp); + printk(VIOPATH_KERN_INFO "opening connection to partition %d, " + "setting sinst %d, tinst %d\n", + remoteLp, viopathStatus[remoteLp].mSourceInst, + viopathStatus[remoteLp].mTargetInst); + } + + spin_unlock_irqrestore(&statuslock, flags); + tempNumAllocated = allocateEvents(remoteLp, numReq); + spin_lock_irqsave(&statuslock, flags); + viopathStatus[remoteLp].numberAllocated += tempNumAllocated; + spin_unlock_irqrestore(&statuslock, flags); + + return 0; +} +EXPORT_SYMBOL(viopath_open); + +int viopath_close(HvLpIndex remoteLp, int subtype, int numReq) +{ + unsigned long flags; + int i; + int numOpen; + struct alloc_parms parms; + + if ((remoteLp >= HVMAXARCHITECTEDLPS) || (remoteLp == HvLpIndexInvalid)) + return -EINVAL; + + subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; + if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) + return -EINVAL; + + spin_lock_irqsave(&statuslock, flags); + /* + * If the viopath_close somehow gets called before a + * viopath_open it could decrement to -1 which is a non + * recoverable state so we'll prevent this from + * happening. + */ + if (viopathStatus[remoteLp].users[subtype] > 0) + viopathStatus[remoteLp].users[subtype]--; + + spin_unlock_irqrestore(&statuslock, flags); + + parms.used_wait_atomic = 0; + init_completion(&parms.done); + mf_deallocate_lp_events(remoteLp, HvLpEvent_Type_VirtualIo, + numReq, &viopath_donealloc, &parms); + wait_for_completion(&parms.done); + + spin_lock_irqsave(&statuslock, flags); + for (i = 0, numOpen = 0; i < VIO_MAX_SUBTYPES; i++) + numOpen += viopathStatus[remoteLp].users[i]; + + if ((viopathStatus[remoteLp].isOpen) && (numOpen == 0)) { + printk(VIOPATH_KERN_INFO "closing connection to partition %d\n", + remoteLp); + + HvCallEvent_closeLpEventPath(remoteLp, + HvLpEvent_Type_VirtualIo); + viopathStatus[remoteLp].isOpen = 0; + viopathStatus[remoteLp].isActive = 0; + + for (i = 0; i < VIO_MAX_SUBTYPES; i++) + atomic_set(&event_buffer_available[i], 0); + event_buffer_initialised = 0; + } + spin_unlock_irqrestore(&statuslock, flags); + return 0; +} +EXPORT_SYMBOL(viopath_close); + +void *vio_get_event_buffer(int subtype) +{ + subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; + if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) + return NULL; + + if (atomic_dec_if_positive(&event_buffer_available[subtype]) == 0) + return &event_buffer[subtype * 256]; + else + return NULL; +} +EXPORT_SYMBOL(vio_get_event_buffer); + +void vio_free_event_buffer(int subtype, void *buffer) +{ + subtype = subtype >> VIOMAJOR_SUBTYPE_SHIFT; + if ((subtype < 0) || (subtype >= VIO_MAX_SUBTYPES)) { + printk(VIOPATH_KERN_WARN + "unexpected subtype %d freeing event buffer\n", subtype); + return; + } + + if (atomic_read(&event_buffer_available[subtype]) != 0) { + printk(VIOPATH_KERN_WARN + "freeing unallocated event buffer, subtype %d\n", + subtype); + return; + } + + if (buffer != &event_buffer[subtype * 256]) { + printk(VIOPATH_KERN_WARN + "freeing invalid event buffer, subtype %d\n", subtype); + } + + atomic_set(&event_buffer_available[subtype], 1); +} +EXPORT_SYMBOL(vio_free_event_buffer); + +static const struct vio_error_entry vio_no_error = + { 0, 0, "Non-VIO Error" }; +static const struct vio_error_entry vio_unknown_error = + { 0, EIO, "Unknown Error" }; + +static const struct vio_error_entry vio_default_errors[] = { + {0x0001, EIO, "No Connection"}, + {0x0002, EIO, "No Receiver"}, + {0x0003, EIO, "No Buffer Available"}, + {0x0004, EBADRQC, "Invalid Message Type"}, + {0x0000, 0, NULL}, +}; + +const struct vio_error_entry *vio_lookup_rc( + const struct vio_error_entry *local_table, u16 rc) +{ + const struct vio_error_entry *cur; + + if (!rc) + return &vio_no_error; + if (local_table) + for (cur = local_table; cur->rc; ++cur) + if (cur->rc == rc) + return cur; + for (cur = vio_default_errors; cur->rc; ++cur) + if (cur->rc == rc) + return cur; + return &vio_unknown_error; +} +EXPORT_SYMBOL(vio_lookup_rc); diff --git a/arch/powerpc/platforms/iseries/vpd_areas.h b/arch/powerpc/platforms/iseries/vpd_areas.h new file mode 100644 index 00000000..feb001f3 --- /dev/null +++ b/arch/powerpc/platforms/iseries/vpd_areas.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2001 Mike Corrigan IBM Corporation + * + * 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 + */ +#ifndef _ISERIES_VPD_AREAS_H +#define _ISERIES_VPD_AREAS_H + +/* + * This file defines the address and length of all of the VPD area passed to + * the OS from PLIC (most of which start from the SP). + */ + +#include <asm/types.h> + +/* VPD Entry index is carved in stone - cannot be changed (easily). */ +#define ItVpdCecVpd 0 +#define ItVpdDynamicSpace 1 +#define ItVpdExtVpd 2 +#define ItVpdExtVpdOnPanel 3 +#define ItVpdFirstPaca 4 +#define ItVpdIoVpd 5 +#define ItVpdIplParms 6 +#define ItVpdMsVpd 7 +#define ItVpdPanelVpd 8 +#define ItVpdLpNaca 9 +#define ItVpdBackplaneAndMaybeClockCardVpd 10 +#define ItVpdRecoveryLogBuffer 11 +#define ItVpdSpCommArea 12 +#define ItVpdSpLogBuffer 13 +#define ItVpdSpLogBufferSave 14 +#define ItVpdSpCardVpd 15 +#define ItVpdFirstProcVpd 16 +#define ItVpdApModelVpd 17 +#define ItVpdClockCardVpd 18 +#define ItVpdBusExtCardVpd 19 +#define ItVpdProcCapacityVpd 20 +#define ItVpdInteractiveCapacityVpd 21 +#define ItVpdFirstSlotLabel 22 +#define ItVpdFirstLpQueue 23 +#define ItVpdFirstL3CacheVpd 24 +#define ItVpdFirstProcFruVpd 25 + +#define ItVpdMaxEntries 26 + +#define ItDmaMaxEntries 10 + +#define ItVpdAreasMaxSlotLabels 192 + + +struct ItVpdAreas { + u32 xSlicDesc; // Descriptor 000-003 + u16 xSlicSize; // Size of this control block 004-005 + u16 xPlicAdjustVpdLens:1; // Flag to indicate new interface006-007 + u16 xRsvd1:15; // Reserved bits ... + u16 xSlicVpdEntries; // Number of VPD entries 008-009 + u16 xSlicDmaEntries; // Number of DMA entries 00A-00B + u16 xSlicMaxLogicalProcs; // Maximum logical processors 00C-00D + u16 xSlicMaxPhysicalProcs; // Maximum physical processors 00E-00F + u16 xSlicDmaToksOffset; // Offset into this of array 010-011 + u16 xSlicVpdAdrsOffset; // Offset into this of array 012-013 + u16 xSlicDmaLensOffset; // Offset into this of array 014-015 + u16 xSlicVpdLensOffset; // Offset into this of array 016-017 + u16 xSlicMaxSlotLabels; // Maximum number of slot labels018-019 + u16 xSlicMaxLpQueues; // Maximum number of LP Queues 01A-01B + u8 xRsvd2[4]; // Reserved 01C-01F + u64 xRsvd3[12]; // Reserved 020-07F + u32 xPlicDmaLens[ItDmaMaxEntries];// Array of DMA lengths 080-0A7 + u32 xPlicDmaToks[ItDmaMaxEntries];// Array of DMA tokens 0A8-0CF + u32 xSlicVpdLens[ItVpdMaxEntries];// Array of VPD lengths 0D0-12F + const void *xSlicVpdAdrs[ItVpdMaxEntries];// Array of VPD buffers 130-1EF +}; + +extern const struct ItVpdAreas itVpdAreas; + +#endif /* _ISERIES_VPD_AREAS_H */ |