Commit 457450ab authored by Nicolas Pitre's avatar Nicolas Pitre Committed by Russell King

[ARM PATCH] 2154/2: XIP kernel for ARM

Patch from Nicolas Pitre

This patch allows for the kernel to be configured for XIP.
A lot of people are using semi hacked up XIP patches already so
it is a good idea to have a generic and clean implementation 
supporting all ARM targets. The patch isn't too intrusive.

It involves:

- modifying the kernel entry code to map separate .text and .data
  sections in the initial page table, as well as relocating
  .data to ram when needed

- modifying the linker script to account for the different VMA and
  LMA for .data, as well as making sure that .init.data gets
  relocated to ram

- adding the final kernel mapping with a new MT_ROM mem type

- distinguishing between XIP and non-XIP for bootmem and memory
  resource declaration

- and adding proper target handling to Makefiles.

While at it, this also cleans up the kernel boot code a bit
so the kernel can now be compiled for any address in ram,
removing the need for a relation between kernel address and
start of ram.  Also throws in some more comments.

And finally the _text, _etext, _end and similar variables are now
declared extern void instead of extern char, or even extern int.
That allows for operations on their address directly without any
cast, and trying to reference them by mistake would yield an 
error which is a good thing.

Tested both configurations: XIP and non XIP, the later 
producing a kernel for execution from ram just as before.

Signed-off-by: Nicolas Pitre 
Signed-off-by: Russell King
parent d8eea8e7
......@@ -343,6 +343,50 @@ config ZBOOT_ROM_BSS
while the decompressor is running. Unless you have special requirements,
you should not change this value.
config XIP_KERNEL
bool "Kernel Execute-In-Place from ROM"
depends on !ZBOOT_ROM
help
Execute-In-Place allows the kernel to run from non-volatile storage
directly addressable by the CPU, such as NOR flash. This saves RAM
space since the text section of the kernel is not loaded from flash
to RAM. Read-write sections, such as the data section and stack,
are still copied to RAM. The XIP kernel is not compressed since
it has to run directly from flash, so it will take more space to
store it. The flash address used to link the kernel object files,
and for storing it, is configuration dependent. Therefore, if you
say Y here, you must know the proper physical address where to
store the kernel image depending on your own flash memory usage.
Also note that the make target becomes "make xipImage" rather than
"make zImage" or "make Image". The final kernel binary to put in
ROM memory will be arch/arm/boot/xipImage.
If unsure, say N.
config XIP_PHYS_ADDR
hex "XIP Kernel Physical Location"
depends on XIP_KERNEL
default "0x00080000"
help
This is the physical address in your flash memory the kernel will
be linked for and stored to. This address is dependent on your
own flash usage.
Please note that, if you're using MTD, you must use a flash chip
that is NOT handled by MTD or the flash will be turned into non
data mode for status and query purposes which will instantaneously
crash the kernel.
MTD can however be used with a XIP kernel on the same flash chip
but only if the flash memory supports multiple partitions in
hardware, like with the Intel K3 flash parts, and only if the
kernel is not stored within the firrst hardware partition of the
chip.
In any case, make sure that MTD support is configured out for
the first attempt.
if (ARCH_SA1100 || ARCH_INTEGRATOR)
config CPU_FREQ
......
......@@ -65,8 +65,6 @@ AFLAGS +=$(CFLAGS_ABI) $(arch-y) $(tune-y) -msoft-float
CHECKFLAGS += -D__arm__
#Default value
DATAADDR := .
head-y := arch/arm/kernel/head.o arch/arm/kernel/init_task.o
textaddr-y := 0xC0008000
......@@ -110,6 +108,15 @@ export CFLAGS_3c589_cs.o
endif
TEXTADDR := $(textaddr-y)
ifeq ($(CONFIG_XIP_KERNEL),y)
DATAADDR := $(TEXTADDR)
# Replace phys addr with virt addr while keeping offset from base.
xipaddr-y ?= 0xe8000000
TEXTADDR := $(shell echo $(CONFIG_XIP_PHYS_ADDR) $(xipaddr-y) | \
awk --non-decimal-data '/[:xdigit:]/ \
{ printf("0x%x\n", and($$1, 0x000fffff) + $$2) }' )
endif
ifeq ($(incdir-y),)
incdir-y := $(machine-y)
endif
......@@ -120,7 +127,7 @@ else
MACHINE :=
endif
export TEXTADDR GZFLAGS
export TEXTADDR DATAADDR GZFLAGS
# Do we have FASTFPE?
FASTFPE :=arch/arm/fastfpe
......@@ -142,7 +149,11 @@ drivers-$(CONFIG_ARCH_L7200) += drivers/acorn/char/
libs-y += arch/arm/lib/
# Default target when executing plain make
ifeq ($(CONFIG_XIP_KERNEL),y)
all: xipImage
else
all: zImage
endif
boot := arch/arm/boot
......@@ -169,7 +180,7 @@ maketools: include/asm-arm/constants.h include/linux/version.h FORCE
# Convert bzImage to zImage
bzImage: zImage
zImage Image bootpImage uImage: vmlinux
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
zinstall install: vmlinux
......@@ -195,6 +206,7 @@ include/asm-$(ARCH)/constants.h: arch/$(ARCH)/kernel/asm-offsets.s
define archhelp
echo '* zImage - Compressed kernel image (arch/$(ARCH)/boot/zImage)'
echo ' Image - Uncompressed kernel image (arch/$(ARCH)/boot/Image)'
echo '* xipImage - XIP kernel image, if configured (arch/$(ARCH)/boot/xipImage)'
echo ' bootpImage - Combined zImage and initial RAM disk'
echo ' (supply initrd image via make variable INITRD=<path>)'
echo ' install - Install uncompressed kernel'
......
......@@ -24,7 +24,24 @@ INITRD_PHYS := $(initrd_phys-y)
export ZRELADDR INITRD_PHYS PARAMS_PHYS
targets := Image zImage bootpImage uImage
targets := Image zImage xipImage bootpImage uImage
ifeq ($(CONFIG_XIP_KERNEL),y)
$(obj)/xipImage: vmlinux FORCE
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready (physical address: $(CONFIG_XIP_PHYS_ADDR))'
$(obj)/Image $(obj)/zImage: FORCE
@echo 'Kernel configured for XIP (CONFIG_XIP_KERNEL=y)'
@echo 'Only the xipImage target is available in this case'
@false
else
$(obj)/xipImage: FORCE
@echo 'Kernel not configured for XIP (CONFIG_XIP_KERNEL!=y)'
@false
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
......@@ -37,6 +54,8 @@ $(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo ' Kernel: $@ is ready'
endif
quiet_cmd_uimage = UIMAGE $@
cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A arm -O linux -T kernel \
-C none -a $(ZRELADDR) -e $(ZRELADDR) \
......
......@@ -2,7 +2,7 @@
# Makefile for the linux kernel.
#
AFLAGS_head.o := -DTEXTADDR=$(TEXTADDR)
AFLAGS_head.o := -DTEXTADDR=$(TEXTADDR) -DDATAADDR=$(DATAADDR)
# Object file lists.
......
......@@ -14,6 +14,7 @@
#include <linux/mm.h>
#include <asm/mach/arch.h>
#include <asm/thread_info.h>
#include <asm/memory.h>
/*
* Make sure that the compiler and target are compatible.
......@@ -72,6 +73,7 @@ int main(void)
DEFINE(VM_EXEC, VM_EXEC);
BLANK();
DEFINE(PAGE_SZ, PAGE_SIZE);
DEFINE(VIRT_OFFSET, PAGE_OFFSET);
BLANK();
DEFINE(SYS_ERROR0, 0x9f0000);
BLANK();
......
......@@ -19,17 +19,16 @@
#include <asm/ptrace.h>
#include <asm/constants.h>
#ifndef CONFIG_XIP_KERNEL
/*
* We place the page tables 16K below TEXTADDR. Therefore, we must make sure
* that TEXTADDR is correctly set. Currently, we expect the least significant
* 16 bits to be 0x8000, but we could probably relax this restriction to
* TEXTADDR > PAGE_OFFSET + 0x4000
* TEXTADDR >= PAGE_OFFSET + 0x4000
*
* Note that swapper_pg_dir is the virtual address of the page tables, and
* pgtbl gives us a position-independent reference to these tables. We can
* do this because stext == TEXTADDR
*
* swapper_pg_dir, pgtbl and krnladr are all closely related.
*/
#if (TEXTADDR & 0xffff) != 0x8000
#error TEXTADDR must start at 0xXXXX8000
......@@ -38,19 +37,35 @@
.globl swapper_pg_dir
.equ swapper_pg_dir, TEXTADDR - 0x4000
.macro pgtbl, reg
adr \reg, stext
sub \reg, \reg, #0x4000
.macro pgtbl, rd, phys
adr \rd, stext
sub \rd, \rd, #0x4000
.endm
#else
/*
* Since the page table is closely related to the kernel start address, we
* can convert the page table base address to the base address of the section
* containing both.
* XIP Kernel:
*
* We place the page tables 16K below DATAADDR. Therefore, we must make sure
* that DATAADDR is correctly set. Currently, we expect the least significant
* 16 bits to be 0x8000, but we could probably relax this restriction to
* DATAADDR >= PAGE_OFFSET + 0x4000
*
* Note that pgtbl is meant to return the physical address of swapper_pg_dir.
* We can't make it relative to the kernel position in this case since
* the kernel can physically be anywhere.
*/
.macro krnladr, rd, pgtable
bic \rd, \pgtable, #0x000ff000
#if (DATAADDR & 0xffff) != 0x8000
#error DATAADDR must start at 0xXXXX8000
#endif
.globl swapper_pg_dir
.equ swapper_pg_dir, DATAADDR - 0x4000
.macro pgtbl, rd, phys
ldr \rd, =((DATAADDR - 0x4000) - VIRT_OFFSET)
add \rd, \rd, \phys
.endm
#endif
/*
* Kernel startup entry point.
......@@ -99,6 +114,8 @@ ENTRY(stext)
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r2
.long __data_start @ r3
.long __bss_start @ r4
.long _end @ r5
.long processor_id @ r6
......@@ -136,12 +153,20 @@ __turn_mmu_on:
*/
.align 5
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3, {r4, r5, r6, r7, r8, sp}
adr r2, __switch_data + 4
ldmia r2, {r2, r3, r4, r5, r6, r7, r8, sp}
cmp r2, r3 @ Copy data segment if needed
1: cmpne r3, r4
ldrne fp, [r2], #4
strne fp, [r3], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r4, r5
strcc fp, [r4],#4
bcc 1b
str r9, [r6] @ Save processor ID
str r1, [r7] @ Save machine type
bic r2, r0, #2 @ Clear 'A' bit
......@@ -156,16 +181,13 @@ __mmap_switched:
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* We only map in 4MB of RAM, which should be sufficient in
* all cases.
*
* r5 = physical address of start of RAM
* r6 = physical IO address
* r7 = byte offset into page tables for IO
* r8 = page table flags
*/
__create_page_tables:
pgtbl r4 @ page table address
pgtbl r4, r5 @ page table address
/*
* Clear the 16K level 1 swapper page table
......@@ -183,28 +205,50 @@ __create_page_tables:
/*
* Create identity mapping for first MB of kernel to
* cater for the MMU enable. This identity mapping
* will be removed by paging_init()
* will be removed by paging_init(). We use our current program
* counter to determine corresponding section base address.
*/
krnladr r2, r4 @ start of kernel
add r3, r8, r2 @ flags + kernel base
str r3, [r4, r2, lsr #18] @ identity mapping
mov r2, pc, lsr #20 @ start of kernel section
add r3, r8, r2, lsl #20 @ flags + kernel base
str r3, [r4, r2, lsl #2] @ identity mapping
/*
* Now setup the pagetables for our kernel direct
* mapped region. We round TEXTADDR down to the
* nearest megabyte boundary.
* nearest megabyte boundary. It is assumed that
* the kernel fits within 4 contigous 1MB sections.
*/
add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel
bic r2, r3, #0x00f00000
str r2, [r0] @ PAGE_OFFSET + 0MB
add r0, r0, #(TEXTADDR & 0x00f00000) >> 18
str r3, [r0], #4 @ KERNEL + 0MB
add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel
str r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 1MB
str r3, [r0, #4]! @ KERNEL + 1MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 2MB
str r3, [r0, #4]! @ KERNEL + 2MB
add r3, r3, #1 << 20
str r3, [r0], #4 @ KERNEL + 3MB
str r3, [r0, #4] @ KERNEL + 3MB
/*
* Then map first 1MB of ram in case it contains our boot params.
*/
add r0, r4, #VIRT_OFFSET >> 18
add r2, r5, r8
str r2, [r0]
#ifdef CONFIG_XIP_KERNEL
/*
* Map some ram to cover our .data and .bss areas.
* Mapping 3MB should be plenty.
*/
sub r3, r4, r5
mov r3, r3, lsr #20
add r0, r0, r3, lsl #2
add r2, r2, r3, lsl #20
str r2, [r0], #4
add r2, r2, #(1 << 20)
str r2, [r0], #4
add r2, r2, #(1 << 20)
str r2, [r0]
#endif
bic r8, r8, #0x0c @ turn off cacheable
@ and bufferable bits
......@@ -255,6 +299,7 @@ __create_page_tables:
str r3, [r0]
#endif
mov pc, lr
.ltorg
......
......@@ -59,7 +59,7 @@ extern void convert_to_tag_list(struct tag *tags);
extern void squash_mem_tags(struct tag *tag);
extern void reboot_setup(char *str);
extern int root_mountflags;
extern int _stext, _text, _etext, _edata, _end;
extern void _stext, _text, _etext, __data_start, _edata, _end;
unsigned int processor_id;
unsigned int __machine_arch_type;
......@@ -113,7 +113,7 @@ static union { char c[4]; unsigned long l; } endian_test __initdata = { { 'l', '
*/
static struct resource mem_res[] = {
{ "Video RAM", 0, 0, IORESOURCE_MEM },
{ "Kernel code", 0, 0, IORESOURCE_MEM },
{ "Kernel text", 0, 0, IORESOURCE_MEM },
{ "Kernel data", 0, 0, IORESOURCE_MEM }
};
......@@ -447,10 +447,10 @@ request_standard_resources(struct meminfo *mi, struct machine_desc *mdesc)
struct resource *res;
int i;
kernel_code.start = __virt_to_phys(init_mm.start_code);
kernel_code.end = __virt_to_phys(init_mm.end_code - 1);
kernel_data.start = __virt_to_phys(init_mm.end_code);
kernel_data.end = __virt_to_phys(init_mm.brk - 1);
kernel_code.start = virt_to_phys(&_text);
kernel_code.end = virt_to_phys(&_etext - 1);
kernel_data.start = virt_to_phys(&__data_start);
kernel_data.end = virt_to_phys(&_end - 1);
for (i = 0; i < mi->nr_banks; i++) {
unsigned long virt_start, virt_end;
......
......@@ -4,6 +4,7 @@
*/
#include <asm-generic/vmlinux.lds.h>
#include <linux/config.h>
OUTPUT_ARCH(arm)
ENTRY(stext)
......@@ -17,7 +18,6 @@ SECTIONS
. = TEXTADDR;
.init : { /* Init code and data */
_stext = .;
__init_begin = .;
_sinittext = .;
*(.init.text)
_einittext = .;
......@@ -30,7 +30,6 @@ SECTIONS
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
*(.init.data)
. = ALIGN(16);
__setup_start = .;
*(.init.setup)
......@@ -57,8 +56,12 @@ SECTIONS
__initramfs_start = .;
usr/built-in.o(.init.ramfs)
__initramfs_end = .;
#ifndef CONFIG_XIP_KERNEL
__init_begin = _stext;
*(.init.data)
. = ALIGN(4096);
__init_end = .;
#endif
}
/DISCARD/ : { /* Exit code and data */
......@@ -79,8 +82,6 @@ SECTIONS
*(.glue_7)
*(.glue_7t)
*(.got) /* Global offset table */
_etext = .; /* End of text section */
}
. = ALIGN(16);
......@@ -92,15 +93,33 @@ SECTIONS
RODATA
_etext = .; /* End of text and rodata section */
#ifdef CONFIG_XIP_KERNEL
__data_loc = ALIGN(4); /* location in binary */
. = DATAADDR;
#else
. = ALIGN(8192);
__data_loc = .;
#endif
.data : AT(__data_loc) {
__data_start = .; /* address in memory */
.data : {
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
*(.init.task)
#ifdef CONFIG_XIP_KERNEL
. = ALIGN(4096);
__init_begin = .;
*(.init.data)
. = ALIGN(4096);
__init_end = .;
#endif
. = ALIGN(4096);
__nosave_begin = .;
*(.data.nosave)
......@@ -126,7 +145,7 @@ SECTIONS
__bss_start = .; /* BSS */
*(.bss)
*(COMMON)
_end = . ;
_end = .;
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
......
......@@ -30,7 +30,7 @@
DEFINE_PER_CPU(struct mmu_gather, mmu_gathers);
extern pgd_t swapper_pg_dir[PTRS_PER_PGD];
extern char _stext, _text, _etext, _end, __init_begin, __init_end;
extern void _stext, _text, _etext, __data_start, _end, __init_begin, __init_end;
extern unsigned long phys_initrd_start;
extern unsigned long phys_initrd_size;
......@@ -282,7 +282,11 @@ static __init void reserve_node_zero(unsigned int bootmap_pfn, unsigned int boot
* Register the kernel text and data with bootmem.
* Note that this can only be in node 0.
*/
#ifdef CONFIG_XIP_KERNEL
reserve_bootmem_node(pgdat, __pa(&__data_start), &_end - &__data_start);
#else
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext);
#endif
/*
* Reserve the page tables. These are already in use,
......@@ -540,7 +544,7 @@ void __init mem_init(void)
int i, node;
codepages = &_etext - &_text;
datapages = &_end - &_etext;
datapages = &_end - &__data_start;
initpages = &__init_end - &__init_begin;
#ifndef CONFIG_DISCONTIGMEM
......
......@@ -326,6 +326,10 @@ static struct mem_types mem_types[] __initdata = {
[MT_MEMORY] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
.domain = DOMAIN_KERNEL,
},
[MT_ROM] = {
.prot_sect = PMD_TYPE_SECT,
.domain = DOMAIN_KERNEL,
}
};
......@@ -359,6 +363,7 @@ static void __init build_mem_type_table(void)
mem_types[MT_MINICLEAN].prot_sect |= PMD_BIT4;
mem_types[MT_VECTORS].prot_l1 |= PMD_BIT4;
mem_types[MT_MEMORY].prot_sect |= PMD_BIT4;
mem_types[MT_ROM].prot_sect |= PMD_BIT4;
}
/*
......@@ -370,6 +375,7 @@ static void __init build_mem_type_table(void)
* kernel memory mapping.
*/
mem_types[MT_MEMORY].prot_sect &= ~PMD_BIT4;
mem_types[MT_ROM].prot_sect &= ~PMD_BIT4;
/*
* Mark cache clean areas read only from SVC mode
* and no access from userspace.
......@@ -389,6 +395,7 @@ static void __init build_mem_type_table(void)
mem_types[MT_VECTORS].prot_l1 |= ecc_mask;
mem_types[MT_MEMORY].prot_sect |= ecc_mask | cp->pmd;
mem_types[MT_ROM].prot_sect |= cp->pmd;
for (i = 0; i < 16; i++) {
unsigned long v = pgprot_val(protection_map[i]);
......@@ -433,7 +440,7 @@ static void __init create_mapping(struct map_desc *md)
return;
}
if (md->type == MT_DEVICE &&
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END) {
printk(KERN_WARNING "BUG: mapping for 0x%08lx at 0x%08lx "
"overlaps vmalloc space\n",
......@@ -508,6 +515,8 @@ void setup_mm_for_reboot(char mode)
}
}
extern void _stext, _etext;
/*
* Setup initial mappings. We use the page we allocated for zero page to hold
* the mappings, which will get overwritten by the vectors in traps_init().
......@@ -534,6 +543,14 @@ void __init memtable_init(struct meminfo *mi)
p ++;
}
#ifdef CONFIG_XIP_KERNEL
p->physical = CONFIG_XIP_PHYS_ADDR & PMD_MASK;
p->virtual = (unsigned long)&_stext & PMD_MASK;
p->length = ((unsigned long)&_etext - p->virtual + ~PMD_MASK) & PMD_MASK;
p->type = MT_ROM;
p ++;
#endif
#ifdef FLUSH_BASE
p->physical = FLUSH_BASE_PHYS;
p->virtual = FLUSH_BASE;
......
......@@ -23,6 +23,7 @@ struct meminfo;
#define MT_MINICLEAN 2
#define MT_VECTORS 3
#define MT_MEMORY 4
#define MT_ROM 5
extern void create_memmap_holes(struct meminfo *);
extern void memtable_init(struct meminfo *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment