ROM disk driver for 2.0.32 (patch!)

Oliver Xymoron (oxymoron@waste.org)
Tue, 25 Nov 1997 22:12:19 -0600 (CST)


This is the ROM disk driver that I've been tinkering with. This is a block
device that uses data directly linked into the driver. Together with
romfs, this allows you to make a minimal kernel that can boot with just a
zImage! This should prove useful for embedded systems.

Features:
- low fat - the driver code and a minimal bootable image take less
than a page
- works as a module
- can work with RLE-compressed disk images to (slightly) reduce memory
requirements
- might work on non-intel systems
- completely experimental
- uses an unregistered major number!

diff -urpN linux-orig/Documentation/Configure.help linux/Documentation/Configure.help
--- linux-orig/Documentation/Configure.help Fri Sep 5 22:43:58 1997
+++ linux/Documentation/Configure.help Mon Nov 24 10:12:49 1997
@@ -112,6 +112,13 @@ CONFIG_BLK_DEV_INITRD
"real" root file system, etc. See Documentation/initrd.txt for
details.

+ROM disk support
+CONFIG_BLK_DEV_ROM
+ This allows you to embed a read-only filesystem image directly in the
+ kernel image. Combined with ROMFS, you can use this to create a minimal
+ kernel image that contains its own boot filesystem. See Documentation/rom.txt
+ for details. Most users should say N here.
+
Loop device support
CONFIG_BLK_DEV_LOOP
Enabling this option will allow you to mount a file as a file
diff -urpN linux-orig/Documentation/rom.txt linux/Documentation/rom.txt
--- linux-orig/Documentation/rom.txt Wed Dec 31 18:00:00 1969
+++ linux/Documentation/rom.txt Tue Nov 25 16:46:31 1997
@@ -0,0 +1,319 @@
+Using ROM disk support
+======================
+
+oxymoron@waste.org 11/97
+
+= Introduction =
+
+ROM disk allows you to embed a read-only filesystem image directly in
+the kernel image. Combined with ROMFS, you can use this to create a
+minimal kernel image that contains its own boot filesystem. This is
+especially useful for embedded systems that have a minimum of memory
+and no disk drives. The driver itself is very small and supports
+run-length encoding of disk images, allowing the creation of boot
+images as small as one page!
+
+The ROM disk driver works by building the filesystem data into an
+array that is linked into the kernel. This array is represented as C
+source code in drivers/block/romdisk.inc and can be built from an
+existing disk image with the genromdisk perl script attached to the
+end of this file.
+
+= Building an image =
+
+To create a ROM disk image, first you must create a filesystem image
+in another device or file. This can be done for instance with the genromfs
+package. You can then use the attached perl script to create the romdisk.inc
+source file:
+
+ perl genromdisk < diskimage > drivers/block/romdisk.inc
+
+Then rebuild your kernel with ROM disk and appropriate filesystem
+support (probably ROMFS). You will also want to create the appropriate
+/dev entry for the device:
+
+ mknod /dev/rom0 b 60 0
+
+To build a bootable ROM disk image, you will need to build a
+filesystem with at least the following files:
+
+ /dev
+ console
+ /etc
+ init
+
+The following is the source for a minimal statically linked init:
+
+----init.c
+/*
+ minimal init - oxymoron@waste.org
+ compile with gcc -s -static init.c -nostdlib -lc -o init
+*/
+#include <unistd.h>
+#include <fcntl.h>
+
+main() {
+ int fd;
+ fd=open("/dev/console",O_RDWR);
+ write(fd,"Init!\n",7);
+ for(;;);
+}
+----
+
+= Image Format =
+
+The image array include an 8 byte header, the first 4 bytes of which
+indicate the size of the data, and the second 4 indicating encoding
+format. All multibyte values are in network order.
+
+Encoding format 0 is uncompressed and the remainder of the array
+consists of raw data bytes of the specified size.
+
+Encoding format 1 is run length encoded (RLE). Directly following the
+header is an table of 4 byte offsets, one entry for each block in the
+uncompressed image. These offsets are the number of bytes from the
+beginning of the table to the start of the compressed data in the
+image for a given block and reduce the overhead of decompression for
+random access.
+
+The RLE compressed data begins immediately after the table. Run length
+encoding replaces runs of identical bytes with an escape byte,
+followed by a one byte repeat count, followed by the character to be
+repeated. Escape bytes in the input are encoded as an escape byte and
+a zero count in the output. Other single bytes are written directly.
+
+--- genromdisk ---
+#!/usr/bin/perl
+$blocksize=1024;
+$escape=chr 167;
+
+while(<STDIN>)
+{
+ $disk.=$_;
+}
+
+$size=length($disk);
+if($size%$blocksize)
+{
+ $diff=($blocksize-($size%$blocksize));
+ $disk.="\0"x$diff;
+ $size=length($disk);
+ warn "Padded $diff bytes to $size\n";
+}
+
+$blocknum=0;
+
+warn "Compressing...\n";
+
+while($pos<$size)
+{
+ $pos[$blocknum]=length $cdisk;
+ $block=substr($disk,$pos,$blocksize);
+
+ $s=length($block);
+
+# warn "Block $blocknum pos $pos length $s\n";
+
+ $last="foo";
+ $run=0;
+ $i=0;
+ while($i<$blocksize)
+ {
+ $c=substr($block,$i,1);
+ $a=ord $c;
+ $hist{$a}++;
+ if($c eq $last)
+ {
+ $run++;
+ if($run eq 255)
+ {
+ $runs++;
+ $runhist{$a}++;
+ $runlength{$run-3}++;
+ $cdisk.=pack("ACA",$escape,$run,$c);
+ $run=0;
+ }
+ }
+ else
+ {
+ if($run)
+ {
+ if($last eq $escape)
+ {
+ $overhead++;
+ if($run==1)
+ {
+ $cdisk.=$escape."\0";
+ }
+ if($run>1)
+ {
+ if($run>3)
+ {
+ $runhist{ord $escape}++;
+ $runlength{$run-3}++;
+ $runs++;
+ }
+ $cdisk.=pack("ACA",$escape,$run,$escape);
+ }
+ }
+ elsif($run == 1)
+ {
+ $cdisk.=$last;
+ }
+ elsif($run<4)
+ {
+ $cdisk.=$last x $run;
+ }
+ elsif($run>3)
+ {
+ $runs++;
+ $runhist{ord $last}++;
+ $runlength{$run-3}++;
+ $cdisk.=pack("ACA",$escape,$run,$last);
+ }
+ }
+ $last=$c;
+ $run=1;
+ }
+ $i++;
+ }
+
+ if($run)
+ {
+ if($last eq $escape)
+ {
+ $overhead++;
+ if($run==1)
+ {
+ $cdisk.=$escape x 2;
+ }
+ if($run>1)
+ {
+ if($run>3)
+ {
+ $runhist{ord $escape}++;
+ $runlength{$run-3}++;
+ $runs++;
+ }
+ $cdisk.=pack("ACA",$escape,$run,$escape);
+ }
+ }
+ elsif($run == 1)
+ {
+ $cdisk.=$last;
+ }
+ elsif($run<4)
+ {
+ $cdisk.=$last x $run;
+ }
+ elsif($run>3)
+ {
+ $runs++;
+ $runhist{ord $last}++;
+ $runlength{$run}++;
+ $cdisk.=pack("ACA",$escape,$run,$last);
+ }
+ }
+
+ $pos+=$blocksize;
+ $blocknum++;
+}
+
+$offset=$blocknum*4;
+
+#open(DUMP,">cdisk");
+#print DUMP $cdisk;
+#close DUMP;
+
+foreach $pos (@pos)
+{
+# warn "$pos\n";
+ $blockmap.=pack("N",$pos+$offset);
+}
+
+$cdisk=$blockmap.$cdisk;
+
+print "/*\n";
+print "Escape: ",ord $escape," Block size: $blocksize\n";
+print "Input hist:\n";
+foreach $a (sort {$hist{$b}<=>$hist{$a}} keys %hist)
+{
+ print "$a\t$hist{$a}\n";
+}
+
+print "Runchar hist:\n";
+foreach $a (sort {$runhist{$b}<=>$runhist{$a}} keys %runhist)
+{
+ print "$a\t$runhist{$a}\n";
+}
+
+print "Runlength hist:\n";
+foreach $a (sort {$runlength{$b}*$b<=>$runlength{$a}*$a} keys %runlength)
+{
+ print "$a\t$runlength{$a} (",$runlength{$a}*$a,")\n";
+}
+
+print "*/\n";
+
+warn "Original size: ",length $disk," compressed: ",length $cdisk," (o: $overhead r: $runs)\n";
+
+if(length $cdisk < length $disk)
+{
+ $type=1;
+ $disk=$cdisk;
+}
+
+$disk=pack("NN",$size,$type).$disk;
+$size=length $disk;
+
+warn "Encoding ($size bytes)...\n";
+
+print "const unsigned char romdisk[$size]=\n";
+
+$i=0;
+while($i<$size)
+{
+ $c=substr($disk,$i,1);
+
+ $a=ord $c;
+
+ if($c eq "\\")
+ {
+ $l.='\\';
+ }
+ if($c eq "\"")
+ {
+ $l.='\"';
+ }
+ elsif($a>31&&$a<127)
+ {
+ $l.=$c;
+ }
+ elsif($a==13)
+ {
+ $l.="\\r";
+ }
+ elsif($a==10)
+ {
+ $l.="\\n";
+ }
+ else
+ {
+ $l.="\\".int($a/64).int(($a%64)/8).int($a%8);
+ }
+
+ $i++;
+ if(length $l>74||$i==$size)
+ {
+ print "\"$l\"";
+ $l="";
+ print ";" if $i==$size;
+ print "\n";
+ }
+}
+
+
+
+
+
+
diff -urpN linux-orig/drivers/block/Config.in linux/drivers/block/Config.in
--- linux-orig/drivers/block/Config.in Mon Aug 4 13:45:55 1997
+++ linux/drivers/block/Config.in Fri Nov 21 16:40:17 1997
@@ -51,7 +51,7 @@ if [ "$CONFIG_BLK_DEV_RAM" = "y" ]; then
bool ' Initial RAM disk (initrd) support' CONFIG_BLK_DEV_INITRD
fi
tristate 'XT harddisk support' CONFIG_BLK_DEV_XD
-
+tristate 'ROM block device support' CONFIG_BLK_DEV_ROM

if [ "$CONFIG_BLK_DEV_HD_IDE" = "y" -o "$CONFIG_BLK_DEV_HD_ONLY" = "y" ]; then
define_bool CONFIG_BLK_DEV_HD y
diff -urpN linux-orig/drivers/block/Makefile linux/drivers/block/Makefile
--- linux-orig/drivers/block/Makefile Mon Aug 4 13:45:55 1997
+++ linux/drivers/block/Makefile Fri Nov 21 16:44:21 1997
@@ -45,6 +45,30 @@ else
endif
endif

+ifeq ($(CONFIG_BLK_DEV_ROM),y)
+L_OBJS += rom.o
+else
+ ifeq ($(CONFIG_BLK_DEV_ROM),m)
+ M_OBJS += rom.o
+ endif
+endif
+
+ifeq ($(CONFIG_BLK_DEV_ROM),y)
+L_OBJS += rom.o
+else
+ ifeq ($(CONFIG_BLK_DEV_ROM),m)
+ M_OBJS += rom.o
+ endif
+endif
+
+ifeq ($(CONFIG_BLK_DEV_ROM),y)
+L_OBJS += rom.o
+else
+ ifeq ($(CONFIG_BLK_DEV_ROM),m)
+ M_OBJS += rom.o
+ endif
+endif
+
ifeq ($(CONFIG_BLK_DEV_HD),y)
L_OBJS += hd.o
endif
diff -urpN linux-orig/drivers/block/ll_rw_blk.c linux/drivers/block/ll_rw_blk.c
--- linux-orig/drivers/block/ll_rw_blk.c Wed Feb 26 13:10:15 1997
+++ linux/drivers/block/ll_rw_blk.c Fri Nov 21 16:40:17 1997
@@ -684,5 +684,8 @@ int blk_dev_init(void)
#ifdef CONFIG_BLK_DEV_MD
md_init();
#endif CONFIG_BLK_DEV_MD
+#ifdef CONFIG_BLK_DEV_ROM
+ rom_init();
+#endif
return 0;
}
diff -urpN linux-orig/drivers/block/rom.c linux/drivers/block/rom.c
--- linux-orig/drivers/block/rom.c Wed Dec 31 18:00:00 1969
+++ linux/drivers/block/rom.c Tue Nov 25 16:46:56 1997
@@ -0,0 +1,326 @@
+/*
+ * rom.c v0.1 oxymoron@waste.org 11/97
+ *
+ * Based largely on:
+ * linux/drivers/block/loop.c
+ * Copyright 1993 by Theodore Ts'o. Redistribution of this file is
+ * permitted under the GNU Public License.
+ */
+
+#include <linux/module.h>
+
+#include <linux/config.h>
+#include <linux/fs.h>
+#include <linux/stat.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/string.h>
+
+#include <asm/segment.h>
+#include <asm/byteorder.h>
+
+#include <linux/rom.h>
+
+#define DEBUG_BLKDEV_ROM
+
+#define MAJOR_NR 60
+
+#define DEVICE_NAME "rom"
+#define DEVICE_REQUEST do_rom_request
+#define DEVICE_NR(device) (MINOR(device))
+#define DEVICE_ON(device)
+#define DEVICE_OFF(device)
+#define DEVICE_NO_RANDOM
+#define TIMEOUT_VALUE (6 * HZ)
+#include <linux/blk.h>
+
+#define MAX_ROM 8
+static struct rom_device rom_dev[MAX_ROM];
+static int rom_sizes[MAX_ROM];
+static int rom_blksizes[MAX_ROM];
+
+#define ESCAPE 167
+#define BLOCKSIZE 1024
+
+#include "romdisk.inc"
+
+static void do_rom_request(void)
+{
+ unsigned int minor;
+ int offset, len;
+ struct rom_device *rom;
+ int block,boffset,run,c;
+ const unsigned char *data;
+ unsigned char *dest;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk("do_rom_request\n");
+#endif
+
+repeat:
+ INIT_REQUEST;
+
+ minor=MINOR(CURRENT->rq_dev);
+
+ if ((minor >= MAX_ROM) ||
+ (CURRENT->cmd != READ)) {
+#ifdef DEBUG_BLKDEV_ROM
+ printk(" bad minor or not read\n");
+#endif
+ end_request(0);
+ goto repeat;
+ }
+
+ rom = &rom_dev[minor];
+
+ offset = CURRENT->sector << 9;
+ len = CURRENT->current_nr_sectors << 9;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk(" %x(%d)\n",offset,len);
+#endif
+
+ if((offset+len) > rom->length) {
+#ifdef DEBUG_BLKDEV_ROM
+ printk(" too long\n");
+#endif
+ end_request(0);
+ goto repeat;
+ }
+
+ if(!rom->rle) {
+ memcpy(CURRENT->buffer,rom->data+offset,len);
+ }
+ else {
+ dest=CURRENT->buffer;
+
+ block=offset/BLOCKSIZE; /* Locate encoded block */
+ boffset=offset%BLOCKSIZE;
+ data=rom->data+ntohl(*(long *)(rom->data+(block*4)));
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk(" rle blk %d off %d data %p\n",block,boffset,data);
+#endif
+
+ while(boffset>0) {
+ if(*data != ESCAPE) { /* Normal character */
+ boffset--; /* We've skipped a char */
+ }
+ else { /* ESCAPE indicates RLE */
+ data++;
+ run=*data;
+ if(run) { /* Run count? */
+ data++;
+ c=(int)*data;
+ boffset-=run;
+ }
+ }
+ data++;
+ }
+
+ if(boffset<0) { /* Did an RLE span take us too far? */
+ boffset=-boffset>len?len:-boffset;
+ memset(dest,c,boffset);
+ len+=boffset;
+ dest+=boffset;
+ }
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk(" cpy off %d len %d data %p\n",boffset,len,data);
+#endif
+
+ while(len) {
+ if(*data!=ESCAPE) {
+ *dest++=*data;
+ len--;
+ }
+ else { /* RLE */
+ data++;
+ run=*data;
+ if(run) {
+ data++;
+ run=run>len?len:run;
+ memset(dest,*data,run);
+ dest+=run;
+ len-=run;
+ }
+ else {
+ *dest++=ESCAPE;
+ len--;
+ }
+ }
+ data++;
+ }
+ }
+
+ end_request(1);
+ goto repeat;
+}
+
+static int rom_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct rom_device *rom;
+ int dev, err;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk("rom_ioctl: %d:%d cmd %d arg %lx\n",MAJOR(inode->i_rdev),
+ MINOR(inode->i_rdev), cmd, arg);
+#endif
+
+ if (!inode)
+ return -EINVAL;
+ if (MAJOR(inode->i_rdev) != MAJOR_NR) {
+ printk("rom_ioctl: pseudo-major != %d\n", MAJOR_NR);
+ return -ENODEV;
+ }
+ dev = MINOR(inode->i_rdev);
+ if (dev >= MAX_ROM)
+ return -ENODEV;
+ rom = &rom_dev[dev];
+ switch (cmd) {
+ case BLKGETSIZE: /* Return device size */
+ if (!arg) return -EINVAL;
+ err = verify_area(VERIFY_WRITE, (long *) arg, sizeof(long));
+ if (err)
+ return err;
+ put_fs_long((rom->length)>>9, (long *) arg);
+#ifdef DEBUG_BLKDEV_ROM
+ printk(" BLKGETSIZE= %ld\n",(rom->length)>>9);
+#endif
+
+ return 0;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int rom_open(struct inode *inode, struct file *file)
+{
+ struct rom_device *rom;
+ int dev;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk("rom_open: %d\n",MINOR(inode->i_rdev));
+#endif
+
+ if (!inode)
+ return -EINVAL;
+ if (MAJOR(inode->i_rdev) != MAJOR_NR) {
+ printk("rom_open: pseudo-major != %d\n", MAJOR_NR);
+ return -ENODEV;
+ }
+ dev = MINOR(inode->i_rdev);
+ if (dev >= MAX_ROM)
+ return -ENODEV;
+ rom = &rom_dev[dev];
+ rom->rom_refcnt++;
+ MOD_INC_USE_COUNT;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk(" rom %d: %p (%ld) (%d)\n",
+ dev,rom->data,rom->length,rom->rom_refcnt);
+#endif
+ return 0;
+}
+
+static void rom_release(struct inode *inode, struct file *file)
+{
+ struct rom_device *rom;
+ int dev;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk("rom_release: %d\n",MINOR(inode->i_rdev));
+#endif
+
+ if (!inode)
+ return;
+ if (MAJOR(inode->i_rdev) != MAJOR_NR) {
+ printk("rom_release: pseudo-major != %d\n", MAJOR_NR);
+ return;
+ }
+ dev = MINOR(inode->i_rdev);
+ if (dev >= MAX_ROM)
+ return;
+ fsync_dev(inode->i_rdev);
+ rom = &rom_dev[dev];
+ if (rom->rom_refcnt <= 0)
+ printk("rom_release: refcount(%d) <= 0\n", rom->rom_refcnt);
+ else {
+ rom->rom_refcnt--;
+ MOD_DEC_USE_COUNT;
+ }
+}
+
+static struct file_operations rom_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ rom_ioctl, /* ioctl */
+ NULL, /* mmap */
+ rom_open, /* open */
+ rom_release /* release */
+};
+
+/*
+ * And now the modules code and kernel interface.
+ */
+#ifdef MODULE
+#define rom_init init_module
+#endif
+
+int
+rom_init( void ) {
+ int i;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk("*** rom_init\n");
+#endif
+
+ if (register_blkdev(MAJOR_NR, "rom", &rom_fops)) {
+ printk("Unable to get major number %d for rom device\n",
+ MAJOR_NR);
+ return -EIO;
+ }
+#ifndef MODULE
+ printk("rom: registered device at major %d\n", MAJOR_NR);
+#endif
+
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+
+ for (i=0; i < MAX_ROM; i++) {
+ memset(&rom_dev[i], 0, sizeof(struct rom_device));
+ rom_sizes[i]=0;
+ rom_blksizes[i]=BLOCKSIZE;
+ }
+
+ blk_size[MAJOR_NR] = rom_sizes;
+ blksize_size[MAJOR_NR] = rom_blksizes;
+
+ rom_dev[0].length=ntohl(*(long *)romdisk);
+ rom_dev[0].rle=(int)ntohl(*(long *)(romdisk+4));
+ rom_dev[0].data=romdisk+8;
+ rom_sizes[0]=rom_dev[0].length/BLOCKSIZE;
+
+#ifdef DEBUG_BLKDEV_ROM
+ printk("romdisk: %p\n",romdisk);
+ printk("rom_dev[0]: loc %p data %p length %ld\n",
+ &rom_dev[0],rom_dev[0].data,rom_dev[0].length);
+#endif
+
+ return 0;
+}
+
+#ifdef MODULE
+void
+cleanup_module( void ) {
+#ifdef DEBUG_BLKDEV_ROM
+ printk("rom: cleanup_module\n");
+#endif
+ if (unregister_blkdev(MAJOR_NR, "rom") != 0)
+ printk("rom: cleanup_module failed\n");
+}
+#endif
diff -urpN linux-orig/drivers/block/romdisk.inc linux/drivers/block/romdisk.inc
--- linux-orig/drivers/block/romdisk.inc Wed Dec 31 18:00:00 1969
+++ linux/drivers/block/romdisk.inc Tue Nov 25 16:47:35 1997
@@ -0,0 +1 @@
+#error "You haven't created a romdisk! See Documentation/rom.txt"
diff -urpN linux-orig/include/linux/rom.h linux/include/linux/rom.h
--- linux-orig/include/linux/rom.h Wed Dec 31 18:00:00 1969
+++ linux/include/linux/rom.h Tue Nov 25 16:33:12 1997
@@ -0,0 +1,15 @@
+#ifndef _LINUX_ROM_H
+#define _LINUX_ROM_H
+
+#ifdef __KERNEL__
+
+struct rom_device {
+ int rle;
+ int rom_refcnt;
+ const unsigned char *data;
+ long length;
+};
+
+#endif /* __KERNEL__ */
+#endif
+

--
 "Love the dolphins," she advised him. "Write by W.A.S.T.E.."