Compare commits
4 Commits
dd0c837930
...
6e82d49488
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e82d49488 | |||
| b5016c23b0 | |||
| 824b71c283 | |||
| 7e2e162fe1 |
Vendored
+153
@@ -0,0 +1,153 @@
|
||||
def suites = ['threadx', 'smp']
|
||||
def presets = ['Debug32', 'Release32']
|
||||
|
||||
def runOneRegression(String suite, String preset, String simulatorPath) {
|
||||
def suiteDir = "test/${suite}"
|
||||
def buildDir = "../../build/${preset}/test/${suite}"
|
||||
|
||||
stage("${suite}-${preset}") {
|
||||
dir(suiteDir) {
|
||||
sh """
|
||||
set -eu
|
||||
test -n "${simulatorPath}"
|
||||
test -x "${simulatorPath}"
|
||||
cmake --preset "${preset}" \\
|
||||
-DTHREADX_TEST_SIMULATOR="${simulatorPath}"
|
||||
cmake --build "${buildDir}" --parallel "\$(nproc)"
|
||||
ctest \\
|
||||
--test-dir "${buildDir}" \\
|
||||
--output-on-failure \\
|
||||
--output-junit "${buildDir}/ctest-results.xml" \\
|
||||
--parallel "1"
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def resolveImageCommit(String image) {
|
||||
sh(
|
||||
script: """
|
||||
set -eu
|
||||
docker pull "${image}" >/dev/null
|
||||
docker image inspect --format='{{ index .Config.Labels "git-commit" }}' "${image}"
|
||||
""",
|
||||
returnStdout: true
|
||||
).trim()
|
||||
}
|
||||
|
||||
def runRegressionLane(String image, String simulatorPath, boolean allowFailure) {
|
||||
def parallelTasks = [:]
|
||||
|
||||
for (String suite : suites) {
|
||||
for (String preset : presets) {
|
||||
def suiteName = suite
|
||||
def presetName = preset
|
||||
def taskName = "${suiteName}-${presetName}"
|
||||
parallelTasks[taskName] = {
|
||||
if (allowFailure) {
|
||||
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
|
||||
runOneRegression(suiteName, presetName, simulatorPath)
|
||||
}
|
||||
} else {
|
||||
runOneRegression(suiteName, presetName, simulatorPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
docker.image(image).inside {
|
||||
sh '''
|
||||
set -eu
|
||||
cmake --version
|
||||
ctest --version
|
||||
'''
|
||||
parallel parallelTasks
|
||||
}
|
||||
}
|
||||
|
||||
properties([
|
||||
parameters([
|
||||
string(
|
||||
name: 'SIMULATOR_IMAGE_PINNED',
|
||||
defaultValue: 'git.minres.com/here/here-vp:ac4f736',
|
||||
description: 'Version-pinned Docker image used for the blocking regression lane'
|
||||
),
|
||||
string(
|
||||
name: 'SIMULATOR_IMAGE_LATEST',
|
||||
defaultValue: 'git.minres.com/here/here-vp:latest',
|
||||
description: 'Moving Docker image tag used for the canary lane'
|
||||
),
|
||||
string(
|
||||
name: 'THREADX_TEST_SIMULATOR',
|
||||
defaultValue: '/usr/local/bin/riscv-vp',
|
||||
description: 'Absolute path to the simulator executable inside the Docker image'
|
||||
)
|
||||
])
|
||||
])
|
||||
|
||||
def canaryShouldRun = true
|
||||
|
||||
node {
|
||||
timestamps {
|
||||
ansiColor('xterm') {
|
||||
try {
|
||||
stage('Checkout') {
|
||||
checkout scm
|
||||
}
|
||||
|
||||
stage('Check Canary Divergence') {
|
||||
def pinnedCommit = resolveImageCommit(params.SIMULATOR_IMAGE_PINNED)
|
||||
def latestCommit = resolveImageCommit(params.SIMULATOR_IMAGE_LATEST)
|
||||
|
||||
if (!pinnedCommit) {
|
||||
error "Missing git-commit label on ${params.SIMULATOR_IMAGE_PINNED}"
|
||||
}
|
||||
if (!latestCommit) {
|
||||
error "Missing git-commit label on ${params.SIMULATOR_IMAGE_LATEST}"
|
||||
}
|
||||
|
||||
canaryShouldRun = (pinnedCommit != latestCommit)
|
||||
|
||||
if (canaryShouldRun) {
|
||||
echo "Canary enabled: ${params.SIMULATOR_IMAGE_LATEST} (${latestCommit}) diverges from ${params.SIMULATOR_IMAGE_PINNED} (${pinnedCommit})"
|
||||
} else {
|
||||
echo "Canary skipped: ${params.SIMULATOR_IMAGE_LATEST} and ${params.SIMULATOR_IMAGE_PINNED} both use git-commit ${pinnedCommit}"
|
||||
}
|
||||
}
|
||||
|
||||
stage('Stable Regression') {
|
||||
runRegressionLane(params.SIMULATOR_IMAGE_PINNED, params.THREADX_TEST_SIMULATOR, false)
|
||||
}
|
||||
|
||||
stage('Simulator Canary') {
|
||||
if (canaryShouldRun) {
|
||||
runRegressionLane(params.SIMULATOR_IMAGE_LATEST, params.THREADX_TEST_SIMULATOR, true)
|
||||
} else {
|
||||
echo 'Skipping canary lane because latest and pinned images are identical.'
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
currentBuild.result = 'FAILURE'
|
||||
throw err
|
||||
} finally {
|
||||
junit allowEmptyResults: true, testResults: 'build/*/test/*/ctest-results.xml'
|
||||
archiveArtifacts artifacts: 'build/*/test/*/*.map,build/*/test/*/*.dis,build/*/test/*/Testing/**', allowEmptyArchive: true
|
||||
|
||||
if (currentBuild.currentResult == 'SUCCESS') {
|
||||
rocketSend ":thumbsup: ThreadX regression run passed, results at ${env.RUN_DISPLAY_URL} "
|
||||
} else if (currentBuild.currentResult == 'UNSTABLE') {
|
||||
rocketSend ":warning: ThreadX canary regression is unstable on ${params.SIMULATOR_IMAGE_LATEST}, please check ${env.RUN_DISPLAY_URL} "
|
||||
} else if (currentBuild.currentResult == 'FAILURE') {
|
||||
archiveArtifacts artifacts: 'failed_seeds_*.txt', followSymlinks: false, onlyIfSuccessful: false
|
||||
rocketSend ":thumbsdown: ThreadX regression failed, please check ${env.RUN_DISPLAY_URL} "
|
||||
emailext recipientProviders: [culprits(), requestor()],
|
||||
subject: "ThreadX Pipeline Failed: ${currentBuild.fullDisplayName}",
|
||||
body: """
|
||||
<p>Build Status: ${currentBuild.currentResult}</p>
|
||||
<p> Check logs at <a href='${env.BUILD_URL}console'> Build Console Logs </a> or at <a href='${env.RUN_DISPLAY_URL}'> Overview </a></p>
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,46 @@
|
||||
# Running the RTOS
|
||||
# ThreadX Port for the TGFS cores and accompanying Virtual prototypes
|
||||
|
||||
This repositiory contains prototypical RISC-V implementation of ThreadX designed to run on the RISC-V VP by MINRES (tested on commit [d96cd4a](https://github.com/Minres/RISCV-VP/tree/d96cd4a01de1f4bcc77db3eceb57f5c144395847)).
|
||||
This repositiory contains a RISC-V implementation of ThreadX designed to run on the TGFS cores or the RISC-V VP by MINRES (available on [Github](https://github.com/Minres/RISCV-VP/tree/d96cd4a01de1f4bcc77db3eceb57f5c144395847)).
|
||||
|
||||
**Note**: SMP support additionally requires a VP variant that allows the number of cores to be configured.
|
||||
|
||||
## Building the RTOS
|
||||
|
||||
Four presets are provided, targetting RV32 and RV64 combined with GC or IMAC. The presets use rv32imac and rv64imac.
|
||||
A set of presets is provided, targetting 32 and 64-bit systems.
|
||||
|
||||
E.g. building for the RV64IMAC configuration:
|
||||
|
||||
```bash
|
||||
cmake --preset Debug
|
||||
cmake --build --preset Debug --parallel
|
||||
```
|
||||
|
||||
## Running on the VP
|
||||
|
||||
A run command can look like this:
|
||||
|
||||
```bash
|
||||
riscv-vp --isa=rv64gc_m -f build/Debug/main.elf
|
||||
riscv-vp --isa=rv64imac_m -f build/Debug/thread_demo.elf
|
||||
```
|
||||
|
||||
## What is running?
|
||||
## Running the Regression Suites
|
||||
|
||||
The current implementation is just a demo, taken from the existing threadx qemu implementation.
|
||||
The regression suites live in test/. Both suites register their cases with CTest and require the path to the MINRES `riscv-vp` simulator to be provided through `THREADX_TEST_SIMULATOR`.
|
||||
|
||||
The SMP regression suite additionally requires a `riscv-vp` variant that supports a configurable number of cores.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
cmake -S test/threadx --preset Debug32 -DTHREADX_TEST_SIMULATOR=/opt/riscv-vp/bin/riscv-vp
|
||||
cmake --build --preset Debug32 --parallel
|
||||
ctest --test-dir ../../build/Debug32/test/threadx --output-on-failure --parallel 4
|
||||
```
|
||||
|
||||
## Demo Applications
|
||||
|
||||
The repository currently provides three demo applications:
|
||||
|
||||
- `thread_demo`: a basic single-kernel ThreadX demo based on the standard ThreadX sample application
|
||||
- `smp_demo`: the same basic demo application for running on a ThreadX SMP kernel
|
||||
- `tcp_demo`: a NetX Duo demo that exercises the TCP/IP stack on top of ThreadX
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2024 Microsoft Corporation
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License which is available at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef CSR_H
|
||||
#define CSR_H
|
||||
|
||||
// Machine Status Register, mstatus
|
||||
#define MSTATUS_MPP_MASK (3L << 11) // previous mode.
|
||||
#define MSTATUS_MPP_M (3L << 11)
|
||||
#define MSTATUS_MPP_S (1L << 11)
|
||||
#define MSTATUS_MPP_U (0L << 11)
|
||||
#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable.
|
||||
#define MSTATUS_MPIE (1L << 7)
|
||||
#define MSTATUS_FS (1L << 13)
|
||||
|
||||
// Machine-mode Interrupt Enable
|
||||
#define MIE_MTIE (1L << 7)
|
||||
#define MIE_MSIE (1L << 3)
|
||||
#define MIE_MEIE (1L << 11)
|
||||
#define MIE_STIE (1L << 5) // supervisor timer
|
||||
#define MIE_SSIE (1L << 1)
|
||||
#define MIE_SEIE (1L << 9)
|
||||
|
||||
// Supervisor Status Register, sstatus
|
||||
#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User
|
||||
#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable
|
||||
#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable
|
||||
#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable
|
||||
#define SSTATUS_UIE (1L << 0) // User Interrupt Enable
|
||||
#define SSTATUS_SPIE (1L << 5)
|
||||
#define SSTATUS_UPIE (1L << 4)
|
||||
|
||||
// Supervisor Interrupt Enable
|
||||
#define SIE_SEIE (1L << 9) // external
|
||||
#define SIE_STIE (1L << 5) // timer
|
||||
#define SIE_SSIE (1L << 1) // software
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static inline uint64_t riscv_get_core()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, mhartid" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_mstatus()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, mstatus" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_mstatus(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw mstatus, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// machine exception program counter, holds the
|
||||
// instruction address to which a return from
|
||||
// exception will go.
|
||||
static inline void riscv_writ_mepc(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw mepc, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_sstatus()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, sstatus" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_sstatus(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw sstatus, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// Supervisor Interrupt Pending
|
||||
static inline uint64_t riscv_get_sip()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, sip" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_sip(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw sip, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_sie()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, sie" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_sie(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw sie, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_mie()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, mie" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_mie(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw mie, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// supervisor exception program counter, holds the
|
||||
// instruction address to which a return from
|
||||
// exception will go.
|
||||
static inline void riscv_writ_sepc(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw sepc, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_sepc()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, sepc" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// Machine Exception Delegation
|
||||
static inline uint64_t riscv_get_medeleg()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, medeleg" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_medeleg(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw medeleg, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// Machine Interrupt Delegation
|
||||
static inline uint64_t riscv_get_mideleg()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, mideleg" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_mideleg(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw mideleg, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// Supervisor Trap-Vector Base Address
|
||||
// low two bits are mode.
|
||||
static inline void riscv_writ_stvec(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw stvec, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_stvec()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, stvec" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// Supervisor Timer Comparison Register
|
||||
static inline uint64_t riscv_get_stimecmp()
|
||||
{
|
||||
uint64_t x;
|
||||
// asm volatile("csrr %0, stimecmp" : "=r" (x) );
|
||||
asm volatile("csrr %0, 0x14d" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_stimecmp(uint64_t x)
|
||||
{
|
||||
// asm volatile("csrw stimecmp, %0" : : "r" (x));
|
||||
asm volatile("csrw 0x14d, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// Machine Environment Configuration Register
|
||||
static inline uint64_t riscv_get_menvcfg()
|
||||
{
|
||||
uint64_t x;
|
||||
// asm volatile("csrr %0, menvcfg" : "=r" (x) );
|
||||
asm volatile("csrr %0, 0x30a" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_menvcfg(uint64_t x)
|
||||
{
|
||||
// asm volatile("csrw menvcfg, %0" : : "r" (x));
|
||||
asm volatile("csrw 0x30a, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// Physical Memory Protection
|
||||
static inline void riscv_writ_pmpcfg0(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw pmpcfg0, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline void riscv_writ_pmpaddr0(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw pmpaddr0, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
// supervisor address translation and protection;
|
||||
// holds the address of the page table.
|
||||
static inline void riscv_writ_satp(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw satp, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_satp()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, satp" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// Supervisor Trap Cause
|
||||
static inline uint64_t riscv_get_scause()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, scause" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// Supervisor Trap Value
|
||||
static inline uint64_t riscv_get_stval()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, stval" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// Machine-mode Counter-Enable
|
||||
static inline void riscv_writ_mcounteren(uint64_t x)
|
||||
{
|
||||
asm volatile("csrw mcounteren, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_mcounteren()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, mcounteren" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// machine-mode cycle counter
|
||||
static inline uint64_t riscv_get_time()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("csrr %0, time" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// enable device interrupts
|
||||
static inline void riscv_sintr_on()
|
||||
{
|
||||
uint64_t sstatus = riscv_get_sstatus();
|
||||
sstatus |= SSTATUS_SIE;
|
||||
riscv_writ_sstatus(sstatus);
|
||||
}
|
||||
|
||||
// disable device interrupts
|
||||
static inline void riscv_sintr_off()
|
||||
{
|
||||
uint64_t sstatus = riscv_get_sstatus();
|
||||
sstatus &= (~SSTATUS_SIE);
|
||||
riscv_writ_sstatus(sstatus);
|
||||
}
|
||||
|
||||
// are device interrupts enabled?
|
||||
static inline int riscv_sintr_get()
|
||||
{
|
||||
uint64_t x = riscv_get_sstatus();
|
||||
return (x & SSTATUS_SIE) != 0;
|
||||
}
|
||||
|
||||
static inline void riscv_sintr_restore(int x)
|
||||
{
|
||||
if (x)
|
||||
riscv_sintr_on();
|
||||
else
|
||||
riscv_sintr_off();
|
||||
}
|
||||
|
||||
// enable device interrupts
|
||||
static inline void riscv_mintr_on()
|
||||
{
|
||||
uint64_t mstatus = riscv_get_mstatus();
|
||||
mstatus |= MSTATUS_MIE;
|
||||
riscv_writ_mstatus(mstatus);
|
||||
}
|
||||
|
||||
// disable device interrupts
|
||||
static inline void riscv_mintr_off()
|
||||
{
|
||||
uint64_t mstatus = riscv_get_mstatus();
|
||||
mstatus &= (~MSTATUS_MIE);
|
||||
riscv_writ_mstatus(mstatus);
|
||||
}
|
||||
|
||||
// are device interrupts enabled?
|
||||
static inline int riscv_mintr_get()
|
||||
{
|
||||
uint64_t x = riscv_get_mstatus();
|
||||
return (x & MSTATUS_MIE) != 0;
|
||||
}
|
||||
|
||||
static inline void riscv_mintr_restore(int x)
|
||||
{
|
||||
if (x)
|
||||
riscv_mintr_on();
|
||||
else
|
||||
riscv_mintr_off();
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_sp()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("mv %0, sp" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// read and write tp, the thread pointer, which xv6 uses to hold
|
||||
// this core's hartid (core number), the index into cpus[].
|
||||
static inline uint64_t riscv_get_tp()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("mv %0, tp" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
static inline void riscv_writ_tp(uint64_t x)
|
||||
{
|
||||
asm volatile("mv tp, %0" : : "r"(x));
|
||||
}
|
||||
|
||||
static inline uint64_t riscv_get_ra()
|
||||
{
|
||||
uint64_t x;
|
||||
asm volatile("mv %0, ra" : "=r"(x));
|
||||
return x;
|
||||
}
|
||||
|
||||
// flush the TLB.
|
||||
static inline void sfence_vma()
|
||||
{
|
||||
// the zero, zero means flush all TLB entries.
|
||||
asm volatile("sfence.vma zero, zero");
|
||||
}
|
||||
|
||||
#endif // __ASSEMBLER__
|
||||
|
||||
#endif
|
||||
@@ -47,7 +47,6 @@
|
||||
|
||||
#ifndef NX_PORT_H
|
||||
#define NX_PORT_H
|
||||
#include "tx_port.h"
|
||||
/* Determine if the optional NetX user define file should be used. */
|
||||
|
||||
#ifdef NX_INCLUDE_USER_DEFINE_FILE
|
||||
|
||||
Reference in New Issue
Block a user