提交 66a77ea6 编写于 作者: P Peter Maydell

Merge remote-tracking branch 'remotes/dgibson/tags/ppc-for-2.8-20161028' into staging

ppc patch queue 2016-10-28

This pull request supersedes and extends the one from 2016-10-26
(which had a build bug).

Highlights:
  * SLOF (pseries guest firmware) update
  * Enable a number of extra testcases on ppc / pseries
  * Added the 'powernv' machine type
    - Almost enough to be minimally usable
    - But still missing necessary interrupt controller updates
  * Cleanup and consolidation of NVRAM handling on several platforms
    with related firmware
  * Substantial cleanup to device tree construction
  * Some more POWER9 instruction emulation
  * Cleanup to handling of pseries option vectors and CAS reboot
    handling (host/guest feature negotiation mechanism)
  * Significant cleanups to handling of PCI devices in test cases
  * New hotplug event infrastructure
  * Memory hot unplug support for pseries
  * Several bug fixes

The NVRAM cleanup affects some Sun sparc platforms as well as ppc
ones, but have been tested by the sparc maintainer (Mark Cave-Ayland).

The test additions also include substantial general changes to the
test framework that aren't strictly ppc related.  They don't seem to
break tests on other platforms, they're for the benefit of enabling
tests on ppc and there isn't a specific maintainer for them, so
they're included in this tree.

# gpg: Signature made Fri 28 Oct 2016 02:37:19 BST
# gpg:                using RSA key 0x6C38CACA20D9B392
# gpg: Good signature from "David Gibson <david@gibson.dropbear.id.au>"
# gpg:                 aka "David Gibson (Red Hat) <dgibson@redhat.com>"
# gpg:                 aka "David Gibson (ozlabs.org) <dgibson@ozlabs.org>"
# gpg:                 aka "David Gibson (kernel.org) <dwg@kernel.org>"
# Primary key fingerprint: 75F4 6586 AE61 A66C C44E  87DC 6C38 CACA 20D9 B392

* remotes/dgibson/tags/ppc-for-2.8-20161028: (73 commits)
  ppc: allow certain HV interrupts to be delivered to guests
  spapr: Memory hot-unplug support
  spapr: use count+index for memory hotplug
  spapr: Add DRC count indexed hotplug identifier type
  spapr: add hotplug interrupt machine options
  spapr_events: add support for dedicated hotplug event source
  spapr: update spapr hotplug documentation
  target-ppc: Add xvcmpnesp, xvcmpnedp instructions
  target-ppc: add xscmp[eq,gt,ge,ne]dp instructions
  tests: Add pseries machine to the prom-env-test, too
  spapr_nvram: Pre-initialize the NVRAM to support the -prom-env parameter
  libqos: Change PCI accessors to take opaque BAR handle
  tests: Don't assume structure of PCI IO base in ahci-test
  tests: Use qpci_mem{read,write} in ivshmem-test
  libqos: Add 64-bit PCI IO accessors
  tests: Clean up IO handling in ide-test
  libqos: Implement mmio accessors in terms of mem{read,write}
  libqos: Add streaming accessors for PCI MMIO
  tests: Adjust tco-test to use qpci_legacy_iomap()
  libqos: Better handling of PCI legacy IO
  ...
Signed-off-by: NPeter Maydell <peter.maydell@linaro.org>
......@@ -31,3 +31,6 @@
[submodule "roms/u-boot"]
path = roms/u-boot
url = git://git.qemu-project.org/u-boot.git
[submodule "roms/skiboot"]
path = roms/skiboot
url = git://git.qemu.org/skiboot.git
......@@ -656,6 +656,7 @@ F: include/hw/*/xics*
F: pc-bios/spapr-rtas/*
F: pc-bios/spapr-rtas.bin
F: pc-bios/slof.bin
F: pc-bios/skiboot.lid
F: docs/specs/ppc-spapr-hcalls.txt
F: docs/specs/ppc-spapr-hotplug.txt
F: tests/spapr*
......
......@@ -421,7 +421,7 @@ qemu-icon.bmp qemu_logo_no_text.svg \
bamboo.dtb petalogix-s3adsp1800.dtb petalogix-ml605.dtb \
multiboot.bin linuxboot.bin linuxboot_dma.bin kvmvapic.bin \
s390-ccw.img \
spapr-rtas.bin slof.bin \
spapr-rtas.bin slof.bin skiboot.lid \
palcode-clipper \
u-boot.e500
else
......
......@@ -6131,6 +6131,7 @@ FILES="$FILES roms/seabios/Makefile roms/vgabios/Makefile"
FILES="$FILES pc-bios/qemu-icon.bmp"
for bios_file in \
$source_path/pc-bios/*.bin \
$source_path/pc-bios/*.lid \
$source_path/pc-bios/*.aml \
$source_path/pc-bios/*.rom \
$source_path/pc-bios/*.dtb \
......
......@@ -39,6 +39,7 @@ CONFIG_I8259=y
CONFIG_XILINX=y
CONFIG_XILINX_ETHLITE=y
CONFIG_PSERIES=y
CONFIG_POWERNV=y
CONFIG_PREP=y
CONFIG_MAC=y
CONFIG_E500=y
......
......@@ -233,12 +233,27 @@ tools by host-level management such as an HMC. This level of management is not
applicable to PowerKVM, hence the reason for extending the notification
framework to support hotplug events.
Note that these events are not yet formally part of the PAPR+ specification,
but support for this format has already been implemented in DR-related
guest tools such as powerpc-utils/librtas, as well as kernel patches that have
been submitted to handle in-kernel processing of memory/cpu-related hotplug
events[1], and is planned for formal inclusion is PAPR+ specification. The
hotplug-specific payload is QEMU implemented as follows (with all values
The format for these EPOW-signalled events is described below under
"hotplug/unplug event structure". Note that these events are not
formally part of the PAPR+ specification, and have been superseded by a
newer format, also described below under "hotplug/unplug event structure",
and so are now deemed a "legacy" format. The formats are similar, but the
"modern" format contains additional fields/flags, which are denoted for the
purposes of this documentation with "#ifdef GUEST_SUPPORTS_MODERN" guards.
QEMU should assume support only for "legacy" fields/flags unless the guest
advertises support for the "modern" format via ibm,client-architecture-support
hcall by setting byte 5, bit 6 of it's ibm,architecture-vec-5 option vector
structure (as described by LoPAPR v11, B.6.2.3). As with "legacy" format events,
"modern" format events are surfaced to the guest via check-exception RTAS calls,
but use a dedicated event source to signal the guest. This event source is
advertised to the guest by the addition of a "hot-plug-events" node under
"/event-sources" node of the guest's device tree using the standard format
described in LoPAPR v11, B.6.12.1.
== hotplug/unplug event structure ==
The hotplug-specific payload in QEMU is implemented as follows (with all values
encoded in big-endian format):
struct rtas_event_log_v6_hp {
......@@ -263,14 +278,23 @@ struct rtas_event_log_v6_hp {
#define RTAS_LOG_V6_HP_ACTION_ADD 1
#define RTAS_LOG_V6_HP_ACTION_REMOVE 2
uint8_t hotplug_action; /* action (add/remove) */
#define RTAS_LOG_V6_HP_ID_DRC_NAME 1
#define RTAS_LOG_V6_HP_ID_DRC_INDEX 2
#define RTAS_LOG_V6_HP_ID_DRC_COUNT 3
#define RTAS_LOG_V6_HP_ID_DRC_NAME 1
#define RTAS_LOG_V6_HP_ID_DRC_INDEX 2
#define RTAS_LOG_V6_HP_ID_DRC_COUNT 3
#ifdef GUEST_SUPPORTS_MODERN
#define RTAS_LOG_V6_HP_ID_DRC_COUNT_INDEXED 4
#endif
uint8_t hotplug_identifier; /* type of the resource identifier,
* which serves as the discriminator
* for the 'drc' union field below
*/
#ifdef GUEST_SUPPORTS_MODERN
uint8_t capabilities; /* capability flags, currently unused
* by QEMU
*/
#else
uint8_t reserved;
#endif
union {
uint32_t index; /* DRC index of resource to take action
* on
......@@ -278,6 +302,19 @@ struct rtas_event_log_v6_hp {
uint32_t count; /* number of DR resources to take
* action on (guest chooses which)
*/
#ifdef GUEST_SUPPORTS_MODERN
struct {
uint32_t count; /* number of DR resources to take
* action on
*/
uint32_t index; /* DRC index of first resource to take
* action on. guest will take action
* on DRC index <index> through
* DRC index <index + count - 1> in
* sequential order
*/
} count_indexed;
#endif
char name[1]; /* string representing the name of the
* DRC to take action on
*/
......
......@@ -396,9 +396,15 @@ static int adb_kbd_request(ADBDevice *d, uint8_t *obuf,
d->devaddr = buf[1] & 0xf;
break;
default:
/* XXX: check this */
d->devaddr = buf[1] & 0xf;
d->handler = buf[2];
/* we support handlers:
* 1: Apple Standard Keyboard
* 2: Apple Extended Keyboard (LShift = RShift)
* 3: Apple Extended Keyboard (LShift != RShift)
*/
if (buf[2] == 1 || buf[2] == 2 || buf[2] == 3) {
d->handler = buf[2];
}
break;
}
}
......@@ -437,6 +443,7 @@ static void adb_keyboard_event(DeviceState *dev, QemuConsole *src,
if (qcode >= ARRAY_SIZE(qcode_to_adb_keycode)) {
return;
}
/* FIXME: take handler into account when translating qcode */
keycode = qcode_to_adb_keycode[qcode];
if (keycode == NO_KEY) { /* We don't want to send this to the guest */
ADB_DPRINTF("Ignoring NO_KEY\n");
......@@ -631,8 +638,21 @@ static int adb_mouse_request(ADBDevice *d, uint8_t *obuf,
d->devaddr = buf[1] & 0xf;
break;
default:
/* XXX: check this */
d->devaddr = buf[1] & 0xf;
/* we support handlers:
* 0x01: Classic Apple Mouse Protocol / 100 cpi operations
* 0x02: Classic Apple Mouse Protocol / 200 cpi operations
* we don't support handlers (at least):
* 0x03: Mouse systems A3 trackball
* 0x04: Extended Apple Mouse Protocol
* 0x2f: Microspeed mouse
* 0x42: Macally
* 0x5f: Microspeed mouse
* 0x66: Microspeed mouse
*/
if (buf[2] == 1 || buf[2] == 2) {
d->handler = buf[2];
}
break;
}
}
......
......@@ -35,6 +35,8 @@
#include "hw/ppc/xics.h"
#include "qemu/error-report.h"
#include "qapi/visitor.h"
#include "monitor/monitor.h"
#include "hw/intc/intc.h"
int xics_get_cpu_index_by_dt_id(int cpu_dt_id)
{
......@@ -90,6 +92,47 @@ void xics_cpu_setup(XICSState *xics, PowerPCCPU *cpu)
}
}
static void xics_common_pic_print_info(InterruptStatsProvider *obj,
Monitor *mon)
{
XICSState *xics = XICS_COMMON(obj);
ICSState *ics;
uint32_t i;
for (i = 0; i < xics->nr_servers; i++) {
ICPState *icp = &xics->ss[i];
if (!icp->output) {
continue;
}
monitor_printf(mon, "CPU %d XIRR=%08x (%p) PP=%02x MFRR=%02x\n",
i, icp->xirr, icp->xirr_owner,
icp->pending_priority, icp->mfrr);
}
QLIST_FOREACH(ics, &xics->ics, list) {
monitor_printf(mon, "ICS %4x..%4x %p\n",
ics->offset, ics->offset + ics->nr_irqs - 1, ics);
if (!ics->irqs) {
continue;
}
for (i = 0; i < ics->nr_irqs; i++) {
ICSIRQState *irq = ics->irqs + i;
if (!(irq->flags & XICS_FLAGS_IRQ_MASK)) {
continue;
}
monitor_printf(mon, " %4x %s %02x %02x\n",
ics->offset + i,
(irq->flags & XICS_FLAGS_IRQ_LSI) ?
"LSI" : "MSI",
irq->priority, irq->status);
}
}
}
/*
* XICS Common class - parent for emulated XICS and KVM-XICS
*/
......@@ -140,6 +183,25 @@ static void xics_prop_set_nr_irqs(Object *obj, Visitor *v, const char *name,
info->set_nr_irqs(xics, value, errp);
}
void xics_set_nr_servers(XICSState *xics, uint32_t nr_servers,
const char *typename, Error **errp)
{
int i;
xics->nr_servers = nr_servers;
xics->ss = g_malloc0(xics->nr_servers * sizeof(ICPState));
for (i = 0; i < xics->nr_servers; i++) {
char name[32];
ICPState *icp = &xics->ss[i];
object_initialize(icp, sizeof(*icp), typename);
snprintf(name, sizeof(name), "icp[%d]", i);
object_property_add_child(OBJECT(xics), name, OBJECT(icp), errp);
icp->xics = xics;
}
}
static void xics_prop_get_nr_servers(Object *obj, Visitor *v,
const char *name, void *opaque,
Error **errp)
......@@ -155,7 +217,7 @@ static void xics_prop_set_nr_servers(Object *obj, Visitor *v,
Error **errp)
{
XICSState *xics = XICS_COMMON(obj);
XICSStateClass *info = XICS_COMMON_GET_CLASS(xics);
XICSStateClass *xsc = XICS_COMMON_GET_CLASS(xics);
Error *error = NULL;
int64_t value;
......@@ -170,8 +232,8 @@ static void xics_prop_set_nr_servers(Object *obj, Visitor *v,
return;
}
assert(info->set_nr_servers);
info->set_nr_servers(xics, value, errp);
assert(xsc->set_nr_servers);
xsc->set_nr_servers(xics, value, errp);
}
static void xics_common_initfn(Object *obj)
......@@ -190,8 +252,10 @@ static void xics_common_initfn(Object *obj)
static void xics_common_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
InterruptStatsProviderClass *ic = INTERRUPT_STATS_PROVIDER_CLASS(oc);
dc->reset = xics_common_reset;
ic->print_info = xics_common_pic_print_info;
}
static const TypeInfo xics_common_info = {
......@@ -201,6 +265,10 @@ static const TypeInfo xics_common_info = {
.class_size = sizeof(XICSStateClass),
.instance_init = xics_common_initfn,
.class_init = xics_common_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_INTERRUPT_STATS_PROVIDER },
{ }
},
};
/*
......@@ -258,22 +326,20 @@ static void icp_check_ipi(ICPState *ss)
qemu_irq_raise(ss->output);
}
static void icp_resend(XICSState *xics, int server)
static void icp_resend(ICPState *ss)
{
ICPState *ss = xics->ss + server;
ICSState *ics;
if (ss->mfrr < CPPR(ss)) {
icp_check_ipi(ss);
}
QLIST_FOREACH(ics, &xics->ics, list) {
QLIST_FOREACH(ics, &ss->xics->ics, list) {
ics_resend(ics);
}
}
void icp_set_cppr(XICSState *xics, int server, uint8_t cppr)
void icp_set_cppr(ICPState *ss, uint8_t cppr)
{
ICPState *ss = xics->ss + server;
uint8_t old_cppr;
uint32_t old_xisr;
......@@ -293,15 +359,13 @@ void icp_set_cppr(XICSState *xics, int server, uint8_t cppr)
}
} else {
if (!XISR(ss)) {
icp_resend(xics, server);
icp_resend(ss);
}
}
}
void icp_set_mfrr(XICSState *xics, int server, uint8_t mfrr)
void icp_set_mfrr(ICPState *ss, uint8_t mfrr)
{
ICPState *ss = xics->ss + server;
ss->mfrr = mfrr;
if (mfrr < CPPR(ss)) {
icp_check_ipi(ss);
......@@ -330,23 +394,22 @@ uint32_t icp_ipoll(ICPState *ss, uint32_t *mfrr)
return ss->xirr;
}
void icp_eoi(XICSState *xics, int server, uint32_t xirr)
void icp_eoi(ICPState *ss, uint32_t xirr)
{
ICPState *ss = xics->ss + server;
ICSState *ics;
uint32_t irq;
/* Send EOI -> ICS */
ss->xirr = (ss->xirr & ~CPPR_MASK) | (xirr & CPPR_MASK);
trace_xics_icp_eoi(server, xirr, ss->xirr);
trace_xics_icp_eoi(ss->cs->cpu_index, xirr, ss->xirr);
irq = xirr & XISR_MASK;
QLIST_FOREACH(ics, &xics->ics, list) {
QLIST_FOREACH(ics, &ss->xics->ics, list) {
if (ics_valid_irq(ics, irq)) {
ics_eoi(ics, irq);
}
}
if (!XISR(ss)) {
icp_resend(xics, server);
icp_resend(ss);
}
}
......@@ -605,7 +668,7 @@ static int ics_simple_post_load(ICSState *ics, int version_id)
int i;
for (i = 0; i < ics->xics->nr_servers; i++) {
icp_resend(ics->xics, i);
icp_resend(&ics->xics->ss[i]);
}
return 0;
......
......@@ -373,18 +373,7 @@ static void xics_kvm_set_nr_irqs(XICSState *xics, uint32_t nr_irqs,
static void xics_kvm_set_nr_servers(XICSState *xics, uint32_t nr_servers,
Error **errp)
{
int i;
xics->nr_servers = nr_servers;
xics->ss = g_malloc0(xics->nr_servers * sizeof(ICPState));
for (i = 0; i < xics->nr_servers; i++) {
char buffer[32];
object_initialize(&xics->ss[i], sizeof(xics->ss[i]), TYPE_KVM_ICP);
snprintf(buffer, sizeof(buffer), "icp[%d]", i);
object_property_add_child(OBJECT(xics), buffer, OBJECT(&xics->ss[i]),
errp);
}
xics_set_nr_servers(xics, nr_servers, TYPE_KVM_ICP, errp);
}
static void rtas_dummy(PowerPCCPU *cpu, sPAPRMachineState *spapr,
......
......@@ -32,6 +32,7 @@
#include "qemu/timer.h"
#include "hw/ppc/spapr.h"
#include "hw/ppc/xics.h"
#include "hw/ppc/fdt.h"
#include "qapi/visitor.h"
#include "qapi/error.h"
......@@ -43,9 +44,10 @@ static target_ulong h_cppr(PowerPCCPU *cpu, sPAPRMachineState *spapr,
target_ulong opcode, target_ulong *args)
{
CPUState *cs = CPU(cpu);
ICPState *icp = &spapr->xics->ss[cs->cpu_index];
target_ulong cppr = args[0];
icp_set_cppr(spapr->xics, cs->cpu_index, cppr);
icp_set_cppr(icp, cppr);
return H_SUCCESS;
}
......@@ -59,7 +61,7 @@ static target_ulong h_ipi(PowerPCCPU *cpu, sPAPRMachineState *spapr,
return H_PARAMETER;
}
icp_set_mfrr(spapr->xics, server, mfrr);
icp_set_mfrr(spapr->xics->ss + server, mfrr);
return H_SUCCESS;
}
......@@ -67,7 +69,8 @@ static target_ulong h_xirr(PowerPCCPU *cpu, sPAPRMachineState *spapr,
target_ulong opcode, target_ulong *args)
{
CPUState *cs = CPU(cpu);
uint32_t xirr = icp_accept(spapr->xics->ss + cs->cpu_index);
ICPState *icp = &spapr->xics->ss[cs->cpu_index];
uint32_t xirr = icp_accept(icp);
args[0] = xirr;
return H_SUCCESS;
......@@ -77,8 +80,8 @@ static target_ulong h_xirr_x(PowerPCCPU *cpu, sPAPRMachineState *spapr,
target_ulong opcode, target_ulong *args)
{
CPUState *cs = CPU(cpu);
ICPState *ss = &spapr->xics->ss[cs->cpu_index];
uint32_t xirr = icp_accept(ss);
ICPState *icp = &spapr->xics->ss[cs->cpu_index];
uint32_t xirr = icp_accept(icp);
args[0] = xirr;
args[1] = cpu_get_host_ticks();
......@@ -89,9 +92,10 @@ static target_ulong h_eoi(PowerPCCPU *cpu, sPAPRMachineState *spapr,
target_ulong opcode, target_ulong *args)
{
CPUState *cs = CPU(cpu);
ICPState *icp = &spapr->xics->ss[cs->cpu_index];
target_ulong xirr = args[0];
icp_eoi(spapr->xics, cs->cpu_index, xirr);
icp_eoi(icp, xirr);
return H_SUCCESS;
}
......@@ -99,8 +103,9 @@ static target_ulong h_ipoll(PowerPCCPU *cpu, sPAPRMachineState *spapr,
target_ulong opcode, target_ulong *args)
{
CPUState *cs = CPU(cpu);
ICPState *icp = &spapr->xics->ss[cs->cpu_index];
uint32_t mfrr;
uint32_t xirr = icp_ipoll(spapr->xics->ss + cs->cpu_index, &mfrr);
uint32_t xirr = icp_ipoll(icp, &mfrr);
args[0] = xirr;
args[1] = mfrr;
......@@ -249,18 +254,7 @@ static void xics_spapr_set_nr_irqs(XICSState *xics, uint32_t nr_irqs,
static void xics_spapr_set_nr_servers(XICSState *xics, uint32_t nr_servers,
Error **errp)
{
int i;
xics->nr_servers = nr_servers;
xics->ss = g_malloc0(xics->nr_servers * sizeof(ICPState));
for (i = 0; i < xics->nr_servers; i++) {
char buffer[32];
object_initialize(&xics->ss[i], sizeof(xics->ss[i]), TYPE_ICP);
snprintf(buffer, sizeof(buffer), "icp[%d]", i);
object_property_add_child(OBJECT(xics), buffer, OBJECT(&xics->ss[i]),
errp);
}
xics_set_nr_servers(xics, nr_servers, TYPE_ICP, errp);
}
static void xics_spapr_realize(DeviceState *dev, Error **errp)
......@@ -456,6 +450,27 @@ void xics_spapr_free(XICSState *xics, int irq, int num)
}
}
void spapr_dt_xics(XICSState *xics, void *fdt, uint32_t phandle)
{
uint32_t interrupt_server_ranges_prop[] = {
0, cpu_to_be32(xics->nr_servers),
};
int node;
_FDT(node = fdt_add_subnode(fdt, 0, "interrupt-controller"));
_FDT(fdt_setprop_string(fdt, node, "device_type",
"PowerPC-External-Interrupt-Presentation"));
_FDT(fdt_setprop_string(fdt, node, "compatible", "IBM,ppc-xicp"));
_FDT(fdt_setprop(fdt, node, "interrupt-controller", NULL, 0));
_FDT(fdt_setprop(fdt, node, "ibm,interrupt-server-ranges",
interrupt_server_ranges_prop,
sizeof(interrupt_server_ranges_prop)));
_FDT(fdt_setprop_cell(fdt, node, "#interrupt-cells", 2));
_FDT(fdt_setprop_cell(fdt, node, "linux,phandle", phandle));
_FDT(fdt_setprop_cell(fdt, node, "phandle", phandle));
}
static void xics_spapr_register_types(void)
{
type_register_static(&xics_spapr_info);
......
common-obj-$(CONFIG_DS1225Y) += ds1225y.o
common-obj-y += eeprom93xx.o
common-obj-y += fw_cfg.o
common-obj-y += chrp_nvram.o
common-obj-$(CONFIG_MAC_NVRAM) += mac_nvram.o
obj-$(CONFIG_PSERIES) += spapr_nvram.o
/*
* Common Hardware Reference Platform NVRAM helper functions.
*
* The CHRP NVRAM layout is used by OpenBIOS and SLOF. See CHRP
* specification, chapter 8, or the LoPAPR specification for details
* about the NVRAM layout.
*
* This code 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, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/cutils.h"
#include "hw/hw.h"
#include "hw/nvram/chrp_nvram.h"
#include "sysemu/sysemu.h"
static int chrp_nvram_set_var(uint8_t *nvram, int addr, const char *str)
{
int len;
len = strlen(str) + 1;
memcpy(&nvram[addr], str, len);
return addr + len;
}
/**
* Create a "system partition", used for the Open Firmware
* environment variables.
*/
int chrp_nvram_create_system_partition(uint8_t *data, int min_len)
{
ChrpNvramPartHdr *part_header;
unsigned int i;
int end;
part_header = (ChrpNvramPartHdr *)data;
part_header->signature = CHRP_NVPART_SYSTEM;
pstrcpy(part_header->name, sizeof(part_header->name), "system");
end = sizeof(ChrpNvramPartHdr);
for (i = 0; i < nb_prom_envs; i++) {
end = chrp_nvram_set_var(data, end, prom_envs[i]);
}
/* End marker */
data[end++] = '\0';
end = (end + 15) & ~15;
/* XXX: OpenBIOS is not able to grow up a partition. Leave some space for
new variables. */
if (end < min_len) {
end = min_len;
}
chrp_nvram_finish_partition(part_header, end);
return end;
}
/**
* Create a "free space" partition
*/
int chrp_nvram_create_free_partition(uint8_t *data, int len)
{
ChrpNvramPartHdr *part_header;
part_header = (ChrpNvramPartHdr *)data;
part_header->signature = CHRP_NVPART_FREE;
pstrcpy(part_header->name, sizeof(part_header->name), "free");
chrp_nvram_finish_partition(part_header, len);
return len;
}
......@@ -24,8 +24,7 @@
*/
#include "qemu/osdep.h"
#include "hw/hw.h"
#include "hw/nvram/openbios_firmware_abi.h"
#include "sysemu/sysemu.h"
#include "hw/nvram/chrp_nvram.h"
#include "hw/ppc/mac.h"
#include "qemu/cutils.h"
#include <zlib.h>
......@@ -146,38 +145,14 @@ static void macio_nvram_register_types(void)
static void pmac_format_nvram_partition_of(MacIONVRAMState *nvr, int off,
int len)
{
unsigned int i;
uint32_t start = off, end;
struct OpenBIOS_nvpart_v1 *part_header;
// OpenBIOS nvram variables
// Variable partition
part_header = (struct OpenBIOS_nvpart_v1 *)&nvr->data[start];
part_header->signature = OPENBIOS_PART_SYSTEM;
pstrcpy(part_header->name, sizeof(part_header->name), "system");
end = start + sizeof(struct OpenBIOS_nvpart_v1);
for (i = 0; i < nb_prom_envs; i++)
end = OpenBIOS_set_var(nvr->data, end, prom_envs[i]);
// End marker
nvr->data[end++] = '\0';
end = start + ((end - start + 15) & ~15);
/* XXX: OpenBIOS is not able to grow up a partition. Leave some space for
new variables. */
if (end < DEF_SYSTEM_SIZE)
end = DEF_SYSTEM_SIZE;
OpenBIOS_finish_partition(part_header, end - start);
// free partition
start = end;
part_header = (struct OpenBIOS_nvpart_v1 *)&nvr->data[start];
part_header->signature = OPENBIOS_PART_FREE;
pstrcpy(part_header->name, sizeof(part_header->name), "free");
end = len;
OpenBIOS_finish_partition(part_header, end - start);
int sysp_end;
/* OpenBIOS nvram variables partition */
sysp_end = chrp_nvram_create_system_partition(&nvr->data[off],
DEF_SYSTEM_SIZE) + off;
/* Free space partition */
chrp_nvram_create_free_partition(&nvr->data[sysp_end], len - sysp_end);
}
#define OSX_NVRAM_SIGNATURE (0x5A)
......@@ -187,15 +162,15 @@ static void pmac_format_nvram_partition_osx(MacIONVRAMState *nvr, int off,
int len)
{
uint32_t start = off;
struct OpenBIOS_nvpart_v1 *part_header;
ChrpNvramPartHdr *part_header;
unsigned char *data = &nvr->data[start];
/* empty partition */
part_header = (struct OpenBIOS_nvpart_v1 *)data;
part_header = (ChrpNvramPartHdr *)data;
part_header->signature = OSX_NVRAM_SIGNATURE;
pstrcpy(part_header->name, sizeof(part_header->name), "wwwwwwwwwwww");
OpenBIOS_finish_partition(part_header, len);
chrp_nvram_finish_partition(part_header, len);
/* Generation */
stl_be_p(&data[20], 2);
......
......@@ -31,6 +31,7 @@
#include "sysemu/block-backend.h"
#include "sysemu/device_tree.h"
#include "hw/sysbus.h"
#include "hw/nvram/chrp_nvram.h"
#include "hw/ppc/spapr.h"
#include "hw/ppc/spapr_vio.h"
......@@ -162,6 +163,11 @@ static void spapr_nvram_realize(VIOsPAPRDevice *dev, Error **errp)
error_setg(errp, "can't read spapr-nvram contents");
return;
}
} else if (nb_prom_envs > 0) {
/* Create a system partition to pass the -prom-env variables */
chrp_nvram_create_system_partition(nvram->buf, MIN_NVRAM_SIZE / 4);
chrp_nvram_create_free_partition(&nvram->buf[MIN_NVRAM_SIZE / 4],
nvram->size - MIN_NVRAM_SIZE / 4);
}
spapr_rtas_register(RTAS_NVRAM_FETCH, "nvram-fetch", rtas_nvram_fetch);
......
......@@ -4,7 +4,9 @@ obj-y += ppc.o ppc_booke.o fdt.o
obj-$(CONFIG_PSERIES) += spapr.o spapr_vio.o spapr_events.o
obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o spapr_rtas.o
obj-$(CONFIG_PSERIES) += spapr_pci.o spapr_rtc.o spapr_drc.o spapr_rng.o
obj-$(CONFIG_PSERIES) += spapr_cpu_core.o
obj-$(CONFIG_PSERIES) += spapr_cpu_core.o spapr_ovec.o
# IBM PowerNV
obj-$(CONFIG_POWERNV) += pnv.o pnv_xscom.o pnv_core.o pnv_lpc.o
ifeq ($(CONFIG_PCI)$(CONFIG_PSERIES)$(CONFIG_LINUX), yyy)
obj-y += spapr_pci_vfio.o
endif
......
此差异已折叠。
/*
* QEMU PowerPC PowerNV CPU Core model
*
* Copyright (c) 2016, IBM Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "sysemu/sysemu.h"
#include "qapi/error.h"
#include "qemu/log.h"
#include "target-ppc/cpu.h"
#include "hw/ppc/ppc.h"
#include "hw/ppc/pnv.h"
#include "hw/ppc/pnv_core.h"
static void powernv_cpu_reset(void *opaque)
{
PowerPCCPU *cpu = opaque;
CPUState *cs = CPU(cpu);
CPUPPCState *env = &cpu->env;
cpu_reset(cs);
/*
* the skiboot firmware elects a primary thread to initialize the
* system and it can be any.
*/
env->gpr[3] = PNV_FDT_ADDR;
env->nip = 0x10;
env->msr |= MSR_HVB; /* Hypervisor mode */
}
static void powernv_cpu_init(PowerPCCPU *cpu, Error **errp)
{
CPUPPCState *env = &cpu->env;
int core_pir;
int thread_index = 0; /* TODO: TCG supports only one thread */
ppc_spr_t *pir = &env->spr_cb[SPR_PIR];
core_pir = object_property_get_int(OBJECT(cpu), "core-pir", &error_abort);
/*
* The PIR of a thread is the core PIR + the thread index. We will
* need to find a way to get the thread index when TCG supports
* more than 1. We could use the object name ?
*/
pir->default_value = core_pir + thread_index;
/* Set time-base frequency to 512 MHz */
cpu_ppc_tb_init(env, PNV_TIMEBASE_FREQ);
qemu_register_reset(powernv_cpu_reset, cpu);
}
/*
* These values are read by the PowerNV HW monitors under Linux
*/
#define PNV_XSCOM_EX_DTS_RESULT0 0x50000
#define PNV_XSCOM_EX_DTS_RESULT1 0x50001
static uint64_t pnv_core_xscom_read(void *opaque, hwaddr addr,
unsigned int width)
{
uint32_t offset = addr >> 3;
uint64_t val = 0;
/* The result should be 38 C */
switch (offset) {
case PNV_XSCOM_EX_DTS_RESULT0:
val = 0x26f024f023f0000ull;
break;
case PNV_XSCOM_EX_DTS_RESULT1:
val = 0x24f000000000000ull;
break;
default:
qemu_log_mask(LOG_UNIMP, "Warning: reading reg=0x%" HWADDR_PRIx,
addr);
}
return val;
}
static void pnv_core_xscom_write(void *opaque, hwaddr addr, uint64_t val,
unsigned int width)
{
qemu_log_mask(LOG_UNIMP, "Warning: writing to reg=0x%" HWADDR_PRIx,
addr);
}
static const MemoryRegionOps pnv_core_xscom_ops = {
.read = pnv_core_xscom_read,
.write = pnv_core_xscom_write,
.valid.min_access_size = 8,
.valid.max_access_size = 8,
.impl.min_access_size = 8,
.impl.max_access_size = 8,
.endianness = DEVICE_BIG_ENDIAN,
};
static void pnv_core_realize_child(Object *child, Error **errp)
{
Error *local_err = NULL;
CPUState *cs = CPU(child);
PowerPCCPU *cpu = POWERPC_CPU(cs);
object_property_set_bool(child, true, "realized", &local_err);
if (local_err) {
error_propagate(errp, local_err);
return;
}
powernv_cpu_init(cpu, &local_err);
if (local_err) {
error_propagate(errp, local_err);
return;
}
}
static void pnv_core_realize(DeviceState *dev, Error **errp)
{
PnvCore *pc = PNV_CORE(OBJECT(dev));
CPUCore *cc = CPU_CORE(OBJECT(dev));
PnvCoreClass *pcc = PNV_CORE_GET_CLASS(OBJECT(dev));
const char *typename = object_class_get_name(pcc->cpu_oc);
size_t size = object_type_get_instance_size(typename);
Error *local_err = NULL;
void *obj;
int i, j;
char name[32];
pc->threads = g_malloc0(size * cc->nr_threads);
for (i = 0; i < cc->nr_threads; i++) {
obj = pc->threads + i * size;
object_initialize(obj, size, typename);
snprintf(name, sizeof(name), "thread[%d]", i);
object_property_add_child(OBJECT(pc), name, obj, &local_err);
object_property_add_alias(obj, "core-pir", OBJECT(pc),
"pir", &local_err);
if (local_err) {
goto err;
}
object_unref(obj);
}
for (j = 0; j < cc->nr_threads; j++) {
obj = pc->threads + j * size;
pnv_core_realize_child(obj, &local_err);
if (local_err) {
goto err;
}
}
snprintf(name, sizeof(name), "xscom-core.%d", cc->core_id);
pnv_xscom_region_init(&pc->xscom_regs, OBJECT(dev), &pnv_core_xscom_ops,
pc, name, PNV_XSCOM_EX_CORE_SIZE);
return;
err:
while (--i >= 0) {
obj = pc->threads + i * size;
object_unparent(obj);
}
g_free(pc->threads);
error_propagate(errp, local_err);
}
static Property pnv_core_properties[] = {
DEFINE_PROP_UINT32("pir", PnvCore, pir, 0),
DEFINE_PROP_END_OF_LIST(),
};
static void pnv_core_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
PnvCoreClass *pcc = PNV_CORE_CLASS(oc);
dc->realize = pnv_core_realize;
dc->props = pnv_core_properties;
pcc->cpu_oc = cpu_class_by_name(TYPE_POWERPC_CPU, data);
}
static const TypeInfo pnv_core_info = {
.name = TYPE_PNV_CORE,
.parent = TYPE_CPU_CORE,
.instance_size = sizeof(PnvCore),
.class_size = sizeof(PnvCoreClass),
.abstract = true,
};
static const char *pnv_core_models[] = {
"POWER8E", "POWER8", "POWER8NVL", "POWER9"
};
static void pnv_core_register_types(void)
{
int i ;
type_register_static(&pnv_core_info);
for (i = 0; i < ARRAY_SIZE(pnv_core_models); ++i) {
TypeInfo ti = {
.parent = TYPE_PNV_CORE,
.instance_size = sizeof(PnvCore),
.class_init = pnv_core_class_init,
.class_data = (void *) pnv_core_models[i],
};
ti.name = pnv_core_typename(pnv_core_models[i]);
type_register(&ti);
g_free((void *)ti.name);
}
}
type_init(pnv_core_register_types)
char *pnv_core_typename(const char *model)
{
return g_strdup_printf(TYPE_PNV_CORE "-%s", model);
}
/*
* QEMU PowerPC PowerNV LPC controller
*
* Copyright (c) 2016, IBM Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "sysemu/sysemu.h"
#include "target-ppc/cpu.h"
#include "qapi/error.h"
#include "qemu/log.h"
#include "hw/ppc/pnv_lpc.h"
#include "hw/ppc/pnv.h"
#include "hw/ppc/fdt.h"
#include <libfdt.h>
enum {
ECCB_CTL = 0,
ECCB_RESET = 1,
ECCB_STAT = 2,
ECCB_DATA = 3,
};
/* OPB Master LS registers */
#define OPB_MASTER_LS_IRQ_STAT 0x50
#define OPB_MASTER_IRQ_LPC 0x00000800
#define OPB_MASTER_LS_IRQ_MASK 0x54
#define OPB_MASTER_LS_IRQ_POL 0x58
#define OPB_MASTER_LS_IRQ_INPUT 0x5c
/* LPC HC registers */
#define LPC_HC_FW_SEG_IDSEL 0x24
#define LPC_HC_FW_RD_ACC_SIZE 0x28
#define LPC_HC_FW_RD_1B 0x00000000
#define LPC_HC_FW_RD_2B 0x01000000
#define LPC_HC_FW_RD_4B 0x02000000
#define LPC_HC_FW_RD_16B 0x04000000
#define LPC_HC_FW_RD_128B 0x07000000
#define LPC_HC_IRQSER_CTRL 0x30
#define LPC_HC_IRQSER_EN 0x80000000
#define LPC_HC_IRQSER_QMODE 0x40000000
#define LPC_HC_IRQSER_START_MASK 0x03000000
#define LPC_HC_IRQSER_START_4CLK 0x00000000
#define LPC_HC_IRQSER_START_6CLK 0x01000000
#define LPC_HC_IRQSER_START_8CLK 0x02000000
#define LPC_HC_IRQMASK 0x34 /* same bit defs as LPC_HC_IRQSTAT */
#define LPC_HC_IRQSTAT 0x38
#define LPC_HC_IRQ_SERIRQ0 0x80000000 /* all bits down to ... */
#define LPC_HC_IRQ_SERIRQ16 0x00008000 /* IRQ16=IOCHK#, IRQ2=SMI# */
#define LPC_HC_IRQ_SERIRQ_ALL 0xffff8000
#define LPC_HC_IRQ_LRESET 0x00000400
#define LPC_HC_IRQ_SYNC_ABNORM_ERR 0x00000080
#define LPC_HC_IRQ_SYNC_NORESP_ERR 0x00000040
#define LPC_HC_IRQ_SYNC_NORM_ERR 0x00000020
#define LPC_HC_IRQ_SYNC_TIMEOUT_ERR 0x00000010
#define LPC_HC_IRQ_SYNC_TARG_TAR_ERR 0x00000008
#define LPC_HC_IRQ_SYNC_BM_TAR_ERR 0x00000004
#define LPC_HC_IRQ_SYNC_BM0_REQ 0x00000002
#define LPC_HC_IRQ_SYNC_BM1_REQ 0x00000001
#define LPC_HC_ERROR_ADDRESS 0x40
#define LPC_OPB_SIZE 0x100000000ull
#define ISA_IO_SIZE 0x00010000
#define ISA_MEM_SIZE 0x10000000
#define LPC_IO_OPB_ADDR 0xd0010000
#define LPC_IO_OPB_SIZE 0x00010000
#define LPC_MEM_OPB_ADDR 0xe0010000
#define LPC_MEM_OPB_SIZE 0x10000000
#define LPC_FW_OPB_ADDR 0xf0000000
#define LPC_FW_OPB_SIZE 0x10000000
#define LPC_OPB_REGS_OPB_ADDR 0xc0010000
#define LPC_OPB_REGS_OPB_SIZE 0x00002000
#define LPC_HC_REGS_OPB_ADDR 0xc0012000
#define LPC_HC_REGS_OPB_SIZE 0x00001000
/*
* TODO: the "primary" cell should only be added on chip 0. This is
* how skiboot chooses the default LPC controller on multichip
* systems.
*
* It would be easly done if we can change the populate() interface to
* replace the PnvXScomInterface parameter by a PnvChip one
*/
static int pnv_lpc_populate(PnvXScomInterface *dev, void *fdt, int xscom_offset)
{
const char compat[] = "ibm,power8-lpc\0ibm,lpc";
char *name;
int offset;
uint32_t lpc_pcba = PNV_XSCOM_LPC_BASE;
uint32_t reg[] = {
cpu_to_be32(lpc_pcba),
cpu_to_be32(PNV_XSCOM_LPC_SIZE)
};
name = g_strdup_printf("isa@%x", lpc_pcba);
offset = fdt_add_subnode(fdt, xscom_offset, name);
_FDT(offset);
g_free(name);
_FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg))));
_FDT((fdt_setprop_cell(fdt, offset, "#address-cells", 2)));
_FDT((fdt_setprop_cell(fdt, offset, "#size-cells", 1)));
_FDT((fdt_setprop(fdt, offset, "primary", NULL, 0)));
_FDT((fdt_setprop(fdt, offset, "compatible", compat, sizeof(compat))));
return 0;
}
/*
* These read/write handlers of the OPB address space should be common
* with the P9 LPC Controller which uses direct MMIOs.
*
* TODO: rework to use address_space_stq() and address_space_ldq()
* instead.
*/
static bool opb_read(PnvLpcController *lpc, uint32_t addr, uint8_t *data,
int sz)
{
bool success;
/* XXX Handle access size limits and FW read caching here */
success = !address_space_rw(&lpc->opb_as, addr, MEMTXATTRS_UNSPECIFIED,
data, sz, false);
return success;
}
static bool opb_write(PnvLpcController *lpc, uint32_t addr, uint8_t *data,
int sz)
{
bool success;
/* XXX Handle access size limits here */
success = !address_space_rw(&lpc->opb_as, addr, MEMTXATTRS_UNSPECIFIED,
data, sz, true);
return success;
}
#define ECCB_CTL_READ (1ull << (63 - 15))
#define ECCB_CTL_SZ_LSH (63 - 7)
#define ECCB_CTL_SZ_MASK (0xfull << ECCB_CTL_SZ_LSH)
#define ECCB_CTL_ADDR_MASK 0xffffffffu;
#define ECCB_STAT_OP_DONE (1ull << (63 - 52))
#define ECCB_STAT_OP_ERR (1ull << (63 - 52))
#define ECCB_STAT_RD_DATA_LSH (63 - 37)
#define ECCB_STAT_RD_DATA_MASK (0xffffffff << ECCB_STAT_RD_DATA_LSH)
static void pnv_lpc_do_eccb(PnvLpcController *lpc, uint64_t cmd)
{
/* XXX Check for magic bits at the top, addr size etc... */
unsigned int sz = (cmd & ECCB_CTL_SZ_MASK) >> ECCB_CTL_SZ_LSH;
uint32_t opb_addr = cmd & ECCB_CTL_ADDR_MASK;
uint8_t data[4];
bool success;
if (cmd & ECCB_CTL_READ) {
success = opb_read(lpc, opb_addr, data, sz);
if (success) {
lpc->eccb_stat_reg = ECCB_STAT_OP_DONE |
(((uint64_t)data[0]) << 24 |
((uint64_t)data[1]) << 16 |
((uint64_t)data[2]) << 8 |
((uint64_t)data[3])) << ECCB_STAT_RD_DATA_LSH;
} else {
lpc->eccb_stat_reg = ECCB_STAT_OP_DONE |
(0xffffffffull << ECCB_STAT_RD_DATA_LSH);
}
} else {
data[0] = lpc->eccb_data_reg >> 24;
data[1] = lpc->eccb_data_reg >> 16;
data[2] = lpc->eccb_data_reg >> 8;
data[3] = lpc->eccb_data_reg;
success = opb_write(lpc, opb_addr, data, sz);
lpc->eccb_stat_reg = ECCB_STAT_OP_DONE;
}
/* XXX Which error bit (if any) to signal OPB error ? */
}
static uint64_t pnv_lpc_xscom_read(void *opaque, hwaddr addr, unsigned size)
{
PnvLpcController *lpc = PNV_LPC(opaque);
uint32_t offset = addr >> 3;
uint64_t val = 0;
switch (offset & 3) {
case ECCB_CTL:
case ECCB_RESET:
val = 0;
break;
case ECCB_STAT:
val = lpc->eccb_stat_reg;
lpc->eccb_stat_reg = 0;
break;
case ECCB_DATA:
val = ((uint64_t)lpc->eccb_data_reg) << 32;
break;
}
return val;
}
static void pnv_lpc_xscom_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
PnvLpcController *lpc = PNV_LPC(opaque);
uint32_t offset = addr >> 3;
switch (offset & 3) {
case ECCB_CTL:
pnv_lpc_do_eccb(lpc, val);
break;
case ECCB_RESET:
/* XXXX */
break;
case ECCB_STAT:
break;
case ECCB_DATA:
lpc->eccb_data_reg = val >> 32;
break;
}
}
static const MemoryRegionOps pnv_lpc_xscom_ops = {
.read = pnv_lpc_xscom_read,
.write = pnv_lpc_xscom_write,
.valid.min_access_size = 8,
.valid.max_access_size = 8,
.impl.min_access_size = 8,
.impl.max_access_size = 8,
.endianness = DEVICE_BIG_ENDIAN,
};
static uint64_t lpc_hc_read(void *opaque, hwaddr addr, unsigned size)
{
PnvLpcController *lpc = opaque;
uint64_t val = 0xfffffffffffffffful;
switch (addr) {
case LPC_HC_FW_SEG_IDSEL:
val = lpc->lpc_hc_fw_seg_idsel;
break;
case LPC_HC_FW_RD_ACC_SIZE:
val = lpc->lpc_hc_fw_rd_acc_size;
break;
case LPC_HC_IRQSER_CTRL:
val = lpc->lpc_hc_irqser_ctrl;
break;
case LPC_HC_IRQMASK:
val = lpc->lpc_hc_irqmask;
break;
case LPC_HC_IRQSTAT:
val = lpc->lpc_hc_irqstat;
break;
case LPC_HC_ERROR_ADDRESS:
val = lpc->lpc_hc_error_addr;
break;
default:
qemu_log_mask(LOG_UNIMP, "LPC HC Unimplemented register: Ox%"
HWADDR_PRIx "\n", addr);
}
return val;
}
static void lpc_hc_write(void *opaque, hwaddr addr, uint64_t val,
unsigned size)
{
PnvLpcController *lpc = opaque;
/* XXX Filter out reserved bits */
switch (addr) {
case LPC_HC_FW_SEG_IDSEL:
/* XXX Actually figure out how that works as this impact
* memory regions/aliases
*/
lpc->lpc_hc_fw_seg_idsel = val;
break;
case LPC_HC_FW_RD_ACC_SIZE:
lpc->lpc_hc_fw_rd_acc_size = val;
break;
case LPC_HC_IRQSER_CTRL:
lpc->lpc_hc_irqser_ctrl = val;
break;
case LPC_HC_IRQMASK:
lpc->lpc_hc_irqmask = val;
break;
case LPC_HC_IRQSTAT:
lpc->lpc_hc_irqstat &= ~val;
break;
case LPC_HC_ERROR_ADDRESS:
break;
default:
qemu_log_mask(LOG_UNIMP, "LPC HC Unimplemented register: Ox%"
HWADDR_PRIx "\n", addr);
}
}
static const MemoryRegionOps lpc_hc_ops = {
.read = lpc_hc_read,
.write = lpc_hc_write,
.endianness = DEVICE_BIG_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static uint64_t opb_master_read(void *opaque, hwaddr addr, unsigned size)
{
PnvLpcController *lpc = opaque;
uint64_t val = 0xfffffffffffffffful;
switch (addr) {
case OPB_MASTER_LS_IRQ_STAT:
val = lpc->opb_irq_stat;
break;
case OPB_MASTER_LS_IRQ_MASK:
val = lpc->opb_irq_mask;
break;
case OPB_MASTER_LS_IRQ_POL:
val = lpc->opb_irq_pol;
break;
case OPB_MASTER_LS_IRQ_INPUT:
val = lpc->opb_irq_input;
break;
default:
qemu_log_mask(LOG_UNIMP, "OPB MASTER Unimplemented register: Ox%"
HWADDR_PRIx "\n", addr);
}
return val;
}
static void opb_master_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
PnvLpcController *lpc = opaque;
switch (addr) {
case OPB_MASTER_LS_IRQ_STAT:
lpc->opb_irq_stat &= ~val;
break;
case OPB_MASTER_LS_IRQ_MASK:
/* XXX Filter out reserved bits */
lpc->opb_irq_mask = val;
break;
case OPB_MASTER_LS_IRQ_POL:
/* XXX Filter out reserved bits */
lpc->opb_irq_pol = val;
break;
case OPB_MASTER_LS_IRQ_INPUT:
/* Read only */
break;
default:
qemu_log_mask(LOG_UNIMP, "OPB MASTER Unimplemented register: Ox%"
HWADDR_PRIx "\n", addr);
}
}
static const MemoryRegionOps opb_master_ops = {
.read = opb_master_read,
.write = opb_master_write,
.endianness = DEVICE_BIG_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void pnv_lpc_realize(DeviceState *dev, Error **errp)
{
PnvLpcController *lpc = PNV_LPC(dev);
/* Reg inits */
lpc->lpc_hc_fw_rd_acc_size = LPC_HC_FW_RD_4B;
/* Create address space and backing MR for the OPB bus */
memory_region_init(&lpc->opb_mr, OBJECT(dev), "lpc-opb", 0x100000000ull);
address_space_init(&lpc->opb_as, &lpc->opb_mr, "lpc-opb");
/* Create ISA IO and Mem space regions which are the root of
* the ISA bus (ie, ISA address spaces). We don't create a
* separate one for FW which we alias to memory.
*/
memory_region_init(&lpc->isa_io, OBJECT(dev), "isa-io", ISA_IO_SIZE);
memory_region_init(&lpc->isa_mem, OBJECT(dev), "isa-mem", ISA_MEM_SIZE);
/* Create windows from the OPB space to the ISA space */
memory_region_init_alias(&lpc->opb_isa_io, OBJECT(dev), "lpc-isa-io",
&lpc->isa_io, 0, LPC_IO_OPB_SIZE);
memory_region_add_subregion(&lpc->opb_mr, LPC_IO_OPB_ADDR,
&lpc->opb_isa_io);
memory_region_init_alias(&lpc->opb_isa_mem, OBJECT(dev), "lpc-isa-mem",
&lpc->isa_mem, 0, LPC_MEM_OPB_SIZE);
memory_region_add_subregion(&lpc->opb_mr, LPC_MEM_OPB_ADDR,
&lpc->opb_isa_mem);
memory_region_init_alias(&lpc->opb_isa_fw, OBJECT(dev), "lpc-isa-fw",
&lpc->isa_mem, 0, LPC_FW_OPB_SIZE);
memory_region_add_subregion(&lpc->opb_mr, LPC_FW_OPB_ADDR,
&lpc->opb_isa_fw);
/* Create MMIO regions for LPC HC and OPB registers */
memory_region_init_io(&lpc->opb_master_regs, OBJECT(dev), &opb_master_ops,
lpc, "lpc-opb-master", LPC_OPB_REGS_OPB_SIZE);
memory_region_add_subregion(&lpc->opb_mr, LPC_OPB_REGS_OPB_ADDR,
&lpc->opb_master_regs);
memory_region_init_io(&lpc->lpc_hc_regs, OBJECT(dev), &lpc_hc_ops, lpc,
"lpc-hc", LPC_HC_REGS_OPB_SIZE);
memory_region_add_subregion(&lpc->opb_mr, LPC_HC_REGS_OPB_ADDR,
&lpc->lpc_hc_regs);
/* XScom region for LPC registers */
pnv_xscom_region_init(&lpc->xscom_regs, OBJECT(dev),
&pnv_lpc_xscom_ops, lpc, "xscom-lpc",
PNV_XSCOM_LPC_SIZE);
}
static void pnv_lpc_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass);
xdc->populate = pnv_lpc_populate;
dc->realize = pnv_lpc_realize;
}
static const TypeInfo pnv_lpc_info = {
.name = TYPE_PNV_LPC,
.parent = TYPE_DEVICE,
.instance_size = sizeof(PnvLpcController),
.class_init = pnv_lpc_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_PNV_XSCOM_INTERFACE },
{ }
}
};
static void pnv_lpc_register_types(void)
{
type_register_static(&pnv_lpc_info);
}
type_init(pnv_lpc_register_types)
/*
* QEMU PowerPC PowerNV XSCOM bus
*
* Copyright (c) 2016, IBM Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/hw.h"
#include "qemu/log.h"
#include "sysemu/kvm.h"
#include "target-ppc/cpu.h"
#include "hw/sysbus.h"
#include "hw/ppc/fdt.h"
#include "hw/ppc/pnv_xscom.h"
#include "hw/ppc/pnv.h"
#include <libfdt.h>
static void xscom_complete(CPUState *cs, uint64_t hmer_bits)
{
/*
* TODO: When the read/write comes from the monitor, NULL is
* passed for the cpu, and no CPU completion is generated.
*/
if (cs) {
PowerPCCPU *cpu = POWERPC_CPU(cs);
CPUPPCState *env = &cpu->env;
/*
* TODO: Need a CPU helper to set HMER, also handle generation
* of HMIs
*/
cpu_synchronize_state(cs);
env->spr[SPR_HMER] |= hmer_bits;
}
}
static uint32_t pnv_xscom_pcba(PnvChip *chip, uint64_t addr)
{
PnvChipClass *pcc = PNV_CHIP_GET_CLASS(chip);
addr &= (PNV_XSCOM_SIZE - 1);
if (pcc->chip_type == PNV_CHIP_POWER9) {
return addr >> 3;
} else {
return ((addr >> 4) & ~0xfull) | ((addr >> 3) & 0xf);
}
}
static uint64_t xscom_read_default(PnvChip *chip, uint32_t pcba)
{
switch (pcba) {
case 0xf000f:
return PNV_CHIP_GET_CLASS(chip)->chip_cfam_id;
case 0x1010c00: /* PIBAM FIR */
case 0x1010c03: /* PIBAM FIR MASK */
case 0x2020007: /* ADU stuff */
case 0x2020009: /* ADU stuff */
case 0x202000f: /* ADU stuff */
return 0;
case 0x2013f00: /* PBA stuff */
case 0x2013f01: /* PBA stuff */
case 0x2013f02: /* PBA stuff */
case 0x2013f03: /* PBA stuff */
case 0x2013f04: /* PBA stuff */
case 0x2013f05: /* PBA stuff */
case 0x2013f06: /* PBA stuff */
case 0x2013f07: /* PBA stuff */
return 0;
case 0x2013028: /* CAPP stuff */
case 0x201302a: /* CAPP stuff */
case 0x2013801: /* CAPP stuff */
case 0x2013802: /* CAPP stuff */
return 0;
default:
return -1;
}
}
static bool xscom_write_default(PnvChip *chip, uint32_t pcba, uint64_t val)
{
/* We ignore writes to these */
switch (pcba) {
case 0xf000f: /* chip id is RO */
case 0x1010c00: /* PIBAM FIR */
case 0x1010c01: /* PIBAM FIR */
case 0x1010c02: /* PIBAM FIR */
case 0x1010c03: /* PIBAM FIR MASK */
case 0x1010c04: /* PIBAM FIR MASK */
case 0x1010c05: /* PIBAM FIR MASK */
case 0x2020007: /* ADU stuff */
case 0x2020009: /* ADU stuff */
case 0x202000f: /* ADU stuff */
return true;
default:
return false;
}
}
static uint64_t xscom_read(void *opaque, hwaddr addr, unsigned width)
{
PnvChip *chip = opaque;
uint32_t pcba = pnv_xscom_pcba(chip, addr);
uint64_t val = 0;
MemTxResult result;
/* Handle some SCOMs here before dispatch */
val = xscom_read_default(chip, pcba);
if (val != -1) {
goto complete;
}
val = address_space_ldq(&chip->xscom_as, pcba << 3, MEMTXATTRS_UNSPECIFIED,
&result);
if (result != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR, "XSCOM read failed at @0x%"
HWADDR_PRIx " pcba=0x%08x\n", addr, pcba);
xscom_complete(current_cpu, HMER_XSCOM_FAIL | HMER_XSCOM_DONE);
return 0;
}
complete:
xscom_complete(current_cpu, HMER_XSCOM_DONE);
return val;
}
static void xscom_write(void *opaque, hwaddr addr, uint64_t val,
unsigned width)
{
PnvChip *chip = opaque;
uint32_t pcba = pnv_xscom_pcba(chip, addr);
MemTxResult result;
/* Handle some SCOMs here before dispatch */
if (xscom_write_default(chip, pcba, val)) {
goto complete;
}
address_space_stq(&chip->xscom_as, pcba << 3, val, MEMTXATTRS_UNSPECIFIED,
&result);
if (result != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR, "XSCOM write failed at @0x%"
HWADDR_PRIx " pcba=0x%08x data=0x%" PRIx64 "\n",
addr, pcba, val);
xscom_complete(current_cpu, HMER_XSCOM_FAIL | HMER_XSCOM_DONE);
return;
}
complete:
xscom_complete(current_cpu, HMER_XSCOM_DONE);
}
const MemoryRegionOps pnv_xscom_ops = {
.read = xscom_read,
.write = xscom_write,
.valid.min_access_size = 8,
.valid.max_access_size = 8,
.impl.min_access_size = 8,
.impl.max_access_size = 8,
.endianness = DEVICE_BIG_ENDIAN,
};
void pnv_xscom_realize(PnvChip *chip, Error **errp)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(chip);
char *name;
name = g_strdup_printf("xscom-%x", chip->chip_id);
memory_region_init_io(&chip->xscom_mmio, OBJECT(chip), &pnv_xscom_ops,
chip, name, PNV_XSCOM_SIZE);
sysbus_init_mmio(sbd, &chip->xscom_mmio);
memory_region_init(&chip->xscom, OBJECT(chip), name, PNV_XSCOM_SIZE);
address_space_init(&chip->xscom_as, &chip->xscom, name);
g_free(name);
}
static const TypeInfo pnv_xscom_interface_info = {
.name = TYPE_PNV_XSCOM_INTERFACE,
.parent = TYPE_INTERFACE,
.class_size = sizeof(PnvXScomInterfaceClass),
};
static void pnv_xscom_register_types(void)
{
type_register_static(&pnv_xscom_interface_info);
}
type_init(pnv_xscom_register_types)
typedef struct ForeachPopulateArgs {
void *fdt;
int xscom_offset;
} ForeachPopulateArgs;
static int xscom_populate_child(Object *child, void *opaque)
{
if (object_dynamic_cast(child, TYPE_PNV_XSCOM_INTERFACE)) {
ForeachPopulateArgs *args = opaque;
PnvXScomInterface *xd = PNV_XSCOM_INTERFACE(child);
PnvXScomInterfaceClass *xc = PNV_XSCOM_INTERFACE_GET_CLASS(xd);
if (xc->populate) {
_FDT((xc->populate(xd, args->fdt, args->xscom_offset)));
}
}
return 0;
}
static const char compat_p8[] = "ibm,power8-xscom\0ibm,xscom";
static const char compat_p9[] = "ibm,power9-xscom\0ibm,xscom";
int pnv_xscom_populate(PnvChip *chip, void *fdt, int root_offset)
{
uint64_t reg[] = { cpu_to_be64(PNV_XSCOM_BASE(chip)),
cpu_to_be64(PNV_XSCOM_SIZE) };
int xscom_offset;
ForeachPopulateArgs args;
char *name;
PnvChipClass *pcc = PNV_CHIP_GET_CLASS(chip);
name = g_strdup_printf("xscom@%" PRIx64, be64_to_cpu(reg[0]));
xscom_offset = fdt_add_subnode(fdt, root_offset, name);
_FDT(xscom_offset);
g_free(name);
_FDT((fdt_setprop_cell(fdt, xscom_offset, "ibm,chip-id", chip->chip_id)));
_FDT((fdt_setprop_cell(fdt, xscom_offset, "#address-cells", 1)));
_FDT((fdt_setprop_cell(fdt, xscom_offset, "#size-cells", 1)));
_FDT((fdt_setprop(fdt, xscom_offset, "reg", reg, sizeof(reg))));
if (pcc->chip_type == PNV_CHIP_POWER9) {
_FDT((fdt_setprop(fdt, xscom_offset, "compatible", compat_p9,
sizeof(compat_p9))));
} else {
_FDT((fdt_setprop(fdt, xscom_offset, "compatible", compat_p8,
sizeof(compat_p8))));
}
_FDT((fdt_setprop(fdt, xscom_offset, "scom-controller", NULL, 0)));
args.fdt = fdt;
args.xscom_offset = xscom_offset;
object_child_foreach(OBJECT(chip), xscom_populate_child, &args);
return 0;
}
void pnv_xscom_add_subregion(PnvChip *chip, hwaddr offset, MemoryRegion *mr)
{
memory_region_add_subregion(&chip->xscom, offset << 3, mr);
}
void pnv_xscom_region_init(MemoryRegion *mr,
struct Object *owner,
const MemoryRegionOps *ops,
void *opaque,
const char *name,
uint64_t size)
{
memory_region_init_io(mr, owner, ops, opaque, name, size << 3);
}
此差异已折叠。
......@@ -184,7 +184,7 @@ void spapr_core_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
/*
* Setup CPU DT entries only for hotplugged CPUs. For boot time or
* coldplugged CPUs DT entries are setup in spapr_finalize_fdt().
* coldplugged CPUs DT entries are setup in spapr_build_fdt().
*/
if (dev->hotplugged) {
fdt = spapr_populate_hotplug_cpu_dt(cs, &fdt_offset, spapr);
......
......@@ -68,6 +68,23 @@ static uint32_t set_isolation_state(sPAPRDRConnector *drc,
}
}
/*
* Fail any requests to ISOLATE the LMB DRC if this LMB doesn't
* belong to a DIMM device that is marked for removal.
*
* Currently the guest userspace tool drmgr that drives the memory
* hotplug/unplug will just try to remove a set of 'removable' LMBs
* in response to a hot unplug request that is based on drc-count.
* If the LMB being removed doesn't belong to a DIMM device that is
* actually being unplugged, fail the isolation request here.
*/
if (drc->type == SPAPR_DR_CONNECTOR_TYPE_LMB) {
if ((state == SPAPR_DR_ISOLATION_STATE_ISOLATED) &&
!drc->awaiting_release) {
return RTAS_OUT_HW_ERROR;
}
}
drc->isolation_state = state;
if (drc->isolation_state == SPAPR_DR_ISOLATION_STATE_ISOLATED) {
......
......@@ -40,6 +40,7 @@
#include "hw/ppc/spapr_drc.h"
#include "qemu/help_option.h"
#include "qemu/bcd.h"
#include "hw/ppc/spapr_ovec.h"
#include <libfdt.h>
struct rtas_error_log {
......@@ -174,6 +175,16 @@ struct epow_log_full {
struct rtas_event_log_v6_epow epow;
} QEMU_PACKED;
union drc_identifier {
uint32_t index;
uint32_t count;
struct {
uint32_t count;
uint32_t index;
} count_indexed;
char name[1];
} QEMU_PACKED;
struct rtas_event_log_v6_hp {
#define RTAS_LOG_V6_SECTION_ID_HOTPLUG 0x4850 /* HP */
struct rtas_event_log_v6_section_header hdr;
......@@ -190,12 +201,9 @@ struct rtas_event_log_v6_hp {
#define RTAS_LOG_V6_HP_ID_DRC_NAME 1
#define RTAS_LOG_V6_HP_ID_DRC_INDEX 2
#define RTAS_LOG_V6_HP_ID_DRC_COUNT 3
#define RTAS_LOG_V6_HP_ID_DRC_COUNT_INDEXED 4
uint8_t reserved;
union {
uint32_t index;
uint32_t count;
char name[1];
} drc;
union drc_identifier drc_id;
} QEMU_PACKED;
struct hp_log_full {
......@@ -206,28 +214,132 @@ struct hp_log_full {
struct rtas_event_log_v6_hp hp;
} QEMU_PACKED;
#define EVENT_MASK_INTERNAL_ERRORS 0x80000000
#define EVENT_MASK_EPOW 0x40000000
#define EVENT_MASK_HOTPLUG 0x10000000
#define EVENT_MASK_IO 0x08000000
typedef enum EventClass {
EVENT_CLASS_INTERNAL_ERRORS = 0,
EVENT_CLASS_EPOW = 1,
EVENT_CLASS_RESERVED = 2,
EVENT_CLASS_HOT_PLUG = 3,
EVENT_CLASS_IO = 4,
EVENT_CLASS_MAX
} EventClassIndex;
#define EVENT_CLASS_MASK(index) (1 << (31 - index))
static const char * const event_names[EVENT_CLASS_MAX] = {
[EVENT_CLASS_INTERNAL_ERRORS] = "internal-errors",
[EVENT_CLASS_EPOW] = "epow-events",
[EVENT_CLASS_HOT_PLUG] = "hot-plug-events",
[EVENT_CLASS_IO] = "ibm,io-events",
};
struct sPAPREventSource {
int irq;
uint32_t mask;
bool enabled;
};
static sPAPREventSource *spapr_event_sources_new(void)
{
return g_new0(sPAPREventSource, EVENT_CLASS_MAX);
}
static void spapr_event_sources_register(sPAPREventSource *event_sources,
EventClassIndex index, int irq)
{
/* we only support 1 irq per event class at the moment */
g_assert(event_sources);
g_assert(!event_sources[index].enabled);
event_sources[index].irq = irq;
event_sources[index].mask = EVENT_CLASS_MASK(index);
event_sources[index].enabled = true;
}
static const sPAPREventSource *
spapr_event_sources_get_source(sPAPREventSource *event_sources,
EventClassIndex index)
{
g_assert(index < EVENT_CLASS_MAX);
g_assert(event_sources);
return &event_sources[index];
}
void spapr_dt_events(sPAPRMachineState *spapr, void *fdt)
{
uint32_t irq_ranges[EVENT_CLASS_MAX * 2];
int i, count = 0, event_sources;
sPAPREventSource *events = spapr->event_sources;
g_assert(events);
_FDT(event_sources = fdt_add_subnode(fdt, 0, "event-sources"));
for (i = 0, count = 0; i < EVENT_CLASS_MAX; i++) {
int node_offset;
uint32_t interrupts[2];
const sPAPREventSource *source =
spapr_event_sources_get_source(events, i);
const char *source_name = event_names[i];
if (!source->enabled) {
continue;
}
interrupts[0] = cpu_to_be32(source->irq);
interrupts[1] = 0;
void spapr_events_fdt_skel(void *fdt, uint32_t check_exception_irq)
_FDT(node_offset = fdt_add_subnode(fdt, event_sources, source_name));
_FDT(fdt_setprop(fdt, node_offset, "interrupts", interrupts,
sizeof(interrupts)));
irq_ranges[count++] = interrupts[0];
irq_ranges[count++] = cpu_to_be32(1);
}
irq_ranges[count] = cpu_to_be32(count);
count++;
_FDT((fdt_setprop(fdt, event_sources, "interrupt-controller", NULL, 0)));
_FDT((fdt_setprop_cell(fdt, event_sources, "#interrupt-cells", 2)));
_FDT((fdt_setprop(fdt, event_sources, "interrupt-ranges",
irq_ranges, count * sizeof(uint32_t))));
}
static const sPAPREventSource *
rtas_event_log_to_source(sPAPRMachineState *spapr, int log_type)
{
uint32_t irq_ranges[] = {cpu_to_be32(check_exception_irq), cpu_to_be32(1)};
uint32_t interrupts[] = {cpu_to_be32(check_exception_irq), 0};
const sPAPREventSource *source;
g_assert(spapr->event_sources);
switch (log_type) {
case RTAS_LOG_TYPE_HOTPLUG:
source = spapr_event_sources_get_source(spapr->event_sources,
EVENT_CLASS_HOT_PLUG);
if (spapr_ovec_test(spapr->ov5_cas, OV5_HP_EVT)) {
g_assert(source->enabled);
break;
}
/* fall back to epow for legacy hotplug interrupt source */
case RTAS_LOG_TYPE_EPOW:
source = spapr_event_sources_get_source(spapr->event_sources,
EVENT_CLASS_EPOW);
break;
default:
source = NULL;
}
_FDT((fdt_begin_node(fdt, "event-sources")));
return source;
}
_FDT((fdt_property(fdt, "interrupt-controller", NULL, 0)));
_FDT((fdt_property_cell(fdt, "#interrupt-cells", 2)));
_FDT((fdt_property(fdt, "interrupt-ranges",
irq_ranges, sizeof(irq_ranges))));
static int rtas_event_log_to_irq(sPAPRMachineState *spapr, int log_type)
{
const sPAPREventSource *source;
_FDT((fdt_begin_node(fdt, "epow-events")));
_FDT((fdt_property(fdt, "interrupts", interrupts, sizeof(interrupts))));
_FDT((fdt_end_node(fdt)));
source = rtas_event_log_to_source(spapr, log_type);
g_assert(source);
g_assert(source->enabled);
_FDT((fdt_end_node(fdt)));
return source->irq;
}
static void rtas_event_log_queue(int log_type, void *data, bool exception)
......@@ -248,19 +360,15 @@ static sPAPREventLogEntry *rtas_event_log_dequeue(uint32_t event_mask,
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
sPAPREventLogEntry *entry = NULL;
/* we only queue EPOW events atm. */
if ((event_mask & EVENT_MASK_EPOW) == 0) {
return NULL;
}
QTAILQ_FOREACH(entry, &spapr->pending_events, next) {
const sPAPREventSource *source =
rtas_event_log_to_source(spapr, entry->log_type);
if (entry->exception != exception) {
continue;
}
/* EPOW and hotplug events are surfaced in the same manner */
if (entry->log_type == RTAS_LOG_TYPE_EPOW ||
entry->log_type == RTAS_LOG_TYPE_HOTPLUG) {
if (source->mask & event_mask) {
break;
}
}
......@@ -277,19 +385,15 @@ static bool rtas_event_log_contains(uint32_t event_mask, bool exception)
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
sPAPREventLogEntry *entry = NULL;
/* we only queue EPOW events atm. */
if ((event_mask & EVENT_MASK_EPOW) == 0) {
return false;
}
QTAILQ_FOREACH(entry, &spapr->pending_events, next) {
const sPAPREventSource *source =
rtas_event_log_to_source(spapr, entry->log_type);
if (entry->exception != exception) {
continue;
}
/* EPOW and hotplug events are surfaced in the same manner */
if (entry->log_type == RTAS_LOG_TYPE_EPOW ||
entry->log_type == RTAS_LOG_TYPE_HOTPLUG) {
if (source->mask & event_mask) {
return true;
}
}
......@@ -377,7 +481,9 @@ static void spapr_powerdown_req(Notifier *n, void *opaque)
rtas_event_log_queue(RTAS_LOG_TYPE_EPOW, new_epow, true);
qemu_irq_pulse(xics_get_qirq(spapr->xics, spapr->check_exception_irq));
qemu_irq_pulse(xics_get_qirq(spapr->xics,
rtas_event_log_to_irq(spapr,
RTAS_LOG_TYPE_EPOW)));
}
static void spapr_hotplug_set_signalled(uint32_t drc_index)
......@@ -389,7 +495,7 @@ static void spapr_hotplug_set_signalled(uint32_t drc_index)
static void spapr_hotplug_req_event(uint8_t hp_id, uint8_t hp_action,
sPAPRDRConnectorType drc_type,
uint32_t drc)
union drc_identifier *drc_id)
{
sPAPRMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
struct hp_log_full *new_hp;
......@@ -434,7 +540,7 @@ static void spapr_hotplug_req_event(uint8_t hp_id, uint8_t hp_action,
case SPAPR_DR_CONNECTOR_TYPE_PCI:
hp->hotplug_type = RTAS_LOG_V6_HP_TYPE_PCI;
if (hp->hotplug_action == RTAS_LOG_V6_HP_ACTION_ADD) {
spapr_hotplug_set_signalled(drc);
spapr_hotplug_set_signalled(drc_id->index);
}
break;
case SPAPR_DR_CONNECTOR_TYPE_LMB:
......@@ -452,48 +558,89 @@ static void spapr_hotplug_req_event(uint8_t hp_id, uint8_t hp_action,
}
if (hp_id == RTAS_LOG_V6_HP_ID_DRC_COUNT) {
hp->drc.count = cpu_to_be32(drc);
hp->drc_id.count = cpu_to_be32(drc_id->count);
} else if (hp_id == RTAS_LOG_V6_HP_ID_DRC_INDEX) {
hp->drc.index = cpu_to_be32(drc);
hp->drc_id.index = cpu_to_be32(drc_id->index);
} else if (hp_id == RTAS_LOG_V6_HP_ID_DRC_COUNT_INDEXED) {
/* we should not be using count_indexed value unless the guest
* supports dedicated hotplug event source
*/
g_assert(spapr_ovec_test(spapr->ov5_cas, OV5_HP_EVT));
hp->drc_id.count_indexed.count =
cpu_to_be32(drc_id->count_indexed.count);
hp->drc_id.count_indexed.index =
cpu_to_be32(drc_id->count_indexed.index);
}
rtas_event_log_queue(RTAS_LOG_TYPE_HOTPLUG, new_hp, true);
qemu_irq_pulse(xics_get_qirq(spapr->xics, spapr->check_exception_irq));
qemu_irq_pulse(xics_get_qirq(spapr->xics,
rtas_event_log_to_irq(spapr,
RTAS_LOG_TYPE_HOTPLUG)));
}
void spapr_hotplug_req_add_by_index(sPAPRDRConnector *drc)
{
sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
sPAPRDRConnectorType drc_type = drck->get_type(drc);
uint32_t index = drck->get_index(drc);
union drc_identifier drc_id;
drc_id.index = drck->get_index(drc);
spapr_hotplug_req_event(RTAS_LOG_V6_HP_ID_DRC_INDEX,
RTAS_LOG_V6_HP_ACTION_ADD, drc_type, index);
RTAS_LOG_V6_HP_ACTION_ADD, drc_type, &drc_id);
}
void spapr_hotplug_req_remove_by_index(sPAPRDRConnector *drc)
{
sPAPRDRConnectorClass *drck = SPAPR_DR_CONNECTOR_GET_CLASS(drc);
sPAPRDRConnectorType drc_type = drck->get_type(drc);
uint32_t index = drck->get_index(drc);
union drc_identifier drc_id;
drc_id.index = drck->get_index(drc);
spapr_hotplug_req_event(RTAS_LOG_V6_HP_ID_DRC_INDEX,
RTAS_LOG_V6_HP_ACTION_REMOVE, drc_type, index);
RTAS_LOG_V6_HP_ACTION_REMOVE, drc_type, &drc_id);
}
void spapr_hotplug_req_add_by_count(sPAPRDRConnectorType drc_type,
uint32_t count)
{
union drc_identifier drc_id;
drc_id.count = count;
spapr_hotplug_req_event(RTAS_LOG_V6_HP_ID_DRC_COUNT,
RTAS_LOG_V6_HP_ACTION_ADD, drc_type, count);
RTAS_LOG_V6_HP_ACTION_ADD, drc_type, &drc_id);
}
void spapr_hotplug_req_remove_by_count(sPAPRDRConnectorType drc_type,
uint32_t count)
{
union drc_identifier drc_id;
drc_id.count = count;
spapr_hotplug_req_event(RTAS_LOG_V6_HP_ID_DRC_COUNT,
RTAS_LOG_V6_HP_ACTION_REMOVE, drc_type, count);
RTAS_LOG_V6_HP_ACTION_REMOVE, drc_type, &drc_id);
}
void spapr_hotplug_req_add_by_count_indexed(sPAPRDRConnectorType drc_type,
uint32_t count, uint32_t index)
{
union drc_identifier drc_id;
drc_id.count_indexed.count = count;
drc_id.count_indexed.index = index;
spapr_hotplug_req_event(RTAS_LOG_V6_HP_ID_DRC_COUNT_INDEXED,
RTAS_LOG_V6_HP_ACTION_ADD, drc_type, &drc_id);
}
void spapr_hotplug_req_remove_by_count_indexed(sPAPRDRConnectorType drc_type,
uint32_t count, uint32_t index)
{
union drc_identifier drc_id;
drc_id.count_indexed.count = count;
drc_id.count_indexed.index = index;
spapr_hotplug_req_event(RTAS_LOG_V6_HP_ID_DRC_COUNT_INDEXED,
RTAS_LOG_V6_HP_ACTION_REMOVE, drc_type, &drc_id);
}
static void check_exception(PowerPCCPU *cpu, sPAPRMachineState *spapr,
......@@ -505,6 +652,7 @@ static void check_exception(PowerPCCPU *cpu, sPAPRMachineState *spapr,
uint64_t xinfo;
sPAPREventLogEntry *event;
struct rtas_error_log *hdr;
int i;
if ((nargs < 6) || (nargs > 7) || nret != 1) {
rtas_st(rets, 0, RTAS_OUT_PARAM_ERROR);
......@@ -541,8 +689,14 @@ static void check_exception(PowerPCCPU *cpu, sPAPRMachineState *spapr,
* do the latter here, since our code relies on edge-triggered
* interrupts.
*/
if (rtas_event_log_contains(mask, true)) {
qemu_irq_pulse(xics_get_qirq(spapr->xics, spapr->check_exception_irq));
for (i = 0; i < EVENT_CLASS_MAX; i++) {
if (rtas_event_log_contains(EVENT_CLASS_MASK(i), true)) {
const sPAPREventSource *source =
spapr_event_sources_get_source(spapr->event_sources, i);
g_assert(source->enabled);
qemu_irq_pulse(xics_get_qirq(spapr->xics, source->irq));
}
}
return;
......@@ -594,8 +748,27 @@ out_no_events:
void spapr_events_init(sPAPRMachineState *spapr)
{
QTAILQ_INIT(&spapr->pending_events);
spapr->check_exception_irq = xics_spapr_alloc(spapr->xics, 0, false,
&error_fatal);
spapr->event_sources = spapr_event_sources_new();
spapr_event_sources_register(spapr->event_sources, EVENT_CLASS_EPOW,
xics_spapr_alloc(spapr->xics, 0, false,
&error_fatal));
/* NOTE: if machine supports modern/dedicated hotplug event source,
* we add it to the device-tree unconditionally. This means we may
* have cases where the source is enabled in QEMU, but unused by the
* guest because it does not support modern hotplug events, so we
* take care to rely on checking for negotiation of OV5_HP_EVT option
* before attempting to use it to signal events, rather than simply
* checking that it's enabled.
*/
if (spapr->use_hotplug_event_source) {
spapr_event_sources_register(spapr->event_sources, EVENT_CLASS_HOT_PLUG,
xics_spapr_alloc(spapr->xics, 0, false,
&error_fatal));
}
spapr->epow_notifier.notify = spapr_powerdown_req;
qemu_register_powerdown_notifier(&spapr->epow_notifier);
spapr_rtas_register(RTAS_CHECK_EXCEPTION, "check-exception",
......
......@@ -11,6 +11,7 @@
#include "trace.h"
#include "sysemu/kvm.h"
#include "kvm_ppc.h"
#include "hw/ppc/spapr_ovec.h"
struct SPRSyncState {
int spr;
......@@ -880,32 +881,6 @@ static target_ulong h_set_mode(PowerPCCPU *cpu, sPAPRMachineState *spapr,
return ret;
}
/*
* Return the offset to the requested option vector @vector in the
* option vector table @table.
*/
static target_ulong cas_get_option_vector(int vector, target_ulong table)
{
int i;
char nr_vectors, nr_entries;
if (!table) {
return 0;
}
nr_vectors = (ldl_phys(&address_space_memory, table) >> 24) + 1;
if (!vector || vector > nr_vectors) {
return 0;
}
table++; /* skip nr option vectors */
for (i = 0; i < vector - 1; i++) {
nr_entries = ldl_phys(&address_space_memory, table) >> 24;
table += nr_entries + 2;
}
return table;
}
typedef struct {
uint32_t cpu_version;
Error *err;
......@@ -961,23 +936,21 @@ static void cas_handle_compat_cpu(PowerPCCPUClass *pcc, uint32_t pvr,
}
}
#define OV5_DRCONF_MEMORY 0x20
static target_ulong h_client_architecture_support(PowerPCCPU *cpu_,
sPAPRMachineState *spapr,
target_ulong opcode,
target_ulong *args)
{
target_ulong list = ppc64_phys_to_real(args[0]);
target_ulong ov_table, ov5;
target_ulong ov_table;
PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cpu_);
CPUState *cs;
bool cpu_match = false, cpu_update = true, memory_update = false;
bool cpu_match = false, cpu_update = true;
unsigned old_cpu_version = cpu_->cpu_version;
unsigned compat_lvl = 0, cpu_version = 0;
unsigned max_lvl = get_compat_level(cpu_->max_compat);
int counter;
char ov5_byte2;
sPAPROptionVector *ov5_guest, *ov5_cas_old, *ov5_updates;
/* Parse PVR list */
for (counter = 0; counter < 512; ++counter) {
......@@ -1033,19 +1006,34 @@ static target_ulong h_client_architecture_support(PowerPCCPU *cpu_,
/* For the future use: here @ov_table points to the first option vector */
ov_table = list;
ov5 = cas_get_option_vector(5, ov_table);
if (!ov5) {
return H_SUCCESS;
}
ov5_guest = spapr_ovec_parse_vector(ov_table, 5);
/* NOTE: there are actually a number of ov5 bits where input from the
* guest is always zero, and the platform/QEMU enables them independently
* of guest input. To model these properly we'd want some sort of mask,
* but since they only currently apply to memory migration as defined
* by LoPAPR 1.1, 14.5.4.8, which QEMU doesn't implement, we don't need
* to worry about this for now.
*/
ov5_cas_old = spapr_ovec_clone(spapr->ov5_cas);
/* full range of negotiated ov5 capabilities */
spapr_ovec_intersect(spapr->ov5_cas, spapr->ov5, ov5_guest);
spapr_ovec_cleanup(ov5_guest);
/* capabilities that have been added since CAS-generated guest reset.
* if capabilities have since been removed, generate another reset
*/
ov5_updates = spapr_ovec_new();
spapr->cas_reboot = spapr_ovec_diff(ov5_updates,
ov5_cas_old, spapr->ov5_cas);
/* @list now points to OV 5 */
ov5_byte2 = ldub_phys(&address_space_memory, ov5 + 2);
if (ov5_byte2 & OV5_DRCONF_MEMORY) {
memory_update = true;
if (!spapr->cas_reboot) {
spapr->cas_reboot =
(spapr_h_cas_compose_response(spapr, args[1], args[2], cpu_update,
ov5_updates) != 0);
}
spapr_ovec_cleanup(ov5_updates);
if (spapr_h_cas_compose_response(spapr, args[1], args[2],
cpu_update, memory_update)) {
if (spapr->cas_reboot) {
qemu_system_reset_request();
}
......
/*
* QEMU SPAPR Architecture Option Vector Helper Functions
*
* Copyright IBM Corp. 2016
*
* Authors:
* Bharata B Rao <bharata@linux.vnet.ibm.com>
* Michael Roth <mdroth@linux.vnet.ibm.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "hw/ppc/spapr_ovec.h"
#include "qemu/bitmap.h"
#include "exec/address-spaces.h"
#include "qemu/error-report.h"
#include <libfdt.h>
/* #define DEBUG_SPAPR_OVEC */
#ifdef DEBUG_SPAPR_OVEC
#define DPRINTFN(fmt, ...) \
do { fprintf(stderr, fmt "\n", ## __VA_ARGS__); } while (0)
#else
#define DPRINTFN(fmt, ...) \
do { } while (0)
#endif
#define OV_MAXBYTES 256 /* not including length byte */
#define OV_MAXBITS (OV_MAXBYTES * BITS_PER_BYTE)
/* we *could* work with bitmaps directly, but handling the bitmap privately
* allows us to more safely make assumptions about the bitmap size and
* simplify the calling code somewhat
*/
struct sPAPROptionVector {
unsigned long *bitmap;
};
sPAPROptionVector *spapr_ovec_new(void)
{
sPAPROptionVector *ov;
ov = g_new0(sPAPROptionVector, 1);
ov->bitmap = bitmap_new(OV_MAXBITS);
return ov;
}
sPAPROptionVector *spapr_ovec_clone(sPAPROptionVector *ov_orig)
{
sPAPROptionVector *ov;
g_assert(ov_orig);
ov = spapr_ovec_new();
bitmap_copy(ov->bitmap, ov_orig->bitmap, OV_MAXBITS);
return ov;
}
void spapr_ovec_intersect(sPAPROptionVector *ov,
sPAPROptionVector *ov1,
sPAPROptionVector *ov2)
{
g_assert(ov);
g_assert(ov1);
g_assert(ov2);
bitmap_and(ov->bitmap, ov1->bitmap, ov2->bitmap, OV_MAXBITS);
}
/* returns true if options bits were removed, false otherwise */
bool spapr_ovec_diff(sPAPROptionVector *ov,
sPAPROptionVector *ov_old,
sPAPROptionVector *ov_new)
{
unsigned long *change_mask = bitmap_new(OV_MAXBITS);
unsigned long *removed_bits = bitmap_new(OV_MAXBITS);
bool bits_were_removed = false;
g_assert(ov);
g_assert(ov_old);
g_assert(ov_new);
bitmap_xor(change_mask, ov_old->bitmap, ov_new->bitmap, OV_MAXBITS);
bitmap_and(ov->bitmap, ov_new->bitmap, change_mask, OV_MAXBITS);
bitmap_and(removed_bits, ov_old->bitmap, change_mask, OV_MAXBITS);
if (!bitmap_empty(removed_bits, OV_MAXBITS)) {
bits_were_removed = true;
}
g_free(change_mask);
g_free(removed_bits);
return bits_were_removed;
}
void spapr_ovec_cleanup(sPAPROptionVector *ov)
{
if (ov) {
g_free(ov->bitmap);
g_free(ov);
}
}
void spapr_ovec_set(sPAPROptionVector *ov, long bitnr)
{
g_assert(ov);
g_assert_cmpint(bitnr, <, OV_MAXBITS);
set_bit(bitnr, ov->bitmap);
}
void spapr_ovec_clear(sPAPROptionVector *ov, long bitnr)
{
g_assert(ov);
g_assert_cmpint(bitnr, <, OV_MAXBITS);
clear_bit(bitnr, ov->bitmap);
}
bool spapr_ovec_test(sPAPROptionVector *ov, long bitnr)
{
g_assert(ov);
g_assert_cmpint(bitnr, <, OV_MAXBITS);
return test_bit(bitnr, ov->bitmap) ? true : false;
}
static void guest_byte_to_bitmap(uint8_t entry, unsigned long *bitmap,
long bitmap_offset)
{
int i;
for (i = 0; i < BITS_PER_BYTE; i++) {
if (entry & (1 << (BITS_PER_BYTE - 1 - i))) {
bitmap_set(bitmap, bitmap_offset + i, 1);
}
}
}
static uint8_t guest_byte_from_bitmap(unsigned long *bitmap, long bitmap_offset)
{
uint8_t entry = 0;
int i;
for (i = 0; i < BITS_PER_BYTE; i++) {
if (test_bit(bitmap_offset + i, bitmap)) {
entry |= (1 << (BITS_PER_BYTE - 1 - i));
}
}
return entry;
}
static target_ulong vector_addr(target_ulong table_addr, int vector)
{
uint16_t vector_count, vector_len;
int i;
vector_count = ldub_phys(&address_space_memory, table_addr) + 1;
if (vector > vector_count) {
return 0;
}
table_addr++; /* skip nr option vectors */
for (i = 0; i < vector - 1; i++) {
vector_len = ldub_phys(&address_space_memory, table_addr) + 1;
table_addr += vector_len + 1; /* bit-vector + length byte */
}
return table_addr;
}
sPAPROptionVector *spapr_ovec_parse_vector(target_ulong table_addr, int vector)
{
sPAPROptionVector *ov;
target_ulong addr;
uint16_t vector_len;
int i;
g_assert(table_addr);
g_assert_cmpint(vector, >=, 1); /* vector numbering starts at 1 */
addr = vector_addr(table_addr, vector);
if (!addr) {
/* specified vector isn't present */
return NULL;
}
vector_len = ldub_phys(&address_space_memory, addr++) + 1;
g_assert_cmpint(vector_len, <=, OV_MAXBYTES);
ov = spapr_ovec_new();
for (i = 0; i < vector_len; i++) {
uint8_t entry = ldub_phys(&address_space_memory, addr + i);
if (entry) {
DPRINTFN("read guest vector %2d, byte %3d / %3d: 0x%.2x",
vector, i + 1, vector_len, entry);
guest_byte_to_bitmap(entry, ov->bitmap, i * BITS_PER_BYTE);
}
}
return ov;
}
int spapr_ovec_populate_dt(void *fdt, int fdt_offset,
sPAPROptionVector *ov, const char *name)
{
uint8_t vec[OV_MAXBYTES + 1];
uint16_t vec_len;
unsigned long lastbit;
int i;
g_assert(ov);
lastbit = find_last_bit(ov->bitmap, OV_MAXBITS);
/* if no bits are set, include at least 1 byte of the vector so we can
* still encoded this in the device tree while abiding by the same
* encoding/sizing expected in ibm,client-architecture-support
*/
vec_len = (lastbit == OV_MAXBITS) ? 1 : lastbit / BITS_PER_BYTE + 1;
g_assert_cmpint(vec_len, <=, OV_MAXBYTES);
/* guest expects vector len encoded as vec_len - 1, since the length byte
* is assumed and not included, and the first byte of the vector
* is assumed as well
*/
vec[0] = vec_len - 1;
for (i = 1; i < vec_len + 1; i++) {
vec[i] = guest_byte_from_bitmap(ov->bitmap, (i - 1) * BITS_PER_BYTE);
if (vec[i]) {
DPRINTFN("encoding guest vector byte %3d / %3d: 0x%.2x",
i, vec_len, vec[i]);
}
}
return fdt_setprop(fdt, fdt_offset, name, vec, vec_len);
}
......@@ -1392,6 +1392,12 @@ static void spapr_phb_realize(DeviceState *dev, Error **errp)
return;
}
if (sphb->numa_node != -1 &&
(sphb->numa_node >= MAX_NODES || !numa_info[sphb->numa_node].present)) {
error_setg(errp, "Invalid NUMA node ID for PCI host bridge");
return;
}
sphb->dtbusname = g_strdup_printf("pci@%" PRIx64, sphb->buid);
namebuf = alloca(strlen(sphb->dtbusname) + 32);
......@@ -1880,7 +1886,7 @@ int spapr_populate_pci_dt(sPAPRPHBState *phb,
}
/* Advertise NUMA via ibm,associativity */
if (nb_numa_nodes > 1) {
if (phb->numa_node != -1) {
_FDT(fdt_setprop(fdt, bus_off, "ibm,associativity", associativity,
sizeof(associativity)));
}
......
......@@ -46,6 +46,7 @@
#include "hw/ppc/spapr_drc.h"
#include "qemu/cutils.h"
#include "trace.h"
#include "hw/ppc/fdt.h"
static sPAPRConfigureConnectorState *spapr_ccs_find(sPAPRMachineState *spapr,
uint32_t drc_index)
......@@ -710,78 +711,60 @@ void spapr_rtas_register(int token, const char *name, spapr_rtas_fn fn)
rtas_table[token].fn = fn;
}
int spapr_rtas_device_tree_setup(void *fdt, hwaddr rtas_addr,
hwaddr rtas_size)
void spapr_dt_rtas_tokens(void *fdt, int rtas)
{
int ret;
int i;
uint32_t lrdr_capacity[5];
MachineState *machine = MACHINE(qdev_get_machine());
sPAPRMachineState *spapr = SPAPR_MACHINE(machine);
uint64_t max_hotplug_addr = spapr->hotplug_memory.base +
memory_region_size(&spapr->hotplug_memory.mr);
ret = fdt_add_mem_rsv(fdt, rtas_addr, rtas_size);
for (i = 0; i < RTAS_TOKEN_MAX - RTAS_TOKEN_BASE; i++) {
struct rtas_call *call = &rtas_table[i];
if (!call->name) {
continue;
}
_FDT(fdt_setprop_cell(fdt, rtas, call->name, i + RTAS_TOKEN_BASE));
}
}
void spapr_load_rtas(sPAPRMachineState *spapr, void *fdt, hwaddr addr)
{
int rtas_node;
int ret;
/* Copy RTAS blob into guest RAM */
cpu_physical_memory_write(addr, spapr->rtas_blob, spapr->rtas_size);
ret = fdt_add_mem_rsv(fdt, addr, spapr->rtas_size);
if (ret < 0) {
error_report("Couldn't add RTAS reserve entry: %s",
fdt_strerror(ret));
return ret;
fdt_strerror(ret));
exit(1);
}
ret = qemu_fdt_setprop_cell(fdt, "/rtas", "linux,rtas-base",
rtas_addr);
/* Update the device tree with the blob's location */
rtas_node = fdt_path_offset(fdt, "/rtas");
assert(rtas_node >= 0);
ret = fdt_setprop_cell(fdt, rtas_node, "linux,rtas-base", addr);
if (ret < 0) {
error_report("Couldn't add linux,rtas-base property: %s",
fdt_strerror(ret));
return ret;
fdt_strerror(ret));
exit(1);
}
ret = qemu_fdt_setprop_cell(fdt, "/rtas", "linux,rtas-entry",
rtas_addr);
ret = fdt_setprop_cell(fdt, rtas_node, "linux,rtas-entry", addr);
if (ret < 0) {
error_report("Couldn't add linux,rtas-entry property: %s",
fdt_strerror(ret));
return ret;
fdt_strerror(ret));
exit(1);
}
ret = qemu_fdt_setprop_cell(fdt, "/rtas", "rtas-size",
rtas_size);
ret = fdt_setprop_cell(fdt, rtas_node, "rtas-size", spapr->rtas_size);
if (ret < 0) {
error_report("Couldn't add rtas-size property: %s",
fdt_strerror(ret));
return ret;
fdt_strerror(ret));
exit(1);
}
for (i = 0; i < RTAS_TOKEN_MAX - RTAS_TOKEN_BASE; i++) {
struct rtas_call *call = &rtas_table[i];
if (!call->name) {
continue;
}
ret = qemu_fdt_setprop_cell(fdt, "/rtas", call->name,
i + RTAS_TOKEN_BASE);
if (ret < 0) {
error_report("Couldn't add rtas token for %s: %s",
call->name, fdt_strerror(ret));
return ret;
}
}
lrdr_capacity[0] = cpu_to_be32(max_hotplug_addr >> 32);
lrdr_capacity[1] = cpu_to_be32(max_hotplug_addr & 0xffffffff);
lrdr_capacity[2] = 0;
lrdr_capacity[3] = cpu_to_be32(SPAPR_MEMORY_BLOCK_SIZE);
lrdr_capacity[4] = cpu_to_be32(max_cpus/smp_threads);
ret = qemu_fdt_setprop(fdt, "/rtas", "ibm,lrdr-capacity", lrdr_capacity,
sizeof(lrdr_capacity));
if (ret < 0) {
error_report("Couldn't add ibm,lrdr-capacity rtas property");
return ret;
}
return 0;
}
static void core_rtas_register_types(void)
......
......@@ -36,6 +36,7 @@
#include "hw/ppc/spapr.h"
#include "hw/ppc/spapr_vio.h"
#include "hw/ppc/xics.h"
#include "hw/ppc/fdt.h"
#include "trace.h"
#include <libfdt.h>
......@@ -624,11 +625,21 @@ static int compare_reg(const void *p1, const void *p2)
return 1;
}
int spapr_populate_vdevice(VIOsPAPRBus *bus, void *fdt)
void spapr_dt_vdevice(VIOsPAPRBus *bus, void *fdt)
{
DeviceState *qdev, **qdevs;
BusChild *kid;
int i, num, ret = 0;
int node;
_FDT(node = fdt_add_subnode(fdt, 0, "vdevice"));
_FDT(fdt_setprop_string(fdt, node, "device_type", "vdevice"));
_FDT(fdt_setprop_string(fdt, node, "compatible", "IBM,vdevice"));
_FDT(fdt_setprop_cell(fdt, node, "#address-cells", 1));
_FDT(fdt_setprop_cell(fdt, node, "#size-cells", 0));
_FDT(fdt_setprop_cell(fdt, node, "#interrupt-cells", 2));
_FDT(fdt_setprop(fdt, node, "interrupt-controller", NULL, 0));
/* Count qdevs on the bus list */
num = 0;
......@@ -650,43 +661,32 @@ int spapr_populate_vdevice(VIOsPAPRBus *bus, void *fdt)
* to know that will mean they are in forward order in the tree. */
for (i = num - 1; i >= 0; i--) {
VIOsPAPRDevice *dev = (VIOsPAPRDevice *)(qdevs[i]);
VIOsPAPRDeviceClass *vdc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
ret = vio_make_devnode(dev, fdt);
if (ret < 0) {
goto out;
error_report("Couldn't create device node /vdevice/%s@%"PRIx32,
vdc->dt_name, dev->reg);
exit(1);
}
}
ret = 0;
out:
g_free(qdevs);
return ret;
}
int spapr_populate_chosen_stdout(void *fdt, VIOsPAPRBus *bus)
gchar *spapr_vio_stdout_path(VIOsPAPRBus *bus)
{
VIOsPAPRDevice *dev;
char *name, *path;
int ret, offset;
dev = spapr_vty_get_default(bus);
if (!dev)
return 0;
offset = fdt_path_offset(fdt, "/chosen");
if (offset < 0) {
return offset;
if (!dev) {
return NULL;
}
name = spapr_vio_get_dev_name(DEVICE(dev));
path = g_strdup_printf("/vdevice/%s", name);
ret = fdt_setprop_string(fdt, offset, "linux,stdout-path", path);
g_free(name);
g_free(path);
return ret;
return path;
}
......@@ -35,10 +35,11 @@
#include "sysemu/sysemu.h"
#include "net/net.h"
#include "hw/boards.h"
#include "hw/nvram/openbios_firmware_abi.h"
#include "hw/scsi/esp.h"
#include "hw/i386/pc.h"
#include "hw/isa/isa.h"
#include "hw/nvram/sun_nvram.h"
#include "hw/nvram/chrp_nvram.h"
#include "hw/nvram/fw_cfg.h"
#include "hw/char/escc.h"
#include "hw/empty_slot.h"
......@@ -117,39 +118,17 @@ static void nvram_init(Nvram *nvram, uint8_t *macaddr,
int nvram_machine_id, const char *arch)
{
unsigned int i;
uint32_t start, end;
int sysp_end;
uint8_t image[0x1ff0];
struct OpenBIOS_nvpart_v1 *part_header;
NvramClass *k = NVRAM_GET_CLASS(nvram);
memset(image, '\0', sizeof(image));
start = 0;
/* OpenBIOS nvram variables partition */
sysp_end = chrp_nvram_create_system_partition(image, 0);
// OpenBIOS nvram variables
// Variable partition
part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
part_header->signature = OPENBIOS_PART_SYSTEM;
pstrcpy(part_header->name, sizeof(part_header->name), "system");
end = start + sizeof(struct OpenBIOS_nvpart_v1);
for (i = 0; i < nb_prom_envs; i++)
end = OpenBIOS_set_var(image, end, prom_envs[i]);
// End marker
image[end++] = '\0';
end = start + ((end - start + 15) & ~15);
OpenBIOS_finish_partition(part_header, end - start);
// free partition
start = end;
part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
part_header->signature = OPENBIOS_PART_FREE;
pstrcpy(part_header->name, sizeof(part_header->name), "free");
end = 0x1fd0;
OpenBIOS_finish_partition(part_header, end - start);
/* Free space partition */
chrp_nvram_create_free_partition(&image[sysp_end], 0x1fd0 - sysp_end);
Sun_init_header((struct Sun_nvram *)&image[0x1fd8], macaddr,
nvram_machine_id);
......
......@@ -36,7 +36,8 @@
#include "qemu/timer.h"
#include "sysemu/sysemu.h"
#include "hw/boards.h"
#include "hw/nvram/openbios_firmware_abi.h"
#include "hw/nvram/sun_nvram.h"
#include "hw/nvram/chrp_nvram.h"
#include "hw/nvram/fw_cfg.h"
#include "hw/sysbus.h"
#include "hw/ide.h"
......@@ -124,39 +125,17 @@ static int sun4u_NVRAM_set_params(Nvram *nvram, uint16_t NVRAM_size,
const uint8_t *macaddr)
{
unsigned int i;
uint32_t start, end;
int sysp_end;
uint8_t image[0x1ff0];
struct OpenBIOS_nvpart_v1 *part_header;
NvramClass *k = NVRAM_GET_CLASS(nvram);
memset(image, '\0', sizeof(image));
start = 0;
/* OpenBIOS nvram variables partition */
sysp_end = chrp_nvram_create_system_partition(image, 0);
// OpenBIOS nvram variables
// Variable partition
part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
part_header->signature = OPENBIOS_PART_SYSTEM;
pstrcpy(part_header->name, sizeof(part_header->name), "system");
end = start + sizeof(struct OpenBIOS_nvpart_v1);
for (i = 0; i < nb_prom_envs; i++)
end = OpenBIOS_set_var(image, end, prom_envs[i]);
// End marker
image[end++] = '\0';
end = start + ((end - start + 15) & ~15);
OpenBIOS_finish_partition(part_header, end - start);
// free partition
start = end;
part_header = (struct OpenBIOS_nvpart_v1 *)&image[start];
part_header->signature = OPENBIOS_PART_FREE;
pstrcpy(part_header->name, sizeof(part_header->name), "free");
end = 0x1fd0;
OpenBIOS_finish_partition(part_header, end - start);
/* Free space partition */
chrp_nvram_create_free_partition(&image[sysp_end], 0x1fd0 - sysp_end);
Sun_init_header((struct Sun_nvram *)&image[0x1fd8], macaddr, 0x80);
......
/*
* Common Hardware Reference Platform NVRAM functions.
*
* This code 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef CHRP_NVRAM_H
#define CHRP_NVRAM_H
/* OpenBIOS NVRAM partition */
typedef struct {
uint8_t signature;
uint8_t checksum;
uint16_t len; /* Big endian, length divided by 16 */
char name[12];
} ChrpNvramPartHdr;
#define CHRP_NVPART_SYSTEM 0x70
#define CHRP_NVPART_FREE 0x7f
static inline void
chrp_nvram_finish_partition(ChrpNvramPartHdr *header, uint32_t size)
{
unsigned int i, sum;
uint8_t *tmpptr;
/* Length divided by 16 */
header->len = cpu_to_be16(size >> 4);
/* Checksum */
tmpptr = (uint8_t *)header;
sum = *tmpptr;
for (i = 0; i < 14; i++) {
sum += tmpptr[2 + i];
sum = (sum + ((sum & 0xff00) >> 8)) & 0xff;
}
header->checksum = sum & 0xff;
}
int chrp_nvram_create_system_partition(uint8_t *data, int min_len);
int chrp_nvram_create_free_partition(uint8_t *data, int len);
#endif
#ifndef OPENBIOS_FIRMWARE_ABI_H
#define OPENBIOS_FIRMWARE_ABI_H
/* OpenBIOS NVRAM partition */
struct OpenBIOS_nvpart_v1 {
uint8_t signature;
uint8_t checksum;
uint16_t len; // BE, length divided by 16
char name[12];
};
#define OPENBIOS_PART_SYSTEM 0x70
#define OPENBIOS_PART_FREE 0x7f
static inline void
OpenBIOS_finish_partition(struct OpenBIOS_nvpart_v1 *header, uint32_t size)
{
unsigned int i, sum;
uint8_t *tmpptr;
// Length divided by 16
header->len = cpu_to_be16(size >> 4);
// Checksum
tmpptr = (uint8_t *)header;
sum = *tmpptr;
for (i = 0; i < 14; i++) {
sum += tmpptr[2 + i];
sum = (sum + ((sum & 0xff00) >> 8)) & 0xff;
}
header->checksum = sum & 0xff;
}
static inline uint32_t
OpenBIOS_set_var(uint8_t *nvram, uint32_t addr, const char *str)
{
uint32_t len;
len = strlen(str) + 1;
memcpy(&nvram[addr], str, len);
return addr + len;
}
#ifndef SUN_NVRAM_H
#define SUN_NVRAM_H
/* Sun IDPROM structure at the end of NVRAM */
/* from http://www.squirrel.com/squirrel/sun-nvram-hostid.faq.html */
......@@ -72,4 +31,4 @@ Sun_init_header(struct Sun_nvram *header, const uint8_t *macaddr, int machine_id
header->checksum = tmp;
}
#endif /* OPENBIOS_FIRMWARE_ABI_H */
#endif /* SUN_NVRAM_H */
/*
* QEMU PowerPC PowerNV various definitions
*
* Copyright (c) 2014-2016 BenH, IBM Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _PPC_PNV_H
#define _PPC_PNV_H
#include "hw/boards.h"
#include "hw/sysbus.h"
#include "hw/ppc/pnv_xscom.h"
#include "hw/ppc/pnv_lpc.h"
#define TYPE_PNV_CHIP "powernv-chip"
#define PNV_CHIP(obj) OBJECT_CHECK(PnvChip, (obj), TYPE_PNV_CHIP)
#define PNV_CHIP_CLASS(klass) \
OBJECT_CLASS_CHECK(PnvChipClass, (klass), TYPE_PNV_CHIP)
#define PNV_CHIP_GET_CLASS(obj) \
OBJECT_GET_CLASS(PnvChipClass, (obj), TYPE_PNV_CHIP)
typedef enum PnvChipType {
PNV_CHIP_POWER8E, /* AKA Murano (default) */
PNV_CHIP_POWER8, /* AKA Venice */
PNV_CHIP_POWER8NVL, /* AKA Naples */
PNV_CHIP_POWER9, /* AKA Nimbus */
} PnvChipType;
typedef struct PnvChip {
/*< private >*/
SysBusDevice parent_obj;
/*< public >*/
uint32_t chip_id;
uint64_t ram_start;
uint64_t ram_size;
uint32_t nr_cores;
uint64_t cores_mask;
void *cores;
hwaddr xscom_base;
MemoryRegion xscom_mmio;
MemoryRegion xscom;
AddressSpace xscom_as;
PnvLpcController lpc;
} PnvChip;
typedef struct PnvChipClass {
/*< private >*/
SysBusDeviceClass parent_class;
/*< public >*/
const char *cpu_model;
PnvChipType chip_type;
uint64_t chip_cfam_id;
uint64_t cores_mask;
hwaddr xscom_base;
uint32_t (*core_pir)(PnvChip *chip, uint32_t core_id);
} PnvChipClass;
#define TYPE_PNV_CHIP_POWER8E TYPE_PNV_CHIP "-POWER8E"
#define PNV_CHIP_POWER8E(obj) \
OBJECT_CHECK(PnvChip, (obj), TYPE_PNV_CHIP_POWER8E)
#define TYPE_PNV_CHIP_POWER8 TYPE_PNV_CHIP "-POWER8"
#define PNV_CHIP_POWER8(obj) \
OBJECT_CHECK(PnvChip, (obj), TYPE_PNV_CHIP_POWER8)
#define TYPE_PNV_CHIP_POWER8NVL TYPE_PNV_CHIP "-POWER8NVL"
#define PNV_CHIP_POWER8NVL(obj) \
OBJECT_CHECK(PnvChip, (obj), TYPE_PNV_CHIP_POWER8NVL)
#define TYPE_PNV_CHIP_POWER9 TYPE_PNV_CHIP "-POWER9"
#define PNV_CHIP_POWER9(obj) \
OBJECT_CHECK(PnvChip, (obj), TYPE_PNV_CHIP_POWER9)
/*
* This generates a HW chip id depending on an index:
*
* 0x0, 0x1, 0x10, 0x11
*
* 4 chips should be the maximum
*/
#define PNV_CHIP_HWID(i) ((((i) & 0x3e) << 3) | ((i) & 0x1))
#define TYPE_POWERNV_MACHINE MACHINE_TYPE_NAME("powernv")
#define POWERNV_MACHINE(obj) \
OBJECT_CHECK(PnvMachineState, (obj), TYPE_POWERNV_MACHINE)
typedef struct PnvMachineState {
/*< private >*/
MachineState parent_obj;
uint32_t initrd_base;
long initrd_size;
uint32_t num_chips;
PnvChip **chips;
ISABus *isa_bus;
} PnvMachineState;
#define PNV_FDT_ADDR 0x01000000
#define PNV_TIMEBASE_FREQ 512000000ULL
/*
* POWER8 MMIO base addresses
*/
#define PNV_XSCOM_SIZE 0x800000000ull
#define PNV_XSCOM_BASE(chip) \
(chip->xscom_base + ((uint64_t)(chip)->chip_id) * PNV_XSCOM_SIZE)
#endif /* _PPC_PNV_H */
/*
* QEMU PowerPC PowerNV CPU Core model
*
* Copyright (c) 2016, IBM Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _PPC_PNV_CORE_H
#define _PPC_PNV_CORE_H
#include "hw/cpu/core.h"
#define TYPE_PNV_CORE "powernv-cpu-core"
#define PNV_CORE(obj) \
OBJECT_CHECK(PnvCore, (obj), TYPE_PNV_CORE)
#define PNV_CORE_CLASS(klass) \
OBJECT_CLASS_CHECK(PnvCoreClass, (klass), TYPE_PNV_CORE)
#define PNV_CORE_GET_CLASS(obj) \
OBJECT_GET_CLASS(PnvCoreClass, (obj), TYPE_PNV_CORE)
typedef struct PnvCore {
/*< private >*/
CPUCore parent_obj;
/*< public >*/
void *threads;
uint32_t pir;
MemoryRegion xscom_regs;
} PnvCore;
typedef struct PnvCoreClass {
DeviceClass parent_class;
ObjectClass *cpu_oc;
} PnvCoreClass;
extern char *pnv_core_typename(const char *model);
#endif /* _PPC_PNV_CORE_H */
/*
* QEMU PowerPC PowerNV LPC controller
*
* Copyright (c) 2016, IBM Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _PPC_PNV_LPC_H
#define _PPC_PNV_LPC_H
#define TYPE_PNV_LPC "pnv-lpc"
#define PNV_LPC(obj) \
OBJECT_CHECK(PnvLpcController, (obj), TYPE_PNV_LPC)
typedef struct PnvLpcController {
DeviceState parent;
uint64_t eccb_stat_reg;
uint32_t eccb_data_reg;
/* OPB bus */
MemoryRegion opb_mr;
AddressSpace opb_as;
/* ISA IO and Memory space */
MemoryRegion isa_io;
MemoryRegion isa_mem;
/* Windows from OPB to ISA (aliases) */
MemoryRegion opb_isa_io;
MemoryRegion opb_isa_mem;
MemoryRegion opb_isa_fw;
/* Registers */
MemoryRegion lpc_hc_regs;
MemoryRegion opb_master_regs;
/* OPB Master LS registers */
uint32_t opb_irq_stat;
uint32_t opb_irq_mask;
uint32_t opb_irq_pol;
uint32_t opb_irq_input;
/* LPC HC registers */
uint32_t lpc_hc_fw_seg_idsel;
uint32_t lpc_hc_fw_rd_acc_size;
uint32_t lpc_hc_irqser_ctrl;
uint32_t lpc_hc_irqmask;
uint32_t lpc_hc_irqstat;
uint32_t lpc_hc_error_addr;
/* XSCOM registers */
MemoryRegion xscom_regs;
} PnvLpcController;
#endif /* _PPC_PNV_LPC_H */
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -76,14 +76,12 @@ struct VIOsPAPRDevice {
struct VIOsPAPRBus {
BusState bus;
uint32_t next_reg;
int (*init)(VIOsPAPRDevice *dev);
int (*devnode)(VIOsPAPRDevice *dev, void *fdt, int node_off);
};
extern VIOsPAPRBus *spapr_vio_bus_init(void);
extern VIOsPAPRDevice *spapr_vio_find_by_reg(VIOsPAPRBus *bus, uint32_t reg);
extern int spapr_populate_vdevice(VIOsPAPRBus *bus, void *fdt);
extern int spapr_populate_chosen_stdout(void *fdt, VIOsPAPRBus *bus);
void spapr_dt_vdevice(VIOsPAPRBus *bus, void *fdt);
extern gchar *spapr_vio_stdout_path(VIOsPAPRBus *bus);
static inline qemu_irq spapr_vio_qirq(VIOsPAPRDevice *dev)
{
......
......@@ -117,6 +117,8 @@ struct ICPState {
uint8_t mfrr;
qemu_irq output;
bool cap_irq_xics_enabled;
XICSState *xics;
};
#define TYPE_ICS_BASE "ics-base"
......@@ -185,18 +187,21 @@ int xics_spapr_alloc(XICSState *icp, int irq_hint, bool lsi, Error **errp);
int xics_spapr_alloc_block(XICSState *icp, int num, bool lsi, bool align,
Error **errp);
void xics_spapr_free(XICSState *icp, int irq, int num);
void spapr_dt_xics(XICSState *xics, void *fdt, uint32_t phandle);
void xics_cpu_setup(XICSState *icp, PowerPCCPU *cpu);
void xics_cpu_destroy(XICSState *icp, PowerPCCPU *cpu);
void xics_set_nr_servers(XICSState *xics, uint32_t nr_servers,
const char *typename, Error **errp);
/* Internal XICS interfaces */
int xics_get_cpu_index_by_dt_id(int cpu_dt_id);
void icp_set_cppr(XICSState *icp, int server, uint8_t cppr);
void icp_set_mfrr(XICSState *icp, int server, uint8_t mfrr);
void icp_set_cppr(ICPState *icp, uint8_t cppr);
void icp_set_mfrr(ICPState *icp, uint8_t mfrr);
uint32_t icp_accept(ICPState *ss);
uint32_t icp_ipoll(ICPState *ss, uint32_t *mfrr);
void icp_eoi(XICSState *icp, int server, uint32_t xirr);
void icp_eoi(ICPState *icp, uint32_t xirr);
void ics_simple_write_xive(ICSState *ics, int nr, int server,
uint8_t priority, uint8_t saved_priority);
......
......@@ -17,7 +17,7 @@
- SLOF (Slimline Open Firmware) is a free IEEE 1275 Open Firmware
implementation for certain IBM POWER hardware. The sources are at
https://github.com/aik/SLOF, and the image currently in qemu is
built from git tag qemu-slof-20160223.
built from git tag qemu-slof-20161019.
- sgabios (the Serial Graphics Adapter option ROM) provides a means for
legacy x86 software to communicate with an attached serial console as
......@@ -42,3 +42,8 @@
it was compiled using the qemu-ppce500 target.
A git mirror is available at: git://git.qemu-project.org/u-boot.git
The hash used to compile the current version is: 2072e72
- Skiboot (https://github.com/open-power/skiboot/) is an OPAL
(OpenPower Abstraction Layer) firmware for OpenPOWER systems. It can
run an hypervisor OS or simply a host OS on the "baremetal"
platform, also known as the PowerNV (Non-Virtualized) platform.
文件已添加
无法预览此类型文件
此差异已折叠。
SLOF @ efd65f49
此差异已折叠。
skiboot @ 762d0082
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册