forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
drm/imagination: Implement MIPS firmware processor and MMU support
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
1 parent
cc1aeed
commit 927f3e0
Showing
8 changed files
with
572 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |
Oops, something went wrong.