Search
lxdream.org :: lxdream/src/sh4/sh4mem.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/sh4/sh4mem.c
changeset 933:880c37bb1909
prev931:430048ea8b71
next934:3acd3b3ee6d1
author nkeynes
date Wed Dec 24 06:06:23 2008 +0000 (12 years ago)
branchlxdream-mem
permissions -rw-r--r--
last change Start putting cache together
file annotate diff log raw
nkeynes@10
     1
/**
nkeynes@586
     2
 * $Id$
nkeynes@929
     3
 * sh4mem.c is responsible for interfacing between the SH4's internal memory
nkeynes@10
     4
 *
nkeynes@10
     5
 * Copyright (c) 2005 Nathan Keynes.
nkeynes@10
     6
 *
nkeynes@10
     7
 * This program is free software; you can redistribute it and/or modify
nkeynes@10
     8
 * it under the terms of the GNU General Public License as published by
nkeynes@10
     9
 * the Free Software Foundation; either version 2 of the License, or
nkeynes@10
    10
 * (at your option) any later version.
nkeynes@10
    11
 *
nkeynes@10
    12
 * This program is distributed in the hope that it will be useful,
nkeynes@10
    13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
nkeynes@10
    14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
nkeynes@10
    15
 * GNU General Public License for more details.
nkeynes@10
    16
 */
nkeynes@10
    17
nkeynes@35
    18
#define MODULE sh4_module
nkeynes@35
    19
nkeynes@10
    20
#include <string.h>
nkeynes@10
    21
#include <zlib.h>
nkeynes@10
    22
#include "dream.h"
nkeynes@10
    23
#include "mem.h"
nkeynes@10
    24
#include "mmio.h"
nkeynes@10
    25
#include "dreamcast.h"
nkeynes@422
    26
#include "sh4/sh4core.h"
nkeynes@422
    27
#include "sh4/sh4mmio.h"
nkeynes@422
    28
#include "sh4/xltcache.h"
nkeynes@100
    29
#include "pvr2/pvr2.h"
nkeynes@10
    30
nkeynes@929
    31
/* System regions (probably should be defined elsewhere) */
nkeynes@929
    32
extern struct mem_region_fn mem_region_unmapped;
nkeynes@929
    33
extern struct mem_region_fn mem_region_sdram;
nkeynes@929
    34
extern struct mem_region_fn mem_region_vram32;
nkeynes@929
    35
extern struct mem_region_fn mem_region_vram64;
nkeynes@929
    36
extern struct mem_region_fn mem_region_audioram;
nkeynes@929
    37
extern struct mem_region_fn mem_region_flashram;
nkeynes@929
    38
extern struct mem_region_fn mem_region_bootrom;
nkeynes@10
    39
nkeynes@929
    40
/* On-chip regions other than defined MMIO regions */
nkeynes@933
    41
extern struct mem_region_fn p4_region_storequeue;
nkeynes@933
    42
extern struct mem_region_fn p4_region_icache_addr;
nkeynes@933
    43
extern struct mem_region_fn p4_region_icache_data;
nkeynes@933
    44
extern struct mem_region_fn p4_region_ocache_addr;
nkeynes@933
    45
extern struct mem_region_fn p4_region_ocache_data;
nkeynes@933
    46
extern struct mem_region_fn p4_region_itlb_addr;
nkeynes@933
    47
extern struct mem_region_fn p4_region_itlb_data;
nkeynes@933
    48
extern struct mem_region_fn p4_region_utlb_addr;
nkeynes@933
    49
extern struct mem_region_fn p4_region_utlb_data;
nkeynes@10
    50
nkeynes@929
    51
/********************* The main ram address space **********************/
nkeynes@929
    52
static int32_t FASTCALL ext_sdram_read_long( sh4addr_t addr )
nkeynes@929
    53
{
nkeynes@929
    54
    return *((int32_t *)(sh4_main_ram + (addr&0x00FFFFFF)));
nkeynes@929
    55
}
nkeynes@929
    56
static int32_t FASTCALL ext_sdram_read_word( sh4addr_t addr )
nkeynes@929
    57
{
nkeynes@929
    58
    return SIGNEXT16(*((int16_t *)(sh4_main_ram + (addr&0x00FFFFFF))));
nkeynes@929
    59
}
nkeynes@929
    60
static int32_t FASTCALL ext_sdram_read_byte( sh4addr_t addr )
nkeynes@929
    61
{
nkeynes@929
    62
    return SIGNEXT8(*((int16_t *)(sh4_main_ram + (addr&0x00FFFFFF))));
nkeynes@929
    63
}
nkeynes@929
    64
static void FASTCALL ext_sdram_write_long( sh4addr_t addr, uint32_t val )
nkeynes@929
    65
{
nkeynes@929
    66
    *(uint32_t *)(sh4_main_ram + (addr&0x00FFFFFF)) = val;
nkeynes@929
    67
    xlat_invalidate_long(addr);
nkeynes@929
    68
}
nkeynes@929
    69
static void FASTCALL ext_sdram_write_word( sh4addr_t addr, uint32_t val )
nkeynes@929
    70
{
nkeynes@929
    71
    *(uint16_t *)(sh4_main_ram + (addr&0x00FFFFFF)) = (uint16_t)val;
nkeynes@929
    72
    xlat_invalidate_word(addr);
nkeynes@929
    73
}
nkeynes@929
    74
static void FASTCALL ext_sdram_write_byte( sh4addr_t addr, uint32_t val )
nkeynes@929
    75
{
nkeynes@929
    76
    *(uint8_t *)(sh4_main_ram + (addr&0x00FFFFFF)) = (uint8_t)val;
nkeynes@929
    77
    xlat_invalidate_word(addr);
nkeynes@929
    78
}
nkeynes@929
    79
static void FASTCALL ext_sdram_read_burst( unsigned char *dest, sh4addr_t addr )
nkeynes@929
    80
{
nkeynes@929
    81
    memcpy( dest, sh4_main_ram+(addr&0x00FFFFFF), 32 );
nkeynes@929
    82
}
nkeynes@929
    83
static void FASTCALL ext_sdram_write_burst( sh4addr_t addr, unsigned char *src )
nkeynes@929
    84
{
nkeynes@929
    85
    memcpy( sh4_main_ram+(addr&0x00FFFFFF), src, 32 );
nkeynes@929
    86
}
nkeynes@929
    87
nkeynes@929
    88
struct mem_region_fn mem_region_sdram = { ext_sdram_read_long, ext_sdram_write_long, 
nkeynes@929
    89
        ext_sdram_read_word, ext_sdram_write_word, 
nkeynes@929
    90
        ext_sdram_read_byte, ext_sdram_write_byte, 
nkeynes@929
    91
        ext_sdram_read_burst, ext_sdram_write_burst }; 
nkeynes@929
    92
nkeynes@929
    93
nkeynes@929
    94
/********************* The Boot ROM address space **********************/
nkeynes@929
    95
extern sh4ptr_t dc_boot_rom;
nkeynes@929
    96
extern sh4ptr_t dc_flash_ram;
nkeynes@929
    97
extern sh4ptr_t dc_audio_ram;
nkeynes@929
    98
static int32_t FASTCALL ext_bootrom_read_long( sh4addr_t addr )
nkeynes@929
    99
{
nkeynes@929
   100
    return *((int32_t *)(dc_boot_rom + (addr&0x001FFFFF)));
nkeynes@929
   101
}
nkeynes@929
   102
static int32_t FASTCALL ext_bootrom_read_word( sh4addr_t addr )
nkeynes@929
   103
{
nkeynes@929
   104
    return SIGNEXT16(*((int16_t *)(dc_boot_rom + (addr&0x001FFFFF))));
nkeynes@929
   105
}
nkeynes@929
   106
static int32_t FASTCALL ext_bootrom_read_byte( sh4addr_t addr )
nkeynes@929
   107
{
nkeynes@929
   108
    return SIGNEXT8(*((int16_t *)(dc_boot_rom + (addr&0x001FFFFF))));
nkeynes@929
   109
}
nkeynes@929
   110
static void FASTCALL ext_bootrom_read_burst( unsigned char *dest, sh4addr_t addr )
nkeynes@929
   111
{
nkeynes@929
   112
    memcpy( dest, sh4_main_ram+(addr&0x001FFFFF), 32 );
nkeynes@929
   113
}
nkeynes@929
   114
nkeynes@929
   115
struct mem_region_fn mem_region_bootrom = { 
nkeynes@929
   116
        ext_bootrom_read_long, unmapped_write_long, 
nkeynes@929
   117
        ext_bootrom_read_word, unmapped_write_long, 
nkeynes@929
   118
        ext_bootrom_read_byte, unmapped_write_long, 
nkeynes@929
   119
        ext_bootrom_read_burst, unmapped_write_burst }; 
nkeynes@929
   120
nkeynes@929
   121
/********************* The Flash RAM address space **********************/
nkeynes@929
   122
static int32_t FASTCALL ext_flashram_read_long( sh4addr_t addr )
nkeynes@929
   123
{
nkeynes@929
   124
    return *((int32_t *)(dc_flash_ram + (addr&0x0001FFFF)));
nkeynes@929
   125
}
nkeynes@929
   126
static int32_t FASTCALL ext_flashram_read_word( sh4addr_t addr )
nkeynes@929
   127
{
nkeynes@929
   128
    return SIGNEXT16(*((int16_t *)(dc_flash_ram + (addr&0x0001FFFF))));
nkeynes@929
   129
}
nkeynes@929
   130
static int32_t FASTCALL ext_flashram_read_byte( sh4addr_t addr )
nkeynes@929
   131
{
nkeynes@929
   132
    return SIGNEXT8(*((int16_t *)(dc_flash_ram + (addr&0x0001FFFF))));
nkeynes@929
   133
}
nkeynes@929
   134
static void FASTCALL ext_flashram_write_long( sh4addr_t addr, uint32_t val )
nkeynes@929
   135
{
nkeynes@929
   136
    *(uint32_t *)(dc_flash_ram + (addr&0x0001FFFF)) = val;
nkeynes@929
   137
    asic_g2_write_word();
nkeynes@929
   138
}
nkeynes@929
   139
static void FASTCALL ext_flashram_write_word( sh4addr_t addr, uint32_t val )
nkeynes@929
   140
{
nkeynes@929
   141
    *(uint16_t *)(dc_flash_ram + (addr&0x0001FFFF)) = (uint16_t)val;
nkeynes@929
   142
    asic_g2_write_word();
nkeynes@929
   143
}
nkeynes@929
   144
static void FASTCALL ext_flashram_write_byte( sh4addr_t addr, uint32_t val )
nkeynes@929
   145
{
nkeynes@929
   146
    *(uint8_t *)(dc_flash_ram + (addr&0x0001FFFF)) = (uint8_t)val;
nkeynes@929
   147
    asic_g2_write_word();
nkeynes@929
   148
}
nkeynes@929
   149
static void FASTCALL ext_flashram_read_burst( unsigned char *dest, sh4addr_t addr )
nkeynes@929
   150
{
nkeynes@929
   151
    memcpy( dest, dc_flash_ram+(addr&0x0001FFFF), 32 );
nkeynes@929
   152
}
nkeynes@929
   153
static void FASTCALL ext_flashram_write_burst( sh4addr_t addr, unsigned char *src )
nkeynes@929
   154
{
nkeynes@929
   155
    memcpy( dc_flash_ram+(addr&0x0001FFFF), src, 32 );
nkeynes@929
   156
}
nkeynes@929
   157
nkeynes@929
   158
struct mem_region_fn mem_region_flashram = { ext_flashram_read_long, ext_flashram_write_long, 
nkeynes@929
   159
        ext_flashram_read_word, ext_flashram_write_word, 
nkeynes@929
   160
        ext_flashram_read_byte, ext_flashram_write_byte, 
nkeynes@929
   161
        ext_flashram_read_burst, ext_flashram_write_burst }; 
nkeynes@929
   162
nkeynes@931
   163
/***************************** P4 Regions ************************************/
nkeynes@929
   164
nkeynes@931
   165
/* Store-queue (long-write only?) */
nkeynes@931
   166
static void FASTCALL p4_storequeue_write_long( sh4addr_t addr, uint32_t val )
nkeynes@931
   167
{
nkeynes@931
   168
    sh4r.store_queue[(addr>>2)&0xF] = val;
nkeynes@931
   169
}
nkeynes@931
   170
static int32_t FASTCALL p4_storequeue_read_long( sh4addr_t addr )
nkeynes@931
   171
{
nkeynes@931
   172
    return sh4r.store_queue[(addr>>2)&0xF];
nkeynes@931
   173
}
nkeynes@931
   174
nkeynes@931
   175
struct mem_region_fn p4_region_storequeue = { 
nkeynes@931
   176
        p4_storequeue_read_long, p4_storequeue_write_long,
nkeynes@931
   177
        p4_storequeue_read_long, p4_storequeue_write_long,
nkeynes@931
   178
        p4_storequeue_read_long, p4_storequeue_write_long,
nkeynes@931
   179
        unmapped_read_burst, unmapped_write_burst }; // No burst access.
nkeynes@931
   180
nkeynes@931
   181
/* TLB access */
nkeynes@929
   182
struct mem_region_fn p4_region_itlb_addr = {
nkeynes@929
   183
        mmu_itlb_addr_read, mmu_itlb_addr_write,
nkeynes@929
   184
        mmu_itlb_addr_read, mmu_itlb_addr_write,
nkeynes@929
   185
        mmu_itlb_addr_read, mmu_itlb_addr_write,
nkeynes@929
   186
        unmapped_read_burst, unmapped_write_burst };
nkeynes@929
   187
struct mem_region_fn p4_region_itlb_data = {
nkeynes@929
   188
        mmu_itlb_data_read, mmu_itlb_data_write,
nkeynes@929
   189
        mmu_itlb_data_read, mmu_itlb_data_write,
nkeynes@929
   190
        mmu_itlb_data_read, mmu_itlb_data_write,
nkeynes@929
   191
        unmapped_read_burst, unmapped_write_burst };
nkeynes@929
   192
struct mem_region_fn p4_region_utlb_addr = {
nkeynes@929
   193
        mmu_utlb_addr_read, mmu_utlb_addr_write,
nkeynes@929
   194
        mmu_utlb_addr_read, mmu_utlb_addr_write,
nkeynes@929
   195
        mmu_utlb_addr_read, mmu_utlb_addr_write,
nkeynes@929
   196
        unmapped_read_burst, unmapped_write_burst };
nkeynes@929
   197
struct mem_region_fn p4_region_utlb_data = {
nkeynes@929
   198
        mmu_utlb_data_read, mmu_utlb_data_write,
nkeynes@929
   199
        mmu_utlb_data_read, mmu_utlb_data_write,
nkeynes@929
   200
        mmu_utlb_data_read, mmu_utlb_data_write,
nkeynes@929
   201
        unmapped_read_burst, unmapped_write_burst };
nkeynes@929
   202
nkeynes@931
   203
/********************** Initialization *************************/
nkeynes@931
   204
nkeynes@931
   205
mem_region_fn_t *sh4_address_space;
nkeynes@931
   206
nkeynes@931
   207
static void sh4_register_mem_region( uint32_t start, uint32_t end, mem_region_fn_t fn )
nkeynes@931
   208
{
nkeynes@931
   209
    int count = (end - start) >> 12;
nkeynes@931
   210
    mem_region_fn_t *ptr = &sh4_address_space[start>>12];
nkeynes@931
   211
    while( count-- > 0 ) {
nkeynes@931
   212
        *ptr++ = fn;
nkeynes@931
   213
    }
nkeynes@931
   214
}
nkeynes@931
   215
nkeynes@931
   216
static gboolean sh4_ext_page_remapped( sh4addr_t page, mem_region_fn_t fn, void *user_data )
nkeynes@931
   217
{
nkeynes@931
   218
    int i;
nkeynes@931
   219
    for( i=0; i<= 0xC0000000; i+= 0x20000000 ) {
nkeynes@931
   220
        sh4_address_space[(page|i)>>12] = fn;
nkeynes@931
   221
    }
nkeynes@931
   222
}
nkeynes@931
   223
nkeynes@931
   224
nkeynes@931
   225
void sh4_mem_init()
nkeynes@931
   226
{
nkeynes@931
   227
    int i;
nkeynes@931
   228
    mem_region_fn_t *ptr;
nkeynes@931
   229
    sh4_address_space = mem_alloc_pages( sizeof(mem_region_fn_t) * 256 );
nkeynes@931
   230
    for( i=0, ptr = sh4_address_space; i<7; i++, ptr += LXDREAM_PAGE_TABLE_ENTRIES ) {
nkeynes@931
   231
        memcpy( ptr, ext_address_space, sizeof(mem_region_fn_t) * LXDREAM_PAGE_TABLE_ENTRIES );
nkeynes@931
   232
    }
nkeynes@931
   233
    
nkeynes@931
   234
    /* Setup main P4 regions */
nkeynes@931
   235
    sh4_register_mem_region( 0xE0000000, 0xE4000000, &p4_region_storequeue );
nkeynes@931
   236
    sh4_register_mem_region( 0xE4000000, 0xF0000000, &mem_region_unmapped );
nkeynes@931
   237
    sh4_register_mem_region( 0xF0000000, 0xF1000000, &p4_region_icache_addr );
nkeynes@931
   238
    sh4_register_mem_region( 0xF1000000, 0xF2000000, &p4_region_icache_data );
nkeynes@931
   239
    sh4_register_mem_region( 0xF2000000, 0xF3000000, &p4_region_itlb_addr );
nkeynes@931
   240
    sh4_register_mem_region( 0xF3000000, 0xF4000000, &p4_region_itlb_data );
nkeynes@931
   241
    sh4_register_mem_region( 0xF4000000, 0xF5000000, &p4_region_ocache_addr );
nkeynes@931
   242
    sh4_register_mem_region( 0xF5000000, 0xF6000000, &p4_region_ocache_data );
nkeynes@931
   243
    sh4_register_mem_region( 0xF6000000, 0xF7000000, &p4_region_utlb_addr );
nkeynes@931
   244
    sh4_register_mem_region( 0xF7000000, 0xF8000000, &p4_region_utlb_data );
nkeynes@931
   245
    sh4_register_mem_region( 0xF8000000, 0x00000000, &mem_region_unmapped );
nkeynes@931
   246
    
nkeynes@931
   247
    /* Setup P4 control region */
nkeynes@931
   248
    sh4_register_mem_region( 0xFF000000, 0xFF001000, &mmio_region_MMU.fn );
nkeynes@931
   249
    sh4_register_mem_region( 0xFF100000, 0xFF101000, &mmio_region_PMM.fn );
nkeynes@931
   250
    sh4_register_mem_region( 0xFF200000, 0xFF201000, &mmio_region_UBC.fn );
nkeynes@931
   251
    sh4_register_mem_region( 0xFF800000, 0xFF801000, &mmio_region_BSC.fn );
nkeynes@931
   252
    sh4_register_mem_region( 0xFF900000, 0xFFA00000, &mem_region_unmapped ); // SDMR2 + SDMR3
nkeynes@931
   253
    sh4_register_mem_region( 0xFFA00000, 0xFFA01000, &mmio_region_DMAC.fn );
nkeynes@931
   254
    sh4_register_mem_region( 0xFFC00000, 0xFFC01000, &mmio_region_CPG.fn );
nkeynes@931
   255
    sh4_register_mem_region( 0xFFC80000, 0xFFC81000, &mmio_region_RTC.fn );
nkeynes@931
   256
    sh4_register_mem_region( 0xFFD00000, 0xFFD01000, &mmio_region_INTC.fn );
nkeynes@931
   257
    sh4_register_mem_region( 0xFFD80000, 0xFFD81000, &mmio_region_TMU.fn );
nkeynes@931
   258
    sh4_register_mem_region( 0xFFE00000, 0xFFE01000, &mmio_region_SCI.fn );
nkeynes@931
   259
    sh4_register_mem_region( 0xFFE80000, 0xFFE81000, &mmio_region_SCIF.fn );
nkeynes@931
   260
    sh4_register_mem_region( 0xFFF00000, 0xFFF01000, &mem_region_unmapped ); // H-UDI
nkeynes@931
   261
    
nkeynes@931
   262
    register_mem_page_remapped_hook( sh4_ext_page_remapped, NULL );
nkeynes@931
   263
}
nkeynes@931
   264
                           
nkeynes@931
   265
/************** Access methods ***************/
nkeynes@929
   266
#ifdef HAVE_FRAME_ADDRESS
nkeynes@929
   267
#define RETURN_VIA(exc) do{ *(((void **)__builtin_frame_address(0))+1) = exc; return; } while(0)
nkeynes@10
   268
#else
nkeynes@929
   269
#define RETURN_VIA(exc) return NULL
nkeynes@10
   270
#endif
nkeynes@10
   271
nkeynes@931
   272
nkeynes@931
   273
int32_t FASTCALL sh4_read_long( sh4addr_t addr )
nkeynes@10
   274
{
nkeynes@931
   275
    return sh4_address_space[addr>>12]->read_long(addr);
nkeynes@10
   276
}
nkeynes@10
   277
nkeynes@931
   278
int32_t FASTCALL sh4_read_word( sh4addr_t addr )
nkeynes@10
   279
{
nkeynes@931
   280
    return sh4_address_space[addr>>12]->read_word(addr);
nkeynes@10
   281
}
nkeynes@10
   282
nkeynes@931
   283
int32_t FASTCALL sh4_read_byte( sh4addr_t addr )
nkeynes@10
   284
{
nkeynes@931
   285
    return sh4_address_space[addr>>12]->read_byte(addr);
nkeynes@931
   286
}
nkeynes@931
   287
nkeynes@931
   288
void FASTCALL sh4_write_long( sh4addr_t addr, uint32_t val )
nkeynes@931
   289
{
nkeynes@931
   290
    sh4_address_space[addr>>12]->write_long(addr, val);
nkeynes@931
   291
}
nkeynes@931
   292
nkeynes@931
   293
void FASTCALL sh4_write_word( sh4addr_t addr, uint32_t val )
nkeynes@931
   294
{
nkeynes@931
   295
    sh4_address_space[addr>>12]->write_word(addr,val);
nkeynes@931
   296
}
nkeynes@931
   297
nkeynes@931
   298
void FASTCALL sh4_write_byte( sh4addr_t addr, uint32_t val )
nkeynes@931
   299
{
nkeynes@931
   300
    sh4_address_space[addr>>12]->write_byte(addr, val);
nkeynes@10
   301
}
nkeynes@10
   302
nkeynes@10
   303
/* FIXME: Handle all the many special cases when the range doesn't fall cleanly
nkeynes@400
   304
 * into the same memory block
nkeynes@10
   305
 */
nkeynes@912
   306
void mem_copy_from_sh4( sh4ptr_t dest, sh4addr_t srcaddr, size_t count ) {
nkeynes@103
   307
    if( srcaddr >= 0x04000000 && srcaddr < 0x05000000 ) {
nkeynes@736
   308
        pvr2_vram64_read( dest, srcaddr, count );
nkeynes@103
   309
    } else {
nkeynes@736
   310
        sh4ptr_t src = mem_get_region(srcaddr);
nkeynes@736
   311
        if( src == NULL ) {
nkeynes@736
   312
            WARN( "Attempted block read from unknown address %08X", srcaddr );
nkeynes@736
   313
        } else {
nkeynes@736
   314
            memcpy( dest, src, count );
nkeynes@736
   315
        }
nkeynes@103
   316
    }
nkeynes@10
   317
}
nkeynes@10
   318
nkeynes@912
   319
void mem_copy_to_sh4( sh4addr_t destaddr, sh4ptr_t src, size_t count ) {
nkeynes@325
   320
    if( destaddr >= 0x10000000 && destaddr < 0x14000000 ) {
nkeynes@736
   321
        pvr2_dma_write( destaddr, src, count );
nkeynes@736
   322
        return;
nkeynes@325
   323
    } else if( (destaddr & 0x1F800000) == 0x05000000 ) {
nkeynes@736
   324
        pvr2_render_buffer_invalidate( destaddr, TRUE );
nkeynes@325
   325
    } else if( (destaddr & 0x1F800000) == 0x04000000 ) {
nkeynes@736
   326
        pvr2_vram64_write( destaddr, src, count );
nkeynes@736
   327
        return;
nkeynes@325
   328
    }
nkeynes@502
   329
    sh4ptr_t dest = mem_get_region(destaddr);
nkeynes@325
   330
    if( dest == NULL )
nkeynes@736
   331
        WARN( "Attempted block write to unknown address %08X", destaddr );
nkeynes@325
   332
    else {
nkeynes@736
   333
        xlat_invalidate_block( destaddr, count );
nkeynes@736
   334
        memcpy( dest, src, count );
nkeynes@90
   335
    }
nkeynes@10
   336
}
.