forked from espressif/esp-idf
Merge branch 'feat/add_example_usj' into 'master'
change(usb_serial_jtag): Add example for usb serial jtag echo Closes IDFGH-11503, IDFGH-11493, and IDFGH-11478 See merge request espressif/esp-idf!27366
This commit is contained in:
@@ -229,6 +229,15 @@ pytest_examples_esp32c6_generic:
|
|||||||
artifacts: false
|
artifacts: false
|
||||||
tags: [ esp32c6, generic ]
|
tags: [ esp32c6, generic ]
|
||||||
|
|
||||||
|
pytest_examples_esp32c6_usj_device:
|
||||||
|
extends:
|
||||||
|
- .pytest_examples_dir_template
|
||||||
|
- .rules:test:example_test-esp32c6
|
||||||
|
needs:
|
||||||
|
- job: build_pytest_examples_esp32c6
|
||||||
|
artifacts: false
|
||||||
|
tags: [ esp32c6, usj_device ]
|
||||||
|
|
||||||
pytest_examples_esp32h2_generic:
|
pytest_examples_esp32h2_generic:
|
||||||
extends:
|
extends:
|
||||||
- .pytest_examples_dir_template
|
- .pytest_examples_dir_template
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "hal/usb_serial_jtag_ll.h"
|
#include "hal/usb_serial_jtag_ll.h"
|
||||||
#include "hal/usb_phy_ll.h"
|
#include "hal/usb_phy_ll.h"
|
||||||
@@ -25,12 +26,19 @@
|
|||||||
#define USJ_RCC_ATOMIC()
|
#define USJ_RCC_ATOMIC()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FIFO_IDLE = 0, /*!< Indicates the fifo is in idle state */
|
||||||
|
FIFO_BUSY = 1, /*!< Indicates the fifo is in busy state */
|
||||||
|
} fifo_status_t;
|
||||||
|
|
||||||
// The hardware buffer max size is 64
|
// The hardware buffer max size is 64
|
||||||
#define USB_SER_JTAG_ENDP_SIZE (64)
|
#define USB_SER_JTAG_ENDP_SIZE (64)
|
||||||
#define USB_SER_JTAG_RX_MAX_SIZE (64)
|
#define USB_SER_JTAG_RX_MAX_SIZE (64)
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
intr_handle_t intr_handle; /*!< USB-SERIAL-JTAG interrupt handler */
|
intr_handle_t intr_handle; /*!< USB-SERIAL-JTAG interrupt handler */
|
||||||
|
portMUX_TYPE spinlock; /*!< Spinlock for usb_serial_jtag */
|
||||||
|
_Atomic fifo_status_t fifo_status; /*!< Record the status of fifo */
|
||||||
|
|
||||||
// RX parameters
|
// RX parameters
|
||||||
RingbufHandle_t rx_ring_buf; /*!< RX ring buffer handler */
|
RingbufHandle_t rx_ring_buf; /*!< RX ring buffer handler */
|
||||||
@@ -67,7 +75,7 @@ static void usb_serial_jtag_isr_handler_default(void *arg) {
|
|||||||
// If the hardware fifo is available, write in it. Otherwise, do nothing.
|
// If the hardware fifo is available, write in it. Otherwise, do nothing.
|
||||||
if (usb_serial_jtag_ll_txfifo_writable() == 1) {
|
if (usb_serial_jtag_ll_txfifo_writable() == 1) {
|
||||||
// We disable the interrupt here so that the interrupt won't be triggered if there is no data to send.
|
// We disable the interrupt here so that the interrupt won't be triggered if there is no data to send.
|
||||||
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
||||||
size_t queued_size;
|
size_t queued_size;
|
||||||
uint8_t *queued_buff = NULL;
|
uint8_t *queued_buff = NULL;
|
||||||
bool is_stashed_data = false;
|
bool is_stashed_data = false;
|
||||||
@@ -91,7 +99,10 @@ static void usb_serial_jtag_isr_handler_default(void *arg) {
|
|||||||
|
|
||||||
// On ringbuffer wrap-around the size can be 0 even though the buffer returned is not NULL
|
// On ringbuffer wrap-around the size can be 0 even though the buffer returned is not NULL
|
||||||
if (queued_size > 0) {
|
if (queued_size > 0) {
|
||||||
|
portENTER_CRITICAL_ISR(&p_usb_serial_jtag_obj->spinlock);
|
||||||
|
atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_BUSY);
|
||||||
uint32_t sent_size = usb_serial_jtag_write_and_flush(queued_buff, queued_size);
|
uint32_t sent_size = usb_serial_jtag_write_and_flush(queued_buff, queued_size);
|
||||||
|
portEXIT_CRITICAL_ISR(&p_usb_serial_jtag_obj->spinlock);
|
||||||
|
|
||||||
if (sent_size < queued_size) {
|
if (sent_size < queued_size) {
|
||||||
// Not all bytes could be sent at once; stash the unwritten bytes in a tx buffer
|
// Not all bytes could be sent at once; stash the unwritten bytes in a tx buffer
|
||||||
@@ -108,7 +119,6 @@ static void usb_serial_jtag_isr_handler_default(void *arg) {
|
|||||||
if (is_stashed_data == false) {
|
if (is_stashed_data == false) {
|
||||||
vRingbufferReturnItemFromISR(p_usb_serial_jtag_obj->tx_ring_buf, queued_buff, &xTaskWoken);
|
vRingbufferReturnItemFromISR(p_usb_serial_jtag_obj->tx_ring_buf, queued_buff, &xTaskWoken);
|
||||||
}
|
}
|
||||||
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
|
||||||
} else {
|
} else {
|
||||||
// The last transmit may have sent a full EP worth of data. The host will interpret
|
// The last transmit may have sent a full EP worth of data. The host will interpret
|
||||||
// this as a transaction that hasn't finished yet and keep the data in its internal
|
// this as a transaction that hasn't finished yet and keep the data in its internal
|
||||||
@@ -119,6 +129,7 @@ static void usb_serial_jtag_isr_handler_default(void *arg) {
|
|||||||
// flush will not by itself cause this ISR to be called again.
|
// flush will not by itself cause this ISR to be called again.
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_IDLE);
|
||||||
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +158,7 @@ esp_err_t usb_serial_jtag_driver_install(usb_serial_jtag_driver_config_t *usb_se
|
|||||||
p_usb_serial_jtag_obj->rx_buf_size = usb_serial_jtag_config->rx_buffer_size;
|
p_usb_serial_jtag_obj->rx_buf_size = usb_serial_jtag_config->rx_buffer_size;
|
||||||
p_usb_serial_jtag_obj->tx_buf_size = usb_serial_jtag_config->tx_buffer_size;
|
p_usb_serial_jtag_obj->tx_buf_size = usb_serial_jtag_config->tx_buffer_size;
|
||||||
p_usb_serial_jtag_obj->tx_stash_cnt = 0;
|
p_usb_serial_jtag_obj->tx_stash_cnt = 0;
|
||||||
|
p_usb_serial_jtag_obj->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||||
if (p_usb_serial_jtag_obj == NULL) {
|
if (p_usb_serial_jtag_obj == NULL) {
|
||||||
ESP_LOGE(USB_SERIAL_JTAG_TAG, "memory allocate error");
|
ESP_LOGE(USB_SERIAL_JTAG_TAG, "memory allocate error");
|
||||||
err = ESP_ERR_NO_MEM;
|
err = ESP_ERR_NO_MEM;
|
||||||
@@ -171,6 +183,7 @@ esp_err_t usb_serial_jtag_driver_install(usb_serial_jtag_driver_config_t *usb_se
|
|||||||
USJ_RCC_ATOMIC() {
|
USJ_RCC_ATOMIC() {
|
||||||
usb_serial_jtag_ll_enable_bus_clock(true);
|
usb_serial_jtag_ll_enable_bus_clock(true);
|
||||||
}
|
}
|
||||||
|
atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_IDLE);
|
||||||
|
|
||||||
// Configure PHY
|
// Configure PHY
|
||||||
usb_phy_ll_int_jtag_enable(&USB_SERIAL_JTAG);
|
usb_phy_ll_int_jtag_enable(&USB_SERIAL_JTAG);
|
||||||
@@ -220,10 +233,22 @@ int usb_serial_jtag_write_bytes(const void* src, size_t size, TickType_t ticks_t
|
|||||||
ESP_RETURN_ON_FALSE(src != NULL, ESP_ERR_INVALID_ARG, USB_SERIAL_JTAG_TAG, "Invalid buffer pointer.");
|
ESP_RETURN_ON_FALSE(src != NULL, ESP_ERR_INVALID_ARG, USB_SERIAL_JTAG_TAG, "Invalid buffer pointer.");
|
||||||
ESP_RETURN_ON_FALSE(p_usb_serial_jtag_obj != NULL, ESP_ERR_INVALID_ARG, USB_SERIAL_JTAG_TAG, "The driver hasn't been initialized");
|
ESP_RETURN_ON_FALSE(p_usb_serial_jtag_obj != NULL, ESP_ERR_INVALID_ARG, USB_SERIAL_JTAG_TAG, "The driver hasn't been initialized");
|
||||||
|
|
||||||
|
size_t sent_data = 0;
|
||||||
|
BaseType_t result = pdTRUE;
|
||||||
const uint8_t *buff = (const uint8_t *)src;
|
const uint8_t *buff = (const uint8_t *)src;
|
||||||
|
if (p_usb_serial_jtag_obj->fifo_status == FIFO_IDLE) {
|
||||||
|
portENTER_CRITICAL(&p_usb_serial_jtag_obj->spinlock);
|
||||||
|
atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_BUSY);
|
||||||
|
sent_data = usb_serial_jtag_write_and_flush(src, size);
|
||||||
|
portEXIT_CRITICAL(&p_usb_serial_jtag_obj->spinlock);
|
||||||
|
}
|
||||||
|
|
||||||
// Blocking method, Sending data to ringbuffer, and handle the data in ISR.
|
// Blocking method, Sending data to ringbuffer, and handle the data in ISR.
|
||||||
BaseType_t result = xRingbufferSend(p_usb_serial_jtag_obj->tx_ring_buf, (void*) (buff), size, ticks_to_wait);
|
if (size - sent_data > 0) {
|
||||||
// Now trigger the ISR to read data from the ring buffer.
|
result = xRingbufferSend(p_usb_serial_jtag_obj->tx_ring_buf, (void*) (buff+sent_data), size-sent_data, ticks_to_wait);
|
||||||
|
} else {
|
||||||
|
atomic_store(&p_usb_serial_jtag_obj->fifo_status, FIFO_IDLE);
|
||||||
|
}
|
||||||
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
return (result == pdFALSE) ? 0 : size;
|
return (result == pdFALSE) ? 0 : size;
|
||||||
}
|
}
|
||||||
|
@@ -397,3 +397,11 @@ examples/peripherals/uart/uart_echo_rs485:
|
|||||||
examples/peripherals/usb:
|
examples/peripherals/usb:
|
||||||
disable:
|
disable:
|
||||||
- if: SOC_USB_OTG_SUPPORTED != 1
|
- if: SOC_USB_OTG_SUPPORTED != 1
|
||||||
|
|
||||||
|
examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo:
|
||||||
|
disable:
|
||||||
|
- if: SOC_USB_SERIAL_JTAG_SUPPORTED != 1
|
||||||
|
disable_test:
|
||||||
|
- if: IDF_TARGET not in ["esp32c6"]
|
||||||
|
temporary: true
|
||||||
|
reason: lack of runners. Hardware is similar, test on one target is enough currently.
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||||
|
# in this exact order for cmake to work correctly
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(usb_serial_jtag_echo)
|
@@ -0,0 +1,60 @@
|
|||||||
|
| Supported Targets | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 |
|
||||||
|
| ----------------- | -------- | -------- | -------- | -------- |
|
||||||
|
|
||||||
|
# USB SERIAL JTAG Echo Example
|
||||||
|
|
||||||
|
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||||
|
|
||||||
|
This example demonstrates how to utilize USB_SERIAL_JTAG interfaces by echoing back to the sender any data received on USB_SERIAL_JTAG.
|
||||||
|
|
||||||
|
## How to use example
|
||||||
|
|
||||||
|
### Hardware Required
|
||||||
|
|
||||||
|
The example can be run on development board that supports usb_serial_jtag, that is based on the Espressif SoC. The board shall be connected to a computer with a single USB cable for flashing and monitoring with UART port. The usb_serial_jtag port on board can be be connected to computer with another USB cable to get the echo.
|
||||||
|
|
||||||
|
### Configure the project
|
||||||
|
|
||||||
|
Use the command below to configure project using Kconfig menu as showed in the table above.
|
||||||
|
The default Kconfig values can be changed such as disable the `ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG`
|
||||||
|
```
|
||||||
|
idf.py menuconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build and Flash
|
||||||
|
|
||||||
|
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||||
|
|
||||||
|
```
|
||||||
|
idf.py -p PORT flash monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||||
|
|
||||||
|
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
Type some characters in the terminal connected to the external serial interface. As result you should see echo in the same terminal which you used for typing the characters.
|
||||||
|
|
||||||
|
For example, If I type `hi espressif`, `See you again!`, `Echo a very long buffer. Assume this buffer is very large and you can see whole buffer`
|
||||||
|
|
||||||
|
And you can get the log with:
|
||||||
|
|
||||||
|
```
|
||||||
|
I (296) main_task: Started on CPU0
|
||||||
|
I (296) main_task: Calling app_main()
|
||||||
|
I (296) main_task: Returned from app_main()
|
||||||
|
I (13346) Recv str: : 0x408117b8 68 69 20 65 73 70 72 65 73 73 69 66 0d 0a |hi espressif..|
|
||||||
|
I (16606) Recv str: : 0x408117b8 53 65 65 20 79 6f 75 20 61 67 61 69 6e 21 0d 0a |See you again!..|
|
||||||
|
I (18726) Recv str: : 0x408117b8 45 63 68 6f 20 61 20 76 65 72 79 20 6c 6f 6e 67 |Echo a very long|
|
||||||
|
I (18726) Recv str: : 0x408117c8 20 62 75 66 66 65 72 2e 20 41 73 73 75 6d 65 20 | buffer. Assume |
|
||||||
|
I (18726) Recv str: : 0x408117d8 74 68 69 73 20 62 75 66 66 65 72 20 69 73 20 76 |this buffer is v|
|
||||||
|
I (18736) Recv str: : 0x408117e8 65 72 79 20 6c 61 72 67 65 20 61 6e 64 20 79 6f |ery large and yo|
|
||||||
|
I (18746) Recv str: : 0x408117b8 75 20 63 61 6e 20 73 65 65 20 77 68 6f 6c 65 20 |u can see whole |
|
||||||
|
I (18756) Recv str: : 0x408117c8 62 75 66 66 65 72 0d 0a |buffer..|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Note that you are not supposed to see the echo in the terminal if usb_serial_jtag port is used for flashing and monitoring.
|
@@ -0,0 +1,2 @@
|
|||||||
|
idf_component_register(SRCS "usb_serial_echo_main.c"
|
||||||
|
INCLUDE_DIRS ".")
|
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "driver/usb_serial_jtag.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
|
||||||
|
#define BUF_SIZE (1024)
|
||||||
|
#define ECHO_TASK_STACK_SIZE (2048)
|
||||||
|
|
||||||
|
static void echo_task(void *arg)
|
||||||
|
{
|
||||||
|
// Configure USB SERIAL JTAG
|
||||||
|
usb_serial_jtag_driver_config_t usb_serial_jtag_config = {
|
||||||
|
.rx_buffer_size = BUF_SIZE,
|
||||||
|
.tx_buffer_size = BUF_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&usb_serial_jtag_config));
|
||||||
|
ESP_LOGI("usb_serial_jtag echo", "USB_SERIAL_JTAG init done");
|
||||||
|
|
||||||
|
// Configure a temporary buffer for the incoming data
|
||||||
|
uint8_t *data = (uint8_t *) malloc(BUF_SIZE);
|
||||||
|
if (data == NULL) {
|
||||||
|
ESP_LOGE("usb_serial_jtag echo", "no memory for data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
int len = usb_serial_jtag_read_bytes(data, (BUF_SIZE - 1), 20 / portTICK_PERIOD_MS);
|
||||||
|
|
||||||
|
// Write data back to the USB SERIAL JTAG
|
||||||
|
if (len) {
|
||||||
|
usb_serial_jtag_write_bytes((const char *) data, len, 20 / portTICK_PERIOD_MS);
|
||||||
|
data[len] = '\0';
|
||||||
|
ESP_LOG_BUFFER_HEXDUMP("Recv str: ", data, len, ESP_LOG_INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_main(void)
|
||||||
|
{
|
||||||
|
xTaskCreate(echo_task, "USB SERIAL JTAG_echo_task", ECHO_TASK_STACK_SIZE, NULL, 10, NULL);
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
from pytest_embedded import Dut
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.esp32c6 # usb_serial_jtag is very similar, test C6 is enough.
|
||||||
|
@pytest.mark.usj_device
|
||||||
|
def test_usb_device_serial_example(dut: Dut) -> None:
|
||||||
|
dut.expect_exact('USB_SERIAL_JTAG init done')
|
||||||
|
sleep(2)
|
||||||
|
|
||||||
|
ports = list(serial.tools.list_ports.comports())
|
||||||
|
for p in ports:
|
||||||
|
if (p.device == '/dev/ttyACM0'): # Get the usb_serial_jtag port
|
||||||
|
with serial.Serial(p.device) as s:
|
||||||
|
s.write(b'hi, espressif\n')
|
||||||
|
sleep(1)
|
||||||
|
dut.expect_exact('hi, espressif')
|
||||||
|
res = s.readline()
|
||||||
|
assert b'hi, espressif' in res
|
||||||
|
s.write(b'See you again!\n')
|
||||||
|
sleep(1)
|
||||||
|
dut.expect_exact('See you again!')
|
||||||
|
res = s.readline()
|
||||||
|
assert b'See you again!' in res
|
||||||
|
s.write(b'Echo a very long buffer. Assume this buffer is very large and you can see whole buffer\n')
|
||||||
|
sleep(1)
|
||||||
|
dut.expect_exact('Echo a very long') # 16 bytes a line because we use `ESP_LOG_BUFFER_HEXDUMP` in code.
|
||||||
|
dut.expect_exact(' buffer. Assume ')
|
||||||
|
dut.expect_exact('this buffer is v')
|
||||||
|
dut.expect_exact('ery large and yo')
|
||||||
|
dut.expect_exact('u can see whole ')
|
||||||
|
dut.expect_exact('buffer')
|
||||||
|
res = s.readline()
|
||||||
|
assert b'Echo a very long buffer. Assume this buffer is very large and you can see whole buffer' in res
|
||||||
|
s.write(b'64 bytes buffer:-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-\n')
|
||||||
|
sleep(1)
|
||||||
|
res = s.readline()
|
||||||
|
assert b'64 bytes buffer:-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-' in res
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
raise Exception('usb_serial_jtag port not found')
|
@@ -0,0 +1 @@
|
|||||||
|
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
@@ -108,6 +108,7 @@ ENV_MARKERS = {
|
|||||||
'twai_network': 'multiple runners form a TWAI network.',
|
'twai_network': 'multiple runners form a TWAI network.',
|
||||||
'sdio_master_slave': 'Test sdio multi board, esp32+esp32',
|
'sdio_master_slave': 'Test sdio multi board, esp32+esp32',
|
||||||
'sdio_multidev_32_c6': 'Test sdio multi board, esp32+esp32c6',
|
'sdio_multidev_32_c6': 'Test sdio multi board, esp32+esp32c6',
|
||||||
|
'usj_device': 'Test usb_serial_jtag and usb_serial_jtag is used as serial only (not console)'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user