diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /arch/powerpc/platforms/ps3 | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/powerpc/platforms/ps3')
-rw-r--r-- | arch/powerpc/platforms/ps3/Kconfig | 172 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/Makefile | 9 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/device-init.c | 975 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/exports.c | 13 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/gelic_udbg.c | 244 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/htab.c | 195 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/hvcall.S | 792 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/interrupt.c | 782 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/mm.c | 1251 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/os-area.c | 832 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/platform.h | 253 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/repository.c | 1380 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/setup.c | 270 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/smp.c | 120 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/spu.c | 619 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/system-bus.c | 804 | ||||
-rw-r--r-- | arch/powerpc/platforms/ps3/time.c | 59 |
17 files changed, 8770 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/ps3/Kconfig b/arch/powerpc/platforms/ps3/Kconfig new file mode 100644 index 000000000..e32406e91 --- /dev/null +++ b/arch/powerpc/platforms/ps3/Kconfig @@ -0,0 +1,172 @@ +# SPDX-License-Identifier: GPL-2.0 +config PPC_PS3 + bool "Sony PS3" + depends on PPC64 && PPC_BOOK3S && CPU_BIG_ENDIAN + select PPC_CELL + select USB_OHCI_LITTLE_ENDIAN + select USB_OHCI_BIG_ENDIAN_MMIO + select USB_EHCI_BIG_ENDIAN_MMIO + select HAVE_PCI + help + This option enables support for the Sony PS3 game console + and other platforms using the PS3 hypervisor. Enabling this + option will allow building otheros.bld, a kernel image suitable + for programming into flash memory, and vmlinux, a kernel image + suitable for loading via kexec. + +menu "PS3 Platform Options" + depends on PPC_PS3 + +config PS3_ADVANCED + depends on PPC_PS3 + bool "PS3 Advanced configuration options" + help + This gives you access to some advanced options for the PS3. The + defaults should be fine for most users, but these options may make + it possible to better control the kernel configuration if you know + what you are doing. + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about these options. + + Most users should say N to this question. + +config PS3_HTAB_SIZE + depends on PPC_PS3 + int "PS3 Platform pagetable size" if PS3_ADVANCED + range 18 20 + default 20 + help + This option is only for experts who may have the desire to fine + tune the pagetable size on their system. The value here is + expressed as the log2 of the page table size. Valid values are + 18, 19, and 20, corresponding to 256KB, 512KB and 1MB respectively. + + If unsure, choose the default (20) with the confidence that your + system will have optimal runtime performance. + +config PS3_DYNAMIC_DMA + depends on PPC_PS3 + bool "PS3 Platform dynamic DMA page table management" + help + This option will enable kernel support to take advantage of the + per device dynamic DMA page table management provided by the Cell + processor's IO Controller. This support incurs some runtime + overhead and also slightly increases kernel memory usage. The + current implementation should be considered experimental. + + This support is mainly for Linux kernel development. If unsure, + say N. + +config PS3_VUART + depends on PPC_PS3 + tristate + +config PS3_PS3AV + depends on PPC_PS3 + tristate "PS3 AV settings driver" if PS3_ADVANCED + select PS3_VUART + default y + help + Include support for the PS3 AV Settings driver. + + This support is required for PS3 graphics and sound. In + general, all users will say Y or M. + +config PS3_SYS_MANAGER + depends on PPC_PS3 + tristate "PS3 System Manager driver" if PS3_ADVANCED + select PS3_VUART + default y + help + Include support for the PS3 System Manager. + + This support is required for PS3 system control. In + general, all users will say Y or M. + +config PS3_REPOSITORY_WRITE + bool "PS3 Repository write support" if PS3_ADVANCED + depends on PPC_PS3 + help + Enables support for writing to the PS3 System Repository. + + This support is intended for bootloaders that need to store data + in the repository for later boot stages. + + If in doubt, say N here and reduce the size of the kernel by a + small amount. + +config PS3_STORAGE + depends on PPC_PS3 + tristate + +config PS3_DISK + tristate "PS3 Disk Storage Driver" + depends on PPC_PS3 && BLOCK + select PS3_STORAGE + help + Include support for the PS3 Disk Storage. + + This support is required to access the PS3 hard disk. + In general, all users will say Y or M. + +config PS3_ROM + tristate "PS3 BD/DVD/CD-ROM Storage Driver" + depends on PPC_PS3 && SCSI + select PS3_STORAGE + help + Include support for the PS3 ROM Storage. + + This support is required to access the PS3 BD/DVD/CD-ROM drive. + In general, all users will say Y or M. + Also make sure to say Y or M to "SCSI CDROM support" later. + +config PS3_FLASH + tristate "PS3 FLASH ROM Storage Driver" + depends on PPC_PS3 + select PS3_STORAGE + help + Include support for the PS3 FLASH ROM Storage. + + This support is required to access the PS3 FLASH ROM, which + contains the boot loader and some boot options. + In general, PS3 OtherOS users will say Y or M. + + As this driver needs a fixed buffer of 256 KiB of memory, it can + be disabled on the kernel command line using "ps3flash=off", to + not allocate this fixed buffer. + +config PS3_VRAM + tristate "PS3 Video RAM Storage Driver" + depends on FB_PS3=y && BLOCK && m + help + This driver allows you to use excess PS3 video RAM as volatile + storage or system swap. + +config PS3_LPM + tristate "PS3 Logical Performance Monitor support" + depends on PPC_PS3 + help + Include support for the PS3 Logical Performance Monitor. + + This support is required to use the logical performance monitor + of the PS3's LV1 hypervisor. + + If you intend to use the advanced performance monitoring and + profiling support of the Cell processor with programs like + oprofile and perfmon2, then say Y or M, otherwise say N. + +config PS3GELIC_UDBG + bool "PS3 udbg output via UDP broadcasts on Ethernet" + depends on PPC_PS3 + help + Enables udbg early debugging output by sending broadcast UDP + via the Ethernet port (UDP port number 18194). + + This driver uses a trivial implementation and is independent + from the main PS3 gelic network driver. + + If in doubt, say N here. + +endmenu diff --git a/arch/powerpc/platforms/ps3/Makefile b/arch/powerpc/platforms/ps3/Makefile new file mode 100644 index 000000000..86bf2967a --- /dev/null +++ b/arch/powerpc/platforms/ps3/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-y += setup.o mm.o time.o hvcall.o htab.o repository.o +obj-y += interrupt.o exports.o os-area.o +obj-y += system-bus.o + +obj-$(CONFIG_PS3GELIC_UDBG) += gelic_udbg.o +obj-$(CONFIG_SMP) += smp.o +obj-$(CONFIG_SPU_BASE) += spu.o +obj-y += device-init.o diff --git a/arch/powerpc/platforms/ps3/device-init.c b/arch/powerpc/platforms/ps3/device-init.c new file mode 100644 index 000000000..e87360a0f --- /dev/null +++ b/arch/powerpc/platforms/ps3/device-init.c @@ -0,0 +1,975 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 device registration routines. + * + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2007 Sony Corp. + */ + +#include <linux/delay.h> +#include <linux/freezer.h> +#include <linux/kernel.h> +#include <linux/kthread.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/reboot.h> +#include <linux/rcuwait.h> + +#include <asm/firmware.h> +#include <asm/lv1call.h> +#include <asm/ps3stor.h> + +#include "platform.h" + +static int __init ps3_register_lpm_devices(void) +{ + int result; + u64 tmp1; + u64 tmp2; + struct ps3_system_bus_device *dev; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->match_id = PS3_MATCH_ID_LPM; + dev->dev_type = PS3_DEVICE_TYPE_LPM; + + /* The current lpm driver only supports a single BE processor. */ + + result = ps3_repository_read_be_node_id(0, &dev->lpm.node_id); + + if (result) { + pr_debug("%s:%d: ps3_repository_read_be_node_id failed \n", + __func__, __LINE__); + goto fail_read_repo; + } + + result = ps3_repository_read_lpm_privileges(dev->lpm.node_id, &tmp1, + &dev->lpm.rights); + + if (result) { + pr_debug("%s:%d: ps3_repository_read_lpm_privileges failed\n", + __func__, __LINE__); + goto fail_read_repo; + } + + lv1_get_logical_partition_id(&tmp2); + + if (tmp1 != tmp2) { + pr_debug("%s:%d: wrong lpar\n", + __func__, __LINE__); + result = -ENODEV; + goto fail_rights; + } + + if (!(dev->lpm.rights & PS3_LPM_RIGHTS_USE_LPM)) { + pr_debug("%s:%d: don't have rights to use lpm\n", + __func__, __LINE__); + result = -EPERM; + goto fail_rights; + } + + pr_debug("%s:%d: pu_id %llu, rights %llu(%llxh)\n", + __func__, __LINE__, dev->lpm.pu_id, dev->lpm.rights, + dev->lpm.rights); + + result = ps3_repository_read_pu_id(0, &dev->lpm.pu_id); + + if (result) { + pr_debug("%s:%d: ps3_repository_read_pu_id failed \n", + __func__, __LINE__); + goto fail_read_repo; + } + + result = ps3_system_bus_device_register(dev); + + if (result) { + pr_debug("%s:%d ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_register; + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return 0; + + +fail_register: +fail_rights: +fail_read_repo: + kfree(dev); + pr_debug(" <- %s:%d: failed\n", __func__, __LINE__); + return result; +} + +/** + * ps3_setup_gelic_device - Setup and register a gelic device instance. + * + * Allocates memory for a struct ps3_system_bus_device instance, initialises the + * structure members, and registers the device instance with the system bus. + */ + +static int __init ps3_setup_gelic_device( + const struct ps3_repository_device *repo) +{ + int result; + struct layout { + struct ps3_system_bus_device dev; + struct ps3_dma_region d_region; + } *p; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + BUG_ON(repo->bus_type != PS3_BUS_TYPE_SB); + BUG_ON(repo->dev_type != PS3_DEV_TYPE_SB_GELIC); + + p = kzalloc(sizeof(struct layout), GFP_KERNEL); + + if (!p) { + result = -ENOMEM; + goto fail_malloc; + } + + p->dev.match_id = PS3_MATCH_ID_GELIC; + p->dev.dev_type = PS3_DEVICE_TYPE_SB; + p->dev.bus_id = repo->bus_id; + p->dev.dev_id = repo->dev_id; + p->dev.d_region = &p->d_region; + + result = ps3_repository_find_interrupt(repo, + PS3_INTERRUPT_TYPE_EVENT_PORT, &p->dev.interrupt_id); + + if (result) { + pr_debug("%s:%d ps3_repository_find_interrupt failed\n", + __func__, __LINE__); + goto fail_find_interrupt; + } + + BUG_ON(p->dev.interrupt_id != 0); + + result = ps3_dma_region_init(&p->dev, p->dev.d_region, PS3_DMA_64K, + PS3_DMA_OTHER, NULL, 0); + + if (result) { + pr_debug("%s:%d ps3_dma_region_init failed\n", + __func__, __LINE__); + goto fail_dma_init; + } + + result = ps3_system_bus_device_register(&p->dev); + + if (result) { + pr_debug("%s:%d ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_device_register; + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; + +fail_device_register: +fail_dma_init: +fail_find_interrupt: + kfree(p); +fail_malloc: + pr_debug(" <- %s:%d: fail.\n", __func__, __LINE__); + return result; +} + +static int __ref ps3_setup_uhc_device( + const struct ps3_repository_device *repo, enum ps3_match_id match_id, + enum ps3_interrupt_type interrupt_type, enum ps3_reg_type reg_type) +{ + int result; + struct layout { + struct ps3_system_bus_device dev; + struct ps3_dma_region d_region; + struct ps3_mmio_region m_region; + } *p; + u64 bus_addr; + u64 len; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + BUG_ON(repo->bus_type != PS3_BUS_TYPE_SB); + BUG_ON(repo->dev_type != PS3_DEV_TYPE_SB_USB); + + p = kzalloc(sizeof(struct layout), GFP_KERNEL); + + if (!p) { + result = -ENOMEM; + goto fail_malloc; + } + + p->dev.match_id = match_id; + p->dev.dev_type = PS3_DEVICE_TYPE_SB; + p->dev.bus_id = repo->bus_id; + p->dev.dev_id = repo->dev_id; + p->dev.d_region = &p->d_region; + p->dev.m_region = &p->m_region; + + result = ps3_repository_find_interrupt(repo, + interrupt_type, &p->dev.interrupt_id); + + if (result) { + pr_debug("%s:%d ps3_repository_find_interrupt failed\n", + __func__, __LINE__); + goto fail_find_interrupt; + } + + result = ps3_repository_find_reg(repo, reg_type, + &bus_addr, &len); + + if (result) { + pr_debug("%s:%d ps3_repository_find_reg failed\n", + __func__, __LINE__); + goto fail_find_reg; + } + + result = ps3_dma_region_init(&p->dev, p->dev.d_region, PS3_DMA_64K, + PS3_DMA_INTERNAL, NULL, 0); + + if (result) { + pr_debug("%s:%d ps3_dma_region_init failed\n", + __func__, __LINE__); + goto fail_dma_init; + } + + result = ps3_mmio_region_init(&p->dev, p->dev.m_region, bus_addr, len, + PS3_MMIO_4K); + + if (result) { + pr_debug("%s:%d ps3_mmio_region_init failed\n", + __func__, __LINE__); + goto fail_mmio_init; + } + + result = ps3_system_bus_device_register(&p->dev); + + if (result) { + pr_debug("%s:%d ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_device_register; + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; + +fail_device_register: +fail_mmio_init: +fail_dma_init: +fail_find_reg: +fail_find_interrupt: + kfree(p); +fail_malloc: + pr_debug(" <- %s:%d: fail.\n", __func__, __LINE__); + return result; +} + +static int __init ps3_setup_ehci_device( + const struct ps3_repository_device *repo) +{ + return ps3_setup_uhc_device(repo, PS3_MATCH_ID_EHCI, + PS3_INTERRUPT_TYPE_SB_EHCI, PS3_REG_TYPE_SB_EHCI); +} + +static int __init ps3_setup_ohci_device( + const struct ps3_repository_device *repo) +{ + return ps3_setup_uhc_device(repo, PS3_MATCH_ID_OHCI, + PS3_INTERRUPT_TYPE_SB_OHCI, PS3_REG_TYPE_SB_OHCI); +} + +static int __init ps3_setup_vuart_device(enum ps3_match_id match_id, + unsigned int port_number) +{ + int result; + struct layout { + struct ps3_system_bus_device dev; + } *p; + + pr_debug(" -> %s:%d: match_id %u, port %u\n", __func__, __LINE__, + match_id, port_number); + + p = kzalloc(sizeof(struct layout), GFP_KERNEL); + + if (!p) + return -ENOMEM; + + p->dev.match_id = match_id; + p->dev.dev_type = PS3_DEVICE_TYPE_VUART; + p->dev.port_number = port_number; + + result = ps3_system_bus_device_register(&p->dev); + + if (result) { + pr_debug("%s:%d ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_device_register; + } + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return 0; + +fail_device_register: + kfree(p); + pr_debug(" <- %s:%d fail\n", __func__, __LINE__); + return result; +} + +static int ps3_setup_storage_dev(const struct ps3_repository_device *repo, + enum ps3_match_id match_id) +{ + int result; + struct ps3_storage_device *p; + u64 port, blk_size, num_blocks; + unsigned int num_regions, i; + + pr_debug(" -> %s:%u: match_id %u\n", __func__, __LINE__, match_id); + + result = ps3_repository_read_stor_dev_info(repo->bus_index, + repo->dev_index, &port, + &blk_size, &num_blocks, + &num_regions); + if (result) { + printk(KERN_ERR "%s:%u: _read_stor_dev_info failed %d\n", + __func__, __LINE__, result); + return -ENODEV; + } + + pr_debug("%s:%u: (%u:%u:%u): port %llu blk_size %llu num_blocks %llu " + "num_regions %u\n", __func__, __LINE__, repo->bus_index, + repo->dev_index, repo->dev_type, port, blk_size, num_blocks, + num_regions); + + p = kzalloc(struct_size(p, regions, num_regions), GFP_KERNEL); + if (!p) { + result = -ENOMEM; + goto fail_malloc; + } + + p->sbd.match_id = match_id; + p->sbd.dev_type = PS3_DEVICE_TYPE_SB; + p->sbd.bus_id = repo->bus_id; + p->sbd.dev_id = repo->dev_id; + p->sbd.d_region = &p->dma_region; + p->blk_size = blk_size; + p->num_regions = num_regions; + + result = ps3_repository_find_interrupt(repo, + PS3_INTERRUPT_TYPE_EVENT_PORT, + &p->sbd.interrupt_id); + if (result) { + printk(KERN_ERR "%s:%u: find_interrupt failed %d\n", __func__, + __LINE__, result); + result = -ENODEV; + goto fail_find_interrupt; + } + + for (i = 0; i < num_regions; i++) { + unsigned int id; + u64 start, size; + + result = ps3_repository_read_stor_dev_region(repo->bus_index, + repo->dev_index, + i, &id, &start, + &size); + if (result) { + printk(KERN_ERR + "%s:%u: read_stor_dev_region failed %d\n", + __func__, __LINE__, result); + result = -ENODEV; + goto fail_read_region; + } + pr_debug("%s:%u: region %u: id %u start %llu size %llu\n", + __func__, __LINE__, i, id, start, size); + + p->regions[i].id = id; + p->regions[i].start = start; + p->regions[i].size = size; + } + + result = ps3_system_bus_device_register(&p->sbd); + if (result) { + pr_debug("%s:%u ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_device_register; + } + + pr_debug(" <- %s:%u\n", __func__, __LINE__); + return 0; + +fail_device_register: +fail_read_region: +fail_find_interrupt: + kfree(p); +fail_malloc: + pr_debug(" <- %s:%u: fail.\n", __func__, __LINE__); + return result; +} + +static int __init ps3_register_vuart_devices(void) +{ + int result; + unsigned int port_number; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + result = ps3_repository_read_vuart_av_port(&port_number); + if (result) + port_number = 0; /* av default */ + + result = ps3_setup_vuart_device(PS3_MATCH_ID_AV_SETTINGS, port_number); + WARN_ON(result); + + result = ps3_repository_read_vuart_sysmgr_port(&port_number); + if (result) + port_number = 2; /* sysmgr default */ + + result = ps3_setup_vuart_device(PS3_MATCH_ID_SYSTEM_MANAGER, + port_number); + WARN_ON(result); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +static int __init ps3_register_sound_devices(void) +{ + int result; + struct layout { + struct ps3_system_bus_device dev; + struct ps3_dma_region d_region; + struct ps3_mmio_region m_region; + } *p; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->dev.match_id = PS3_MATCH_ID_SOUND; + p->dev.dev_type = PS3_DEVICE_TYPE_IOC0; + p->dev.d_region = &p->d_region; + p->dev.m_region = &p->m_region; + + result = ps3_system_bus_device_register(&p->dev); + + if (result) { + pr_debug("%s:%d ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_device_register; + } + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return 0; + +fail_device_register: + kfree(p); + pr_debug(" <- %s:%d failed\n", __func__, __LINE__); + return result; +} + +static int __init ps3_register_graphics_devices(void) +{ + int result; + struct layout { + struct ps3_system_bus_device dev; + } *p; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + p = kzalloc(sizeof(struct layout), GFP_KERNEL); + + if (!p) + return -ENOMEM; + + p->dev.match_id = PS3_MATCH_ID_GPU; + p->dev.match_sub_id = PS3_MATCH_SUB_ID_GPU_FB; + p->dev.dev_type = PS3_DEVICE_TYPE_IOC0; + + result = ps3_system_bus_device_register(&p->dev); + + if (result) { + pr_debug("%s:%d ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_device_register; + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return 0; + +fail_device_register: + kfree(p); + pr_debug(" <- %s:%d failed\n", __func__, __LINE__); + return result; +} + +static int __init ps3_register_ramdisk_device(void) +{ + int result; + struct layout { + struct ps3_system_bus_device dev; + } *p; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + p = kzalloc(sizeof(struct layout), GFP_KERNEL); + + if (!p) + return -ENOMEM; + + p->dev.match_id = PS3_MATCH_ID_GPU; + p->dev.match_sub_id = PS3_MATCH_SUB_ID_GPU_RAMDISK; + p->dev.dev_type = PS3_DEVICE_TYPE_IOC0; + + result = ps3_system_bus_device_register(&p->dev); + + if (result) { + pr_debug("%s:%d ps3_system_bus_device_register failed\n", + __func__, __LINE__); + goto fail_device_register; + } + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return 0; + +fail_device_register: + kfree(p); + pr_debug(" <- %s:%d failed\n", __func__, __LINE__); + return result; +} + +/** + * ps3_setup_dynamic_device - Setup a dynamic device from the repository + */ + +static int ps3_setup_dynamic_device(const struct ps3_repository_device *repo) +{ + int result; + + switch (repo->dev_type) { + case PS3_DEV_TYPE_STOR_DISK: + result = ps3_setup_storage_dev(repo, PS3_MATCH_ID_STOR_DISK); + + /* Some devices are not accessible from the Other OS lpar. */ + if (result == -ENODEV) { + result = 0; + pr_debug("%s:%u: not accessible\n", __func__, + __LINE__); + } + + if (result) + pr_debug("%s:%u ps3_setup_storage_dev failed\n", + __func__, __LINE__); + break; + + case PS3_DEV_TYPE_STOR_ROM: + result = ps3_setup_storage_dev(repo, PS3_MATCH_ID_STOR_ROM); + if (result) + pr_debug("%s:%u ps3_setup_storage_dev failed\n", + __func__, __LINE__); + break; + + case PS3_DEV_TYPE_STOR_FLASH: + result = ps3_setup_storage_dev(repo, PS3_MATCH_ID_STOR_FLASH); + if (result) + pr_debug("%s:%u ps3_setup_storage_dev failed\n", + __func__, __LINE__); + break; + + default: + result = 0; + pr_debug("%s:%u: unsupported dev_type %u\n", __func__, __LINE__, + repo->dev_type); + } + + return result; +} + +/** + * ps3_setup_static_device - Setup a static device from the repository + */ + +static int __init ps3_setup_static_device(const struct ps3_repository_device *repo) +{ + int result; + + switch (repo->dev_type) { + case PS3_DEV_TYPE_SB_GELIC: + result = ps3_setup_gelic_device(repo); + if (result) { + pr_debug("%s:%d ps3_setup_gelic_device failed\n", + __func__, __LINE__); + } + break; + case PS3_DEV_TYPE_SB_USB: + + /* Each USB device has both an EHCI and an OHCI HC */ + + result = ps3_setup_ehci_device(repo); + + if (result) { + pr_debug("%s:%d ps3_setup_ehci_device failed\n", + __func__, __LINE__); + } + + result = ps3_setup_ohci_device(repo); + + if (result) { + pr_debug("%s:%d ps3_setup_ohci_device failed\n", + __func__, __LINE__); + } + break; + + default: + return ps3_setup_dynamic_device(repo); + } + + return result; +} + +static void ps3_find_and_add_device(u64 bus_id, u64 dev_id) +{ + struct ps3_repository_device repo; + int res; + unsigned int retries; + unsigned long rem; + + /* + * On some firmware versions (e.g. 1.90), the device may not show up + * in the repository immediately + */ + for (retries = 0; retries < 10; retries++) { + res = ps3_repository_find_device_by_id(&repo, bus_id, dev_id); + if (!res) + goto found; + + rem = msleep_interruptible(100); + if (rem) + break; + } + pr_warn("%s:%u: device %llu:%llu not found\n", + __func__, __LINE__, bus_id, dev_id); + return; + +found: + if (retries) + pr_debug("%s:%u: device %llu:%llu found after %u retries\n", + __func__, __LINE__, bus_id, dev_id, retries); + + ps3_setup_dynamic_device(&repo); + return; +} + +#define PS3_NOTIFICATION_DEV_ID ULONG_MAX +#define PS3_NOTIFICATION_INTERRUPT_ID 0 + +struct ps3_notification_device { + struct ps3_system_bus_device sbd; + spinlock_t lock; + u64 tag; + u64 lv1_status; + struct rcuwait wait; + bool done; +}; + +enum ps3_notify_type { + notify_device_ready = 0, + notify_region_probe = 1, + notify_region_update = 2, +}; + +struct ps3_notify_cmd { + u64 operation_code; /* must be zero */ + u64 event_mask; /* OR of 1UL << enum ps3_notify_type */ +}; + +struct ps3_notify_event { + u64 event_type; /* enum ps3_notify_type */ + u64 bus_id; + u64 dev_id; + u64 dev_type; + u64 dev_port; +}; + +static irqreturn_t ps3_notification_interrupt(int irq, void *data) +{ + struct ps3_notification_device *dev = data; + int res; + u64 tag, status; + + spin_lock(&dev->lock); + res = lv1_storage_get_async_status(PS3_NOTIFICATION_DEV_ID, &tag, + &status); + if (tag != dev->tag) + pr_err("%s:%u: tag mismatch, got %llx, expected %llx\n", + __func__, __LINE__, tag, dev->tag); + + if (res) { + pr_err("%s:%u: res %d status 0x%llx\n", __func__, __LINE__, res, + status); + } else { + pr_debug("%s:%u: completed, status 0x%llx\n", __func__, + __LINE__, status); + dev->lv1_status = status; + dev->done = true; + rcuwait_wake_up(&dev->wait); + } + spin_unlock(&dev->lock); + return IRQ_HANDLED; +} + +static int ps3_notification_read_write(struct ps3_notification_device *dev, + u64 lpar, int write) +{ + const char *op = write ? "write" : "read"; + unsigned long flags; + int res; + + spin_lock_irqsave(&dev->lock, flags); + res = write ? lv1_storage_write(dev->sbd.dev_id, 0, 0, 1, 0, lpar, + &dev->tag) + : lv1_storage_read(dev->sbd.dev_id, 0, 0, 1, 0, lpar, + &dev->tag); + dev->done = false; + spin_unlock_irqrestore(&dev->lock, flags); + if (res) { + pr_err("%s:%u: %s failed %d\n", __func__, __LINE__, op, res); + return -EPERM; + } + pr_debug("%s:%u: notification %s issued\n", __func__, __LINE__, op); + + rcuwait_wait_event(&dev->wait, dev->done || kthread_should_stop(), TASK_IDLE); + + if (kthread_should_stop()) + res = -EINTR; + + if (dev->lv1_status) { + pr_err("%s:%u: %s not completed, status 0x%llx\n", __func__, + __LINE__, op, dev->lv1_status); + return -EIO; + } + pr_debug("%s:%u: notification %s completed\n", __func__, __LINE__, op); + + return 0; +} + +static struct task_struct *probe_task; + +/** + * ps3_probe_thread - Background repository probing at system startup. + * + * This implementation only supports background probing on a single bus. + * It uses the hypervisor's storage device notification mechanism to wait until + * a storage device is ready. The device notification mechanism uses a + * pseudo device to asynchronously notify the guest when storage devices become + * ready. The notification device has a block size of 512 bytes. + */ + +static int ps3_probe_thread(void *data) +{ + struct ps3_notification_device dev; + int res; + unsigned int irq; + u64 lpar; + void *buf; + struct ps3_notify_cmd *notify_cmd; + struct ps3_notify_event *notify_event; + + pr_debug(" -> %s:%u: kthread started\n", __func__, __LINE__); + + buf = kzalloc(512, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + lpar = ps3_mm_phys_to_lpar(__pa(buf)); + notify_cmd = buf; + notify_event = buf; + + /* dummy system bus device */ + dev.sbd.bus_id = (u64)data; + dev.sbd.dev_id = PS3_NOTIFICATION_DEV_ID; + dev.sbd.interrupt_id = PS3_NOTIFICATION_INTERRUPT_ID; + + res = lv1_open_device(dev.sbd.bus_id, dev.sbd.dev_id, 0); + if (res) { + pr_err("%s:%u: lv1_open_device failed %s\n", __func__, + __LINE__, ps3_result(res)); + goto fail_free; + } + + res = ps3_sb_event_receive_port_setup(&dev.sbd, PS3_BINDING_CPU_ANY, + &irq); + if (res) { + pr_err("%s:%u: ps3_sb_event_receive_port_setup failed %d\n", + __func__, __LINE__, res); + goto fail_close_device; + } + + spin_lock_init(&dev.lock); + rcuwait_init(&dev.wait); + + res = request_irq(irq, ps3_notification_interrupt, 0, + "ps3_notification", &dev); + if (res) { + pr_err("%s:%u: request_irq failed %d\n", __func__, __LINE__, + res); + goto fail_sb_event_receive_port_destroy; + } + + /* Setup and write the request for device notification. */ + notify_cmd->operation_code = 0; /* must be zero */ + notify_cmd->event_mask = 1UL << notify_region_probe; + + res = ps3_notification_read_write(&dev, lpar, 1); + if (res) + goto fail_free_irq; + + /* Loop here processing the requested notification events. */ + do { + try_to_freeze(); + + memset(notify_event, 0, sizeof(*notify_event)); + + res = ps3_notification_read_write(&dev, lpar, 0); + if (res) + break; + + pr_debug("%s:%u: notify event type 0x%llx bus id %llu dev id %llu" + " type %llu port %llu\n", __func__, __LINE__, + notify_event->event_type, notify_event->bus_id, + notify_event->dev_id, notify_event->dev_type, + notify_event->dev_port); + + if (notify_event->event_type != notify_region_probe || + notify_event->bus_id != dev.sbd.bus_id) { + pr_warn("%s:%u: bad notify_event: event %llu, dev_id %llu, dev_type %llu\n", + __func__, __LINE__, notify_event->event_type, + notify_event->dev_id, notify_event->dev_type); + continue; + } + + ps3_find_and_add_device(dev.sbd.bus_id, notify_event->dev_id); + + } while (!kthread_should_stop()); + +fail_free_irq: + free_irq(irq, &dev); +fail_sb_event_receive_port_destroy: + ps3_sb_event_receive_port_destroy(&dev.sbd, irq); +fail_close_device: + lv1_close_device(dev.sbd.bus_id, dev.sbd.dev_id); +fail_free: + kfree(buf); + + probe_task = NULL; + + pr_debug(" <- %s:%u: kthread finished\n", __func__, __LINE__); + + return 0; +} + +/** + * ps3_stop_probe_thread - Stops the background probe thread. + * + */ + +static int ps3_stop_probe_thread(struct notifier_block *nb, unsigned long code, + void *data) +{ + if (probe_task) + kthread_stop(probe_task); + return 0; +} + +static struct notifier_block nb = { + .notifier_call = ps3_stop_probe_thread +}; + +/** + * ps3_start_probe_thread - Starts the background probe thread. + * + */ + +static int __init ps3_start_probe_thread(enum ps3_bus_type bus_type) +{ + int result; + struct task_struct *task; + struct ps3_repository_device repo; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + memset(&repo, 0, sizeof(repo)); + + repo.bus_type = bus_type; + + result = ps3_repository_find_bus(repo.bus_type, 0, &repo.bus_index); + + if (result) { + printk(KERN_ERR "%s: Cannot find bus (%d)\n", __func__, result); + return -ENODEV; + } + + result = ps3_repository_read_bus_id(repo.bus_index, &repo.bus_id); + + if (result) { + printk(KERN_ERR "%s: read_bus_id failed %d\n", __func__, + result); + return -ENODEV; + } + + task = kthread_run(ps3_probe_thread, (void *)repo.bus_id, + "ps3-probe-%u", bus_type); + + if (IS_ERR(task)) { + result = PTR_ERR(task); + printk(KERN_ERR "%s: kthread_run failed %d\n", __func__, + result); + return result; + } + + probe_task = task; + register_reboot_notifier(&nb); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return 0; +} + +/** + * ps3_register_devices - Probe the system and register devices found. + * + * A device_initcall() routine. + */ + +static int __init ps3_register_devices(void) +{ + int result; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENODEV; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + /* ps3_repository_dump_bus_info(); */ + + result = ps3_start_probe_thread(PS3_BUS_TYPE_STORAGE); + + ps3_register_vuart_devices(); + + ps3_register_graphics_devices(); + + ps3_repository_find_devices(PS3_BUS_TYPE_SB, ps3_setup_static_device); + + ps3_register_sound_devices(); + + ps3_register_lpm_devices(); + + ps3_register_ramdisk_device(); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return 0; +} + +device_initcall(ps3_register_devices); diff --git a/arch/powerpc/platforms/ps3/exports.c b/arch/powerpc/platforms/ps3/exports.c new file mode 100644 index 000000000..1ac31abcf --- /dev/null +++ b/arch/powerpc/platforms/ps3/exports.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 hvcall exports for modules. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#define LV1_CALL(name, in, out, num) \ + extern s64 _lv1_##name(LV1_##in##_IN_##out##_OUT_ARG_DECL); \ + EXPORT_SYMBOL(_lv1_##name); + +#include <asm/lv1call.h> diff --git a/arch/powerpc/platforms/ps3/gelic_udbg.c b/arch/powerpc/platforms/ps3/gelic_udbg.c new file mode 100644 index 000000000..cba4f8f5b --- /dev/null +++ b/arch/powerpc/platforms/ps3/gelic_udbg.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * udbg debug output routine via GELIC UDP broadcasts + * + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2006, 2007 Sony Corporation + * Copyright (C) 2010 Hector Martin <hector@marcansoft.com> + * Copyright (C) 2011 Andre Heider <a.heider@gmail.com> + */ + +#include <linux/if_ether.h> +#include <linux/etherdevice.h> +#include <linux/if_vlan.h> +#include <linux/ip.h> +#include <linux/udp.h> + +#include <asm/io.h> +#include <asm/udbg.h> +#include <asm/lv1call.h> + +#define GELIC_BUS_ID 1 +#define GELIC_DEVICE_ID 0 +#define GELIC_DEBUG_PORT 18194 +#define GELIC_MAX_MESSAGE_SIZE 1000 + +#define GELIC_LV1_GET_MAC_ADDRESS 1 +#define GELIC_LV1_GET_VLAN_ID 4 +#define GELIC_LV1_VLAN_TX_ETHERNET_0 2 + +#define GELIC_DESCR_DMA_STAT_MASK 0xf0000000 +#define GELIC_DESCR_DMA_CARDOWNED 0xa0000000 + +#define GELIC_DESCR_TX_DMA_IKE 0x00080000 +#define GELIC_DESCR_TX_DMA_NO_CHKSUM 0x00000000 +#define GELIC_DESCR_TX_DMA_FRAME_TAIL 0x00040000 + +#define GELIC_DESCR_DMA_CMD_NO_CHKSUM (GELIC_DESCR_DMA_CARDOWNED | \ + GELIC_DESCR_TX_DMA_IKE | \ + GELIC_DESCR_TX_DMA_NO_CHKSUM) + +static u64 bus_addr; + +struct gelic_descr { + /* as defined by the hardware */ + __be32 buf_addr; + __be32 buf_size; + __be32 next_descr_addr; + __be32 dmac_cmd_status; + __be32 result_size; + __be32 valid_size; /* all zeroes for tx */ + __be32 data_status; + __be32 data_error; /* all zeroes for tx */ +} __attribute__((aligned(32))); + +struct debug_block { + struct gelic_descr descr; + u8 pkt[1520]; +} __packed; + +static __iomem struct ethhdr *h_eth; +static __iomem struct vlan_hdr *h_vlan; +static __iomem struct iphdr *h_ip; +static __iomem struct udphdr *h_udp; + +static __iomem char *pmsg; +static __iomem char *pmsgc; + +static __iomem struct debug_block dbg __attribute__((aligned(32))); + +static int header_size; + +static void map_dma_mem(int bus_id, int dev_id, void *start, size_t len, + u64 *real_bus_addr) +{ + s64 result; + u64 real_addr = ((u64)start) & 0x0fffffffffffffffUL; + u64 real_end = real_addr + len; + u64 map_start = real_addr & ~0xfff; + u64 map_end = (real_end + 0xfff) & ~0xfff; + u64 bus_addr = 0; + + u64 flags = 0xf800000000000000UL; + + result = lv1_allocate_device_dma_region(bus_id, dev_id, + map_end - map_start, 12, 0, + &bus_addr); + if (result) + lv1_panic(0); + + result = lv1_map_device_dma_region(bus_id, dev_id, map_start, + bus_addr, map_end - map_start, + flags); + if (result) + lv1_panic(0); + + *real_bus_addr = bus_addr + real_addr - map_start; +} + +static int unmap_dma_mem(int bus_id, int dev_id, u64 bus_addr, size_t len) +{ + s64 result; + u64 real_bus_addr; + + real_bus_addr = bus_addr & ~0xfff; + len += bus_addr - real_bus_addr; + len = (len + 0xfff) & ~0xfff; + + result = lv1_unmap_device_dma_region(bus_id, dev_id, real_bus_addr, + len); + if (result) + return result; + + return lv1_free_device_dma_region(bus_id, dev_id, real_bus_addr); +} + +static void gelic_debug_init(void) +{ + s64 result; + u64 v2; + u64 mac; + u64 vlan_id; + + result = lv1_open_device(GELIC_BUS_ID, GELIC_DEVICE_ID, 0); + if (result) + lv1_panic(0); + + map_dma_mem(GELIC_BUS_ID, GELIC_DEVICE_ID, &dbg, sizeof(dbg), + &bus_addr); + + memset(&dbg, 0, sizeof(dbg)); + + dbg.descr.buf_addr = bus_addr + offsetof(struct debug_block, pkt); + + wmb(); + + result = lv1_net_control(GELIC_BUS_ID, GELIC_DEVICE_ID, + GELIC_LV1_GET_MAC_ADDRESS, 0, 0, 0, + &mac, &v2); + if (result) + lv1_panic(0); + + mac <<= 16; + + h_eth = (struct ethhdr *)dbg.pkt; + + eth_broadcast_addr(h_eth->h_dest); + memcpy(&h_eth->h_source, &mac, ETH_ALEN); + + header_size = sizeof(struct ethhdr); + + result = lv1_net_control(GELIC_BUS_ID, GELIC_DEVICE_ID, + GELIC_LV1_GET_VLAN_ID, + GELIC_LV1_VLAN_TX_ETHERNET_0, 0, 0, + &vlan_id, &v2); + if (!result) { + h_eth->h_proto= ETH_P_8021Q; + + header_size += sizeof(struct vlan_hdr); + h_vlan = (struct vlan_hdr *)(h_eth + 1); + h_vlan->h_vlan_TCI = vlan_id; + h_vlan->h_vlan_encapsulated_proto = ETH_P_IP; + h_ip = (struct iphdr *)(h_vlan + 1); + } else { + h_eth->h_proto= 0x0800; + h_ip = (struct iphdr *)(h_eth + 1); + } + + header_size += sizeof(struct iphdr); + h_ip->version = 4; + h_ip->ihl = 5; + h_ip->ttl = 10; + h_ip->protocol = 0x11; + h_ip->saddr = 0x00000000; + h_ip->daddr = 0xffffffff; + + header_size += sizeof(struct udphdr); + h_udp = (struct udphdr *)(h_ip + 1); + h_udp->source = GELIC_DEBUG_PORT; + h_udp->dest = GELIC_DEBUG_PORT; + + pmsgc = pmsg = (char *)(h_udp + 1); +} + +static void gelic_debug_shutdown(void) +{ + if (bus_addr) + unmap_dma_mem(GELIC_BUS_ID, GELIC_DEVICE_ID, + bus_addr, sizeof(dbg)); + lv1_close_device(GELIC_BUS_ID, GELIC_DEVICE_ID); +} + +static void gelic_sendbuf(int msgsize) +{ + u16 *p; + u32 sum; + int i; + + dbg.descr.buf_size = header_size + msgsize; + h_ip->tot_len = msgsize + sizeof(struct udphdr) + + sizeof(struct iphdr); + h_udp->len = msgsize + sizeof(struct udphdr); + + h_ip->check = 0; + sum = 0; + p = (u16 *)h_ip; + for (i = 0; i < 5; i++) + sum += *p++; + h_ip->check = ~(sum + (sum >> 16)); + + dbg.descr.dmac_cmd_status = GELIC_DESCR_DMA_CMD_NO_CHKSUM | + GELIC_DESCR_TX_DMA_FRAME_TAIL; + dbg.descr.result_size = 0; + dbg.descr.data_status = 0; + + wmb(); + + lv1_net_start_tx_dma(GELIC_BUS_ID, GELIC_DEVICE_ID, bus_addr, 0); + + while ((dbg.descr.dmac_cmd_status & GELIC_DESCR_DMA_STAT_MASK) == + GELIC_DESCR_DMA_CARDOWNED) + cpu_relax(); +} + +static void ps3gelic_udbg_putc(char ch) +{ + *pmsgc++ = ch; + if (ch == '\n' || (pmsgc-pmsg) >= GELIC_MAX_MESSAGE_SIZE) { + gelic_sendbuf(pmsgc-pmsg); + pmsgc = pmsg; + } +} + +void __init udbg_init_ps3gelic(void) +{ + gelic_debug_init(); + udbg_putc = ps3gelic_udbg_putc; +} + +void udbg_shutdown_ps3gelic(void) +{ + udbg_putc = NULL; + gelic_debug_shutdown(); +} +EXPORT_SYMBOL(udbg_shutdown_ps3gelic); diff --git a/arch/powerpc/platforms/ps3/htab.c b/arch/powerpc/platforms/ps3/htab.c new file mode 100644 index 000000000..7ddc7ec6a --- /dev/null +++ b/arch/powerpc/platforms/ps3/htab.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 pagetable management routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006, 2007 Sony Corporation + */ + +#include <linux/kernel.h> +#include <linux/memblock.h> + +#include <asm/machdep.h> +#include <asm/prom.h> +#include <asm/udbg.h> +#include <asm/lv1call.h> +#include <asm/ps3fb.h> + +#define PS3_VERBOSE_RESULT +#include "platform.h" + +/** + * enum lpar_vas_id - id of LPAR virtual address space. + * @lpar_vas_id_current: Current selected virtual address space + * + * Identify the target LPAR address space. + */ + +enum ps3_lpar_vas_id { + PS3_LPAR_VAS_ID_CURRENT = 0, +}; + + +static DEFINE_SPINLOCK(ps3_htab_lock); + +static long ps3_hpte_insert(unsigned long hpte_group, unsigned long vpn, + unsigned long pa, unsigned long rflags, unsigned long vflags, + int psize, int apsize, int ssize) +{ + int result; + u64 hpte_v, hpte_r; + u64 inserted_index; + u64 evicted_v, evicted_r; + u64 hpte_v_array[4], hpte_rs; + unsigned long flags; + long ret = -1; + + /* + * lv1_insert_htab_entry() will search for victim + * entry in both primary and secondary pte group + */ + vflags &= ~HPTE_V_SECONDARY; + + hpte_v = hpte_encode_v(vpn, psize, apsize, ssize) | vflags | HPTE_V_VALID; + hpte_r = hpte_encode_r(ps3_mm_phys_to_lpar(pa), psize, apsize) | rflags; + + spin_lock_irqsave(&ps3_htab_lock, flags); + + /* talk hvc to replace entries BOLTED == 0 */ + result = lv1_insert_htab_entry(PS3_LPAR_VAS_ID_CURRENT, hpte_group, + hpte_v, hpte_r, + HPTE_V_BOLTED, 0, + &inserted_index, + &evicted_v, &evicted_r); + + if (result) { + /* all entries bolted !*/ + pr_info("%s:result=%s vpn=%lx pa=%lx ix=%lx v=%llx r=%llx\n", + __func__, ps3_result(result), vpn, pa, hpte_group, + hpte_v, hpte_r); + BUG(); + } + + /* + * see if the entry is inserted into secondary pteg + */ + result = lv1_read_htab_entries(PS3_LPAR_VAS_ID_CURRENT, + inserted_index & ~0x3UL, + &hpte_v_array[0], &hpte_v_array[1], + &hpte_v_array[2], &hpte_v_array[3], + &hpte_rs); + BUG_ON(result); + + if (hpte_v_array[inserted_index % 4] & HPTE_V_SECONDARY) + ret = (inserted_index & 7) | (1 << 3); + else + ret = inserted_index & 7; + + spin_unlock_irqrestore(&ps3_htab_lock, flags); + + return ret; +} + +static long ps3_hpte_remove(unsigned long hpte_group) +{ + panic("ps3_hpte_remove() not implemented"); + return 0; +} + +static long ps3_hpte_updatepp(unsigned long slot, unsigned long newpp, + unsigned long vpn, int psize, int apsize, + int ssize, unsigned long inv_flags) +{ + int result; + u64 hpte_v, want_v, hpte_rs; + u64 hpte_v_array[4]; + unsigned long flags; + long ret; + + want_v = hpte_encode_avpn(vpn, psize, ssize); + + spin_lock_irqsave(&ps3_htab_lock, flags); + + result = lv1_read_htab_entries(PS3_LPAR_VAS_ID_CURRENT, slot & ~0x3UL, + &hpte_v_array[0], &hpte_v_array[1], + &hpte_v_array[2], &hpte_v_array[3], + &hpte_rs); + + if (result) { + pr_info("%s: result=%s read vpn=%lx slot=%lx psize=%d\n", + __func__, ps3_result(result), vpn, slot, psize); + BUG(); + } + + hpte_v = hpte_v_array[slot % 4]; + + /* + * As lv1_read_htab_entries() does not give us the RPN, we can + * not synthesize the new hpte_r value here, and therefore can + * not update the hpte with lv1_insert_htab_entry(), so we + * instead invalidate it and ask the caller to update it via + * ps3_hpte_insert() by returning a -1 value. + */ + if (!HPTE_V_COMPARE(hpte_v, want_v) || !(hpte_v & HPTE_V_VALID)) { + /* not found */ + ret = -1; + } else { + /* entry found, just invalidate it */ + result = lv1_write_htab_entry(PS3_LPAR_VAS_ID_CURRENT, + slot, 0, 0); + ret = -1; + } + + spin_unlock_irqrestore(&ps3_htab_lock, flags); + return ret; +} + +static void ps3_hpte_updateboltedpp(unsigned long newpp, unsigned long ea, + int psize, int ssize) +{ + panic("ps3_hpte_updateboltedpp() not implemented"); +} + +static void ps3_hpte_invalidate(unsigned long slot, unsigned long vpn, + int psize, int apsize, int ssize, int local) +{ + unsigned long flags; + int result; + + spin_lock_irqsave(&ps3_htab_lock, flags); + + result = lv1_write_htab_entry(PS3_LPAR_VAS_ID_CURRENT, slot, 0, 0); + + if (result) { + pr_info("%s: result=%s vpn=%lx slot=%lx psize=%d\n", + __func__, ps3_result(result), vpn, slot, psize); + BUG(); + } + + spin_unlock_irqrestore(&ps3_htab_lock, flags); +} + +static void ps3_hpte_clear(void) +{ + unsigned long hpte_count = (1UL << ppc64_pft_size) >> 4; + u64 i; + + for (i = 0; i < hpte_count; i++) + lv1_write_htab_entry(PS3_LPAR_VAS_ID_CURRENT, i, 0, 0); + + ps3_mm_shutdown(); + ps3_mm_vas_destroy(); +} + +void __init ps3_hpte_init(unsigned long htab_size) +{ + mmu_hash_ops.hpte_invalidate = ps3_hpte_invalidate; + mmu_hash_ops.hpte_updatepp = ps3_hpte_updatepp; + mmu_hash_ops.hpte_updateboltedpp = ps3_hpte_updateboltedpp; + mmu_hash_ops.hpte_insert = ps3_hpte_insert; + mmu_hash_ops.hpte_remove = ps3_hpte_remove; + mmu_hash_ops.hpte_clear_all = ps3_hpte_clear; + + ppc64_pft_size = __ilog2(htab_size); +} + diff --git a/arch/powerpc/platforms/ps3/hvcall.S b/arch/powerpc/platforms/ps3/hvcall.S new file mode 100644 index 000000000..509e30ad0 --- /dev/null +++ b/arch/powerpc/platforms/ps3/hvcall.S @@ -0,0 +1,792 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * PS3 hvcall interface. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + * Copyright 2003, 2004 (c) MontaVista Software, Inc. + */ + +#include <asm/processor.h> +#include <asm/ppc_asm.h> + +#define lv1call .long 0x44000022; extsw r3, r3 + +#define LV1_N_IN_0_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_1_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_2_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_3_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_4_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_5_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_6_IN_0_OUT LV1_N_IN_0_OUT +#define LV1_7_IN_0_OUT LV1_N_IN_0_OUT + +#define LV1_0_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r3, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r3, -8(r1); \ + stdu r4, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r3, -8(r1); \ + std r4, -16(r1); \ + stdu r5, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_0_IN_7_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r3, -8(r1); \ + std r4, -16(r1); \ + std r5, -24(r1); \ + std r6, -32(r1); \ + std r7, -40(r1); \ + std r8, -48(r1); \ + stdu r9, -56(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 56; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + ld r11, -48(r1); \ + std r9, 0(r11); \ + ld r11, -56(r1); \ + std r10, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r4, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + stdu r5, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + stdu r6, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_4_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + stdu r7, -32(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 32; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_5_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + std r7, -32(r1); \ + stdu r8, -40(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 40; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_6_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + std r7, -32(r1); \ + std r8, -40(r1); \ + stdu r9, -48(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 48; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + ld r11, -48(r1); \ + std r9, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_1_IN_7_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r4, -8(r1); \ + std r5, -16(r1); \ + std r6, -24(r1); \ + std r7, -32(r1); \ + std r8, -40(r1); \ + std r9, -48(r1); \ + stdu r10, -56(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 56; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + ld r11, -48(r1); \ + std r9, 0(r11); \ + ld r11, -56(r1); \ + std r10, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r5, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + stdu r6, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + std r6, -16(r1); \ + stdu r7, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_4_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + std r6, -16(r1); \ + std r7, -24(r1); \ + stdu r8, -32(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 32; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_2_IN_5_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r5, -8(r1); \ + std r6, -16(r1); \ + std r7, -24(r1); \ + std r8, -32(r1); \ + stdu r9, -40(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 40; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + ld r11, -32(r1); \ + std r7, 0(r11); \ + ld r11, -40(r1); \ + std r8, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_3_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r6, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_3_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r6, -8(r1); \ + stdu r7, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_3_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r6, -8(r1); \ + std r7, -16(r1); \ + stdu r8, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_4_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r7, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_4_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r7, -8(r1); \ + stdu r8, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_4_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r7, -8(r1); \ + std r8, -16(r1); \ + stdu r9, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_5_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r8, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_5_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r8, -8(r1); \ + stdu r9, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_5_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r8, -8(r1); \ + std r9, -16(r1); \ + stdu r10, -24(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 24; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, -24(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_6_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r9, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_6_IN_2_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r9, -8(r1); \ + stdu r10, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_6_IN_3_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r9, -8(r1); \ + stdu r10, -16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 16; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + ld r11, -16(r1); \ + std r5, 0(r11); \ + ld r11, 48+8*8(r1); \ + std r6, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_7_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + stdu r10, -8(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + addi r1, r1, 8; \ + ld r11, -8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_7_IN_6_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + std r10, 48+8*7(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + ld r11, 48+8*7(r1); \ + std r4, 0(r11); \ + ld r11, 48+8*8(r1); \ + std r5, 0(r11); \ + ld r11, 48+8*9(r1); \ + std r6, 0(r11); \ + ld r11, 48+8*10(r1); \ + std r7, 0(r11); \ + ld r11, 48+8*11(r1); \ + std r8, 0(r11); \ + ld r11, 48+8*12(r1); \ + std r9, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + +#define LV1_8_IN_1_OUT(API_NAME, API_NUMBER) \ +_GLOBAL(_##API_NAME) \ + \ + mflr r0; \ + std r0, 16(r1); \ + \ + li r11, API_NUMBER; \ + lv1call; \ + \ + ld r11, 48+8*8(r1); \ + std r4, 0(r11); \ + \ + ld r0, 16(r1); \ + mtlr r0; \ + blr + + .text + +/* the lv1 underscored call definitions expand here */ + +#define LV1_CALL(name, in, out, num) LV1_##in##_IN_##out##_OUT(lv1_##name, num) +#include <asm/lv1call.h> diff --git a/arch/powerpc/platforms/ps3/interrupt.c b/arch/powerpc/platforms/ps3/interrupt.c new file mode 100644 index 000000000..78f2339ed --- /dev/null +++ b/arch/powerpc/platforms/ps3/interrupt.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 interrupt routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/irq.h> + +#include <asm/machdep.h> +#include <asm/udbg.h> +#include <asm/lv1call.h> +#include <asm/smp.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG udbg_printf +#define FAIL udbg_printf +#else +#define DBG pr_devel +#define FAIL pr_debug +#endif + +/** + * struct ps3_bmp - a per cpu irq status and mask bitmap structure + * @status: 256 bit status bitmap indexed by plug + * @unused_1: Alignment + * @mask: 256 bit mask bitmap indexed by plug + * @unused_2: Alignment + * + * The HV maintains per SMT thread mappings of HV outlet to HV plug on + * behalf of the guest. These mappings are implemented as 256 bit guest + * supplied bitmaps indexed by plug number. The addresses of the bitmaps + * are registered with the HV through lv1_configure_irq_state_bitmap(). + * The HV requires that the 512 bits of status + mask not cross a page + * boundary. PS3_BMP_MINALIGN is used to define this minimal 64 byte + * alignment. + * + * The HV supports 256 plugs per thread, assigned as {0..255}, for a total + * of 512 plugs supported on a processor. To simplify the logic this + * implementation equates HV plug value to Linux virq value, constrains each + * interrupt to have a system wide unique plug number, and limits the range + * of the plug values to map into the first dword of the bitmaps. This + * gives a usable range of plug values of {NUM_ISA_INTERRUPTS..63}. Note + * that there is no constraint on how many in this set an individual thread + * can acquire. + * + * The mask is declared as unsigned long so we can use set/clear_bit on it. + */ + +#define PS3_BMP_MINALIGN 64 + +struct ps3_bmp { + struct { + u64 status; + u64 unused_1[3]; + unsigned long mask; + u64 unused_2[3]; + }; +}; + +/** + * struct ps3_private - a per cpu data structure + * @bmp: ps3_bmp structure + * @bmp_lock: Synchronize access to bmp. + * @ipi_debug_brk_mask: Mask for debug break IPIs + * @ppe_id: HV logical_ppe_id + * @thread_id: HV thread_id + * @ipi_mask: Mask of IPI virqs + */ + +struct ps3_private { + struct ps3_bmp bmp __attribute__ ((aligned (PS3_BMP_MINALIGN))); + spinlock_t bmp_lock; + u64 ppe_id; + u64 thread_id; + unsigned long ipi_debug_brk_mask; + unsigned long ipi_mask; +}; + +static DEFINE_PER_CPU(struct ps3_private, ps3_private); + +/** + * ps3_chip_mask - Set an interrupt mask bit in ps3_bmp. + * @virq: The assigned Linux virq. + * + * Sets ps3_bmp.mask and calls lv1_did_update_interrupt_mask(). + */ + +static void ps3_chip_mask(struct irq_data *d) +{ + struct ps3_private *pd = irq_data_get_irq_chip_data(d); + unsigned long flags; + + DBG("%s:%d: thread_id %llu, virq %d\n", __func__, __LINE__, + pd->thread_id, d->irq); + + local_irq_save(flags); + clear_bit(63 - d->irq, &pd->bmp.mask); + lv1_did_update_interrupt_mask(pd->ppe_id, pd->thread_id); + local_irq_restore(flags); +} + +/** + * ps3_chip_unmask - Clear an interrupt mask bit in ps3_bmp. + * @virq: The assigned Linux virq. + * + * Clears ps3_bmp.mask and calls lv1_did_update_interrupt_mask(). + */ + +static void ps3_chip_unmask(struct irq_data *d) +{ + struct ps3_private *pd = irq_data_get_irq_chip_data(d); + unsigned long flags; + + DBG("%s:%d: thread_id %llu, virq %d\n", __func__, __LINE__, + pd->thread_id, d->irq); + + local_irq_save(flags); + set_bit(63 - d->irq, &pd->bmp.mask); + lv1_did_update_interrupt_mask(pd->ppe_id, pd->thread_id); + local_irq_restore(flags); +} + +/** + * ps3_chip_eoi - HV end-of-interrupt. + * @virq: The assigned Linux virq. + * + * Calls lv1_end_of_interrupt_ext(). + */ + +static void ps3_chip_eoi(struct irq_data *d) +{ + const struct ps3_private *pd = irq_data_get_irq_chip_data(d); + + /* non-IPIs are EOIed here. */ + + if (!test_bit(63 - d->irq, &pd->ipi_mask)) + lv1_end_of_interrupt_ext(pd->ppe_id, pd->thread_id, d->irq); +} + +/** + * ps3_irq_chip - Represents the ps3_bmp as a Linux struct irq_chip. + */ + +static struct irq_chip ps3_irq_chip = { + .name = "ps3", + .irq_mask = ps3_chip_mask, + .irq_unmask = ps3_chip_unmask, + .irq_eoi = ps3_chip_eoi, +}; + +/** + * ps3_virq_setup - virq related setup. + * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be + * serviced on. + * @outlet: The HV outlet from the various create outlet routines. + * @virq: The assigned Linux virq. + * + * Calls irq_create_mapping() to get a virq and sets the chip data to + * ps3_private data. + */ + +static int ps3_virq_setup(enum ps3_cpu_binding cpu, unsigned long outlet, + unsigned int *virq) +{ + int result; + struct ps3_private *pd; + + /* This defines the default interrupt distribution policy. */ + + if (cpu == PS3_BINDING_CPU_ANY) + cpu = 0; + + pd = &per_cpu(ps3_private, cpu); + + *virq = irq_create_mapping(NULL, outlet); + + if (!*virq) { + FAIL("%s:%d: irq_create_mapping failed: outlet %lu\n", + __func__, __LINE__, outlet); + result = -ENOMEM; + goto fail_create; + } + + DBG("%s:%d: outlet %lu => cpu %u, virq %u\n", __func__, __LINE__, + outlet, cpu, *virq); + + result = irq_set_chip_data(*virq, pd); + + if (result) { + FAIL("%s:%d: irq_set_chip_data failed\n", + __func__, __LINE__); + goto fail_set; + } + + ps3_chip_mask(irq_get_irq_data(*virq)); + + return result; + +fail_set: + irq_dispose_mapping(*virq); +fail_create: + return result; +} + +/** + * ps3_virq_destroy - virq related teardown. + * @virq: The assigned Linux virq. + * + * Clears chip data and calls irq_dispose_mapping() for the virq. + */ + +static int ps3_virq_destroy(unsigned int virq) +{ + const struct ps3_private *pd = irq_get_chip_data(virq); + + DBG("%s:%d: ppe_id %llu, thread_id %llu, virq %u\n", __func__, + __LINE__, pd->ppe_id, pd->thread_id, virq); + + irq_set_chip_data(virq, NULL); + irq_dispose_mapping(virq); + + DBG("%s:%d <-\n", __func__, __LINE__); + return 0; +} + +/** + * ps3_irq_plug_setup - Generic outlet and virq related setup. + * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be + * serviced on. + * @outlet: The HV outlet from the various create outlet routines. + * @virq: The assigned Linux virq. + * + * Sets up virq and connects the irq plug. + */ + +int ps3_irq_plug_setup(enum ps3_cpu_binding cpu, unsigned long outlet, + unsigned int *virq) +{ + int result; + struct ps3_private *pd; + + result = ps3_virq_setup(cpu, outlet, virq); + + if (result) { + FAIL("%s:%d: ps3_virq_setup failed\n", __func__, __LINE__); + goto fail_setup; + } + + pd = irq_get_chip_data(*virq); + + /* Binds outlet to cpu + virq. */ + + result = lv1_connect_irq_plug_ext(pd->ppe_id, pd->thread_id, *virq, + outlet, 0); + + if (result) { + FAIL("%s:%d: lv1_connect_irq_plug_ext failed: %s\n", + __func__, __LINE__, ps3_result(result)); + result = -EPERM; + goto fail_connect; + } + + return result; + +fail_connect: + ps3_virq_destroy(*virq); +fail_setup: + return result; +} +EXPORT_SYMBOL_GPL(ps3_irq_plug_setup); + +/** + * ps3_irq_plug_destroy - Generic outlet and virq related teardown. + * @virq: The assigned Linux virq. + * + * Disconnects the irq plug and tears down virq. + * Do not call for system bus event interrupts setup with + * ps3_sb_event_receive_port_setup(). + */ + +int ps3_irq_plug_destroy(unsigned int virq) +{ + int result; + const struct ps3_private *pd = irq_get_chip_data(virq); + + DBG("%s:%d: ppe_id %llu, thread_id %llu, virq %u\n", __func__, + __LINE__, pd->ppe_id, pd->thread_id, virq); + + ps3_chip_mask(irq_get_irq_data(virq)); + + result = lv1_disconnect_irq_plug_ext(pd->ppe_id, pd->thread_id, virq); + + if (result) + FAIL("%s:%d: lv1_disconnect_irq_plug_ext failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + ps3_virq_destroy(virq); + + return result; +} +EXPORT_SYMBOL_GPL(ps3_irq_plug_destroy); + +/** + * ps3_event_receive_port_setup - Setup an event receive port. + * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be + * serviced on. + * @virq: The assigned Linux virq. + * + * The virq can be used with lv1_connect_interrupt_event_receive_port() to + * arrange to receive interrupts from system-bus devices, or with + * ps3_send_event_locally() to signal events. + */ + +int ps3_event_receive_port_setup(enum ps3_cpu_binding cpu, unsigned int *virq) +{ + int result; + u64 outlet; + + result = lv1_construct_event_receive_port(&outlet); + + if (result) { + FAIL("%s:%d: lv1_construct_event_receive_port failed: %s\n", + __func__, __LINE__, ps3_result(result)); + *virq = 0; + return result; + } + + result = ps3_irq_plug_setup(cpu, outlet, virq); + BUG_ON(result); + + return result; +} +EXPORT_SYMBOL_GPL(ps3_event_receive_port_setup); + +/** + * ps3_event_receive_port_destroy - Destroy an event receive port. + * @virq: The assigned Linux virq. + * + * Since ps3_event_receive_port_destroy destroys the receive port outlet, + * SB devices need to call disconnect_interrupt_event_receive_port() before + * this. + */ + +int ps3_event_receive_port_destroy(unsigned int virq) +{ + int result; + + DBG(" -> %s:%d virq %u\n", __func__, __LINE__, virq); + + ps3_chip_mask(irq_get_irq_data(virq)); + + result = lv1_destruct_event_receive_port(virq_to_hw(virq)); + + if (result) + FAIL("%s:%d: lv1_destruct_event_receive_port failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + /* + * Don't call ps3_virq_destroy() here since ps3_smp_cleanup_cpu() + * calls from interrupt context (smp_call_function) when kexecing. + */ + + DBG(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +int ps3_send_event_locally(unsigned int virq) +{ + return lv1_send_event_locally(virq_to_hw(virq)); +} + +/** + * ps3_sb_event_receive_port_setup - Setup a system bus event receive port. + * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be + * serviced on. + * @dev: The system bus device instance. + * @virq: The assigned Linux virq. + * + * An event irq represents a virtual device interrupt. The interrupt_id + * coresponds to the software interrupt number. + */ + +int ps3_sb_event_receive_port_setup(struct ps3_system_bus_device *dev, + enum ps3_cpu_binding cpu, unsigned int *virq) +{ + /* this should go in system-bus.c */ + + int result; + + result = ps3_event_receive_port_setup(cpu, virq); + + if (result) + return result; + + result = lv1_connect_interrupt_event_receive_port(dev->bus_id, + dev->dev_id, virq_to_hw(*virq), dev->interrupt_id); + + if (result) { + FAIL("%s:%d: lv1_connect_interrupt_event_receive_port" + " failed: %s\n", __func__, __LINE__, + ps3_result(result)); + ps3_event_receive_port_destroy(*virq); + *virq = 0; + return result; + } + + DBG("%s:%d: interrupt_id %u, virq %u\n", __func__, __LINE__, + dev->interrupt_id, *virq); + + return 0; +} +EXPORT_SYMBOL(ps3_sb_event_receive_port_setup); + +int ps3_sb_event_receive_port_destroy(struct ps3_system_bus_device *dev, + unsigned int virq) +{ + /* this should go in system-bus.c */ + + int result; + + DBG(" -> %s:%d: interrupt_id %u, virq %u\n", __func__, __LINE__, + dev->interrupt_id, virq); + + result = lv1_disconnect_interrupt_event_receive_port(dev->bus_id, + dev->dev_id, virq_to_hw(virq), dev->interrupt_id); + + if (result) + FAIL("%s:%d: lv1_disconnect_interrupt_event_receive_port" + " failed: %s\n", __func__, __LINE__, + ps3_result(result)); + + result = ps3_event_receive_port_destroy(virq); + BUG_ON(result); + + /* + * ps3_event_receive_port_destroy() destroys the IRQ plug, + * so don't call ps3_irq_plug_destroy() here. + */ + + result = ps3_virq_destroy(virq); + BUG_ON(result); + + DBG(" <- %s:%d\n", __func__, __LINE__); + return result; +} +EXPORT_SYMBOL(ps3_sb_event_receive_port_destroy); + +/** + * ps3_io_irq_setup - Setup a system bus io irq. + * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be + * serviced on. + * @interrupt_id: The device interrupt id read from the system repository. + * @virq: The assigned Linux virq. + * + * An io irq represents a non-virtualized device interrupt. interrupt_id + * coresponds to the interrupt number of the interrupt controller. + */ + +int ps3_io_irq_setup(enum ps3_cpu_binding cpu, unsigned int interrupt_id, + unsigned int *virq) +{ + int result; + u64 outlet; + + result = lv1_construct_io_irq_outlet(interrupt_id, &outlet); + + if (result) { + FAIL("%s:%d: lv1_construct_io_irq_outlet failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + result = ps3_irq_plug_setup(cpu, outlet, virq); + BUG_ON(result); + + return result; +} +EXPORT_SYMBOL_GPL(ps3_io_irq_setup); + +int ps3_io_irq_destroy(unsigned int virq) +{ + int result; + unsigned long outlet = virq_to_hw(virq); + + ps3_chip_mask(irq_get_irq_data(virq)); + + /* + * lv1_destruct_io_irq_outlet() will destroy the IRQ plug, + * so call ps3_irq_plug_destroy() first. + */ + + result = ps3_irq_plug_destroy(virq); + BUG_ON(result); + + result = lv1_destruct_io_irq_outlet(outlet); + + if (result) + FAIL("%s:%d: lv1_destruct_io_irq_outlet failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + return result; +} +EXPORT_SYMBOL_GPL(ps3_io_irq_destroy); + +/** + * ps3_vuart_irq_setup - Setup the system virtual uart virq. + * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be + * serviced on. + * @virt_addr_bmp: The caller supplied virtual uart interrupt bitmap. + * @virq: The assigned Linux virq. + * + * The system supports only a single virtual uart, so multiple calls without + * freeing the interrupt will return a wrong state error. + */ + +int ps3_vuart_irq_setup(enum ps3_cpu_binding cpu, void* virt_addr_bmp, + unsigned int *virq) +{ + int result; + u64 outlet; + u64 lpar_addr; + + BUG_ON(!is_kernel_addr((u64)virt_addr_bmp)); + + lpar_addr = ps3_mm_phys_to_lpar(__pa(virt_addr_bmp)); + + result = lv1_configure_virtual_uart_irq(lpar_addr, &outlet); + + if (result) { + FAIL("%s:%d: lv1_configure_virtual_uart_irq failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + result = ps3_irq_plug_setup(cpu, outlet, virq); + BUG_ON(result); + + return result; +} +EXPORT_SYMBOL_GPL(ps3_vuart_irq_setup); + +int ps3_vuart_irq_destroy(unsigned int virq) +{ + int result; + + ps3_chip_mask(irq_get_irq_data(virq)); + result = lv1_deconfigure_virtual_uart_irq(); + + if (result) { + FAIL("%s:%d: lv1_configure_virtual_uart_irq failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + result = ps3_irq_plug_destroy(virq); + BUG_ON(result); + + return result; +} +EXPORT_SYMBOL_GPL(ps3_vuart_irq_destroy); + +/** + * ps3_spe_irq_setup - Setup an spe virq. + * @cpu: enum ps3_cpu_binding indicating the cpu the interrupt should be + * serviced on. + * @spe_id: The spe_id returned from lv1_construct_logical_spe(). + * @class: The spe interrupt class {0,1,2}. + * @virq: The assigned Linux virq. + * + */ + +int ps3_spe_irq_setup(enum ps3_cpu_binding cpu, unsigned long spe_id, + unsigned int class, unsigned int *virq) +{ + int result; + u64 outlet; + + BUG_ON(class > 2); + + result = lv1_get_spe_irq_outlet(spe_id, class, &outlet); + + if (result) { + FAIL("%s:%d: lv1_get_spe_irq_outlet failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + result = ps3_irq_plug_setup(cpu, outlet, virq); + BUG_ON(result); + + return result; +} + +int ps3_spe_irq_destroy(unsigned int virq) +{ + int result; + + ps3_chip_mask(irq_get_irq_data(virq)); + + result = ps3_irq_plug_destroy(virq); + BUG_ON(result); + + return result; +} + + +#define PS3_INVALID_OUTLET ((irq_hw_number_t)-1) +#define PS3_PLUG_MAX 63 + +#if defined(DEBUG) +static void _dump_64_bmp(const char *header, const u64 *p, unsigned cpu, + const char* func, int line) +{ + pr_debug("%s:%d: %s %u {%04llx_%04llx_%04llx_%04llx}\n", + func, line, header, cpu, + *p >> 48, (*p >> 32) & 0xffff, (*p >> 16) & 0xffff, + *p & 0xffff); +} + +static void __maybe_unused _dump_256_bmp(const char *header, + const u64 *p, unsigned cpu, const char* func, int line) +{ + pr_debug("%s:%d: %s %u {%016llx:%016llx:%016llx:%016llx}\n", + func, line, header, cpu, p[0], p[1], p[2], p[3]); +} + +#define dump_bmp(_x) _dump_bmp(_x, __func__, __LINE__) +static void _dump_bmp(struct ps3_private* pd, const char* func, int line) +{ + unsigned long flags; + + spin_lock_irqsave(&pd->bmp_lock, flags); + _dump_64_bmp("stat", &pd->bmp.status, pd->thread_id, func, line); + _dump_64_bmp("mask", (u64*)&pd->bmp.mask, pd->thread_id, func, line); + spin_unlock_irqrestore(&pd->bmp_lock, flags); +} + +#define dump_mask(_x) _dump_mask(_x, __func__, __LINE__) +static void __maybe_unused _dump_mask(struct ps3_private *pd, + const char* func, int line) +{ + unsigned long flags; + + spin_lock_irqsave(&pd->bmp_lock, flags); + _dump_64_bmp("mask", (u64*)&pd->bmp.mask, pd->thread_id, func, line); + spin_unlock_irqrestore(&pd->bmp_lock, flags); +} +#else +static void dump_bmp(struct ps3_private* pd) {}; +#endif /* defined(DEBUG) */ + +static int ps3_host_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hwirq) +{ + DBG("%s:%d: hwirq %lu, virq %u\n", __func__, __LINE__, hwirq, + virq); + + irq_set_chip_and_handler(virq, &ps3_irq_chip, handle_fasteoi_irq); + + return 0; +} + +static int ps3_host_match(struct irq_domain *h, struct device_node *np, + enum irq_domain_bus_token bus_token) +{ + /* Match all */ + return 1; +} + +static const struct irq_domain_ops ps3_host_ops = { + .map = ps3_host_map, + .match = ps3_host_match, +}; + +void __init ps3_register_ipi_debug_brk(unsigned int cpu, unsigned int virq) +{ + struct ps3_private *pd = &per_cpu(ps3_private, cpu); + + set_bit(63 - virq, &pd->ipi_debug_brk_mask); + + DBG("%s:%d: cpu %u, virq %u, mask %lxh\n", __func__, __LINE__, + cpu, virq, pd->ipi_debug_brk_mask); +} + +void __init ps3_register_ipi_irq(unsigned int cpu, unsigned int virq) +{ + struct ps3_private *pd = &per_cpu(ps3_private, cpu); + + set_bit(63 - virq, &pd->ipi_mask); + + DBG("%s:%d: cpu %u, virq %u, ipi_mask %lxh\n", __func__, __LINE__, + cpu, virq, pd->ipi_mask); +} + +static unsigned int ps3_get_irq(void) +{ + struct ps3_private *pd = this_cpu_ptr(&ps3_private); + u64 x = (pd->bmp.status & pd->bmp.mask); + unsigned int plug; + + /* check for ipi break first to stop this cpu ASAP */ + + if (x & pd->ipi_debug_brk_mask) + x &= pd->ipi_debug_brk_mask; + + asm volatile("cntlzd %0,%1" : "=r" (plug) : "r" (x)); + plug &= 0x3f; + + if (unlikely(!plug)) { + DBG("%s:%d: no plug found: thread_id %llu\n", __func__, + __LINE__, pd->thread_id); + dump_bmp(&per_cpu(ps3_private, 0)); + dump_bmp(&per_cpu(ps3_private, 1)); + return 0; + } + +#if defined(DEBUG) + if (unlikely(plug < NUM_ISA_INTERRUPTS || plug > PS3_PLUG_MAX)) { + dump_bmp(&per_cpu(ps3_private, 0)); + dump_bmp(&per_cpu(ps3_private, 1)); + BUG(); + } +#endif + + /* IPIs are EOIed here. */ + + if (test_bit(63 - plug, &pd->ipi_mask)) + lv1_end_of_interrupt_ext(pd->ppe_id, pd->thread_id, plug); + + return plug; +} + +void __init ps3_init_IRQ(void) +{ + int result; + unsigned cpu; + struct irq_domain *host; + + host = irq_domain_add_nomap(NULL, PS3_PLUG_MAX + 1, &ps3_host_ops, NULL); + irq_set_default_host(host); + + for_each_possible_cpu(cpu) { + struct ps3_private *pd = &per_cpu(ps3_private, cpu); + + lv1_get_logical_ppe_id(&pd->ppe_id); + pd->thread_id = get_hard_smp_processor_id(cpu); + spin_lock_init(&pd->bmp_lock); + + DBG("%s:%d: ppe_id %llu, thread_id %llu, bmp %lxh\n", + __func__, __LINE__, pd->ppe_id, pd->thread_id, + ps3_mm_phys_to_lpar(__pa(&pd->bmp))); + + result = lv1_configure_irq_state_bitmap(pd->ppe_id, + pd->thread_id, ps3_mm_phys_to_lpar(__pa(&pd->bmp))); + + if (result) + FAIL("%s:%d: lv1_configure_irq_state_bitmap failed:" + " %s\n", __func__, __LINE__, + ps3_result(result)); + } + + ppc_md.get_irq = ps3_get_irq; +} + +void ps3_shutdown_IRQ(int cpu) +{ + int result; + u64 ppe_id; + u64 thread_id = get_hard_smp_processor_id(cpu); + + lv1_get_logical_ppe_id(&ppe_id); + result = lv1_configure_irq_state_bitmap(ppe_id, thread_id, 0); + + DBG("%s:%d: lv1_configure_irq_state_bitmap (%llu:%llu/%d) %s\n", __func__, + __LINE__, ppe_id, thread_id, cpu, ps3_result(result)); +} diff --git a/arch/powerpc/platforms/ps3/mm.c b/arch/powerpc/platforms/ps3/mm.c new file mode 100644 index 000000000..a81eac35d --- /dev/null +++ b/arch/powerpc/platforms/ps3/mm.c @@ -0,0 +1,1251 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 address space management. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/dma-mapping.h> +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/memblock.h> +#include <linux/slab.h> + +#include <asm/cell-regs.h> +#include <asm/firmware.h> +#include <asm/prom.h> +#include <asm/udbg.h> +#include <asm/lv1call.h> +#include <asm/setup.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG udbg_printf +#else +#define DBG pr_devel +#endif + +enum { +#if defined(CONFIG_PS3_DYNAMIC_DMA) + USE_DYNAMIC_DMA = 1, +#else + USE_DYNAMIC_DMA = 0, +#endif +}; + +enum { + PAGE_SHIFT_4K = 12U, + PAGE_SHIFT_64K = 16U, + PAGE_SHIFT_16M = 24U, +}; + +static unsigned long make_page_sizes(unsigned long a, unsigned long b) +{ + return (a << 56) | (b << 48); +} + +enum { + ALLOCATE_MEMORY_TRY_ALT_UNIT = 0X04, + ALLOCATE_MEMORY_ADDR_ZERO = 0X08, +}; + +/* valid htab sizes are {18,19,20} = 256K, 512K, 1M */ + +enum { + HTAB_SIZE_MAX = 20U, /* HV limit of 1MB */ + HTAB_SIZE_MIN = 18U, /* CPU limit of 256KB */ +}; + +/*============================================================================*/ +/* virtual address space routines */ +/*============================================================================*/ + +/** + * struct mem_region - memory region structure + * @base: base address + * @size: size in bytes + * @offset: difference between base and rm.size + * @destroy: flag if region should be destroyed upon shutdown + */ + +struct mem_region { + u64 base; + u64 size; + unsigned long offset; + int destroy; +}; + +/** + * struct map - address space state variables holder + * @total: total memory available as reported by HV + * @vas_id - HV virtual address space id + * @htab_size: htab size in bytes + * + * The HV virtual address space (vas) allows for hotplug memory regions. + * Memory regions can be created and destroyed in the vas at runtime. + * @rm: real mode (bootmem) region + * @r1: highmem region(s) + * + * ps3 addresses + * virt_addr: a cpu 'translated' effective address + * phys_addr: an address in what Linux thinks is the physical address space + * lpar_addr: an address in the HV virtual address space + * bus_addr: an io controller 'translated' address on a device bus + */ + +struct map { + u64 total; + u64 vas_id; + u64 htab_size; + struct mem_region rm; + struct mem_region r1; +}; + +#define debug_dump_map(x) _debug_dump_map(x, __func__, __LINE__) +static void __maybe_unused _debug_dump_map(const struct map *m, + const char *func, int line) +{ + DBG("%s:%d: map.total = %llxh\n", func, line, m->total); + DBG("%s:%d: map.rm.size = %llxh\n", func, line, m->rm.size); + DBG("%s:%d: map.vas_id = %llu\n", func, line, m->vas_id); + DBG("%s:%d: map.htab_size = %llxh\n", func, line, m->htab_size); + DBG("%s:%d: map.r1.base = %llxh\n", func, line, m->r1.base); + DBG("%s:%d: map.r1.offset = %lxh\n", func, line, m->r1.offset); + DBG("%s:%d: map.r1.size = %llxh\n", func, line, m->r1.size); +} + +static struct map map; + +/** + * ps3_mm_phys_to_lpar - translate a linux physical address to lpar address + * @phys_addr: linux physical address + */ + +unsigned long ps3_mm_phys_to_lpar(unsigned long phys_addr) +{ + BUG_ON(is_kernel_addr(phys_addr)); + return (phys_addr < map.rm.size || phys_addr >= map.total) + ? phys_addr : phys_addr + map.r1.offset; +} + +EXPORT_SYMBOL(ps3_mm_phys_to_lpar); + +/** + * ps3_mm_vas_create - create the virtual address space + */ + +void __init ps3_mm_vas_create(unsigned long* htab_size) +{ + int result; + u64 start_address; + u64 size; + u64 access_right; + u64 max_page_size; + u64 flags; + + result = lv1_query_logical_partition_address_region_info(0, + &start_address, &size, &access_right, &max_page_size, + &flags); + + if (result) { + DBG("%s:%d: lv1_query_logical_partition_address_region_info " + "failed: %s\n", __func__, __LINE__, + ps3_result(result)); + goto fail; + } + + if (max_page_size < PAGE_SHIFT_16M) { + DBG("%s:%d: bad max_page_size %llxh\n", __func__, __LINE__, + max_page_size); + goto fail; + } + + BUILD_BUG_ON(CONFIG_PS3_HTAB_SIZE > HTAB_SIZE_MAX); + BUILD_BUG_ON(CONFIG_PS3_HTAB_SIZE < HTAB_SIZE_MIN); + + result = lv1_construct_virtual_address_space(CONFIG_PS3_HTAB_SIZE, + 2, make_page_sizes(PAGE_SHIFT_16M, PAGE_SHIFT_64K), + &map.vas_id, &map.htab_size); + + if (result) { + DBG("%s:%d: lv1_construct_virtual_address_space failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail; + } + + result = lv1_select_virtual_address_space(map.vas_id); + + if (result) { + DBG("%s:%d: lv1_select_virtual_address_space failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail; + } + + *htab_size = map.htab_size; + + debug_dump_map(&map); + + return; + +fail: + panic("ps3_mm_vas_create failed"); +} + +/** + * ps3_mm_vas_destroy - + */ + +void ps3_mm_vas_destroy(void) +{ + int result; + + if (map.vas_id) { + result = lv1_select_virtual_address_space(0); + result += lv1_destruct_virtual_address_space(map.vas_id); + + if (result) { + lv1_panic(0); + } + + map.vas_id = 0; + } +} + +static int ps3_mm_get_repository_highmem(struct mem_region *r) +{ + int result; + + /* Assume a single highmem region. */ + + result = ps3_repository_read_highmem_info(0, &r->base, &r->size); + + if (result) + goto zero_region; + + if (!r->base || !r->size) { + result = -1; + goto zero_region; + } + + r->offset = r->base - map.rm.size; + + DBG("%s:%d: Found high region in repository: %llxh %llxh\n", + __func__, __LINE__, r->base, r->size); + + return 0; + +zero_region: + DBG("%s:%d: No high region in repository.\n", __func__, __LINE__); + + r->size = r->base = r->offset = 0; + return result; +} + +static int ps3_mm_set_repository_highmem(const struct mem_region *r) +{ + /* Assume a single highmem region. */ + + return r ? ps3_repository_write_highmem_info(0, r->base, r->size) : + ps3_repository_write_highmem_info(0, 0, 0); +} + +/** + * ps3_mm_region_create - create a memory region in the vas + * @r: pointer to a struct mem_region to accept initialized values + * @size: requested region size + * + * This implementation creates the region with the vas large page size. + * @size is rounded down to a multiple of the vas large page size. + */ + +static int ps3_mm_region_create(struct mem_region *r, unsigned long size) +{ + int result; + u64 muid; + + r->size = ALIGN_DOWN(size, 1 << PAGE_SHIFT_16M); + + DBG("%s:%d requested %lxh\n", __func__, __LINE__, size); + DBG("%s:%d actual %llxh\n", __func__, __LINE__, r->size); + DBG("%s:%d difference %llxh (%lluMB)\n", __func__, __LINE__, + size - r->size, (size - r->size) / 1024 / 1024); + + if (r->size == 0) { + DBG("%s:%d: size == 0\n", __func__, __LINE__); + result = -1; + goto zero_region; + } + + result = lv1_allocate_memory(r->size, PAGE_SHIFT_16M, 0, + ALLOCATE_MEMORY_TRY_ALT_UNIT, &r->base, &muid); + + if (result || r->base < map.rm.size) { + DBG("%s:%d: lv1_allocate_memory failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto zero_region; + } + + r->destroy = 1; + r->offset = r->base - map.rm.size; + return result; + +zero_region: + r->size = r->base = r->offset = 0; + return result; +} + +/** + * ps3_mm_region_destroy - destroy a memory region + * @r: pointer to struct mem_region + */ + +static void ps3_mm_region_destroy(struct mem_region *r) +{ + int result; + + if (!r->destroy) { + return; + } + + if (r->base) { + result = lv1_release_memory(r->base); + + if (result) { + lv1_panic(0); + } + + r->size = r->base = r->offset = 0; + map.total = map.rm.size; + } + + ps3_mm_set_repository_highmem(NULL); +} + +/*============================================================================*/ +/* dma routines */ +/*============================================================================*/ + +/** + * dma_sb_lpar_to_bus - Translate an lpar address to ioc mapped bus address. + * @r: pointer to dma region structure + * @lpar_addr: HV lpar address + */ + +static unsigned long dma_sb_lpar_to_bus(struct ps3_dma_region *r, + unsigned long lpar_addr) +{ + if (lpar_addr >= map.rm.size) + lpar_addr -= map.r1.offset; + BUG_ON(lpar_addr < r->offset); + BUG_ON(lpar_addr >= r->offset + r->len); + return r->bus_addr + lpar_addr - r->offset; +} + +#define dma_dump_region(_a) _dma_dump_region(_a, __func__, __LINE__) +static void __maybe_unused _dma_dump_region(const struct ps3_dma_region *r, + const char *func, int line) +{ + DBG("%s:%d: dev %llu:%llu\n", func, line, r->dev->bus_id, + r->dev->dev_id); + DBG("%s:%d: page_size %u\n", func, line, r->page_size); + DBG("%s:%d: bus_addr %lxh\n", func, line, r->bus_addr); + DBG("%s:%d: len %lxh\n", func, line, r->len); + DBG("%s:%d: offset %lxh\n", func, line, r->offset); +} + + /** + * dma_chunk - A chunk of dma pages mapped by the io controller. + * @region - The dma region that owns this chunk. + * @lpar_addr: Starting lpar address of the area to map. + * @bus_addr: Starting ioc bus address of the area to map. + * @len: Length in bytes of the area to map. + * @link: A struct list_head used with struct ps3_dma_region.chunk_list, the + * list of all chuncks owned by the region. + * + * This implementation uses a very simple dma page manager + * based on the dma_chunk structure. This scheme assumes + * that all drivers use very well behaved dma ops. + */ + +struct dma_chunk { + struct ps3_dma_region *region; + unsigned long lpar_addr; + unsigned long bus_addr; + unsigned long len; + struct list_head link; + unsigned int usage_count; +}; + +#define dma_dump_chunk(_a) _dma_dump_chunk(_a, __func__, __LINE__) +static void _dma_dump_chunk (const struct dma_chunk* c, const char* func, + int line) +{ + DBG("%s:%d: r.dev %llu:%llu\n", func, line, + c->region->dev->bus_id, c->region->dev->dev_id); + DBG("%s:%d: r.bus_addr %lxh\n", func, line, c->region->bus_addr); + DBG("%s:%d: r.page_size %u\n", func, line, c->region->page_size); + DBG("%s:%d: r.len %lxh\n", func, line, c->region->len); + DBG("%s:%d: r.offset %lxh\n", func, line, c->region->offset); + DBG("%s:%d: c.lpar_addr %lxh\n", func, line, c->lpar_addr); + DBG("%s:%d: c.bus_addr %lxh\n", func, line, c->bus_addr); + DBG("%s:%d: c.len %lxh\n", func, line, c->len); +} + +static struct dma_chunk * dma_find_chunk(struct ps3_dma_region *r, + unsigned long bus_addr, unsigned long len) +{ + struct dma_chunk *c; + unsigned long aligned_bus = ALIGN_DOWN(bus_addr, 1 << r->page_size); + unsigned long aligned_len = ALIGN(len+bus_addr-aligned_bus, + 1 << r->page_size); + + list_for_each_entry(c, &r->chunk_list.head, link) { + /* intersection */ + if (aligned_bus >= c->bus_addr && + aligned_bus + aligned_len <= c->bus_addr + c->len) + return c; + + /* below */ + if (aligned_bus + aligned_len <= c->bus_addr) + continue; + + /* above */ + if (aligned_bus >= c->bus_addr + c->len) + continue; + + /* we don't handle the multi-chunk case for now */ + dma_dump_chunk(c); + BUG(); + } + return NULL; +} + +static struct dma_chunk *dma_find_chunk_lpar(struct ps3_dma_region *r, + unsigned long lpar_addr, unsigned long len) +{ + struct dma_chunk *c; + unsigned long aligned_lpar = ALIGN_DOWN(lpar_addr, 1 << r->page_size); + unsigned long aligned_len = ALIGN(len + lpar_addr - aligned_lpar, + 1 << r->page_size); + + list_for_each_entry(c, &r->chunk_list.head, link) { + /* intersection */ + if (c->lpar_addr <= aligned_lpar && + aligned_lpar < c->lpar_addr + c->len) { + if (aligned_lpar + aligned_len <= c->lpar_addr + c->len) + return c; + else { + dma_dump_chunk(c); + BUG(); + } + } + /* below */ + if (aligned_lpar + aligned_len <= c->lpar_addr) { + continue; + } + /* above */ + if (c->lpar_addr + c->len <= aligned_lpar) { + continue; + } + } + return NULL; +} + +static int dma_sb_free_chunk(struct dma_chunk *c) +{ + int result = 0; + + if (c->bus_addr) { + result = lv1_unmap_device_dma_region(c->region->dev->bus_id, + c->region->dev->dev_id, c->bus_addr, c->len); + BUG_ON(result); + } + + kfree(c); + return result; +} + +static int dma_ioc0_free_chunk(struct dma_chunk *c) +{ + int result = 0; + int iopage; + unsigned long offset; + struct ps3_dma_region *r = c->region; + + DBG("%s:start\n", __func__); + for (iopage = 0; iopage < (c->len >> r->page_size); iopage++) { + offset = (1 << r->page_size) * iopage; + /* put INVALID entry */ + result = lv1_put_iopte(0, + c->bus_addr + offset, + c->lpar_addr + offset, + r->ioid, + 0); + DBG("%s: bus=%#lx, lpar=%#lx, ioid=%d\n", __func__, + c->bus_addr + offset, + c->lpar_addr + offset, + r->ioid); + + if (result) { + DBG("%s:%d: lv1_put_iopte failed: %s\n", __func__, + __LINE__, ps3_result(result)); + } + } + kfree(c); + DBG("%s:end\n", __func__); + return result; +} + +/** + * dma_sb_map_pages - Maps dma pages into the io controller bus address space. + * @r: Pointer to a struct ps3_dma_region. + * @phys_addr: Starting physical address of the area to map. + * @len: Length in bytes of the area to map. + * c_out: A pointer to receive an allocated struct dma_chunk for this area. + * + * This is the lowest level dma mapping routine, and is the one that will + * make the HV call to add the pages into the io controller address space. + */ + +static int dma_sb_map_pages(struct ps3_dma_region *r, unsigned long phys_addr, + unsigned long len, struct dma_chunk **c_out, u64 iopte_flag) +{ + int result; + struct dma_chunk *c; + + c = kzalloc(sizeof(*c), GFP_ATOMIC); + if (!c) { + result = -ENOMEM; + goto fail_alloc; + } + + c->region = r; + c->lpar_addr = ps3_mm_phys_to_lpar(phys_addr); + c->bus_addr = dma_sb_lpar_to_bus(r, c->lpar_addr); + c->len = len; + + BUG_ON(iopte_flag != 0xf800000000000000UL); + result = lv1_map_device_dma_region(c->region->dev->bus_id, + c->region->dev->dev_id, c->lpar_addr, + c->bus_addr, c->len, iopte_flag); + if (result) { + DBG("%s:%d: lv1_map_device_dma_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail_map; + } + + list_add(&c->link, &r->chunk_list.head); + + *c_out = c; + return 0; + +fail_map: + kfree(c); +fail_alloc: + *c_out = NULL; + DBG(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +static int dma_ioc0_map_pages(struct ps3_dma_region *r, unsigned long phys_addr, + unsigned long len, struct dma_chunk **c_out, + u64 iopte_flag) +{ + int result; + struct dma_chunk *c, *last; + int iopage, pages; + unsigned long offset; + + DBG(KERN_ERR "%s: phy=%#lx, lpar%#lx, len=%#lx\n", __func__, + phys_addr, ps3_mm_phys_to_lpar(phys_addr), len); + c = kzalloc(sizeof(*c), GFP_ATOMIC); + if (!c) { + result = -ENOMEM; + goto fail_alloc; + } + + c->region = r; + c->len = len; + c->lpar_addr = ps3_mm_phys_to_lpar(phys_addr); + /* allocate IO address */ + if (list_empty(&r->chunk_list.head)) { + /* first one */ + c->bus_addr = r->bus_addr; + } else { + /* derive from last bus addr*/ + last = list_entry(r->chunk_list.head.next, + struct dma_chunk, link); + c->bus_addr = last->bus_addr + last->len; + DBG("%s: last bus=%#lx, len=%#lx\n", __func__, + last->bus_addr, last->len); + } + + /* FIXME: check whether length exceeds region size */ + + /* build ioptes for the area */ + pages = len >> r->page_size; + DBG("%s: pgsize=%#x len=%#lx pages=%#x iopteflag=%#llx\n", __func__, + r->page_size, r->len, pages, iopte_flag); + for (iopage = 0; iopage < pages; iopage++) { + offset = (1 << r->page_size) * iopage; + result = lv1_put_iopte(0, + c->bus_addr + offset, + c->lpar_addr + offset, + r->ioid, + iopte_flag); + if (result) { + pr_warn("%s:%d: lv1_put_iopte failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail_map; + } + DBG("%s: pg=%d bus=%#lx, lpar=%#lx, ioid=%#x\n", __func__, + iopage, c->bus_addr + offset, c->lpar_addr + offset, + r->ioid); + } + + /* be sure that last allocated one is inserted at head */ + list_add(&c->link, &r->chunk_list.head); + + *c_out = c; + DBG("%s: end\n", __func__); + return 0; + +fail_map: + for (iopage--; 0 <= iopage; iopage--) { + lv1_put_iopte(0, + c->bus_addr + offset, + c->lpar_addr + offset, + r->ioid, + 0); + } + kfree(c); +fail_alloc: + *c_out = NULL; + return result; +} + +/** + * dma_sb_region_create - Create a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * + * This is the lowest level dma region create routine, and is the one that + * will make the HV call to create the region. + */ + +static int dma_sb_region_create(struct ps3_dma_region *r) +{ + int result; + u64 bus_addr; + + DBG(" -> %s:%d:\n", __func__, __LINE__); + + BUG_ON(!r); + + if (!r->dev->bus_id) { + pr_info("%s:%d: %llu:%llu no dma\n", __func__, __LINE__, + r->dev->bus_id, r->dev->dev_id); + return 0; + } + + DBG("%s:%u: len = 0x%lx, page_size = %u, offset = 0x%lx\n", __func__, + __LINE__, r->len, r->page_size, r->offset); + + BUG_ON(!r->len); + BUG_ON(!r->page_size); + BUG_ON(!r->region_ops); + + INIT_LIST_HEAD(&r->chunk_list.head); + spin_lock_init(&r->chunk_list.lock); + + result = lv1_allocate_device_dma_region(r->dev->bus_id, r->dev->dev_id, + roundup_pow_of_two(r->len), r->page_size, r->region_type, + &bus_addr); + r->bus_addr = bus_addr; + + if (result) { + DBG("%s:%d: lv1_allocate_device_dma_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + r->len = r->bus_addr = 0; + } + + return result; +} + +static int dma_ioc0_region_create(struct ps3_dma_region *r) +{ + int result; + u64 bus_addr; + + INIT_LIST_HEAD(&r->chunk_list.head); + spin_lock_init(&r->chunk_list.lock); + + result = lv1_allocate_io_segment(0, + r->len, + r->page_size, + &bus_addr); + r->bus_addr = bus_addr; + if (result) { + DBG("%s:%d: lv1_allocate_io_segment failed: %s\n", + __func__, __LINE__, ps3_result(result)); + r->len = r->bus_addr = 0; + } + DBG("%s: len=%#lx, pg=%d, bus=%#lx\n", __func__, + r->len, r->page_size, r->bus_addr); + return result; +} + +/** + * dma_region_free - Free a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * + * This is the lowest level dma region free routine, and is the one that + * will make the HV call to free the region. + */ + +static int dma_sb_region_free(struct ps3_dma_region *r) +{ + int result; + struct dma_chunk *c; + struct dma_chunk *tmp; + + BUG_ON(!r); + + if (!r->dev->bus_id) { + pr_info("%s:%d: %llu:%llu no dma\n", __func__, __LINE__, + r->dev->bus_id, r->dev->dev_id); + return 0; + } + + list_for_each_entry_safe(c, tmp, &r->chunk_list.head, link) { + list_del(&c->link); + dma_sb_free_chunk(c); + } + + result = lv1_free_device_dma_region(r->dev->bus_id, r->dev->dev_id, + r->bus_addr); + + if (result) + DBG("%s:%d: lv1_free_device_dma_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + r->bus_addr = 0; + + return result; +} + +static int dma_ioc0_region_free(struct ps3_dma_region *r) +{ + int result; + struct dma_chunk *c, *n; + + DBG("%s: start\n", __func__); + list_for_each_entry_safe(c, n, &r->chunk_list.head, link) { + list_del(&c->link); + dma_ioc0_free_chunk(c); + } + + result = lv1_release_io_segment(0, r->bus_addr); + + if (result) + DBG("%s:%d: lv1_free_device_dma_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + r->bus_addr = 0; + DBG("%s: end\n", __func__); + + return result; +} + +/** + * dma_sb_map_area - Map an area of memory into a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @virt_addr: Starting virtual address of the area to map. + * @len: Length in bytes of the area to map. + * @bus_addr: A pointer to return the starting ioc bus address of the area to + * map. + * + * This is the common dma mapping routine. + */ + +static int dma_sb_map_area(struct ps3_dma_region *r, unsigned long virt_addr, + unsigned long len, dma_addr_t *bus_addr, + u64 iopte_flag) +{ + int result; + unsigned long flags; + struct dma_chunk *c; + unsigned long phys_addr = is_kernel_addr(virt_addr) ? __pa(virt_addr) + : virt_addr; + unsigned long aligned_phys = ALIGN_DOWN(phys_addr, 1 << r->page_size); + unsigned long aligned_len = ALIGN(len + phys_addr - aligned_phys, + 1 << r->page_size); + *bus_addr = dma_sb_lpar_to_bus(r, ps3_mm_phys_to_lpar(phys_addr)); + + if (!USE_DYNAMIC_DMA) { + unsigned long lpar_addr = ps3_mm_phys_to_lpar(phys_addr); + DBG(" -> %s:%d\n", __func__, __LINE__); + DBG("%s:%d virt_addr %lxh\n", __func__, __LINE__, + virt_addr); + DBG("%s:%d phys_addr %lxh\n", __func__, __LINE__, + phys_addr); + DBG("%s:%d lpar_addr %lxh\n", __func__, __LINE__, + lpar_addr); + DBG("%s:%d len %lxh\n", __func__, __LINE__, len); + DBG("%s:%d bus_addr %llxh (%lxh)\n", __func__, __LINE__, + *bus_addr, len); + } + + spin_lock_irqsave(&r->chunk_list.lock, flags); + c = dma_find_chunk(r, *bus_addr, len); + + if (c) { + DBG("%s:%d: reusing mapped chunk", __func__, __LINE__); + dma_dump_chunk(c); + c->usage_count++; + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return 0; + } + + result = dma_sb_map_pages(r, aligned_phys, aligned_len, &c, iopte_flag); + + if (result) { + *bus_addr = 0; + DBG("%s:%d: dma_sb_map_pages failed (%d)\n", + __func__, __LINE__, result); + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return result; + } + + c->usage_count = 1; + + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return result; +} + +static int dma_ioc0_map_area(struct ps3_dma_region *r, unsigned long virt_addr, + unsigned long len, dma_addr_t *bus_addr, + u64 iopte_flag) +{ + int result; + unsigned long flags; + struct dma_chunk *c; + unsigned long phys_addr = is_kernel_addr(virt_addr) ? __pa(virt_addr) + : virt_addr; + unsigned long aligned_phys = ALIGN_DOWN(phys_addr, 1 << r->page_size); + unsigned long aligned_len = ALIGN(len + phys_addr - aligned_phys, + 1 << r->page_size); + + DBG(KERN_ERR "%s: vaddr=%#lx, len=%#lx\n", __func__, + virt_addr, len); + DBG(KERN_ERR "%s: ph=%#lx a_ph=%#lx a_l=%#lx\n", __func__, + phys_addr, aligned_phys, aligned_len); + + spin_lock_irqsave(&r->chunk_list.lock, flags); + c = dma_find_chunk_lpar(r, ps3_mm_phys_to_lpar(phys_addr), len); + + if (c) { + /* FIXME */ + BUG(); + *bus_addr = c->bus_addr + phys_addr - aligned_phys; + c->usage_count++; + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return 0; + } + + result = dma_ioc0_map_pages(r, aligned_phys, aligned_len, &c, + iopte_flag); + + if (result) { + *bus_addr = 0; + DBG("%s:%d: dma_ioc0_map_pages failed (%d)\n", + __func__, __LINE__, result); + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return result; + } + *bus_addr = c->bus_addr + phys_addr - aligned_phys; + DBG("%s: va=%#lx pa=%#lx a_pa=%#lx bus=%#llx\n", __func__, + virt_addr, phys_addr, aligned_phys, *bus_addr); + c->usage_count = 1; + + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return result; +} + +/** + * dma_sb_unmap_area - Unmap an area of memory from a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @bus_addr: The starting ioc bus address of the area to unmap. + * @len: Length in bytes of the area to unmap. + * + * This is the common dma unmap routine. + */ + +static int dma_sb_unmap_area(struct ps3_dma_region *r, dma_addr_t bus_addr, + unsigned long len) +{ + unsigned long flags; + struct dma_chunk *c; + + spin_lock_irqsave(&r->chunk_list.lock, flags); + c = dma_find_chunk(r, bus_addr, len); + + if (!c) { + unsigned long aligned_bus = ALIGN_DOWN(bus_addr, + 1 << r->page_size); + unsigned long aligned_len = ALIGN(len + bus_addr + - aligned_bus, 1 << r->page_size); + DBG("%s:%d: not found: bus_addr %llxh\n", + __func__, __LINE__, bus_addr); + DBG("%s:%d: not found: len %lxh\n", + __func__, __LINE__, len); + DBG("%s:%d: not found: aligned_bus %lxh\n", + __func__, __LINE__, aligned_bus); + DBG("%s:%d: not found: aligned_len %lxh\n", + __func__, __LINE__, aligned_len); + BUG(); + } + + c->usage_count--; + + if (!c->usage_count) { + list_del(&c->link); + dma_sb_free_chunk(c); + } + + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + return 0; +} + +static int dma_ioc0_unmap_area(struct ps3_dma_region *r, + dma_addr_t bus_addr, unsigned long len) +{ + unsigned long flags; + struct dma_chunk *c; + + DBG("%s: start a=%#llx l=%#lx\n", __func__, bus_addr, len); + spin_lock_irqsave(&r->chunk_list.lock, flags); + c = dma_find_chunk(r, bus_addr, len); + + if (!c) { + unsigned long aligned_bus = ALIGN_DOWN(bus_addr, + 1 << r->page_size); + unsigned long aligned_len = ALIGN(len + bus_addr + - aligned_bus, + 1 << r->page_size); + DBG("%s:%d: not found: bus_addr %llxh\n", + __func__, __LINE__, bus_addr); + DBG("%s:%d: not found: len %lxh\n", + __func__, __LINE__, len); + DBG("%s:%d: not found: aligned_bus %lxh\n", + __func__, __LINE__, aligned_bus); + DBG("%s:%d: not found: aligned_len %lxh\n", + __func__, __LINE__, aligned_len); + BUG(); + } + + c->usage_count--; + + if (!c->usage_count) { + list_del(&c->link); + dma_ioc0_free_chunk(c); + } + + spin_unlock_irqrestore(&r->chunk_list.lock, flags); + DBG("%s: end\n", __func__); + return 0; +} + +/** + * dma_sb_region_create_linear - Setup a linear dma mapping for a device. + * @r: Pointer to a struct ps3_dma_region. + * + * This routine creates an HV dma region for the device and maps all available + * ram into the io controller bus address space. + */ + +static int dma_sb_region_create_linear(struct ps3_dma_region *r) +{ + int result; + unsigned long virt_addr, len; + dma_addr_t tmp; + + if (r->len > 16*1024*1024) { /* FIXME: need proper fix */ + /* force 16M dma pages for linear mapping */ + if (r->page_size != PS3_DMA_16M) { + pr_info("%s:%d: forcing 16M pages for linear map\n", + __func__, __LINE__); + r->page_size = PS3_DMA_16M; + r->len = ALIGN(r->len, 1 << r->page_size); + } + } + + result = dma_sb_region_create(r); + BUG_ON(result); + + if (r->offset < map.rm.size) { + /* Map (part of) 1st RAM chunk */ + virt_addr = map.rm.base + r->offset; + len = map.rm.size - r->offset; + if (len > r->len) + len = r->len; + result = dma_sb_map_area(r, virt_addr, len, &tmp, + CBE_IOPTE_PP_W | CBE_IOPTE_PP_R | CBE_IOPTE_SO_RW | + CBE_IOPTE_M); + BUG_ON(result); + } + + if (r->offset + r->len > map.rm.size) { + /* Map (part of) 2nd RAM chunk */ + virt_addr = map.rm.size; + len = r->len; + if (r->offset >= map.rm.size) + virt_addr += r->offset - map.rm.size; + else + len -= map.rm.size - r->offset; + result = dma_sb_map_area(r, virt_addr, len, &tmp, + CBE_IOPTE_PP_W | CBE_IOPTE_PP_R | CBE_IOPTE_SO_RW | + CBE_IOPTE_M); + BUG_ON(result); + } + + return result; +} + +/** + * dma_sb_region_free_linear - Free a linear dma mapping for a device. + * @r: Pointer to a struct ps3_dma_region. + * + * This routine will unmap all mapped areas and free the HV dma region. + */ + +static int dma_sb_region_free_linear(struct ps3_dma_region *r) +{ + int result; + dma_addr_t bus_addr; + unsigned long len, lpar_addr; + + if (r->offset < map.rm.size) { + /* Unmap (part of) 1st RAM chunk */ + lpar_addr = map.rm.base + r->offset; + len = map.rm.size - r->offset; + if (len > r->len) + len = r->len; + bus_addr = dma_sb_lpar_to_bus(r, lpar_addr); + result = dma_sb_unmap_area(r, bus_addr, len); + BUG_ON(result); + } + + if (r->offset + r->len > map.rm.size) { + /* Unmap (part of) 2nd RAM chunk */ + lpar_addr = map.r1.base; + len = r->len; + if (r->offset >= map.rm.size) + lpar_addr += r->offset - map.rm.size; + else + len -= map.rm.size - r->offset; + bus_addr = dma_sb_lpar_to_bus(r, lpar_addr); + result = dma_sb_unmap_area(r, bus_addr, len); + BUG_ON(result); + } + + result = dma_sb_region_free(r); + BUG_ON(result); + + return result; +} + +/** + * dma_sb_map_area_linear - Map an area of memory into a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @virt_addr: Starting virtual address of the area to map. + * @len: Length in bytes of the area to map. + * @bus_addr: A pointer to return the starting ioc bus address of the area to + * map. + * + * This routine just returns the corresponding bus address. Actual mapping + * occurs in dma_region_create_linear(). + */ + +static int dma_sb_map_area_linear(struct ps3_dma_region *r, + unsigned long virt_addr, unsigned long len, dma_addr_t *bus_addr, + u64 iopte_flag) +{ + unsigned long phys_addr = is_kernel_addr(virt_addr) ? __pa(virt_addr) + : virt_addr; + *bus_addr = dma_sb_lpar_to_bus(r, ps3_mm_phys_to_lpar(phys_addr)); + return 0; +} + +/** + * dma_unmap_area_linear - Unmap an area of memory from a device dma region. + * @r: Pointer to a struct ps3_dma_region. + * @bus_addr: The starting ioc bus address of the area to unmap. + * @len: Length in bytes of the area to unmap. + * + * This routine does nothing. Unmapping occurs in dma_sb_region_free_linear(). + */ + +static int dma_sb_unmap_area_linear(struct ps3_dma_region *r, + dma_addr_t bus_addr, unsigned long len) +{ + return 0; +}; + +static const struct ps3_dma_region_ops ps3_dma_sb_region_ops = { + .create = dma_sb_region_create, + .free = dma_sb_region_free, + .map = dma_sb_map_area, + .unmap = dma_sb_unmap_area +}; + +static const struct ps3_dma_region_ops ps3_dma_sb_region_linear_ops = { + .create = dma_sb_region_create_linear, + .free = dma_sb_region_free_linear, + .map = dma_sb_map_area_linear, + .unmap = dma_sb_unmap_area_linear +}; + +static const struct ps3_dma_region_ops ps3_dma_ioc0_region_ops = { + .create = dma_ioc0_region_create, + .free = dma_ioc0_region_free, + .map = dma_ioc0_map_area, + .unmap = dma_ioc0_unmap_area +}; + +int ps3_dma_region_init(struct ps3_system_bus_device *dev, + struct ps3_dma_region *r, enum ps3_dma_page_size page_size, + enum ps3_dma_region_type region_type, void *addr, unsigned long len) +{ + unsigned long lpar_addr; + int result; + + lpar_addr = addr ? ps3_mm_phys_to_lpar(__pa(addr)) : 0; + + r->dev = dev; + r->page_size = page_size; + r->region_type = region_type; + r->offset = lpar_addr; + if (r->offset >= map.rm.size) + r->offset -= map.r1.offset; + r->len = len ? len : ALIGN(map.total, 1 << r->page_size); + + dev->core.dma_mask = &r->dma_mask; + + result = dma_set_mask_and_coherent(&dev->core, DMA_BIT_MASK(32)); + + if (result < 0) { + dev_err(&dev->core, "%s:%d: dma_set_mask_and_coherent failed: %d\n", + __func__, __LINE__, result); + return result; + } + + switch (dev->dev_type) { + case PS3_DEVICE_TYPE_SB: + r->region_ops = (USE_DYNAMIC_DMA) + ? &ps3_dma_sb_region_ops + : &ps3_dma_sb_region_linear_ops; + break; + case PS3_DEVICE_TYPE_IOC0: + r->region_ops = &ps3_dma_ioc0_region_ops; + break; + default: + BUG(); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL(ps3_dma_region_init); + +int ps3_dma_region_create(struct ps3_dma_region *r) +{ + BUG_ON(!r); + BUG_ON(!r->region_ops); + BUG_ON(!r->region_ops->create); + return r->region_ops->create(r); +} +EXPORT_SYMBOL(ps3_dma_region_create); + +int ps3_dma_region_free(struct ps3_dma_region *r) +{ + BUG_ON(!r); + BUG_ON(!r->region_ops); + BUG_ON(!r->region_ops->free); + return r->region_ops->free(r); +} +EXPORT_SYMBOL(ps3_dma_region_free); + +int ps3_dma_map(struct ps3_dma_region *r, unsigned long virt_addr, + unsigned long len, dma_addr_t *bus_addr, + u64 iopte_flag) +{ + return r->region_ops->map(r, virt_addr, len, bus_addr, iopte_flag); +} + +int ps3_dma_unmap(struct ps3_dma_region *r, dma_addr_t bus_addr, + unsigned long len) +{ + return r->region_ops->unmap(r, bus_addr, len); +} + +/*============================================================================*/ +/* system startup routines */ +/*============================================================================*/ + +/** + * ps3_mm_init - initialize the address space state variables + */ + +void __init ps3_mm_init(void) +{ + int result; + + DBG(" -> %s:%d\n", __func__, __LINE__); + + result = ps3_repository_read_mm_info(&map.rm.base, &map.rm.size, + &map.total); + + if (result) + panic("ps3_repository_read_mm_info() failed"); + + map.rm.offset = map.rm.base; + map.vas_id = map.htab_size = 0; + + /* this implementation assumes map.rm.base is zero */ + + BUG_ON(map.rm.base); + BUG_ON(!map.rm.size); + + /* Check if we got the highmem region from an earlier boot step */ + + if (ps3_mm_get_repository_highmem(&map.r1)) { + result = ps3_mm_region_create(&map.r1, map.total - map.rm.size); + + if (!result) + ps3_mm_set_repository_highmem(&map.r1); + } + + /* correct map.total for the real total amount of memory we use */ + map.total = map.rm.size + map.r1.size; + + if (!map.r1.size) { + DBG("%s:%d: No highmem region found\n", __func__, __LINE__); + } else { + DBG("%s:%d: Adding highmem region: %llxh %llxh\n", + __func__, __LINE__, map.rm.size, + map.total - map.rm.size); + memblock_add(map.rm.size, map.total - map.rm.size); + } + + DBG(" <- %s:%d\n", __func__, __LINE__); +} + +/** + * ps3_mm_shutdown - final cleanup of address space + */ + +void ps3_mm_shutdown(void) +{ + ps3_mm_region_destroy(&map.r1); +} diff --git a/arch/powerpc/platforms/ps3/os-area.c b/arch/powerpc/platforms/ps3/os-area.c new file mode 100644 index 000000000..e8530371a --- /dev/null +++ b/arch/powerpc/platforms/ps3/os-area.c @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 flash memory os area. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/workqueue.h> +#include <linux/fs.h> +#include <linux/syscalls.h> +#include <linux/export.h> +#include <linux/ctype.h> +#include <linux/memblock.h> +#include <linux/of.h> +#include <linux/slab.h> + +#include <asm/prom.h> + +#include "platform.h" + +enum { + OS_AREA_SEGMENT_SIZE = 0X200, +}; + +enum os_area_ldr_format { + HEADER_LDR_FORMAT_RAW = 0, + HEADER_LDR_FORMAT_GZIP = 1, +}; + +#define OS_AREA_HEADER_MAGIC_NUM "cell_ext_os_area" + +/** + * struct os_area_header - os area header segment. + * @magic_num: Always 'cell_ext_os_area'. + * @hdr_version: Header format version number. + * @db_area_offset: Starting segment number of other os database area. + * @ldr_area_offset: Starting segment number of bootloader image area. + * @ldr_format: HEADER_LDR_FORMAT flag. + * @ldr_size: Size of bootloader image in bytes. + * + * Note that the docs refer to area offsets. These are offsets in units of + * segments from the start of the os area (top of the header). These are + * better thought of as segment numbers. The os area of the os area is + * reserved for the os image. + */ + +struct os_area_header { + u8 magic_num[16]; + u32 hdr_version; + u32 db_area_offset; + u32 ldr_area_offset; + u32 _reserved_1; + u32 ldr_format; + u32 ldr_size; + u32 _reserved_2[6]; +}; + +enum os_area_boot_flag { + PARAM_BOOT_FLAG_GAME_OS = 0, + PARAM_BOOT_FLAG_OTHER_OS = 1, +}; + +enum os_area_ctrl_button { + PARAM_CTRL_BUTTON_O_IS_YES = 0, + PARAM_CTRL_BUTTON_X_IS_YES = 1, +}; + +/** + * struct os_area_params - os area params segment. + * @boot_flag: User preference of operating system, PARAM_BOOT_FLAG flag. + * @num_params: Number of params in this (params) segment. + * @rtc_diff: Difference in seconds between 1970 and the ps3 rtc value. + * @av_multi_out: User preference of AV output, PARAM_AV_MULTI_OUT flag. + * @ctrl_button: User preference of controller button config, PARAM_CTRL_BUTTON + * flag. + * @static_ip_addr: User preference of static IP address. + * @network_mask: User preference of static network mask. + * @default_gateway: User preference of static default gateway. + * @dns_primary: User preference of static primary dns server. + * @dns_secondary: User preference of static secondary dns server. + * + * The ps3 rtc maintains a read-only value that approximates seconds since + * 2000-01-01 00:00:00 UTC. + * + * User preference of zero for static_ip_addr means use dhcp. + */ + +struct os_area_params { + u32 boot_flag; + u32 _reserved_1[3]; + u32 num_params; + u32 _reserved_2[3]; + /* param 0 */ + s64 rtc_diff; + u8 av_multi_out; + u8 ctrl_button; + u8 _reserved_3[6]; + /* param 1 */ + u8 static_ip_addr[4]; + u8 network_mask[4]; + u8 default_gateway[4]; + u8 _reserved_4[4]; + /* param 2 */ + u8 dns_primary[4]; + u8 dns_secondary[4]; + u8 _reserved_5[8]; +}; + +#define OS_AREA_DB_MAGIC_NUM "-db-" + +/** + * struct os_area_db - Shared flash memory database. + * @magic_num: Always '-db-'. + * @version: os_area_db format version number. + * @index_64: byte offset of the database id index for 64 bit variables. + * @count_64: number of usable 64 bit index entries + * @index_32: byte offset of the database id index for 32 bit variables. + * @count_32: number of usable 32 bit index entries + * @index_16: byte offset of the database id index for 16 bit variables. + * @count_16: number of usable 16 bit index entries + * + * Flash rom storage for exclusive use by guests running in the other os lpar. + * The current system configuration allocates 1K (two segments) for other os + * use. + */ + +struct os_area_db { + u8 magic_num[4]; + u16 version; + u16 _reserved_1; + u16 index_64; + u16 count_64; + u16 index_32; + u16 count_32; + u16 index_16; + u16 count_16; + u32 _reserved_2; + u8 _db_data[1000]; +}; + +/** + * enum os_area_db_owner - Data owners. + */ + +enum os_area_db_owner { + OS_AREA_DB_OWNER_ANY = -1, + OS_AREA_DB_OWNER_NONE = 0, + OS_AREA_DB_OWNER_PROTOTYPE = 1, + OS_AREA_DB_OWNER_LINUX = 2, + OS_AREA_DB_OWNER_PETITBOOT = 3, + OS_AREA_DB_OWNER_MAX = 32, +}; + +enum os_area_db_key { + OS_AREA_DB_KEY_ANY = -1, + OS_AREA_DB_KEY_NONE = 0, + OS_AREA_DB_KEY_RTC_DIFF = 1, + OS_AREA_DB_KEY_VIDEO_MODE = 2, + OS_AREA_DB_KEY_MAX = 8, +}; + +struct os_area_db_id { + int owner; + int key; +}; + +static const struct os_area_db_id os_area_db_id_empty = { + .owner = OS_AREA_DB_OWNER_NONE, + .key = OS_AREA_DB_KEY_NONE +}; + +static const struct os_area_db_id os_area_db_id_any = { + .owner = OS_AREA_DB_OWNER_ANY, + .key = OS_AREA_DB_KEY_ANY +}; + +static const struct os_area_db_id os_area_db_id_rtc_diff = { + .owner = OS_AREA_DB_OWNER_LINUX, + .key = OS_AREA_DB_KEY_RTC_DIFF +}; + +#define SECONDS_FROM_1970_TO_2000 946684800LL + +/** + * struct saved_params - Static working copies of data from the PS3 'os area'. + * + * The order of preference we use for the rtc_diff source: + * 1) The database value. + * 2) The game os value. + * 3) The number of seconds from 1970 to 2000. + */ + +static struct saved_params { + unsigned int valid; + s64 rtc_diff; + unsigned int av_multi_out; +} saved_params; + +static struct property property_rtc_diff = { + .name = "linux,rtc_diff", + .length = sizeof(saved_params.rtc_diff), + .value = &saved_params.rtc_diff, +}; + +static struct property property_av_multi_out = { + .name = "linux,av_multi_out", + .length = sizeof(saved_params.av_multi_out), + .value = &saved_params.av_multi_out, +}; + + +static DEFINE_MUTEX(os_area_flash_mutex); + +static const struct ps3_os_area_flash_ops *os_area_flash_ops; + +void ps3_os_area_flash_register(const struct ps3_os_area_flash_ops *ops) +{ + mutex_lock(&os_area_flash_mutex); + os_area_flash_ops = ops; + mutex_unlock(&os_area_flash_mutex); +} +EXPORT_SYMBOL_GPL(ps3_os_area_flash_register); + +static ssize_t os_area_flash_read(void *buf, size_t count, loff_t pos) +{ + ssize_t res = -ENODEV; + + mutex_lock(&os_area_flash_mutex); + if (os_area_flash_ops) + res = os_area_flash_ops->read(buf, count, pos); + mutex_unlock(&os_area_flash_mutex); + + return res; +} + +static ssize_t os_area_flash_write(const void *buf, size_t count, loff_t pos) +{ + ssize_t res = -ENODEV; + + mutex_lock(&os_area_flash_mutex); + if (os_area_flash_ops) + res = os_area_flash_ops->write(buf, count, pos); + mutex_unlock(&os_area_flash_mutex); + + return res; +} + + +/** + * os_area_set_property - Add or overwrite a saved_params value to the device tree. + * + * Overwrites an existing property. + */ + +static void os_area_set_property(struct device_node *node, + struct property *prop) +{ + int result; + struct property *tmp = of_find_property(node, prop->name, NULL); + + if (tmp) { + pr_debug("%s:%d found %s\n", __func__, __LINE__, prop->name); + of_remove_property(node, tmp); + } + + result = of_add_property(node, prop); + + if (result) + pr_debug("%s:%d of_set_property failed\n", __func__, + __LINE__); +} + +/** + * os_area_get_property - Get a saved_params value from the device tree. + * + */ + +static void __init os_area_get_property(struct device_node *node, + struct property *prop) +{ + const struct property *tmp = of_find_property(node, prop->name, NULL); + + if (tmp) { + BUG_ON(prop->length != tmp->length); + memcpy(prop->value, tmp->value, prop->length); + } else + pr_debug("%s:%d not found %s\n", __func__, __LINE__, + prop->name); +} + +static void dump_field(char *s, const u8 *field, int size_of_field) +{ +#if defined(DEBUG) + int i; + + for (i = 0; i < size_of_field; i++) + s[i] = isprint(field[i]) ? field[i] : '.'; + s[i] = 0; +#endif +} + +#define dump_header(_a) _dump_header(_a, __func__, __LINE__) +static void _dump_header(const struct os_area_header *h, const char *func, + int line) +{ + char str[sizeof(h->magic_num) + 1]; + + dump_field(str, h->magic_num, sizeof(h->magic_num)); + pr_debug("%s:%d: h.magic_num: '%s'\n", func, line, + str); + pr_debug("%s:%d: h.hdr_version: %u\n", func, line, + h->hdr_version); + pr_debug("%s:%d: h.db_area_offset: %u\n", func, line, + h->db_area_offset); + pr_debug("%s:%d: h.ldr_area_offset: %u\n", func, line, + h->ldr_area_offset); + pr_debug("%s:%d: h.ldr_format: %u\n", func, line, + h->ldr_format); + pr_debug("%s:%d: h.ldr_size: %xh\n", func, line, + h->ldr_size); +} + +#define dump_params(_a) _dump_params(_a, __func__, __LINE__) +static void _dump_params(const struct os_area_params *p, const char *func, + int line) +{ + pr_debug("%s:%d: p.boot_flag: %u\n", func, line, p->boot_flag); + pr_debug("%s:%d: p.num_params: %u\n", func, line, p->num_params); + pr_debug("%s:%d: p.rtc_diff %lld\n", func, line, p->rtc_diff); + pr_debug("%s:%d: p.av_multi_out %u\n", func, line, p->av_multi_out); + pr_debug("%s:%d: p.ctrl_button: %u\n", func, line, p->ctrl_button); + pr_debug("%s:%d: p.static_ip_addr: %u.%u.%u.%u\n", func, line, + p->static_ip_addr[0], p->static_ip_addr[1], + p->static_ip_addr[2], p->static_ip_addr[3]); + pr_debug("%s:%d: p.network_mask: %u.%u.%u.%u\n", func, line, + p->network_mask[0], p->network_mask[1], + p->network_mask[2], p->network_mask[3]); + pr_debug("%s:%d: p.default_gateway: %u.%u.%u.%u\n", func, line, + p->default_gateway[0], p->default_gateway[1], + p->default_gateway[2], p->default_gateway[3]); + pr_debug("%s:%d: p.dns_primary: %u.%u.%u.%u\n", func, line, + p->dns_primary[0], p->dns_primary[1], + p->dns_primary[2], p->dns_primary[3]); + pr_debug("%s:%d: p.dns_secondary: %u.%u.%u.%u\n", func, line, + p->dns_secondary[0], p->dns_secondary[1], + p->dns_secondary[2], p->dns_secondary[3]); +} + +static int verify_header(const struct os_area_header *header) +{ + if (memcmp(header->magic_num, OS_AREA_HEADER_MAGIC_NUM, + sizeof(header->magic_num))) { + pr_debug("%s:%d magic_num failed\n", __func__, __LINE__); + return -1; + } + + if (header->hdr_version < 1) { + pr_debug("%s:%d hdr_version failed\n", __func__, __LINE__); + return -1; + } + + if (header->db_area_offset > header->ldr_area_offset) { + pr_debug("%s:%d offsets failed\n", __func__, __LINE__); + return -1; + } + + return 0; +} + +static int db_verify(const struct os_area_db *db) +{ + if (memcmp(db->magic_num, OS_AREA_DB_MAGIC_NUM, + sizeof(db->magic_num))) { + pr_debug("%s:%d magic_num failed\n", __func__, __LINE__); + return -EINVAL; + } + + if (db->version != 1) { + pr_debug("%s:%d version failed\n", __func__, __LINE__); + return -EINVAL; + } + + return 0; +} + +struct db_index { + uint8_t owner:5; + uint8_t key:3; +}; + +struct db_iterator { + const struct os_area_db *db; + struct os_area_db_id match_id; + struct db_index *idx; + struct db_index *last_idx; + union { + uint64_t *value_64; + uint32_t *value_32; + uint16_t *value_16; + }; +}; + +static unsigned int db_align_up(unsigned int val, unsigned int size) +{ + return (val + (size - 1)) & (~(size - 1)); +} + +/** + * db_for_each_64 - Iterator for 64 bit entries. + * + * A NULL value for id can be used to match all entries. + * OS_AREA_DB_OWNER_ANY and OS_AREA_DB_KEY_ANY can be used to match all. + */ + +static int db_for_each_64(const struct os_area_db *db, + const struct os_area_db_id *match_id, struct db_iterator *i) +{ +next: + if (!i->db) { + i->db = db; + i->match_id = match_id ? *match_id : os_area_db_id_any; + i->idx = (void *)db + db->index_64; + i->last_idx = i->idx + db->count_64; + i->value_64 = (void *)db + db->index_64 + + db_align_up(db->count_64, 8); + } else { + i->idx++; + i->value_64++; + } + + if (i->idx >= i->last_idx) { + pr_debug("%s:%d: reached end\n", __func__, __LINE__); + return 0; + } + + if (i->match_id.owner != OS_AREA_DB_OWNER_ANY + && i->match_id.owner != (int)i->idx->owner) + goto next; + if (i->match_id.key != OS_AREA_DB_KEY_ANY + && i->match_id.key != (int)i->idx->key) + goto next; + + return 1; +} + +static int db_delete_64(struct os_area_db *db, const struct os_area_db_id *id) +{ + struct db_iterator i; + + for (i.db = NULL; db_for_each_64(db, id, &i); ) { + + pr_debug("%s:%d: got (%d:%d) %llxh\n", __func__, __LINE__, + i.idx->owner, i.idx->key, + (unsigned long long)*i.value_64); + + i.idx->owner = 0; + i.idx->key = 0; + *i.value_64 = 0; + } + return 0; +} + +static int db_set_64(struct os_area_db *db, const struct os_area_db_id *id, + uint64_t value) +{ + struct db_iterator i; + + pr_debug("%s:%d: (%d:%d) <= %llxh\n", __func__, __LINE__, + id->owner, id->key, (unsigned long long)value); + + if (!id->owner || id->owner == OS_AREA_DB_OWNER_ANY + || id->key == OS_AREA_DB_KEY_ANY) { + pr_debug("%s:%d: bad id: (%d:%d)\n", __func__, + __LINE__, id->owner, id->key); + return -1; + } + + db_delete_64(db, id); + + i.db = NULL; + if (db_for_each_64(db, &os_area_db_id_empty, &i)) { + + pr_debug("%s:%d: got (%d:%d) %llxh\n", __func__, __LINE__, + i.idx->owner, i.idx->key, + (unsigned long long)*i.value_64); + + i.idx->owner = id->owner; + i.idx->key = id->key; + *i.value_64 = value; + + pr_debug("%s:%d: set (%d:%d) <= %llxh\n", __func__, __LINE__, + i.idx->owner, i.idx->key, + (unsigned long long)*i.value_64); + return 0; + } + pr_debug("%s:%d: database full.\n", + __func__, __LINE__); + return -1; +} + +static int db_get_64(const struct os_area_db *db, + const struct os_area_db_id *id, uint64_t *value) +{ + struct db_iterator i; + + i.db = NULL; + if (db_for_each_64(db, id, &i)) { + *value = *i.value_64; + pr_debug("%s:%d: found %lld\n", __func__, __LINE__, + (long long int)*i.value_64); + return 0; + } + pr_debug("%s:%d: not found\n", __func__, __LINE__); + return -1; +} + +static int db_get_rtc_diff(const struct os_area_db *db, int64_t *rtc_diff) +{ + return db_get_64(db, &os_area_db_id_rtc_diff, (uint64_t*)rtc_diff); +} + +#define dump_db(a) _dump_db(a, __func__, __LINE__) +static void _dump_db(const struct os_area_db *db, const char *func, + int line) +{ + char str[sizeof(db->magic_num) + 1]; + + dump_field(str, db->magic_num, sizeof(db->magic_num)); + pr_debug("%s:%d: db.magic_num: '%s'\n", func, line, + str); + pr_debug("%s:%d: db.version: %u\n", func, line, + db->version); + pr_debug("%s:%d: db.index_64: %u\n", func, line, + db->index_64); + pr_debug("%s:%d: db.count_64: %u\n", func, line, + db->count_64); + pr_debug("%s:%d: db.index_32: %u\n", func, line, + db->index_32); + pr_debug("%s:%d: db.count_32: %u\n", func, line, + db->count_32); + pr_debug("%s:%d: db.index_16: %u\n", func, line, + db->index_16); + pr_debug("%s:%d: db.count_16: %u\n", func, line, + db->count_16); +} + +static void os_area_db_init(struct os_area_db *db) +{ + enum { + HEADER_SIZE = offsetof(struct os_area_db, _db_data), + INDEX_64_COUNT = 64, + VALUES_64_COUNT = 57, + INDEX_32_COUNT = 64, + VALUES_32_COUNT = 57, + INDEX_16_COUNT = 64, + VALUES_16_COUNT = 57, + }; + + memset(db, 0, sizeof(struct os_area_db)); + + memcpy(db->magic_num, OS_AREA_DB_MAGIC_NUM, sizeof(db->magic_num)); + db->version = 1; + db->index_64 = HEADER_SIZE; + db->count_64 = VALUES_64_COUNT; + db->index_32 = HEADER_SIZE + + INDEX_64_COUNT * sizeof(struct db_index) + + VALUES_64_COUNT * sizeof(u64); + db->count_32 = VALUES_32_COUNT; + db->index_16 = HEADER_SIZE + + INDEX_64_COUNT * sizeof(struct db_index) + + VALUES_64_COUNT * sizeof(u64) + + INDEX_32_COUNT * sizeof(struct db_index) + + VALUES_32_COUNT * sizeof(u32); + db->count_16 = VALUES_16_COUNT; + + /* Rules to check db layout. */ + + BUILD_BUG_ON(sizeof(struct db_index) != 1); + BUILD_BUG_ON(sizeof(struct os_area_db) != 2 * OS_AREA_SEGMENT_SIZE); + BUILD_BUG_ON(INDEX_64_COUNT & 0x7); + BUILD_BUG_ON(VALUES_64_COUNT > INDEX_64_COUNT); + BUILD_BUG_ON(INDEX_32_COUNT & 0x7); + BUILD_BUG_ON(VALUES_32_COUNT > INDEX_32_COUNT); + BUILD_BUG_ON(INDEX_16_COUNT & 0x7); + BUILD_BUG_ON(VALUES_16_COUNT > INDEX_16_COUNT); + BUILD_BUG_ON(HEADER_SIZE + + INDEX_64_COUNT * sizeof(struct db_index) + + VALUES_64_COUNT * sizeof(u64) + + INDEX_32_COUNT * sizeof(struct db_index) + + VALUES_32_COUNT * sizeof(u32) + + INDEX_16_COUNT * sizeof(struct db_index) + + VALUES_16_COUNT * sizeof(u16) + > sizeof(struct os_area_db)); +} + +/** + * update_flash_db - Helper for os_area_queue_work_handler. + * + */ + +static int update_flash_db(void) +{ + const unsigned int buf_len = 8 * OS_AREA_SEGMENT_SIZE; + struct os_area_header *header; + ssize_t count; + int error; + loff_t pos; + struct os_area_db* db; + + /* Read in header and db from flash. */ + + header = kmalloc(buf_len, GFP_KERNEL); + if (!header) + return -ENOMEM; + + count = os_area_flash_read(header, buf_len, 0); + if (count < 0) { + pr_debug("%s: os_area_flash_read failed %zd\n", __func__, + count); + error = count; + goto fail; + } + + pos = header->db_area_offset * OS_AREA_SEGMENT_SIZE; + if (count < OS_AREA_SEGMENT_SIZE || verify_header(header) || + count < pos) { + pr_debug("%s: verify_header failed\n", __func__); + dump_header(header); + error = -EINVAL; + goto fail; + } + + /* Now got a good db offset and some maybe good db data. */ + + db = (void *)header + pos; + + error = db_verify(db); + if (error) { + pr_notice("%s: Verify of flash database failed, formatting.\n", + __func__); + dump_db(db); + os_area_db_init(db); + } + + /* Now got good db data. */ + + db_set_64(db, &os_area_db_id_rtc_diff, saved_params.rtc_diff); + + count = os_area_flash_write(db, sizeof(struct os_area_db), pos); + if (count < 0 || count < sizeof(struct os_area_db)) { + pr_debug("%s: os_area_flash_write failed %zd\n", __func__, + count); + error = count < 0 ? count : -EIO; + } + +fail: + kfree(header); + return error; +} + +/** + * os_area_queue_work_handler - Asynchronous write handler. + * + * An asynchronous write for flash memory and the device tree. Do not + * call directly, use os_area_queue_work(). + */ + +static void os_area_queue_work_handler(struct work_struct *work) +{ + struct device_node *node; + int error; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + node = of_find_node_by_path("/"); + if (node) { + os_area_set_property(node, &property_rtc_diff); + of_node_put(node); + } else + pr_debug("%s:%d of_find_node_by_path failed\n", + __func__, __LINE__); + + error = update_flash_db(); + if (error) + pr_warn("%s: Could not update FLASH ROM\n", __func__); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); +} + +static void os_area_queue_work(void) +{ + static DECLARE_WORK(q, os_area_queue_work_handler); + + wmb(); + schedule_work(&q); +} + +/** + * ps3_os_area_save_params - Copy data from os area mirror to @saved_params. + * + * For the convenience of the guest the HV makes a copy of the os area in + * flash to a high address in the boot memory region and then puts that RAM + * address and the byte count into the repository for retrieval by the guest. + * We copy the data we want into a static variable and allow the memory setup + * by the HV to be claimed by the memblock manager. + * + * The os area mirror will not be available to a second stage kernel, and + * the header verify will fail. In this case, the saved_params values will + * be set from flash memory or the passed in device tree in ps3_os_area_init(). + */ + +void __init ps3_os_area_save_params(void) +{ + int result; + u64 lpar_addr; + unsigned int size; + struct os_area_header *header; + struct os_area_params *params; + struct os_area_db *db; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + result = ps3_repository_read_boot_dat_info(&lpar_addr, &size); + + if (result) { + pr_debug("%s:%d ps3_repository_read_boot_dat_info failed\n", + __func__, __LINE__); + return; + } + + header = (struct os_area_header *)__va(lpar_addr); + params = (struct os_area_params *)__va(lpar_addr + + OS_AREA_SEGMENT_SIZE); + + result = verify_header(header); + + if (result) { + /* Second stage kernels exit here. */ + pr_debug("%s:%d verify_header failed\n", __func__, __LINE__); + dump_header(header); + return; + } + + db = (struct os_area_db *)__va(lpar_addr + + header->db_area_offset * OS_AREA_SEGMENT_SIZE); + + dump_header(header); + dump_params(params); + dump_db(db); + + result = db_verify(db) || db_get_rtc_diff(db, &saved_params.rtc_diff); + if (result) + saved_params.rtc_diff = params->rtc_diff ? params->rtc_diff + : SECONDS_FROM_1970_TO_2000; + saved_params.av_multi_out = params->av_multi_out; + saved_params.valid = 1; + + memset(header, 0, sizeof(*header)); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); +} + +/** + * ps3_os_area_init - Setup os area device tree properties as needed. + */ + +void __init ps3_os_area_init(void) +{ + struct device_node *node; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + node = of_find_node_by_path("/"); + + if (!saved_params.valid && node) { + /* Second stage kernels should have a dt entry. */ + os_area_get_property(node, &property_rtc_diff); + os_area_get_property(node, &property_av_multi_out); + } + + if(!saved_params.rtc_diff) + saved_params.rtc_diff = SECONDS_FROM_1970_TO_2000; + + if (node) { + os_area_set_property(node, &property_rtc_diff); + os_area_set_property(node, &property_av_multi_out); + of_node_put(node); + } else + pr_debug("%s:%d of_find_node_by_path failed\n", + __func__, __LINE__); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); +} + +/** + * ps3_os_area_get_rtc_diff - Returns the rtc diff value. + */ + +u64 ps3_os_area_get_rtc_diff(void) +{ + return saved_params.rtc_diff; +} +EXPORT_SYMBOL_GPL(ps3_os_area_get_rtc_diff); + +/** + * ps3_os_area_set_rtc_diff - Set the rtc diff value. + * + * An asynchronous write is needed to support writing updates from + * the timer interrupt context. + */ + +void ps3_os_area_set_rtc_diff(u64 rtc_diff) +{ + if (saved_params.rtc_diff != rtc_diff) { + saved_params.rtc_diff = rtc_diff; + os_area_queue_work(); + } +} +EXPORT_SYMBOL_GPL(ps3_os_area_set_rtc_diff); + +/** + * ps3_os_area_get_av_multi_out - Returns the default video mode. + */ + +enum ps3_param_av_multi_out ps3_os_area_get_av_multi_out(void) +{ + return saved_params.av_multi_out; +} +EXPORT_SYMBOL_GPL(ps3_os_area_get_av_multi_out); diff --git a/arch/powerpc/platforms/ps3/platform.h b/arch/powerpc/platforms/ps3/platform.h new file mode 100644 index 000000000..07bd39ef7 --- /dev/null +++ b/arch/powerpc/platforms/ps3/platform.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * PS3 platform declarations. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#if !defined(_PS3_PLATFORM_H) +#define _PS3_PLATFORM_H + +#include <linux/rtc.h> +#include <scsi/scsi.h> + +#include <asm/ps3.h> + +/* htab */ + +void __init ps3_hpte_init(unsigned long htab_size); +void __init ps3_map_htab(void); + +/* mm */ + +void __init ps3_mm_init(void); +void __init ps3_mm_vas_create(unsigned long* htab_size); +void ps3_mm_vas_destroy(void); +void ps3_mm_shutdown(void); + +/* irq */ + +void ps3_init_IRQ(void); +void ps3_shutdown_IRQ(int cpu); +void __init ps3_register_ipi_debug_brk(unsigned int cpu, unsigned int virq); +void __init ps3_register_ipi_irq(unsigned int cpu, unsigned int virq); + +/* smp */ + +void smp_init_ps3(void); +#ifdef CONFIG_SMP +void ps3_smp_cleanup_cpu(int cpu); +#else +static inline void ps3_smp_cleanup_cpu(int cpu) { } +#endif + +/* time */ + +void __init ps3_calibrate_decr(void); +time64_t __init ps3_get_boot_time(void); +void ps3_get_rtc_time(struct rtc_time *time); +int ps3_set_rtc_time(struct rtc_time *time); + +/* os area */ + +void __init ps3_os_area_save_params(void); +void __init ps3_os_area_init(void); + +/* spu */ + +#if defined(CONFIG_SPU_BASE) +void ps3_spu_set_platform (void); +#else +static inline void ps3_spu_set_platform (void) {} +#endif + +/* repository bus info */ + +enum ps3_bus_type { + PS3_BUS_TYPE_SB = 4, + PS3_BUS_TYPE_STORAGE = 5, +}; + +enum ps3_dev_type { + PS3_DEV_TYPE_STOR_DISK = TYPE_DISK, /* 0 */ + PS3_DEV_TYPE_SB_GELIC = 3, + PS3_DEV_TYPE_SB_USB = 4, + PS3_DEV_TYPE_STOR_ROM = TYPE_ROM, /* 5 */ + PS3_DEV_TYPE_SB_GPIO = 6, + PS3_DEV_TYPE_STOR_FLASH = TYPE_RBC, /* 14 */ +}; + +int ps3_repository_read_bus_str(unsigned int bus_index, const char *bus_str, + u64 *value); +int ps3_repository_read_bus_id(unsigned int bus_index, u64 *bus_id); +int ps3_repository_read_bus_type(unsigned int bus_index, + enum ps3_bus_type *bus_type); +int ps3_repository_read_bus_num_dev(unsigned int bus_index, + unsigned int *num_dev); + +/* repository bus device info */ + +enum ps3_interrupt_type { + PS3_INTERRUPT_TYPE_EVENT_PORT = 2, + PS3_INTERRUPT_TYPE_SB_OHCI = 3, + PS3_INTERRUPT_TYPE_SB_EHCI = 4, + PS3_INTERRUPT_TYPE_OTHER = 5, +}; + +enum ps3_reg_type { + PS3_REG_TYPE_SB_OHCI = 3, + PS3_REG_TYPE_SB_EHCI = 4, + PS3_REG_TYPE_SB_GPIO = 5, +}; + +int ps3_repository_read_dev_str(unsigned int bus_index, + unsigned int dev_index, const char *dev_str, u64 *value); +int ps3_repository_read_dev_id(unsigned int bus_index, unsigned int dev_index, + u64 *dev_id); +int ps3_repository_read_dev_type(unsigned int bus_index, + unsigned int dev_index, enum ps3_dev_type *dev_type); +int ps3_repository_read_dev_intr(unsigned int bus_index, + unsigned int dev_index, unsigned int intr_index, + enum ps3_interrupt_type *intr_type, unsigned int *interrupt_id); +int ps3_repository_read_dev_reg_type(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, + enum ps3_reg_type *reg_type); +int ps3_repository_read_dev_reg_addr(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, u64 *bus_addr, + u64 *len); +int ps3_repository_read_dev_reg(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, + enum ps3_reg_type *reg_type, u64 *bus_addr, u64 *len); + +/* repository bus enumerators */ + +struct ps3_repository_device { + unsigned int bus_index; + unsigned int dev_index; + enum ps3_bus_type bus_type; + enum ps3_dev_type dev_type; + u64 bus_id; + u64 dev_id; +}; + +int ps3_repository_find_device(struct ps3_repository_device *repo); +int ps3_repository_find_device_by_id(struct ps3_repository_device *repo, + u64 bus_id, u64 dev_id); +int ps3_repository_find_devices(enum ps3_bus_type bus_type, + int (*callback)(const struct ps3_repository_device *repo)); +int ps3_repository_find_bus(enum ps3_bus_type bus_type, unsigned int from, + unsigned int *bus_index); +int ps3_repository_find_interrupt(const struct ps3_repository_device *repo, + enum ps3_interrupt_type intr_type, unsigned int *interrupt_id); +int ps3_repository_find_reg(const struct ps3_repository_device *repo, + enum ps3_reg_type reg_type, u64 *bus_addr, u64 *len); + +/* repository block device info */ + +int ps3_repository_read_stor_dev_port(unsigned int bus_index, + unsigned int dev_index, u64 *port); +int ps3_repository_read_stor_dev_blk_size(unsigned int bus_index, + unsigned int dev_index, u64 *blk_size); +int ps3_repository_read_stor_dev_num_blocks(unsigned int bus_index, + unsigned int dev_index, u64 *num_blocks); +int ps3_repository_read_stor_dev_num_regions(unsigned int bus_index, + unsigned int dev_index, unsigned int *num_regions); +int ps3_repository_read_stor_dev_region_id(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, + unsigned int *region_id); +int ps3_repository_read_stor_dev_region_size(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, u64 *region_size); +int ps3_repository_read_stor_dev_region_start(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, u64 *region_start); +int ps3_repository_read_stor_dev_info(unsigned int bus_index, + unsigned int dev_index, u64 *port, u64 *blk_size, + u64 *num_blocks, unsigned int *num_regions); +int ps3_repository_read_stor_dev_region(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, + unsigned int *region_id, u64 *region_start, u64 *region_size); + +/* repository logical pu and memory info */ + +int ps3_repository_read_num_pu(u64 *num_pu); +int ps3_repository_read_pu_id(unsigned int pu_index, u64 *pu_id); +int ps3_repository_read_rm_base(unsigned int ppe_id, u64 *rm_base); +int ps3_repository_read_rm_size(unsigned int ppe_id, u64 *rm_size); +int ps3_repository_read_region_total(u64 *region_total); +int ps3_repository_read_mm_info(u64 *rm_base, u64 *rm_size, + u64 *region_total); +int ps3_repository_read_highmem_region_count(unsigned int *region_count); +int ps3_repository_read_highmem_base(unsigned int region_index, + u64 *highmem_base); +int ps3_repository_read_highmem_size(unsigned int region_index, + u64 *highmem_size); +int ps3_repository_read_highmem_info(unsigned int region_index, + u64 *highmem_base, u64 *highmem_size); + +#if defined (CONFIG_PS3_REPOSITORY_WRITE) +int ps3_repository_write_highmem_region_count(unsigned int region_count); +int ps3_repository_write_highmem_base(unsigned int region_index, + u64 highmem_base); +int ps3_repository_write_highmem_size(unsigned int region_index, + u64 highmem_size); +int ps3_repository_write_highmem_info(unsigned int region_index, + u64 highmem_base, u64 highmem_size); +int ps3_repository_delete_highmem_info(unsigned int region_index); +#else +static inline int ps3_repository_write_highmem_region_count( + unsigned int region_count) {return 0;} +static inline int ps3_repository_write_highmem_base(unsigned int region_index, + u64 highmem_base) {return 0;} +static inline int ps3_repository_write_highmem_size(unsigned int region_index, + u64 highmem_size) {return 0;} +static inline int ps3_repository_write_highmem_info(unsigned int region_index, + u64 highmem_base, u64 highmem_size) {return 0;} +static inline int ps3_repository_delete_highmem_info(unsigned int region_index) + {return 0;} +#endif + +/* repository pme info */ + +int ps3_repository_read_num_be(unsigned int *num_be); +int ps3_repository_read_be_node_id(unsigned int be_index, u64 *node_id); +int ps3_repository_read_be_id(u64 node_id, u64 *be_id); +int ps3_repository_read_tb_freq(u64 node_id, u64 *tb_freq); +int ps3_repository_read_be_tb_freq(unsigned int be_index, u64 *tb_freq); + +/* repository performance monitor info */ + +int ps3_repository_read_lpm_privileges(unsigned int be_index, u64 *lpar, + u64 *rights); + +/* repository 'Other OS' area */ + +int ps3_repository_read_boot_dat_addr(u64 *lpar_addr); +int ps3_repository_read_boot_dat_size(unsigned int *size); +int ps3_repository_read_boot_dat_info(u64 *lpar_addr, unsigned int *size); + +/* repository spu info */ + +/** + * enum spu_resource_type - Type of spu resource. + * @spu_resource_type_shared: Logical spu is shared with other partions. + * @spu_resource_type_exclusive: Logical spu is not shared with other partions. + * + * Returned by ps3_repository_read_spu_resource_id(). + */ + +enum ps3_spu_resource_type { + PS3_SPU_RESOURCE_TYPE_SHARED = 0, + PS3_SPU_RESOURCE_TYPE_EXCLUSIVE = 0x8000000000000000UL, +}; + +int ps3_repository_read_num_spu_reserved(unsigned int *num_spu_reserved); +int ps3_repository_read_num_spu_resource_id(unsigned int *num_resource_id); +int ps3_repository_read_spu_resource_id(unsigned int res_index, + enum ps3_spu_resource_type* resource_type, unsigned int *resource_id); + +/* repository vuart info */ + +int ps3_repository_read_vuart_av_port(unsigned int *port); +int ps3_repository_read_vuart_sysmgr_port(unsigned int *port); + +#endif diff --git a/arch/powerpc/platforms/ps3/repository.c b/arch/powerpc/platforms/ps3/repository.c new file mode 100644 index 000000000..21712964e --- /dev/null +++ b/arch/powerpc/platforms/ps3/repository.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 repository routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <asm/lv1call.h> + +#include "platform.h" + +enum ps3_vendor_id { + PS3_VENDOR_ID_NONE = 0, + PS3_VENDOR_ID_SONY = 0x8000000000000000UL, +}; + +enum ps3_lpar_id { + PS3_LPAR_ID_CURRENT = 0, + PS3_LPAR_ID_PME = 1, +}; + +#define dump_field(_a, _b) _dump_field(_a, _b, __func__, __LINE__) +static void _dump_field(const char *hdr, u64 n, const char *func, int line) +{ +#if defined(DEBUG) + char s[16]; + const char *const in = (const char *)&n; + unsigned int i; + + for (i = 0; i < 8; i++) + s[i] = (in[i] <= 126 && in[i] >= 32) ? in[i] : '.'; + s[i] = 0; + + pr_devel("%s:%d: %s%016llx : %s\n", func, line, hdr, n, s); +#endif +} + +#define dump_node_name(_a, _b, _c, _d, _e) \ + _dump_node_name(_a, _b, _c, _d, _e, __func__, __LINE__) +static void _dump_node_name(unsigned int lpar_id, u64 n1, u64 n2, u64 n3, + u64 n4, const char *func, int line) +{ + pr_devel("%s:%d: lpar: %u\n", func, line, lpar_id); + _dump_field("n1: ", n1, func, line); + _dump_field("n2: ", n2, func, line); + _dump_field("n3: ", n3, func, line); + _dump_field("n4: ", n4, func, line); +} + +#define dump_node(_a, _b, _c, _d, _e, _f, _g) \ + _dump_node(_a, _b, _c, _d, _e, _f, _g, __func__, __LINE__) +static void _dump_node(unsigned int lpar_id, u64 n1, u64 n2, u64 n3, u64 n4, + u64 v1, u64 v2, const char *func, int line) +{ + pr_devel("%s:%d: lpar: %u\n", func, line, lpar_id); + _dump_field("n1: ", n1, func, line); + _dump_field("n2: ", n2, func, line); + _dump_field("n3: ", n3, func, line); + _dump_field("n4: ", n4, func, line); + pr_devel("%s:%d: v1: %016llx\n", func, line, v1); + pr_devel("%s:%d: v2: %016llx\n", func, line, v2); +} + +/** + * make_first_field - Make the first field of a repository node name. + * @text: Text portion of the field. + * @index: Numeric index portion of the field. Use zero for 'don't care'. + * + * This routine sets the vendor id to zero (non-vendor specific). + * Returns field value. + */ + +static u64 make_first_field(const char *text, u64 index) +{ + u64 n; + + strncpy((char *)&n, text, 8); + return PS3_VENDOR_ID_NONE + (n >> 32) + index; +} + +/** + * make_field - Make subsequent fields of a repository node name. + * @text: Text portion of the field. Use "" for 'don't care'. + * @index: Numeric index portion of the field. Use zero for 'don't care'. + * + * Returns field value. + */ + +static u64 make_field(const char *text, u64 index) +{ + u64 n = 0; + + memcpy((char *)&n, text, strnlen(text, sizeof(n))); + return n + index; +} + +/** + * read_node - Read a repository node from raw fields. + * @n1: First field of node name. + * @n2: Second field of node name. Use zero for 'don't care'. + * @n3: Third field of node name. Use zero for 'don't care'. + * @n4: Fourth field of node name. Use zero for 'don't care'. + * @v1: First repository value (high word). + * @v2: Second repository value (low word). Optional parameter, use zero + * for 'don't care'. + */ + +static int read_node(unsigned int lpar_id, u64 n1, u64 n2, u64 n3, u64 n4, + u64 *_v1, u64 *_v2) +{ + int result; + u64 v1; + u64 v2; + + if (lpar_id == PS3_LPAR_ID_CURRENT) { + u64 id; + lv1_get_logical_partition_id(&id); + lpar_id = id; + } + + result = lv1_read_repository_node(lpar_id, n1, n2, n3, n4, &v1, + &v2); + + if (result) { + pr_warn("%s:%d: lv1_read_repository_node failed: %s\n", + __func__, __LINE__, ps3_result(result)); + dump_node_name(lpar_id, n1, n2, n3, n4); + return -ENOENT; + } + + dump_node(lpar_id, n1, n2, n3, n4, v1, v2); + + if (_v1) + *_v1 = v1; + if (_v2) + *_v2 = v2; + + if (v1 && !_v1) + pr_devel("%s:%d: warning: discarding non-zero v1: %016llx\n", + __func__, __LINE__, v1); + if (v2 && !_v2) + pr_devel("%s:%d: warning: discarding non-zero v2: %016llx\n", + __func__, __LINE__, v2); + + return 0; +} + +int ps3_repository_read_bus_str(unsigned int bus_index, const char *bus_str, + u64 *value) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field(bus_str, 0), + 0, 0, + value, NULL); +} + +int ps3_repository_read_bus_id(unsigned int bus_index, u64 *bus_id) +{ + return read_node(PS3_LPAR_ID_PME, make_first_field("bus", bus_index), + make_field("id", 0), 0, 0, bus_id, NULL); +} + +int ps3_repository_read_bus_type(unsigned int bus_index, + enum ps3_bus_type *bus_type) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("type", 0), + 0, 0, + &v1, NULL); + *bus_type = v1; + return result; +} + +int ps3_repository_read_bus_num_dev(unsigned int bus_index, + unsigned int *num_dev) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("num_dev", 0), + 0, 0, + &v1, NULL); + *num_dev = v1; + return result; +} + +int ps3_repository_read_dev_str(unsigned int bus_index, + unsigned int dev_index, const char *dev_str, u64 *value) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field(dev_str, 0), + 0, + value, NULL); +} + +int ps3_repository_read_dev_id(unsigned int bus_index, unsigned int dev_index, + u64 *dev_id) +{ + return read_node(PS3_LPAR_ID_PME, make_first_field("bus", bus_index), + make_field("dev", dev_index), make_field("id", 0), 0, + dev_id, NULL); +} + +int ps3_repository_read_dev_type(unsigned int bus_index, + unsigned int dev_index, enum ps3_dev_type *dev_type) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("type", 0), + 0, + &v1, NULL); + *dev_type = v1; + return result; +} + +int ps3_repository_read_dev_intr(unsigned int bus_index, + unsigned int dev_index, unsigned int intr_index, + enum ps3_interrupt_type *intr_type, unsigned int *interrupt_id) +{ + int result; + u64 v1 = 0; + u64 v2 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("intr", intr_index), + 0, + &v1, &v2); + *intr_type = v1; + *interrupt_id = v2; + return result; +} + +int ps3_repository_read_dev_reg_type(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, + enum ps3_reg_type *reg_type) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("reg", reg_index), + make_field("type", 0), + &v1, NULL); + *reg_type = v1; + return result; +} + +int ps3_repository_read_dev_reg_addr(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, u64 *bus_addr, u64 *len) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("reg", reg_index), + make_field("data", 0), + bus_addr, len); +} + +int ps3_repository_read_dev_reg(unsigned int bus_index, + unsigned int dev_index, unsigned int reg_index, + enum ps3_reg_type *reg_type, u64 *bus_addr, u64 *len) +{ + int result = ps3_repository_read_dev_reg_type(bus_index, dev_index, + reg_index, reg_type); + return result ? result + : ps3_repository_read_dev_reg_addr(bus_index, dev_index, + reg_index, bus_addr, len); +} + + + +int ps3_repository_find_device(struct ps3_repository_device *repo) +{ + int result; + struct ps3_repository_device tmp = *repo; + unsigned int num_dev; + + BUG_ON(repo->bus_index > 10); + BUG_ON(repo->dev_index > 10); + + result = ps3_repository_read_bus_num_dev(tmp.bus_index, &num_dev); + + if (result) { + pr_devel("%s:%d read_bus_num_dev failed\n", __func__, __LINE__); + return result; + } + + pr_devel("%s:%d: bus_type %u, bus_index %u, bus_id %llu, num_dev %u\n", + __func__, __LINE__, tmp.bus_type, tmp.bus_index, tmp.bus_id, + num_dev); + + if (tmp.dev_index >= num_dev) { + pr_devel("%s:%d: no device found\n", __func__, __LINE__); + return -ENODEV; + } + + result = ps3_repository_read_dev_type(tmp.bus_index, tmp.dev_index, + &tmp.dev_type); + + if (result) { + pr_devel("%s:%d read_dev_type failed\n", __func__, __LINE__); + return result; + } + + result = ps3_repository_read_dev_id(tmp.bus_index, tmp.dev_index, + &tmp.dev_id); + + if (result) { + pr_devel("%s:%d ps3_repository_read_dev_id failed\n", __func__, + __LINE__); + return result; + } + + pr_devel("%s:%d: found: dev_type %u, dev_index %u, dev_id %llu\n", + __func__, __LINE__, tmp.dev_type, tmp.dev_index, tmp.dev_id); + + *repo = tmp; + return 0; +} + +int ps3_repository_find_device_by_id(struct ps3_repository_device *repo, + u64 bus_id, u64 dev_id) +{ + int result = -ENODEV; + struct ps3_repository_device tmp; + unsigned int num_dev; + + pr_devel(" -> %s:%u: find device by id %llu:%llu\n", __func__, __LINE__, + bus_id, dev_id); + + for (tmp.bus_index = 0; tmp.bus_index < 10; tmp.bus_index++) { + result = ps3_repository_read_bus_id(tmp.bus_index, + &tmp.bus_id); + if (result) { + pr_devel("%s:%u read_bus_id(%u) failed\n", __func__, + __LINE__, tmp.bus_index); + return result; + } + + if (tmp.bus_id == bus_id) + goto found_bus; + + pr_devel("%s:%u: skip, bus_id %llu\n", __func__, __LINE__, + tmp.bus_id); + } + pr_devel(" <- %s:%u: bus not found\n", __func__, __LINE__); + return result; + +found_bus: + result = ps3_repository_read_bus_type(tmp.bus_index, &tmp.bus_type); + if (result) { + pr_devel("%s:%u read_bus_type(%u) failed\n", __func__, + __LINE__, tmp.bus_index); + return result; + } + + result = ps3_repository_read_bus_num_dev(tmp.bus_index, &num_dev); + if (result) { + pr_devel("%s:%u read_bus_num_dev failed\n", __func__, + __LINE__); + return result; + } + + for (tmp.dev_index = 0; tmp.dev_index < num_dev; tmp.dev_index++) { + result = ps3_repository_read_dev_id(tmp.bus_index, + tmp.dev_index, + &tmp.dev_id); + if (result) { + pr_devel("%s:%u read_dev_id(%u:%u) failed\n", __func__, + __LINE__, tmp.bus_index, tmp.dev_index); + return result; + } + + if (tmp.dev_id == dev_id) + goto found_dev; + + pr_devel("%s:%u: skip, dev_id %llu\n", __func__, __LINE__, + tmp.dev_id); + } + pr_devel(" <- %s:%u: dev not found\n", __func__, __LINE__); + return result; + +found_dev: + result = ps3_repository_read_dev_type(tmp.bus_index, tmp.dev_index, + &tmp.dev_type); + if (result) { + pr_devel("%s:%u read_dev_type failed\n", __func__, __LINE__); + return result; + } + + pr_devel(" <- %s:%u: found: type (%u:%u) index (%u:%u) id (%llu:%llu)\n", + __func__, __LINE__, tmp.bus_type, tmp.dev_type, tmp.bus_index, + tmp.dev_index, tmp.bus_id, tmp.dev_id); + *repo = tmp; + return 0; +} + +int ps3_repository_find_devices(enum ps3_bus_type bus_type, + int (*callback)(const struct ps3_repository_device *repo)) +{ + int result = 0; + struct ps3_repository_device repo; + + pr_devel(" -> %s:%d: find bus_type %u\n", __func__, __LINE__, bus_type); + + repo.bus_type = bus_type; + result = ps3_repository_find_bus(repo.bus_type, 0, &repo.bus_index); + if (result) { + pr_devel(" <- %s:%u: bus not found\n", __func__, __LINE__); + return result; + } + + result = ps3_repository_read_bus_id(repo.bus_index, &repo.bus_id); + if (result) { + pr_devel("%s:%d read_bus_id(%u) failed\n", __func__, __LINE__, + repo.bus_index); + return result; + } + + for (repo.dev_index = 0; ; repo.dev_index++) { + result = ps3_repository_find_device(&repo); + if (result == -ENODEV) { + result = 0; + break; + } else if (result) + break; + + result = callback(&repo); + if (result) { + pr_devel("%s:%d: abort at callback\n", __func__, + __LINE__); + break; + } + } + + pr_devel(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +int ps3_repository_find_bus(enum ps3_bus_type bus_type, unsigned int from, + unsigned int *bus_index) +{ + unsigned int i; + enum ps3_bus_type type; + int error; + + for (i = from; i < 10; i++) { + error = ps3_repository_read_bus_type(i, &type); + if (error) { + pr_devel("%s:%d read_bus_type failed\n", + __func__, __LINE__); + *bus_index = UINT_MAX; + return error; + } + if (type == bus_type) { + *bus_index = i; + return 0; + } + } + *bus_index = UINT_MAX; + return -ENODEV; +} + +int ps3_repository_find_interrupt(const struct ps3_repository_device *repo, + enum ps3_interrupt_type intr_type, unsigned int *interrupt_id) +{ + int result = 0; + unsigned int res_index; + + pr_devel("%s:%d: find intr_type %u\n", __func__, __LINE__, intr_type); + + *interrupt_id = UINT_MAX; + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_interrupt_type t; + unsigned int id; + + result = ps3_repository_read_dev_intr(repo->bus_index, + repo->dev_index, res_index, &t, &id); + + if (result) { + pr_devel("%s:%d read_dev_intr failed\n", + __func__, __LINE__); + return result; + } + + if (t == intr_type) { + *interrupt_id = id; + break; + } + } + + if (res_index == 10) + return -ENODEV; + + pr_devel("%s:%d: found intr_type %u at res_index %u\n", + __func__, __LINE__, intr_type, res_index); + + return result; +} + +int ps3_repository_find_reg(const struct ps3_repository_device *repo, + enum ps3_reg_type reg_type, u64 *bus_addr, u64 *len) +{ + int result = 0; + unsigned int res_index; + + pr_devel("%s:%d: find reg_type %u\n", __func__, __LINE__, reg_type); + + *bus_addr = *len = 0; + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_reg_type t; + u64 a; + u64 l; + + result = ps3_repository_read_dev_reg(repo->bus_index, + repo->dev_index, res_index, &t, &a, &l); + + if (result) { + pr_devel("%s:%d read_dev_reg failed\n", + __func__, __LINE__); + return result; + } + + if (t == reg_type) { + *bus_addr = a; + *len = l; + break; + } + } + + if (res_index == 10) + return -ENODEV; + + pr_devel("%s:%d: found reg_type %u at res_index %u\n", + __func__, __LINE__, reg_type, res_index); + + return result; +} + +int ps3_repository_read_stor_dev_port(unsigned int bus_index, + unsigned int dev_index, u64 *port) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("port", 0), + 0, port, NULL); +} + +int ps3_repository_read_stor_dev_blk_size(unsigned int bus_index, + unsigned int dev_index, u64 *blk_size) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("blk_size", 0), + 0, blk_size, NULL); +} + +int ps3_repository_read_stor_dev_num_blocks(unsigned int bus_index, + unsigned int dev_index, u64 *num_blocks) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("n_blocks", 0), + 0, num_blocks, NULL); +} + +int ps3_repository_read_stor_dev_num_regions(unsigned int bus_index, + unsigned int dev_index, unsigned int *num_regions) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("n_regs", 0), + 0, &v1, NULL); + *num_regions = v1; + return result; +} + +int ps3_repository_read_stor_dev_region_id(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, + unsigned int *region_id) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("region", region_index), + make_field("id", 0), + &v1, NULL); + *region_id = v1; + return result; +} + +int ps3_repository_read_stor_dev_region_size(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, u64 *region_size) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("region", region_index), + make_field("size", 0), + region_size, NULL); +} + +int ps3_repository_read_stor_dev_region_start(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, u64 *region_start) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("bus", bus_index), + make_field("dev", dev_index), + make_field("region", region_index), + make_field("start", 0), + region_start, NULL); +} + +int ps3_repository_read_stor_dev_info(unsigned int bus_index, + unsigned int dev_index, u64 *port, u64 *blk_size, + u64 *num_blocks, unsigned int *num_regions) +{ + int result; + + result = ps3_repository_read_stor_dev_port(bus_index, dev_index, port); + if (result) + return result; + + result = ps3_repository_read_stor_dev_blk_size(bus_index, dev_index, + blk_size); + if (result) + return result; + + result = ps3_repository_read_stor_dev_num_blocks(bus_index, dev_index, + num_blocks); + if (result) + return result; + + result = ps3_repository_read_stor_dev_num_regions(bus_index, dev_index, + num_regions); + return result; +} + +int ps3_repository_read_stor_dev_region(unsigned int bus_index, + unsigned int dev_index, unsigned int region_index, + unsigned int *region_id, u64 *region_start, u64 *region_size) +{ + int result; + + result = ps3_repository_read_stor_dev_region_id(bus_index, dev_index, + region_index, region_id); + if (result) + return result; + + result = ps3_repository_read_stor_dev_region_start(bus_index, dev_index, + region_index, region_start); + if (result) + return result; + + result = ps3_repository_read_stor_dev_region_size(bus_index, dev_index, + region_index, region_size); + return result; +} + +/** + * ps3_repository_read_num_pu - Number of logical PU processors for this lpar. + */ + +int ps3_repository_read_num_pu(u64 *num_pu) +{ + *num_pu = 0; + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("pun", 0), + 0, 0, + num_pu, NULL); +} + +/** + * ps3_repository_read_pu_id - Read the logical PU id. + * @pu_index: Zero based index. + * @pu_id: The logical PU id. + */ + +int ps3_repository_read_pu_id(unsigned int pu_index, u64 *pu_id) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("pu", pu_index), + 0, 0, + pu_id, NULL); +} + +int ps3_repository_read_rm_size(unsigned int ppe_id, u64 *rm_size) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("pu", 0), + ppe_id, + make_field("rm_size", 0), + rm_size, NULL); +} + +int ps3_repository_read_region_total(u64 *region_total) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("rgntotal", 0), + 0, 0, + region_total, NULL); +} + +/** + * ps3_repository_read_mm_info - Read mm info for single pu system. + * @rm_base: Real mode memory base address. + * @rm_size: Real mode memory size. + * @region_total: Maximum memory region size. + */ + +int ps3_repository_read_mm_info(u64 *rm_base, u64 *rm_size, u64 *region_total) +{ + int result; + u64 ppe_id; + + lv1_get_logical_ppe_id(&ppe_id); + *rm_base = 0; + result = ps3_repository_read_rm_size(ppe_id, rm_size); + return result ? result + : ps3_repository_read_region_total(region_total); +} + +/** + * ps3_repository_read_highmem_region_count - Read the number of highmem regions + * + * Bootloaders must arrange the repository nodes such that regions are indexed + * with a region_index from 0 to region_count-1. + */ + +int ps3_repository_read_highmem_region_count(unsigned int *region_count) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("highmem", 0), + make_field("region", 0), + make_field("count", 0), + 0, + &v1, NULL); + *region_count = v1; + return result; +} + + +int ps3_repository_read_highmem_base(unsigned int region_index, + u64 *highmem_base) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("highmem", 0), + make_field("region", region_index), + make_field("base", 0), + 0, + highmem_base, NULL); +} + +int ps3_repository_read_highmem_size(unsigned int region_index, + u64 *highmem_size) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("highmem", 0), + make_field("region", region_index), + make_field("size", 0), + 0, + highmem_size, NULL); +} + +/** + * ps3_repository_read_highmem_info - Read high memory region info + * @region_index: Region index, {0,..,region_count-1}. + * @highmem_base: High memory base address. + * @highmem_size: High memory size. + * + * Bootloaders that preallocate highmem regions must place the + * region info into the repository at these well known nodes. + */ + +int ps3_repository_read_highmem_info(unsigned int region_index, + u64 *highmem_base, u64 *highmem_size) +{ + int result; + + *highmem_base = 0; + result = ps3_repository_read_highmem_base(region_index, highmem_base); + return result ? result + : ps3_repository_read_highmem_size(region_index, highmem_size); +} + +/** + * ps3_repository_read_num_spu_reserved - Number of physical spus reserved. + * @num_spu: Number of physical spus. + */ + +int ps3_repository_read_num_spu_reserved(unsigned int *num_spu_reserved) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("spun", 0), + 0, 0, + &v1, NULL); + *num_spu_reserved = v1; + return result; +} + +/** + * ps3_repository_read_num_spu_resource_id - Number of spu resource reservations. + * @num_resource_id: Number of spu resource ids. + */ + +int ps3_repository_read_num_spu_resource_id(unsigned int *num_resource_id) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("spursvn", 0), + 0, 0, + &v1, NULL); + *num_resource_id = v1; + return result; +} + +/** + * ps3_repository_read_spu_resource_id - spu resource reservation id value. + * @res_index: Resource reservation index. + * @resource_type: Resource reservation type. + * @resource_id: Resource reservation id. + */ + +int ps3_repository_read_spu_resource_id(unsigned int res_index, + enum ps3_spu_resource_type *resource_type, unsigned int *resource_id) +{ + int result; + u64 v1 = 0; + u64 v2 = 0; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("spursv", 0), + res_index, + 0, + &v1, &v2); + *resource_type = v1; + *resource_id = v2; + return result; +} + +static int ps3_repository_read_boot_dat_address(u64 *address) +{ + return read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("boot_dat", 0), + make_field("address", 0), + 0, + address, NULL); +} + +int ps3_repository_read_boot_dat_size(unsigned int *size) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("boot_dat", 0), + make_field("size", 0), + 0, + &v1, NULL); + *size = v1; + return result; +} + +int ps3_repository_read_vuart_av_port(unsigned int *port) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("vir_uart", 0), + make_field("port", 0), + make_field("avset", 0), + &v1, NULL); + *port = v1; + return result; +} + +int ps3_repository_read_vuart_sysmgr_port(unsigned int *port) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_CURRENT, + make_first_field("bi", 0), + make_field("vir_uart", 0), + make_field("port", 0), + make_field("sysmgr", 0), + &v1, NULL); + *port = v1; + return result; +} + +/** + * ps3_repository_read_boot_dat_info - Get address and size of cell_ext_os_area. + * address: lpar address of cell_ext_os_area + * @size: size of cell_ext_os_area + */ + +int ps3_repository_read_boot_dat_info(u64 *lpar_addr, unsigned int *size) +{ + int result; + + *size = 0; + result = ps3_repository_read_boot_dat_address(lpar_addr); + return result ? result + : ps3_repository_read_boot_dat_size(size); +} + +/** + * ps3_repository_read_num_be - Number of physical BE processors in the system. + */ + +int ps3_repository_read_num_be(unsigned int *num_be) +{ + int result; + u64 v1 = 0; + + result = read_node(PS3_LPAR_ID_PME, + make_first_field("ben", 0), + 0, + 0, + 0, + &v1, NULL); + *num_be = v1; + return result; +} + +/** + * ps3_repository_read_be_node_id - Read the physical BE processor node id. + * @be_index: Zero based index. + * @node_id: The BE processor node id. + */ + +int ps3_repository_read_be_node_id(unsigned int be_index, u64 *node_id) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("be", be_index), + 0, + 0, + 0, + node_id, NULL); +} + +/** + * ps3_repository_read_be_id - Read the physical BE processor id. + * @node_id: The BE processor node id. + * @be_id: The BE processor id. + */ + +int ps3_repository_read_be_id(u64 node_id, u64 *be_id) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("be", 0), + node_id, + 0, + 0, + be_id, NULL); +} + +int ps3_repository_read_tb_freq(u64 node_id, u64 *tb_freq) +{ + return read_node(PS3_LPAR_ID_PME, + make_first_field("be", 0), + node_id, + make_field("clock", 0), + 0, + tb_freq, NULL); +} + +int ps3_repository_read_be_tb_freq(unsigned int be_index, u64 *tb_freq) +{ + int result; + u64 node_id; + + *tb_freq = 0; + result = ps3_repository_read_be_node_id(be_index, &node_id); + return result ? result + : ps3_repository_read_tb_freq(node_id, tb_freq); +} + +int ps3_repository_read_lpm_privileges(unsigned int be_index, u64 *lpar, + u64 *rights) +{ + int result; + u64 node_id; + + *lpar = 0; + *rights = 0; + result = ps3_repository_read_be_node_id(be_index, &node_id); + return result ? result + : read_node(PS3_LPAR_ID_PME, + make_first_field("be", 0), + node_id, + make_field("lpm", 0), + make_field("priv", 0), + lpar, rights); +} + +#if defined(CONFIG_PS3_REPOSITORY_WRITE) + +static int create_node(u64 n1, u64 n2, u64 n3, u64 n4, u64 v1, u64 v2) +{ + int result; + + dump_node(0, n1, n2, n3, n4, v1, v2); + + result = lv1_create_repository_node(n1, n2, n3, n4, v1, v2); + + if (result) { + pr_devel("%s:%d: lv1_create_repository_node failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return -ENOENT; + } + + return 0; +} + +static int delete_node(u64 n1, u64 n2, u64 n3, u64 n4) +{ + int result; + + dump_node(0, n1, n2, n3, n4, 0, 0); + + result = lv1_delete_repository_node(n1, n2, n3, n4); + + if (result) { + pr_devel("%s:%d: lv1_delete_repository_node failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return -ENOENT; + } + + return 0; +} + +static int write_node(u64 n1, u64 n2, u64 n3, u64 n4, u64 v1, u64 v2) +{ + int result; + + result = create_node(n1, n2, n3, n4, v1, v2); + + if (!result) + return 0; + + result = lv1_write_repository_node(n1, n2, n3, n4, v1, v2); + + if (result) { + pr_devel("%s:%d: lv1_write_repository_node failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return -ENOENT; + } + + return 0; +} + +int ps3_repository_write_highmem_region_count(unsigned int region_count) +{ + int result; + u64 v1 = (u64)region_count; + + result = write_node( + make_first_field("highmem", 0), + make_field("region", 0), + make_field("count", 0), + 0, + v1, 0); + return result; +} + +int ps3_repository_write_highmem_base(unsigned int region_index, + u64 highmem_base) +{ + return write_node( + make_first_field("highmem", 0), + make_field("region", region_index), + make_field("base", 0), + 0, + highmem_base, 0); +} + +int ps3_repository_write_highmem_size(unsigned int region_index, + u64 highmem_size) +{ + return write_node( + make_first_field("highmem", 0), + make_field("region", region_index), + make_field("size", 0), + 0, + highmem_size, 0); +} + +int ps3_repository_write_highmem_info(unsigned int region_index, + u64 highmem_base, u64 highmem_size) +{ + int result; + + result = ps3_repository_write_highmem_base(region_index, highmem_base); + return result ? result + : ps3_repository_write_highmem_size(region_index, highmem_size); +} + +static int ps3_repository_delete_highmem_base(unsigned int region_index) +{ + return delete_node( + make_first_field("highmem", 0), + make_field("region", region_index), + make_field("base", 0), + 0); +} + +static int ps3_repository_delete_highmem_size(unsigned int region_index) +{ + return delete_node( + make_first_field("highmem", 0), + make_field("region", region_index), + make_field("size", 0), + 0); +} + +int ps3_repository_delete_highmem_info(unsigned int region_index) +{ + int result; + + result = ps3_repository_delete_highmem_base(region_index); + result += ps3_repository_delete_highmem_size(region_index); + + return result ? -1 : 0; +} + +#endif /* defined(CONFIG_PS3_REPOSITORY_WRITE) */ + +#if defined(DEBUG) + +int ps3_repository_dump_resource_info(const struct ps3_repository_device *repo) +{ + int result = 0; + unsigned int res_index; + + pr_devel(" -> %s:%d: (%u:%u)\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_interrupt_type intr_type; + unsigned int interrupt_id; + + result = ps3_repository_read_dev_intr(repo->bus_index, + repo->dev_index, res_index, &intr_type, &interrupt_id); + + if (result) { + if (result != LV1_NO_ENTRY) + pr_devel("%s:%d ps3_repository_read_dev_intr" + " (%u:%u) failed\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + break; + } + + pr_devel("%s:%d (%u:%u) intr_type %u, interrupt_id %u\n", + __func__, __LINE__, repo->bus_index, repo->dev_index, + intr_type, interrupt_id); + } + + for (res_index = 0; res_index < 10; res_index++) { + enum ps3_reg_type reg_type; + u64 bus_addr; + u64 len; + + result = ps3_repository_read_dev_reg(repo->bus_index, + repo->dev_index, res_index, ®_type, &bus_addr, &len); + + if (result) { + if (result != LV1_NO_ENTRY) + pr_devel("%s:%d ps3_repository_read_dev_reg" + " (%u:%u) failed\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + break; + } + + pr_devel("%s:%d (%u:%u) reg_type %u, bus_addr %llxh, len %llxh\n", + __func__, __LINE__, repo->bus_index, repo->dev_index, + reg_type, bus_addr, len); + } + + pr_devel(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +static int dump_stor_dev_info(struct ps3_repository_device *repo) +{ + int result = 0; + unsigned int num_regions, region_index; + u64 port, blk_size, num_blocks; + + pr_devel(" -> %s:%d: (%u:%u)\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + + result = ps3_repository_read_stor_dev_info(repo->bus_index, + repo->dev_index, &port, &blk_size, &num_blocks, &num_regions); + if (result) { + pr_devel("%s:%d ps3_repository_read_stor_dev_info" + " (%u:%u) failed\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + goto out; + } + + pr_devel("%s:%d (%u:%u): port %llu, blk_size %llu, num_blocks " + "%llu, num_regions %u\n", + __func__, __LINE__, repo->bus_index, repo->dev_index, + port, blk_size, num_blocks, num_regions); + + for (region_index = 0; region_index < num_regions; region_index++) { + unsigned int region_id; + u64 region_start, region_size; + + result = ps3_repository_read_stor_dev_region(repo->bus_index, + repo->dev_index, region_index, ®ion_id, + ®ion_start, ®ion_size); + if (result) { + pr_devel("%s:%d ps3_repository_read_stor_dev_region" + " (%u:%u) failed\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + break; + } + + pr_devel("%s:%d (%u:%u) region_id %u, start %lxh, size %lxh\n", + __func__, __LINE__, repo->bus_index, repo->dev_index, + region_id, (unsigned long)region_start, + (unsigned long)region_size); + } + +out: + pr_devel(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +static int dump_device_info(struct ps3_repository_device *repo, + unsigned int num_dev) +{ + int result = 0; + + pr_devel(" -> %s:%d: bus_%u\n", __func__, __LINE__, repo->bus_index); + + for (repo->dev_index = 0; repo->dev_index < num_dev; + repo->dev_index++) { + + result = ps3_repository_read_dev_type(repo->bus_index, + repo->dev_index, &repo->dev_type); + + if (result) { + pr_devel("%s:%d ps3_repository_read_dev_type" + " (%u:%u) failed\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + break; + } + + result = ps3_repository_read_dev_id(repo->bus_index, + repo->dev_index, &repo->dev_id); + + if (result) { + pr_devel("%s:%d ps3_repository_read_dev_id" + " (%u:%u) failed\n", __func__, __LINE__, + repo->bus_index, repo->dev_index); + continue; + } + + pr_devel("%s:%d (%u:%u): dev_type %u, dev_id %lu\n", __func__, + __LINE__, repo->bus_index, repo->dev_index, + repo->dev_type, (unsigned long)repo->dev_id); + + ps3_repository_dump_resource_info(repo); + + if (repo->bus_type == PS3_BUS_TYPE_STORAGE) + dump_stor_dev_info(repo); + } + + pr_devel(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +int ps3_repository_dump_bus_info(void) +{ + int result = 0; + struct ps3_repository_device repo; + + pr_devel(" -> %s:%d\n", __func__, __LINE__); + + memset(&repo, 0, sizeof(repo)); + + for (repo.bus_index = 0; repo.bus_index < 10; repo.bus_index++) { + unsigned int num_dev; + + result = ps3_repository_read_bus_type(repo.bus_index, + &repo.bus_type); + + if (result) { + pr_devel("%s:%d read_bus_type(%u) failed\n", + __func__, __LINE__, repo.bus_index); + break; + } + + result = ps3_repository_read_bus_id(repo.bus_index, + &repo.bus_id); + + if (result) { + pr_devel("%s:%d read_bus_id(%u) failed\n", + __func__, __LINE__, repo.bus_index); + continue; + } + + if (repo.bus_index != repo.bus_id) + pr_devel("%s:%d bus_index != bus_id\n", + __func__, __LINE__); + + result = ps3_repository_read_bus_num_dev(repo.bus_index, + &num_dev); + + if (result) { + pr_devel("%s:%d read_bus_num_dev(%u) failed\n", + __func__, __LINE__, repo.bus_index); + continue; + } + + pr_devel("%s:%d bus_%u: bus_type %u, bus_id %lu, num_dev %u\n", + __func__, __LINE__, repo.bus_index, repo.bus_type, + (unsigned long)repo.bus_id, num_dev); + + dump_device_info(&repo, num_dev); + } + + pr_devel(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +#endif /* defined(DEBUG) */ diff --git a/arch/powerpc/platforms/ps3/setup.c b/arch/powerpc/platforms/ps3/setup.c new file mode 100644 index 000000000..e9ae5dd03 --- /dev/null +++ b/arch/powerpc/platforms/ps3/setup.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 platform setup routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/root_dev.h> +#include <linux/console.h> +#include <linux/export.h> +#include <linux/memblock.h> + +#include <asm/machdep.h> +#include <asm/firmware.h> +#include <asm/time.h> +#include <asm/iommu.h> +#include <asm/udbg.h> +#include <asm/prom.h> +#include <asm/lv1call.h> +#include <asm/ps3gpu.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG udbg_printf +#else +#define DBG pr_debug +#endif + +/* mutex synchronizing GPU accesses and video mode changes */ +DEFINE_MUTEX(ps3_gpu_mutex); +EXPORT_SYMBOL_GPL(ps3_gpu_mutex); + +static union ps3_firmware_version ps3_firmware_version; + +void ps3_get_firmware_version(union ps3_firmware_version *v) +{ + *v = ps3_firmware_version; +} +EXPORT_SYMBOL_GPL(ps3_get_firmware_version); + +int ps3_compare_firmware_version(u16 major, u16 minor, u16 rev) +{ + union ps3_firmware_version x; + + x.pad = 0; + x.major = major; + x.minor = minor; + x.rev = rev; + + return (ps3_firmware_version.raw > x.raw) - + (ps3_firmware_version.raw < x.raw); +} +EXPORT_SYMBOL_GPL(ps3_compare_firmware_version); + +static void ps3_power_save(void) +{ + /* + * lv1_pause() puts the PPE thread into inactive state until an + * irq on an unmasked plug exists. MSR[EE] has no effect. + * flags: 0 = wake on DEC interrupt, 1 = ignore DEC interrupt. + */ + + lv1_pause(0); +} + +static void __noreturn ps3_restart(char *cmd) +{ + DBG("%s:%d cmd '%s'\n", __func__, __LINE__, cmd); + + smp_send_stop(); + ps3_sys_manager_restart(); /* never returns */ +} + +static void ps3_power_off(void) +{ + DBG("%s:%d\n", __func__, __LINE__); + + smp_send_stop(); + ps3_sys_manager_power_off(); /* never returns */ +} + +static void __noreturn ps3_halt(void) +{ + DBG("%s:%d\n", __func__, __LINE__); + + smp_send_stop(); + ps3_sys_manager_halt(); /* never returns */ +} + +static void ps3_panic(char *str) +{ + DBG("%s:%d %s\n", __func__, __LINE__, str); + + smp_send_stop(); + printk("\n"); + printk(" System does not reboot automatically.\n"); + printk(" Please press POWER button.\n"); + printk("\n"); + panic_flush_kmsg_end(); + + while(1) + lv1_pause(1); +} + +#if defined(CONFIG_FB_PS3) || defined(CONFIG_FB_PS3_MODULE) || \ + defined(CONFIG_PS3_FLASH) || defined(CONFIG_PS3_FLASH_MODULE) +static void __init prealloc(struct ps3_prealloc *p) +{ + if (!p->size) + return; + + p->address = memblock_alloc(p->size, p->align); + if (!p->address) + panic("%s: Failed to allocate %lu bytes align=0x%lx\n", + __func__, p->size, p->align); + + printk(KERN_INFO "%s: %lu bytes at %p\n", p->name, p->size, + p->address); +} +#endif + +#if defined(CONFIG_FB_PS3) || defined(CONFIG_FB_PS3_MODULE) +struct ps3_prealloc ps3fb_videomemory = { + .name = "ps3fb videomemory", + .size = CONFIG_FB_PS3_DEFAULT_SIZE_M*1024*1024, + .align = 1024*1024 /* the GPU requires 1 MiB alignment */ +}; +EXPORT_SYMBOL_GPL(ps3fb_videomemory); +#define prealloc_ps3fb_videomemory() prealloc(&ps3fb_videomemory) + +static int __init early_parse_ps3fb(char *p) +{ + if (!p) + return 1; + + ps3fb_videomemory.size = ALIGN(memparse(p, &p), + ps3fb_videomemory.align); + return 0; +} +early_param("ps3fb", early_parse_ps3fb); +#else +#define prealloc_ps3fb_videomemory() do { } while (0) +#endif + +#if defined(CONFIG_PS3_FLASH) || defined(CONFIG_PS3_FLASH_MODULE) +struct ps3_prealloc ps3flash_bounce_buffer = { + .name = "ps3flash bounce buffer", + .size = 256*1024, + .align = 256*1024 +}; +EXPORT_SYMBOL_GPL(ps3flash_bounce_buffer); +#define prealloc_ps3flash_bounce_buffer() prealloc(&ps3flash_bounce_buffer) + +static int __init early_parse_ps3flash(char *p) +{ + if (!p) + return 1; + + if (!strcmp(p, "off")) + ps3flash_bounce_buffer.size = 0; + + return 0; +} +early_param("ps3flash", early_parse_ps3flash); +#else +#define prealloc_ps3flash_bounce_buffer() do { } while (0) +#endif + +static int ps3_set_dabr(unsigned long dabr, unsigned long dabrx) +{ + /* Have to set at least one bit in the DABRX */ + if (dabrx == 0 && dabr == 0) + dabrx = DABRX_USER; + /* hypervisor only allows us to set BTI, Kernel and user */ + dabrx &= DABRX_BTI | DABRX_KERNEL | DABRX_USER; + + return lv1_set_dabr(dabr, dabrx) ? -1 : 0; +} + +static void __init ps3_setup_arch(void) +{ + u64 tmp; + + DBG(" -> %s:%d\n", __func__, __LINE__); + + lv1_get_version_info(&ps3_firmware_version.raw, &tmp); + + printk(KERN_INFO "PS3 firmware version %u.%u.%u\n", + ps3_firmware_version.major, ps3_firmware_version.minor, + ps3_firmware_version.rev); + + ps3_spu_set_platform(); + +#ifdef CONFIG_SMP + smp_init_ps3(); +#endif + + prealloc_ps3fb_videomemory(); + prealloc_ps3flash_bounce_buffer(); + + ppc_md.power_save = ps3_power_save; + ps3_os_area_init(); + + DBG(" <- %s:%d\n", __func__, __LINE__); +} + +static void __init ps3_progress(char *s, unsigned short hex) +{ + printk("*** %04x : %s\n", hex, s ? s : ""); +} + +void __init ps3_early_mm_init(void) +{ + unsigned long htab_size; + + ps3_mm_init(); + ps3_mm_vas_create(&htab_size); + ps3_hpte_init(htab_size); +} + +static int __init ps3_probe(void) +{ + DBG(" -> %s:%d\n", __func__, __LINE__); + + if (!of_machine_is_compatible("sony,ps3")) + return 0; + + ps3_os_area_save_params(); + + pm_power_off = ps3_power_off; + + DBG(" <- %s:%d\n", __func__, __LINE__); + return 1; +} + +#if defined(CONFIG_KEXEC_CORE) +static void ps3_kexec_cpu_down(int crash_shutdown, int secondary) +{ + int cpu = smp_processor_id(); + + DBG(" -> %s:%d: (%d)\n", __func__, __LINE__, cpu); + + ps3_smp_cleanup_cpu(cpu); + ps3_shutdown_IRQ(cpu); + + DBG(" <- %s:%d\n", __func__, __LINE__); +} +#endif + +define_machine(ps3) { + .name = "PS3", + .probe = ps3_probe, + .setup_arch = ps3_setup_arch, + .init_IRQ = ps3_init_IRQ, + .panic = ps3_panic, + .get_boot_time = ps3_get_boot_time, + .set_dabr = ps3_set_dabr, + .calibrate_decr = ps3_calibrate_decr, + .progress = ps3_progress, + .restart = ps3_restart, + .halt = ps3_halt, +#if defined(CONFIG_KEXEC_CORE) + .kexec_cpu_down = ps3_kexec_cpu_down, +#endif +}; diff --git a/arch/powerpc/platforms/ps3/smp.c b/arch/powerpc/platforms/ps3/smp.c new file mode 100644 index 000000000..93b1e73b3 --- /dev/null +++ b/arch/powerpc/platforms/ps3/smp.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 SMP routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/kernel.h> +#include <linux/smp.h> + +#include <asm/machdep.h> +#include <asm/udbg.h> + +#include "platform.h" + +#if defined(DEBUG) +#define DBG udbg_printf +#else +#define DBG pr_debug +#endif + +/** + * ps3_ipi_virqs - a per cpu array of virqs for ipi use + */ + +#define MSG_COUNT 4 +static DEFINE_PER_CPU(unsigned int [MSG_COUNT], ps3_ipi_virqs); + +static void ps3_smp_message_pass(int cpu, int msg) +{ + int result; + unsigned int virq; + + if (msg >= MSG_COUNT) { + DBG("%s:%d: bad msg: %d\n", __func__, __LINE__, msg); + return; + } + + virq = per_cpu(ps3_ipi_virqs, cpu)[msg]; + result = ps3_send_event_locally(virq); + + if (result) + DBG("%s:%d: ps3_send_event_locally(%d, %d) failed" + " (%d)\n", __func__, __LINE__, cpu, msg, result); +} + +static void __init ps3_smp_probe(void) +{ + int cpu; + + for (cpu = 0; cpu < 2; cpu++) { + int result; + unsigned int *virqs = per_cpu(ps3_ipi_virqs, cpu); + int i; + + DBG(" -> %s:%d: (%d)\n", __func__, __LINE__, cpu); + + /* + * Check assumptions on ps3_ipi_virqs[] indexing. If this + * check fails, then a different mapping of PPC_MSG_ + * to index needs to be setup. + */ + + BUILD_BUG_ON(PPC_MSG_CALL_FUNCTION != 0); + BUILD_BUG_ON(PPC_MSG_RESCHEDULE != 1); + BUILD_BUG_ON(PPC_MSG_TICK_BROADCAST != 2); + BUILD_BUG_ON(PPC_MSG_NMI_IPI != 3); + + for (i = 0; i < MSG_COUNT; i++) { + result = ps3_event_receive_port_setup(cpu, &virqs[i]); + + if (result) + continue; + + DBG("%s:%d: (%d, %d) => virq %u\n", + __func__, __LINE__, cpu, i, virqs[i]); + + result = smp_request_message_ipi(virqs[i], i); + + if (result) + virqs[i] = 0; + else + ps3_register_ipi_irq(cpu, virqs[i]); + } + + ps3_register_ipi_debug_brk(cpu, virqs[PPC_MSG_NMI_IPI]); + + DBG(" <- %s:%d: (%d)\n", __func__, __LINE__, cpu); + } +} + +void ps3_smp_cleanup_cpu(int cpu) +{ + unsigned int *virqs = per_cpu(ps3_ipi_virqs, cpu); + int i; + + DBG(" -> %s:%d: (%d)\n", __func__, __LINE__, cpu); + + for (i = 0; i < MSG_COUNT; i++) { + /* Can't call free_irq from interrupt context. */ + ps3_event_receive_port_destroy(virqs[i]); + virqs[i] = 0; + } + + DBG(" <- %s:%d: (%d)\n", __func__, __LINE__, cpu); +} + +static struct smp_ops_t ps3_smp_ops = { + .probe = ps3_smp_probe, + .message_pass = ps3_smp_message_pass, + .kick_cpu = smp_generic_kick_cpu, +}; + +void smp_init_ps3(void) +{ + DBG(" -> %s\n", __func__); + smp_ops = &ps3_smp_ops; + DBG(" <- %s\n", __func__); +} diff --git a/arch/powerpc/platforms/ps3/spu.c b/arch/powerpc/platforms/ps3/spu.c new file mode 100644 index 000000000..0c252478e --- /dev/null +++ b/arch/powerpc/platforms/ps3/spu.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 Platform spu routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mmzone.h> +#include <linux/export.h> +#include <linux/io.h> +#include <linux/mm.h> + +#include <asm/spu.h> +#include <asm/spu_priv1.h> +#include <asm/lv1call.h> +#include <asm/ps3.h> + +#include "../cell/spufs/spufs.h" +#include "platform.h" + +/* spu_management_ops */ + +/** + * enum spe_type - Type of spe to create. + * @spe_type_logical: Standard logical spe. + * + * For use with lv1_construct_logical_spe(). The current HV does not support + * any types other than those listed. + */ + +enum spe_type { + SPE_TYPE_LOGICAL = 0, +}; + +/** + * struct spe_shadow - logical spe shadow register area. + * + * Read-only shadow of spe registers. + */ + +struct spe_shadow { + u8 padding_0140[0x0140]; + u64 int_status_class0_RW; /* 0x0140 */ + u64 int_status_class1_RW; /* 0x0148 */ + u64 int_status_class2_RW; /* 0x0150 */ + u8 padding_0158[0x0610-0x0158]; + u64 mfc_dsisr_RW; /* 0x0610 */ + u8 padding_0618[0x0620-0x0618]; + u64 mfc_dar_RW; /* 0x0620 */ + u8 padding_0628[0x0800-0x0628]; + u64 mfc_dsipr_R; /* 0x0800 */ + u8 padding_0808[0x0810-0x0808]; + u64 mfc_lscrr_R; /* 0x0810 */ + u8 padding_0818[0x0c00-0x0818]; + u64 mfc_cer_R; /* 0x0c00 */ + u8 padding_0c08[0x0f00-0x0c08]; + u64 spe_execution_status; /* 0x0f00 */ + u8 padding_0f08[0x1000-0x0f08]; +}; + +/** + * enum spe_ex_state - Logical spe execution state. + * @spe_ex_state_unexecutable: Uninitialized. + * @spe_ex_state_executable: Enabled, not ready. + * @spe_ex_state_executed: Ready for use. + * + * The execution state (status) of the logical spe as reported in + * struct spe_shadow:spe_execution_status. + */ + +enum spe_ex_state { + SPE_EX_STATE_UNEXECUTABLE = 0, + SPE_EX_STATE_EXECUTABLE = 2, + SPE_EX_STATE_EXECUTED = 3, +}; + +/** + * struct priv1_cache - Cached values of priv1 registers. + * @masks[]: Array of cached spe interrupt masks, indexed by class. + * @sr1: Cached mfc_sr1 register. + * @tclass_id: Cached mfc_tclass_id register. + */ + +struct priv1_cache { + u64 masks[3]; + u64 sr1; + u64 tclass_id; +}; + +/** + * struct spu_pdata - Platform state variables. + * @spe_id: HV spe id returned by lv1_construct_logical_spe(). + * @resource_id: HV spe resource id returned by + * ps3_repository_read_spe_resource_id(). + * @priv2_addr: lpar address of spe priv2 area returned by + * lv1_construct_logical_spe(). + * @shadow_addr: lpar address of spe register shadow area returned by + * lv1_construct_logical_spe(). + * @shadow: Virtual (ioremap) address of spe register shadow area. + * @cache: Cached values of priv1 registers. + */ + +struct spu_pdata { + u64 spe_id; + u64 resource_id; + u64 priv2_addr; + u64 shadow_addr; + struct spe_shadow __iomem *shadow; + struct priv1_cache cache; +}; + +static struct spu_pdata *spu_pdata(struct spu *spu) +{ + return spu->pdata; +} + +#define dump_areas(_a, _b, _c, _d, _e) \ + _dump_areas(_a, _b, _c, _d, _e, __func__, __LINE__) +static void _dump_areas(unsigned int spe_id, unsigned long priv2, + unsigned long problem, unsigned long ls, unsigned long shadow, + const char* func, int line) +{ + pr_debug("%s:%d: spe_id: %xh (%u)\n", func, line, spe_id, spe_id); + pr_debug("%s:%d: priv2: %lxh\n", func, line, priv2); + pr_debug("%s:%d: problem: %lxh\n", func, line, problem); + pr_debug("%s:%d: ls: %lxh\n", func, line, ls); + pr_debug("%s:%d: shadow: %lxh\n", func, line, shadow); +} + +u64 ps3_get_spe_id(void *arg) +{ + return spu_pdata(arg)->spe_id; +} +EXPORT_SYMBOL_GPL(ps3_get_spe_id); + +static unsigned long get_vas_id(void) +{ + u64 id; + + lv1_get_logical_ppe_id(&id); + lv1_get_virtual_address_space_id_of_ppe(&id); + + return id; +} + +static int __init construct_spu(struct spu *spu) +{ + int result; + u64 unused; + u64 problem_phys; + u64 local_store_phys; + + result = lv1_construct_logical_spe(PAGE_SHIFT, PAGE_SHIFT, PAGE_SHIFT, + PAGE_SHIFT, PAGE_SHIFT, get_vas_id(), SPE_TYPE_LOGICAL, + &spu_pdata(spu)->priv2_addr, &problem_phys, + &local_store_phys, &unused, + &spu_pdata(spu)->shadow_addr, + &spu_pdata(spu)->spe_id); + spu->problem_phys = problem_phys; + spu->local_store_phys = local_store_phys; + + if (result) { + pr_debug("%s:%d: lv1_construct_logical_spe failed: %s\n", + __func__, __LINE__, ps3_result(result)); + return result; + } + + return result; +} + +static void spu_unmap(struct spu *spu) +{ + iounmap(spu->priv2); + iounmap(spu->problem); + iounmap((__force u8 __iomem *)spu->local_store); + iounmap(spu_pdata(spu)->shadow); +} + +/** + * setup_areas - Map the spu regions into the address space. + * + * The current HV requires the spu shadow regs to be mapped with the + * PTE page protection bits set as read-only. + */ + +static int __init setup_areas(struct spu *spu) +{ + struct table {char* name; unsigned long addr; unsigned long size;}; + unsigned long shadow_flags = pgprot_val(pgprot_noncached_wc(PAGE_KERNEL_RO)); + + spu_pdata(spu)->shadow = ioremap_prot(spu_pdata(spu)->shadow_addr, + sizeof(struct spe_shadow), shadow_flags); + if (!spu_pdata(spu)->shadow) { + pr_debug("%s:%d: ioremap shadow failed\n", __func__, __LINE__); + goto fail_ioremap; + } + + spu->local_store = (__force void *)ioremap_wc(spu->local_store_phys, LS_SIZE); + + if (!spu->local_store) { + pr_debug("%s:%d: ioremap local_store failed\n", + __func__, __LINE__); + goto fail_ioremap; + } + + spu->problem = ioremap(spu->problem_phys, + sizeof(struct spu_problem)); + + if (!spu->problem) { + pr_debug("%s:%d: ioremap problem failed\n", __func__, __LINE__); + goto fail_ioremap; + } + + spu->priv2 = ioremap(spu_pdata(spu)->priv2_addr, + sizeof(struct spu_priv2)); + + if (!spu->priv2) { + pr_debug("%s:%d: ioremap priv2 failed\n", __func__, __LINE__); + goto fail_ioremap; + } + + dump_areas(spu_pdata(spu)->spe_id, spu_pdata(spu)->priv2_addr, + spu->problem_phys, spu->local_store_phys, + spu_pdata(spu)->shadow_addr); + dump_areas(spu_pdata(spu)->spe_id, (unsigned long)spu->priv2, + (unsigned long)spu->problem, (unsigned long)spu->local_store, + (unsigned long)spu_pdata(spu)->shadow); + + return 0; + +fail_ioremap: + spu_unmap(spu); + + return -ENOMEM; +} + +static int __init setup_interrupts(struct spu *spu) +{ + int result; + + result = ps3_spe_irq_setup(PS3_BINDING_CPU_ANY, spu_pdata(spu)->spe_id, + 0, &spu->irqs[0]); + + if (result) + goto fail_alloc_0; + + result = ps3_spe_irq_setup(PS3_BINDING_CPU_ANY, spu_pdata(spu)->spe_id, + 1, &spu->irqs[1]); + + if (result) + goto fail_alloc_1; + + result = ps3_spe_irq_setup(PS3_BINDING_CPU_ANY, spu_pdata(spu)->spe_id, + 2, &spu->irqs[2]); + + if (result) + goto fail_alloc_2; + + return result; + +fail_alloc_2: + ps3_spe_irq_destroy(spu->irqs[1]); +fail_alloc_1: + ps3_spe_irq_destroy(spu->irqs[0]); +fail_alloc_0: + spu->irqs[0] = spu->irqs[1] = spu->irqs[2] = 0; + return result; +} + +static int __init enable_spu(struct spu *spu) +{ + int result; + + result = lv1_enable_logical_spe(spu_pdata(spu)->spe_id, + spu_pdata(spu)->resource_id); + + if (result) { + pr_debug("%s:%d: lv1_enable_logical_spe failed: %s\n", + __func__, __LINE__, ps3_result(result)); + goto fail_enable; + } + + result = setup_areas(spu); + + if (result) + goto fail_areas; + + result = setup_interrupts(spu); + + if (result) + goto fail_interrupts; + + return 0; + +fail_interrupts: + spu_unmap(spu); +fail_areas: + lv1_disable_logical_spe(spu_pdata(spu)->spe_id, 0); +fail_enable: + return result; +} + +static int ps3_destroy_spu(struct spu *spu) +{ + int result; + + pr_debug("%s:%d spu_%d\n", __func__, __LINE__, spu->number); + + result = lv1_disable_logical_spe(spu_pdata(spu)->spe_id, 0); + BUG_ON(result); + + ps3_spe_irq_destroy(spu->irqs[2]); + ps3_spe_irq_destroy(spu->irqs[1]); + ps3_spe_irq_destroy(spu->irqs[0]); + + spu->irqs[0] = spu->irqs[1] = spu->irqs[2] = 0; + + spu_unmap(spu); + + result = lv1_destruct_logical_spe(spu_pdata(spu)->spe_id); + BUG_ON(result); + + kfree(spu->pdata); + spu->pdata = NULL; + + return 0; +} + +static int __init ps3_create_spu(struct spu *spu, void *data) +{ + int result; + + pr_debug("%s:%d spu_%d\n", __func__, __LINE__, spu->number); + + spu->pdata = kzalloc(sizeof(struct spu_pdata), + GFP_KERNEL); + + if (!spu->pdata) { + result = -ENOMEM; + goto fail_malloc; + } + + spu_pdata(spu)->resource_id = (unsigned long)data; + + /* Init cached reg values to HV defaults. */ + + spu_pdata(spu)->cache.sr1 = 0x33; + + result = construct_spu(spu); + + if (result) + goto fail_construct; + + /* For now, just go ahead and enable it. */ + + result = enable_spu(spu); + + if (result) + goto fail_enable; + + /* Make sure the spu is in SPE_EX_STATE_EXECUTED. */ + + /* need something better here!!! */ + while (in_be64(&spu_pdata(spu)->shadow->spe_execution_status) + != SPE_EX_STATE_EXECUTED) + (void)0; + + return result; + +fail_enable: +fail_construct: + ps3_destroy_spu(spu); +fail_malloc: + return result; +} + +static int __init ps3_enumerate_spus(int (*fn)(void *data)) +{ + int result; + unsigned int num_resource_id; + unsigned int i; + + result = ps3_repository_read_num_spu_resource_id(&num_resource_id); + + pr_debug("%s:%d: num_resource_id %u\n", __func__, __LINE__, + num_resource_id); + + /* + * For now, just create logical spus equal to the number + * of physical spus reserved for the partition. + */ + + for (i = 0; i < num_resource_id; i++) { + enum ps3_spu_resource_type resource_type; + unsigned int resource_id; + + result = ps3_repository_read_spu_resource_id(i, + &resource_type, &resource_id); + + if (result) + break; + + if (resource_type == PS3_SPU_RESOURCE_TYPE_EXCLUSIVE) { + result = fn((void*)(unsigned long)resource_id); + + if (result) + break; + } + } + + if (result) { + printk(KERN_WARNING "%s:%d: Error initializing spus\n", + __func__, __LINE__); + return result; + } + + return num_resource_id; +} + +static int ps3_init_affinity(void) +{ + return 0; +} + +/** + * ps3_enable_spu - Enable SPU run control. + * + * An outstanding enhancement for the PS3 would be to add a guard to check + * for incorrect access to the spu problem state when the spu context is + * disabled. This check could be implemented with a flag added to the spu + * context that would inhibit mapping problem state pages, and a routine + * to unmap spu problem state pages. When the spu is enabled with + * ps3_enable_spu() the flag would be set allowing pages to be mapped, + * and when the spu is disabled with ps3_disable_spu() the flag would be + * cleared and the mapped problem state pages would be unmapped. + */ + +static void ps3_enable_spu(struct spu_context *ctx) +{ +} + +static void ps3_disable_spu(struct spu_context *ctx) +{ + ctx->ops->runcntl_stop(ctx); +} + +static const struct spu_management_ops spu_management_ps3_ops = { + .enumerate_spus = ps3_enumerate_spus, + .create_spu = ps3_create_spu, + .destroy_spu = ps3_destroy_spu, + .enable_spu = ps3_enable_spu, + .disable_spu = ps3_disable_spu, + .init_affinity = ps3_init_affinity, +}; + +/* spu_priv1_ops */ + +static void int_mask_and(struct spu *spu, int class, u64 mask) +{ + u64 old_mask; + + /* are these serialized by caller??? */ + old_mask = spu_int_mask_get(spu, class); + spu_int_mask_set(spu, class, old_mask & mask); +} + +static void int_mask_or(struct spu *spu, int class, u64 mask) +{ + u64 old_mask; + + old_mask = spu_int_mask_get(spu, class); + spu_int_mask_set(spu, class, old_mask | mask); +} + +static void int_mask_set(struct spu *spu, int class, u64 mask) +{ + spu_pdata(spu)->cache.masks[class] = mask; + lv1_set_spe_interrupt_mask(spu_pdata(spu)->spe_id, class, + spu_pdata(spu)->cache.masks[class]); +} + +static u64 int_mask_get(struct spu *spu, int class) +{ + return spu_pdata(spu)->cache.masks[class]; +} + +static void int_stat_clear(struct spu *spu, int class, u64 stat) +{ + /* Note that MFC_DSISR will be cleared when class1[MF] is set. */ + + lv1_clear_spe_interrupt_status(spu_pdata(spu)->spe_id, class, + stat, 0); +} + +static u64 int_stat_get(struct spu *spu, int class) +{ + u64 stat; + + lv1_get_spe_interrupt_status(spu_pdata(spu)->spe_id, class, &stat); + return stat; +} + +static void cpu_affinity_set(struct spu *spu, int cpu) +{ + /* No support. */ +} + +static u64 mfc_dar_get(struct spu *spu) +{ + return in_be64(&spu_pdata(spu)->shadow->mfc_dar_RW); +} + +static void mfc_dsisr_set(struct spu *spu, u64 dsisr) +{ + /* Nothing to do, cleared in int_stat_clear(). */ +} + +static u64 mfc_dsisr_get(struct spu *spu) +{ + return in_be64(&spu_pdata(spu)->shadow->mfc_dsisr_RW); +} + +static void mfc_sdr_setup(struct spu *spu) +{ + /* Nothing to do. */ +} + +static void mfc_sr1_set(struct spu *spu, u64 sr1) +{ + /* Check bits allowed by HV. */ + + static const u64 allowed = ~(MFC_STATE1_LOCAL_STORAGE_DECODE_MASK + | MFC_STATE1_PROBLEM_STATE_MASK); + + BUG_ON((sr1 & allowed) != (spu_pdata(spu)->cache.sr1 & allowed)); + + spu_pdata(spu)->cache.sr1 = sr1; + lv1_set_spe_privilege_state_area_1_register( + spu_pdata(spu)->spe_id, + offsetof(struct spu_priv1, mfc_sr1_RW), + spu_pdata(spu)->cache.sr1); +} + +static u64 mfc_sr1_get(struct spu *spu) +{ + return spu_pdata(spu)->cache.sr1; +} + +static void mfc_tclass_id_set(struct spu *spu, u64 tclass_id) +{ + spu_pdata(spu)->cache.tclass_id = tclass_id; + lv1_set_spe_privilege_state_area_1_register( + spu_pdata(spu)->spe_id, + offsetof(struct spu_priv1, mfc_tclass_id_RW), + spu_pdata(spu)->cache.tclass_id); +} + +static u64 mfc_tclass_id_get(struct spu *spu) +{ + return spu_pdata(spu)->cache.tclass_id; +} + +static void tlb_invalidate(struct spu *spu) +{ + /* Nothing to do. */ +} + +static void resource_allocation_groupID_set(struct spu *spu, u64 id) +{ + /* No support. */ +} + +static u64 resource_allocation_groupID_get(struct spu *spu) +{ + return 0; /* No support. */ +} + +static void resource_allocation_enable_set(struct spu *spu, u64 enable) +{ + /* No support. */ +} + +static u64 resource_allocation_enable_get(struct spu *spu) +{ + return 0; /* No support. */ +} + +static const struct spu_priv1_ops spu_priv1_ps3_ops = { + .int_mask_and = int_mask_and, + .int_mask_or = int_mask_or, + .int_mask_set = int_mask_set, + .int_mask_get = int_mask_get, + .int_stat_clear = int_stat_clear, + .int_stat_get = int_stat_get, + .cpu_affinity_set = cpu_affinity_set, + .mfc_dar_get = mfc_dar_get, + .mfc_dsisr_set = mfc_dsisr_set, + .mfc_dsisr_get = mfc_dsisr_get, + .mfc_sdr_setup = mfc_sdr_setup, + .mfc_sr1_set = mfc_sr1_set, + .mfc_sr1_get = mfc_sr1_get, + .mfc_tclass_id_set = mfc_tclass_id_set, + .mfc_tclass_id_get = mfc_tclass_id_get, + .tlb_invalidate = tlb_invalidate, + .resource_allocation_groupID_set = resource_allocation_groupID_set, + .resource_allocation_groupID_get = resource_allocation_groupID_get, + .resource_allocation_enable_set = resource_allocation_enable_set, + .resource_allocation_enable_get = resource_allocation_enable_get, +}; + +void ps3_spu_set_platform(void) +{ + spu_priv1_ops = &spu_priv1_ps3_ops; + spu_management_ops = &spu_management_ps3_ops; +} diff --git a/arch/powerpc/platforms/ps3/system-bus.c b/arch/powerpc/platforms/ps3/system-bus.c new file mode 100644 index 000000000..c62aaa29a --- /dev/null +++ b/arch/powerpc/platforms/ps3/system-bus.c @@ -0,0 +1,804 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 system bus driver. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/export.h> +#include <linux/dma-map-ops.h> +#include <linux/err.h> +#include <linux/slab.h> + +#include <asm/udbg.h> +#include <asm/lv1call.h> +#include <asm/firmware.h> +#include <asm/cell-regs.h> + +#include "platform.h" + +static struct device ps3_system_bus = { + .init_name = "ps3_system", +}; + +/* FIXME: need device usage counters! */ +static struct { + struct mutex mutex; + int sb_11; /* usb 0 */ + int sb_12; /* usb 0 */ + int gpu; +} usage_hack; + +static int ps3_is_device(struct ps3_system_bus_device *dev, u64 bus_id, + u64 dev_id) +{ + return dev->bus_id == bus_id && dev->dev_id == dev_id; +} + +static int ps3_open_hv_device_sb(struct ps3_system_bus_device *dev) +{ + int result; + + BUG_ON(!dev->bus_id); + mutex_lock(&usage_hack.mutex); + + if (ps3_is_device(dev, 1, 1)) { + usage_hack.sb_11++; + if (usage_hack.sb_11 > 1) { + result = 0; + goto done; + } + } + + if (ps3_is_device(dev, 1, 2)) { + usage_hack.sb_12++; + if (usage_hack.sb_12 > 1) { + result = 0; + goto done; + } + } + + result = lv1_open_device(dev->bus_id, dev->dev_id, 0); + + if (result) { + pr_debug("%s:%d: lv1_open_device failed: %s\n", __func__, + __LINE__, ps3_result(result)); + result = -EPERM; + } + +done: + mutex_unlock(&usage_hack.mutex); + return result; +} + +static int ps3_close_hv_device_sb(struct ps3_system_bus_device *dev) +{ + int result; + + BUG_ON(!dev->bus_id); + mutex_lock(&usage_hack.mutex); + + if (ps3_is_device(dev, 1, 1)) { + usage_hack.sb_11--; + if (usage_hack.sb_11) { + result = 0; + goto done; + } + } + + if (ps3_is_device(dev, 1, 2)) { + usage_hack.sb_12--; + if (usage_hack.sb_12) { + result = 0; + goto done; + } + } + + result = lv1_close_device(dev->bus_id, dev->dev_id); + BUG_ON(result); + +done: + mutex_unlock(&usage_hack.mutex); + return result; +} + +static int ps3_open_hv_device_gpu(struct ps3_system_bus_device *dev) +{ + int result; + + mutex_lock(&usage_hack.mutex); + + usage_hack.gpu++; + if (usage_hack.gpu > 1) { + result = 0; + goto done; + } + + result = lv1_gpu_open(0); + + if (result) { + pr_debug("%s:%d: lv1_gpu_open failed: %s\n", __func__, + __LINE__, ps3_result(result)); + result = -EPERM; + } + +done: + mutex_unlock(&usage_hack.mutex); + return result; +} + +static int ps3_close_hv_device_gpu(struct ps3_system_bus_device *dev) +{ + int result; + + mutex_lock(&usage_hack.mutex); + + usage_hack.gpu--; + if (usage_hack.gpu) { + result = 0; + goto done; + } + + result = lv1_gpu_close(); + BUG_ON(result); + +done: + mutex_unlock(&usage_hack.mutex); + return result; +} + +int ps3_open_hv_device(struct ps3_system_bus_device *dev) +{ + BUG_ON(!dev); + pr_debug("%s:%d: match_id: %u\n", __func__, __LINE__, dev->match_id); + + switch (dev->match_id) { + case PS3_MATCH_ID_EHCI: + case PS3_MATCH_ID_OHCI: + case PS3_MATCH_ID_GELIC: + case PS3_MATCH_ID_STOR_DISK: + case PS3_MATCH_ID_STOR_ROM: + case PS3_MATCH_ID_STOR_FLASH: + return ps3_open_hv_device_sb(dev); + + case PS3_MATCH_ID_SOUND: + case PS3_MATCH_ID_GPU: + return ps3_open_hv_device_gpu(dev); + + case PS3_MATCH_ID_AV_SETTINGS: + case PS3_MATCH_ID_SYSTEM_MANAGER: + pr_debug("%s:%d: unsupported match_id: %u\n", __func__, + __LINE__, dev->match_id); + pr_debug("%s:%d: bus_id: %llu\n", __func__, __LINE__, + dev->bus_id); + BUG(); + return -EINVAL; + + default: + break; + } + + pr_debug("%s:%d: unknown match_id: %u\n", __func__, __LINE__, + dev->match_id); + BUG(); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(ps3_open_hv_device); + +int ps3_close_hv_device(struct ps3_system_bus_device *dev) +{ + BUG_ON(!dev); + pr_debug("%s:%d: match_id: %u\n", __func__, __LINE__, dev->match_id); + + switch (dev->match_id) { + case PS3_MATCH_ID_EHCI: + case PS3_MATCH_ID_OHCI: + case PS3_MATCH_ID_GELIC: + case PS3_MATCH_ID_STOR_DISK: + case PS3_MATCH_ID_STOR_ROM: + case PS3_MATCH_ID_STOR_FLASH: + return ps3_close_hv_device_sb(dev); + + case PS3_MATCH_ID_SOUND: + case PS3_MATCH_ID_GPU: + return ps3_close_hv_device_gpu(dev); + + case PS3_MATCH_ID_AV_SETTINGS: + case PS3_MATCH_ID_SYSTEM_MANAGER: + pr_debug("%s:%d: unsupported match_id: %u\n", __func__, + __LINE__, dev->match_id); + pr_debug("%s:%d: bus_id: %llu\n", __func__, __LINE__, + dev->bus_id); + BUG(); + return -EINVAL; + + default: + break; + } + + pr_debug("%s:%d: unknown match_id: %u\n", __func__, __LINE__, + dev->match_id); + BUG(); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(ps3_close_hv_device); + +#define dump_mmio_region(_a) _dump_mmio_region(_a, __func__, __LINE__) +static void _dump_mmio_region(const struct ps3_mmio_region* r, + const char* func, int line) +{ + pr_debug("%s:%d: dev %llu:%llu\n", func, line, r->dev->bus_id, + r->dev->dev_id); + pr_debug("%s:%d: bus_addr %lxh\n", func, line, r->bus_addr); + pr_debug("%s:%d: len %lxh\n", func, line, r->len); + pr_debug("%s:%d: lpar_addr %lxh\n", func, line, r->lpar_addr); +} + +static int ps3_sb_mmio_region_create(struct ps3_mmio_region *r) +{ + int result; + u64 lpar_addr; + + result = lv1_map_device_mmio_region(r->dev->bus_id, r->dev->dev_id, + r->bus_addr, r->len, r->page_size, &lpar_addr); + r->lpar_addr = lpar_addr; + + if (result) { + pr_debug("%s:%d: lv1_map_device_mmio_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + r->lpar_addr = 0; + } + + dump_mmio_region(r); + return result; +} + +static int ps3_ioc0_mmio_region_create(struct ps3_mmio_region *r) +{ + /* device specific; do nothing currently */ + return 0; +} + +int ps3_mmio_region_create(struct ps3_mmio_region *r) +{ + return r->mmio_ops->create(r); +} +EXPORT_SYMBOL_GPL(ps3_mmio_region_create); + +static int ps3_sb_free_mmio_region(struct ps3_mmio_region *r) +{ + int result; + + dump_mmio_region(r); + result = lv1_unmap_device_mmio_region(r->dev->bus_id, r->dev->dev_id, + r->lpar_addr); + + if (result) + pr_debug("%s:%d: lv1_unmap_device_mmio_region failed: %s\n", + __func__, __LINE__, ps3_result(result)); + + r->lpar_addr = 0; + return result; +} + +static int ps3_ioc0_free_mmio_region(struct ps3_mmio_region *r) +{ + /* device specific; do nothing currently */ + return 0; +} + + +int ps3_free_mmio_region(struct ps3_mmio_region *r) +{ + return r->mmio_ops->free(r); +} + +EXPORT_SYMBOL_GPL(ps3_free_mmio_region); + +static const struct ps3_mmio_region_ops ps3_mmio_sb_region_ops = { + .create = ps3_sb_mmio_region_create, + .free = ps3_sb_free_mmio_region +}; + +static const struct ps3_mmio_region_ops ps3_mmio_ioc0_region_ops = { + .create = ps3_ioc0_mmio_region_create, + .free = ps3_ioc0_free_mmio_region +}; + +int ps3_mmio_region_init(struct ps3_system_bus_device *dev, + struct ps3_mmio_region *r, unsigned long bus_addr, unsigned long len, + enum ps3_mmio_page_size page_size) +{ + r->dev = dev; + r->bus_addr = bus_addr; + r->len = len; + r->page_size = page_size; + switch (dev->dev_type) { + case PS3_DEVICE_TYPE_SB: + r->mmio_ops = &ps3_mmio_sb_region_ops; + break; + case PS3_DEVICE_TYPE_IOC0: + r->mmio_ops = &ps3_mmio_ioc0_region_ops; + break; + default: + BUG(); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(ps3_mmio_region_init); + +static int ps3_system_bus_match(struct device *_dev, + struct device_driver *_drv) +{ + int result; + struct ps3_system_bus_driver *drv = ps3_drv_to_system_bus_drv(_drv); + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + + if (!dev->match_sub_id) + result = dev->match_id == drv->match_id; + else + result = dev->match_sub_id == drv->match_sub_id && + dev->match_id == drv->match_id; + + if (result) + pr_info("%s:%d: dev=%u.%u(%s), drv=%u.%u(%s): match\n", + __func__, __LINE__, + dev->match_id, dev->match_sub_id, dev_name(&dev->core), + drv->match_id, drv->match_sub_id, drv->core.name); + else + pr_debug("%s:%d: dev=%u.%u(%s), drv=%u.%u(%s): miss\n", + __func__, __LINE__, + dev->match_id, dev->match_sub_id, dev_name(&dev->core), + drv->match_id, drv->match_sub_id, drv->core.name); + + return result; +} + +static int ps3_system_bus_probe(struct device *_dev) +{ + int result = 0; + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + struct ps3_system_bus_driver *drv; + + BUG_ON(!dev); + dev_dbg(_dev, "%s:%d\n", __func__, __LINE__); + + drv = ps3_system_bus_dev_to_system_bus_drv(dev); + BUG_ON(!drv); + + if (drv->probe) + result = drv->probe(dev); + else + pr_debug("%s:%d: %s no probe method\n", __func__, __LINE__, + dev_name(&dev->core)); + + pr_debug(" <- %s:%d: %s\n", __func__, __LINE__, dev_name(&dev->core)); + return result; +} + +static int ps3_system_bus_remove(struct device *_dev) +{ + int result = 0; + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + struct ps3_system_bus_driver *drv; + + BUG_ON(!dev); + dev_dbg(_dev, "%s:%d\n", __func__, __LINE__); + + drv = ps3_system_bus_dev_to_system_bus_drv(dev); + BUG_ON(!drv); + + if (drv->remove) + result = drv->remove(dev); + else + dev_dbg(&dev->core, "%s:%d %s: no remove method\n", + __func__, __LINE__, drv->core.name); + + pr_debug(" <- %s:%d: %s\n", __func__, __LINE__, dev_name(&dev->core)); + return result; +} + +static void ps3_system_bus_shutdown(struct device *_dev) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + struct ps3_system_bus_driver *drv; + + BUG_ON(!dev); + + dev_dbg(&dev->core, " -> %s:%d: match_id %d\n", __func__, __LINE__, + dev->match_id); + + if (!dev->core.driver) { + dev_dbg(&dev->core, "%s:%d: no driver bound\n", __func__, + __LINE__); + return; + } + + drv = ps3_system_bus_dev_to_system_bus_drv(dev); + + BUG_ON(!drv); + + dev_dbg(&dev->core, "%s:%d: %s -> %s\n", __func__, __LINE__, + dev_name(&dev->core), drv->core.name); + + if (drv->shutdown) + drv->shutdown(dev); + else if (drv->remove) { + dev_dbg(&dev->core, "%s:%d %s: no shutdown, calling remove\n", + __func__, __LINE__, drv->core.name); + drv->remove(dev); + } else { + dev_dbg(&dev->core, "%s:%d %s: no shutdown method\n", + __func__, __LINE__, drv->core.name); + BUG(); + } + + dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__); +} + +static int ps3_system_bus_uevent(struct device *_dev, struct kobj_uevent_env *env) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + + if (add_uevent_var(env, "MODALIAS=ps3:%d:%d", dev->match_id, + dev->match_sub_id)) + return -ENOMEM; + return 0; +} + +static ssize_t modalias_show(struct device *_dev, struct device_attribute *a, + char *buf) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + int len = snprintf(buf, PAGE_SIZE, "ps3:%d:%d\n", dev->match_id, + dev->match_sub_id); + + return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len; +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *ps3_system_bus_dev_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ps3_system_bus_dev); + +struct bus_type ps3_system_bus_type = { + .name = "ps3_system_bus", + .match = ps3_system_bus_match, + .uevent = ps3_system_bus_uevent, + .probe = ps3_system_bus_probe, + .remove = ps3_system_bus_remove, + .shutdown = ps3_system_bus_shutdown, + .dev_groups = ps3_system_bus_dev_groups, +}; + +static int __init ps3_system_bus_init(void) +{ + int result; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENODEV; + + pr_debug(" -> %s:%d\n", __func__, __LINE__); + + mutex_init(&usage_hack.mutex); + + result = device_register(&ps3_system_bus); + BUG_ON(result); + + result = bus_register(&ps3_system_bus_type); + BUG_ON(result); + + pr_debug(" <- %s:%d\n", __func__, __LINE__); + return result; +} + +core_initcall(ps3_system_bus_init); + +/* Allocates a contiguous real buffer and creates mappings over it. + * Returns the virtual address of the buffer and sets dma_handle + * to the dma address (mapping) of the first page. + */ +static void * ps3_alloc_coherent(struct device *_dev, size_t size, + dma_addr_t *dma_handle, gfp_t flag, + unsigned long attrs) +{ + int result; + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + unsigned long virt_addr; + + flag &= ~(__GFP_DMA | __GFP_HIGHMEM); + flag |= __GFP_ZERO; + + virt_addr = __get_free_pages(flag, get_order(size)); + + if (!virt_addr) { + pr_debug("%s:%d: get_free_pages failed\n", __func__, __LINE__); + goto clean_none; + } + + result = ps3_dma_map(dev->d_region, virt_addr, size, dma_handle, + CBE_IOPTE_PP_W | CBE_IOPTE_PP_R | + CBE_IOPTE_SO_RW | CBE_IOPTE_M); + + if (result) { + pr_debug("%s:%d: ps3_dma_map failed (%d)\n", + __func__, __LINE__, result); + BUG_ON("check region type"); + goto clean_alloc; + } + + return (void*)virt_addr; + +clean_alloc: + free_pages(virt_addr, get_order(size)); +clean_none: + dma_handle = NULL; + return NULL; +} + +static void ps3_free_coherent(struct device *_dev, size_t size, void *vaddr, + dma_addr_t dma_handle, unsigned long attrs) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + + ps3_dma_unmap(dev->d_region, dma_handle, size); + free_pages((unsigned long)vaddr, get_order(size)); +} + +/* Creates TCEs for a user provided buffer. The user buffer must be + * contiguous real kernel storage (not vmalloc). The address passed here + * comprises a page address and offset into that page. The dma_addr_t + * returned will point to the same byte within the page as was passed in. + */ + +static dma_addr_t ps3_sb_map_page(struct device *_dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction direction, + unsigned long attrs) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + int result; + dma_addr_t bus_addr; + void *ptr = page_address(page) + offset; + + result = ps3_dma_map(dev->d_region, (unsigned long)ptr, size, + &bus_addr, + CBE_IOPTE_PP_R | CBE_IOPTE_PP_W | + CBE_IOPTE_SO_RW | CBE_IOPTE_M); + + if (result) { + pr_debug("%s:%d: ps3_dma_map failed (%d)\n", + __func__, __LINE__, result); + } + + return bus_addr; +} + +static dma_addr_t ps3_ioc0_map_page(struct device *_dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction direction, + unsigned long attrs) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + int result; + dma_addr_t bus_addr; + u64 iopte_flag; + void *ptr = page_address(page) + offset; + + iopte_flag = CBE_IOPTE_M; + switch (direction) { + case DMA_BIDIRECTIONAL: + iopte_flag |= CBE_IOPTE_PP_R | CBE_IOPTE_PP_W | CBE_IOPTE_SO_RW; + break; + case DMA_TO_DEVICE: + iopte_flag |= CBE_IOPTE_PP_R | CBE_IOPTE_SO_R; + break; + case DMA_FROM_DEVICE: + iopte_flag |= CBE_IOPTE_PP_W | CBE_IOPTE_SO_RW; + break; + default: + /* not happned */ + BUG(); + }; + result = ps3_dma_map(dev->d_region, (unsigned long)ptr, size, + &bus_addr, iopte_flag); + + if (result) { + pr_debug("%s:%d: ps3_dma_map failed (%d)\n", + __func__, __LINE__, result); + } + return bus_addr; +} + +static void ps3_unmap_page(struct device *_dev, dma_addr_t dma_addr, + size_t size, enum dma_data_direction direction, unsigned long attrs) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + int result; + + result = ps3_dma_unmap(dev->d_region, dma_addr, size); + + if (result) { + pr_debug("%s:%d: ps3_dma_unmap failed (%d)\n", + __func__, __LINE__, result); + } +} + +static int ps3_sb_map_sg(struct device *_dev, struct scatterlist *sgl, + int nents, enum dma_data_direction direction, unsigned long attrs) +{ +#if defined(CONFIG_PS3_DYNAMIC_DMA) + BUG_ON("do"); + return -EPERM; +#else + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + struct scatterlist *sg; + int i; + + for_each_sg(sgl, sg, nents, i) { + int result = ps3_dma_map(dev->d_region, sg_phys(sg), + sg->length, &sg->dma_address, 0); + + if (result) { + pr_debug("%s:%d: ps3_dma_map failed (%d)\n", + __func__, __LINE__, result); + return -EINVAL; + } + + sg->dma_length = sg->length; + } + + return nents; +#endif +} + +static int ps3_ioc0_map_sg(struct device *_dev, struct scatterlist *sg, + int nents, + enum dma_data_direction direction, + unsigned long attrs) +{ + BUG(); + return 0; +} + +static void ps3_sb_unmap_sg(struct device *_dev, struct scatterlist *sg, + int nents, enum dma_data_direction direction, unsigned long attrs) +{ +#if defined(CONFIG_PS3_DYNAMIC_DMA) + BUG_ON("do"); +#endif +} + +static void ps3_ioc0_unmap_sg(struct device *_dev, struct scatterlist *sg, + int nents, enum dma_data_direction direction, + unsigned long attrs) +{ + BUG(); +} + +static int ps3_dma_supported(struct device *_dev, u64 mask) +{ + return mask >= DMA_BIT_MASK(32); +} + +static const struct dma_map_ops ps3_sb_dma_ops = { + .alloc = ps3_alloc_coherent, + .free = ps3_free_coherent, + .map_sg = ps3_sb_map_sg, + .unmap_sg = ps3_sb_unmap_sg, + .dma_supported = ps3_dma_supported, + .map_page = ps3_sb_map_page, + .unmap_page = ps3_unmap_page, + .mmap = dma_common_mmap, + .get_sgtable = dma_common_get_sgtable, + .alloc_pages = dma_common_alloc_pages, + .free_pages = dma_common_free_pages, +}; + +static const struct dma_map_ops ps3_ioc0_dma_ops = { + .alloc = ps3_alloc_coherent, + .free = ps3_free_coherent, + .map_sg = ps3_ioc0_map_sg, + .unmap_sg = ps3_ioc0_unmap_sg, + .dma_supported = ps3_dma_supported, + .map_page = ps3_ioc0_map_page, + .unmap_page = ps3_unmap_page, + .mmap = dma_common_mmap, + .get_sgtable = dma_common_get_sgtable, + .alloc_pages = dma_common_alloc_pages, + .free_pages = dma_common_free_pages, +}; + +/** + * ps3_system_bus_release_device - remove a device from the system bus + */ + +static void ps3_system_bus_release_device(struct device *_dev) +{ + struct ps3_system_bus_device *dev = ps3_dev_to_system_bus_dev(_dev); + kfree(dev); +} + +/** + * ps3_system_bus_device_register - add a device to the system bus + * + * ps3_system_bus_device_register() expects the dev object to be allocated + * dynamically by the caller. The system bus takes ownership of the dev + * object and frees the object in ps3_system_bus_release_device(). + */ + +int ps3_system_bus_device_register(struct ps3_system_bus_device *dev) +{ + int result; + static unsigned int dev_ioc0_count; + static unsigned int dev_sb_count; + static unsigned int dev_vuart_count; + static unsigned int dev_lpm_count; + + if (!dev->core.parent) + dev->core.parent = &ps3_system_bus; + dev->core.bus = &ps3_system_bus_type; + dev->core.release = ps3_system_bus_release_device; + + switch (dev->dev_type) { + case PS3_DEVICE_TYPE_IOC0: + dev->core.dma_ops = &ps3_ioc0_dma_ops; + dev_set_name(&dev->core, "ioc0_%02x", ++dev_ioc0_count); + break; + case PS3_DEVICE_TYPE_SB: + dev->core.dma_ops = &ps3_sb_dma_ops; + dev_set_name(&dev->core, "sb_%02x", ++dev_sb_count); + + break; + case PS3_DEVICE_TYPE_VUART: + dev_set_name(&dev->core, "vuart_%02x", ++dev_vuart_count); + break; + case PS3_DEVICE_TYPE_LPM: + dev_set_name(&dev->core, "lpm_%02x", ++dev_lpm_count); + break; + default: + BUG(); + }; + + dev->core.of_node = NULL; + set_dev_node(&dev->core, 0); + + pr_debug("%s:%d add %s\n", __func__, __LINE__, dev_name(&dev->core)); + + result = device_register(&dev->core); + return result; +} + +EXPORT_SYMBOL_GPL(ps3_system_bus_device_register); + +int ps3_system_bus_driver_register(struct ps3_system_bus_driver *drv) +{ + int result; + + pr_debug(" -> %s:%d: %s\n", __func__, __LINE__, drv->core.name); + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENODEV; + + drv->core.bus = &ps3_system_bus_type; + + result = driver_register(&drv->core); + pr_debug(" <- %s:%d: %s\n", __func__, __LINE__, drv->core.name); + return result; +} + +EXPORT_SYMBOL_GPL(ps3_system_bus_driver_register); + +void ps3_system_bus_driver_unregister(struct ps3_system_bus_driver *drv) +{ + pr_debug(" -> %s:%d: %s\n", __func__, __LINE__, drv->core.name); + driver_unregister(&drv->core); + pr_debug(" <- %s:%d: %s\n", __func__, __LINE__, drv->core.name); +} + +EXPORT_SYMBOL_GPL(ps3_system_bus_driver_unregister); diff --git a/arch/powerpc/platforms/ps3/time.c b/arch/powerpc/platforms/ps3/time.c new file mode 100644 index 000000000..c9bfc113a --- /dev/null +++ b/arch/powerpc/platforms/ps3/time.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PS3 time and rtc routines. + * + * Copyright (C) 2006 Sony Computer Entertainment Inc. + * Copyright 2006 Sony Corp. + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> + +#include <asm/firmware.h> +#include <asm/lv1call.h> +#include <asm/ps3.h> + +#include "platform.h" + +void __init ps3_calibrate_decr(void) +{ + int result; + u64 tmp; + + result = ps3_repository_read_be_tb_freq(0, &tmp); + BUG_ON(result); + + ppc_tb_freq = tmp; + ppc_proc_freq = ppc_tb_freq * 40; +} + +static u64 read_rtc(void) +{ + int result; + u64 rtc_val; + u64 tb_val; + + result = lv1_get_rtc(&rtc_val, &tb_val); + BUG_ON(result); + + return rtc_val; +} + +time64_t __init ps3_get_boot_time(void) +{ + return read_rtc() + ps3_os_area_get_rtc_diff(); +} + +static int __init ps3_rtc_init(void) +{ + struct platform_device *pdev; + + if (!firmware_has_feature(FW_FEATURE_PS3_LV1)) + return -ENODEV; + + pdev = platform_device_register_simple("rtc-ps3", -1, NULL, 0); + + return PTR_ERR_OR_ZERO(pdev); +} +device_initcall(ps3_rtc_init); |