mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-09-04 20:19:47 +08:00
drm/imagination: Add RISC-V firmware processor support
Newer PowerVR GPUs (such as the BXS-4-64 MC1) use a RISC-V firmware processor instead of the previous MIPS or META. The current version of this patch depends on a patch[1] which exists in drm-misc-fixes, but has not yet made it back to drm-misc-next (the target of this patch). That patch adds the function pvr_vm_unmap_obj() which is used here. [1]: https://lore.kernel.org/r/20250226-hold-drm_gem_gpuva-lock-for-unmap-v2-1-3fdacded227f@imgtec.com Signed-off-by: Sarah Walker <sarah.walker@imgtec.com> Reviewed-by: Frank Binns <frank.binns@imgtec.com> Link: https://lore.kernel.org/r/20250410-sets-bxs-4-64-patch-v1-v6-14-eda620c5865f@imgtec.com Signed-off-by: Matt Coster <matt.coster@imgtec.com>
This commit is contained in:
parent
f48485ab50
commit
171f378d2a
@ -12,6 +12,7 @@ powervr-y := \
|
|||||||
pvr_fw.o \
|
pvr_fw.o \
|
||||||
pvr_fw_meta.o \
|
pvr_fw_meta.o \
|
||||||
pvr_fw_mips.o \
|
pvr_fw_mips.o \
|
||||||
|
pvr_fw_riscv.o \
|
||||||
pvr_fw_startstop.o \
|
pvr_fw_startstop.o \
|
||||||
pvr_fw_trace.o \
|
pvr_fw_trace.o \
|
||||||
pvr_fw_util.o \
|
pvr_fw_util.o \
|
||||||
|
@ -941,7 +941,7 @@ pvr_fw_init(struct pvr_device *pvr_dev)
|
|||||||
static const struct pvr_fw_defs *fw_defs[PVR_FW_PROCESSOR_TYPE_COUNT] = {
|
static const struct pvr_fw_defs *fw_defs[PVR_FW_PROCESSOR_TYPE_COUNT] = {
|
||||||
[PVR_FW_PROCESSOR_TYPE_META] = &pvr_fw_defs_meta,
|
[PVR_FW_PROCESSOR_TYPE_META] = &pvr_fw_defs_meta,
|
||||||
[PVR_FW_PROCESSOR_TYPE_MIPS] = &pvr_fw_defs_mips,
|
[PVR_FW_PROCESSOR_TYPE_MIPS] = &pvr_fw_defs_mips,
|
||||||
[PVR_FW_PROCESSOR_TYPE_RISCV] = NULL,
|
[PVR_FW_PROCESSOR_TYPE_RISCV] = &pvr_fw_defs_riscv,
|
||||||
};
|
};
|
||||||
|
|
||||||
u32 kccb_size_log2 = ROGUE_FWIF_KCCB_NUMCMDS_LOG2_DEFAULT;
|
u32 kccb_size_log2 = ROGUE_FWIF_KCCB_NUMCMDS_LOG2_DEFAULT;
|
||||||
@ -954,13 +954,6 @@ pvr_fw_init(struct pvr_device *pvr_dev)
|
|||||||
|
|
||||||
fw_dev->defs = fw_defs[fw_dev->processor_type];
|
fw_dev->defs = fw_defs[fw_dev->processor_type];
|
||||||
|
|
||||||
/*
|
|
||||||
* Not all firmware processor types are currently supported.
|
|
||||||
* Once they are, this check can be removed.
|
|
||||||
*/
|
|
||||||
if (!fw_dev->defs)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
err = fw_dev->defs->init(pvr_dev);
|
err = fw_dev->defs->init(pvr_dev);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
@ -1466,6 +1459,15 @@ void pvr_fw_object_get_fw_addr_offset(struct pvr_fw_object *fw_obj, u32 offset,
|
|||||||
*fw_addr_out = pvr_dev->fw_dev.defs->get_fw_addr_with_offset(fw_obj, offset);
|
*fw_addr_out = pvr_dev->fw_dev.defs->get_fw_addr_with_offset(fw_obj, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64
|
||||||
|
pvr_fw_obj_get_gpu_addr(struct pvr_fw_object *fw_obj)
|
||||||
|
{
|
||||||
|
struct pvr_device *pvr_dev = to_pvr_device(gem_from_pvr_gem(fw_obj->gem)->dev);
|
||||||
|
struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev;
|
||||||
|
|
||||||
|
return fw_dev->fw_heap_info.gpu_addr + fw_obj->fw_addr_offset;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* pvr_fw_hard_reset() - Re-initialise the FW code and data segments, and reset all global FW
|
* pvr_fw_hard_reset() - Re-initialise the FW code and data segments, and reset all global FW
|
||||||
* structures
|
* structures
|
||||||
|
@ -392,6 +392,7 @@ enum pvr_fw_processor_type {
|
|||||||
|
|
||||||
extern const struct pvr_fw_defs pvr_fw_defs_meta;
|
extern const struct pvr_fw_defs pvr_fw_defs_meta;
|
||||||
extern const struct pvr_fw_defs pvr_fw_defs_mips;
|
extern const struct pvr_fw_defs pvr_fw_defs_mips;
|
||||||
|
extern const struct pvr_fw_defs pvr_fw_defs_riscv;
|
||||||
|
|
||||||
int pvr_fw_validate_init_device_info(struct pvr_device *pvr_dev);
|
int pvr_fw_validate_init_device_info(struct pvr_device *pvr_dev);
|
||||||
int pvr_fw_init(struct pvr_device *pvr_dev);
|
int pvr_fw_init(struct pvr_device *pvr_dev);
|
||||||
@ -478,6 +479,15 @@ pvr_fw_object_get_fw_addr(struct pvr_fw_object *fw_obj, u32 *fw_addr_out)
|
|||||||
pvr_fw_object_get_fw_addr_offset(fw_obj, 0, fw_addr_out);
|
pvr_fw_object_get_fw_addr_offset(fw_obj, 0, fw_addr_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64
|
||||||
|
pvr_fw_obj_get_gpu_addr(struct pvr_fw_object *fw_obj);
|
||||||
|
|
||||||
|
static __always_inline size_t
|
||||||
|
pvr_fw_obj_get_object_size(struct pvr_fw_object *fw_obj)
|
||||||
|
{
|
||||||
|
return pvr_gem_object_size(fw_obj->gem);
|
||||||
|
}
|
||||||
|
|
||||||
/* Util functions defined in pvr_fw_util.c. These are intended for use in pvr_fw_<arch>.c files. */
|
/* Util functions defined in pvr_fw_util.c. These are intended for use in pvr_fw_<arch>.c files. */
|
||||||
int
|
int
|
||||||
pvr_fw_process_elf_command_stream(struct pvr_device *pvr_dev, const u8 *fw, u8 *fw_code_ptr,
|
pvr_fw_process_elf_command_stream(struct pvr_device *pvr_dev, const u8 *fw, u8 *fw_code_ptr,
|
||||||
|
165
drivers/gpu/drm/imagination/pvr_fw_riscv.c
Normal file
165
drivers/gpu/drm/imagination/pvr_fw_riscv.c
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0 OR MIT
|
||||||
|
/* Copyright (c) 2024 Imagination Technologies Ltd. */
|
||||||
|
|
||||||
|
#include "pvr_device.h"
|
||||||
|
#include "pvr_fw.h"
|
||||||
|
#include "pvr_fw_info.h"
|
||||||
|
#include "pvr_fw_mips.h"
|
||||||
|
#include "pvr_gem.h"
|
||||||
|
#include "pvr_rogue_cr_defs.h"
|
||||||
|
#include "pvr_rogue_riscv.h"
|
||||||
|
#include "pvr_vm.h"
|
||||||
|
|
||||||
|
#include <linux/compiler.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/ktime.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#define ROGUE_FW_HEAP_RISCV_SHIFT 25 /* 32 MB */
|
||||||
|
#define ROGUE_FW_HEAP_RISCV_SIZE (1u << ROGUE_FW_HEAP_RISCV_SHIFT)
|
||||||
|
|
||||||
|
static int
|
||||||
|
pvr_riscv_wrapper_init(struct pvr_device *pvr_dev)
|
||||||
|
{
|
||||||
|
const u64 common_opts =
|
||||||
|
((u64)(ROGUE_FW_HEAP_RISCV_SIZE >> FWCORE_ADDR_REMAP_CONFIG0_SIZE_ALIGNSHIFT)
|
||||||
|
<< ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0_SIZE_SHIFT) |
|
||||||
|
((u64)MMU_CONTEXT_MAPPING_FWPRIV
|
||||||
|
<< FWCORE_ADDR_REMAP_CONFIG0_MMU_CONTEXT_SHIFT);
|
||||||
|
|
||||||
|
u64 code_addr = pvr_fw_obj_get_gpu_addr(pvr_dev->fw_dev.mem.code_obj);
|
||||||
|
u64 data_addr = pvr_fw_obj_get_gpu_addr(pvr_dev->fw_dev.mem.data_obj);
|
||||||
|
|
||||||
|
/* This condition allows us to OR the addresses into the register directly. */
|
||||||
|
static_assert(ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_SHIFT ==
|
||||||
|
ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_ALIGNSHIFT);
|
||||||
|
|
||||||
|
WARN_ON(code_addr & ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_CLRMSK);
|
||||||
|
WARN_ON(data_addr & ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_CLRMSK);
|
||||||
|
|
||||||
|
pvr_cr_write64(pvr_dev, ROGUE_RISCVFW_REGION_REMAP_CR(BOOTLDR_CODE),
|
||||||
|
code_addr | common_opts | ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0_FETCH_EN_EN);
|
||||||
|
|
||||||
|
pvr_cr_write64(pvr_dev, ROGUE_RISCVFW_REGION_REMAP_CR(BOOTLDR_DATA),
|
||||||
|
data_addr | common_opts |
|
||||||
|
ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0_LOAD_STORE_EN_EN);
|
||||||
|
|
||||||
|
/* Garten IDLE bit controlled by RISC-V. */
|
||||||
|
pvr_cr_write64(pvr_dev, ROGUE_CR_MTS_GARTEN_WRAPPER_CONFIG,
|
||||||
|
ROGUE_CR_MTS_GARTEN_WRAPPER_CONFIG_IDLE_CTRL_META);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rogue_riscv_fw_boot_data {
|
||||||
|
u64 coremem_code_dev_vaddr;
|
||||||
|
u64 coremem_data_dev_vaddr;
|
||||||
|
u32 coremem_code_fw_addr;
|
||||||
|
u32 coremem_data_fw_addr;
|
||||||
|
u32 coremem_code_size;
|
||||||
|
u32 coremem_data_size;
|
||||||
|
u32 flags;
|
||||||
|
u32 reserved;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
pvr_riscv_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_mem *fw_mem = &fw_dev->mem;
|
||||||
|
struct rogue_riscv_fw_boot_data *boot_data;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = pvr_fw_process_elf_command_stream(pvr_dev, fw, fw_code_ptr, fw_data_ptr,
|
||||||
|
fw_core_code_ptr, fw_core_data_ptr);
|
||||||
|
if (err)
|
||||||
|
goto err_out;
|
||||||
|
|
||||||
|
boot_data = (struct rogue_riscv_fw_boot_data *)fw_data_ptr;
|
||||||
|
|
||||||
|
if (fw_mem->core_code_obj) {
|
||||||
|
boot_data->coremem_code_dev_vaddr = pvr_fw_obj_get_gpu_addr(fw_mem->core_code_obj);
|
||||||
|
pvr_fw_object_get_fw_addr(fw_mem->core_code_obj, &boot_data->coremem_code_fw_addr);
|
||||||
|
boot_data->coremem_code_size = pvr_fw_obj_get_object_size(fw_mem->core_code_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fw_mem->core_data_obj) {
|
||||||
|
boot_data->coremem_data_dev_vaddr = pvr_fw_obj_get_gpu_addr(fw_mem->core_data_obj);
|
||||||
|
pvr_fw_object_get_fw_addr(fw_mem->core_data_obj, &boot_data->coremem_data_fw_addr);
|
||||||
|
boot_data->coremem_data_size = pvr_fw_obj_get_object_size(fw_mem->core_data_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_out:
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pvr_riscv_init(struct pvr_device *pvr_dev)
|
||||||
|
{
|
||||||
|
pvr_fw_heap_info_init(pvr_dev, ROGUE_FW_HEAP_RISCV_SHIFT, 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32
|
||||||
|
pvr_riscv_get_fw_addr_with_offset(struct pvr_fw_object *fw_obj, u32 offset)
|
||||||
|
{
|
||||||
|
u32 fw_addr = fw_obj->fw_addr_offset + offset;
|
||||||
|
|
||||||
|
/* RISC-V cacheability is determined by address. */
|
||||||
|
if (fw_obj->gem->flags & PVR_BO_FW_FLAGS_DEVICE_UNCACHED)
|
||||||
|
fw_addr |= ROGUE_RISCVFW_REGION_BASE(SHARED_UNCACHED_DATA);
|
||||||
|
else
|
||||||
|
fw_addr |= ROGUE_RISCVFW_REGION_BASE(SHARED_CACHED_DATA);
|
||||||
|
|
||||||
|
return fw_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
pvr_riscv_vm_map(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj)
|
||||||
|
{
|
||||||
|
struct pvr_gem_object *pvr_obj = fw_obj->gem;
|
||||||
|
|
||||||
|
return pvr_vm_map(pvr_dev->kernel_vm_ctx, pvr_obj, 0, fw_obj->fw_mm_node.start,
|
||||||
|
pvr_gem_object_size(pvr_obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pvr_riscv_vm_unmap(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj)
|
||||||
|
{
|
||||||
|
struct pvr_gem_object *pvr_obj = fw_obj->gem;
|
||||||
|
|
||||||
|
pvr_vm_unmap_obj(pvr_dev->kernel_vm_ctx, pvr_obj,
|
||||||
|
fw_obj->fw_mm_node.start, fw_obj->fw_mm_node.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
pvr_riscv_irq_pending(struct pvr_device *pvr_dev)
|
||||||
|
{
|
||||||
|
return pvr_cr_read32(pvr_dev, ROGUE_CR_IRQ_OS0_EVENT_STATUS) &
|
||||||
|
ROGUE_CR_IRQ_OS0_EVENT_STATUS_SOURCE_EN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pvr_riscv_irq_clear(struct pvr_device *pvr_dev)
|
||||||
|
{
|
||||||
|
pvr_cr_write32(pvr_dev, ROGUE_CR_IRQ_OS0_EVENT_CLEAR,
|
||||||
|
ROGUE_CR_IRQ_OS0_EVENT_CLEAR_SOURCE_EN);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct pvr_fw_defs pvr_fw_defs_riscv = {
|
||||||
|
.init = pvr_riscv_init,
|
||||||
|
.fw_process = pvr_riscv_fw_process,
|
||||||
|
.vm_map = pvr_riscv_vm_map,
|
||||||
|
.vm_unmap = pvr_riscv_vm_unmap,
|
||||||
|
.get_fw_addr_with_offset = pvr_riscv_get_fw_addr_with_offset,
|
||||||
|
.wrapper_init = pvr_riscv_wrapper_init,
|
||||||
|
.irq_pending = pvr_riscv_irq_pending,
|
||||||
|
.irq_clear = pvr_riscv_irq_clear,
|
||||||
|
.has_fixed_data_addr = false,
|
||||||
|
};
|
@ -49,6 +49,14 @@ rogue_bif_init(struct pvr_device *pvr_dev)
|
|||||||
|
|
||||||
pvr_cr_write64(pvr_dev, BIF_CAT_BASEX(MMU_CONTEXT_MAPPING_FWPRIV),
|
pvr_cr_write64(pvr_dev, BIF_CAT_BASEX(MMU_CONTEXT_MAPPING_FWPRIV),
|
||||||
pc_addr);
|
pc_addr);
|
||||||
|
|
||||||
|
if (pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_RISCV) {
|
||||||
|
pc_addr = (((u64)pc_dma_addr >> ROGUE_CR_FWCORE_MEM_CAT_BASE0_ADDR_ALIGNSHIFT)
|
||||||
|
<< ROGUE_CR_FWCORE_MEM_CAT_BASE0_ADDR_SHIFT) &
|
||||||
|
~ROGUE_CR_FWCORE_MEM_CAT_BASE0_ADDR_CLRMSK;
|
||||||
|
|
||||||
|
pvr_cr_write64(pvr_dev, FWCORE_MEM_CAT_BASEX(MMU_CONTEXT_MAPPING_FWPRIV), pc_addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -114,6 +122,9 @@ pvr_fw_start(struct pvr_device *pvr_dev)
|
|||||||
(void)pvr_cr_read32(pvr_dev, ROGUE_CR_SYS_BUS_SECURE); /* Fence write */
|
(void)pvr_cr_read32(pvr_dev, ROGUE_CR_SYS_BUS_SECURE); /* Fence write */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_RISCV)
|
||||||
|
pvr_cr_write32(pvr_dev, ROGUE_CR_FWCORE_BOOT, 0);
|
||||||
|
|
||||||
/* Set Rogue in soft-reset. */
|
/* Set Rogue in soft-reset. */
|
||||||
pvr_cr_write64(pvr_dev, ROGUE_CR_SOFT_RESET, soft_reset_mask);
|
pvr_cr_write64(pvr_dev, ROGUE_CR_SOFT_RESET, soft_reset_mask);
|
||||||
if (has_reset2)
|
if (has_reset2)
|
||||||
@ -167,6 +178,12 @@ pvr_fw_start(struct pvr_device *pvr_dev)
|
|||||||
/* ... and afterwards. */
|
/* ... and afterwards. */
|
||||||
udelay(3);
|
udelay(3);
|
||||||
|
|
||||||
|
if (pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_RISCV) {
|
||||||
|
/* Boot the FW. */
|
||||||
|
pvr_cr_write32(pvr_dev, ROGUE_CR_FWCORE_BOOT, 1);
|
||||||
|
udelay(3);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_reset:
|
err_reset:
|
||||||
|
41
drivers/gpu/drm/imagination/pvr_rogue_riscv.h
Normal file
41
drivers/gpu/drm/imagination/pvr_rogue_riscv.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 OR MIT */
|
||||||
|
/* Copyright (c) 2024 Imagination Technologies Ltd. */
|
||||||
|
|
||||||
|
#ifndef PVR_ROGUE_RISCV_H
|
||||||
|
#define PVR_ROGUE_RISCV_H
|
||||||
|
|
||||||
|
#include "pvr_rogue_cr_defs.h"
|
||||||
|
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/sizes.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#define ROGUE_RISCVFW_REGION_SIZE SZ_256M
|
||||||
|
#define ROGUE_RISCVFW_REGION_SHIFT __ffs(ROGUE_RISCVFW_REGION_SIZE)
|
||||||
|
|
||||||
|
enum rogue_riscvfw_region {
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_0 = 0,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_1,
|
||||||
|
ROGUE_RISCV_REGION_SOCIF,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_3,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_4,
|
||||||
|
ROGUE_RISCV_REGION_BOOTLDR_DATA,
|
||||||
|
ROGUE_RISCV_REGION_SHARED_CACHED_DATA,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_7,
|
||||||
|
ROGUE_RISCV_REGION_COREMEM,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_9,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_A,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_B,
|
||||||
|
ROGUE_RISCV_REGION_BOOTLDR_CODE,
|
||||||
|
ROGUE_RISCV_REGION_SHARED_UNCACHED_DATA,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_E,
|
||||||
|
ROGUE_RISCV_REGION__RESERVED_F,
|
||||||
|
|
||||||
|
ROGUE_RISCV_REGION__COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ROGUE_RISCVFW_REGION_BASE(r) ((u32)(ROGUE_RISCV_REGION_##r) << ROGUE_RISCVFW_REGION_SHIFT)
|
||||||
|
#define ROGUE_RISCVFW_REGION_REMAP_CR(r) \
|
||||||
|
(ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0 + (u32)(ROGUE_RISCV_REGION_##r) * 8U)
|
||||||
|
|
||||||
|
#endif /* PVR_ROGUE_RISCV_H */
|
Loading…
Reference in New Issue
Block a user