1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/gpu/drm/imagination/pvr_vm_mips.c
Donald Robson b39610c773
drm/imagination: Fixed infinite loop in pvr_vm_mips_map()
Unwinding loop in error path for this function uses unsigned limit
variable, causing the promotion of the signed counter variable.

--> 204         for (; pfn >= start_pfn; pfn--)
                       ^^^^^^^^^^^^^^^^
If start_pfn can be zero then this is an endless loop.  I've seen this
code in other places as well.  This loop is slightly off as well.  It
should decrement pfn on the first iteration.

Fix by making the loop limit variables signed. Also fix missing
predecrement by modifying to while loop.

Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
Signed-off-by: Donald Robson <donald.robson@imgtec.com>
Signed-off-by: Maxime Ripard <mripard@kernel.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20231208160825.92933-1-donald.robson@imgtec.com
2023-12-15 14:03:57 +01:00

237 lines
6.5 KiB
C

// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright (c) 2023 Imagination Technologies Ltd. */
#include "pvr_device.h"
#include "pvr_fw_mips.h"
#include "pvr_gem.h"
#include "pvr_mmu.h"
#include "pvr_rogue_mips.h"
#include "pvr_vm.h"
#include "pvr_vm_mips.h"
#include <drm/drm_managed.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/types.h>
/**
* pvr_vm_mips_init() - Initialise MIPS FW pagetable
* @pvr_dev: Target PowerVR device.
*
* Returns:
* * 0 on success,
* * -%EINVAL,
* * Any error returned by pvr_gem_object_create(), or
* * And error returned by pvr_gem_object_vmap().
*/
int
pvr_vm_mips_init(struct pvr_device *pvr_dev)
{
u32 pt_size = 1 << ROGUE_MIPSFW_LOG2_PAGETABLE_SIZE_4K(pvr_dev);
struct device *dev = from_pvr_device(pvr_dev)->dev;
struct pvr_fw_mips_data *mips_data;
u32 phys_bus_width;
int page_nr;
int err;
/* Page table size must be at most ROGUE_MIPSFW_MAX_NUM_PAGETABLE_PAGES * 4k pages. */
if (pt_size > ROGUE_MIPSFW_MAX_NUM_PAGETABLE_PAGES * SZ_4K)
return -EINVAL;
if (PVR_FEATURE_VALUE(pvr_dev, phys_bus_width, &phys_bus_width))
return -EINVAL;
mips_data = drmm_kzalloc(from_pvr_device(pvr_dev), sizeof(*mips_data), GFP_KERNEL);
if (!mips_data)
return -ENOMEM;
for (page_nr = 0; page_nr < ARRAY_SIZE(mips_data->pt_pages); page_nr++) {
mips_data->pt_pages[page_nr] = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!mips_data->pt_pages[page_nr]) {
err = -ENOMEM;
goto err_free_pages;
}
mips_data->pt_dma_addr[page_nr] = dma_map_page(dev, mips_data->pt_pages[page_nr], 0,
PAGE_SIZE, DMA_TO_DEVICE);
if (dma_mapping_error(dev, mips_data->pt_dma_addr[page_nr])) {
err = -ENOMEM;
__free_page(mips_data->pt_pages[page_nr]);
goto err_free_pages;
}
}
mips_data->pt = vmap(mips_data->pt_pages, pt_size >> PAGE_SHIFT, VM_MAP,
pgprot_writecombine(PAGE_KERNEL));
if (!mips_data->pt) {
err = -ENOMEM;
goto err_free_pages;
}
mips_data->pfn_mask = (phys_bus_width > 32) ? ROGUE_MIPSFW_ENTRYLO_PFN_MASK_ABOVE_32BIT :
ROGUE_MIPSFW_ENTRYLO_PFN_MASK;
mips_data->cache_policy = (phys_bus_width > 32) ? ROGUE_MIPSFW_CACHED_POLICY_ABOVE_32BIT :
ROGUE_MIPSFW_CACHED_POLICY;
pvr_dev->fw_dev.processor_data.mips_data = mips_data;
return 0;
err_free_pages:
while (--page_nr >= 0) {
dma_unmap_page(from_pvr_device(pvr_dev)->dev,
mips_data->pt_dma_addr[page_nr], PAGE_SIZE, DMA_TO_DEVICE);
__free_page(mips_data->pt_pages[page_nr]);
}
return err;
}
/**
* pvr_vm_mips_fini() - Release MIPS FW pagetable
* @pvr_dev: Target PowerVR device.
*/
void
pvr_vm_mips_fini(struct pvr_device *pvr_dev)
{
struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev;
struct pvr_fw_mips_data *mips_data = fw_dev->processor_data.mips_data;
int page_nr;
vunmap(mips_data->pt);
for (page_nr = ARRAY_SIZE(mips_data->pt_pages) - 1; page_nr >= 0; page_nr--) {
dma_unmap_page(from_pvr_device(pvr_dev)->dev,
mips_data->pt_dma_addr[page_nr], PAGE_SIZE, DMA_TO_DEVICE);
__free_page(mips_data->pt_pages[page_nr]);
}
fw_dev->processor_data.mips_data = NULL;
}
static u32
get_mips_pte_flags(bool read, bool write, u32 cache_policy)
{
u32 flags = 0;
if (read && write) /* Read/write. */
flags |= ROGUE_MIPSFW_ENTRYLO_DIRTY_EN;
else if (write) /* Write only. */
flags |= ROGUE_MIPSFW_ENTRYLO_READ_INHIBIT_EN;
else
WARN_ON(!read);
flags |= cache_policy << ROGUE_MIPSFW_ENTRYLO_CACHE_POLICY_SHIFT;
flags |= ROGUE_MIPSFW_ENTRYLO_VALID_EN | ROGUE_MIPSFW_ENTRYLO_GLOBAL_EN;
return flags;
}
/**
* pvr_vm_mips_map() - Map a FW object into MIPS address space
* @pvr_dev: Target PowerVR device.
* @fw_obj: FW object to map.
*
* Returns:
* * 0 on success,
* * -%EINVAL if object does not reside within FW address space, or
* * Any error returned by pvr_fw_object_get_dma_addr().
*/
int
pvr_vm_mips_map(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj)
{
struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev;
struct pvr_fw_mips_data *mips_data = fw_dev->processor_data.mips_data;
struct pvr_gem_object *pvr_obj = fw_obj->gem;
const u64 start = fw_obj->fw_mm_node.start;
const u64 size = fw_obj->fw_mm_node.size;
u64 end;
u32 cache_policy;
u32 pte_flags;
s32 start_pfn;
s32 end_pfn;
s32 pfn;
int err;
if (check_add_overflow(start, size - 1, &end))
return -EINVAL;
if (start < ROGUE_FW_HEAP_BASE ||
start >= ROGUE_FW_HEAP_BASE + fw_dev->fw_heap_info.raw_size ||
end < ROGUE_FW_HEAP_BASE ||
end >= ROGUE_FW_HEAP_BASE + fw_dev->fw_heap_info.raw_size ||
(start & ROGUE_MIPSFW_PAGE_MASK_4K) ||
((end + 1) & ROGUE_MIPSFW_PAGE_MASK_4K))
return -EINVAL;
start_pfn = (start & fw_dev->fw_heap_info.offset_mask) >> ROGUE_MIPSFW_LOG2_PAGE_SIZE_4K;
end_pfn = (end & fw_dev->fw_heap_info.offset_mask) >> ROGUE_MIPSFW_LOG2_PAGE_SIZE_4K;
if (pvr_obj->flags & PVR_BO_FW_FLAGS_DEVICE_UNCACHED)
cache_policy = ROGUE_MIPSFW_UNCACHED_CACHE_POLICY;
else
cache_policy = mips_data->cache_policy;
pte_flags = get_mips_pte_flags(true, true, cache_policy);
for (pfn = start_pfn; pfn <= end_pfn; pfn++) {
dma_addr_t dma_addr;
u32 pte;
err = pvr_fw_object_get_dma_addr(fw_obj,
(pfn - start_pfn) <<
ROGUE_MIPSFW_LOG2_PAGE_SIZE_4K,
&dma_addr);
if (err)
goto err_unmap_pages;
pte = ((dma_addr >> ROGUE_MIPSFW_LOG2_PAGE_SIZE_4K)
<< ROGUE_MIPSFW_ENTRYLO_PFN_SHIFT) & mips_data->pfn_mask;
pte |= pte_flags;
WRITE_ONCE(mips_data->pt[pfn], pte);
}
pvr_mmu_flush_request_all(pvr_dev);
return 0;
err_unmap_pages:
while (--pfn >= start_pfn)
WRITE_ONCE(mips_data->pt[pfn], 0);
pvr_mmu_flush_request_all(pvr_dev);
WARN_ON(pvr_mmu_flush_exec(pvr_dev, true));
return err;
}
/**
* pvr_vm_mips_unmap() - Unmap a FW object into MIPS address space
* @pvr_dev: Target PowerVR device.
* @fw_obj: FW object to unmap.
*/
void
pvr_vm_mips_unmap(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj)
{
struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev;
struct pvr_fw_mips_data *mips_data = fw_dev->processor_data.mips_data;
const u64 start = fw_obj->fw_mm_node.start;
const u64 size = fw_obj->fw_mm_node.size;
const u64 end = start + size;
const u32 start_pfn = (start & fw_dev->fw_heap_info.offset_mask) >>
ROGUE_MIPSFW_LOG2_PAGE_SIZE_4K;
const u32 end_pfn = (end & fw_dev->fw_heap_info.offset_mask) >>
ROGUE_MIPSFW_LOG2_PAGE_SIZE_4K;
for (u32 pfn = start_pfn; pfn < end_pfn; pfn++)
WRITE_ONCE(mips_data->pt[pfn], 0);
pvr_mmu_flush_request_all(pvr_dev);
WARN_ON(pvr_mmu_flush_exec(pvr_dev, true));
}