From: Domenico Andreoli <[email-masked]>

Main concept here is the "decompressor tag" defined by struct:

struct decomp_tag_hdr {
	unsigned int arch_id;
	unsigned short type;
	unsigned short size;
};

Each tag is associated to an architecture, has a type to identify it
among the different kind of tags (see enum decomp_tag_type) and a size
to easily walk heterogeneous piles of tags.

Secondary concept (but primary for the patchset) is the "architecture
tag" defined by struct:

struct decomp_arch_tag {
	struct decomp_tag_hdr hdr;
	void (*setup)(void);
	void (*error)(char *msg);
};

So here we have the first user of the decompressor tags who adds some
arch specific ops (setup and error) to a tag and ships everything to
the decompressor.

The rest of the patch is exactly what implements that "ships everything
to the decompressor" and what the decompressor does with that "everything".

So, in order to ship stuff to the decopressor, each tag (and respective
pointed-to code/data) needs to be stored in the proper ELF sections. Such
sections are relocated (*) to the decompressor during the build.

Almost all of this is taken care by some type saving (but not too scary)
macros and some help from the linker.

Next, the decompressor at boot walks the shipped tags and looks for a
match of the machine id passed by the boot loader. The first matching
architecture tag provides the possibly NULL pointers to the specific ops.
In case of not matching machid (and not in case of NULL ops), fallback
functions are used instead.

All of this is implemented in file arch.c, which also takes care of
some stuff previously in misc.c and cleaning decompress_kernel(). The
new key function arch_setup() is invoked right before decompress_kernel().

(*) such relocation brings in some difficulties with strings and pointers
used in the shipped ops. Also invoking functions from there can be from
trivial (inline functions) to impossible (we don't relocate the whole
kernel into the decompressor...).

Signed-off-by: Domenico Andreoli <[email-masked]>

---
 arch/arm/boot/compressed/arch.c |   53 +++++++++++++++++++++++++++++++++++++
 arch/arm/boot/compressed/arch.h |   10 +++++++
 arch/arm/boot/compressed/head.S |   14 +++++-----
 arch/arm/boot/compressed/misc.c |   23 ++++++----------
 include/linux/decompress/arch.h |   58 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 136 insertions(+), 22 deletions(-)

Index: b/include/linux/decompress/arch.h
===================================================================
--- /dev/null
+++ b/include/linux/decompress/arch.h
@@ -0,0 +1,58 @@
+/*
+ * Handling of arch specific bits in the decompressor
+ *
+ * Copyright (C) 2012 Domenico Andreoli <[email-masked]>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef LINUX_DECOMP_ARCH_H
+#define LINUX_DECOMP_ARCH_H
+
+#include <linux/init.h>
+
+enum decomp_tag_type {
+	DECOMP_TAG_ARCH = 0,
+};
+
+struct decomp_tag_hdr {
+	unsigned int arch_id;
+	unsigned short type;
+	unsigned short size;
+};
+
+#define DECOMP_TAG_START(_struct, _name, _tagtype, _machid)       \
+static const _struct _name __used                                 \
+  __attribute__((__section__(".arch.tags.decomp"))) = {           \
+	.hdr = {                                                  \
+		.arch_id = _machid,                               \
+		.type = _tagtype,                                 \
+		.size = sizeof(_struct),                          \
+	},
+
+#define DECOMP_TAG_END                                            \
+};
+
+struct decomp_arch_tag {
+	struct decomp_tag_hdr hdr;
+	void (*setup)(void);
+	void (*error)(char *msg);
+};
+
+#define DECOMP_ARCH_START(_machid)                                \
+DECOMP_TAG_START(struct decomp_arch_tag, __decomp_arch_##_machid, \
+	DECOMP_TAG_ARCH, MACH_TYPE_##_machid)
+
+#define DECOMP_ARCH_END     DECOMP_TAG_END
+
+#define __decomp_arch       __section(.arch.text.decomp)
+#define __decomp_archdata   __section(.arch.data.decomp)
+
+#endif /* LINUX_DECOMP_ARCH_H */
Index: b/arch/arm/boot/compressed/misc.c
===================================================================
--- a/arch/arm/boot/compressed/misc.c
+++ b/arch/arm/boot/compressed/misc.c
@@ -16,8 +16,6 @@
  *  This allows for a much quicker boot time.
  */
 
-unsigned int __machine_arch_type;
-
 #include <linux/compiler.h>	/* for inline */
 #include <linux/types.h>
 #include <linux/linkage.h>
@@ -29,6 +27,10 @@ void error(char *x);
 #include <mach/uncompress.h>
 #endif
 
+#include <linux/decompress/arch.h>
+
+#include "arch.h"
+
 #ifdef CONFIG_DEBUG_ICEDCC
 
 #if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_V6K) || defined(CONFIG_CPU_V7)
@@ -134,12 +136,10 @@ extern char input_data_end[];
 
 unsigned char *output_data;
 
-unsigned long free_mem_ptr;
-unsigned long free_mem_end_ptr;
-
 void error(char *x)
 {
-	fallback_arch_error(x);
+	if (arch_error_p)
+		arch_error_p(x);
 
 	putstr("\n\n");
 	putstr(x);
@@ -157,18 +157,11 @@ extern int do_decompress(u8 *input, int

 void
-decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
-		unsigned long free_mem_ptr_end_p,
-		int arch_id)
+decompress_kernel(unsigned long output_start)
 {
 	int ret;
 
-	output_data		= (unsigned char *)output_start;
-	free_mem_ptr		= free_mem_ptr_p;
-	free_mem_end_ptr	= free_mem_ptr_end_p;
-	__machine_arch_type	= arch_id;
-
-	fallback_arch_setup();
+	output_data = (unsigned char *)output_start;
 
 	putstr("Uncompressing Linux...");
 	ret = do_decompress(input_data, input_data_end - input_data,
Index: b/arch/arm/boot/compressed/head.S
===================================================================
--- a/arch/arm/boot/compressed/head.S
+++ b/arch/arm/boot/compressed/head.S
@@ -447,14 +447,14 @@ not_relocated:	mov	r0, #0
 /*
  * The C runtime environment should now be setup sufficiently.
  * Set up some pointers, and start decompressing.
- *   r4  = kernel execution address
- *   r7  = architecture ID
- *   r8  = atags pointer
  */
-		mov	r0, r4
-		mov	r1, sp			@ malloc space above stack
-		add	r2, sp, #0x10000	@ 64k max
-		mov	r3, r7
+
+		mov	r0, r7			@ architecture id
+		mov	r1, r8			@ atags or devicetree
+		mov	r2, sp			@ malloc space above stack
+		add	r3, sp, #0x10000	@ 64k max
+		bl	arch_setup
+		mov	r0, r4			@ kernel execution address
 		bl	decompress_kernel
 		bl	cache_clean_flush
 		bl	cache_off
Index: b/arch/arm/boot/compressed/arch.c
===================================================================
--- a/arch/arm/boot/compressed/arch.c
+++ b/arch/arm/boot/compressed/arch.c
@@ -13,10 +13,22 @@
  * GNU General Public License for more details.
  */
 
+unsigned int __machine_arch_type;
+
+#include <linux/compiler.h>
 #include <linux/types.h>
+#include <linux/linkage.h>
+#include <linux/string.h>
+
+#include <linux/decompress/arch.h>
 
 #include "arch.h"
 
+unsigned long free_mem_ptr;
+unsigned long free_mem_end_ptr;
+
+void (*arch_error_p)(char *x);
+
 /* defined in the linker script */
 extern void *__decomp_text_begin;
 extern void *__decomp_data_begin;
@@ -36,3 +48,44 @@ const void *fix_data_ptr(const void *p)
 
 	return ((void *) &__decomp_data_begin) + (p - __decomp_data_begin);
 }
+
+extern const struct decomp_tag_hdr __decomp_tags_begin[];
+extern const struct decomp_tag_hdr __decomp_tags_end[];
+
+#define for_each_decomp_tag(p)                  \
+	for ((p) = __decomp_tags_begin;         \
+	     (p) < __decomp_tags_end;           \
+	     p = ((void *) p) + (p)->size)
+
+void arch_setup(unsigned int arch_id, void *atag_fdt,
+		unsigned long free_mem_ptr_p,
+		unsigned long free_mem_ptr_end_p)
+{
+	struct decomp_arch_tag *arch_tag = NULL;
+	const struct decomp_tag_hdr *hdr;
+	void (*arch_setup_p)(void);
+
+	__machine_arch_type = arch_id;
+
+	arch_setup_p = fallback_arch_setup;
+	arch_error_p = fallback_arch_error;
+
+	free_mem_ptr = free_mem_ptr_p;
+	free_mem_end_ptr = free_mem_ptr_end_p;
+
+	for_each_decomp_tag(hdr) {
+		if (hdr->arch_id != arch_id)
+			continue;
+
+		if (hdr->type == DECOMP_TAG_ARCH) {
+			arch_tag = (struct decomp_arch_tag *) hdr;
+			arch_setup_p = fix_text_ptr(arch_tag->setup);
+			arch_error_p = fix_text_ptr(arch_tag->error);
+			break;
+		}
+	}
+
+	/* archs without decompressor setup have NULL here */
+	if (arch_setup_p)
+		arch_setup_p();
+}
Index: b/arch/arm/boot/compressed/arch.h
===================================================================
--- a/arch/arm/boot/compressed/arch.h
+++ b/arch/arm/boot/compressed/arch.h
@@ -16,6 +16,16 @@
 #ifndef ARM_BOOT_COMPRESSED_ARCH_H
 #define ARM_BOOT_COMPRESSED_ARCH_H
 
+extern unsigned int __machine_arch_type;
+
+extern unsigned long free_mem_ptr;
+extern unsigned long free_mem_end_ptr;
+
+extern void (*arch_error_p)(char *x);
+
+void fallback_arch_setup(void);
+void fallback_arch_error(char *x);
+
 const void *fix_text_ptr(const void *p);
 const void *fix_data_ptr(const void *p);