Commit 8ee3e0d6 authored by Stephen Rothwell's avatar Stephen Rothwell Committed by Benjamin Herrenschmidt

powerpc: Remove the main legacy iSerie platform code

Signed-off-by: default avatarStephen Rothwell <sfr@canb.auug.org.au>
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
parent 2d4b9712
......@@ -16,7 +16,6 @@ obj-$(CONFIG_FSL_SOC_BOOKE) += 85xx/
obj-$(CONFIG_PPC_86xx) += 86xx/
obj-$(CONFIG_PPC_POWERNV) += powernv/
obj-$(CONFIG_PPC_PSERIES) += pseries/
obj-$(CONFIG_PPC_ISERIES) += iseries/
obj-$(CONFIG_PPC_MAPLE) += maple/
obj-$(CONFIG_PPC_PASEMI) += pasemi/
obj-$(CONFIG_PPC_CELL) += cell/
......
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
/*
* 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 */
/*
* 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 */
/*
* 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 */
/*
* 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;
}
/*
* 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, NOTEST, 0)
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
#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, NOTEST, 0); \
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, NOTEST, 0); \
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 */
/*
* 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;
}
/*
* 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
/*
* 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;
}
}
/*
* 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/export.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);
/*
* 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/export.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);
}
/*
* 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 */
/*
* 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 */
#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 */
/*
* 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 */
/*
* 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 */
/*
* (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/export.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);
/*
* 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/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
}
};
/*
* 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/export.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);
/*
* 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 */
/*
* 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/export.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);
}
/*
* 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
#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 */
/*
* 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/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();
}
#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 */
/*
* 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);
/*
* 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 */
/*
* 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 */
/*
* 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/export.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_idle_enter();
rcu_idle_enter();
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();
rcu_idle_exit();
tick_nohz_idle_exit();
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_idle_enter();
rcu_idle_enter();
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();
rcu_idle_exit();
tick_nohz_idle_exit();
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;
}
/*
* 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__ */
/*
* 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/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/device.h>
#include <linux/cpu.h>
#include <asm/ptrace.h>
#include <linux/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 = NULL, /* Use 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;
}
/*
* 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 */
/*
* 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/export.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), &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);
/* -*- 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/export.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);
/*
* 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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment