Supervisor: move most of systick to the supervisor
This code is shared by most parts, except where not all the #ifdefs inside the tick function were present in all ports. This mostly would have broken gamepad tick support on non-samd ports. The "ms32" and "ms64" variants of the tick functions are introduced because there is no 64-bit atomic read. Disabling interrupts avoids a low probability bug where milliseconds could be off by ~49.5 days once every ~49.5 days (2^32 ms). Avoiding disabling interrupts when only the low 32 bits are needed is a minor optimization. Testing performed: on metro m4 express, USB still works and time.monotonic_ns() still counts up
This commit is contained in:
parent
45d1b290ee
commit
7f744a2369
@ -52,7 +52,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "tick.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
//#include "Ethernet/socket.h"
|
||||
//#include "Internet/DNS/dns.h"
|
||||
@ -125,7 +125,7 @@ uint16_t DNS_MSGID; // DNS message ID
|
||||
|
||||
|
||||
uint32_t HAL_GetTick(void) {
|
||||
return ticks_ms;
|
||||
return supervisor_ticks_ms32();
|
||||
}
|
||||
|
||||
uint32_t hal_sys_tick;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "audio_dma.h"
|
||||
#include "tick.h"
|
||||
#include "supervisor/filesystem.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "supervisor/usb.h"
|
||||
|
||||
#include "py/runtime.h"
|
||||
@ -71,9 +72,9 @@ void run_background_tasks(void) {
|
||||
running_background_tasks = false;
|
||||
assert_heap_ok();
|
||||
|
||||
last_finished_tick = ticks_ms;
|
||||
last_finished_tick = supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
bool background_tasks_ok(void) {
|
||||
return ticks_ms - last_finished_tick < 1000;
|
||||
return supervisor_ticks_ms64() - last_finished_tick < 1000;
|
||||
}
|
||||
|
@ -34,8 +34,7 @@
|
||||
#include "py/runtime.h"
|
||||
#include "py/stream.h"
|
||||
#include "supervisor/shared/translate.h"
|
||||
|
||||
#include "tick.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
#include "hpl_sercom_config.h"
|
||||
#include "peripheral_clk_config.h"
|
||||
@ -272,10 +271,10 @@ size_t common_hal_busio_uart_read(busio_uart_obj_t *self, uint8_t *data, size_t
|
||||
usart_async_get_io_descriptor(usart_desc_p, &io);
|
||||
|
||||
size_t total_read = 0;
|
||||
uint64_t start_ticks = ticks_ms;
|
||||
uint64_t start_ticks = supervisor_ticks_ms64();
|
||||
|
||||
// Busy-wait until timeout or until we've read enough chars.
|
||||
while (ticks_ms - start_ticks <= self->timeout_ms) {
|
||||
while (supervisor_ticks_ms64() - start_ticks <= self->timeout_ms) {
|
||||
// Read as many chars as we can right now, up to len.
|
||||
size_t num_read = io_read(io, data, len);
|
||||
|
||||
@ -289,7 +288,7 @@ size_t common_hal_busio_uart_read(busio_uart_obj_t *self, uint8_t *data, size_t
|
||||
}
|
||||
if (num_read > 0) {
|
||||
// Reset the timeout on every character read.
|
||||
start_ticks = ticks_ms;
|
||||
start_ticks = supervisor_ticks_ms64();
|
||||
}
|
||||
RUN_BACKGROUND_TASKS;
|
||||
// Allow user to break out of a timeout with a KeyboardInterrupt.
|
||||
@ -330,9 +329,9 @@ size_t common_hal_busio_uart_write(busio_uart_obj_t *self, const uint8_t *data,
|
||||
|
||||
// Wait until write is complete or timeout.
|
||||
bool done = false;
|
||||
uint64_t start_ticks = ticks_ms;
|
||||
uint64_t start_ticks = supervisor_ticks_ms64();
|
||||
// Busy-wait for timeout.
|
||||
while (ticks_ms - start_ticks < self->timeout_ms) {
|
||||
while (supervisor_ticks_ms64() - start_ticks < self->timeout_ms) {
|
||||
if (usart_async_is_tx_empty(usart_desc_p)) {
|
||||
done = true;
|
||||
break;
|
||||
|
@ -28,10 +28,10 @@
|
||||
|
||||
#include "shared-bindings/time/__init__.h"
|
||||
|
||||
#include "tick.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
inline uint64_t common_hal_time_monotonic() {
|
||||
return ticks_ms;
|
||||
return supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
void common_hal_time_delay_ms(uint32_t delay) {
|
||||
|
@ -45,12 +45,12 @@
|
||||
#include "mpconfigboard.h"
|
||||
#include "mphalport.h"
|
||||
#include "reset.h"
|
||||
#include "tick.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
extern uint32_t common_hal_mcu_processor_get_frequency(void);
|
||||
|
||||
void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
uint64_t start_tick = ticks_ms;
|
||||
uint64_t start_tick = supervisor_ticks_ms64();
|
||||
uint64_t duration = 0;
|
||||
while (duration < delay) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
@ -59,7 +59,7 @@ void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
|
||||
break;
|
||||
}
|
||||
duration = (ticks_ms - start_tick);
|
||||
duration = (supervisor_ticks_ms64() - start_tick);
|
||||
// TODO(tannewt): Go to sleep for a little while while we wait.
|
||||
}
|
||||
}
|
||||
|
@ -31,11 +31,11 @@
|
||||
|
||||
#include "lib/oofatfs/ff.h"
|
||||
|
||||
// Global millisecond tick count (driven by SysTick interrupt).
|
||||
extern volatile uint64_t ticks_ms;
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
// Global millisecond tick count (driven by SysTick interrupt).
|
||||
static inline mp_uint_t mp_hal_ticks_ms(void) {
|
||||
return ticks_ms;
|
||||
return supervisor_ticks_ms32();
|
||||
}
|
||||
// Number of bytes in receive buffer
|
||||
volatile uint8_t usb_rx_count;
|
||||
|
@ -28,47 +28,21 @@
|
||||
|
||||
#include "peripheral_clk_config.h"
|
||||
|
||||
#include "supervisor/shared/autoreload.h"
|
||||
#include "supervisor/filesystem.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "shared-bindings/microcontroller/__init__.h"
|
||||
#include "shared-bindings/microcontroller/Processor.h"
|
||||
|
||||
#if CIRCUITPY_GAMEPAD
|
||||
#include "shared-module/gamepad/__init__.h"
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_GAMEPADSHIFT
|
||||
#include "shared-module/gamepadshift/__init__.h"
|
||||
#endif
|
||||
// Global millisecond tick count
|
||||
volatile uint64_t ticks_ms = 0;
|
||||
|
||||
void SysTick_Handler(void) {
|
||||
// SysTick interrupt handler called when the SysTick timer reaches zero
|
||||
// (every millisecond).
|
||||
common_hal_mcu_disable_interrupts();
|
||||
ticks_ms += 1;
|
||||
|
||||
// Read the control register to reset the COUNTFLAG.
|
||||
(void) SysTick->CTRL;
|
||||
common_hal_mcu_enable_interrupts();
|
||||
|
||||
#if CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS > 0
|
||||
filesystem_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
|
||||
autoreload_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_GAMEPAD_TICKS
|
||||
if (!(ticks_ms & CIRCUITPY_GAMEPAD_TICKS)) {
|
||||
#if CIRCUITPY_GAMEPAD
|
||||
gamepad_tick();
|
||||
#endif
|
||||
#if CIRCUITPY_GAMEPADSHIFT
|
||||
gamepadshift_tick();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// Do things common to all ports when the tick occurs
|
||||
supervisor_tick();
|
||||
}
|
||||
|
||||
void tick_init() {
|
||||
@ -115,7 +89,7 @@ void current_tick(uint64_t* ms, uint32_t* us_until_ms) {
|
||||
uint32_t tick_status = SysTick->CTRL;
|
||||
uint32_t current_us = SysTick->VAL;
|
||||
uint32_t tick_status2 = SysTick->CTRL;
|
||||
uint64_t current_ms = ticks_ms;
|
||||
uint64_t current_ms = supervisor_ticks_ms64();
|
||||
// The second clause ensures our value actually rolled over. Its possible it hit zero between
|
||||
// the VAL read and CTRL read.
|
||||
if ((tick_status & SysTick_CTRL_COUNTFLAG_Msk) != 0 ||
|
||||
@ -129,5 +103,5 @@ void current_tick(uint64_t* ms, uint32_t* us_until_ms) {
|
||||
|
||||
void wait_until(uint64_t ms, uint32_t us_until_ms) {
|
||||
uint32_t ticks_per_us = common_hal_mcu_processor_get_frequency() / 1000 / 1000;
|
||||
while (ticks_ms <= ms && SysTick->VAL / ticks_per_us >= us_until_ms) {}
|
||||
while (supervisor_ticks_ms64() <= ms && SysTick->VAL / ticks_per_us >= us_until_ms) {}
|
||||
}
|
||||
|
@ -28,8 +28,6 @@
|
||||
|
||||
#include "py/mpconfig.h"
|
||||
|
||||
extern volatile uint64_t ticks_ms;
|
||||
|
||||
extern struct timer_descriptor ms_timer;
|
||||
|
||||
void tick_init(void);
|
||||
|
@ -26,10 +26,10 @@
|
||||
|
||||
#include "py/mphal.h"
|
||||
|
||||
#include "tick.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
uint64_t common_hal_time_monotonic(void) {
|
||||
return ticks_ms;
|
||||
return supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
void common_hal_time_delay_ms(uint32_t delay) {
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
#include "py/mpstate.h"
|
||||
|
||||
#include "tick.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
#define DELAY_CORRECTION (700)
|
||||
#define DELAY_INTERVAL (50)
|
||||
@ -57,7 +57,7 @@ mp_uint_t mp_hal_ticks_cpu(void) {
|
||||
}
|
||||
|
||||
void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
uint64_t start_tick = ticks_ms;
|
||||
uint64_t start_tick = supervisor_ticks_ms64();
|
||||
uint64_t duration = 0;
|
||||
while (duration < delay) {
|
||||
#ifdef MICROPY_VM_HOOK_LOOP
|
||||
@ -68,7 +68,7 @@ void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
|
||||
break;
|
||||
}
|
||||
duration = (ticks_ms - start_tick);
|
||||
duration = (supervisor_ticks_ms64() - start_tick);
|
||||
// TODO(tannewt): Go to sleep for a little while while we wait.
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,4 @@
|
||||
|
||||
#include "lib/utils/interrupt_char.h"
|
||||
|
||||
extern volatile uint64_t ticks_ms;
|
||||
|
||||
#endif // MICROPY_INCLUDED_CXD56_MPHALPORT_H
|
||||
|
@ -27,19 +27,10 @@
|
||||
#include "tick.h"
|
||||
|
||||
#include "supervisor/shared/autoreload.h"
|
||||
#include "supervisor/filesystem.h"
|
||||
|
||||
// Global millisecond tick count
|
||||
volatile uint64_t ticks_ms = 0;
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
void board_timerhook(void)
|
||||
{
|
||||
ticks_ms += 1;
|
||||
|
||||
#if CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS > 0
|
||||
filesystem_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
|
||||
autoreload_tick();
|
||||
#endif
|
||||
// Do things common to all ports when the tick occurs
|
||||
supervisor_tick();
|
||||
}
|
||||
|
@ -29,6 +29,4 @@
|
||||
|
||||
#include "py/mpconfig.h"
|
||||
|
||||
extern volatile uint64_t ticks_ms;
|
||||
|
||||
#endif // MICROPY_INCLUDED_CXD56_TICK_H
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "py/objstr.h"
|
||||
#include "py/runtime.h"
|
||||
#include "supervisor/shared/safe_mode.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "supervisor/usb.h"
|
||||
#include "shared-bindings/_bleio/__init__.h"
|
||||
#include "shared-bindings/_bleio/Adapter.h"
|
||||
@ -353,7 +354,7 @@ STATIC bool scan_on_ble_evt(ble_evt_t *ble_evt, void *scan_results_in) {
|
||||
ble_gap_evt_adv_report_t *report = &ble_evt->evt.gap_evt.params.adv_report;
|
||||
|
||||
shared_module_bleio_scanresults_append(scan_results,
|
||||
ticks_ms,
|
||||
supervisor_ticks_ms64(),
|
||||
report->type.connectable,
|
||||
report->type.scan_response,
|
||||
report->rssi,
|
||||
|
@ -39,6 +39,7 @@
|
||||
|
||||
#include "shared-bindings/_bleio/__init__.h"
|
||||
#include "shared-bindings/_bleio/Connection.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "common-hal/_bleio/CharacteristicBuffer.h"
|
||||
|
||||
STATIC void write_to_ringbuf(bleio_characteristic_buffer_obj_t *self, uint8_t *data, uint16_t len) {
|
||||
@ -100,10 +101,10 @@ void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffe
|
||||
}
|
||||
|
||||
int common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self, uint8_t *data, size_t len, int *errcode) {
|
||||
uint64_t start_ticks = ticks_ms;
|
||||
uint64_t start_ticks = supervisor_ticks_ms64();
|
||||
|
||||
// Wait for all bytes received or timeout
|
||||
while ( (ringbuf_count(&self->ringbuf) < len) && (ticks_ms - start_ticks < self->timeout_ms) ) {
|
||||
while ( (ringbuf_count(&self->ringbuf) < len) && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms) ) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
// Allow user to break out of a timeout with a KeyboardInterrupt.
|
||||
if ( mp_hal_is_interrupted() ) {
|
||||
|
@ -231,10 +231,10 @@ size_t common_hal_busio_uart_read(busio_uart_obj_t *self, uint8_t *data, size_t
|
||||
}
|
||||
|
||||
size_t rx_bytes = 0;
|
||||
uint64_t start_ticks = ticks_ms;
|
||||
uint64_t start_ticks = supervisor_ticks_ms64();
|
||||
|
||||
// Wait for all bytes received or timeout
|
||||
while ( (ringbuf_count(&self->rbuf) < len) && (ticks_ms - start_ticks < self->timeout_ms) ) {
|
||||
while ( (ringbuf_count(&self->rbuf) < len) && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms) ) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
// Allow user to break out of a timeout with a KeyboardInterrupt.
|
||||
if ( mp_hal_is_interrupted() ) {
|
||||
@ -265,15 +265,15 @@ size_t common_hal_busio_uart_write (busio_uart_obj_t *self, const uint8_t *data,
|
||||
|
||||
if ( len == 0 ) return 0;
|
||||
|
||||
uint64_t start_ticks = ticks_ms;
|
||||
uint64_t start_ticks = supervisor_ticks_ms64();
|
||||
|
||||
// Wait for on-going transfer to complete
|
||||
while ( nrfx_uarte_tx_in_progress(self->uarte) && (ticks_ms - start_ticks < self->timeout_ms) ) {
|
||||
while ( nrfx_uarte_tx_in_progress(self->uarte) && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms) ) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
}
|
||||
|
||||
// Time up
|
||||
if ( !(ticks_ms - start_ticks < self->timeout_ms) ) {
|
||||
if ( !(supervisor_ticks_ms64() - start_ticks < self->timeout_ms) ) {
|
||||
*errcode = MP_EAGAIN;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
@ -290,7 +290,7 @@ size_t common_hal_busio_uart_write (busio_uart_obj_t *self, const uint8_t *data,
|
||||
_VERIFY_ERR(*errcode);
|
||||
(*errcode) = 0;
|
||||
|
||||
while ( nrfx_uarte_tx_in_progress(self->uarte) && (ticks_ms - start_ticks < self->timeout_ms) ) {
|
||||
while ( nrfx_uarte_tx_in_progress(self->uarte) && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms) ) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
#include "tick.h"
|
||||
|
||||
uint64_t common_hal_time_monotonic(void) {
|
||||
return ticks_ms;
|
||||
return supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
void common_hal_time_delay_ms(uint32_t delay) {
|
||||
|
@ -31,12 +31,13 @@
|
||||
#include "py/mphal.h"
|
||||
#include "py/mpstate.h"
|
||||
#include "py/gc.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
/*------------------------------------------------------------------*/
|
||||
/* delay
|
||||
*------------------------------------------------------------------*/
|
||||
void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
uint64_t start_tick = ticks_ms;
|
||||
uint64_t start_tick = supervisor_ticks_ms64();
|
||||
uint64_t duration = 0;
|
||||
while (duration < delay) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
@ -45,7 +46,7 @@ void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
|
||||
break;
|
||||
}
|
||||
duration = (ticks_ms - start_tick);
|
||||
duration = (supervisor_ticks_ms64() - start_tick);
|
||||
// TODO(tannewt): Go to sleep for a little while while we wait.
|
||||
}
|
||||
}
|
||||
|
@ -33,12 +33,11 @@
|
||||
#include "lib/utils/interrupt_char.h"
|
||||
#include "nrfx_uarte.h"
|
||||
#include "py/mpconfig.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
extern nrfx_uarte_t serial_instance;
|
||||
|
||||
extern volatile uint64_t ticks_ms;
|
||||
|
||||
#define mp_hal_ticks_ms() ((mp_uint_t) ticks_ms)
|
||||
#define mp_hal_ticks_ms() ((mp_uint_t) supervisor_ticks_ms32())
|
||||
#define mp_hal_delay_us(us) NRFX_DELAY_US((uint32_t) (us))
|
||||
|
||||
bool mp_hal_stdin_any(void);
|
||||
|
@ -26,31 +26,14 @@
|
||||
|
||||
#include "tick.h"
|
||||
|
||||
#include "supervisor/shared/autoreload.h"
|
||||
#include "supervisor/filesystem.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "shared-module/gamepad/__init__.h"
|
||||
#include "shared-bindings/microcontroller/Processor.h"
|
||||
#include "nrf.h"
|
||||
|
||||
// Global millisecond tick count
|
||||
volatile uint64_t ticks_ms = 0;
|
||||
|
||||
void SysTick_Handler(void) {
|
||||
// SysTick interrupt handler called when the SysTick timer reaches zero
|
||||
// (every millisecond).
|
||||
ticks_ms += 1;
|
||||
|
||||
#if CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS > 0
|
||||
filesystem_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
|
||||
autoreload_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_GAMEPAD_TICKS
|
||||
if (!(ticks_ms & CIRCUITPY_GAMEPAD_TICKS)) {
|
||||
gamepad_tick();
|
||||
}
|
||||
#endif
|
||||
// Do things common to all ports when the tick occurs
|
||||
supervisor_tick();
|
||||
}
|
||||
|
||||
void tick_init() {
|
||||
@ -61,11 +44,11 @@ void tick_init() {
|
||||
void tick_delay(uint32_t us) {
|
||||
uint32_t ticks_per_us = common_hal_mcu_processor_get_frequency() / 1000 / 1000;
|
||||
uint32_t us_between_ticks = SysTick->VAL / ticks_per_us;
|
||||
uint64_t start_ms = ticks_ms;
|
||||
uint64_t start_ms = supervisor_ticks_ms64();
|
||||
while (us > 1000) {
|
||||
while (ticks_ms == start_ms) {}
|
||||
while (supervisor_ticks_ms64() == start_ms) {}
|
||||
us -= us_between_ticks;
|
||||
start_ms = ticks_ms;
|
||||
start_ms = supervisor_ticks_ms64();
|
||||
us_between_ticks = 1000;
|
||||
}
|
||||
while (SysTick->VAL > ((us_between_ticks - us) * ticks_per_us)) {}
|
||||
@ -74,11 +57,11 @@ void tick_delay(uint32_t us) {
|
||||
// us counts down!
|
||||
void current_tick(uint64_t* ms, uint32_t* us_until_ms) {
|
||||
uint32_t ticks_per_us = common_hal_mcu_processor_get_frequency() / 1000 / 1000;
|
||||
*ms = ticks_ms;
|
||||
*ms = supervisor_ticks_ms64();
|
||||
*us_until_ms = SysTick->VAL / ticks_per_us;
|
||||
}
|
||||
|
||||
void wait_until(uint64_t ms, uint32_t us_until_ms) {
|
||||
uint32_t ticks_per_us = common_hal_mcu_processor_get_frequency() / 1000 / 1000;
|
||||
while(ticks_ms <= ms && SysTick->VAL / ticks_per_us >= us_until_ms) {}
|
||||
while(supervisor_ticks_ms64() <= ms && SysTick->VAL / ticks_per_us >= us_until_ms) {}
|
||||
}
|
||||
|
@ -30,8 +30,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern volatile uint64_t ticks_ms;
|
||||
|
||||
extern struct timer_descriptor ms_timer;
|
||||
|
||||
void tick_init(void);
|
||||
|
@ -29,7 +29,7 @@
|
||||
#include "tick.h"
|
||||
|
||||
uint64_t common_hal_time_monotonic(void) {
|
||||
return ticks_ms;
|
||||
return supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
void common_hal_time_delay_ms(uint32_t delay) {
|
||||
|
@ -31,11 +31,13 @@
|
||||
#include "py/mpstate.h"
|
||||
#include "py/gc.h"
|
||||
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
/*------------------------------------------------------------------*/
|
||||
/* delay
|
||||
*------------------------------------------------------------------*/
|
||||
void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
uint64_t start_tick = ticks_ms;
|
||||
uint64_t start_tick = supervisor_ticks_ms64();
|
||||
uint64_t duration = 0;
|
||||
while (duration < delay) {
|
||||
#ifdef MICROPY_VM_HOOK_LOOP
|
||||
@ -46,7 +48,7 @@ void mp_hal_delay_ms(mp_uint_t delay) {
|
||||
MP_STATE_VM(mp_pending_exception) == MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_reload_exception))) {
|
||||
break;
|
||||
}
|
||||
duration = (ticks_ms - start_tick);
|
||||
duration = (supervisor_ticks_ms64() - start_tick);
|
||||
// TODO(tannewt): Go to sleep for a little while while we wait.
|
||||
}
|
||||
}
|
||||
|
@ -32,10 +32,10 @@
|
||||
|
||||
#include "lib/utils/interrupt_char.h"
|
||||
#include "py/mpconfig.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
extern volatile uint64_t ticks_ms;
|
||||
|
||||
#define mp_hal_ticks_ms() ((mp_uint_t) ticks_ms)
|
||||
#define mp_hal_ticks_ms() ((mp_uint_t) supervisor_ticks_ms32())
|
||||
//#define mp_hal_delay_us(us) NRFX_DELAY_US((uint32_t) (us))
|
||||
|
||||
bool mp_hal_stdin_any(void);
|
||||
|
@ -26,37 +26,23 @@
|
||||
|
||||
#include "tick.h"
|
||||
|
||||
#include "supervisor/shared/autoreload.h"
|
||||
#include "supervisor/filesystem.h"
|
||||
#include "shared-module/gamepad/__init__.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "shared-bindings/microcontroller/Processor.h"
|
||||
|
||||
#include "stm32f4xx.h"
|
||||
|
||||
// Global millisecond tick count
|
||||
volatile uint64_t ticks_ms = 0;
|
||||
|
||||
void SysTick_Handler(void) {
|
||||
// SysTick interrupt handler called when the SysTick timer reaches zero
|
||||
// (every millisecond).
|
||||
ticks_ms += 1;
|
||||
|
||||
#if CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS > 0
|
||||
filesystem_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
|
||||
autoreload_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_GAMEPAD_TICKS
|
||||
if (!(ticks_ms & CIRCUITPY_GAMEPAD_TICKS)) {
|
||||
gamepad_tick();
|
||||
}
|
||||
#endif
|
||||
// Do things common to all ports when the tick occurs
|
||||
supervisor_tick();
|
||||
}
|
||||
|
||||
uint32_t HAL_GetTick(void) //override ST HAL
|
||||
{
|
||||
return (uint32_t)ticks_ms;
|
||||
return (uint32_t)supervisor_ticks_ms32();
|
||||
}
|
||||
|
||||
void tick_init() {
|
||||
@ -72,11 +58,11 @@ void tick_init() {
|
||||
void tick_delay(uint32_t us) {
|
||||
uint32_t ticks_per_us = SystemCoreClock / 1000 / 1000;
|
||||
uint32_t us_between_ticks = SysTick->VAL / ticks_per_us;
|
||||
uint64_t start_ms = ticks_ms;
|
||||
uint64_t start_ms = supervisor_ticks_ms64();
|
||||
while (us > 1000) {
|
||||
while (ticks_ms == start_ms) {}
|
||||
while (supervisor_ticks_ms64() == start_ms) {}
|
||||
us -= us_between_ticks;
|
||||
start_ms = ticks_ms;
|
||||
start_ms = supervisor_ticks_ms64();
|
||||
us_between_ticks = 1000;
|
||||
}
|
||||
while (SysTick->VAL > ((us_between_ticks - us) * ticks_per_us)) {}
|
||||
@ -85,11 +71,11 @@ void tick_delay(uint32_t us) {
|
||||
// us counts down!
|
||||
void current_tick(uint64_t* ms, uint32_t* us_until_ms) {
|
||||
uint32_t ticks_per_us = SystemCoreClock / 1000 / 1000;
|
||||
*ms = ticks_ms;
|
||||
*ms = supervisor_ticks_ms32();
|
||||
*us_until_ms = SysTick->VAL / ticks_per_us;
|
||||
}
|
||||
|
||||
void wait_until(uint64_t ms, uint32_t us_until_ms) {
|
||||
uint32_t ticks_per_us = SystemCoreClock / 1000 / 1000;
|
||||
while(ticks_ms <= ms && SysTick->VAL / ticks_per_us >= us_until_ms) {}
|
||||
while(supervisor_ticks_ms64() <= ms && SysTick->VAL / ticks_per_us >= us_until_ms) {}
|
||||
}
|
||||
|
@ -30,8 +30,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern volatile uint64_t ticks_ms;
|
||||
|
||||
extern struct timer_descriptor ms_timer;
|
||||
|
||||
void tick_init(void);
|
||||
|
@ -32,6 +32,7 @@
|
||||
#define __INCLUDED_MPCONFIG_CIRCUITPY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
// This is CircuitPython.
|
||||
#define CIRCUITPY 1
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "shared-module/displayio/__init__.h"
|
||||
#include "shared-module/displayio/display_core.h"
|
||||
#include "supervisor/shared/display.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "supervisor/usb.h"
|
||||
|
||||
#include <stdint.h>
|
||||
@ -313,7 +314,7 @@ uint16_t common_hal_displayio_display_get_rotation(displayio_display_obj_t* self
|
||||
|
||||
bool common_hal_displayio_display_refresh(displayio_display_obj_t* self, uint32_t target_ms_per_frame, uint32_t maximum_ms_per_real_frame) {
|
||||
if (!self->auto_refresh && !self->first_manual_refresh) {
|
||||
uint64_t current_time = ticks_ms;
|
||||
uint64_t current_time = supervisor_ticks_ms64();
|
||||
uint32_t current_ms_since_real_refresh = current_time - self->core.last_refresh;
|
||||
// Test to see if the real frame time is below our minimum.
|
||||
if (current_ms_since_real_refresh > maximum_ms_per_real_frame) {
|
||||
@ -327,7 +328,7 @@ bool common_hal_displayio_display_refresh(displayio_display_obj_t* self, uint32_
|
||||
}
|
||||
uint32_t remaining_time = target_ms_per_frame - (current_ms_since_real_refresh % target_ms_per_frame);
|
||||
// We're ahead of the game so wait until we align with the frame rate.
|
||||
while (ticks_ms - self->last_refresh_call < remaining_time) {
|
||||
while (supervisor_ticks_ms64() - self->last_refresh_call < remaining_time) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
}
|
||||
}
|
||||
@ -350,20 +351,20 @@ STATIC void _update_backlight(displayio_display_obj_t* self) {
|
||||
if (!self->auto_brightness || self->updating_backlight) {
|
||||
return;
|
||||
}
|
||||
if (ticks_ms - self->last_backlight_refresh < 100) {
|
||||
if (supervisor_ticks_ms64() - self->last_backlight_refresh < 100) {
|
||||
return;
|
||||
}
|
||||
// TODO(tannewt): Fade the backlight based on it's existing value and a target value. The target
|
||||
// should account for ambient light when possible.
|
||||
common_hal_displayio_display_set_brightness(self, 1.0);
|
||||
|
||||
self->last_backlight_refresh = ticks_ms;
|
||||
self->last_backlight_refresh = supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
void displayio_display_background(displayio_display_obj_t* self) {
|
||||
_update_backlight(self);
|
||||
|
||||
if (self->auto_refresh && (ticks_ms - self->core.last_refresh) > self->native_ms_per_frame) {
|
||||
if (self->auto_refresh && (supervisor_ticks_ms64() - self->core.last_refresh) > self->native_ms_per_frame) {
|
||||
_refresh_display(self);
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "shared-bindings/time/__init__.h"
|
||||
#include "shared-module/displayio/__init__.h"
|
||||
#include "supervisor/shared/display.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "supervisor/usb.h"
|
||||
|
||||
#include <stdint.h>
|
||||
@ -175,7 +176,7 @@ uint32_t common_hal_displayio_epaperdisplay_get_time_to_refresh(displayio_epaper
|
||||
return 0;
|
||||
}
|
||||
// Refresh at seconds per frame rate.
|
||||
uint32_t elapsed_time = ticks_ms - self->core.last_refresh;
|
||||
uint32_t elapsed_time = supervisor_ticks_ms64() - self->core.last_refresh;
|
||||
if (elapsed_time > self->milliseconds_per_frame) {
|
||||
return 0;
|
||||
}
|
||||
@ -339,7 +340,7 @@ void displayio_epaperdisplay_background(displayio_epaperdisplay_obj_t* self) {
|
||||
bool busy = common_hal_digitalio_digitalinout_get_value(&self->busy);
|
||||
refresh_done = busy != self->busy_state;
|
||||
} else {
|
||||
refresh_done = ticks_ms - self->core.last_refresh > self->refresh_time;
|
||||
refresh_done = supervisor_ticks_ms64() - self->core.last_refresh > self->refresh_time;
|
||||
}
|
||||
if (refresh_done) {
|
||||
self->refreshing = false;
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "shared-bindings/time/__init__.h"
|
||||
#include "shared-module/displayio/__init__.h"
|
||||
#include "supervisor/shared/display.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
@ -281,7 +282,7 @@ void displayio_display_core_set_region_to_update(displayio_display_core_t* self,
|
||||
}
|
||||
|
||||
void displayio_display_core_start_refresh(displayio_display_core_t* self) {
|
||||
self->last_refresh = ticks_ms;
|
||||
self->last_refresh = supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
void displayio_display_core_finish_refresh(displayio_display_core_t* self) {
|
||||
@ -289,7 +290,7 @@ void displayio_display_core_finish_refresh(displayio_display_core_t* self) {
|
||||
displayio_group_finish_refresh(self->current_group);
|
||||
}
|
||||
self->full_refresh = false;
|
||||
self->last_refresh = ticks_ms;
|
||||
self->last_refresh = supervisor_ticks_ms64();
|
||||
}
|
||||
|
||||
void release_display_core(displayio_display_core_t* self) {
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "py/mphal.h"
|
||||
#include "py/mperrno.h"
|
||||
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
#include "shared-bindings/random/__init__.h"
|
||||
|
||||
#include "shared-module/network/__init__.h"
|
||||
@ -53,7 +55,7 @@ void network_module_deinit(void) {
|
||||
|
||||
void network_module_background(void) {
|
||||
static uint32_t next_tick = 0;
|
||||
uint32_t this_tick = ticks_ms;
|
||||
uint32_t this_tick = supervisor_ticks_ms32();
|
||||
if (this_tick < next_tick) return;
|
||||
next_tick = this_tick + 1000;
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "shared-bindings/usb_hid/Device.h"
|
||||
#include "shared-module/usb_hid/Device.h"
|
||||
#include "supervisor/shared/translate.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "tusb.h"
|
||||
|
||||
uint8_t common_hal_usb_hid_device_get_usage_page(usb_hid_device_obj_t *self) {
|
||||
@ -46,8 +47,8 @@ void common_hal_usb_hid_device_send_report(usb_hid_device_obj_t *self, uint8_t*
|
||||
}
|
||||
|
||||
// Wait until interface is ready, timeout = 2 seconds
|
||||
uint64_t end_ticks = ticks_ms + 2000;
|
||||
while ( (ticks_ms < end_ticks) && !tud_hid_ready() ) {
|
||||
uint64_t end_ticks = supervisor_ticks_ms64() + 2000;
|
||||
while ( (supervisor_ticks_ms64() < end_ticks) && !tud_hid_ready() ) {
|
||||
RUN_BACKGROUND_TASKS;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "mphalport.h"
|
||||
#include "shared-bindings/microcontroller/Pin.h"
|
||||
#include "rgb_led_status.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
#ifdef MICROPY_HW_NEOPIXEL
|
||||
uint8_t rgb_status_brightness = 63;
|
||||
@ -360,7 +361,7 @@ void prep_rgb_status_animation(const pyexec_result_t* result,
|
||||
rgb_status_animation_t* status) {
|
||||
#if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)) || (defined(CP_RGB_STATUS_LED))
|
||||
new_status_color(ALL_DONE);
|
||||
status->pattern_start = ticks_ms;
|
||||
status->pattern_start = supervisor_ticks_ms32();
|
||||
status->safe_mode = safe_mode;
|
||||
status->found_main = found_main;
|
||||
status->total_exception_cycle = 0;
|
||||
@ -405,11 +406,11 @@ void prep_rgb_status_animation(const pyexec_result_t* result,
|
||||
|
||||
void tick_rgb_status_animation(rgb_status_animation_t* status) {
|
||||
#if defined(MICROPY_HW_NEOPIXEL) || (defined(MICROPY_HW_APA102_MOSI) && defined(MICROPY_HW_APA102_SCK)) || (defined(CP_RGB_STATUS_LED))
|
||||
uint32_t tick_diff = ticks_ms - status->pattern_start;
|
||||
uint32_t tick_diff = supervisor_ticks_ms32() - status->pattern_start;
|
||||
if (status->ok) {
|
||||
// All is good. Ramp ALL_DONE up and down.
|
||||
if (tick_diff > ALL_GOOD_CYCLE_MS) {
|
||||
status->pattern_start = ticks_ms;
|
||||
status->pattern_start = supervisor_ticks_ms32();
|
||||
tick_diff = 0;
|
||||
}
|
||||
|
||||
@ -424,7 +425,7 @@ void tick_rgb_status_animation(rgb_status_animation_t* status) {
|
||||
}
|
||||
} else {
|
||||
if (tick_diff > status->total_exception_cycle) {
|
||||
status->pattern_start = ticks_ms;
|
||||
status->pattern_start = supervisor_ticks_ms32();
|
||||
tick_diff = 0;
|
||||
}
|
||||
// First flash the file color.
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "supervisor/shared/rgb_led_colors.h"
|
||||
#include "supervisor/shared/rgb_led_status.h"
|
||||
#include "supervisor/shared/translate.h"
|
||||
#include "supervisor/shared/tick.h"
|
||||
|
||||
#define SAFE_MODE_DATA_GUARD 0xad0000af
|
||||
#define SAFE_MODE_DATA_GUARD_MASK 0xff0000ff
|
||||
@ -59,14 +60,14 @@ safe_mode_t wait_for_safe_mode_reset(void) {
|
||||
common_hal_digitalio_digitalinout_construct(&status_led, MICROPY_HW_LED_STATUS);
|
||||
common_hal_digitalio_digitalinout_switch_to_output(&status_led, true, DRIVE_MODE_PUSH_PULL);
|
||||
#endif
|
||||
uint64_t start_ticks = ticks_ms;
|
||||
uint64_t start_ticks = supervisor_ticks_ms64();
|
||||
uint64_t diff = 0;
|
||||
while (diff < 700) {
|
||||
#ifdef MICROPY_HW_LED_STATUS
|
||||
// Blink on for 100, off for 100, on for 100, off for 100 and on for 200
|
||||
common_hal_digitalio_digitalinout_set_value(&status_led, diff > 100 && diff / 100 != 2 && diff / 100 != 4);
|
||||
#endif
|
||||
diff = ticks_ms - start_ticks;
|
||||
diff = supervisor_ticks_ms64() - start_ticks;
|
||||
}
|
||||
#ifdef MICROPY_HW_LED_STATUS
|
||||
common_hal_digitalio_digitalinout_deinit(&status_led);
|
||||
|
76
supervisor/shared/tick.c
Normal file
76
supervisor/shared/tick.c
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Jeff Epler for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "supervisor/shared/tick.h"
|
||||
#include "supervisor/filesystem.h"
|
||||
#include "supervisor/shared/autoreload.h"
|
||||
|
||||
static volatile uint64_t ticks_ms;
|
||||
|
||||
#if CIRCUITPY_GAMEPAD
|
||||
#include "shared-module/gamepad/__init__.h"
|
||||
#endif
|
||||
|
||||
#if CIRCUITPY_GAMEPADSHIFT
|
||||
#include "shared-module/gamepadshift/__init__.h"
|
||||
#endif
|
||||
|
||||
#include "shared-bindings/microcontroller/__init__.h"
|
||||
|
||||
void supervisor_tick(void) {
|
||||
|
||||
ticks_ms ++;
|
||||
|
||||
|
||||
#if CIRCUITPY_FILESYSTEM_FLUSH_INTERVAL_MS > 0
|
||||
filesystem_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_AUTORELOAD_DELAY_MS
|
||||
autoreload_tick();
|
||||
#endif
|
||||
#ifdef CIRCUITPY_GAMEPAD_TICKS
|
||||
if (!(ticks_ms & CIRCUITPY_GAMEPAD_TICKS)) {
|
||||
#if CIRCUITPY_GAMEPAD
|
||||
gamepad_tick();
|
||||
#endif
|
||||
#if CIRCUITPY_GAMEPADSHIFT
|
||||
gamepadshift_tick();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t supervisor_ticks_ms64() {
|
||||
uint64_t result;
|
||||
common_hal_mcu_disable_interrupts();
|
||||
result = ticks_ms;
|
||||
common_hal_mcu_enable_interrupts();
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t supervisor_ticks_ms32() {
|
||||
return ticks_ms;
|
||||
}
|
37
supervisor/shared/tick.h
Normal file
37
supervisor/shared/tick.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Jeff Epler for Adafruit Industries
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __INCLUDED_SUPERVISOR_TICK_H
|
||||
#define __INCLUDED_SUPERVISOR_TICK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
extern void supervisor_tick(void);
|
||||
extern uint32_t supervisor_ticks_ms32(void);
|
||||
extern uint64_t supervisor_ticks_ms64(void);
|
||||
|
||||
#endif
|
@ -10,6 +10,7 @@ SRC_SUPERVISOR = \
|
||||
supervisor/shared/safe_mode.c \
|
||||
supervisor/shared/stack.c \
|
||||
supervisor/shared/status_leds.c \
|
||||
supervisor/shared/tick.c \
|
||||
supervisor/shared/translate.c
|
||||
|
||||
ifndef $(NO_USB)
|
||||
|
Loading…
Reference in New Issue
Block a user