diff options
Diffstat (limited to 'arch/mips/dec/prom')
-rw-r--r-- | arch/mips/dec/prom/Makefile | 11 | ||||
-rw-r--r-- | arch/mips/dec/prom/call_o32.S | 91 | ||||
-rw-r--r-- | arch/mips/dec/prom/cmdline.c | 39 | ||||
-rw-r--r-- | arch/mips/dec/prom/console.c | 55 | ||||
-rw-r--r-- | arch/mips/dec/prom/dectypes.h | 14 | ||||
-rw-r--r-- | arch/mips/dec/prom/identify.c | 177 | ||||
-rw-r--r-- | arch/mips/dec/prom/init.c | 134 | ||||
-rw-r--r-- | arch/mips/dec/prom/locore.S | 30 | ||||
-rw-r--r-- | arch/mips/dec/prom/memory.c | 130 |
9 files changed, 681 insertions, 0 deletions
diff --git a/arch/mips/dec/prom/Makefile b/arch/mips/dec/prom/Makefile new file mode 100644 index 00000000000..373822ec2d8 --- /dev/null +++ b/arch/mips/dec/prom/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for the DECstation prom monitor library routines +# under Linux. +# + +lib-y += init.o memory.o cmdline.o identify.o console.o + +lib-$(CONFIG_MIPS32) += locore.o +lib-$(CONFIG_MIPS64) += call_o32.o + +EXTRA_AFLAGS := $(CFLAGS) diff --git a/arch/mips/dec/prom/call_o32.S b/arch/mips/dec/prom/call_o32.S new file mode 100644 index 00000000000..0dd56db9b3d --- /dev/null +++ b/arch/mips/dec/prom/call_o32.S @@ -0,0 +1,91 @@ +/* + * arch/mips/dec/call_o32.S + * + * O32 interface for the 64 (or N32) ABI. + * + * Copyright (C) 2002 Maciej W. Rozycki + * + * 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 the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <asm/asm.h> +#include <asm/regdef.h> + +/* Maximum number of arguments supported. Must be even! */ +#define O32_ARGC 32 +/* Number of static registers we save. */ +#define O32_STATC 11 +/* Frame size for both of the above. */ +#define O32_FRAMESZ (4 * O32_ARGC + SZREG * O32_STATC) + + .text + +/* + * O32 function call dispatcher, for interfacing 32-bit ROM routines. + * + * The standard 64 (N32) calling sequence is supported, with a0 + * holding a function pointer, a1-a7 -- its first seven arguments + * and the stack -- remaining ones (up to O32_ARGC, including a1-a7). + * Static registers, gp and fp are preserved, v0 holds a result. + * This code relies on the called o32 function for sp and ra + * restoration and thus both this dispatcher and the current stack + * have to be placed in a KSEGx (or KUSEG) address space. Any + * pointers passed have to point to addresses within one of these + * spaces as well. + */ +NESTED(call_o32, O32_FRAMESZ, ra) + REG_SUBU sp,O32_FRAMESZ + + REG_S ra,O32_FRAMESZ-1*SZREG(sp) + REG_S fp,O32_FRAMESZ-2*SZREG(sp) + REG_S gp,O32_FRAMESZ-3*SZREG(sp) + REG_S s7,O32_FRAMESZ-4*SZREG(sp) + REG_S s6,O32_FRAMESZ-5*SZREG(sp) + REG_S s5,O32_FRAMESZ-6*SZREG(sp) + REG_S s4,O32_FRAMESZ-7*SZREG(sp) + REG_S s3,O32_FRAMESZ-8*SZREG(sp) + REG_S s2,O32_FRAMESZ-9*SZREG(sp) + REG_S s1,O32_FRAMESZ-10*SZREG(sp) + REG_S s0,O32_FRAMESZ-11*SZREG(sp) + + move jp,a0 + + sll a0,a1,zero + sll a1,a2,zero + sll a2,a3,zero + sll a3,a4,zero + sw a5,0x10(sp) + sw a6,0x14(sp) + sw a7,0x18(sp) + + PTR_LA t0,O32_FRAMESZ(sp) + PTR_LA t1,0x1c(sp) + li t2,O32_ARGC-7 +1: + lw t3,(t0) + REG_ADDU t0,SZREG + sw t3,(t1) + REG_SUBU t2,1 + REG_ADDU t1,4 + bnez t2,1b + + jalr jp + + REG_L s0,O32_FRAMESZ-11*SZREG(sp) + REG_L s1,O32_FRAMESZ-10*SZREG(sp) + REG_L s2,O32_FRAMESZ-9*SZREG(sp) + REG_L s3,O32_FRAMESZ-8*SZREG(sp) + REG_L s4,O32_FRAMESZ-7*SZREG(sp) + REG_L s5,O32_FRAMESZ-6*SZREG(sp) + REG_L s6,O32_FRAMESZ-5*SZREG(sp) + REG_L s7,O32_FRAMESZ-4*SZREG(sp) + REG_L gp,O32_FRAMESZ-3*SZREG(sp) + REG_L fp,O32_FRAMESZ-2*SZREG(sp) + REG_L ra,O32_FRAMESZ-1*SZREG(sp) + + REG_ADDU sp,O32_FRAMESZ + jr ra +END(call_o32) diff --git a/arch/mips/dec/prom/cmdline.c b/arch/mips/dec/prom/cmdline.c new file mode 100644 index 00000000000..c3490bebbc5 --- /dev/null +++ b/arch/mips/dec/prom/cmdline.c @@ -0,0 +1,39 @@ +/* + * cmdline.c: read the command line passed to us by the PROM. + * + * Copyright (C) 1998 Harald Koerfgen + * Copyright (C) 2002, 2004 Maciej W. Rozycki + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <asm/bootinfo.h> +#include <asm/dec/prom.h> + +#undef PROM_DEBUG + +void __init prom_init_cmdline(s32 argc, s32 *argv, u32 magic) +{ + char *arg; + int start_arg, i; + + /* + * collect args and prepare cmd_line + */ + if (!prom_is_rex(magic)) + start_arg = 1; + else + start_arg = 2; + for (i = start_arg; i < argc; i++) { + arg = (void *)(long)(argv[i]); + strcat(arcs_cmdline, arg); + if (i < (argc - 1)) + strcat(arcs_cmdline, " "); + } + +#ifdef PROM_DEBUG + printk("arcs_cmdline: %s\n", &(arcs_cmdline[0])); +#endif +} diff --git a/arch/mips/dec/prom/console.c b/arch/mips/dec/prom/console.c new file mode 100644 index 00000000000..cade16ec7e5 --- /dev/null +++ b/arch/mips/dec/prom/console.c @@ -0,0 +1,55 @@ +/* + * arch/mips/dec/prom/console.c + * + * DECstation PROM-based early console support. + * + * Copyright (C) 2004 Maciej W. Rozycki + * + * 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 the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <linux/console.h> +#include <linux/init.h> +#include <linux/kernel.h> + +#include <asm/dec/prom.h> + +static void __init prom_console_write(struct console *con, const char *s, + unsigned int c) +{ + static char sfmt[] __initdata = "%%%us"; + char fmt[13]; + + snprintf(fmt, sizeof(fmt), sfmt, c); + prom_printf(fmt, s); +} + +static struct console promcons __initdata = { + .name = "prom", + .write = prom_console_write, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +static int promcons_output __initdata = 0; + +void __init register_prom_console(void) +{ + if (!promcons_output) { + promcons_output = 1; + register_console(&promcons); + } +} + +void __init unregister_prom_console(void) +{ + if (promcons_output) { + unregister_console(&promcons); + promcons_output = 0; + } +} + +void disable_early_printk(void) + __attribute__((alias("unregister_prom_console"))); diff --git a/arch/mips/dec/prom/dectypes.h b/arch/mips/dec/prom/dectypes.h new file mode 100644 index 00000000000..707b6f1f5a9 --- /dev/null +++ b/arch/mips/dec/prom/dectypes.h @@ -0,0 +1,14 @@ +#ifndef DECTYPES +#define DECTYPES + +#define DS2100_3100 1 /* DS2100/3100 Pmax */ +#define DS5000_200 2 /* DS5000/200 3max */ +#define DS5000_1XX 3 /* DS5000/1xx kmin */ +#define DS5000_2X0 4 /* DS5000/2x0 3max+ */ +#define DS5800 5 /* DS5800 Isis */ +#define DS5400 6 /* DS5400 MIPSfair */ +#define DS5000_XX 7 /* DS5000/xx maxine */ +#define DS5500 11 /* DS5500 MIPSfair-2 */ +#define DS5100 12 /* DS5100 MIPSmate */ + +#endif diff --git a/arch/mips/dec/prom/identify.c b/arch/mips/dec/prom/identify.c new file mode 100644 index 00000000000..9380588cb15 --- /dev/null +++ b/arch/mips/dec/prom/identify.c @@ -0,0 +1,177 @@ +/* + * identify.c: machine identification code. + * + * Copyright (C) 1998 Harald Koerfgen and Paul M. Antoine + * Copyright (C) 2002, 2003, 2004 Maciej W. Rozycki + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mc146818rtc.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <asm/bootinfo.h> +#include <asm/dec/ioasic.h> +#include <asm/dec/ioasic_addrs.h> +#include <asm/dec/kn01.h> +#include <asm/dec/kn02.h> +#include <asm/dec/kn02ba.h> +#include <asm/dec/kn02ca.h> +#include <asm/dec/kn03.h> +#include <asm/dec/kn230.h> +#include <asm/dec/prom.h> + +#include "dectypes.h" + +extern unsigned long mips_machgroup; +extern unsigned long mips_machtype; + +static const char *dec_system_strings[] = { + [MACH_DSUNKNOWN] "unknown DECstation", + [MACH_DS23100] "DECstation 2100/3100", + [MACH_DS5100] "DECsystem 5100", + [MACH_DS5000_200] "DECstation 5000/200", + [MACH_DS5000_1XX] "DECstation 5000/1xx", + [MACH_DS5000_XX] "Personal DECstation 5000/xx", + [MACH_DS5000_2X0] "DECstation 5000/2x0", + [MACH_DS5400] "DECsystem 5400", + [MACH_DS5500] "DECsystem 5500", + [MACH_DS5800] "DECsystem 5800", + [MACH_DS5900] "DECsystem 5900", +}; + +const char *get_system_type(void) +{ +#define STR_BUF_LEN 64 + static char system[STR_BUF_LEN]; + static int called = 0; + + if (called == 0) { + called = 1; + snprintf(system, STR_BUF_LEN, "Digital %s", + dec_system_strings[mips_machtype]); + } + + return system; +} + + +/* + * Setup essential system-specific memory addresses. We need them + * early. Semantically the functions belong to prom/init.c, but they + * are compact enough we want them inlined. --macro + */ +volatile u8 *dec_rtc_base; + +EXPORT_SYMBOL(dec_rtc_base); + +static inline void prom_init_kn01(void) +{ + dec_rtc_base = (void *)KN01_RTC_BASE; + dec_kn_slot_size = KN01_SLOT_SIZE; +} + +static inline void prom_init_kn230(void) +{ + dec_rtc_base = (void *)KN01_RTC_BASE; + dec_kn_slot_size = KN01_SLOT_SIZE; +} + +static inline void prom_init_kn02(void) +{ + dec_rtc_base = (void *)KN02_RTC_BASE; + dec_kn_slot_size = KN02_SLOT_SIZE; +} + +static inline void prom_init_kn02xa(void) +{ + ioasic_base = (void *)KN02XA_IOASIC_BASE; + dec_rtc_base = (void *)KN02XA_RTC_BASE; + dec_kn_slot_size = IOASIC_SLOT_SIZE; +} + +static inline void prom_init_kn03(void) +{ + ioasic_base = (void *)KN03_IOASIC_BASE; + dec_rtc_base = (void *)KN03_RTC_BASE; + dec_kn_slot_size = IOASIC_SLOT_SIZE; +} + + +void __init prom_identify_arch(u32 magic) +{ + unsigned char dec_cpunum, dec_firmrev, dec_etc, dec_systype; + u32 dec_sysid; + + if (!prom_is_rex(magic)) { + dec_sysid = simple_strtoul(prom_getenv("systype"), + (char **)0, 0); + } else { + dec_sysid = rex_getsysid(); + if (dec_sysid == 0) { + printk("Zero sysid returned from PROM! " + "Assuming a PMAX-like machine.\n"); + dec_sysid = 1; + } + } + + dec_cpunum = (dec_sysid & 0xff000000) >> 24; + dec_systype = (dec_sysid & 0xff0000) >> 16; + dec_firmrev = (dec_sysid & 0xff00) >> 8; + dec_etc = dec_sysid & 0xff; + + /* We're obviously one of the DEC machines */ + mips_machgroup = MACH_GROUP_DEC; + + /* + * FIXME: This may not be an exhaustive list of DECStations/Servers! + * Put all model-specific initialisation calls here. + */ + switch (dec_systype) { + case DS2100_3100: + mips_machtype = MACH_DS23100; + prom_init_kn01(); + break; + case DS5100: /* DS5100 MIPSMATE */ + mips_machtype = MACH_DS5100; + prom_init_kn230(); + break; + case DS5000_200: /* DS5000 3max */ + mips_machtype = MACH_DS5000_200; + prom_init_kn02(); + break; + case DS5000_1XX: /* DS5000/100 3min */ + mips_machtype = MACH_DS5000_1XX; + prom_init_kn02xa(); + break; + case DS5000_2X0: /* DS5000/240 3max+ or DS5900 bigmax */ + mips_machtype = MACH_DS5000_2X0; + prom_init_kn03(); + if (!(ioasic_read(IO_REG_SIR) & KN03_IO_INR_3MAXP)) + mips_machtype = MACH_DS5900; + break; + case DS5000_XX: /* Personal DS5000/xx maxine */ + mips_machtype = MACH_DS5000_XX; + prom_init_kn02xa(); + break; + case DS5800: /* DS5800 Isis */ + mips_machtype = MACH_DS5800; + break; + case DS5400: /* DS5400 MIPSfair */ + mips_machtype = MACH_DS5400; + break; + case DS5500: /* DS5500 MIPSfair-2 */ + mips_machtype = MACH_DS5500; + break; + default: + mips_machtype = MACH_DSUNKNOWN; + break; + } + + if (mips_machtype == MACH_DSUNKNOWN) + printk("This is an %s, id is %x\n", + dec_system_strings[mips_machtype], dec_systype); + else + printk("This is a %s\n", dec_system_strings[mips_machtype]); +} diff --git a/arch/mips/dec/prom/init.c b/arch/mips/dec/prom/init.c new file mode 100644 index 00000000000..60f74256e68 --- /dev/null +++ b/arch/mips/dec/prom/init.c @@ -0,0 +1,134 @@ +/* + * init.c: PROM library initialisation code. + * + * Copyright (C) 1998 Harald Koerfgen + * Copyright (C) 2002, 2004 Maciej W. Rozycki + */ +#include <linux/config.h> +#include <linux/init.h> +#include <linux/smp.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <asm/bootinfo.h> +#include <asm/cpu.h> +#include <asm/processor.h> + +#include <asm/dec/prom.h> + + +int (*__rex_bootinit)(void); +int (*__rex_bootread)(void); +int (*__rex_getbitmap)(memmap *); +unsigned long *(*__rex_slot_address)(int); +void *(*__rex_gettcinfo)(void); +int (*__rex_getsysid)(void); +void (*__rex_clear_cache)(void); + +int (*__prom_getchar)(void); +char *(*__prom_getenv)(char *); +int (*__prom_printf)(char *, ...); + +int (*__pmax_open)(char*, int); +int (*__pmax_lseek)(int, long, int); +int (*__pmax_read)(int, void *, int); +int (*__pmax_close)(int); + + +/* + * Detect which PROM the DECSTATION has, and set the callback vectors + * appropriately. + */ +void __init which_prom(s32 magic, s32 *prom_vec) +{ + /* + * No sign of the REX PROM's magic number means we assume a non-REX + * machine (i.e. we're on a DS2100/3100, DS5100 or DS5000/2xx) + */ + if (prom_is_rex(magic)) { + /* + * Set up prom abstraction structure with REX entry points. + */ + __rex_bootinit = + (void *)(long)*(prom_vec + REX_PROM_BOOTINIT); + __rex_bootread = + (void *)(long)*(prom_vec + REX_PROM_BOOTREAD); + __rex_getbitmap = + (void *)(long)*(prom_vec + REX_PROM_GETBITMAP); + __prom_getchar = + (void *)(long)*(prom_vec + REX_PROM_GETCHAR); + __prom_getenv = + (void *)(long)*(prom_vec + REX_PROM_GETENV); + __rex_getsysid = + (void *)(long)*(prom_vec + REX_PROM_GETSYSID); + __rex_gettcinfo = + (void *)(long)*(prom_vec + REX_PROM_GETTCINFO); + __prom_printf = + (void *)(long)*(prom_vec + REX_PROM_PRINTF); + __rex_slot_address = + (void *)(long)*(prom_vec + REX_PROM_SLOTADDR); + __rex_clear_cache = + (void *)(long)*(prom_vec + REX_PROM_CLEARCACHE); + } else { + /* + * Set up prom abstraction structure with non-REX entry points. + */ + __prom_getchar = (void *)PMAX_PROM_GETCHAR; + __prom_getenv = (void *)PMAX_PROM_GETENV; + __prom_printf = (void *)PMAX_PROM_PRINTF; + __pmax_open = (void *)PMAX_PROM_OPEN; + __pmax_lseek = (void *)PMAX_PROM_LSEEK; + __pmax_read = (void *)PMAX_PROM_READ; + __pmax_close = (void *)PMAX_PROM_CLOSE; + } +} + +void __init prom_init(void) +{ + extern void dec_machine_halt(void); + static char cpu_msg[] __initdata = + "Sorry, this kernel is compiled for a wrong CPU type!\n"; + static char r3k_msg[] __initdata = + "Please recompile with \"CONFIG_CPU_R3000 = y\".\n"; + static char r4k_msg[] __initdata = + "Please recompile with \"CONFIG_CPU_R4x00 = y\".\n"; + s32 argc = fw_arg0; + s32 argv = fw_arg1; + u32 magic = fw_arg2; + s32 prom_vec = fw_arg3; + + /* + * Determine which PROM we have + * (and therefore which machine we're on!) + */ + which_prom(magic, prom_vec); + + if (prom_is_rex(magic)) + rex_clear_cache(); + + /* Register the early console. */ + register_prom_console(); + + /* Were we compiled with the right CPU option? */ +#if defined(CONFIG_CPU_R3000) + if ((current_cpu_data.cputype == CPU_R4000SC) || + (current_cpu_data.cputype == CPU_R4400SC)) { + printk(cpu_msg); + printk(r4k_msg); + dec_machine_halt(); + } +#endif + +#if defined(CONFIG_CPU_R4X00) + if ((current_cpu_data.cputype == CPU_R3000) || + (current_cpu_data.cputype == CPU_R3000A)) { + printk(cpu_msg); + printk(r3k_msg); + dec_machine_halt(); + } +#endif + + prom_meminit(magic); + prom_identify_arch(magic); + prom_init_cmdline(argc, argv, magic); +} diff --git a/arch/mips/dec/prom/locore.S b/arch/mips/dec/prom/locore.S new file mode 100644 index 00000000000..d9acdcefee8 --- /dev/null +++ b/arch/mips/dec/prom/locore.S @@ -0,0 +1,30 @@ +/* + * locore.S + */ +#include <asm/asm.h> +#include <asm/regdef.h> +#include <asm/mipsregs.h> + + .text + +/* + * Simple general exception handling routine. This one is used for the + * Memory sizing routine for pmax machines. HK + */ + +NESTED(genexcept_early, 0, sp) + .set noat + .set noreorder + + mfc0 k0, CP0_STATUS + la k1, mem_err + + sw k0, 0(k1) + + mfc0 k0, CP0_EPC + nop + addiu k0, 4 # skip the causing instruction + jr k0 + rfe +END(genexcept_early) + diff --git a/arch/mips/dec/prom/memory.c b/arch/mips/dec/prom/memory.c new file mode 100644 index 00000000000..e4f6f26425e --- /dev/null +++ b/arch/mips/dec/prom/memory.c @@ -0,0 +1,130 @@ +/* + * memory.c: memory initialisation code. + * + * Copyright (C) 1998 Harald Koerfgen, Frieder Streffer and Paul M. Antoine + * Copyright (C) 2000, 2002 Maciej W. Rozycki + */ +#include <linux/config.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/bootmem.h> +#include <linux/types.h> + +#include <asm/addrspace.h> +#include <asm/bootinfo.h> +#include <asm/dec/machtype.h> +#include <asm/dec/prom.h> +#include <asm/page.h> +#include <asm/sections.h> + + +volatile unsigned long mem_err = 0; /* So we know an error occurred */ + +/* + * Probe memory in 4MB chunks, waiting for an error to tell us we've fallen + * off the end of real memory. Only suitable for the 2100/3100's (PMAX). + */ + +#define CHUNK_SIZE 0x400000 + +static inline void pmax_setup_memory_region(void) +{ + volatile unsigned char *memory_page, dummy; + char old_handler[0x80]; + extern char genexcept_early; + + /* Install exception handler */ + memcpy(&old_handler, (void *)(KSEG0 + 0x80), 0x80); + memcpy((void *)(KSEG0 + 0x80), &genexcept_early, 0x80); + + /* read unmapped and uncached (KSEG1) + * DECstations have at least 4MB RAM + * Assume less than 480MB of RAM, as this is max for 5000/2xx + * FIXME this should be replaced by the first free page! + */ + for (memory_page = (unsigned char *) KSEG1 + CHUNK_SIZE; + (mem_err== 0) && (memory_page < ((unsigned char *) KSEG1+0x1E000000)); + memory_page += CHUNK_SIZE) { + dummy = *memory_page; + } + memcpy((void *)(KSEG0 + 0x80), &old_handler, 0x80); + + add_memory_region(0, (unsigned long)memory_page - KSEG1 - CHUNK_SIZE, + BOOT_MEM_RAM); +} + +/* + * Use the REX prom calls to get hold of the memory bitmap, and thence + * determine memory size. + */ +static inline void rex_setup_memory_region(void) +{ + int i, bitmap_size; + unsigned long mem_start = 0, mem_size = 0; + memmap *bm; + + /* some free 64k */ + bm = (memmap *)KSEG0ADDR(0x28000); + + bitmap_size = rex_getbitmap(bm); + + for (i = 0; i < bitmap_size; i++) { + /* FIXME: very simplistically only add full sets of pages */ + if (bm->bitmap[i] == 0xff) + mem_size += (8 * bm->pagesize); + else if (!mem_size) + mem_start += (8 * bm->pagesize); + else { + add_memory_region(mem_start, mem_size, BOOT_MEM_RAM); + mem_start += mem_size + (8 * bm->pagesize); + mem_size = 0; + } + } + if (mem_size) + add_memory_region(mem_start, mem_size, BOOT_MEM_RAM); +} + +void __init prom_meminit(u32 magic) +{ + if (!prom_is_rex(magic)) + pmax_setup_memory_region(); + else + rex_setup_memory_region(); +} + +unsigned long __init prom_free_prom_memory(void) +{ + unsigned long addr, end; + + /* + * Free everything below the kernel itself but leave + * the first page reserved for the exception handlers. + */ + +#if defined(CONFIG_DECLANCE) || defined(CONFIG_DECLANCE_MODULE) + /* + * Leave 128 KB reserved for Lance memory for + * IOASIC DECstations. + * + * XXX: save this address for use in dec_lance.c? + */ + if (IOASIC) + end = __pa(&_text) - 0x00020000; + else +#endif + end = __pa(&_text); + + addr = PAGE_SIZE; + while (addr < end) { + ClearPageReserved(virt_to_page(__va(addr))); + set_page_count(virt_to_page(__va(addr)), 1); + free_page((unsigned long)__va(addr)); + addr += PAGE_SIZE; + } + + printk("Freeing unused PROM memory: %ldk freed\n", + (end - PAGE_SIZE) >> 10); + + return end - PAGE_SIZE; +} |