Skip to content

Commit

Permalink
drm/imagination: Implement MIPS firmware processor and MMU support
Browse files Browse the repository at this point in the history
Add support for the MIPS firmware processor, used in the Series AXE GPU.
The MIPS firmware processor uses a separate MMU to the rest of the GPU, so
this patch adds support for that as well.

Changes since v8:
- Corrected license identifiers

Changes since v6:
- Fix integer overflow in VM map error path

Changes since v5:
- Use alloc_page() when allocating MIPS pagetable

Changes since v3:
- Get regs resource (removed from GPU resources commit)

Signed-off-by: Sarah Walker <sarah.walker@imgtec.com>
Signed-off-by: Donald Robson <donald.robson@imgtec.com>
Link: https://lore.kernel.org/r/a114f7b3e97cb07460c7f2842901716a9207b0c4.1700668843.git.donald.robson@imgtec.com
Signed-off-by: Maxime Ripard <mripard@kernel.org>
  • Loading branch information
sarah-walker-imgtec authored and mripard committed Nov 23, 2023
1 parent cc1aeed commit 927f3e0
Show file tree
Hide file tree
Showing 8 changed files with 572 additions and 2 deletions.
4 changes: 3 additions & 1 deletion drivers/gpu/drm/imagination/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ powervr-y := \
pvr_drv.o \
pvr_fw.o \
pvr_fw_meta.o \
pvr_fw_mips.o \
pvr_fw_startstop.o \
pvr_fw_trace.o \
pvr_gem.o \
pvr_mmu.o \
pvr_power.o \
pvr_vm.o
pvr_vm.o \
pvr_vm_mips.o

obj-$(CONFIG_DRM_POWERVR) += powervr.o
5 changes: 4 additions & 1 deletion drivers/gpu/drm/imagination/pvr_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,19 @@ pvr_device_reg_init(struct pvr_device *pvr_dev)
{
struct drm_device *drm_dev = from_pvr_device(pvr_dev);
struct platform_device *plat_dev = to_platform_device(drm_dev->dev);
struct resource *regs_resource;
void __iomem *regs;

pvr_dev->regs_resource = NULL;
pvr_dev->regs = NULL;

regs = devm_platform_ioremap_resource(plat_dev, 0);
regs = devm_platform_get_and_ioremap_resource(plat_dev, 0, &regs_resource);
if (IS_ERR(regs))
return dev_err_probe(drm_dev->dev, PTR_ERR(regs),
"failed to ioremap gpu registers\n");

pvr_dev->regs = regs;
pvr_dev->regs_resource = regs_resource;

return 0;
}
Expand Down
3 changes: 3 additions & 0 deletions drivers/gpu/drm/imagination/pvr_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ struct pvr_device {
/** @fw_version: Firmware version detected at runtime. */
struct pvr_fw_version fw_version;

/** @regs_resource: Resource representing device control registers. */
struct resource *regs_resource;

/**
* @regs: Device control registers.
*
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/imagination/pvr_fw.c
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,8 @@ pvr_fw_init(struct pvr_device *pvr_dev)

if (fw_dev->processor_type == PVR_FW_PROCESSOR_TYPE_META)
fw_dev->defs = &pvr_fw_defs_meta;
else if (fw_dev->processor_type == PVR_FW_PROCESSOR_TYPE_MIPS)
fw_dev->defs = &pvr_fw_defs_mips;
else
return -EINVAL;

Expand Down
252 changes: 252 additions & 0 deletions drivers/gpu/drm/imagination/pvr_fw_mips.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright (c) 2023 Imagination Technologies Ltd. */

#include "pvr_device.h"
#include "pvr_fw.h"
#include "pvr_fw_mips.h"
#include "pvr_gem.h"
#include "pvr_rogue_mips.h"
#include "pvr_vm_mips.h"

#include <linux/elf.h>
#include <linux/err.h>
#include <linux/types.h>

#define ROGUE_FW_HEAP_MIPS_BASE 0xC0000000
#define ROGUE_FW_HEAP_MIPS_SHIFT 24 /* 16 MB */
#define ROGUE_FW_HEAP_MIPS_RESERVED_SIZE SZ_1M

/**
* process_elf_command_stream() - Process ELF firmware image and populate
* firmware sections
* @pvr_dev: Device pointer.
* @fw: Pointer to firmware image.
* @fw_code_ptr: Pointer to FW code section.
* @fw_data_ptr: Pointer to FW data section.
* @fw_core_code_ptr: Pointer to FW coremem code section.
* @fw_core_data_ptr: Pointer to FW coremem data section.
*
* Returns :
* * 0 on success, or
* * -EINVAL on any error in ELF command stream.
*/
static int
process_elf_command_stream(struct pvr_device *pvr_dev, const u8 *fw, u8 *fw_code_ptr,
u8 *fw_data_ptr, u8 *fw_core_code_ptr, u8 *fw_core_data_ptr)
{
struct elf32_hdr *header = (struct elf32_hdr *)fw;
struct elf32_phdr *program_header = (struct elf32_phdr *)(fw + header->e_phoff);
struct drm_device *drm_dev = from_pvr_device(pvr_dev);
u32 entry;
int err;

for (entry = 0; entry < header->e_phnum; entry++, program_header++) {
void *write_addr;

/* Only consider loadable entries in the ELF segment table */
if (program_header->p_type != PT_LOAD)
continue;

err = pvr_fw_find_mmu_segment(pvr_dev, program_header->p_vaddr,
program_header->p_memsz, fw_code_ptr, fw_data_ptr,
fw_core_code_ptr, fw_core_data_ptr, &write_addr);
if (err) {
drm_err(drm_dev,
"Addr 0x%x (size: %d) not found in any firmware segment",
program_header->p_vaddr, program_header->p_memsz);
return err;
}

/* Write to FW allocation only if available */
if (write_addr) {
memcpy(write_addr, fw + program_header->p_offset,
program_header->p_filesz);

memset((u8 *)write_addr + program_header->p_filesz, 0,
program_header->p_memsz - program_header->p_filesz);
}
}

return 0;
}

static int
pvr_mips_init(struct pvr_device *pvr_dev)
{
pvr_fw_heap_info_init(pvr_dev, ROGUE_FW_HEAP_MIPS_SHIFT, ROGUE_FW_HEAP_MIPS_RESERVED_SIZE);

return pvr_vm_mips_init(pvr_dev);
}

static void
pvr_mips_fini(struct pvr_device *pvr_dev)
{
pvr_vm_mips_fini(pvr_dev);
}

static int
pvr_mips_fw_process(struct pvr_device *pvr_dev, const u8 *fw,
u8 *fw_code_ptr, u8 *fw_data_ptr, u8 *fw_core_code_ptr, u8 *fw_core_data_ptr,
u32 core_code_alloc_size)
{
struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev;
struct pvr_fw_mips_data *mips_data = fw_dev->processor_data.mips_data;
const struct pvr_fw_layout_entry *boot_code_entry;
const struct pvr_fw_layout_entry *boot_data_entry;
const struct pvr_fw_layout_entry *exception_code_entry;
const struct pvr_fw_layout_entry *stack_entry;
struct rogue_mipsfw_boot_data *boot_data;
dma_addr_t dma_addr;
u32 page_nr;
int err;

err = process_elf_command_stream(pvr_dev, fw, fw_code_ptr, fw_data_ptr, fw_core_code_ptr,
fw_core_data_ptr);
if (err)
return err;

boot_code_entry = pvr_fw_find_layout_entry(pvr_dev, MIPS_BOOT_CODE);
boot_data_entry = pvr_fw_find_layout_entry(pvr_dev, MIPS_BOOT_DATA);
exception_code_entry = pvr_fw_find_layout_entry(pvr_dev, MIPS_EXCEPTIONS_CODE);
if (!boot_code_entry || !boot_data_entry || !exception_code_entry)
return -EINVAL;

WARN_ON(pvr_gem_get_dma_addr(fw_dev->mem.code_obj->gem, boot_code_entry->alloc_offset,
&mips_data->boot_code_dma_addr));
WARN_ON(pvr_gem_get_dma_addr(fw_dev->mem.data_obj->gem, boot_data_entry->alloc_offset,
&mips_data->boot_data_dma_addr));
WARN_ON(pvr_gem_get_dma_addr(fw_dev->mem.code_obj->gem,
exception_code_entry->alloc_offset,
&mips_data->exception_code_dma_addr));

stack_entry = pvr_fw_find_layout_entry(pvr_dev, MIPS_STACK);
if (!stack_entry)
return -EINVAL;

boot_data = (struct rogue_mipsfw_boot_data *)(fw_data_ptr + boot_data_entry->alloc_offset +
ROGUE_MIPSFW_BOOTLDR_CONF_OFFSET);

WARN_ON(pvr_fw_object_get_dma_addr(fw_dev->mem.data_obj, stack_entry->alloc_offset,
&dma_addr));
boot_data->stack_phys_addr = dma_addr;

boot_data->reg_base = pvr_dev->regs_resource->start;

for (page_nr = 0; page_nr < ARRAY_SIZE(boot_data->pt_phys_addr); page_nr++) {
/* Firmware expects 4k pages, but host page size might be different. */
u32 src_page_nr = (page_nr * ROGUE_MIPSFW_PAGE_SIZE_4K) >> PAGE_SHIFT;
u32 page_offset = (page_nr * ROGUE_MIPSFW_PAGE_SIZE_4K) & ~PAGE_MASK;

boot_data->pt_phys_addr[page_nr] = mips_data->pt_dma_addr[src_page_nr] +
page_offset;
}

boot_data->pt_log2_page_size = ROGUE_MIPSFW_LOG2_PAGE_SIZE_4K;
boot_data->pt_num_pages = ROGUE_MIPSFW_MAX_NUM_PAGETABLE_PAGES;
boot_data->reserved1 = 0;
boot_data->reserved2 = 0;

return 0;
}

static int
pvr_mips_wrapper_init(struct pvr_device *pvr_dev)
{
struct pvr_fw_mips_data *mips_data = pvr_dev->fw_dev.processor_data.mips_data;
const u64 remap_settings = ROGUE_MIPSFW_BOOT_REMAP_LOG2_SEGMENT_SIZE;
u32 phys_bus_width;

int err = PVR_FEATURE_VALUE(pvr_dev, phys_bus_width, &phys_bus_width);

if (WARN_ON(err))
return err;

/* Currently MIPS FW only supported with physical bus width > 32 bits. */
if (WARN_ON(phys_bus_width <= 32))
return -EINVAL;

pvr_cr_write32(pvr_dev, ROGUE_CR_MIPS_WRAPPER_CONFIG,
(ROGUE_MIPSFW_REGISTERS_VIRTUAL_BASE >>
ROGUE_MIPSFW_WRAPPER_CONFIG_REGBANK_ADDR_ALIGN) |
ROGUE_CR_MIPS_WRAPPER_CONFIG_BOOT_ISA_MODE_MICROMIPS);

/* Configure remap for boot code, boot data and exceptions code areas. */
pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP1_CONFIG1,
ROGUE_MIPSFW_BOOT_REMAP_PHYS_ADDR_IN |
ROGUE_CR_MIPS_ADDR_REMAP1_CONFIG1_MODE_ENABLE_EN);
pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP1_CONFIG2,
(mips_data->boot_code_dma_addr &
~ROGUE_CR_MIPS_ADDR_REMAP1_CONFIG2_ADDR_OUT_CLRMSK) | remap_settings);

if (PVR_HAS_QUIRK(pvr_dev, 63553)) {
/*
* WA always required on 36 bit cores, to avoid continuous unmapped memory accesses
* to address 0x0.
*/
WARN_ON(phys_bus_width != 36);

pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP5_CONFIG1,
ROGUE_CR_MIPS_ADDR_REMAP5_CONFIG1_MODE_ENABLE_EN);
pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP5_CONFIG2,
(mips_data->boot_code_dma_addr &
~ROGUE_CR_MIPS_ADDR_REMAP5_CONFIG2_ADDR_OUT_CLRMSK) |
remap_settings);
}

pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP2_CONFIG1,
ROGUE_MIPSFW_DATA_REMAP_PHYS_ADDR_IN |
ROGUE_CR_MIPS_ADDR_REMAP2_CONFIG1_MODE_ENABLE_EN);
pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP2_CONFIG2,
(mips_data->boot_data_dma_addr &
~ROGUE_CR_MIPS_ADDR_REMAP2_CONFIG2_ADDR_OUT_CLRMSK) | remap_settings);

pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP3_CONFIG1,
ROGUE_MIPSFW_CODE_REMAP_PHYS_ADDR_IN |
ROGUE_CR_MIPS_ADDR_REMAP3_CONFIG1_MODE_ENABLE_EN);
pvr_cr_write64(pvr_dev, ROGUE_CR_MIPS_ADDR_REMAP3_CONFIG2,
(mips_data->exception_code_dma_addr &
~ROGUE_CR_MIPS_ADDR_REMAP3_CONFIG2_ADDR_OUT_CLRMSK) | remap_settings);

/* Garten IDLE bit controlled by MIPS. */
pvr_cr_write64(pvr_dev, ROGUE_CR_MTS_GARTEN_WRAPPER_CONFIG,
ROGUE_CR_MTS_GARTEN_WRAPPER_CONFIG_IDLE_CTRL_META);

/* Turn on the EJTAG probe. */
pvr_cr_write32(pvr_dev, ROGUE_CR_MIPS_DEBUG_CONFIG, 0);

return 0;
}

static u32
pvr_mips_get_fw_addr_with_offset(struct pvr_fw_object *fw_obj, u32 offset)
{
struct pvr_device *pvr_dev = to_pvr_device(gem_from_pvr_gem(fw_obj->gem)->dev);

/* MIPS cacheability is determined by page table. */
return ((fw_obj->fw_addr_offset + offset) & pvr_dev->fw_dev.fw_heap_info.offset_mask) |
ROGUE_FW_HEAP_MIPS_BASE;
}

static bool
pvr_mips_has_fixed_data_addr(void)
{
return true;
}

const struct pvr_fw_defs pvr_fw_defs_mips = {
.init = pvr_mips_init,
.fini = pvr_mips_fini,
.fw_process = pvr_mips_fw_process,
.vm_map = pvr_vm_mips_map,
.vm_unmap = pvr_vm_mips_unmap,
.get_fw_addr_with_offset = pvr_mips_get_fw_addr_with_offset,
.wrapper_init = pvr_mips_wrapper_init,
.has_fixed_data_addr = pvr_mips_has_fixed_data_addr,
.irq = {
.enable_reg = ROGUE_CR_MIPS_WRAPPER_IRQ_ENABLE,
.status_reg = ROGUE_CR_MIPS_WRAPPER_IRQ_STATUS,
.clear_reg = ROGUE_CR_MIPS_WRAPPER_IRQ_CLEAR,
.event_mask = ROGUE_CR_MIPS_WRAPPER_IRQ_STATUS_EVENT_EN,
.clear_mask = ROGUE_CR_MIPS_WRAPPER_IRQ_CLEAR_EVENT_EN,
},
};
48 changes: 48 additions & 0 deletions drivers/gpu/drm/imagination/pvr_fw_mips.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
/* Copyright (c) 2023 Imagination Technologies Ltd. */

#ifndef PVR_FW_MIPS_H
#define PVR_FW_MIPS_H

#include "pvr_rogue_mips.h"

#include <asm/page.h>
#include <linux/types.h>

/* Forward declaration from pvr_gem.h. */
struct pvr_gem_object;

#define PVR_MIPS_PT_PAGE_COUNT ((ROGUE_MIPSFW_MAX_NUM_PAGETABLE_PAGES * ROGUE_MIPSFW_PAGE_SIZE_4K) \
>> PAGE_SHIFT)
/**
* struct pvr_fw_mips_data - MIPS-specific data
*/
struct pvr_fw_mips_data {
/**
* @pt_pages: Pages containing MIPS pagetable.
*/
struct page *pt_pages[PVR_MIPS_PT_PAGE_COUNT];

/** @pt: Pointer to CPU mapping of MIPS pagetable. */
u32 *pt;

/** @pt_dma_addr: DMA mappings of MIPS pagetable. */
dma_addr_t pt_dma_addr[PVR_MIPS_PT_PAGE_COUNT];

/** @boot_code_dma_addr: DMA address of MIPS boot code. */
dma_addr_t boot_code_dma_addr;

/** @boot_data_dma_addr: DMA address of MIPS boot data. */
dma_addr_t boot_data_dma_addr;

/** @exception_code_dma_addr: DMA address of MIPS exception code. */
dma_addr_t exception_code_dma_addr;

/** @cache_policy: Cache policy for this processor. */
u32 cache_policy;

/** @pfn_mask: PFN mask for MIPS pagetable. */
u32 pfn_mask;
};

#endif /* PVR_FW_MIPS_H */
Loading

0 comments on commit 927f3e0

Please sign in to comment.