mirror of
https://github.com/fail0verflow/switch-coreboot.git
synced 2025-05-04 01:39:18 -04:00
Lots of cosmetic and coding style fixes (trivial).
Signed-off-by: Uwe Hermann <uwe@hermann-uwe.de> Acked-by: Uwe Hermann <uwe@hermann-uwe.de> git-svn-id: svn://coreboot.org/repository/LinuxBIOSv3@326 f3766cd6-281f-0410-b1cd-43a5c92072e9
This commit is contained in:
parent
6a69df4065
commit
bd53a46fbd
1 changed files with 199 additions and 167 deletions
|
@ -2,16 +2,12 @@
|
|||
## This file is part of the LinuxBIOS project.
|
||||
##
|
||||
## Copyright (C) 2000,2007 Ronald G. Minnich <rminnich@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2005 Eswar Nallusamy, LANL
|
||||
##
|
||||
## Copyright (C) 2005 Tyan
|
||||
## Written by Yinghai Lu <yhlu@tyan.com> for Tyan.
|
||||
##
|
||||
## (Written by Yinghai Lu <yhlu@tyan.com> for Tyan)
|
||||
## Copyright (C) 2007 coresystems GmbH
|
||||
## Written by Stefan Reinauer <stepan@coresystems.de> for coresystems GmbH.
|
||||
##
|
||||
## Copyright (C) 2007 Advanced Micro Devices
|
||||
## (Written by Stefan Reinauer <stepan@coresystems.de> for coresystems GmbH)
|
||||
## Copyright (C) 2007 Advanced Micro Devices, Inc.
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
|
@ -27,34 +23,38 @@
|
|||
## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
##
|
||||
|
||||
# init code - switch cpu to pmode and enable cache as ram.
|
||||
/* Init code - Switch CPU to protected mode and enable Cache-as-Ram. */
|
||||
|
||||
#include "macros.h"
|
||||
#include <amd_geodelx.h>
|
||||
|
||||
#define LX_STACK_BASE DCACHE_RAM_BASE /* this is where the DCache will be mapped and be used as stack, It would be cool if it was the same base as LinuxBIOS normal stack */
|
||||
/* This is where the DCache will be mapped and be used as stack. It would be
|
||||
* cool if it was the same base as LinuxBIOS normal stack.
|
||||
*/
|
||||
#define LX_STACK_BASE DCACHE_RAM_BASE
|
||||
#define LX_STACK_END LX_STACK_BASE+(DCACHE_RAM_SIZE-4)
|
||||
|
||||
#define LX_NUM_CACHELINES 0x080 /* there are 128lines per way */
|
||||
#define LX_CACHELINE_SIZE 0x020 /* there are 32bytes per line */
|
||||
#define LX_NUM_CACHELINES 0x080 /* There are 128 lines per way. */
|
||||
#define LX_CACHELINE_SIZE 0x020 /* There are 32 bytes per line. */
|
||||
#define LX_CACHEWAY_SIZE (LX_NUM_CACHELINES * LX_CACHELINE_SIZE)
|
||||
#define CR0_CD 0x40000000 /* bit 30 = Cache Disable */
|
||||
#define CR0_NW 0x20000000 /* bit 29 = Not Write Through */
|
||||
#define CR0_CD 0x40000000 /* Bit 30 = Cache Disable */
|
||||
#define CR0_NW 0x20000000 /* Bit 29 = Not Write Through */
|
||||
|
||||
#define ROM_CODE_SEG 0x08
|
||||
#define ROM_DATA_SEG 0x10
|
||||
|
||||
#define CACHE_RAM_CODE_SEG 0x18
|
||||
#define CACHE_RAM_DATA_SEG 0x20
|
||||
|
||||
.code16
|
||||
.globl _stage0
|
||||
_stage0:
|
||||
cli
|
||||
|
||||
/* save the BIST result */
|
||||
/* Save the BIST result. */
|
||||
movl %eax, %ebp;
|
||||
|
||||
/* thanks to kmliu@sis.com.tw for this TLB fix */
|
||||
/* Thanks to kmliu@sis.com.tw for this TLB fix. */
|
||||
/* IMMEDIATELY invalidate the translation lookaside buffer before
|
||||
* executing any further code. Even though paging is disabled we
|
||||
* could still get false address translations due to the TLB if we
|
||||
|
@ -64,28 +64,30 @@ _stage0:
|
|||
xorl %eax, %eax
|
||||
movl %eax, %cr3 /* Invalidate TLB */
|
||||
|
||||
/* switch to protected mode */
|
||||
/* Switch to protected mode. */
|
||||
|
||||
/* NOTE: With GNU assembler version 2.15.94.0.2.2 (i386-redhat-linux)
|
||||
* using BFD version 2.15.94.0.2.2 20041220 this works fine without all
|
||||
* the ld hackery and other things. So leave it as is with this comment.
|
||||
* using BFD version 2.15.94.0.2.2 20041220 this works fine without
|
||||
* all the ld hackery and so on. So leave it as is with this comment.
|
||||
*/
|
||||
|
||||
data32 lgdt %cs:gdtptr
|
||||
|
||||
movl %cr0, %eax
|
||||
andl $0x7FFAFFD1, %eax /* PG,AM,WP,NE,TS,EM,MP = 0 */
|
||||
andl $0x7FFAFFD1, %eax /* PG, AM, WP, NE, TS, EM, MP = 0 */
|
||||
orl $0x60000001, %eax /* CD, NW, PE = 1 */
|
||||
movl %eax, %cr0
|
||||
|
||||
/* Restore BIST result */
|
||||
/* Restore BIST result. */
|
||||
movl %ebp, %eax
|
||||
|
||||
// port80_post(0x23)
|
||||
|
||||
// port80_post (0x23) /* post 0x01 */
|
||||
/* Now we are in protected mode. Jump to a 32 bit code segment. */
|
||||
data32 ljmp $ROM_CODE_SEG, $protected_stage0
|
||||
/* I am leaving this weird jump in here in the event that future gas bugs force it to be used. */
|
||||
/* I am leaving this weird jump in here in the event that future gas
|
||||
* bugs force it to be used.
|
||||
*/
|
||||
#.byte 0x66
|
||||
.code32
|
||||
#ljmp $ROM_CODE_SEG, $protected_stage0
|
||||
|
@ -95,7 +97,7 @@ _stage0:
|
|||
.globl gdt16
|
||||
gdt16 = . - _stage0
|
||||
gdt16x:
|
||||
.word gdt16xend - gdt16x -1 /* compute the table limit */
|
||||
.word gdt16xend - gdt16x -1 /* Compute the table limit. */
|
||||
.long gdt16x
|
||||
.word 0
|
||||
|
||||
|
@ -103,42 +105,42 @@ gdt16x:
|
|||
.word 0xffff, 0x0000
|
||||
.byte 0x00, 0x9b, 0xcf, 0x00
|
||||
|
||||
/* selgdt 0x10,flat data segment */
|
||||
/* selgdt 0x10, flat data segment */
|
||||
.word 0xffff, 0x0000
|
||||
.byte 0x00, 0x93, 0xcf, 0x00
|
||||
gdt16xend:
|
||||
|
||||
/* From now on we are 32bit */
|
||||
|
||||
/* From now on we are 32bit. */
|
||||
.code32
|
||||
|
||||
/* We have two gdts where we could have one. That is ok.
|
||||
/* We have two gdts where we could have one. That is ok.
|
||||
*
|
||||
* Let's not worry about this -- optimizing gdt is pointless since we're
|
||||
* only in it for a little bit.
|
||||
* Let's not worry about this -- optimizing gdt is pointless since
|
||||
* we're only in it for a little bit.
|
||||
*
|
||||
* BTW note the trick below: The GDT points to ITSELF, and the first
|
||||
* good descriptor is at offset 8. So you word-align the table, and
|
||||
* then because you chose 8, you get a nice 64-bit aligned GDT entry,
|
||||
* which is good as this is the size of the entry.
|
||||
*
|
||||
* BTW note the trick below: The GDT points to ITSELF, and the first good
|
||||
* descriptor is at offset 8. So you word-align the table, and then because
|
||||
* you chose 8, you get a nice 64-bit aligned GDT entry, which is good as
|
||||
* this is the size of the entry.
|
||||
* Just in case you ever wonder why people do this.
|
||||
*/
|
||||
.align 4
|
||||
.globl gdtptr
|
||||
.globl gdt_limit
|
||||
gdt_limit = gdt_end - gdt - 1 /* compute the table limit */
|
||||
gdt_limit = gdt_end - gdt - 1 /* Compute the table limit. */
|
||||
|
||||
gdt:
|
||||
gdtptr:
|
||||
.word gdt_end - gdt -1 /* compute the table limit */
|
||||
.long gdt /* we know the offset */
|
||||
.word gdt_end - gdt -1 /* Compute the table limit. */
|
||||
.long gdt /* We know the offset. */
|
||||
.word 0
|
||||
|
||||
/* selgdt 0x08, flat code segment */
|
||||
.word 0xffff, 0x0000
|
||||
.byte 0x00, 0x9b, 0xcf, 0x00
|
||||
|
||||
/* selgdt 0x10,flat data segment */
|
||||
/* selgdt 0x10, flat data segment */
|
||||
.word 0xffff, 0x0000
|
||||
.byte 0x00, 0x93, 0xcf, 0x00
|
||||
|
||||
|
@ -146,15 +148,14 @@ gdtptr:
|
|||
.word 0xffff, 0x0000
|
||||
.byte 0x00, 0x9b, 0xcf, 0x00
|
||||
|
||||
/* selgdt 0x20,flat data segment for CAR */
|
||||
/* selgdt 0x20, flat data segment for CAR */
|
||||
.word 0xffff, 0x0000
|
||||
.byte 0x00, 0x93, 0xcf, 0x00
|
||||
gdt_end:
|
||||
|
||||
/*
|
||||
* When we come here we are in protected mode. We expand
|
||||
* the stack and copies the data segment from ROM to the
|
||||
* memory.
|
||||
/*
|
||||
* When we come here we are in protected mode. We expand the stack
|
||||
* and copy the data segment from ROM to the memory.
|
||||
*
|
||||
* After that, we call the chipset bootstrap routine that
|
||||
* does what is left of the chipset initialization.
|
||||
|
@ -166,16 +167,16 @@ gdt_end:
|
|||
.align 4
|
||||
.globl protected_stage0
|
||||
protected_stage0:
|
||||
//This code was used by v2. TODO
|
||||
// This code was used by v2. TODO
|
||||
lgdt %cs:gdtptr
|
||||
ljmp $ROM_CODE_SEG, $__protected_stage0
|
||||
|
||||
.globl __protected_stage0
|
||||
__protected_stage0:
|
||||
/* Save the BIST value */
|
||||
/* Save the BIST value. */
|
||||
movl %eax, %ebp
|
||||
|
||||
port80_post (0x01) /* post 0x01 */
|
||||
port80_post(0x01)
|
||||
|
||||
movw $ROM_DATA_SEG, %ax
|
||||
movw %ax, %ds
|
||||
|
@ -184,37 +185,37 @@ __protected_stage0:
|
|||
movw %ax, %fs
|
||||
movw %ax, %gs
|
||||
|
||||
/* Restore the BIST value to %eax */
|
||||
/* Restore the BIST value to %eax. */
|
||||
movl %ebp, %eax
|
||||
|
||||
.align 4
|
||||
/* here begins CAR support */
|
||||
/* this particular code is straight from LinuxBIOS V2 */
|
||||
/***************************************************************************
|
||||
/**
|
||||
/** DCacheSetup
|
||||
/**
|
||||
/** Setup data cache for use as RAM for a stack.
|
||||
/**
|
||||
/***************************************************************************/
|
||||
/* Here begins CAR support. */
|
||||
/* This particular code is straight from LinuxBIOS V2. */
|
||||
|
||||
/* DCacheSetup: Setup data cache for use as RAM for a stack. */
|
||||
DCacheSetup:
|
||||
|
||||
invd
|
||||
/* set cache properties */
|
||||
/* Set cache properties. */
|
||||
movl $CPU_RCONF_DEFAULT, %ecx
|
||||
rdmsr
|
||||
movl $0x010010000, %eax /*1MB system memory in write back 1|00100|00 */
|
||||
/* 1MB system memory in write back 1|00100|00. */
|
||||
movl $0x010010000, %eax
|
||||
wrmsr
|
||||
|
||||
/* in LX DCDIS is set after POR which disables the cache..., clear this bit */
|
||||
/* In LX DCDIS is set after POR which disables the cache..., clear
|
||||
* this bit.
|
||||
*/
|
||||
movl CPU_DM_CONFIG0,%ecx
|
||||
rdmsr
|
||||
andl $(~(DM_CONFIG0_LOWER_DCDIS_SET)), %eax /* TODO: make consistent with i$ init, either whole reg = 0, or just this bit... */
|
||||
/* TODO: Make consistent with i$ init, either whole reg = 0, or just
|
||||
* this bit...
|
||||
*/
|
||||
andl $(~(DM_CONFIG0_LOWER_DCDIS_SET)), %eax
|
||||
wrmsr
|
||||
|
||||
/* get cache timing params from BIOS config data locations and apply */
|
||||
/* fix delay controls for DM and IM arrays */
|
||||
/* fix delay controls for DM and IM arrays */
|
||||
/* Get cache timing params from BIOS config data locations and apply. */
|
||||
/* Fix delay controls for DM and IM arrays. */
|
||||
movl $CPU_BC_MSS_ARRAY_CTL0, %ecx
|
||||
xorl %edx, %edx
|
||||
movl $0x2814D352, %eax
|
||||
|
@ -235,7 +236,7 @@ DCacheSetup:
|
|||
movl $0x00000005, %edx
|
||||
wrmsr
|
||||
|
||||
/* Enable setting */
|
||||
/* Enable setting. */
|
||||
movl $CPU_BC_MSS_ARRAY_CTL_ENA, %ecx
|
||||
xorl %edx, %edx
|
||||
movl $0x01, %eax
|
||||
|
@ -246,63 +247,86 @@ DCacheSetup:
|
|||
xorl %esi, %esi
|
||||
xorl %ebp, %ebp
|
||||
|
||||
/* DCache Ways0 through Ways7 will be tagged for LX_STACK_BASE + DCACHE_RAM_SIZE for holding stack */
|
||||
/* remember, there is NO stack yet... */
|
||||
/* DCache Ways0 through Ways7 will be tagged for
|
||||
* LX_STACK_BASE + DCACHE_RAM_SIZE for holding stack.
|
||||
*/
|
||||
/* Remember, there is NO stack yet... */
|
||||
|
||||
/* Tell cache we want to fill WAY 0 starting at the top */
|
||||
/* Tell cache we want to fill Way 0 starting at the top. */
|
||||
xorl %edx, %edx
|
||||
xorl %eax, %eax
|
||||
movl $CPU_DC_INDEX, %ecx
|
||||
wrmsr
|
||||
|
||||
/* startaddress for tag of Way0: ebp will hold the incrementing address. dont destroy! */
|
||||
movl $LX_STACK_BASE, %ebp /* init to start address */
|
||||
orl $1, %ebp /* set valid bit and tag for this Way (B[31:12] : Cache tag value for line/way curr. selected by CPU_DC_INDEX */
|
||||
/* Startaddress for tag of Way0: ebp will hold the incrementing
|
||||
* address. dont destroy!
|
||||
*/
|
||||
movl $LX_STACK_BASE, %ebp /* Init to start address. */
|
||||
/* Set valid bit and tag for this Way (B[31:12] : Cache tag value for
|
||||
* line/way curr. selected by CPU_DC_INDEX.
|
||||
*/
|
||||
orl $1, %ebp
|
||||
|
||||
/* start tag Ways 0 with 128 lines with 32bytes each: edi will hold the line counter. dont destroy! */
|
||||
/* Start tag Ways 0 with 128 lines with 32 bytes each: edi will hold
|
||||
* the line counter. dont destroy!
|
||||
*/
|
||||
movl $LX_NUM_CACHELINES, %edi
|
||||
DCacheSetupFillWay:
|
||||
|
||||
/* fill with dummy data: zero it so we can tell it from PCI memory space (returns FFs). */
|
||||
/* We will now store a line (32 bytes = 4 x 8bytes = 4 quadWords) */
|
||||
DCacheSetupFillWay:
|
||||
/* Fill with dummy data: zero it so we can tell it from PCI memory
|
||||
* space (returns FFs).
|
||||
*/
|
||||
/* We will now store a line (32 bytes = 4 x 8 bytes = 4 quad-words). */
|
||||
movw $0x04, %si
|
||||
xorl %edx, %edx
|
||||
xorl %eax, %eax
|
||||
movl $CPU_DC_DATA, %ecx
|
||||
|
||||
DCacheSetup_quadWordLoop:
|
||||
wrmsr
|
||||
decw %si
|
||||
jnz DCacheSetup_quadWordLoop
|
||||
|
||||
/* Set the tag for this line, need to do this for every new cache line to validate it! */
|
||||
/* accessing CPU_DC_TAG_I makes the LINE field in CPU_DC_INDEX increment and thus cont. in the next cache line... */
|
||||
/* Set the tag for this line, need to do this for every new cache
|
||||
* line to validate it!
|
||||
*/
|
||||
/* Accessing CPU_DC_TAG_I makes the LINE field in CPU_DC_INDEX
|
||||
* increment and thus continue in the next cache line...
|
||||
*/
|
||||
xorl %edx, %edx
|
||||
movl %ebp, %eax
|
||||
movl $CPU_DC_TAG, %ecx
|
||||
wrmsr
|
||||
|
||||
/* switch to next line */
|
||||
/* lines are in Bits10:4 */
|
||||
/* when index is crossing 0x7F -> 0x80 writing a RSVD bit as 0x80 is not a valid CL anymore! */
|
||||
/* Switch to next line. Lines are in Bits10:4. */
|
||||
/* When index is crossing 0x7F -> 0x80 writing a RSVD bit as 0x80 is
|
||||
* not a valid CL anymore!
|
||||
*/
|
||||
movl $CPU_DC_INDEX, %ecx
|
||||
rdmsr
|
||||
addl $0x010, %eax /* TODO: prob. would be more elegant to calc. this from counter var edi... */
|
||||
/* TODO: Probably would be more elegant to calculate this from
|
||||
* counter var edi...
|
||||
*/
|
||||
addl $0x010, %eax
|
||||
wrmsr
|
||||
|
||||
decl %edi
|
||||
jnz DCacheSetupFillWay
|
||||
|
||||
/* 1 Way has been filled, forward start address for next Way, terminate if we have reached end of desired address range */
|
||||
/* 1 Way has been filled, forward start address for next Way,
|
||||
* terminate if we have reached end of desired address range.
|
||||
*/
|
||||
addl $LX_CACHEWAY_SIZE, %ebp
|
||||
cmpl $LX_STACK_END, %ebp
|
||||
jge leave_DCacheSetup
|
||||
movl $LX_NUM_CACHELINES, %edi
|
||||
|
||||
/* switch to next way */
|
||||
/* Switch to next way. */
|
||||
movl $CPU_DC_INDEX, %ecx
|
||||
rdmsr
|
||||
addl $0x01, %eax
|
||||
andl $0xFFFFF80F, %eax /* lets be sure: reset line index Bits10:4 */
|
||||
/* Let's be sure: reset line index Bits10:4. */
|
||||
andl $0xFFFFF80F, %eax
|
||||
wrmsr
|
||||
|
||||
jmp DCacheSetupFillWay
|
||||
|
@ -312,20 +336,22 @@ leave_DCacheSetup:
|
|||
xorl %esi, %esi
|
||||
xorl %ebp, %ebp
|
||||
|
||||
/* Disable the cache, but ... DO NOT INVALIDATE the tags. */
|
||||
/* Disable the cache, but... DO NOT INVALIDATE the tags. */
|
||||
/* Memory reads and writes will all hit in the cache. */
|
||||
/* Cache updates and memory write-backs will not occur ! */
|
||||
/* Cache updates and memory write-backs will not occur! */
|
||||
movl %cr0, %eax
|
||||
orl $(CR0_CD + CR0_NW), %eax /* set the CD and NW bits */
|
||||
orl $(CR0_CD + CR0_NW), %eax /* Set the CD and NW bits. */
|
||||
movl %eax, %cr0
|
||||
|
||||
/* Now point sp to the cached stack. */
|
||||
/* The stack will be fully functional at this location. No system memory is required at all ! */
|
||||
/* set up the stack pointer */
|
||||
/* The stack will be fully functional at this location. No system
|
||||
* memory is required at all!
|
||||
*/
|
||||
/* Set up the stack pointer. */
|
||||
movl $LX_STACK_END, %eax
|
||||
movl %eax, %esp
|
||||
|
||||
/* test the stack*/
|
||||
/* Test the stack. */
|
||||
movl $0x0F0F05A5A, %edx
|
||||
pushl %edx
|
||||
popl %ecx
|
||||
|
@ -333,35 +359,40 @@ leave_DCacheSetup:
|
|||
je DCacheSetupGood
|
||||
movb $0xC5, %al
|
||||
outb %al, $0x80
|
||||
DCacheSetupBad:
|
||||
hlt /* issues */
|
||||
jmp DCacheSetupBad
|
||||
DCacheSetupGood:
|
||||
|
||||
/* If you wanted to maintain the stack in memory you would need to set the tags as dirty
|
||||
so the wbinvd would push out the old stack contents to memory */
|
||||
/* Clear the cache, the following code from crt0.S.lb will setup a new stack*/
|
||||
DCacheSetupBad:
|
||||
hlt /* Issues */
|
||||
jmp DCacheSetupBad
|
||||
|
||||
DCacheSetupGood:
|
||||
/* If you wanted to maintain the stack in memory you would need to
|
||||
* set the tags as dirty so the wbinvd would push out the old stack
|
||||
* contents to memory.
|
||||
*/
|
||||
/* Clear the cache, the following code from crt0.S.lb will setup
|
||||
* a new stack.
|
||||
*/
|
||||
wbinvd
|
||||
|
||||
/* at this point, CAR should be working */
|
||||
/* At this point, CAR should be working. */
|
||||
movl $(LX_STACK_END), %eax
|
||||
movl %eax, %esp
|
||||
|
||||
/* Load a different set of data segments */
|
||||
/* Load a different set of data segments. */
|
||||
movw $CACHE_RAM_DATA_SEG, %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
movw %ax, %ss
|
||||
|
||||
lout:
|
||||
|
||||
/* Restore the BIST result */
|
||||
/* Restore the BIST result. */
|
||||
movl %ebp, %eax
|
||||
/* We need to set ebp ? No need */
|
||||
/* We need to set ebp? No need. */
|
||||
movl %esp, %ebp
|
||||
pushl %eax /* bist */
|
||||
pushl %eax /* BIST */
|
||||
call stage1_main
|
||||
/* We will not go back */
|
||||
/* We will not go back. */
|
||||
|
||||
fixed_mtrr_msr:
|
||||
.long 0x250, 0x258, 0x259
|
||||
.long 0x268, 0x269, 0x26A
|
||||
|
@ -377,13 +408,14 @@ var_mtrr_msr:
|
|||
# Reset vector.
|
||||
|
||||
/*
|
||||
RVECTOR: size of reset vector, default is 0x10
|
||||
RESRVED: size of vpd code, default is 0xf0
|
||||
BOOTBLK: size of bootblock code, default is 0x1f00 (8k-256b)
|
||||
*/
|
||||
* RVECTOR: size of reset vector, default is 0x10
|
||||
* RESRVED: size of vpd code, default is 0xf0
|
||||
* BOOTBLK: size of bootblock code, default is 0x1f00 (8k-256b)
|
||||
*/
|
||||
|
||||
SEGMENT_SIZE = 0x10000
|
||||
RVECTOR = 0x00010
|
||||
|
||||
# Due to YET ANOTHER BUG in GNU bintools, you can NOT have a code16 here.
|
||||
# I think we should leave it this way forever, as the bugs come and
|
||||
# go -- and come again.
|
||||
|
@ -397,8 +429,8 @@ _resetjump:
|
|||
.byte 0xe9
|
||||
.int _stage0 - ( . + 2 )
|
||||
/* Note: The above jump is hand coded to work around bugs in binutils.
|
||||
* 5 byte are used for a 3 byte instruction. This works because x86
|
||||
* is little endian and allows us to use supported 32bit relocations
|
||||
* 5 bytes are used for a 3 byte instruction. This works because x86
|
||||
* is little endian and allows us to use supported 32 bit relocations
|
||||
* instead of the weird 16 bit relocations that binutils does not
|
||||
* handle consistenly between versions because they are used so rarely.
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue