diff options
-rw-r--r-- | arch/ia64/Kconfig | 13 | ||||
-rw-r--r-- | arch/ia64/configs/sn2_defconfig | 2 | ||||
-rw-r--r-- | arch/ia64/defconfig | 2 | ||||
-rw-r--r-- | arch/ia64/kernel/ivt.S | 63 | ||||
-rw-r--r-- | arch/ia64/sn/kernel/setup.c | 157 | ||||
-rw-r--r-- | include/asm-ia64/page.h | 8 | ||||
-rw-r--r-- | include/asm-ia64/pgalloc.h | 19 | ||||
-rw-r--r-- | include/asm-ia64/pgtable.h | 76 |
8 files changed, 303 insertions, 37 deletions
diff --git a/arch/ia64/Kconfig b/arch/ia64/Kconfig index d4de8a4814be..8796e12c56f3 100644 --- a/arch/ia64/Kconfig +++ b/arch/ia64/Kconfig @@ -164,6 +164,19 @@ config IA64_PAGE_SIZE_64KB endchoice +choice + prompt "Page Table Levels" + default PGTABLE_3 + +config PGTABLE_3 + bool "3 Levels" + +config PGTABLE_4 + depends on !IA64_PAGE_SIZE_64KB + bool "4 Levels" + +endchoice + source kernel/Kconfig.hz config IA64_BRL_EMU diff --git a/arch/ia64/configs/sn2_defconfig b/arch/ia64/configs/sn2_defconfig index 08112ab38468..87cfd31a4a39 100644 --- a/arch/ia64/configs/sn2_defconfig +++ b/arch/ia64/configs/sn2_defconfig @@ -80,6 +80,8 @@ CONFIG_MCKINLEY=y # CONFIG_IA64_PAGE_SIZE_8KB is not set CONFIG_IA64_PAGE_SIZE_16KB=y # CONFIG_IA64_PAGE_SIZE_64KB is not set +# CONFIG_PGTABLE_3 is not set +CONFIG_PGTABLE_4=y # CONFIG_HZ_100 is not set CONFIG_HZ_250=y # CONFIG_HZ_1000 is not set diff --git a/arch/ia64/defconfig b/arch/ia64/defconfig index 6e3f147e03e5..275a26c6e5aa 100644 --- a/arch/ia64/defconfig +++ b/arch/ia64/defconfig @@ -82,6 +82,8 @@ CONFIG_MCKINLEY=y # CONFIG_IA64_PAGE_SIZE_8KB is not set CONFIG_IA64_PAGE_SIZE_16KB=y # CONFIG_IA64_PAGE_SIZE_64KB is not set +CONFIG_PGTABLE_3=y +# CONFIG_PGTABLE_4 is not set # CONFIG_HZ_100 is not set CONFIG_HZ_250=y # CONFIG_HZ_1000 is not set diff --git a/arch/ia64/kernel/ivt.S b/arch/ia64/kernel/ivt.S index c13ca0d49c4a..e06f21f60dc5 100644 --- a/arch/ia64/kernel/ivt.S +++ b/arch/ia64/kernel/ivt.S @@ -114,7 +114,7 @@ ENTRY(vhpt_miss) shl r21=r16,3 // shift bit 60 into sign bit shr.u r17=r16,61 // get the region number into r17 ;; - shr r22=r21,3 + shr.u r22=r21,3 #ifdef CONFIG_HUGETLB_PAGE extr.u r26=r25,2,6 ;; @@ -140,20 +140,34 @@ ENTRY(vhpt_miss) (p6) dep r17=r18,r19,3,(PAGE_SHIFT-3) // r17=PTA + IFA(33,42)*8 (p7) dep r17=r18,r17,3,(PAGE_SHIFT-6) // r17=PTA + (((IFA(61,63) << 7) | IFA(33,39))*8) cmp.eq p7,p6=0,r21 // unused address bits all zeroes? - shr.u r18=r22,PMD_SHIFT // shift L2 index into position +#ifdef CONFIG_PGTABLE_4 + shr.u r28=r22,PUD_SHIFT // shift L2 index into position +#else + shr.u r18=r22,PMD_SHIFT // shift L3 index into position +#endif ;; ld8 r17=[r17] // fetch the L1 entry (may be 0) ;; (p7) cmp.eq p6,p7=r17,r0 // was L1 entry NULL? - dep r17=r18,r17,3,(PAGE_SHIFT-3) // compute address of L2 page table entry +#ifdef CONFIG_PGTABLE_4 + dep r28=r28,r17,3,(PAGE_SHIFT-3) // compute address of L2 page table entry + ;; + shr.u r18=r22,PMD_SHIFT // shift L3 index into position +(p7) ld8 r29=[r28] // fetch the L2 entry (may be 0) ;; -(p7) ld8 r20=[r17] // fetch the L2 entry (may be 0) - shr.u r19=r22,PAGE_SHIFT // shift L3 index into position +(p7) cmp.eq.or.andcm p6,p7=r29,r0 // was L2 entry NULL? + dep r17=r18,r29,3,(PAGE_SHIFT-3) // compute address of L3 page table entry +#else + dep r17=r18,r17,3,(PAGE_SHIFT-3) // compute address of L3 page table entry +#endif ;; -(p7) cmp.eq.or.andcm p6,p7=r20,r0 // was L2 entry NULL? - dep r21=r19,r20,3,(PAGE_SHIFT-3) // compute address of L3 page table entry +(p7) ld8 r20=[r17] // fetch the L3 entry (may be 0) + shr.u r19=r22,PAGE_SHIFT // shift L4 index into position ;; -(p7) ld8 r18=[r21] // read the L3 PTE +(p7) cmp.eq.or.andcm p6,p7=r20,r0 // was L3 entry NULL? + dep r21=r19,r20,3,(PAGE_SHIFT-3) // compute address of L4 page table entry + ;; +(p7) ld8 r18=[r21] // read the L4 PTE mov r19=cr.isr // cr.isr bit 0 tells us if this is an insn miss ;; (p7) tbit.z p6,p7=r18,_PAGE_P_BIT // page present bit cleared? @@ -192,14 +206,21 @@ ENTRY(vhpt_miss) * between reading the pagetable and the "itc". If so, flush the entry we * inserted and retry. */ - ld8 r25=[r21] // read L3 PTE again - ld8 r26=[r17] // read L2 entry again + ld8 r25=[r21] // read L4 entry again + ld8 r26=[r17] // read L3 PTE again +#ifdef CONFIG_PGTABLE_4 + ld8 r18=[r28] // read L2 entry again +#endif + cmp.ne p6,p7=r0,r0 ;; - cmp.ne p6,p7=r26,r20 // did L2 entry change + cmp.ne.or.andcm p6,p7=r26,r20 // did L3 entry change +#ifdef CONFIG_PGTABLE_4 + cmp.ne.or.andcm p6,p7=r29,r18 // did L4 PTE change +#endif mov r27=PAGE_SHIFT<<2 ;; (p6) ptc.l r22,r27 // purge PTE page translation -(p7) cmp.ne.or.andcm p6,p7=r25,r18 // did L3 PTE change +(p7) cmp.ne.or.andcm p6,p7=r25,r18 // did L4 PTE change ;; (p6) ptc.l r16,r27 // purge translation #endif @@ -432,18 +453,30 @@ ENTRY(nested_dtlb_miss) (p6) dep r17=r18,r19,3,(PAGE_SHIFT-3) // r17=PTA + IFA(33,42)*8 (p7) dep r17=r18,r17,3,(PAGE_SHIFT-6) // r17=PTA + (((IFA(61,63) << 7) | IFA(33,39))*8) cmp.eq p7,p6=0,r21 // unused address bits all zeroes? - shr.u r18=r22,PMD_SHIFT // shift L2 index into position +#ifdef CONFIG_PGTABLE_4 + shr.u r18=r22,PUD_SHIFT // shift L2 index into position +#else + shr.u r18=r22,PMD_SHIFT // shift L3 index into position +#endif ;; ld8 r17=[r17] // fetch the L1 entry (may be 0) ;; (p7) cmp.eq p6,p7=r17,r0 // was L1 entry NULL? dep r17=r18,r17,3,(PAGE_SHIFT-3) // compute address of L2 page table entry ;; +#ifdef CONFIG_PGTABLE_4 (p7) ld8 r17=[r17] // fetch the L2 entry (may be 0) - shr.u r19=r22,PAGE_SHIFT // shift L3 index into position + shr.u r18=r22,PMD_SHIFT // shift L3 index into position ;; (p7) cmp.eq.or.andcm p6,p7=r17,r0 // was L2 entry NULL? - dep r17=r19,r17,3,(PAGE_SHIFT-3) // compute address of L3 page table entry + dep r17=r18,r17,3,(PAGE_SHIFT-3) // compute address of L2 page table entry + ;; +#endif +(p7) ld8 r17=[r17] // fetch the L3 entry (may be 0) + shr.u r19=r22,PAGE_SHIFT // shift L4 index into position + ;; +(p7) cmp.eq.or.andcm p6,p7=r17,r0 // was L3 entry NULL? + dep r17=r19,r17,3,(PAGE_SHIFT-3) // compute address of L4 page table entry (p6) br.cond.spnt page_fault mov b0=r30 br.sptk.many b0 // return to continuation point diff --git a/arch/ia64/sn/kernel/setup.c b/arch/ia64/sn/kernel/setup.c index 0fb579ef18c2..e510dce9971f 100644 --- a/arch/ia64/sn/kernel/setup.c +++ b/arch/ia64/sn/kernel/setup.c @@ -30,6 +30,7 @@ #include <linux/root_dev.h> #include <linux/nodemask.h> #include <linux/pm.h> +#include <linux/efi.h> #include <asm/io.h> #include <asm/sal.h> @@ -242,6 +243,135 @@ static void __init sn_check_for_wars(void) } } +/* + * Scan the EFI PCDP table (if it exists) for an acceptable VGA console + * output device. If one exists, pick it and set sn_legacy_{io,mem} to + * reflect the bus offsets needed to address it. + * + * Since pcdp support in SN is not supported in the 2.4 kernel (or at least + * the one lbs is based on) just declare the needed structs here. + * + * Reference spec http://www.dig64.org/specifications/DIG64_PCDPv20.pdf + * + * Returns 0 if no acceptable vga is found, !0 otherwise. + * + * Note: This stuff is duped here because Altix requires the PCDP to + * locate a usable VGA device due to lack of proper ACPI support. Structures + * could be used from drivers/firmware/pcdp.h, but it was decided that moving + * this file to a more public location just for Altix use was undesireable. + */ + +struct hcdp_uart_desc { + u8 pad[45]; +}; + +struct pcdp { + u8 signature[4]; /* should be 'HCDP' */ + u32 length; + u8 rev; /* should be >=3 for pcdp, <3 for hcdp */ + u8 sum; + u8 oem_id[6]; + u64 oem_tableid; + u32 oem_rev; + u32 creator_id; + u32 creator_rev; + u32 num_type0; + struct hcdp_uart_desc uart[0]; /* num_type0 of these */ + /* pcdp descriptors follow */ +} __attribute__((packed)); + +struct pcdp_device_desc { + u8 type; + u8 primary; + u16 length; + u16 index; + /* interconnect specific structure follows */ + /* device specific structure follows that */ +} __attribute__((packed)); + +struct pcdp_interface_pci { + u8 type; /* 1 == pci */ + u8 reserved; + u16 length; + u8 segment; + u8 bus; + u8 dev; + u8 fun; + u16 devid; + u16 vendid; + u32 acpi_interrupt; + u64 mmio_tra; + u64 ioport_tra; + u8 flags; + u8 translation; +} __attribute__((packed)); + +struct pcdp_vga_device { + u8 num_eas_desc; + /* ACPI Extended Address Space Desc follows */ +} __attribute__((packed)); + +/* from pcdp_device_desc.primary */ +#define PCDP_PRIMARY_CONSOLE 0x01 + +/* from pcdp_device_desc.type */ +#define PCDP_CONSOLE_INOUT 0x0 +#define PCDP_CONSOLE_DEBUG 0x1 +#define PCDP_CONSOLE_OUT 0x2 +#define PCDP_CONSOLE_IN 0x3 +#define PCDP_CONSOLE_TYPE_VGA 0x8 + +#define PCDP_CONSOLE_VGA (PCDP_CONSOLE_TYPE_VGA | PCDP_CONSOLE_OUT) + +/* from pcdp_interface_pci.type */ +#define PCDP_IF_PCI 1 + +/* from pcdp_interface_pci.translation */ +#define PCDP_PCI_TRANS_IOPORT 0x02 +#define PCDP_PCI_TRANS_MMIO 0x01 + +static void +sn_scan_pcdp(void) +{ + u8 *bp; + struct pcdp *pcdp; + struct pcdp_device_desc device; + struct pcdp_interface_pci if_pci; + extern struct efi efi; + + pcdp = efi.hcdp; + if (! pcdp) + return; /* no hcdp/pcdp table */ + + if (pcdp->rev < 3) + return; /* only support PCDP (rev >= 3) */ + + for (bp = (u8 *)&pcdp->uart[pcdp->num_type0]; + bp < (u8 *)pcdp + pcdp->length; + bp += device.length) { + memcpy(&device, bp, sizeof(device)); + if (! (device.primary & PCDP_PRIMARY_CONSOLE)) + continue; /* not primary console */ + + if (device.type != PCDP_CONSOLE_VGA) + continue; /* not VGA descriptor */ + + memcpy(&if_pci, bp+sizeof(device), sizeof(if_pci)); + if (if_pci.type != PCDP_IF_PCI) + continue; /* not PCI interconnect */ + + if (if_pci.translation & PCDP_PCI_TRANS_IOPORT) + vga_console_iobase = + if_pci.ioport_tra | __IA64_UNCACHED_OFFSET; + + if (if_pci.translation & PCDP_PCI_TRANS_MMIO) + vga_console_membase = + if_pci.mmio_tra | __IA64_UNCACHED_OFFSET; + + break; /* once we find the primary, we're done */ + } +} + /** * sn_setup - SN platform setup routine * @cmdline_p: kernel command line @@ -263,16 +393,35 @@ void __init sn_setup(char **cmdline_p) #if defined(CONFIG_VT) && defined(CONFIG_VGA_CONSOLE) /* - * If there was a primary vga adapter identified through the - * EFI PCDP table, make it the preferred console. Otherwise - * zero out conswitchp. + * Handle SN vga console. + * + * SN systems do not have enough ACPI table information + * being passed from prom to identify VGA adapters and the legacy + * addresses to access them. Until that is done, SN systems rely + * on the PCDP table to identify the primary VGA console if one + * exists. + * + * However, kernel PCDP support is optional, and even if it is built + * into the kernel, it will not be used if the boot cmdline contains + * console= directives. + * + * So, to work around this mess, we duplicate some of the PCDP code + * here so that the primary VGA console (as defined by PCDP) will + * work on SN systems even if a different console (e.g. serial) is + * selected on the boot line (or CONFIG_EFI_PCDP is off). */ + if (! vga_console_membase) + sn_scan_pcdp(); + if (vga_console_membase) { /* usable vga ... make tty0 the preferred default console */ - add_preferred_console("tty", 0, NULL); + if (!strstr(*cmdline_p, "console=")) + add_preferred_console("tty", 0, NULL); } else { printk(KERN_DEBUG "SGI: Disabling VGA console\n"); + if (!strstr(*cmdline_p, "console=")) + add_preferred_console("ttySG", 0, NULL); #ifdef CONFIG_DUMMY_CONSOLE conswitchp = &dummy_con; #else diff --git a/include/asm-ia64/page.h b/include/asm-ia64/page.h index 9d41548b7fef..9dd9da105278 100644 --- a/include/asm-ia64/page.h +++ b/include/asm-ia64/page.h @@ -47,8 +47,6 @@ #define PERCPU_PAGE_SHIFT 16 /* log2() of max. size of per-CPU area */ #define PERCPU_PAGE_SIZE (__IA64_UL_CONST(1) << PERCPU_PAGE_SHIFT) -#define RGN_MAP_LIMIT ((1UL << (4*PAGE_SHIFT - 12)) - PAGE_SIZE) /* per region addr limit */ - #ifdef CONFIG_HUGETLB_PAGE # define HPAGE_REGION_BASE RGN_BASE(RGN_HPAGE) @@ -175,11 +173,17 @@ get_order (unsigned long size) */ typedef struct { unsigned long pte; } pte_t; typedef struct { unsigned long pmd; } pmd_t; +#ifdef CONFIG_PGTABLE_4 + typedef struct { unsigned long pud; } pud_t; +#endif typedef struct { unsigned long pgd; } pgd_t; typedef struct { unsigned long pgprot; } pgprot_t; # define pte_val(x) ((x).pte) # define pmd_val(x) ((x).pmd) +#ifdef CONFIG_PGTABLE_4 +# define pud_val(x) ((x).pud) +#endif # define pgd_val(x) ((x).pgd) # define pgprot_val(x) ((x).pgprot) diff --git a/include/asm-ia64/pgalloc.h b/include/asm-ia64/pgalloc.h index a5f214554afd..f2f233846476 100644 --- a/include/asm-ia64/pgalloc.h +++ b/include/asm-ia64/pgalloc.h @@ -86,6 +86,25 @@ static inline void pgd_free(pgd_t * pgd) pgtable_quicklist_free(pgd); } +#ifdef CONFIG_PGTABLE_4 +static inline void +pgd_populate(struct mm_struct *mm, pgd_t * pgd_entry, pud_t * pud) +{ + pgd_val(*pgd_entry) = __pa(pud); +} + +static inline pud_t *pud_alloc_one(struct mm_struct *mm, unsigned long addr) +{ + return pgtable_quicklist_alloc(); +} + +static inline void pud_free(pud_t * pud) +{ + pgtable_quicklist_free(pud); +} +#define __pud_free_tlb(tlb, pud) pud_free(pud) +#endif /* CONFIG_PGTABLE_4 */ + static inline void pud_populate(struct mm_struct *mm, pud_t * pud_entry, pmd_t * pmd) { diff --git a/include/asm-ia64/pgtable.h b/include/asm-ia64/pgtable.h index c34ba80c1c31..e2560c58384b 100644 --- a/include/asm-ia64/pgtable.h +++ b/include/asm-ia64/pgtable.h @@ -84,32 +84,55 @@ #define __DIRTY_BITS _PAGE_ED | __DIRTY_BITS_NO_ED /* - * Definitions for first level: - * - * PGDIR_SHIFT determines what a first-level page table entry can map. + * How many pointers will a page table level hold expressed in shift */ -#define PGDIR_SHIFT (PAGE_SHIFT + 2*(PAGE_SHIFT-3)) -#define PGDIR_SIZE (__IA64_UL(1) << PGDIR_SHIFT) -#define PGDIR_MASK (~(PGDIR_SIZE-1)) -#define PTRS_PER_PGD (1UL << (PAGE_SHIFT-3)) -#define USER_PTRS_PER_PGD (5*PTRS_PER_PGD/8) /* regions 0-4 are user regions */ -#define FIRST_USER_ADDRESS 0 +#define PTRS_PER_PTD_SHIFT (PAGE_SHIFT-3) /* - * Definitions for second level: + * Definitions for fourth level: + */ +#define PTRS_PER_PTE (__IA64_UL(1) << (PTRS_PER_PTD_SHIFT)) + +/* + * Definitions for third level: * - * PMD_SHIFT determines the size of the area a second-level page table + * PMD_SHIFT determines the size of the area a third-level page table * can map. */ -#define PMD_SHIFT (PAGE_SHIFT + (PAGE_SHIFT-3)) +#define PMD_SHIFT (PAGE_SHIFT + (PTRS_PER_PTD_SHIFT)) #define PMD_SIZE (1UL << PMD_SHIFT) #define PMD_MASK (~(PMD_SIZE-1)) -#define PTRS_PER_PMD (1UL << (PAGE_SHIFT-3)) +#define PTRS_PER_PMD (1UL << (PTRS_PER_PTD_SHIFT)) +#ifdef CONFIG_PGTABLE_4 /* - * Definitions for third level: + * Definitions for second level: + * + * PUD_SHIFT determines the size of the area a second-level page table + * can map. */ -#define PTRS_PER_PTE (__IA64_UL(1) << (PAGE_SHIFT-3)) +#define PUD_SHIFT (PMD_SHIFT + (PTRS_PER_PTD_SHIFT)) +#define PUD_SIZE (1UL << PUD_SHIFT) +#define PUD_MASK (~(PUD_SIZE-1)) +#define PTRS_PER_PUD (1UL << (PTRS_PER_PTD_SHIFT)) +#endif + +/* + * Definitions for first level: + * + * PGDIR_SHIFT determines what a first-level page table entry can map. + */ +#ifdef CONFIG_PGTABLE_4 +#define PGDIR_SHIFT (PUD_SHIFT + (PTRS_PER_PTD_SHIFT)) +#else +#define PGDIR_SHIFT (PMD_SHIFT + (PTRS_PER_PTD_SHIFT)) +#endif +#define PGDIR_SIZE (__IA64_UL(1) << PGDIR_SHIFT) +#define PGDIR_MASK (~(PGDIR_SIZE-1)) +#define PTRS_PER_PGD_SHIFT PTRS_PER_PTD_SHIFT +#define PTRS_PER_PGD (1UL << PTRS_PER_PGD_SHIFT) +#define USER_PTRS_PER_PGD (5*PTRS_PER_PGD/8) /* regions 0-4 are user regions */ +#define FIRST_USER_ADDRESS 0 /* * All the normal masks have the "page accessed" bits on, as any time @@ -161,6 +184,9 @@ #define __S111 __pgprot(__ACCESS_BITS | _PAGE_PL_3 | _PAGE_AR_RWX) #define pgd_ERROR(e) printk("%s:%d: bad pgd %016lx.\n", __FILE__, __LINE__, pgd_val(e)) +#ifdef CONFIG_PGTABLE_4 +#define pud_ERROR(e) printk("%s:%d: bad pud %016lx.\n", __FILE__, __LINE__, pud_val(e)) +#endif #define pmd_ERROR(e) printk("%s:%d: bad pmd %016lx.\n", __FILE__, __LINE__, pmd_val(e)) #define pte_ERROR(e) printk("%s:%d: bad pte %016lx.\n", __FILE__, __LINE__, pte_val(e)) @@ -218,6 +244,9 @@ ia64_phys_addr_valid (unsigned long addr) #define kc_vaddr_to_offset(v) ((v) - RGN_BASE(RGN_GATE)) #define kc_offset_to_vaddr(o) ((o) + RGN_BASE(RGN_GATE)) +#define RGN_MAP_SHIFT (PGDIR_SHIFT + PTRS_PER_PGD_SHIFT - 3) +#define RGN_MAP_LIMIT ((1UL << RGN_MAP_SHIFT) - PAGE_SIZE) /* per region addr limit */ + /* * Conversion functions: convert page frame number (pfn) and a protection value to a page * table entry (pte). @@ -254,9 +283,16 @@ ia64_phys_addr_valid (unsigned long addr) #define pud_bad(pud) (!ia64_phys_addr_valid(pud_val(pud))) #define pud_present(pud) (pud_val(pud) != 0UL) #define pud_clear(pudp) (pud_val(*(pudp)) = 0UL) - #define pud_page(pud) ((unsigned long) __va(pud_val(pud) & _PFN_MASK)) +#ifdef CONFIG_PGTABLE_4 +#define pgd_none(pgd) (!pgd_val(pgd)) +#define pgd_bad(pgd) (!ia64_phys_addr_valid(pgd_val(pgd))) +#define pgd_present(pgd) (pgd_val(pgd) != 0UL) +#define pgd_clear(pgdp) (pgd_val(*(pgdp)) = 0UL) +#define pgd_page(pgd) ((unsigned long) __va(pgd_val(pgd) & _PFN_MASK)) +#endif + /* * The following have defined behavior only work if pte_present() is true. */ @@ -324,7 +360,13 @@ pgd_offset (struct mm_struct *mm, unsigned long address) here. */ #define pgd_offset_gate(mm, addr) pgd_offset_k(addr) +#ifdef CONFIG_PGTABLE_4 /* Find an entry in the second-level page table.. */ +#define pud_offset(dir,addr) \ + ((pud_t *) pgd_page(*(dir)) + (((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))) +#endif + +/* Find an entry in the third-level page table.. */ #define pmd_offset(dir,addr) \ ((pmd_t *) pud_page(*(dir)) + (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))) @@ -557,7 +599,9 @@ do { \ #define __HAVE_ARCH_PGD_OFFSET_GATE #define __HAVE_ARCH_LAZY_MMU_PROT_UPDATE +#ifndef CONFIG_PGTABLE_4 #include <asm-generic/pgtable-nopud.h> +#endif #include <asm-generic/pgtable.h> #endif /* _ASM_IA64_PGTABLE_H */ |