Compressed kernel image

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

for the embedded Linux project I'm working on I needed the
ability to boot a compressed kernel image from ROM, i.e.
uncompress it to RAM and jump to the kernel entry.

So I took some parts from arch/i386/boot/compressed,
and some from linux-mips.sf.net/arch/mips/zboot/ and
hacked up something which works for me.

The appended patch is incomplete as I removed anything
specific to my platform. To use it for a different platform
you would need:

- A head.S which works for your board. Depending on what
  your bootloader does, you probably don't need the
  cache flushing stuff.
- For debug output, you need a putc() implementation; maybe
  the included uart16550.c works for you. If you think you don't
  need to debug anything ;-), you could use a dummy putc().
- Set some configuration variables in the Makefile. I tested
  both booting from ROM and loading the zImage to RAM, then
  uncompressing to some different area in RAM.
  Note: For my platform I allocate a 2MB frame buffer at
  0x80002000 in unified memory, so reuse this space
  for uncompressing the zImage.

I hope the patch is of use to someone. Comments are welcome.


Regards,
Johannes
diff -urN linux-oss.orig/arch/mips/Makefile linux-oss/arch/mips/Makefile
--- linux-oss.orig/arch/mips/Makefile	2002-09-09 21:30:11.000000000 +0200
+++ linux-oss/arch/mips/Makefile	2002-09-16 14:55:40.000000000 +0200
@@ -475,6 +475,11 @@
 endif
 
 MAKEBOOT = $(MAKE) -C arch/$(ARCH)/boot
+MAKEZBOOT = $(MAKE) -C arch/$(ARCH)/boot/compressed
+
+BOOT_TARGETS = zImage
+$(BOOT_TARGETS):  vmlinux
+	@$(MAKEZBOOT) $@
 
 vmlinux.ecoff: vmlinux
 	@$(MAKEBOOT) $@
@@ -484,6 +489,7 @@
 	rm -f arch/$(ARCH)/ld.script
 	$(MAKE) -C arch/$(ARCH)/tools clean
 	$(MAKE) -C arch/mips/baget clean
+	$(MAKE) -C arch/$(ARCH)/boot/compressed clean
 
 archmrproper:
 	@$(MAKEBOOT) mrproper
diff -urN linux-oss.orig/arch/mips/boot/compressed/Makefile linux-oss/arch/mips/boot/compressed/Makefile
--- linux-oss.orig/arch/mips/boot/compressed/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ linux-oss/arch/mips/boot/compressed/Makefile	2002-09-16 15:01:13.000000000 +0200
@@ -0,0 +1,74 @@
+#
+# linux/arch/mips/boot/compressed/Makefile
+#
+# create a compressed zImage from the original vmlinux
+#
+
+HEAD = head.o
+SYSTEM = $(TOPDIR)/vmlinux
+
+ZLDFLAGS = -e zstartup -T $(TOPDIR)/arch/mips/ld.script
+
+all: $(TOPDIR)/zImage
+
+##################################################################
+# board configuration
+##################################################################
+
+ifdef CONFIG_FOOBAR // board specific
+
+# ZIMAGE_OFFSET is the load offset of the compression loader
+ifdef BOOT_FROM_ROM
+# zImage in ROM after boot code
+ZIMAGE_OFFSET := 0x9fc20000
+ZBSS          := -Tbss 0x80002000
+else
+# for loading in RAM
+ZIMAGE_OFFSET := 0x81000000
+endif
+
+# *MUST* match LOADADDR from arch/mips/Makefile
+LOADADDR      := 0x80202000
+KERNEL_ENTRY  = $(shell $(OBJDUMP) -f $(SYSTEM) | sed -n -e 's/^start address //p')
+# working space for gunzip:
+FREE_RAM      := 0x80080000
+END_RAM       := 0x80200000
+# putc config
+PUTC_IMPL     := uart16550.o
+uart16550.o: uart16550.c Makefile
+	$(CC) $(CFLAGS) -DBASE=0xb2001000 -DMAX_BAUD=1152000 -DREG_OFFSET=0x10 -c uart16550.c
+endif
+
+##################################################################
+
+OBJECTS = $(HEAD) misc.o $(PUTC_IMPL)
+
+ZLINKFLAGS = -Ttext $(ZIMAGE_OFFSET) $(ZBSS) $(ZLDFLAGS)
+
+.PHONY: zImage
+zImage: $(TOPDIR)/zImage
+
+$(TOPDIR)/zImage: piggy.o $(OBJECTS)
+	$(LD) $(ZLINKFLAGS) -o $(TOPDIR)/zImage $(OBJECTS) piggy.o
+
+head.o: head.S Makefile $(SYSTEM)
+	$(CC) $(AFLAGS) -DKERNEL_ENTRY=$(KERNEL_ENTRY) -c head.S
+
+comma	:= ,
+
+misc.o: misc.c Makefile
+	$(CC) $(CFLAGS) -DLOADADDR=$(LOADADDR) \
+	    -DFREE_RAM=$(FREE_RAM) -DEND_RAM=$(END_RAM) \
+	    -DKBUILD_BASENAME=$(subst $(comma),_,$(subst -,_,$(*F))) -c misc.c
+
+piggy.o:	$(SYSTEM)
+	tmppiggy=_tmp_$$$$piggy; \
+	rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk; \
+	$(OBJCOPY) -S -O binary -R .note -R .comment $(SYSTEM) $$tmppiggy; \
+	gzip -f -9 < $$tmppiggy > $$tmppiggy.gz; \
+	echo "SECTIONS { .data : { input_len = .; LONG(input_data_end - input_data) input_data = .; *(.data) input_data_end = .; }}" > $$tmppiggy.lnk; \
+	$(LD) -r -o piggy.o -b binary $$tmppiggy.gz -b elf32-tradbigmips -T $$tmppiggy.lnk; \
+	rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk
+
+clean:
+	rm -f $(TOPDIR)/zImage _tmp_* *.o
diff -urN linux-oss.orig/arch/mips/boot/compressed/head.S linux-oss/arch/mips/boot/compressed/head.S
--- linux-oss.orig/arch/mips/boot/compressed/head.S	1970-01-01 01:00:00.000000000 +0100
+++ linux-oss/arch/mips/boot/compressed/head.S	2002-09-16 15:11:48.000000000 +0200
@@ -0,0 +1,100 @@
+/*
+ * arch/mips/boot/compressed/head.S
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 1994, 1995 Waldorf Electronics
+ * Written by Ralf Baechle and Andreas Busse
+ * Copyright (C) 1995 - 1999 Ralf Baechle
+ * Copyright (C) 1996 Paul M. Antoine
+ * Modified for DECStation and hence R3000 support by Paul M. Antoine
+ * Further modifications by David S. Miller and Harald Koerfgen
+ * Copyright (C) 1999 Silicon Graphics, Inc.
+ *
+ * Head.S contains the MIPS exception handler and startup code.
+ *
+ **************************************************************************
+ *  9 Nov, 2000.
+ *  Added Cache Error exception handler and SBDDP EJTAG debug exception.
+ *
+ *  Kevin Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com
+ *  Copyright (C) 2000 MIPS Technologies, Inc.  All rights reserved.
+ **************************************************************************
+ * 08/2002 modified for zImage boot from ROM
+ * Johannes Stezenbach <js@convergence.de>
+ */
+
+
+#include <linux/config.h>
+#include <linux/threads.h>
+
+#include <asm/asm.h>
+#include <asm/cacheops.h>
+#include <asm/mipsregs.h>
+#include <asm/offset.h>
+#include <asm/cachectl.h>
+#include <asm/regdef.h>
+
+#define IndexInvalidate_I       0x00
+
+	.set noreorder
+	.cprestore
+	LEAF(zstartup)
+zstartup:
+
+        la      sp, .stack
+	move	s0, a0
+	move	s1, a1
+	move	s2, a2
+	move	s3, a3
+
+	/* Clear BSS */
+	/* Note: when zImage is in ROM, _edata and _bss point to
+	 * ROM space even when using -Tbss on the linker command line;
+	 * maybe ld.script needs to be corrected.
+	 */
+	la	a0, .stack
+	la	a2, _end
+1:	sw	zero, 0(a0)
+	bne	a2, a0, 1b
+	addu	a0, 4
+
+	/* flush the I-Cache */
+	li	k0, 0x80000000  # start address
+	li	k1, 0x80004000  # end address (16KB I-Cache)
+	subu	k1, 128
+
+2:
+	.set mips3
+	cache	IndexInvalidate_I, 0(k0)
+	cache	IndexInvalidate_I, 32(k0)
+	cache	IndexInvalidate_I, 64(k0)
+	cache	IndexInvalidate_I, 96(k0)
+	.set mips0
+
+	bne	k0, k1, 2b
+	addu	k0, k0, 128
+	/* done */
+
+	la	ra, 3f
+	la	k0, decompress_kernel
+	jr	k0
+	nop
+3:
+
+	move	a0, s0
+	move	a1, s1
+	move	a2, s2
+	move	a3, s3
+	li	k0, KERNEL_ENTRY
+	jr	k0
+	nop
+4:
+	b 4b
+	END(zstartup)
+
+	.bss
+	.fill 0x2000
+	EXPORT(.stack)
diff -urN linux-oss.orig/arch/mips/boot/compressed/misc.c linux-oss/arch/mips/boot/compressed/misc.c
--- linux-oss.orig/arch/mips/boot/compressed/misc.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-oss/arch/mips/boot/compressed/misc.c	2002-08-06 19:19:11.000000000 +0200
@@ -0,0 +1,311 @@
+/*
+ * arch/mips/boot/misc.c
+ *
+ * This is a collection of several routines from gzip-1.0.3
+ * adapted for Linux.
+ *
+ * malloc by Hannu Savolainen 1993 and Matthias Urlichs 1994
+ * puts by Nick Holloway 1993, better puts by Martin Mares 1995
+ * High loaded stuff by Hans Lermen & Werner Almesberger, Feb. 1996
+ *
+ * Modified by RidgeRun Inc.
+ *
+ * 08/2002 modified for booting zImage from ROM
+ * Johannes Stezenbach <js@convergence.de>
+ * (All statically initialized data is read only, all data which needs to be
+ * in RAM must not be statically initialized.)
+ */
+
+#define ZDEBUG 1
+
+#include <linux/types.h>
+
+/*
+ * gzip declarations
+ */
+#define OF(args)  args
+#define STATIC static
+#define memzero(s, n)     memset ((s), 0, (n))
+typedef unsigned char uch;
+typedef unsigned short ush;
+typedef unsigned long ulg;
+#define WSIZE 0x8000		/* Window size must be at least 32k, */
+				/* and a power of two */
+static uch *inbuf;		/* input buffer */
+static uch window[WSIZE];	/* Sliding window buffer */
+
+/* gzip flag byte */
+#define ASCII_FLAG   0x01	/* bit 0 set: file probably ASCII text */
+#define CONTINUATION 0x02	/* bit 1 set: continuation of multi-part gzip file */
+#define EXTRA_FIELD  0x04	/* bit 2 set: extra field present */
+#define ORIG_NAME    0x08	/* bit 3 set: original file name present */
+#define COMMENT      0x10	/* bit 4 set: file comment present */
+#define ENCRYPTED    0x20	/* bit 5 set: file is encrypted */
+#define RESERVED     0xC0	/* bit 6,7:   reserved */
+
+
+static unsigned insize;	/* valid bytes in inbuf */
+static unsigned inptr;	/* index of next byte to be processed in inbuf */
+static unsigned outcnt;	/* bytes in output buffer */
+
+void variable_init(void);
+static void puts(const char *);
+extern void putc_init(void);
+extern void putc(unsigned char c);
+static int fill_inbuf(void);
+static void flush_window(void);
+static void error(char *m);
+static void gzip_mark(void **);
+static void gzip_release(void **);
+
+extern char input_data[];
+extern int input_len;
+
+
+void int2hex(unsigned long val)
+{
+        unsigned char buf[10];
+        int i;
+        for (i = 7;  i >= 0;  i--)
+        {
+                buf[i] = "0123456789ABCDEF"[val & 0x0F];
+                val >>= 4;
+        }
+        buf[8] = '\0';
+        puts(buf);
+}
+
+static unsigned long byte_count;
+
+int get_byte(void)
+{
+#if ZDEBUG > 1
+	static int printCnt;
+#endif
+	unsigned char c = (inptr < insize ? inbuf[inptr++] : fill_inbuf());
+	byte_count++;
+
+#if ZDEBUG > 1
+	if (printCnt++ < 32)
+	{
+	  puts("byte count = ");
+	  int2hex(byte_count);
+	  puts(" byte val = ");
+	  int2hex(c);
+	  puts("\n");
+	}
+#endif
+	return c;
+}
+
+/* Diagnostic functions */
+#ifdef DEBUG
+#  define Assert(cond,msg) {if(!(cond)) error(msg);}
+#  define Trace(x) fprintf x
+#  define Tracev(x) {if (verbose) fprintf x ;}
+#  define Tracevv(x) {if (verbose>1) fprintf x ;}
+#  define Tracec(c,x) {if (verbose && (c)) fprintf x ;}
+#  define Tracecv(c,x) {if (verbose>1 && (c)) fprintf x ;}
+#else
+#  define Assert(cond,msg)
+#  define Trace(x)
+#  define Tracev(x)
+#  define Tracevv(x)
+#  define Tracec(c,x)
+#  define Tracecv(c,x)
+#endif
+
+/*
+ * This is set up by the setup-routine at boot-time
+ */
+
+static long bytes_out;
+static uch *output_data;
+static unsigned long output_ptr;
+
+
+static void *malloc(int size);
+static void free(void *where);
+static void error(char *m);
+static void gzip_mark(void **);
+static void gzip_release(void **);
+
+static unsigned long free_mem_ptr;
+static unsigned long free_mem_end_ptr;
+
+#include "../../../../lib/inflate.c"
+
+static void *malloc(int size)
+{
+	void *p;
+
+	if (size < 0)
+		error("Malloc error\n");
+	if (free_mem_ptr <= 0) error("Memory error\n");
+
+	free_mem_ptr = (free_mem_ptr + 3) & ~3;	/* Align */
+
+	p = (void *) free_mem_ptr;
+	free_mem_ptr += size;
+
+	if (free_mem_ptr >= free_mem_end_ptr)
+		error("\nOut of memory\n");
+
+	return p;
+}
+
+static void free(void *where)
+{				/* Don't care */
+}
+
+static void gzip_mark(void **ptr)
+{
+	*ptr = (void *) free_mem_ptr;
+}
+
+static void gzip_release(void **ptr)
+{
+	free_mem_ptr = (long) *ptr;
+}
+
+static void puts(const char *s)
+{
+	while (*s) {
+		if (*s == 10)
+			putc(13);
+		putc(*s++);
+	}
+}
+
+void *memset(void *s, int c, size_t n)
+{
+	int i;
+	char *ss = (char *) s;
+
+	for (i = 0; i < n; i++)
+		ss[i] = c;
+	return s;
+}
+
+void *memcpy(void *__dest, __const void *__src, size_t __n)
+{
+	int i;
+	char *d = (char *) __dest, *s = (char *) __src;
+
+	for (i = 0; i < __n; i++)
+		d[i] = s[i];
+	return __dest;
+}
+
+/* ===========================================================================
+ * Fill the input buffer. This is called only when the buffer is empty
+ * and at least one byte is really needed.
+ */
+static int fill_inbuf(void)
+{
+	if (insize != 0) {
+		error("ran out of input data\n");
+	}
+
+	inbuf = input_data;
+	insize = input_len;
+	inptr = 1;
+	return inbuf[0];
+}
+
+/* ===========================================================================
+ * Write the output window window[0..outcnt-1] and update crc and bytes_out.
+ * (Used for the decompressed data only.)
+ */
+static void flush_window(void)
+{
+	ulg c = crc;		/* temporary variable */
+	unsigned n;
+	uch *in, *out, ch;
+
+	in = window;
+	out = &output_data[output_ptr];
+	for (n = 0; n < outcnt; n++) {
+		ch = *out++ = *in++;
+		c = crc_32_tab[((int) c ^ ch) & 0xff] ^ (c >> 8);
+	}
+	crc = c;
+	bytes_out += (ulg) outcnt;
+	output_ptr += (ulg) outcnt;
+	outcnt = 0;
+}
+
+void check_mem(void)
+{
+	int i;
+
+	puts("\ncplens = ");
+	for (i = 0; i < 10; i++) {
+		int2hex(cplens[i]);
+		puts(" ");
+	}
+	puts("\ncplext = ");
+	for (i = 0; i < 10; i++) {
+		int2hex(cplext[i]);
+		puts(" ");
+	}
+	puts("\nborder = ");
+	for (i = 0; i < 10; i++) {
+		int2hex(border[i]);
+		puts(" ");
+	}
+	puts("\n");
+}
+
+static void error(char *x)
+{
+	check_mem();
+	puts("\n\n");
+	puts(x);
+	puts("byte_count = ");
+	int2hex(byte_count);
+	puts("\n");
+	puts("\n\n -- System halted");
+	while (1);		/* Halt */
+}
+
+void variable_init(void)
+{
+	byte_count = 0;
+	output_data = (char *) LOADADDR;
+	free_mem_ptr = FREE_RAM;
+	free_mem_end_ptr = END_RAM;
+#if ZDEBUG
+	puts("variable_init:\n");
+	int2hex((unsigned long)output_data); puts("\n");
+	int2hex(free_mem_ptr); puts("\n");
+	int2hex(free_mem_end_ptr); puts("\n");
+	int2hex((unsigned long)input_data); puts("\n");
+	int2hex(input_len); puts("\n");
+#endif
+}
+
+int decompress_kernel(void)
+{
+#if ZDEBUG
+	//check_mem();
+	unsigned long *p = (unsigned long *)LOADADDR;
+#endif
+
+	putc_init();
+	variable_init();
+
+	makecrc();
+	puts("Uncompressing Linux... \n");
+	gunzip();		// ...see inflate.c
+	puts("Ok, booting the kernel.\n");
+
+#if ZDEBUG
+	int2hex(p[0]); puts("\n");
+	int2hex(p[1]); puts("\n");
+	int2hex(p[2]); puts("\n");
+	int2hex(p[3]); puts("\n");
+#endif
+
+	return 0;
+}
diff -urN linux-oss.orig/arch/mips/boot/compressed/uart16550.c linux-oss/arch/mips/boot/compressed/uart16550.c
--- linux-oss.orig/arch/mips/boot/compressed/uart16550.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-oss/arch/mips/boot/compressed/uart16550.c	2002-09-16 15:16:38.000000000 +0200
@@ -0,0 +1,141 @@
+/*
+ * 16550 uart routines.
+ * based on something similar to arch/mips/vr4181/osprey/dbg_io.c
+ * Copyright (C) 2001 MontaVista Software Inc.
+ * Author: jsun@mvista.com or jsun@junsun.net
+ */
+#include <linux/config.h>
+#include <asm/io.h>
+
+/* === CONFIG === */
+
+/*
+ * #define BASE			0xb2001000
+ * #define MAX_BAUD		1152000
+ * #define REG_OFFSET		0x10
+ */
+#if (!defined(BASE) || !defined(MAX_BAUD) || !defined(REG_OFFSET))
+#error You must define BASE, MAX_BAUD and REG_OFFSET in the Makefile.
+#endif
+
+#ifndef INIT_SERIAL_PORT
+#define INIT_SERIAL_PORT	1
+#endif
+
+#ifndef DEFAULT_BAUD
+#define DEFAULT_BAUD		UART16550_BAUD_115200
+#endif
+#ifndef DEFAULT_PARITY
+#define DEFAULT_PARITY		UART16550_PARITY_NONE
+#endif
+#ifndef DEFAULT_DATA
+#define DEFAULT_DATA		UART16550_DATA_8BIT
+#endif
+#ifndef DEFAULT_STOP
+#define DEFAULT_STOP		UART16550_STOP_1BIT
+#endif
+
+/* === END OF CONFIG === */
+
+typedef         unsigned char uint8;
+typedef         unsigned int  uint32;
+
+#define         UART16550_BAUD_2400             2400
+#define         UART16550_BAUD_4800             4800
+#define         UART16550_BAUD_9600             9600
+#define         UART16550_BAUD_19200            19200
+#define         UART16550_BAUD_38400            38400
+#define         UART16550_BAUD_57600            57600
+#define         UART16550_BAUD_115200           115200
+
+#define         UART16550_PARITY_NONE           0
+#define         UART16550_PARITY_ODD            0x08
+#define         UART16550_PARITY_EVEN           0x18
+#define         UART16550_PARITY_MARK           0x28
+#define         UART16550_PARITY_SPACE          0x38
+
+#define         UART16550_DATA_5BIT             0x0
+#define         UART16550_DATA_6BIT             0x1
+#define         UART16550_DATA_7BIT             0x2
+#define         UART16550_DATA_8BIT             0x3
+
+#define         UART16550_STOP_1BIT             0x0
+#define         UART16550_STOP_2BIT             0x4
+
+/* register offset */
+#define		OFS_RCV_BUFFER		(0*REG_OFFSET)
+#define		OFS_TRANS_HOLD		(0*REG_OFFSET)
+#define		OFS_SEND_BUFFER		(0*REG_OFFSET)
+#define		OFS_INTR_ENABLE		(1*REG_OFFSET)
+#define		OFS_INTR_ID		(2*REG_OFFSET)
+#define		OFS_DATA_FORMAT		(3*REG_OFFSET)
+#define		OFS_LINE_CONTROL	(3*REG_OFFSET)
+#define		OFS_MODEM_CONTROL	(4*REG_OFFSET)
+#define		OFS_RS232_OUTPUT	(4*REG_OFFSET)
+#define		OFS_LINE_STATUS		(5*REG_OFFSET)
+#define		OFS_MODEM_STATUS	(6*REG_OFFSET)
+#define		OFS_RS232_INPUT		(6*REG_OFFSET)
+#define		OFS_SCRATCH_PAD		(7*REG_OFFSET)
+
+#define		OFS_DIVISOR_LSB		(0*REG_OFFSET)
+#define		OFS_DIVISOR_MSB		(1*REG_OFFSET)
+
+#define		UART16550_READ(y)    (*((volatile uint8*)(BASE + y)))
+#define		UART16550_WRITE(y, z)  ((*((volatile uint8*)(BASE + y))) = z)
+
+static void Uart16550Init(uint32 baud, uint8 data, uint8 parity, uint8 stop)
+{
+	/* disable interrupts */
+	UART16550_WRITE(OFS_LINE_CONTROL, 0x0);
+	UART16550_WRITE(OFS_INTR_ENABLE, 0);
+
+	/* set up baud rate */
+	{
+		uint32 divisor;
+
+		/* set DIAB bit */
+		UART16550_WRITE(OFS_LINE_CONTROL, 0x80);
+
+		/* set divisor */
+		divisor = MAX_BAUD / baud;
+		UART16550_WRITE(OFS_DIVISOR_LSB, divisor & 0xff);
+		UART16550_WRITE(OFS_DIVISOR_MSB, (divisor & 0xff00)>>8);
+
+		/* clear DIAB bit */
+		UART16550_WRITE(OFS_LINE_CONTROL, 0x0);
+	}
+
+	/* set data format */
+	UART16550_WRITE(OFS_DATA_FORMAT, data | parity | stop);
+}
+
+
+void
+putc_init(void)
+{
+#if INIT_SERIAL_PORT
+	Uart16550Init(DEFAULT_BAUD, DEFAULT_DATA, DEFAULT_PARITY, DEFAULT_STOP);
+#endif
+}
+
+void
+putc(unsigned char c)
+{
+	while ((UART16550_READ(OFS_LINE_STATUS) &0x20) == 0);
+	UART16550_WRITE(OFS_SEND_BUFFER, c);
+}
+
+#if 0
+unsigned char
+getc(void)
+{
+	while((UART16550_READ(OFS_LINE_STATUS) & 0x1) == 0);
+	return UART16550_READ(OFS_RCV_BUFFER);
+}
+
+int
+tstc(void)
+{
+	return((UART16550_READ(OFS_LINE_STATUS) & 0x01) != 0);
+}
+#endif

[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux