[edk2-devel] [PATCH v5 08/35] OvmfPkg/XenResetVector: Allow jumpstart from either hvmloader or PVH

Anthony PERARD posted 35 patches 5 years, 3 months ago
[edk2-devel] [PATCH v5 08/35] OvmfPkg/XenResetVector: Allow jumpstart from either hvmloader or PVH
Posted by Anthony PERARD 5 years, 3 months ago
This patch allows the ResetVector to be run indenpendently from build
time addresses.

The goal of the patch is to avoid having to create RAM just below 4G
when creating a Xen PVH guest while being compatible with the way
hvmloader currently load OVMF, just below 4G.

Only the new PVH entry point will do the calculation.

The ResetVector will figure out its current running address by creating
a temporary stack, make a call and calculate the difference between the
build time address and the address at run time.

This patch copies and make the necessary modification to some other asm
files:
- copy of UefiCpuPkg/.../Flat32ToFlat64.asm:
  Allow Transition32FlatTo64Flat to be run from anywhere in memory
- copy of UefiCpuPkg/../SearchForBfvBase.asm:
  Add a extra parameter to indicate where to start the search for the
  boot firmware volume.

Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1689
Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>
Acked-by: Laszlo Ersek <lersek@redhat.com>
---

Notes:
    v3:
    - rebased, SPDX
    - fix commit message

 .../XenResetVector/Ia16/Real16ToFlat32.asm    |  3 +
 .../XenResetVector/Ia32/Flat32ToFlat64.asm    | 68 +++++++++++++++
 .../XenResetVector/Ia32/SearchForBfvBase.asm  | 87 +++++++++++++++++++
 OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm    | 43 +++++++--
 4 files changed, 194 insertions(+), 7 deletions(-)
 create mode 100644 OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm
 create mode 100644 OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm

diff --git a/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm b/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm
index 5c329bfaea..36ea74f7fe 100644
--- a/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm
+++ b/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm
@@ -54,6 +54,9 @@ jumpTo32BitAndLandHere:
     mov     gs, ax
     mov     ss, ax
 
+    ; parameter for Flat32SearchForBfvBase
+    xor     eax, eax ; Start searching from top of 4GB for BfvBase
+
     OneTimeCallRet TransitionFromReal16To32BitFlat
 
 ALIGN   2
diff --git a/OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm b/OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm
new file mode 100644
index 0000000000..661a8e7028
--- /dev/null
+++ b/OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm
@@ -0,0 +1,68 @@
+;------------------------------------------------------------------------------
+; @file
+; Transition from 32 bit flat protected mode into 64 bit flat protected mode
+;
+; Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved.<BR>
+; Copyright (c) 2019, Citrix Systems, Inc.
+;
+; SPDX-License-Identifier: BSD-2-Clause-Patent
+;
+;------------------------------------------------------------------------------
+
+BITS    32
+
+;
+; Modified:  EAX, EBX, ECX, EDX, ESP
+;
+Transition32FlatTo64Flat:
+
+    OneTimeCall SetCr3ForPageTables64
+
+    mov     eax, cr4
+    bts     eax, 5                      ; enable PAE
+    mov     cr4, eax
+
+    mov     ecx, 0xc0000080
+    rdmsr
+    bts     eax, 8                      ; set LME
+    wrmsr
+
+    mov     eax, cr0
+    bts     eax, 31                     ; set PG
+    mov     cr0, eax                    ; enable paging
+
+    ;
+    ; backup ESP
+    ;
+    mov     ebx, esp
+
+    ;
+    ; recalculate delta
+    ;
+    mov     esp, PVH_SPACE(16)
+    call    .delta
+.delta:
+    pop     edx
+    sub     edx, ADDR_OF(.delta)
+
+    ;
+    ; push return addr and seg to the stack, then return far
+    ;
+    push    dword LINEAR_CODE64_SEL
+    mov     eax, ADDR_OF(jumpTo64BitAndLandHere)
+    add     eax, edx                             ; add delta
+    push    eax
+    retf
+
+BITS    64
+jumpTo64BitAndLandHere:
+
+    ;
+    ; restore ESP
+    ;
+    mov     esp, ebx
+
+    debugShowPostCode POSTCODE_64BIT_MODE
+
+    OneTimeCallRet Transition32FlatTo64Flat
+
diff --git a/OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm b/OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm
new file mode 100644
index 0000000000..190389c46f
--- /dev/null
+++ b/OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm
@@ -0,0 +1,87 @@
+;------------------------------------------------------------------------------
+; @file
+; Search for the Boot Firmware Volume (BFV) base address
+;
+; Copyright (c) 2008 - 2009, Intel Corporation. All rights reserved.<BR>
+; Copyright (c) 2019, Citrix Systems, Inc.
+;
+; SPDX-License-Identifier: BSD-2-Clause-Patent
+;
+;------------------------------------------------------------------------------
+
+;#define EFI_FIRMWARE_FILE_SYSTEM2_GUID \
+;  { 0x8c8ce578, 0x8a3d, 0x4f1c, { 0x99, 0x35, 0x89, 0x61, 0x85, 0xc3, 0x2d, 0xd3 } }
+%define FFS_GUID_DWORD0 0x8c8ce578
+%define FFS_GUID_DWORD1 0x4f1c8a3d
+%define FFS_GUID_DWORD2 0x61893599
+%define FFS_GUID_DWORD3 0xd32dc385
+
+BITS    32
+
+;
+; Modified:  EAX, EBX, ECX
+; Preserved: EDI, ESP
+;
+; @param[in]   EAX  Start search from here
+; @param[out]  EBP  Address of Boot Firmware Volume (BFV)
+;
+Flat32SearchForBfvBase:
+
+    mov     ecx, eax
+searchingForBfvHeaderLoop:
+    ;
+    ; We check for a firmware volume at every 4KB address in the 16MB
+    ; just below where we started, ECX.
+    ;
+    sub     eax, 0x1000
+    mov     ebx, ecx
+    sub     ebx, eax
+    cmp     ebx, 0x01000000
+    ; if ECX-EAX > 16MB; jump notfound
+    ja      searchedForBfvHeaderButNotFound
+
+    ;
+    ; Check FFS GUID
+    ;
+    cmp     dword [eax + 0x10], FFS_GUID_DWORD0
+    jne     searchingForBfvHeaderLoop
+    cmp     dword [eax + 0x14], FFS_GUID_DWORD1
+    jne     searchingForBfvHeaderLoop
+    cmp     dword [eax + 0x18], FFS_GUID_DWORD2
+    jne     searchingForBfvHeaderLoop
+    cmp     dword [eax + 0x1c], FFS_GUID_DWORD3
+    jne     searchingForBfvHeaderLoop
+
+    ;
+    ; Check FV Length
+    ;
+    cmp     dword [eax + 0x24], 0
+    jne     searchingForBfvHeaderLoop
+    mov     ebx, eax
+    add     ebx, dword [eax + 0x20]
+    cmp     ebx, ecx
+    jnz     searchingForBfvHeaderLoop
+
+    jmp     searchedForBfvHeaderAndItWasFound
+
+searchedForBfvHeaderButNotFound:
+    ;
+    ; Hang if the SEC entry point was not found
+    ;
+    debugShowPostCode POSTCODE_BFV_NOT_FOUND
+
+    ;
+    ; 0xbfbfbfbf in the EAX & EBP registers helps signal what failed
+    ; for debugging purposes.
+    ;
+    mov     eax, 0xBFBFBFBF
+    mov     ebp, eax
+    jmp     $
+
+searchedForBfvHeaderAndItWasFound:
+    mov     ebp, eax
+
+    debugShowPostCode POSTCODE_BFV_FOUND
+
+    OneTimeCallRet Flat32SearchForBfvBase
+
diff --git a/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm b/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm
index f42df3dba2..2df0f12e18 100644
--- a/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm
+++ b/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm
@@ -16,25 +16,42 @@ xenPVHMain:
     ;
     mov     di, 'BP'
 
-    ;
-    ; ESP will be used as initial value of the EAX register
-    ; in Main.asm
-    ;
-    xor     esp, esp
-
     ;
     ; Store "Start of day" struct pointer for later use
     ;
     mov     dword[PVH_SPACE (0)], ebx
     mov     dword[PVH_SPACE (4)], 'XPVH'
 
+    ;
+    ; calculate delta between build-addr and run position
+    ;
+    mov     esp, PVH_SPACE(16)          ; create a temporary stack
+    call    .delta
+.delta:
+    pop     edx                         ; get addr of .delta
+    sub     edx, ADDR_OF(.delta)        ; calculate delta
+
+    ;
+    ; Find address of GDT and gdtr and fix the later
+    ;
     mov     ebx, ADDR_OF(gdtr)
+    add     ebx, edx                    ; add delta gdtr
+    mov     eax, ADDR_OF(GDT_BASE)
+    add     eax, edx                    ; add delta to GDT_BASE
+    mov     dword[ebx + 2], eax         ; fix GDT_BASE addr in gdtr
     lgdt    [ebx]
 
     mov     eax, SEC_DEFAULT_CR0
     mov     cr0, eax
 
-    jmp     LINEAR_CODE_SEL:ADDR_OF(.jmpToNewCodeSeg)
+    ;
+    ; push return addr to the stack, then return far
+    ;
+    push    dword LINEAR_CODE_SEL          ; segment to select
+    mov     eax, ADDR_OF(.jmpToNewCodeSeg) ; return addr
+    add     eax, edx                       ; add delta to return addr
+    push    eax
+    retf
 .jmpToNewCodeSeg:
 
     mov     eax, SEC_DEFAULT_CR4
@@ -47,6 +64,18 @@ xenPVHMain:
     mov     gs, ax
     mov     ss, ax
 
+    ;
+    ; ESP will be used as initial value of the EAX register
+    ; in Main.asm
+    ;
+    xor     esp, esp
+
+    ;
+    ; parameter for Flat32SearchForBfvBase
+    ;
+    mov     eax, ADDR_OF(fourGigabytes)
+    add     eax, edx ; add delta
+
     ;
     ; Jump to the main routine of the pre-SEC code
     ; skiping the 16-bit part of the routine and
-- 
Anthony PERARD


-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.

View/Reply Online (#45532): https://edk2.groups.io/g/devel/message/45532
Mute This Topic: https://groups.io/mt/32851503/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub  [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-

Re: [edk2-devel] [PATCH v5 08/35] OvmfPkg/XenResetVector: Allow jumpstart from either hvmloader or PVH
Posted by Laszlo Ersek 5 years, 2 months ago
Hi Anthony,

On 08/13/19 13:30, Anthony PERARD wrote:
> This patch allows the ResetVector to be run indenpendently from build
> time addresses.
> 
> The goal of the patch is to avoid having to create RAM just below 4G
> when creating a Xen PVH guest while being compatible with the way
> hvmloader currently load OVMF, just below 4G.
> 
> Only the new PVH entry point will do the calculation.
> 
> The ResetVector will figure out its current running address by creating
> a temporary stack, make a call and calculate the difference between the
> build time address and the address at run time.
> 
> This patch copies and make the necessary modification to some other asm
> files:
> - copy of UefiCpuPkg/.../Flat32ToFlat64.asm:
>   Allow Transition32FlatTo64Flat to be run from anywhere in memory
> - copy of UefiCpuPkg/../SearchForBfvBase.asm:
>   Add a extra parameter to indicate where to start the search for the
>   boot firmware volume.
> 
> Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1689
> Signed-off-by: Anthony PERARD <anthony.perard@citrix.com>
> Acked-by: Laszlo Ersek <lersek@redhat.com>
> ---
> 
> Notes:
>     v3:
>     - rebased, SPDX
>     - fix commit message
> 
>  .../XenResetVector/Ia16/Real16ToFlat32.asm    |  3 +
>  .../XenResetVector/Ia32/Flat32ToFlat64.asm    | 68 +++++++++++++++
>  .../XenResetVector/Ia32/SearchForBfvBase.asm  | 87 +++++++++++++++++++
>  OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm    | 43 +++++++--
>  4 files changed, 194 insertions(+), 7 deletions(-)
>  create mode 100644 OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm
>  create mode 100644 OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm
> 
> diff --git a/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm b/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm
> index 5c329bfaea..36ea74f7fe 100644
> --- a/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm
> +++ b/OvmfPkg/XenResetVector/Ia16/Real16ToFlat32.asm
> @@ -54,6 +54,9 @@ jumpTo32BitAndLandHere:
>      mov     gs, ax
>      mov     ss, ax
>  
> +    ; parameter for Flat32SearchForBfvBase
> +    xor     eax, eax ; Start searching from top of 4GB for BfvBase
> +
>      OneTimeCallRet TransitionFromReal16To32BitFlat
>  
>  ALIGN   2
> diff --git a/OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm b/OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm
> new file mode 100644
> index 0000000000..661a8e7028
> --- /dev/null
> +++ b/OvmfPkg/XenResetVector/Ia32/Flat32ToFlat64.asm
> @@ -0,0 +1,68 @@
> +;------------------------------------------------------------------------------
> +; @file
> +; Transition from 32 bit flat protected mode into 64 bit flat protected mode
> +;
> +; Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved.<BR>
> +; Copyright (c) 2019, Citrix Systems, Inc.
> +;
> +; SPDX-License-Identifier: BSD-2-Clause-Patent
> +;
> +;------------------------------------------------------------------------------
> +
> +BITS    32
> +
> +;
> +; Modified:  EAX, EBX, ECX, EDX, ESP
> +;
> +Transition32FlatTo64Flat:
> +
> +    OneTimeCall SetCr3ForPageTables64
> +
> +    mov     eax, cr4
> +    bts     eax, 5                      ; enable PAE
> +    mov     cr4, eax
> +
> +    mov     ecx, 0xc0000080
> +    rdmsr
> +    bts     eax, 8                      ; set LME
> +    wrmsr
> +
> +    mov     eax, cr0
> +    bts     eax, 31                     ; set PG
> +    mov     cr0, eax                    ; enable paging
> +
> +    ;
> +    ; backup ESP
> +    ;
> +    mov     ebx, esp
> +
> +    ;
> +    ; recalculate delta
> +    ;
> +    mov     esp, PVH_SPACE(16)
> +    call    .delta
> +.delta:
> +    pop     edx
> +    sub     edx, ADDR_OF(.delta)
> +
> +    ;
> +    ; push return addr and seg to the stack, then return far
> +    ;
> +    push    dword LINEAR_CODE64_SEL
> +    mov     eax, ADDR_OF(jumpTo64BitAndLandHere)
> +    add     eax, edx                             ; add delta
> +    push    eax
> +    retf
> +
> +BITS    64
> +jumpTo64BitAndLandHere:
> +
> +    ;
> +    ; restore ESP
> +    ;
> +    mov     esp, ebx
> +
> +    debugShowPostCode POSTCODE_64BIT_MODE
> +
> +    OneTimeCallRet Transition32FlatTo64Flat
> +
> diff --git a/OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm b/OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm
> new file mode 100644
> index 0000000000..190389c46f
> --- /dev/null
> +++ b/OvmfPkg/XenResetVector/Ia32/SearchForBfvBase.asm
> @@ -0,0 +1,87 @@
> +;------------------------------------------------------------------------------
> +; @file
> +; Search for the Boot Firmware Volume (BFV) base address
> +;
> +; Copyright (c) 2008 - 2009, Intel Corporation. All rights reserved.<BR>
> +; Copyright (c) 2019, Citrix Systems, Inc.
> +;
> +; SPDX-License-Identifier: BSD-2-Clause-Patent
> +;
> +;------------------------------------------------------------------------------
> +
> +;#define EFI_FIRMWARE_FILE_SYSTEM2_GUID \
> +;  { 0x8c8ce578, 0x8a3d, 0x4f1c, { 0x99, 0x35, 0x89, 0x61, 0x85, 0xc3, 0x2d, 0xd3 } }
> +%define FFS_GUID_DWORD0 0x8c8ce578
> +%define FFS_GUID_DWORD1 0x4f1c8a3d
> +%define FFS_GUID_DWORD2 0x61893599
> +%define FFS_GUID_DWORD3 0xd32dc385
> +
> +BITS    32
> +
> +;
> +; Modified:  EAX, EBX, ECX
> +; Preserved: EDI, ESP
> +;
> +; @param[in]   EAX  Start search from here
> +; @param[out]  EBP  Address of Boot Firmware Volume (BFV)
> +;
> +Flat32SearchForBfvBase:
> +
> +    mov     ecx, eax
> +searchingForBfvHeaderLoop:
> +    ;
> +    ; We check for a firmware volume at every 4KB address in the 16MB
> +    ; just below where we started, ECX.
> +    ;
> +    sub     eax, 0x1000
> +    mov     ebx, ecx
> +    sub     ebx, eax
> +    cmp     ebx, 0x01000000
> +    ; if ECX-EAX > 16MB; jump notfound
> +    ja      searchedForBfvHeaderButNotFound
> +
> +    ;
> +    ; Check FFS GUID
> +    ;
> +    cmp     dword [eax + 0x10], FFS_GUID_DWORD0
> +    jne     searchingForBfvHeaderLoop
> +    cmp     dword [eax + 0x14], FFS_GUID_DWORD1
> +    jne     searchingForBfvHeaderLoop
> +    cmp     dword [eax + 0x18], FFS_GUID_DWORD2
> +    jne     searchingForBfvHeaderLoop
> +    cmp     dword [eax + 0x1c], FFS_GUID_DWORD3
> +    jne     searchingForBfvHeaderLoop
> +
> +    ;
> +    ; Check FV Length
> +    ;
> +    cmp     dword [eax + 0x24], 0
> +    jne     searchingForBfvHeaderLoop
> +    mov     ebx, eax
> +    add     ebx, dword [eax + 0x20]
> +    cmp     ebx, ecx
> +    jnz     searchingForBfvHeaderLoop
> +
> +    jmp     searchedForBfvHeaderAndItWasFound
> +
> +searchedForBfvHeaderButNotFound:
> +    ;
> +    ; Hang if the SEC entry point was not found
> +    ;
> +    debugShowPostCode POSTCODE_BFV_NOT_FOUND
> +
> +    ;
> +    ; 0xbfbfbfbf in the EAX & EBP registers helps signal what failed
> +    ; for debugging purposes.
> +    ;
> +    mov     eax, 0xBFBFBFBF
> +    mov     ebp, eax
> +    jmp     $
> +
> +searchedForBfvHeaderAndItWasFound:
> +    mov     ebp, eax
> +
> +    debugShowPostCode POSTCODE_BFV_FOUND
> +
> +    OneTimeCallRet Flat32SearchForBfvBase
> +
> diff --git a/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm b/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm
> index f42df3dba2..2df0f12e18 100644
> --- a/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm
> +++ b/OvmfPkg/XenResetVector/Ia32/XenPVHMain.asm
> @@ -16,25 +16,42 @@ xenPVHMain:
>      ;
>      mov     di, 'BP'
>  
> -    ;
> -    ; ESP will be used as initial value of the EAX register
> -    ; in Main.asm
> -    ;
> -    xor     esp, esp
> -
>      ;
>      ; Store "Start of day" struct pointer for later use
>      ;
>      mov     dword[PVH_SPACE (0)], ebx
>      mov     dword[PVH_SPACE (4)], 'XPVH'
>  
> +    ;
> +    ; calculate delta between build-addr and run position
> +    ;
> +    mov     esp, PVH_SPACE(16)          ; create a temporary stack
> +    call    .delta
> +.delta:
> +    pop     edx                         ; get addr of .delta
> +    sub     edx, ADDR_OF(.delta)        ; calculate delta
> +
> +    ;
> +    ; Find address of GDT and gdtr and fix the later
> +    ;
>      mov     ebx, ADDR_OF(gdtr)
> +    add     ebx, edx                    ; add delta gdtr
> +    mov     eax, ADDR_OF(GDT_BASE)
> +    add     eax, edx                    ; add delta to GDT_BASE
> +    mov     dword[ebx + 2], eax         ; fix GDT_BASE addr in gdtr
>      lgdt    [ebx]
>  
>      mov     eax, SEC_DEFAULT_CR0
>      mov     cr0, eax
>  
> -    jmp     LINEAR_CODE_SEL:ADDR_OF(.jmpToNewCodeSeg)
> +    ;
> +    ; push return addr to the stack, then return far
> +    ;
> +    push    dword LINEAR_CODE_SEL          ; segment to select
> +    mov     eax, ADDR_OF(.jmpToNewCodeSeg) ; return addr
> +    add     eax, edx                       ; add delta to return addr
> +    push    eax
> +    retf
>  .jmpToNewCodeSeg:
>  
>      mov     eax, SEC_DEFAULT_CR4
> @@ -47,6 +64,18 @@ xenPVHMain:
>      mov     gs, ax
>      mov     ss, ax
>  
> +    ;
> +    ; ESP will be used as initial value of the EAX register
> +    ; in Main.asm
> +    ;
> +    xor     esp, esp
> +
> +    ;
> +    ; parameter for Flat32SearchForBfvBase
> +    ;
> +    mov     eax, ADDR_OF(fourGigabytes)

I've just noticed that the line above triggers the following NASM warning:

Ia32/XenPVHMain.asm:76: warning: dword data exceeds bounds

The warning does not stop the build.

The macro comes from "UefiCpuPkg/ResetVector/Vtf0/CommonMacros.inc":

%define ADDR_OF(x) (0x100000000 - fourGigabytes + x)

So I believe we effectively store 0 to EAX. If that's the intent (I
think it *could* be), please submit a cleanup in the next development
cycle -- we could store a 0 explicitly and add a comment, just to shut
up the warning. If, on the other hand, this is a bug, please submit a
fix soon (for the upcoming release).

Thanks
Laszlo


> +    add     eax, edx ; add delta
> +
>      ;
>      ; Jump to the main routine of the pre-SEC code
>      ; skiping the 16-bit part of the routine and
> 


-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.

View/Reply Online (#46167): https://edk2.groups.io/g/devel/message/46167
Mute This Topic: https://groups.io/mt/32851503/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub  [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-