mirror of
https://github.com/wwarthen/RomWBW.git
synced 2026-02-06 14:11:48 -06:00
Enhanced TIMER App from MartinR
- MartinR has enhanced the TIMER application to display output in decimal.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
;===============================================================================
|
||||
; TIMER - Display system timer value
|
||||
;
|
||||
; Version 1.21 30-June-2024
|
||||
;===============================================================================
|
||||
;
|
||||
; Author: Wayne Warthen (wwarthen@gmail.com)
|
||||
; Updated: MartinR (June 2024)
|
||||
;_______________________________________________________________________________
|
||||
;
|
||||
; Usage:
|
||||
@@ -14,38 +15,58 @@
|
||||
;
|
||||
; Operation:
|
||||
; Reads and displays system timer value.
|
||||
;
|
||||
; This code will only execute on a Z80 CPU (or derivitive)
|
||||
;
|
||||
; This source code assembles with TASM V3.2 under Windows-11 using the
|
||||
; following command line:
|
||||
; tasm -80 -g3 -l TIMER.ASM TIMER.COM
|
||||
; ie: Z80 CPU; output format 'binary' named .COM (rather than .OBJ)
|
||||
; and includes a symbol table as part of the listing file.
|
||||
;_______________________________________________________________________________
|
||||
;
|
||||
; Change Log:
|
||||
; 2018-01-14 [WBW] Initial release
|
||||
; 2018-01-17 [WBW] Add HBIOS check
|
||||
; 2019-11-08 [WBW] Add seconds support
|
||||
; 2024-06-30 [MR ] Display values in decimal rather than hexadecimal
|
||||
;_______________________________________________________________________________
|
||||
;
|
||||
; Includes binary-to-decimal subroutine by Alwin Henseler
|
||||
; Located at: https://www.msx.org/forum/development/msx-development/32-bit-long-ascii
|
||||
;_______________________________________________________________________________
|
||||
;
|
||||
; ToDo:
|
||||
; Display the elapsed time in HH:MM:SS
|
||||
;_______________________________________________________________________________
|
||||
;
|
||||
#include "../ver.inc"
|
||||
#include "../ver.inc" ; Used for building RomWBW
|
||||
;#include "ver.inc" ; Used for testing purposes during code development
|
||||
;
|
||||
;===============================================================================
|
||||
; Definitions
|
||||
;===============================================================================
|
||||
;
|
||||
stksiz .equ $40 ; Working stack size
|
||||
;
|
||||
restart .equ $0000 ; CP/M restart vector
|
||||
bdos .equ $0005 ; BDOS invocation vector
|
||||
;
|
||||
ident .equ $FFFE ; loc of RomWBW HBIOS ident ptr
|
||||
stksiz .equ $80 ; Working stack size (was $40)
|
||||
;
|
||||
restart .equ $0000 ; CP/M restart vector
|
||||
bdos .equ $0005 ; BDOS invocation vector
|
||||
;
|
||||
ident .equ $FFFE ; loc of RomWBW HBIOS ident ptr
|
||||
;
|
||||
bf_sysver .equ $F1 ; BIOS: VER function
|
||||
bf_sysget .equ $F8 ; HBIOS: SYSGET function
|
||||
bf_sysset .equ $F9 ; HBIOS: SYSGET function
|
||||
bf_sysset .equ $F9 ; HBIOS: SYSSET function
|
||||
bf_sysgettimer .equ $D0 ; TIMER subfunction
|
||||
bf_syssettimer .equ $D0 ; TIMER subfunction
|
||||
bf_sysgetsecs .equ $D1 ; SECONDS subfunction
|
||||
bf_syssetsecs .equ $D1 ; SECONDS subfunction
|
||||
;
|
||||
; ASCII Control Characters
|
||||
;
|
||||
lf .equ $0A ; Line Feed
|
||||
cr .equ $0D ; Carriage Return
|
||||
;
|
||||
;===============================================================================
|
||||
; Code Section
|
||||
;===============================================================================
|
||||
@@ -73,17 +94,17 @@ exit: ; clean up and return to command processor
|
||||
; Initialization
|
||||
;
|
||||
init:
|
||||
call crlf ; formatting
|
||||
ld de,msgban ; point to version message part 1
|
||||
call prtstr ; print it
|
||||
;
|
||||
call idbio ; identify active BIOS
|
||||
cp 1 ; check for HBIOS
|
||||
jp nz,errbio ; handle BIOS error
|
||||
;
|
||||
call crlf ; formatting
|
||||
ld de,msgban ; point to version message part 1
|
||||
call prtstr ; print it
|
||||
;
|
||||
call idbio ; identify active BIOS
|
||||
cp 1 ; check for HBIOS
|
||||
jp nz,errbio ; handle BIOS error
|
||||
;
|
||||
ld a,rmj << 4 | rmn ; expected HBIOS ver
|
||||
cp d ; compare with result above
|
||||
jp nz,errbio ; handle BIOS error
|
||||
cp d ; compare with result above
|
||||
jp nz,errbio ; handle BIOS error
|
||||
;
|
||||
initx
|
||||
; initialization complete
|
||||
@@ -120,9 +141,9 @@ process0:
|
||||
call crlf2 ; formatting
|
||||
;
|
||||
process1:
|
||||
ld b,bf_sysget ; HBIOS SYSGET function
|
||||
ld b,bf_sysget ; HBIOS SYSGET function
|
||||
ld c,bf_sysgettimer ; TIMER subfunction
|
||||
rst 08 ; call HBIOS, DE:HL := timer value
|
||||
rst 08 ; call HBIOS, DE:HL := timer value
|
||||
|
||||
ld a,(first)
|
||||
or a
|
||||
@@ -135,28 +156,50 @@ process1:
|
||||
cp l ; compare to current LSB
|
||||
jr z,process2 ; if equal, bypass display
|
||||
|
||||
process1a:
|
||||
;*******************************************************************************
|
||||
|
||||
; Code added/amended to print values in decimal
|
||||
; MartinR June2024
|
||||
|
||||
process1a:
|
||||
; save and print new value
|
||||
ld a,l ; new LSB value to A
|
||||
ld (last),a ; save as last value
|
||||
call prtcr ; back to start of line
|
||||
;call nz,prthex32 ; display it
|
||||
call prthex32 ; display it
|
||||
ld de,strtick ; tag
|
||||
call prtstr ; display it
|
||||
ld a,l ; new LSB value to A
|
||||
ld (last),a ; save as last value
|
||||
call prtcr ; back to start of line
|
||||
|
||||
call b2d32 ; Convert DE:HL into ASCII; Start of ASCII buffer returned in HL
|
||||
ex de,hl
|
||||
call prtstr ; Display the value
|
||||
|
||||
ld de,strtick ; "Ticks" message
|
||||
call prtstr ; Display it
|
||||
|
||||
; get and print seconds value
|
||||
ld b,bf_sysget ; HBIOS SYSGET function
|
||||
ld c,bf_sysgetsecs ; SECONDS subfunction
|
||||
rst 08 ; call HBIOS, DE:HL := seconds value
|
||||
call prthex32 ; display it
|
||||
ld a,'.' ; fraction separator
|
||||
call prtchr ; print it
|
||||
ld a,c ; get fractional component
|
||||
call prthex ; print it
|
||||
ld de,strsec ; tag
|
||||
call prtstr ; display it
|
||||
;
|
||||
ld b,bf_sysget ; HBIOS SYSGET function
|
||||
ld c,bf_sysgetsecs ; SECONDS subfunction
|
||||
rst 08 ; Call HBIOS; DE:HL := seconds value; C := fractional part
|
||||
push bc ; Preserve the fractional part on the stack
|
||||
|
||||
call b2d32 ; Convert DE:HL into ASCII; Start of ASCII buffer returned in HL
|
||||
ex de,hl
|
||||
call prtstr ; Display the value
|
||||
|
||||
ld a,'.' ; Fraction separator, ie decimal point
|
||||
call prtchr ; Print it
|
||||
|
||||
pop bc ; Retrieve fractional part into A
|
||||
ld a,c
|
||||
sla a ; Double the 50Hz 'ticks' value to give 1/100s of a second
|
||||
|
||||
call b2d8 ; Convert into ASCII - up to 3 digits
|
||||
ex de,hl ; Start of ASCII buffer returned in HL
|
||||
call prtstr ; Display fractional part of the value
|
||||
|
||||
ld de,strsec ; "Seconds" message
|
||||
call prtstr ; Display it
|
||||
|
||||
;*******************************************************************************
|
||||
|
||||
process2:
|
||||
ld a,(cont) ; continuous display?
|
||||
or a ; test for true/false
|
||||
@@ -269,9 +312,9 @@ prtdot:
|
||||
;
|
||||
prtcr:
|
||||
;
|
||||
; shortcut to print a dot preserving all regs
|
||||
; shortcut to print carriage return preserving all regs
|
||||
push af ; save af
|
||||
ld a,13 ; load CR value
|
||||
ld a,cr ; load CR value
|
||||
call prtchr ; print it
|
||||
pop af ; restore af
|
||||
ret ; done
|
||||
@@ -477,6 +520,124 @@ err2: ; without the string
|
||||
or $FF ; signal error
|
||||
ret ; done
|
||||
;
|
||||
;
|
||||
;===============================================================================
|
||||
; Subroutine to print decimal numbers
|
||||
;===============================================================================
|
||||
;
|
||||
; Combined routine for conversion of different sized binary numbers into
|
||||
; directly printable ASCII(Z)-string
|
||||
; Input value in registers, number size and -related to that- registers to fill
|
||||
; is selected by calling the correct entry:
|
||||
;
|
||||
; entry input decimal value 0 to:
|
||||
; b2d8 A 255 (3 digits)
|
||||
; b2d16 HL 65535 5 "
|
||||
; b2d24 E:HL 16777215 8 "
|
||||
; b2d32 DE:HL 4294967295 10 "
|
||||
; b2d48 BC:DE:HL 281474976710655 15 "
|
||||
; b2d64 IX:BC:DE:HL 18446744073709551615 20 "
|
||||
;
|
||||
; The resulting string is placed into a small buffer attached to this routine,
|
||||
; this buffer needs no initialization and can be modified as desired.
|
||||
; The number is aligned to the right, and leading 0's are replaced with spaces.
|
||||
; On exit HL points to the first digit, (B)C = number of decimals
|
||||
; This way any re-alignment / postprocessing is made easy.
|
||||
; Changes: AF,BC,DE,HL,IX
|
||||
;
|
||||
; by Alwin Henseler
|
||||
; https://msx.org/forum/topic/who-who/dutch-hardware-guy-pops-back-sort
|
||||
;
|
||||
; Found at:
|
||||
; https://www.msx.org/forum/development/msx-development/32-bit-long-ascii
|
||||
;
|
||||
; Tweaked to assemble using TASM 3.2 by MartinR 23June2024
|
||||
;
|
||||
b2d8: ld h,0
|
||||
ld l,a
|
||||
b2d16: ld e,0
|
||||
b2d24: ld d,0
|
||||
b2d32: ld bc,0
|
||||
b2d48: ld ix,0 ; zero all non-used bits
|
||||
b2d64: ld (b2dinv),hl
|
||||
ld (b2dinv+2),de
|
||||
ld (b2dinv+4),bc
|
||||
ld (b2dinv+6),ix ; place full 64-bit input value in buffer
|
||||
ld hl,b2dbuf
|
||||
ld de,b2dbuf+1
|
||||
ld (hl),' '
|
||||
b2dfilc:.equ $-1 ; address of fill-character
|
||||
ld bc,18
|
||||
ldir ; fill 1st 19 bytes of buffer with spaces
|
||||
ld (b2dend-1),bc ; set BCD value to "0" & place terminating 0
|
||||
ld e,1 ; no. of bytes in BCD value
|
||||
ld hl,b2dinv+8 ; (address MSB input)+1
|
||||
ld bc,$0909
|
||||
xor a
|
||||
b2dskp0:dec b
|
||||
jr z,b2dsiz ; all 0: continue with postprocessing
|
||||
dec hl
|
||||
or (hl) ; find first byte <> 0
|
||||
jr z,b2dskp0
|
||||
b2dfnd1:dec c
|
||||
rla
|
||||
jr nc,b2dfnd1 ; determine no. of most significant 1-bit
|
||||
rra
|
||||
ld d,a ; byte from binary input value
|
||||
b2dlus2:push hl
|
||||
push bc
|
||||
b2dlus1:ld hl,b2dend-1 ; address LSB of bcd value
|
||||
ld b,e ; current length of BCD value in bytes
|
||||
rl d ; highest bit from input value -> carry
|
||||
b2dlus0:ld a,(hl)
|
||||
adc a,a
|
||||
daa
|
||||
ld (hl),a ; double 1 BCD byte from intermediate result
|
||||
dec hl
|
||||
djnz b2dlus0 ; and go on to double entire BCD value (+carry!)
|
||||
jr nc,b2dnxt
|
||||
inc e ; carry at MSB -> BCD value grew 1 byte larger
|
||||
ld (hl),1 ; initialize new MSB of BCD value
|
||||
b2dnxt: dec c
|
||||
jr nz,b2dlus1 ; repeat for remaining bits from 1 input byte
|
||||
pop bc ; no. of remaining bytes in input value
|
||||
ld c,8 ; reset bit-counter
|
||||
pop hl ; pointer to byte from input value
|
||||
dec hl
|
||||
ld d,(hl) ; get next group of 8 bits
|
||||
djnz b2dlus2 ; and repeat until last byte from input value
|
||||
b2dsiz: ld hl,b2dend ; address of terminating 0
|
||||
ld c,e ; size of bcd value in bytes
|
||||
or a
|
||||
sbc hl,bc ; calculate address of MSB BCD
|
||||
ld d,h
|
||||
ld e,l
|
||||
sbc hl,bc
|
||||
ex de,hl ; HL=address BCD value, de=start of decimal value
|
||||
ld b,c ; no. of bytes BCD
|
||||
sla c ; no. of bytes decimal (possibly 1 too high)
|
||||
ld a,'0'
|
||||
rld ; shift bits 4-7 of (HL) into bit 0-3 of A
|
||||
cp '0' ; (HL) was > 9h?
|
||||
jr nz,b2dexph ; if yes, start with recording high digit
|
||||
dec c ; correct number of decimals
|
||||
inc de ; correct start address
|
||||
jr b2dexpl ; continue with converting low digit
|
||||
b2dexp: rld ; shift high digit (HL) into low digit of a
|
||||
b2dexph:ld (de),a ; record resulting ascii-code
|
||||
inc de
|
||||
b2dexpl:rld
|
||||
ld (de),a
|
||||
inc de
|
||||
inc hl ; next BCD-byte
|
||||
djnz b2dexp ; and go on to convert each BCD-byte into 2 ASCII
|
||||
sbc hl,bc ; return with HL pointing to 1st decimal
|
||||
ret
|
||||
|
||||
b2dinv .fill 8 ; space for 64-bit input value (LSB first)
|
||||
b2dbuf .fill 20 ; space for 20 decimal digits
|
||||
b2dend .db 0 ; space for terminating character
|
||||
;
|
||||
;===============================================================================
|
||||
; Storage Section
|
||||
;===============================================================================
|
||||
@@ -487,20 +648,21 @@ first .db $FF ; first pass flag (true at start)
|
||||
;
|
||||
stksav .dw 0 ; stack pointer saved at start
|
||||
.fill stksiz,0 ; stack
|
||||
stack .equ $ ; stack top
|
||||
stack .equ $ ; new stack top
|
||||
;
|
||||
; Messages
|
||||
;
|
||||
msgban .db "TIMER v1.1, 10-Nov-2019",13,10
|
||||
.db "Copyright (C) 2019, Wayne Warthen, GNU GPL v3",0
|
||||
msguse .db "Usage: TIMER [/C] [/?]",13,10
|
||||
.db " ex. TIMER (display current timer value)",13,10
|
||||
.db " TIMER /? (display version and usage)",13,10
|
||||
msgban .db "TIMER v1.21, 30-Jun-2024",cr,lf
|
||||
.db "Copyright (C) 2019, Wayne Warthen, GNU GPL v3",cr,lf
|
||||
.db "Updated by MartinR 2024",0
|
||||
msguse .db "Usage: TIMER [/C] [/?]",cr,lf
|
||||
.db " ex. TIMER (display current timer value)",cr,lf
|
||||
.db " TIMER /? (display version and usage)",cr,lf
|
||||
.db " TIMER /C (display timer value continuously)",0
|
||||
msgprm .db "Parameter error (TIMER /? for usage)",0
|
||||
msgbio .db "Incompatible BIOS or version, "
|
||||
.db "HBIOS v", '0' + rmj, ".", '0' + rmn, " required",0
|
||||
strtick .db " Ticks, ",0
|
||||
strsec .db " Seconds",0
|
||||
strtick .db " Ticks ",0
|
||||
strsec .db " Seconds ",0
|
||||
;
|
||||
.end
|
||||
.end
|
||||
|
||||
@@ -126,6 +126,142 @@ execution.
|
||||
|
||||
{ width=100% }
|
||||
|
||||
## Bank Id
|
||||
|
||||
RomWBW utilizes a specific assignment of memory banks for dedicated
|
||||
purposes. A numeric Bank Id is used to refer to the memory banks. The
|
||||
Bank Id is a single byte. In general, the Bank Id simply refers to each
|
||||
of the 32K banks in sequential order. In other words, Bank Id 0 is the
|
||||
first physical 32K, Bank Id 1 is the second, etc. However, the high
|
||||
order bit of the Bank Id has a special meaning. If it is 0, it indicates
|
||||
a ROM bank is being referred to. If it is 1, it indicates a RAM bank
|
||||
is being referred to.
|
||||
|
||||
For example, let's say we have a typical system with 512KB of ROM and
|
||||
512KB of RAM. The Bank Ids would look like this:
|
||||
|
||||
| Physical Memory | Type | Physical Bank | Bank Id |
|
||||
|-------------------|------|---------------|-----------|
|
||||
| 0x000000-0x007FFF | ROM | 0 | 0x00 |
|
||||
| 0x008000-0x00FFFF | ROM | 1 | 0x01 |
|
||||
| 0x010000-0x07FFFF | ROM | 2-15 | 0x02-0x0F |
|
||||
| 0x080000-0x087FFF | RAM | 16 | 0x80 |
|
||||
| 0x088000-0x08FFFF | RAM | 17 | 0x81 |
|
||||
| 0x090000-0x0FFFFF | RAM | 18-31 | 0x82-0x8F |
|
||||
|
||||
Note that Bank Id 0x00 is **always** the first bank of ROM and 0x80 is
|
||||
**always** the first bank of RAM. If there were more banks of physical ROM,
|
||||
they would be assigned Bank Ids starting with 0x10. Likewise, additional
|
||||
bank of physical RAM would be assigned Bank Ids starting with 0x90.
|
||||
|
||||
The Bank Id is used in all RomWBW API functions when referring to
|
||||
the mapping of banks to the lower 32K bank area of the processor. In
|
||||
this way, all RomWBW functions can refer to a generic Bank Id without
|
||||
needing to understand how a specific hardware platform accesses the
|
||||
physical memory areas. A single routine within the HBIOS is implemented
|
||||
for each memory manager that maps Bank Ids to physical memory.
|
||||
|
||||
## Bank Assignments
|
||||
|
||||
RomWBW requires dedicated banks of memory for specific purposes. It
|
||||
uses Bank Ids via an algorithm to make these assignments. The following
|
||||
table describes the way the banks are assigned. The Typical column
|
||||
shows the specific values that would be assigned for a common system
|
||||
with 512KB of ROM and 512KB of RAM (nROM=16, nRAM=16).
|
||||
|
||||
| Bank Id | Identity | Typical | Purpose |
|
||||
|-------------------|-----------|---------|------------------------------------------|
|
||||
| 0x00 |BID_BOOT | 0x00 | Boot Bank (HBIOS image) |
|
||||
| 0x01 |BID_IMG0 | 0x01 | Boot Loader, Monitor, ROM OSes, ROM Apps |
|
||||
| 0x02 |BID_IMG1 | 0x02 | ROM Apps |
|
||||
| 0x03 |BID_IMG2 | 0x03 | \<Reserved\> |
|
||||
| 0x04 |BID_ROMD0 | 0x04 | First ROM Disk Bank |
|
||||
| nROM - 1 | | 0x0F | Last ROM Disk Bank |
|
||||
| 0x80 |BID_BIOS | 0x80 | HBIOS (working copy) |
|
||||
| 0x81 |BID_RAMD0 | 0x81 | First RAM Disk Bank |
|
||||
| 0x80 + nRAM - 8 | | 0x88 | Last RAM Disk Bank |
|
||||
| 0x80 + nRAM - 7 |BID_APP0 | 0x89 | First Application Bank |
|
||||
| 0x80 + nRAM - 5 | | 0x8B | Last Application Bank |
|
||||
| 0x80 + nRAM - 4 |BID_BUF | 0x8C | OS Disk Buffers |
|
||||
| 0x80 + nRAM - 3 |BID_AUX | 0x8D | OS Code Bank |
|
||||
| 0x80 + nRAM - 2 |BID_USR | 0x8E | User Bank (CP/M TPA) |
|
||||
| 0x80 + nRAM - 1 |BID_COM | 0x8F | Common Bank |
|
||||
|
||||
In this table, nROM and nRAM refer to the number of corresponding
|
||||
ROM and RAM banks in the the system.
|
||||
|
||||
The contents of the banks referred to above are described in more detail
|
||||
below:
|
||||
|
||||
Boot Bank:
|
||||
|
||||
: The Boot Bank receives control when a system is first powered
|
||||
on. It contains a ROM (read-only) copy of the HBIOS. At boot, it does
|
||||
minimal hardware initialization, then copies itself to the HBIOS bank
|
||||
in RAM, then resumes execution from the RAM bank.
|
||||
|
||||
Boot Loader:
|
||||
|
||||
: The application that handles loading of ROM or Disk based applications
|
||||
including operating systems. It copies itself to a RAM bank at the
|
||||
start of it's execution.
|
||||
|
||||
Monitor:
|
||||
|
||||
: The application that implements the basic system monitor functions.
|
||||
It copies itself to a RAM bank at the start of it's execution.
|
||||
|
||||
ROM OSes:
|
||||
|
||||
: Code images of CP/M 2.2 and Z-System which are copied to RAM and
|
||||
executed when a ROM-based operating system is selected in the Boot
|
||||
Loader.
|
||||
|
||||
ROM Applications:
|
||||
|
||||
: Various ROM-based application images such as BASIC, FORTH, etc. They
|
||||
can be selected in the Boot Loader. The Boot Loader will copy the
|
||||
application image to a RAM bank, then transfer control to it.
|
||||
|
||||
ROM Disk:
|
||||
|
||||
: A sequential series of banks assigned to provide the system ROM Disk
|
||||
contents.
|
||||
|
||||
HBIOS:
|
||||
|
||||
: This bank hosts the running copy of the RomWBW HBIOS.
|
||||
|
||||
RAM Disk:
|
||||
|
||||
: A sequential series of banks assigned to provide the system RAM Disk.
|
||||
|
||||
Application Bank:
|
||||
|
||||
: A sequential series of banks that are available for use by applications
|
||||
that wish to utilize banked memory.
|
||||
|
||||
OS Disk Buffers:
|
||||
|
||||
: This bank is used by CP/M 3 and ZPM3 for disk buffer storage.
|
||||
|
||||
OS Code Bank:
|
||||
|
||||
: This bank is used by CP/M 3 and ZPM3 as an alternate bank for code.
|
||||
This allows these operating systems to make additional TPA space
|
||||
available for applications.
|
||||
|
||||
User Bank:
|
||||
|
||||
: This is the default bank for applications to use. This includes the
|
||||
traditional TPA space for CP/M.
|
||||
|
||||
Common Bank:
|
||||
|
||||
: This bank is mapped to the upper 32K of the processors memory space.
|
||||
It is a fixed mapping that is never changed in normal RomWBW operation
|
||||
hence the name "Common".
|
||||
|
||||
# System Boot Process
|
||||
|
||||
A multi-phase boot strategy is employed. This is necessary because at
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#DEFINE RMN 5
|
||||
#DEFINE RUP 0
|
||||
#DEFINE RTP 0
|
||||
#DEFINE BIOSVER "3.5.0-dev.47"
|
||||
#DEFINE BIOSVER "3.5.0-dev.48"
|
||||
#define rmj RMJ
|
||||
#define rmn RMN
|
||||
#define rup RUP
|
||||
|
||||
@@ -3,5 +3,5 @@ rmn equ 5
|
||||
rup equ 0
|
||||
rtp equ 0
|
||||
biosver macro
|
||||
db "3.5.0-dev.47"
|
||||
db "3.5.0-dev.48"
|
||||
endm
|
||||
|
||||
Reference in New Issue
Block a user