this includes more fixes for the bootloader and implementing more of the vga driver, idt, and keyboard driver in preparation for working on the kernel.

This commit is contained in:
Gregory Kenneth Bowne 2024-03-15 14:54:01 -07:00
parent a332daa215
commit 5ff47dde28
13 changed files with 505 additions and 311 deletions

BIN
.vscode/browse.vc.db vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -108,6 +108,6 @@
"*.json": "default", "*.json": "default",
"*.asm": "default" "*.asm": "default"
}, },
"C_Cpp.default.intelliSenseMode": "linux-clang-x86", "C_Cpp.default.intelliSenseMode": "linux-gcc-x86",
"cmake.cmakePath": "/usr/bin/cmake" "cmake.cmakePath": "/usr/bin/cmake"
} }

17
ask.txt
View File

@ -1,4 +1,6 @@
I am making an x86 32 bit IA32 Operating System in Visual Studio Code 1.81 for Debian Linux. This will not a linux based OS. The system I am developing on has a Intel i7-3770k and is an x86_64 bit and runs Debian 10 Buster Linux with Linux Kernel version 4.19.0-25-amd64 (supports UNIX 98 PTY) and Bash 5.0.3 and the drive has an ext4 partition and a tempfs partition. I have the extension ms-vscode.cpptools installed on VSCode. I also have both gcc 8.3.0 and clang 7.0.1-8+deb10u2 installed. All of my compilers, debuggers, linkers are in /usr/bin. I have Coreutils 8.30, Binutils 2.31.1, Bison 3.3.2, Diffutils 3.7, Findutils 4.6.0.225, Gawk 4.2.1, Grep 3.3, Gzip 1.9, M4 1.4.18, Make 4.2.1, Patch 2.7.6, Perl 5.28.1, Python 3.7.3, Sed 4.7, Tar 1.30, Texinfo 6.5, Xz 5.2.4. The operating system would run on any system that has a 386 CPU up to a Pentium 4. I am making a fully custom x86 32 bit IA32 Operating System in Visual Studio Code 1.81 for Debian Linux. This will not be a linux based OS. It's also not posix, linux, unix, windows, bsd or any other derivitive thereof but does use the same concepts. The system I am developing on has a Intel i7-3770k and is an x86_64 bit and runs Debian 10 Buster Linux with Linux Kernel version 4.19.0-25-amd64 (supports UNIX 98 PTY) and Bash 5.0.3 and the drive has an ext4 partition and a tempfs partition. I have the extension ms-vscode.cpptools installed on VSCode. I also have both gcc 8.3.0 and clang 7.0.1-8+deb10u2 installed. All of my compilers, debuggers, linkers are in /usr/bin. I have Coreutils 8.30, Binutils 2.31.1, Bison 3.3.2, Diffutils 3.7, Findutils 4.6.0.225, Gawk 4.2.1, Grep 3.3, Gzip 1.9, M4 1.4.18, Make 4.2.1, Patch 2.7.6, Perl 5.28.1, Python 3.7.3, Sed 4.7, Tar 1.30, Texinfo 6.5, Xz 5.2.4. The operating system would run on any system that has a 386 CPU up to a 32 bit only Prescott Pentium 4 released in 2004. I am using gcc to compile, ld to link, gdb to debug, nasm to do the assembly and qemu to emulate the system. it has a two stage bootloader. The os should boot from floppy, hard drive and optical media.
here is the file structure:
ClassicOS/ ClassicOS/
├── .github/ ├── .github/
@ -17,6 +19,7 @@ ClassicOS/
│ │ │ └── menu.lst │ │ │ └── menu.lst
│ │ ├── boot.asm │ │ ├── boot.asm
│ │ ├── boot.o │ │ ├── boot.o
│ │ ├── boot2.asm
│ │ ├── linker.ld │ │ ├── linker.ld
│ ├── cpu/ │ ├── cpu/
│ │ ├── cpu.c │ │ ├── cpu.c
@ -80,12 +83,20 @@ ClassicOS/
│ │ │ │ │ ├── memory.h │ │ │ │ │ ├── memory.h
│ │ │ │ │ ├── types.h │ │ │ │ │ ├── types.h
│ │ │ │ │ └── types.h │ │ │ │ │ └── types.h
│ │ │ │ └── isr/
│ │ │ │ │ ├── exceptions.c
│ │ │ │ │ ├── exceptions.h
│ │ │ │ │ ├── isr.asm
│ │ │ │ │ ├── isr.c
│ │ │ │ │ └── isr.h
│ │ │ │ └── memory/ │ │ │ │ └── memory/
│ │ │ │ │ ├── memory.c │ │ │ │ │ ├── memory.c
│ │ │ │ │ └── memory.o │ │ │ │ │ └── memory.o
│ │ │ ├── gdt.asm
│ │ │ ├── gdt.c │ │ │ ├── gdt.c
│ │ │ ├── gdt.h │ │ │ ├── gdt.h
│ │ │ ├── gdt.o │ │ │ ├── gdt.o
│ │ │ ├── idt.asm
│ │ │ ├── idt.c │ │ │ ├── idt.c
│ │ │ ├── idt.h │ │ │ ├── idt.h
│ │ │ ├── gdt.o │ │ │ ├── gdt.o
@ -101,7 +112,9 @@ ClassicOS/
│ │ ├── kernel.o │ │ ├── kernel.o
│ │ ├── linker.ld │ │ ├── linker.ld
│ │ ├── print.c │ │ ├── print.c
│ │ ├── print.c │ │ ├── print.h
│ │ ├── stack.c
│ │ ├── stack.h
│ │ ├── timer.c │ │ ├── timer.c
│ │ └── timer.h │ │ └── timer.h
│ └── shell/ │ └── shell/

View File

@ -2,86 +2,130 @@
[ORG 0x7c00] [ORG 0x7c00]
start: start:
XOR AX, AX ; Processor initialization (only stack segment register needed)
MOV DS, AX mov ss, 0x1000 ; Set stack segment register
MOV SS, AX mov sp, 0x7C00 ; Set stack pointer (SP)
MOV SP, 0x7c00 mov ds, ss ; Set data segment register (DS)
MOV SI, loading ; Identify boot device using BIOS calls
CALL printnl identify_drive:
mov ah, 0x0E ; Get Interrupt Vector for INT 13h (Disk Services)
int 0x80
cmp cl, 0x41 ; Check for floppy drive interrupt vector (example)
je is_floppy
cmp cl, 0x80 ; Check for hard disk interrupt vector (example)
je is_harddrive
; Handle invalid drive type (error handling)
...
CALL reset_drives is_floppy:
CALL read_drive Perform floppy disk access (assuming AH=0x02 for read sectors)
mov ah, 0x02 ; Read sectors
mov al, 1 ; Number of sectors to read (1)
MOV SI, loadedSS ; Set CH, CL, DH, DL for floppy based on your system configuration
CALL printnl ; (Replace these values with appropriate settings for your floppy drive)
mov ch, 0 ; Cylinder (example, adjust based on your floppy)
mov cl, 1 ; Sector number (example, adjust based on boot sector location)
mov dh, 0 ; Head number (example, typically 0 for single-sided floppies)
mov dl, 0x00 ; Drive number (0x00 for floppy drive A)
JMP 0x1000:0x0000 ; Set ES:BX to the memory address where you want to store the read data
mov es, 0x1000 ; Example segment (adjust as needed)
mov bx, 0x0 ; Memory offset within the segment (example)
RET int 0x13
jc error_floppy
printnl: ; Implement error handling (omitted here for brevity)
LODSB ; Process the read data from the floppy sector (load second stage bootloader, etc.)
OR AL, AL
JZ .stop
MOV AH, 0x0E is_harddrive:
MOV BH, 0x00
MOV BL, 0x07
INT 0x10
JMP printnl ; Sample code (not guaranteed to work universally)
mov ah, 0x02 ; Set function for read sectors
mov al, 0x01 ; Read one sector
; Set CH, CL, DH for desired sector location (e.g., first sector)
mov dl, 0x80 ; Drive number (assuming drive A is boot device)
mov es, segment_address ; Set ES for buffer to store read data
mov bx, buffer_offset ; Set BX for offset within the buffer
.stop: int 13h ; Raise BIOS interrupt for disk read
MOV AH, 0x03 ; AH = 0x03 (Get Cursor Position and Shape)
XOR BH, BH ; BH = 0x00 (Video Page Number)
INT 0x10 ; Call video interrupt
INC DH ; Check Carry flag for error handling
jc harddrive_not_found ; Jump if Carry flag is set (potential error)
MOV AH, 0x02 ; Set cursor position ; Hard drive likely present (further processing can occur)
XOR BH, BH ; Page number
XOR DL, DL ; Column (start from 0)
INT 0x10 ; Call video interrupt
RET
reset_drives: ; ... (rest of your bootloader code)
XOR AH, AH ; 0 = Reset floppy disk
INT 0x13
JC .reset_error ; If carry flag was set, try again
RET
.reset_error: harddrive_not_found:
MOV SI, resetError ; Handle error condition (missing drive or other issue)
CALL printnl
RET
read_drive: ; ... (error handling logic)
MOV AH, 0x02
MOV AL, 1 ; Number of sectors to read
XOR CH, CH ; Cylinder/Track number
MOV CL, 2 ; Sector number (starting from 1)
XOR DH, DH ; Head number
XOR DL, DL ; Drive number (0x0 for floppy disk / 0x80 for hard disk)
MOV BX, 0x1000 memory_error:
MOV ES, BX ; ... (error handling or continue with limited memory)
XOR BX, BX
INT 0x13 ; Second stage loading (simplified example)
Here's an improved version of the load_second_stage section with the placeholder jump replaced by actual loading logic:
Code snippet
JC .read_error load_second_stage:
RET ; Calculate address of second stage bootloader (assuming offset from boot sector)
mov dx, 0x0000 ; Clear DX register for better calculation
add dx, sector_count ; Add number of sectors to skip (adjust as needed)
shl dx, 5 ; Multiply by sector size (512 bytes)
add dx, 0x7C00 ; Add boot sector address offset
.read_error: ; Read second stage bootloader from calculated address
MOV SI, readError mov ah, 0x02 ; Function for reading sectors
CALL printnl mov al, 1 ; Number of sectors to read (1 for second stage)
RET mov es, dx ; Set ES segment register to calculated address
mov bx, 0x0000 ; Set BX offset within the segment (example)
loading db "Loading Second Stage", 0 int 13h ; Raise BIOS interrupt for disk read
resetError db "Failed to reset drives.", 0
readError db "Failed to read drive", 0
loadedSS db "Loaded second stage...", 0
; Check Carry flag for error handling
jc memory_error ; Jump if Carry flag is set (potential error)
; Second stage likely loaded successfully, jump to it
jmp second_stage_address ; Direct jump to the defined address
error_floppy:
; Display a basic error message (optional)
mov ah, 0x0E ; BIOS video call for displaying text (educational purposes)
mov bh, 0x00 ; Set background color (black)
mov bl, 0x07 ; Set text color (white)
mov dx, error_floppy_message ; Address of error message string
int 0x10 ; BIOS video interrupt
; Halt the boot process (replace with a retry or more advanced error handling)
hlt ; Halt instruction
error_floppy_message db 'Floppy disk read error!', 0x0
memory_error:
; Check for available memory (replace with actual method)
; ... (e.g., call BIOS service for memory size or use a constant value)
cmp available_memory, memory_threshold ; Compare with minimum required memory
jb limited_memory_boot ; Jump if below threshold
; ... (standard error handling for other scenarios)
limited_memory_boot:
; Perform minimal setup for limited memory boot
; ... (disable non-essential features, adjust kernel parameters)
; Load and jump to second stage bootloader (potentially with adjustments)
; ... (modify loading logic if necessary)
; Define variables used for calculation (adjust as needed)
sector_count db 1 ; Number of sectors to skip before second stage (change if needed)
second_stage_address equ 0x8000 ; Replace with actual address of your second stage bootloader
available_memory equ 0x100000 ; Replace with actual memory size detection (1MB in this example)
memory_threshold equ 0x0A0000 ; Example minimum memory required (adjust based on needs)
; Padding and magic number (standard practice)
times 510-($-$$) db 0 times 510-($-$$) db 0
dw 0xaa55 dw 0xaa55

View File

@ -1,32 +1,112 @@
#include "vga.h" #include "vga.h"
#include <memory.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
uint8_t *map_memory(uint32_t base_address, uint32_t size);
#define VGA_MEMORY_ADDRESS 0xA0000 #define VGA_MEMORY_ADDRESS 0xA0000
#define VGA_MEMORY_SIZE 0x60000 #define VGA_MEMORY_SIZE 0x60000
#define VGA_MEMORY_BASE_ADDRESS 0xA0000
static uint8_t* vga_memory = (uint8_t*) VGA_MEMORY_ADDRESS; #define COLOR_GREEN 0x00FF00
uint8_t *vga_memory = (uint8_t *)VGA_MEMORY_ADDRESS;
static uint16_t vga_width = VGA_WIDTH; static uint16_t vga_width = VGA_WIDTH;
static uint16_t vga_height = VGA_HEIGHT; static uint16_t vga_height = VGA_HEIGHT;
static uint8_t vga_depth = VGA_DEPTH; static uint8_t vga_depth = VGA_DEPTH;
uint8_t *framebuffer = (uint8_t *)vga_memory;
void vga_init() { // Set a specific pixel color (assuming RGB format)
int x = 10;
int y = 20;
int color = 0x00FF00; // Green
framebuffer[y * width + x * 3] = (color & 0x00FF) >> 8; // Green value
framebuffer[y * width + x * 3 + 1] = color & 0x00FF; // Blue value
framebuffer[y * width + x * 3 + 2] = color >> 8; // Red value
uint8_t misc_output_value = read_vga_register(0x3C2);
// Check bit 1 to determine text mode (0) or graphics mode (1)
if (misc_output_value & 0x02)
{
printf("Graphics mode active\n");
}
else
{
printf("Text mode active\n");
}
// Function to retrieve the actual VGA base memory address (if needed)
uint32_t get_vga_memory_address()
{
// Implement logic to query the system for the actual VGA base address
// This might involve consulting BIOS information, system calls, etc.
// Return the retrieved address if successful, otherwise return the default value.
return VGA_MEMORY_ADDRESS;
}
void vga_init(void)
{
// Initialize VGA driver here // Initialize VGA driver here
vga_memory = (uint8_t *)map_memory(VGA_MEMORY_BASE_ADDRESS, VGA_MEMORY_SIZE);
if (vga_memory == NULL)
{
// Handle memory mapping error
fprintf(stderr, "Failed to map VGA Memory\n");
}
framebuffer = vga_memory;
} }
void vga_set_resolution(uint16_t width, uint16_t height, uint8_t depth) { // Replace with actual functions to read VGA registers based on your system
uint8_t read_vga_register(uint16_t register_address)
{
uint8_t value = inb(register_address); // Read from the specified port
return value;
}
int inb(uint16_t port) {
// Implement port access logic (replace with system-specific function)
}
void outb(uint16_t port, uint8_t data) {
}
void vga_set_resolution(uint16_t width, uint16_t height, uint8_t depth)
{
// Set VGA resolution here // Set VGA resolution here
// Example (replace with actual register values and writes based on your system):
// Sequencer unlock
outb(0x3C4, 0x01);
outb(0x3C5, 0x01);
// Set specific CRTC registers for desired resolution and refresh rate
// ... (write to specific VGA registers using outb)
// Graphics controller registers for memory access
// ... (write to specific VGA registers using outb)
} }
void vga_draw_pixel(uint16_t x, uint16_t y, uint8_t color) { void vga_draw_pixel(uint16_t x, uint16_t y, uint8_t color)
{
// Draw a pixel on the screen at (x, y) with the given color // Draw a pixel on the screen at (x, y) with the given color
uint32_t offset = y * vga_width + x; uint32_t offset = y * vga_width + x;
vga_memory[offset] = color; vga_memory[offset] = color;
} }
void vga_clear_screen(uint8_t color) { void vga_clear_screen(uint8_t color)
{
// Clear the screen with the given color // Clear the screen with the given color
for (uint32_t i = 0; i < vga_width * vga_height; i++) { for (uint32_t i = 0; i < vga_width * vga_height; i++)
{
vga_memory[i] = color; vga_memory[i] = color;
} }
} }

View File

@ -3,13 +3,20 @@
#include <stdint.h> #include <stdint.h>
#define VGA_WIDTH 640 typedef struct {
#define VGA_HEIGHT 480 uint16_t width;
#define VGA_DEPTH 8 uint16_t height;
uint8_t depth;
// Add other relevant settings like color palette, etc.
} vga_display_settings_t;
void vga_init(); void vga_init();
void vga_set_resolution(uint16_t width, uint16_t height, uint8_t depth); void vga_set_resolution(uint16_t width, uint16_t height, uint8_t depth);
void vga_draw_pixel(uint16_t x, uint16_t y, uint8_t color); void vga_draw_pixel(uint16_t x, uint16_t y, uint8_t color);
void vga_clear_screen(uint8_t color); void vga_clear_screen(uint8_t color);
uint8_t read_vga_register(uint16_t register_address);
int inb(uint16_t port);
void outb(uint16_t port, uint8_t data);
#endif #endif

View File

@ -13,6 +13,8 @@
#define KEYBOARD_ENABLE_COMMAND 0xAE #define KEYBOARD_ENABLE_COMMAND 0xAE
#define KEYBOARD_ENABLE_SCANCODE 0xF4 #define KEYBOARD_ENABLE_SCANCODE 0xF4
#define KEYBOARD_ACKNOWLEDGE_SCANCODE 0xFA #define KEYBOARD_ACKNOWLEDGE_SCANCODE 0xFA
#define KEYCODE_UNKNOWN 0xFF
#define KEYCODE_C 0x03
static uint8_t keyboard_buffer[KEYBOARD_BUFFER_SIZE]; static uint8_t keyboard_buffer[KEYBOARD_BUFFER_SIZE];
static size_t keyboard_buffer_head = 0; static size_t keyboard_buffer_head = 0;
@ -24,18 +26,52 @@ void enable_interrupt(uint8_t vector);
void KeyboardInterruptHandler() void KeyboardInterruptHandler()
{ {
uint8_t scancode = inb(KEYBOARD_DATA_PORT); uint8_t scancode = inb(KEYBOARD_DATA_PORT);
uint8_t keycode = translate_scancode_to_keycode(scancode);
// Add scancode to buffer // Add scancode to buffer
keyboard_buffer[keyboard_buffer_head] = scancode; keyboard_buffer[keyboard_buffer_head] = scancode;
keyboard_buffer_head = (keyboard_buffer_head + 1) % KEYBOARD_BUFFER_SIZE; keyboard_buffer_head = (keyboard_buffer_head + 1) % KEYBOARD_BUFFER_SIZE;
} }
uint8_t translate_scancode_to_keycode(uint8_t scancode)
{
static uint8_t keycode_map[128] = {
[0x01] = KEYCODE_ESC,
[0x02] = KEYCODE_1,
[0x03] = KEYCODE_2,
[0x04] = KEYCODE_3,
[0x05] = KEYCODE_4,
[0x06] = KEYCODE_5,
[0x07] = KEYCODE_6,
[0x08] = KEYCODE_7,
[0x09] = KEYCODE_8,
[0x0A] = KEYCODE_9,
[0x0B] = KEYCODE_0,
[0x0C] = KEYCODE_MINUS,
[0x0D] = KEYCODE_EQUALS,
[0x0E] = KEYCODE_BACKSPACE,
// ... (complete the rest based on the scancode table)
[0xE0] = 0, // Handle extended scancodes (e.g., Print Screen) separately
};
if (scancode < sizeof(keycode_map))
{
// Map scancode directly to keycode
return keycode_map[scancode];
}
else
{
// Handle unknown scancode
return KEYCODE_UNKNOWN; // Or return a special keycode indicating error
}
}
// Initialize keyboard // Initialize keyboard
void keyboard_init() void keyboard_init()
{ {
// Install keyboard interrupt handler // Install keyboard interrupt handler
//set_interrupt_vector(KEYBOARD_INTERRUPT_VECTOR, KeyboardInterruptHandler); set_interrupt_vector(KEYBOARD_INTERRUPT_VECTOR, KeyboardInterruptHandler);
//enable_interrupt(KEYBOARD_INTERRUPT_VECTOR); enable_interrupt(KEYBOARD_INTERRUPT_VECTOR);
// Enable keyboard // Enable keyboard
outb(KEYBOARD_COMMAND_PORT, KEYBOARD_ENABLE_COMMAND); outb(KEYBOARD_COMMAND_PORT, KEYBOARD_ENABLE_COMMAND);

View File

@ -11,5 +11,5 @@ bool keyboard_buffer_empty();
uint8_t keyboard_read_scancode(); uint8_t keyboard_read_scancode();
void set_interrupt_vector(uint8_t vector, void (*handler)()); void set_interrupt_vector(uint8_t vector, void (*handler)());
void enable_interrupt(uint8_t vector); void enable_interrupt(uint8_t vector);
uint8_t translate_scancode_to_keycode(uint8_t scancode);
#endif #endif

View File

@ -19,7 +19,7 @@
#define ISR_TABLE_SIZE 256 #define ISR_TABLE_SIZE 256
// GDT table // GDT table
struct gdt_entry *gdt; struct gdt_entry gdt_entries[3] __attribute__((aligned(0x1000)));
// GDT constants // GDT constants
enum GDT_ACCESS enum GDT_ACCESS
@ -63,9 +63,9 @@ void gdt_init()
//memset(gdt, 0, sizeof(struct gdt_entry) * 3); //memset(gdt, 0, sizeof(struct gdt_entry) * 3);
// Initialize GDT entries // Initialize GDT entries
gdt_set_gate(0, 0, 0, 0, 0, gdt); // Null segment gdt_set_gate(0, 0, 0, 0, 0, &gdt_entries[0]); // Null segment
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF, gdt); // Code segment gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF, &gdt_entries[1]); // Code segment
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF, gdt); // Data segment gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF, &gdt_entries[2]); // Data segment
// Load GDT // Load GDT
struct gdt_ptr gdtp; struct gdt_ptr gdtp;

View File

@ -2,5 +2,6 @@ global LoadIDT
section .text section .text
LoadIDT: LoadIDT:
LIDT [ESP + 32] mov eax, [offset idt] ; Get the IDT address
LIDT [eax + 8*0x33] ; Load only the timer interrupt entry (offset 8*0x33)
RET RET

View File

@ -1,19 +1,32 @@
#include "idt.h" #include "idt.h"
#include "isr/isr.h"
#include "../../../drivers/keyboard/keyboard.h" #include "../../../drivers/keyboard/keyboard.h"
#include "isr/isr.h"
#include <stdint.h>
struct idt_entry idt[256]; struct idt_entry idt[256];
extern void LoadIDT(struct idt_entry* entry); extern void LoadIDT(struct idt_entry *entry);
void TimerInterruptHandler()
{
static uint16_t timer_count = 0;
timer_count++;
}
// Initialize the IDT // Initialize the IDT
void InitializeIDT() void InitializeIDT()
{ {
idt[KEYBOARD_INTERRUPT].base_lo = ((uint16_t*)KeyboardInterruptHandler)[0]; idt[KEYBOARD_INTERRUPT].base_lo = ((uint16_t *)KeyboardInterruptHandler)[0];
idt[KEYBOARD_INTERRUPT].sel = 0x08; idt[KEYBOARD_INTERRUPT].sel = 0x08;
idt[KEYBOARD_INTERRUPT].always0 = 0x00; idt[KEYBOARD_INTERRUPT].always0 = 0x00;
idt[KEYBOARD_INTERRUPT].flags = 0x8E; idt[KEYBOARD_INTERRUPT].flags = 0x8E;
idt[KEYBOARD_INTERRUPT].base_hi = ((uint16_t*)KeyboardInterruptHandler)[1]; idt[KEYBOARD_INTERRUPT].base_hi = ((uint16_t *)KeyboardInterruptHandler)[1];
LoadIDT(&idt[KEYBOARD_INTERRUPT]); idt[0x33].base_lo = (uint16_t)TimerInterruptHandler;
idt[0x33].sel = 0x08;
idt[0x33].always0 = 0x00;
idt[0x33].flags = 0x8E;
idt[0x33].base_hi = (uint16_t)TimerInterruptHandler >> 16;
LoadIDT(&idt[0]);
} }