mirror of
https://github.com/Tom-on64/kern.git
synced 2026-11-03 18:22:22 +00:00
Started working on multistage bootloader. Added VBE data in stage2.asm
This commit is contained in:
@@ -17,7 +17,7 @@ os: $(BUILD) bootloader kernel
|
|||||||
@cat $(BUILD)/boot.bin $(BUILD)/filetable.bin $(BUILD)/kernel.bin > $(BUILD)/os.bin
|
@cat $(BUILD)/boot.bin $(BUILD)/filetable.bin $(BUILD)/kernel.bin > $(BUILD)/os.bin
|
||||||
@dd if=/dev/zero of=kern.iso bs=512 count=2880
|
@dd if=/dev/zero of=kern.iso bs=512 count=2880
|
||||||
@dd if=$(BUILD)/os.bin of=kern.iso conv=notrunc
|
@dd if=$(BUILD)/os.bin of=kern.iso conv=notrunc
|
||||||
@dd if=$(SRC)/fs/test.txt of=kern.iso bs=512 seek=22 conv=notrunc
|
@dd if=$(SRC)/fs/test.txt of=kern.iso bs=512 seek=25 conv=notrunc
|
||||||
|
|
||||||
$(BUILD):
|
$(BUILD):
|
||||||
@[ -d $(BUILD) ] || mkdir $(BUILD)
|
@[ -d $(BUILD) ] || mkdir $(BUILD)
|
||||||
@@ -25,7 +25,9 @@ $(BUILD):
|
|||||||
|
|
||||||
# Bootloader: This target is responsible for creating boot.bin
|
# Bootloader: This target is responsible for creating boot.bin
|
||||||
bootloader:
|
bootloader:
|
||||||
@$(AS) -fbin -o $(BUILD)/boot.bin $(SRC)/boot.asm
|
@$(AS) -fbin -o $(BUILD)/stage1.bin $(SRC)/stage1.asm
|
||||||
|
@$(AS) -fbin -o $(BUILD)/stage2.bin $(SRC)/stage2.asm
|
||||||
|
@cat $(BUILD)/stage1.bin $(BUILD)/stage2.bin > $(BUILD)/boot.bin
|
||||||
|
|
||||||
# Kernel: This target is responsible for creating kernel.bin
|
# Kernel: This target is responsible for creating kernel.bin
|
||||||
kernel: $(C_FILES) $(SRC)/entry.asm
|
kernel: $(C_FILES) $(SRC)/entry.asm
|
||||||
|
|||||||
-154
@@ -1,154 +0,0 @@
|
|||||||
[bits 16]
|
|
||||||
[org 0x7c00]
|
|
||||||
[global start]
|
|
||||||
|
|
||||||
%define KERNEL_LOC 0x1000
|
|
||||||
%define KERNEL_SIZE 20 ; 20 Sectors -> 10kB
|
|
||||||
%define ENDL 0x0a, 0x0d
|
|
||||||
%define VIDMEM 0xb800
|
|
||||||
|
|
||||||
start:
|
|
||||||
mov [driveNum], dl ; Store the drive number
|
|
||||||
|
|
||||||
; Set up the stack
|
|
||||||
xor ax, ax
|
|
||||||
mov es, ax
|
|
||||||
mov ds, ax
|
|
||||||
mov bp, 0x7c00
|
|
||||||
mov sp, bp
|
|
||||||
|
|
||||||
; Setup screen
|
|
||||||
mov ax, 3 ; 80x25 Text mode
|
|
||||||
int 0x10
|
|
||||||
|
|
||||||
mov si, msg
|
|
||||||
call puts
|
|
||||||
|
|
||||||
; Reset disk
|
|
||||||
mov ah, 0x0
|
|
||||||
mov dl, [driveNum]
|
|
||||||
int 0x13
|
|
||||||
.read:
|
|
||||||
; Location
|
|
||||||
mov bx, KERNEL_LOC
|
|
||||||
|
|
||||||
; CHS Location
|
|
||||||
mov al, KERNEL_SIZE
|
|
||||||
mov ch, 0 ; Cylinder
|
|
||||||
mov dh, 0 ; Head
|
|
||||||
mov cl, 3 ; Sector - 1 is this file, 2 is filetable
|
|
||||||
mov dl, [driveNum]
|
|
||||||
|
|
||||||
mov ah, 2 ; Read from disk
|
|
||||||
int 0x13 ; Read interrupt
|
|
||||||
|
|
||||||
jc .diskErr
|
|
||||||
|
|
||||||
cli
|
|
||||||
lgdt [GDT_Descriptor]
|
|
||||||
; Enable protected mode
|
|
||||||
mov eax, cr0
|
|
||||||
or eax, 1
|
|
||||||
mov cr0, eax
|
|
||||||
|
|
||||||
; Far jump to 32 bit mode!!
|
|
||||||
jmp CODE_SEG:protected_start
|
|
||||||
|
|
||||||
.diskErr:
|
|
||||||
dec byte [counter]
|
|
||||||
jnz .read
|
|
||||||
|
|
||||||
mov si, diskErrMsg
|
|
||||||
mov bl, 0x04
|
|
||||||
call puts
|
|
||||||
|
|
||||||
cli
|
|
||||||
hlt
|
|
||||||
|
|
||||||
;
|
|
||||||
; Prints a string
|
|
||||||
; si: char* - String to print
|
|
||||||
; bl: byte - Color attribute
|
|
||||||
;
|
|
||||||
puts:
|
|
||||||
push ax
|
|
||||||
push es
|
|
||||||
|
|
||||||
mov ax, VIDMEM
|
|
||||||
mov es, ax
|
|
||||||
mov word di, [cursor]
|
|
||||||
|
|
||||||
mov ah, bl
|
|
||||||
.loop:
|
|
||||||
lodsb ; mov al, [si]; inc si
|
|
||||||
cmp al, 0
|
|
||||||
je .done
|
|
||||||
mov [es:di], ax ; Really smart thing
|
|
||||||
add di, 2
|
|
||||||
jmp .loop
|
|
||||||
.done:
|
|
||||||
mov [cursor], di
|
|
||||||
pop es
|
|
||||||
pop ax
|
|
||||||
ret
|
|
||||||
|
|
||||||
;; Data
|
|
||||||
driveNum: db 0
|
|
||||||
counter: db 0
|
|
||||||
cursor: dw 0
|
|
||||||
|
|
||||||
; Strings
|
|
||||||
msg: db "Booting...", 0
|
|
||||||
diskErrMsg: db "Failed while reading from disk!", 0
|
|
||||||
|
|
||||||
;; 32 Bit entry code
|
|
||||||
[bits 32]
|
|
||||||
protected_start:
|
|
||||||
; Set up data and stack segments
|
|
||||||
mov ax, DATA_SEG
|
|
||||||
mov ds, ax
|
|
||||||
mov ss, ax
|
|
||||||
mov es, ax
|
|
||||||
mov fs, ax
|
|
||||||
mov gs, ax
|
|
||||||
|
|
||||||
; Set up the stack pointer in protected mode
|
|
||||||
mov ebp, 0x90000
|
|
||||||
mov esp, ebp
|
|
||||||
|
|
||||||
; Perform the far jump
|
|
||||||
mov al, [driveNum]
|
|
||||||
push ax
|
|
||||||
jmp KERNEL_LOC
|
|
||||||
|
|
||||||
;; GDT
|
|
||||||
GDT_Start:
|
|
||||||
null_descriptor: ; No clue why this exists
|
|
||||||
dd 0
|
|
||||||
dd 0
|
|
||||||
code_descriptor: ; Memory for code
|
|
||||||
dw 0xffff ; First 16 bits of limit
|
|
||||||
dw 0 ; 16 bits +
|
|
||||||
db 0 ; + 8 bits = 24 bits
|
|
||||||
db 0b10011010 ; p, dpl, s, type flags
|
|
||||||
db 0b11001111 ; Other flags, last 4 bits of limit
|
|
||||||
db 0 ; Base
|
|
||||||
data_descriptor: ; Memory for data
|
|
||||||
dw 0xffff ; First 16 bits of limit
|
|
||||||
dw 0 ; 16 bits +
|
|
||||||
db 0 ; + 8 bits = 24 bits
|
|
||||||
db 0b10010010 ; p, dpl, s, type flags
|
|
||||||
db 0b11001111 ; Other flags, last 4 bits of limit
|
|
||||||
db 0 ; Base
|
|
||||||
GDT_End:
|
|
||||||
|
|
||||||
GDT_Descriptor:
|
|
||||||
dw GDT_End - GDT_Start - 1 ; Size
|
|
||||||
dd GDT_Start ; GDT Pointer
|
|
||||||
|
|
||||||
CODE_SEG equ code_descriptor - GDT_Start
|
|
||||||
DATA_SEG equ data_descriptor - GDT_Start
|
|
||||||
|
|
||||||
times 510-($-$$) db 0
|
|
||||||
dw 0xaa55
|
|
||||||
|
|
||||||
+4
-4
@@ -10,10 +10,10 @@
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
filetable:
|
filetable:
|
||||||
db "boot",0,0,0,0,0,0, "bin",0,0,1
|
db "boot",0,0,0,0,0,0, "bin",0,0,4
|
||||||
db "filetable",0, "tab",0,1,1
|
db "filetable",0, "tab",0,4,1
|
||||||
db "kernel",0,0,0,0, "bin",0,2,20
|
db "kernel",0,0,0,0, "bin",0,5,20
|
||||||
db "test",0,0,0,0,0,0, "txt",0,22,1
|
db "test",0,0,0,0,0,0, "txt",0,25,1
|
||||||
|
|
||||||
times 512-($-$$) db 0
|
times 512-($-$$) db 0
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ void main() {
|
|||||||
clear(0x0f);
|
clear(0x0f);
|
||||||
print("kern.\n\n", 0x0f);
|
print("kern.\n\n", 0x0f);
|
||||||
|
|
||||||
diskRead(1, 1, filetable); // Read the filetable and store it in memory
|
diskRead(4, 1, filetable); // Read the filetable and store it in memory
|
||||||
|
|
||||||
// Run Interactive Shell Program
|
// Run Interactive Shell Program
|
||||||
// TODO: Make it in another file
|
// TODO: Make it in another file
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
[bits 16]
|
||||||
|
[org 0x7c00]
|
||||||
|
[global start]
|
||||||
|
|
||||||
|
%define VIDMEM 0xb800
|
||||||
|
%define KERNEL_LOC 0x1000
|
||||||
|
%define KERNEL_SIZE 20 ; 10kB
|
||||||
|
|
||||||
|
start:
|
||||||
|
mov [driveNum], dl ; Store the drive number
|
||||||
|
|
||||||
|
; Clear screen
|
||||||
|
mov ax, 0x03
|
||||||
|
int 0x10
|
||||||
|
|
||||||
|
; Reset disk
|
||||||
|
mov ah, 0x0
|
||||||
|
mov dl, [driveNum]
|
||||||
|
int 0x13
|
||||||
|
|
||||||
|
.read:
|
||||||
|
; Location
|
||||||
|
mov ax, 0
|
||||||
|
mov es, ax
|
||||||
|
mov bx, 0x7e00 ; 2nd stage bootloader location
|
||||||
|
|
||||||
|
; CHS Location
|
||||||
|
mov al, 3 ; Number of sectors to read (2 = 1kB)
|
||||||
|
mov ch, 0 ; Cylinder
|
||||||
|
mov dh, 0 ; Head
|
||||||
|
mov cl, 2 ; Sector
|
||||||
|
mov dl, [driveNum]
|
||||||
|
|
||||||
|
mov ah, 2 ; Read from disk
|
||||||
|
int 0x13 ; Read interrupt
|
||||||
|
|
||||||
|
jnc loadKernel
|
||||||
|
|
||||||
|
dec byte [counter]
|
||||||
|
jnz .read
|
||||||
|
|
||||||
|
diskErr:
|
||||||
|
mov si, diskErrMsg
|
||||||
|
mov bl, 0x04
|
||||||
|
mov ax, VIDMEM
|
||||||
|
mov es, ax
|
||||||
|
xor di, di
|
||||||
|
.printLoop:
|
||||||
|
movsb
|
||||||
|
mov [es:di], bl
|
||||||
|
inc di
|
||||||
|
cmp byte [si], 0
|
||||||
|
jne .printLoop
|
||||||
|
|
||||||
|
cli
|
||||||
|
hlt
|
||||||
|
|
||||||
|
loadKernel:
|
||||||
|
mov byte [counter], 3
|
||||||
|
|
||||||
|
.read:
|
||||||
|
; Location
|
||||||
|
xor ax, ax
|
||||||
|
mov es, ax
|
||||||
|
mov bx, KERNEL_LOC ; 2nd stage bootloader location
|
||||||
|
|
||||||
|
; CHS Location
|
||||||
|
mov al, KERNEL_SIZE
|
||||||
|
mov ch, 0 ; Cylinder
|
||||||
|
mov dh, 0 ; Head
|
||||||
|
mov cl, 6 ; Sector
|
||||||
|
mov dl, [driveNum]
|
||||||
|
|
||||||
|
mov ah, 2 ; Read from disk
|
||||||
|
int 0x13 ; Read interrupt
|
||||||
|
|
||||||
|
jnc nextStage
|
||||||
|
dec byte [counter]
|
||||||
|
jnz .read
|
||||||
|
|
||||||
|
;; Far jump to second stage bootloader
|
||||||
|
nextStage:
|
||||||
|
mov dl, [driveNum]
|
||||||
|
jmp 0x0000:0x7e00
|
||||||
|
|
||||||
|
;; Data
|
||||||
|
driveNum: db 0
|
||||||
|
counter: db 3
|
||||||
|
|
||||||
|
; Strings
|
||||||
|
diskErrMsg: db "Failed while reading from disk!", 0
|
||||||
|
|
||||||
|
times 510-($-$$) db 0
|
||||||
|
dw 0xaa55
|
||||||
|
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
[bits 16]
|
||||||
|
[org 0x7e00]
|
||||||
|
|
||||||
|
%define KERNEL_LOC 0x1000
|
||||||
|
|
||||||
|
start:
|
||||||
|
mov [driveNum], dl
|
||||||
|
|
||||||
|
;; Load the global descriptor table (GDT)
|
||||||
|
loadGDT:
|
||||||
|
cli
|
||||||
|
lgdt [GDT_Descriptor]
|
||||||
|
; Enable protected mode
|
||||||
|
mov eax, cr0
|
||||||
|
or eax, 1
|
||||||
|
mov cr0, eax
|
||||||
|
|
||||||
|
; Far jump to 32 bit mode!!
|
||||||
|
jmp CODE_SEG:protected_start
|
||||||
|
|
||||||
|
;; 32 Bit entry code
|
||||||
|
[bits 32]
|
||||||
|
protected_start:
|
||||||
|
; Set up data and stack segments
|
||||||
|
mov ax, DATA_SEG
|
||||||
|
mov ds, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov es, ax
|
||||||
|
mov fs, ax
|
||||||
|
mov gs, ax
|
||||||
|
|
||||||
|
; Set up the stack pointer in protected mode
|
||||||
|
mov ebp, 0x90000
|
||||||
|
mov esp, ebp
|
||||||
|
|
||||||
|
; Perform The far jump
|
||||||
|
mov al, [driveNum]
|
||||||
|
push ax
|
||||||
|
|
||||||
|
jmp CODE_SEG:KERNEL_LOC
|
||||||
|
|
||||||
|
;; Data
|
||||||
|
driveNum: db 0
|
||||||
|
|
||||||
|
;; GDT
|
||||||
|
GDT:
|
||||||
|
.start:
|
||||||
|
.nullDescriptor: ; No clue why this exists
|
||||||
|
dd 0
|
||||||
|
dd 0
|
||||||
|
.codeDescriptor: ; Memory for code
|
||||||
|
dw 0xffff ; First 16 bits of limit
|
||||||
|
dw 0 ; 16 bits +
|
||||||
|
db 0 ; + 8 bits = 24 bits
|
||||||
|
db 0b10011010 ; p, dpl, s, type flags
|
||||||
|
db 0b11001111 ; Other flags, last 4 bits of limit
|
||||||
|
db 0 ; Base
|
||||||
|
.dataDescriptor: ; Memory for data
|
||||||
|
dw 0xffff ; First 16 bits of limit
|
||||||
|
dw 0 ; 16 bits +
|
||||||
|
db 0 ; + 8 bits = 24 bits
|
||||||
|
db 0b10010010 ; p, dpl, s, type flags
|
||||||
|
db 0b11001111 ; Other flags, last 4 bits of limit
|
||||||
|
db 0 ; Base
|
||||||
|
.end:
|
||||||
|
|
||||||
|
GDT_Descriptor:
|
||||||
|
dw GDT.end - GDT.start - 1 ; Size
|
||||||
|
dd GDT.start ; GDT Pointer
|
||||||
|
|
||||||
|
CODE_SEG equ GDT.codeDescriptor - GDT.start
|
||||||
|
DATA_SEG equ GDT.dataDescriptor - GDT.start
|
||||||
|
|
||||||
|
;; VBE Stuff
|
||||||
|
width: dw 1920
|
||||||
|
height: dw 1080
|
||||||
|
bpp: db 32
|
||||||
|
offset: dw 0
|
||||||
|
_segment: dw 0 ; segment is a keyword
|
||||||
|
mode: dw 0
|
||||||
|
|
||||||
|
times 512-($-$$) db 0 ; End Sector 1
|
||||||
|
|
||||||
|
;; Sector 2
|
||||||
|
vbeInfoBlock:
|
||||||
|
.vbeSigniture: db "VBE2"
|
||||||
|
.vbeVersion: dw 0x0300 ; BCD Version
|
||||||
|
.oemStringPtr: dd 0
|
||||||
|
.capibilities: dd 0
|
||||||
|
.videoModePtr: dd 0
|
||||||
|
.totalMemory: dw 0
|
||||||
|
.oemSoftwareRev: dw 0
|
||||||
|
.oemVendorNamePtr: dd 0
|
||||||
|
.oemProductNamePtr: dd 0
|
||||||
|
.oemProductRevisionPtr: dd 0
|
||||||
|
.reserved: times 222 db 0
|
||||||
|
.oemData: times 256 db 0
|
||||||
|
|
||||||
|
;; Sector 3
|
||||||
|
modeInfoBlock:
|
||||||
|
;; Required by all VBE revisions
|
||||||
|
.modeAttr: dw 0
|
||||||
|
.windowAAtrr: db 0
|
||||||
|
.windowBAttr: db 0
|
||||||
|
.windowGranularity: dw 0
|
||||||
|
.windowSize: dw 0
|
||||||
|
.windowASegment: dw 0
|
||||||
|
.windowBSegment: dw 0
|
||||||
|
.windowFunctionPtr: dd 0
|
||||||
|
.bytesPerScanline: dw 0
|
||||||
|
|
||||||
|
;; Required by VBE 1.2+
|
||||||
|
.xRes: dw 0
|
||||||
|
.yRex: dw 0
|
||||||
|
.xCharSize: db 0
|
||||||
|
.yCharSize: db 0
|
||||||
|
.planeCount: db 0
|
||||||
|
.bpp: db 0
|
||||||
|
.bankCount: db 0
|
||||||
|
.memoryModel: db 0
|
||||||
|
.bankSize: db 0
|
||||||
|
.imagePageCount: db 0
|
||||||
|
.reserved1: db 1
|
||||||
|
|
||||||
|
;; Direct color fields
|
||||||
|
.redMaskSize: db 0
|
||||||
|
.redFieldPos: db 0
|
||||||
|
.greenMaskSize: db 0
|
||||||
|
.greenFieldPos: db 0
|
||||||
|
.blueMaskSize: db 0
|
||||||
|
.blueFieldPos: db 0
|
||||||
|
.reservedMaskSize: db 0
|
||||||
|
.reservedFieldPos: db 0
|
||||||
|
.directColorModeInfo: db 0
|
||||||
|
|
||||||
|
;; Required by VBE 2.0+
|
||||||
|
.physicalAddressPtr: dd 0
|
||||||
|
.reserved2: db 0
|
||||||
|
.reserved3: db 0
|
||||||
|
|
||||||
|
;; Required by VBE 3.0+
|
||||||
|
.linearBytesPerScanline: dw 0
|
||||||
|
.bankImagePageCount: db 0
|
||||||
|
.linearImagePageCount: db 0
|
||||||
|
.linearRedMaskSize: db 0
|
||||||
|
.linearRedFieldPos: db 0
|
||||||
|
.linearGreenMaskSize: db 0
|
||||||
|
.linearGreenFieldPos: db 0
|
||||||
|
.linearBlueMaskSize: db 0
|
||||||
|
.linearBlueFieldPos: db 0
|
||||||
|
.linearReservedMaskSize: db 0
|
||||||
|
.linearReservedFieldPos: db 0
|
||||||
|
.maxPixelClock: dd 0
|
||||||
|
|
||||||
|
.reserved4: times 190 db 0
|
||||||
|
|
||||||
|
times (512*3)-($-$$) db 0 ; 2 Pad out to 3 sectors
|
||||||
|
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
=== Todo ===
|
=== Todo ===
|
||||||
|
|
||||||
- [ ] Multistage Bootloader
|
- [*] Multistage Bootloader
|
||||||
- [ ] VESA BIOS Extensions
|
- [ ] Screen
|
||||||
- [ ] Custom font
|
- [*] Scrolling
|
||||||
|
- [ ] Scroll-back
|
||||||
|
- [ ] VESA BIOS Extensions
|
||||||
|
- [ ] Custom font
|
||||||
- [ ] Filesystem
|
- [ ] Filesystem
|
||||||
- [ ] Filetable
|
- [ ] Utility for filesystem building (not just an assembly file)
|
||||||
|
- [*] Filetable
|
||||||
- [ ] Files
|
- [ ] Files
|
||||||
- [ ] Larger than one sector (512B)
|
- [*] Larger than one sector (512B)
|
||||||
- [ ] Any file size (Not multiples of 512B)
|
- [ ] Any file size (Not multiples of 512B)
|
||||||
- [ ] Filetypes
|
- [ ] Filetypes
|
||||||
- [ ] Directories
|
- [ ] Directories
|
||||||
@@ -22,11 +26,43 @@
|
|||||||
- [ ] Close()
|
- [ ] Close()
|
||||||
- [ ] Malloc()
|
- [ ] Malloc()
|
||||||
- [ ] Free()
|
- [ ] Free()
|
||||||
|
- [ ] Shell
|
||||||
|
- [ ] ';' separated commands
|
||||||
|
- [ ] I/O Redirection with '<' '>'
|
||||||
|
- [ ] Commands
|
||||||
|
- [*] echo
|
||||||
|
- [*] clear
|
||||||
|
- [*] help
|
||||||
|
- [*] ls
|
||||||
|
- [*] exit
|
||||||
|
- [ ] touch (make file)
|
||||||
|
- [ ] rm
|
||||||
|
- [ ] mkdir (add dirs first)
|
||||||
|
- [ ] reboot
|
||||||
|
- [ ] cd
|
||||||
|
- [ ] Standard I/O stuff
|
||||||
|
- [ ] stdin
|
||||||
|
- [ ] stdout
|
||||||
|
- [ ] stderr
|
||||||
|
- [ ] printf
|
||||||
|
- [ ] color codes
|
||||||
- [ ] Virtual Memory
|
- [ ] Virtual Memory
|
||||||
- [ ] Paging
|
- [ ] Paging
|
||||||
- [ ] User (or system) programs
|
- [ ] User (or system) programs
|
||||||
- [ ] Loading them
|
- [ ] Loading them
|
||||||
- [ ] Running them
|
- [ ] Running them
|
||||||
- [ ] Exit back to kernel/shell
|
- [ ] Exit back to kernel/shell
|
||||||
|
- [ ] argc, argv[]
|
||||||
- [ ] Standalone shell program
|
- [ ] Standalone shell program
|
||||||
|
- [ ] Code Editor
|
||||||
|
- [ ] Text editor
|
||||||
|
- [ ] Syntax highlighting
|
||||||
|
- [ ] Saving/Loading
|
||||||
|
- [ ] Hex editor
|
||||||
|
- [ ] Assembler/Compiler
|
||||||
|
- [ ] Assembler
|
||||||
|
- [ ] Compiler (probably C)
|
||||||
|
- [ ] Standard libs
|
||||||
|
- [ ] Finish all todos
|
||||||
|
- [ ] Refactoring!!!
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user