Add RAM memory managment

This commit is contained in:
Yann MAGNIN 2019-12-29 16:39:30 +01:00
parent df00142f07
commit 145e75088c
29 changed files with 809 additions and 23 deletions

View File

@ -48,6 +48,16 @@ typedef struct fx9860_context_s
uint32_t vbr;
} fx9860_context_t;
typedef struct common_context_s
{
uint32_t reg[16];
uint32_t gbr;
uint32_t macl;
uint32_t mach;
uint32_t ssr;
uint32_t spc;
} common_context_t;
// Context primitive.
extern void fx9860_context_save(fx9860_context_t *context);
extern void fx9860_context_restore(fx9860_context_t *context);

40
include/kernel/memory.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef __KERNEL_MEMORY_H__
# define __KERNEL_MEMORY_H__
#include <stddef.h>
#include <stdint.h>
#define PM_BLOCK_SIZE (64)
struct pm_block_cache_s
{
// Block status
enum {
UNUSED,
USED
} status;
// Space informations.
uint16_t start;
uint32_t end;
// Linked list
struct pm_block_cache_s *next;
};
struct memory_info_s
{
// Cache informations.
struct pm_block_cache_s *cache;
struct pm_block_cache_s *head;
// RAM informations.
uint32_t start;
uint32_t blocks;
};
// Function
extern void *pm_alloc(size_t size);
extern void pm_free(void *ptr);
#endif /*__KERNEL_MEMORY_H__*/

49
include/kernel/process.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef __KERNEL_PROCESS_H__
# define __KERNEL_PROCESS_H__
#include <stddef.h>
#include <stdint.h>
#include <kernel/context.h>
#include <kernel/types.h>
#define PROCESS_NAME_LENGHT (16)
#define PROCESS_MAX (3)
#define PROC_IDLE (0)
// define process struct.
//TODO: signal !
typedef struct process_s
{
// Process name.
char name[PROCESS_NAME_LENGHT];
// Context management
common_context_t context;
// Signals management.
//sighandler_t signal[NSIG];
// Other process management.
struct process_s *parent;
struct process_s *child;
struct process_s *next;
} process_t;
// Internal struct used by the
// static process stack
struct process_stack_s
{
struct process_s process;
int status;
};
// Functions.
extern pid_t process_create(const char *name);
extern process_t *process_get(pid_t pid);
extern int process_switch(pid_t pid);
// Internal function.
extern pid_t process_alloc(process_t **process);
#endif /*__KERNEL_PROCESS_H__*/

View File

@ -3,6 +3,26 @@
#include <stdint.h>
#include <stddef.h>
#include <kernel/types.h>
//---
//
// Vhex part !!
//
//---
extern pid_t sys_fork(void);
extern pid_t sys_getpid(void);
extern pid_t sys_getppid(void);
extern pid_t sys_waitpid(pid_t pid, int *wstatus, int options);
//---
//
// CASIO PART !!
// TODO: remove me ?
//
//----
// Internal Casio datat structure
struct rect

View File

@ -26,6 +26,8 @@ typedef enum mpu_e
MPU_UNKNOWN,
} mpu_t;
typedef int pid_t;
// Force inline function.
#define INLINE __attribute__((always_inline)) inline

View File

@ -0,0 +1,13 @@
#ifndef __KERNEL_UNISTD_32_H__
# define __KERNEL_UNISTD_32_H__
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#endif /*__KERNEL_UNISTD_32_H__*/

19
include/lib/unistd.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef __LIB_UNISTD_H__
# define __LIB_UNISTD_H__
#include <stddef.h>
#include <stdint.h>
#include <kernel/types.h>
// Define syscall LIST
#include <kernel/unistd_32.h>
extern pid_t fork(void);
//TODO: move me
#define WNOHANG 0
#define WUNTRACED 1
#define WCONTINUED 2
extern pid_t waitpid(pid_t pid, int *wstatus, int options);
#endif /*__LIB_UNISTD_H__*/

View File

@ -0,0 +1,56 @@
.text
.global _kernel_switch
.type _kernel_switch, @function
.align 2
_kernel_switch:
! Save process context into unbakable register
! @note:
! I do not save r8 ~ r14 because we will
! never return into the bootstrap part.
mov r4, r8
! Update SR register to block
! interrupt / exception and
! switch register bank to simalate
! system call.
mov.l .sr_msk, r1 ! get mask for SR.BL = 1, SR.RB = 1 and SR.IMASK = 0b1111
stc sr, r0 ! get SR register
or r1, r0 ! set mask for BL and IMASK
ldc r0, sr ! update SR regsiter
! set process context into bankable register
! because unbankable register will be over-written.
mov r8, r0
! Load first process
ldc.l @r0+, R0_BANK ! set "process" r0 regsiter
ldc.l @r0+, R1_BANK ! set "process" r1 regsiter
ldc.l @r0+, R2_BANK ! set "process" r2 regsiter
ldc.l @r0+, R3_BANK ! set "process" r3 regsiter
ldc.l @r0+, R4_BANK ! set "process" r4 regsiter
ldc.l @r0+, R5_BANK ! set "process" r5 regsiter
ldc.l @r0+, R6_BANK ! set "process" r6 regsiter
ldc.l @r0+, R7_BANK ! set "process" r7 regsiter
mov.l @r0+, r8 ! set r8 regsiter
mov.l @r0+, r9 ! set r9 regsiter
mov.l @r0+, r10 ! set r10 regsiter
mov.l @r0+, r11 ! set r11 regsiter
mov.l @r0+, r12 ! set r12 regsiter
mov.l @r0+, r13 ! set r13 regsiter
mov.l @r0+, r14 ! set r14 regsiter
mov.l @r0+, r2 ! Stack not handled for now
ldc.l @r0+, gbr ! set gbr regsiter
lds.l @r0+, macl ! set macl regsiter
lds.l @r0+, mach ! set mach regsiter
ldc.l @r0+, ssr ! set ssr regsiter
ldc.l @r0+, spc ! set spc regsiter
! Process switch
rte
nop
.align 4
.sr_msk: .long 0x300000f0
.end

View File

@ -3,8 +3,8 @@
#include <kernel/context.h>
#include <kernel/atomic.h>
#include <kernel/types.h>
#include <kernel/process.h>
#include <lib/display.h>
#include <kernel/syscall.h>
#include <lib/string.h>
// Internal symbols
@ -35,6 +35,7 @@ extern uint32_t edtors;
// Internal functions.
extern void vhex_context_set(void);
extern void kernel_switch(common_context_t *context);
extern mpu_t mpu_get(void);
extern int main(void);
@ -116,18 +117,20 @@ int start(void)
vhex_context_set();
atomic_end();
// Call high level abstraction
error = main();
// Create first process: Vhex.
uint32_t ssr = atomic_start();
pid_t vhex_pid = process_create("Vhex");
process_t *vhex_process = process_get(vhex_pid);
vhex_process->context.spc = (uint32_t)&main;
vhex_process->context.ssr = ssr;
// Restore Casio's context.
atomic_start();
fx9860_context_restore(&casio_context);
atomic_end();
// Switch to first process.
kernel_switch(&vhex_process->context);
// Execute destructor.
section_execute(&bdtors, &edtors);
// Return properly
// TODO: involve main menu ?
return (error);
// normally the kernel SHOULD not
// arrive here.
while (1)
{
__asm__ volatile ("sleep");
}
}

View File

@ -1,6 +1,5 @@
#include <lib/display.h>
__attribute__((section(".vhex.exception"), interrupt_handler))
void exception_handler(void)
{
uint32_t spc;

View File

@ -0,0 +1,52 @@
.section ".vhex.exception", "awx", @progbits
.global _exception_handler_pre
.type _exception_handler_pre, @function
.extern _exception_handler
.extern _syscall_pre
.align 2
_exception_handler_pre:
! Save critical regsiter.
stc.l spc, @-r15 ! save SPC register.
stc.l ssr, @-r15 ! save SSR register.
sts.l pr, @-r15 ! save pr register.
! Check syscall (trapa)
mov.l .expevt, r0 ! r0 = EXPEVT address.
mov.l .trapa_code, r1 ! r1 = trapa exception code
mov.l @r0, r0 ! r0 = excecption code
cmp/eq r1, r0 ! if exception code == TRAPA CODE...
bt _trapa_entry ! ...if yes, jump at <trapa_entry>
! Call high-level abstraction
mov.l .exception_handler, r0 ! get high-level aception abstraction
jsr @r0 ! call abstraction
nop ! (db) nop
bra _exception_handler_exit ! jump at <_exception_handler_exit>
nop ! (db) nop.
_trapa_entry:
! Call syscall pre handler
mov.l .syscall_pre, r0 ! get syscall pre handler address
jsr @r0 ! call pre handler
nop ! (db) nop
_exception_handler_exit:
! Restore critical regsiter
lds.l @r15+, pr ! restore PR register.
ldc.l @r15+, ssr ! restore SSR regsiter.
ldc.l @r15+, spc ! restore SPC regsiter.
! Exit properly
rte ! exit
nop ! (db) nop
.align 4
.expevt: .long 0xff000024
.trapa_code: .long 0x00000160
.exception_handler: .long _exception_handler
.syscall_pre: .long _syscall_pre
.end

View File

@ -0,0 +1,59 @@
#include <kernel/memory.h>
#include <lib/display.h>
// Internal data.
struct memory_info_s pmemory;
__attribute__((constructor(101)))
void memory_init(void)
{
extern uint32_t ram_start;
uint32_t ram_end;
uint32_t ram_size;
// TODO: determine RAM's end.
ram_end = 0x88080000;
ram_size = ram_end - (uint32_t)&ram_start;
// DEBUG
/*dclear();
dprint(0, 0, "RAM diagnostic");
dprint(0, 1, "start = %p", &ram_start);
dprint(0, 2, "end = %p", ram_end);
dprint(0, 3, "size = %dko", ram_size / 1024);
dupdate();*/
//for (int i = 0 ; i < 9000000 ; i = i + 1);
// Get the number of block available
// and calculate the real numer of block with
// the cache.
// TODO: check if RAM can be used ? (block == 0)
// @note: try to avoid too long search part.
pmemory.cache = (void*)&ram_start;
pmemory.blocks = (ram_size / PM_BLOCK_SIZE) >> 1;
ram_end = ram_end - (PM_BLOCK_SIZE * pmemory.blocks);
while ((uint32_t)&pmemory.cache[pmemory.blocks] < ram_end - PM_BLOCK_SIZE)
{
pmemory.blocks = pmemory.blocks + 1;
ram_end = ram_end - PM_BLOCK_SIZE;
}
// Get "real" physical memory start
pmemory.start = ram_end;
// DEBUG
/*dclear();
dprint(0, 0, "Cache diagnostic");
dprint(0, 1, "Bloks = %d (%do)", pmemory.blocks, PM_BLOCK_SIZE);
dprint(0, 2, "Start = %p", pmemory.cache);
dprint(0, 3, "PRAM = %p", pmemory.start);
dupdate();
for (int i = 0 ; i < 9000000 ; i = i + 1);*/
// Initialize cache
for (uint32_t i = 0 ; i < pmemory.blocks ; i = i + 1)
{
pmemory.cache[i].status = UNUSED;
pmemory.cache[i].next = NULL;
}
}

View File

@ -0,0 +1,85 @@
#include <kernel/memory.h>
static struct pm_block_cache_s *block_alloc(void)
{
extern struct memory_info_s pmemory;
uint32_t i;
i = -1;
while (++i < pmemory.blocks)
{
// Check if the block are used
if (pmemory.cache[i].status == USED)
continue;
// Initialize block and return address
pmemory.cache[i].status = USED;
pmemory.cache[i].next = NULL;
return (&pmemory.cache[i]);
}
return (NULL);
}
void *pm_alloc(size_t size)
{
extern struct memory_info_s pmemory;
struct pm_block_cache_s **head;
struct pm_block_cache_s *block;
uint32_t block_entry;
uint32_t nb_blocks;
// Check obvious error.
if (size == 0)
return (NULL);
// Get the number of blocks we need.
nb_blocks = (size + PM_BLOCK_SIZE - 1) / PM_BLOCK_SIZE;
// Find block entry.
block_entry = 0;
head = &pmemory.head;
while (*head != NULL)
{
// Check is it is the last allocated
// object.
if ((*head)->next == NULL)
{
// Check memory space.
if ((*head)->end + 1 + nb_blocks >= pmemory.blocks)
return (NULL);
// Get cache entry.
block_entry = (*head)->end + 1;
head = &(*head)->next;
break;
}
// Calculate the gap between current
// allocated object and the next object.
if (((*head)->next->start) - ((*head)->end + 1) >= nb_blocks)
{
block_entry = (*head)->end + 1;
head = &(*head)->next;
break;
}
// Get next allocated block.
head = &(*head)->next;
}
// Setup new allocated block
block = block_alloc();
if (block == NULL)
return (NULL);
// Initialize new block
block->start = block_entry;
block->end = block_entry + nb_blocks - 1;
// Insert new block.
block->next = *head;
*head = block;
// Generate physical memory address
return ((void*)((block_entry * PM_BLOCK_SIZE) + pmemory.start));
}

View File

@ -0,0 +1,53 @@
#include <kernel/memory.h>
#include <kernel/devices/tty.h>
void pm_free(void *ptr)
{
extern struct memory_info_s pmemory;
struct pm_block_cache_s **head;
uint32_t block_entry;
uint32_t sptr;
// Save address for error message.
sptr = (uint32_t)ptr;
// Get the "real" physical space.
ptr = (void*)(ptr - pmemory.start);
// Check misaligned pointer.
if (((uint32_t)ptr % PM_BLOCK_SIZE) != 0)
{
tty_write(
"pm_free: Warning, you try to free misaligned"
"pointer address (%p)\n", sptr
);
return;
}
// Get block entry.
block_entry = (uint32_t)ptr / PM_BLOCK_SIZE;
// Walk into "head" cache and try to find
// the allocated block.
head = &pmemory.head;
while (*head != NULL)
{
// Check the allocated block.
if ((*head)->start != block_entry)
{
head = &(*head)->next;
continue;
}
// Free the block and return
(*head)->status = UNUSED;
*head = (*head)->next;
return;
}
// No block found, display error.
tty_write(
"pm_free: Warning, you try to free unused"
"allocated memory (%p)", sptr
);
}

View File

@ -0,0 +1,19 @@
#include <kernel/process.h>
#include <lib/string.h>
// This function SHOULD not be called
// without atomic operation !!
pid_t process_alloc(process_t **process)
{
extern struct process_stack_s process_stack[PROCESS_MAX];
for (int i = 0 ; i < PROCESS_MAX ; i = i + 1)
{
if (process_stack[i].status == PROC_IDLE)
{
*process = &process_stack[i].process;
return (i);
}
}
return (-1);
}

View File

@ -0,0 +1,19 @@
#include <kernel/process.h>
// Create all internal global
// used to handle process.
struct process_stack_s process_stack[PROCESS_MAX];
process_t *process_current;
__attribute__((constructor))
void process_constructor(void)
{
// Set all process to idle state.
for (int i = 0 ; i < PROCESS_MAX ; i = i + 1)
{
process_stack[i].status = PROC_IDLE;
}
// No process is currently running.
process_current = NULL;
}

View File

@ -0,0 +1,36 @@
#include <kernel/process.h>
#include <lib/string.h>
pid_t process_create(const char *name)
{
extern process_t *process_current;
process_t *process;
pid_t process_pid;
// Check error
if (name == NULL)
return (-1);
// Try to find free slot.
process_pid = process_alloc(&process);
if (process == NULL)
return (-1);
// Set process name.
strncpy(process->name, name, PROCESS_NAME_LENGHT);
// Initialize context.
for (int i = 0 ; i < 16 ; i = i + 1)
process->context.reg[i] = 0x00000000;
process->context.gbr = 0x00000000;
process->context.macl = 0x00000000;
process->context.mach = 0x00000000;
process->context.ssr = 0x00000000;
process->context.spc = 0x00000000;
// Initialize processes.
process->parent = process_current;
process->child = NULL;
process->next = NULL;
return (process_pid);
}

13
src/kernel/process/get.c Normal file
View File

@ -0,0 +1,13 @@
#include <kernel/process.h>
process_t *process_get(pid_t pid)
{
extern struct process_stack_s process_stack[PROCESS_MAX];
// Check error
if (pid < 0 || pid >= PROCESS_MAX)
return (NULL);
// Return process.
return (&process_stack[pid].process);
}

View File

@ -0,0 +1,27 @@
#include <kernel/process.h>
//FIXME: atomic operation !!
int process_switch(pid_t pid)
{
extern process_t *process_current;
common_context_t *context_current;
common_context_t *context_next;
process_t *process;
// Get current context
context_current =
(process_current != NULL)
? &process_current->context
: NULL;
// Get next context.
process = process_get(pid);
if (process == NULL)
return (-1);
context_next = &process->context;
// Context switch
// TODO: SYSCALL !!!!!
//context_switch(context_current, context_next);
return (0);
}

View File

@ -0,0 +1,21 @@
#include <kernel/process.h>
pid_t sys_fork(void)
{
extern process_t *process_current;
process_t **process_new;
pid_t process_pid;
// Get the new process slot.
process_new = &process_current->child;
while (*process_new != NULL)
process_new = &(*process_new)->next;
// Try to find new sheduler place.
process_pid = process_alloc(&(*process_new));
if (*process_new == NULL)
return (-1);
// Initialize context.
return (-1);
}

View File

@ -0,0 +1,6 @@
#include <kernel/process.h>
pid_t sys_waitpid(pid_t pid, int *wstatus, int options)
{
return (-1);
}

View File

@ -0,0 +1,19 @@
#include <kernel/syscall.h>
#include <lib/display.h>
static const void *sys_handler[] = {
NULL, //restart
NULL, //exit
sys_fork, //fork
NULL, //read
NULL, //write
NULL, //open
NULL, //close
sys_waitpid, //waitpid
};
void *sys_get_handler(int sysno)
{
//FIXME: Check sysno validity
return ((void *)sys_handler[sysno]);
}

View File

@ -0,0 +1,57 @@
.text
.global _syscall_pre
.type _syscall_pre, @function
.extern _sys_get_handler
.align 2
_syscall_pre:
! save some used register.
mov.l r8, @-r15 ! save r8 register
mov.l r9, @-r15 ! save r9 register
sts.l pr, @-r15 ! save PR register
! Call syscall high-level abstraction
! to get appropriate system handler
mov.l .tra, r4 ! r4 = TRA regsiter address (trapa exception code)
mov.l .sys_get_handler, r0 ! r0 = high-level abstration handler
mov.l @r4, r4 ! r4 = trapa exception code
jsr @r0 ! call abstraction
shlr2 r4 ! (db) r4 = syscall number
cmp/eq #0, r0 ! if r0 == NULL...
bt.s syscall_pre_exit ! ...if yes, jump at <syscall_pre_exit>
mov r0, r9 ! save kernel handler into unbankable register
! Get and save SR register
stc sr, r0 ! get SR register
mov r0, r8 ! save SR register
! Switch register bank and allow interrupt
! @note: user context has been saved
! during the `exception_handler_pre`
mov.l .sr_mask, r1 ! get SR mask for BL and IMASK
and r1, r0 ! set SR.BL = 0, SR.RB = 0 and SR.IMASK = 0b0000
ldc r0, sr ! update SR regsiter
! Call kernel abstraction
jsr @r9 ! call system handler
nop ! (db) nop.
! Restore SR regsiter
ldc r8, sr ! SR.BL = 1, SR.RB = 1 and SR.IMASK = old mask.
syscall_pre_exit:
! Restore used regsiter.
lds.l @r15+, pr ! restore PR register
mov.l @r15+, r9 ! restore r9 register
mov.l @r15+, r8 ! restore r8 register
! Exit properly
rts ! exit
nop ! (db) nop
.align 4
.tra: .long 0xff000020
.sr_mask: .long ~(0x300000f0)
.sys_get_handler: .long _sys_get_handler
.end

12
src/lib/unistd/fork.S Normal file
View File

@ -0,0 +1,12 @@
.text
.global _fork
.type _fork, @function
#include "kernel/unistd_32.h"
.align 2
_fork:
trapa #__NR_fork
rts
nop
.end

12
src/lib/unistd/waitpid.S Normal file
View File

@ -0,0 +1,12 @@
.text
.global _waitpid
.type _waitpid, @function
#include "kernel/unistd_32.h"
.align 2
_waitpid:
trapa #__NR_waitpid
rts
nop
.end

View File

@ -1,6 +1,7 @@
#include "builtin.h"
#include <kernel/devices/ubc.h>
#include <lib/display.h>
#include <lib/unistd.h>
// TODO: remove me !!
extern void test(void);
@ -15,6 +16,14 @@ VHEX_BUILTIN(fxdb)
dprint(0, 0, "FXDB - entry !!");
dupdate();
for (int i = 0 ; i < 9000000 ; i = i + 1);
int fion = fork();
dclear();
dprint(0, 0, "FXDB - entry !!");
dprint(0, 1, "fork test = %d", fion);
dupdate();
for (int i = 0 ; i < 9000000 ; i = i + 1);
return (0);
// Open User Break Controller.

View File

@ -0,0 +1,60 @@
#include "builtin.h"
#include <kernel/memory.h>
#include <lib/display.h>
VHEX_BUILTIN(ram)
{
extern struct memory_info_s pmemory;
extern uint32_t ram_start;
uint32_t ram_end;
uint32_t ram_size;
// TODO: determine RAM's end.
ram_end = 0x88080000;
ram_size = ram_end - (uint32_t)&ram_start;
//dprint(0, 0, "RAM diagnostic");
//dprint(0, 1, "start = %p", &ram_start);
//dprint(0, 2, "end = %p", 0x88080000);
//dprint(0, 3, "size = %dko", ram_size / 1024);
//dprint(0, 5, "Cache diagnostic");
//dprint(0, 0, "Bloks = %d (%do)", pmemory.blocks, PM_BLOCK_SIZE);
//dprint(0, 1, "Start = %p", pmemory.cache);
//dprint(0, 2, "PRAM = %p", pmemory.start);
//dupdate();
//TODO: GetKey
//for (int i = 0 ; i < 9000000 ; i = i + 1);
// Try to alloc.
void *test0 = pm_alloc(129);
void *test1 = pm_alloc(1024);
void *test2 = pm_alloc(129);
// Display address
dclear();
dprint(0, 0, "test0 = %p (129o)", test0);
dprint(0, 1, "test1 = %p (1024o)", test1);
dprint(0, 2, "test2 = %p (129o)", test2);
// Try to free allocated space.
pm_free(test1);
// Try to alloc again.
test1 = pm_alloc(64);
// Display RAM abstract cache
int i = 0;
struct pm_block_cache_s *head = pmemory.head;
while (head != NULL && i < 7)
{
dprint(0, 3 + i, "s:%d-e:%d-n:%p", head->start, head->end, head->next);
head = head->next;
i = i + 1;
}
dupdate();
for (int i = 0 ; i < 9000000 ; i = i + 1);
return (0);
}

View File

@ -2,6 +2,7 @@
#include "builtin.h"
#include <lib/string.h>
#include <lib/display.h>
#include <lib/unistd.h>
//TODO: use agc, argv.
int check_builtin(char *cmd)
@ -9,23 +10,38 @@ int check_builtin(char *cmd)
extern uint32_t bbuiltin_section;
extern uint32_t ebuiltin_section;
struct builtin_s *list;
int wstatus;
pid_t pid;
int i;
i = -1;
list = (void*)&bbuiltin_section;
while ((uint32_t)&list[++i] < (uint32_t)&ebuiltin_section)
{
dclear();
dprint(0, 0, "builtin - %s", list[i].name);
dupdate();
for (int i = 0 ; i < 9000000 ; i = i + 1);
if (strcmp(list[i].name, cmd) != 0)
continue;
// Create subprocess
//pid = fork();
//if (pid < 0)
// return (1);
// If we are the child execute
// the builtins.
//if (pid == 0)
//{
/*dclear();
dprint(0, 0, "Child process !!");
dprint(0, 1, "PID = %d", getpid());
dprint(0, 2, "PPID = %d", getppid());
dupdate();*/
if (strcmp(list[i].name, cmd) == 0)
{
list[i].entry(0, NULL);
return (0);
}
// } else {
// waitpid(pid, &wstatus, WCONTINUED);
//TODO: signal handling.
// }
}
return (1);
}

View File

@ -88,7 +88,7 @@ SECTIONS
*(.data)
. = ALIGN(4);
_ram_start = ALIGN(1024);
} > ram AT> rom : data
_sdata = SIZEOF(.data);