Contents

1 Introduction
2 Copyright and Acknowledgements
3 The brief analysis of the Android architecture
3.1 Android Kernel
3.1.1 ARM EABI
3.1.2 OpenBinder
3.1.3 Frame Buffer
3.1.4 Input Devices
3.1.5 Low Memory Killer
3.1.6 Android Logger
3.1.7 Android Power
3.1.8 Panic Timeout
3.2 Android Root File system
3.3 Licenses of the Android Packages
4 Toolchain supporting ARM EABI
4.1 Building toolchain
4.2 Other toolchain
5 Kernel
5.1 Patch kernel
5.2 .config
6 Root file system
6.1 Get ramdisk image from emulator
6.2 Get data and system directory from emulator
6.3 Integrate the Android system with a existing ramdisk image.
6.4 System and Data directories
6.5 Run and Debug
6.6 Screenshots
7 Application Development
7.1 Install Eclipse IDE
7.2 Build and Run Sample Applications
7.3 Screenshots
8 Epilogue
9 Links and References

1 Introduction

Google explains that Android is a software stack for mobile
devices that includes an operating system, middleware and key
applications. This document explains the Android architecture by Google
and porting procedure on the real hardware. The explanation is based on
the m3 sdk version of the Android emulator.
If you have enough knowledge about patching the kernel, resolving
rejections from a patch, making an ramdisk image, and the Linux kernel
itself, reading this article will be easier.

2 Copyright and Acknowledgements

This document is copyright (c) Kwangwoo Lee (kwangwoo.lee at gmail
dot com). Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License.

AndroidPortingOnRealTarget/ko – Korean translation by dasomoli (dasomoli at gmail dot com).

3 The brief analysis of the Android architecture

3.1 Android Kernel

The most different things are the Android kernel uses ARM EABI(Embedded Application Binary Interface) and OpenBinder
IPC(Inter Process Communication). If you want to compile the kernel
supporting ARM EABI, you should rebuild toolchains to support ARM EABI.

The Android sdk emulates goldfish architecture using qemu. The
alsa may be used for audio on Android. See the audio.c file in the
goldfish architecture directory and the driver uses /dev/eac for audio
on the Android system. RTC(Real Time Clock) device is also used through
/dev/rtc0.

The following parts explain the main differences:

3.1.1 ARM EABI

EABI is the new "Embedded" ABI by ARM Ltd. The changes are listed on Debian wiki. (http://wiki.debian.org/ArmEabiPort)

Example with long ftruncate64(unsigned int fd, loff_t length):

legacy ABI:
- put fd into r0
- put length into r1-r2
- use "swi #(0x900000 + 194)" to call the kernel

new ARM EABI:
- put fd into r0
- put length into r2-r3 (skipping over r1)
- put 194 into r7
- use "swi 0" to call the kernel

The Android uses EABI kernel feature. Enable kernel options of the
CONFIG_AEABI and CONFIG_OABI_COMPAT. You can see the differences of the
executable binary as follows :

  • Legacy ABI
$ arm-softfloat-linux-gnu-objdump -x t7-demo | grep private
private flags = 202: [APCS-32] [FPA float format] [software FP] [has entry point]

$ file t7-demo
t7-demo: ELF 32-bit LSB executable, ARM, version 1 (ARM),
for GNU/Linux 2.4.3, dynamically linked (uses shared libs),
for GNU/Linux 2.4.3, stripped

  • ARM EABI
$ arm-softfloat-linux-gnueabi-objdump -x t7-demo  | grep private
private flags = 4000002: [Version4 EABI] [has entry point]

$ file t7-demo
t7-demo: ELF 32-bit LSB executable, ARM, version 1 (SYSV),
for GNU/Linux 2.6.14, dynamically linked (uses shared libs),
for GNU/Linux 2.6.14, stripped

What is the ABI for the ARM Architecture? Is it the same as the ARM EABI?

The ABI for the ARM Architecture is a standard developed by ARM
and its partners (including CodeSourcery) that explains how compilers,
assemblers, linkers, and other similar tools should generate object
files and executable files. Tools that correctly implement the ABI for
the ARM Architecture can interoperate; i.e., objects files built with
one toolchain can be combined with object files built with another
toolchain if both compilers use the ABI for the ARM Architecture. The
"ARM EABI" is an informal name for the ABI for the ARM Architecture.

3.1.2 OpenBinder

The OpenBinder
provides a object-oriented operating system environment. It is designed
to be hosted by traditional kernels. This project is started at Be.
Inc. as the part of the next generation BeOS, and finished implementing at PalmSource as a core part at the Cobalt system.

It is a system oriented component architecture rather than
application oriented, and It provides IPC between processes,
threadpool, memory management and clean up feature at the end of
reference of an binder object.

The vanilla kernel do not have OpenBinder IPC mechanism you should patch the kernel. The OpenBinder
offers thread management for the system through /dev/binder. It is the
reason that Android system do not offer thread libraries.

After patching the kernel, you can see the files for binder at drivers/binder/.

3.1.3 Frame Buffer

The basic frame buffer driver should be implemented already. After
that you need to implement the differences between your architecture
driver and the goldfish driver.

The frame buffer driver of the goldfish architecture supports the
fb_pan_display function of the struct fb_ops. It means you should
allocate memory twice rather than the actual frame size.

  • Initialize frame buffer information
struct fb_info *fbinfo;
...
fbinfo->fix.ypanstep = 1;
fbinfo->var.yres_virtual = gm->lcd.yres * 2;
fbinfo->fix.smem_len = (gm->lcd.xres *
gm->lcd.yres *
gm->lcd.bpp / 8) * 2;

  • Allocate frame buffer memory
struct mvfb_info *fbi;
...
fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);
fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size,
&fbi->map_dma, GFP_KERNEL);

  • Implement fb_pan_display fuction hook
static int mvfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fb)
{
...
}

static struct fb_ops mvfb_ops = {
.owner = THIS_MODULE,

.fb_check_var = mvfb_check_var,
.fb_set_par = mvfb_set_par,
.fb_setcolreg = mvfb_setcolreg,
.fb_blank = mvfb_blank,
.fb_pan_display = mvfb_pan_display,

.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,

.fb_mmap = mvfb_mmap,
};

The device file is located at /dev/graphics/fb0.

3.1.4 Input Devices

Android uses event device for user input. There are three devices
such as keypad, qwerty2 keyboard and mouse. The qwerty2 keyboard and
mouse are normal devices. So I just explain the keypad and touchscreen
which mouse device is replaced with.

On the Android shell, Cat the /proc/bus/input/{devices,handlers} and then you will see the devices used for the Android.

$ adb shell

# cat /proc/bus/input/devices
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="goldfish-events-keyboard"
P: Phys=
S: Sysfs=/class/inut/input0
U: Uniq=
H: Handlers=kbd mouse0 event0
...
#
# cat /proc/bus/input/handlers
N: Number=0 Name=kbd
N: Number=1 Name=mousedev Minor=32
N: Number=2 Name=evdev Minor=64
#

  • Keypad

Qemu emulates goldfish-events-keyboard. It is a keypad using event
device(/dev/input/event0). So you should know which key event and
values come from the event device to activate Android applications. To
do that, read event0 device with cat and redirect the output to a file.
If you push and release the key button on emulator, the output values
will be saved.

The output format is struct input_event. So the output on each
event is 16 bytes like 8 bytes for time, 2 bytes for type, 2 bytes for
code, 4 bytes for value. Read input.txt and input-programming.txt about
input event devices in the Documentation/input directory of the Linux
kernel source code.

struct input_event {
struct timeval time;
unsigned short type;
unsigned short code;
unsigned int value;
};

The Tiger7 evaluation board has it’s own scancode table. The
following shows the key layout on evaluation board, scancode table, and
Android keycodes:

/*
* Key Layout Scancode Table
*
* 1 2 3 0x1 0x10 0x100
* 4 5 6 0x2 0x20 0x200
* 7 8 9 0x4 0x40 0x400
* * 0 # 0x8 0x80 0x800
*/

static unsigned short android_keycode[] = {
/*
* 0x66 0x67 0x9e Home Up Back
* 0x69 0xe8 0x6a Left Ok Right
* 0xe7 0x6c 0x6b Send Down Hangup
* 0xe5 Menu just_distinction_for_private
*/
KEY_HOME, KEY_UP, KEY_BACK,
KEY_LEFT, KEY_REPLY, KEY_RIGHT,
KEY_SEND, KEY_DOWN, KEY_END,
KEY_KBDILLUMDOWN, KEY_RESERVED, KEY_PLAY
};

There is a power button on emulator, but I skipped it to get output value.

If an interrupt of the keypad is caught, translate the scancode
with the keycode of the Android on the above table and send event to
user space application.

...
keycode = translate_keycode(scancode);
...
input_event(keydev->input, EV_KEY, keycode, KEY_PRESSED);
or
input_event(keydev->input, EV_KEY, keycode, KEY_RELEASED);
...

The high resolution timer – hrtimer is used for reduce keypad debounce.

  • Touchscreen

If you have a touchscreen driver supporting the event interface
for a pointing device, it’ll work well. If you do not have it, you may
implement it or use other pointing devices. Fortunately the evaluation
board has already implemented touchscreen driver –
drivers/input/touchscreen/tsc2007.c – which is made just before
beginning to porting Android. Refer the drivers on
drivers/input/touchscreen/ to implement your own driver and the text
files on Documentation/input/.

Here is the output of the /proc/bus/input/{devices,handlers} on evaluation board.

# cat /proc/bus/input/devices
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="MVT7 KEYPAD"
P: Phys=
S: Sysfs=/class/input/input0
U: Uniq=
H: Handlers=kbd event0 evbug
B: EV=f
...

I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="TSC2007 Touchscreen"
P: Phys=0-0090/input0
S: Sysfs=/class/input/input1
U: Uniq=
H: Handlers=event1 evbug
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=1000003

# cat /proc/bus/input/handlers
N: Number=0 Name=kbd
N: Number=1 Name=evdev Minor=64
N: Number=2 Name=evbug

As a result, the keypad uses /dev/input/event0 and the touchscreen interface uses /dev/input/event1 on application layer.

3.1.5 Low Memory Killer

The Linux Kernel has an OOM(Out of Memory) killer for the
situation that no memory is left to allocate for a request of a
process. It examines all processes and keeps score with some
restrictions. The process with highest score will be killed except
init.

The Low Memory Killer of the Android behaves a bit different
against OOM killer. It classifies processes according to the importance
with groups and kills the process in the lowest group. It will make the
system to be stable at the view of the end users. For example, the UI
Process – foreground process – is the most important process for the
end users. So to keep the process live looks more stable than keeping
other background processes live.

Enable CONFIG_LOW_MEMORY_KILLER after patching the kernel.

3.1.6 Android Logger

If you enable this feature, you can see some useful information
about Android through /dev/log/main. There are three device files on
/dev/log such as main, events, radio. The /dev/log/radio file seems to
be related with a modem device and ril daemon – rild – on Android
system.

When this logger is enabled, the system performance is a bit
slower on the system. To use this feature, enable
CONFIG_ANDROID_LOGGER.

3.1.7 Android Power

The Android power is for the battery management on devices and
some subsystem related with power management like inotify feature on
file system. It is not necessary to start up Android through the init(ShellScript)
of the Android system. But the runtime binary looks up some files
regarding Android power – /sys/android_power/acruire_partial_wake_lock
– on starting up Android manually and failed to start up. Enable
CONFIG_ANDROID_POWER to use.

3.1.8 Panic Timeout

It is not necessary to start up Android on evaluation board. Set CONFIG_PANIC_TIMEOUT with a desired value.

3.2 Android Root File system

Android emulator has 3 basic images on tools/lib/images directory.

  • ramdisk.img
  • system.img
  • userdata.img

ramdisk.img is gziped cpio archive. ramdisk image is very small
and contains configuration files, and some executable files such as
init and recovery. The init file is not a regular system V init. It is
made just for the Android and do special things to start up the Android
system.

system.img and userdata.img are VMS Alpha executable. system.img
and userdata.img have the contents of /system and /data directory on
root file system. They are mapped on NAND devices with yaffs2 file
system. /dev/block/mtdblock0 for /system and /dev/block/mtdblock1 for
/data.

/system directory has libraries and default system packages (*.apk). /data directory has timezone, cache, and ApiDemos.apk package.

The main services are zygote(/system/bin/app_process),
runtime(/system/bin/runtime), and dbus(/system/bin/dbus-daemon). You
can see the /etc/init.rc file on the Android ramdisk image.

...
zygote {
exec /system/bin/app_process
args {
0 -Xzygote
1 /system/bin
2 --zygote
}
autostart 1
}
runtime {
exec /system/bin/runtime
autostart 1
}
...
dbus {
exec /system/bin/dbus-daemon
args.0 --system
args.1 --nofork
autostart 1
}
...

3.3 Licenses of the Android Packages

tools/lib/images/NOTICE contains package lists and licenses for
each libraries. The table of the licenses is cited from the
presentation by Lim,GeunSik at 2008 Korea Android Summit.

Open Source License
Linux Kernel GPL
NetBSD C Library BSD
DBUS GPL2
OpenBinder (core) GPL2
YAFFS2 GPL
SQLite GPL2
Webkit BSD (including LGPL)
WebCore LGPL
SDL LGPL
SGL Google(Skia)
OpenGL SGI OpenGL (BSD/MPL)

4 Toolchain supporting ARM EABI

The toolchain represents the tools to be used for the system
development. It contains C/C++ compiler, linker, libraries, binutils,
and etc. The Android kernel and system requires EABI support. So legacy
toolchain is not compatible to make the Android system.

4.1 Building toolchain

To make life easier, I used the crosstool-0.43 script (http://www.kegel.com/crosstool/) by Dan Kegel. Unfortunately it is not support to build eabi toolchain, so I applied a glibc 2.5+ nptl build for arm softfloat eabi patch (http://sources.redhat.com/ml/crossgcc/2006-12/msg00076.html) by Khem Raj.

$./arm-softfloat-eabi.sh

If the network is connected, the script will download and build toolchain using gcc 4.1.1 and glibc 2.5.

4.2 Other toolchain

I did not use the codesourcery toolchain, but they said it will work for the building Android system.

5 Kernel

To port the Android on a real hardware is started by Benno (http://benno.id.au),
you can see some useful information on his blog. On his blog some
pre-compiled static binaries are linked. It is very helpful for
debugging Android system. You can also build static build busybox and
strace binaries, but it’s better to get them and use.

You can get patch file including the differences between the
Android kernel and the vanilla kernel with 2.6.23 version. It has all
differences between them. So you need to extract parts of them, and
make your own patch for your system architecture.

For example, the Android kernel has it’s own yaffs file system
patch. If you have your own yaffs or some other file systems like jffs2
on your architecture, then you need to remove the yaffs parts of the
patch. The goldfish architecture which the Android kernel emulate an
ARM architecture on qemu is not necessary part for your architecture.
It can be removed.

The Android kernel emulates ARMv5 instructions. So ARM926EJ-S (ARMv5TEJ) will be good to work.

5.1 Patch kernel

Benno played with a NEO1973 device by openmoko. So he made patch files for it. Get the original patch file from http://benno.id.au/blog/2007/11/21/android-neo1973, I used android.diff. It has whole things about goldfish, qemu, yaffs, and Android specific parts.

You can edit and remove the patch file directly. After making
patch including binder, android power, android logger, low memory
killer except goldfish and qemu specific parts, get vanilla 2.6.23
version Linux kernel and patch it.

If you use a 2.6.24.1 version Linux kernel, some part regarding android power should be fixed accordingly or disabled to work.

5.2 .config

  • Necessary
...
CONFIG_PANIC_TIMEOUT=0
CONFIG_AEABI=y
CONFIG_OABI_COMPAT=y
CONFIG_BINDER=y
CONFIG_LOW_MEMORY_KILLER=y
...

  • Optional
...
# CONFIG_ANDROID_GADGET is not set
# CONFIG_ANDROID_RAM_CONSOLE is not set
# CONFIG_ANDROID_POWER is not set
# CONFIG_ANDROID_LOGGER is not set
...

6 Root file system

The root file system is composed of three parts such as a primary
ramdisk image on ram, a system image on nand dev0
(/dev/block/mtdblock0), and a data image on nand dev1
(/dev/block/mtdblock1). The mtd devices has a yaffs2 file system and
each of them has 64 MiB capacity on the Android emulator.

The extracted system and data directories are copied to the real
existing NAND device and they are mounted with –bind option to work on
a real hardware.

6.1 Get ramdisk image from emulator

1. unpack ramdisk image from tools/lib/images/ramdisk.img

$ gzip -cd ramdisk.img > ramdisk
$ cpio -iv -F ramdisk

cpio will extract files and directories on current working directory.

2. the contents list of the ramdisk

data
dev
etc
etc/default.prop
etc/firmware
etc/firmware/brf6150.bin
etc/firmware/brf6300.bin
etc/hcid.conf
etc/hosts
etc/init.gprs-pppd
etc/init.rc
etc/init.ril
etc/init.testmenu
etc/ppp
etc/ppp/chap-secrets
etc/ppp/ip-down
etc/ppp/ip-up
etc/qemu-init.sh
etc/system.conf
etc/system.d
etc/system.d/bluez-hcid.conf
etc/usbd.conf
init
proc
sbin
sbin/recovery
sys
system
tmp
var
var/run

6.2 Get data and system directory from emulator

To get data and system directory you need a static compiled busybox binary. The compiled binary can be obtained from http://benno.id.au/blog/2007/11/14/android-busybox , or make your own binary.

1. launch the Android emulator

2. push static compiled busybox into emulator

# adb push busybox .

3. launch the Android shell

# adb shell

4. make tarball with busybox

# chmod +x /busybox
# busybox tar -c /data.tar /data
# busybox tar -c /system.tar /system
# exit

5. extract tarball from the emulator

# adb pull /data.tar .
# adb pull /system.tar .

Extract command often failed. So you may repeat it again until it has done successfully.

6.3 Integrate the Android system with a existing ramdisk image.

The ramdisk for your architecture can make your work a bit easier.
Copy the contents of the Android ramdisk to your own ramdisk except
system and data directory. And make just mount point for system and
data directory. The mount points will be used later with a bind option.
The init binary of the Android ramdisk image is the key binary to start
the system and It read a configuration file on /etc/init.rc.

Edit /etc/init.rc and comment out qemu part.

...
startup {
...
# qemu-init {
# exec /etc/qemu-init.sh
# }
}
...

Make run.sh script. /dev/block/mtdblock5 is a mtd partition on a
real NAND device, and it is mounted on /mnt. data and system
directories are already copied on mtdblock5. So the script below just
shows bind mounting each directory on /. Fix your script according to
your board configuration.

#!/bin/sh
mount -t yaffs /dev/block/mtdblock5 /mnt
mount --bind /mnt/data /data
mount --bind /mnt/system /system

# data folder is owned by system user on emulator. Fix 777 to other.
chmod 777 /data
#chmod 777 /system

export PATH=/system/sbin:/system/bin:/sbin/usr/local/bin
export LD_LIBRARY_PATH=/system/lib

export ANDROID_BOOTLOGO=1
export ANDROID_ROOT=/system
export ANDROID_ASSETS=/system/app
export EXTERNAL_STORAGE=/sdcard
export ANDROID_DATA=/data
export DRM_CONTENT=/data/drm/content

/init &

An optional configuration for touchscreen – TSLib.

...
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_TSDEVICE=/dev/input/event1
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts

export LD_PRELOAD=/lib/libts.so:/lib/ts/pthres.so
...

6.4 System and Data directories

The contents of the system and data directories are copied to
mtdblock5 already. You should copy your own method. To use it, I chose
bind mounting on root directory. Bind mounting is a technique to mount
an existing directory with a new mount point.

6.5 Run and Debug

Now the kernel, ramdisk, and data directories – data and system –
are ready. It’s time to see the red cylon eye. After boot up your
integrated system, run the run.sh on root directory.

# cd /
# . /android/run.sh
yaffs: dev is 32505861 name is "mtdblock5"
yaffs: passed flags ""
yaffs: Attempting MTD mount on 31.5, "mtdblock5"
yaffs: auto selecting yaffs2
# init: HOW ARE YOU GENTLEMEN
init: reading config file
init: device init
init: mtd partition -1,
init: mtd partition 0, "l1boot"
init: mtd partition 1, "u-boot"
init: mtd partition 2, "params"
init: mtd partition 3, "kernel"
init: mtd partition 4, "ramdisk"
init: mtd partition 5, "rootfs"
sh: can't access tty; job control turned off
# binder_open(c394bcc8 c3c731a0) (pid 1577) got c3e48000
binder_open(c394bcc8 c3cd8dc0) (pid 1616) got c319f000
binder_open(c394bcc8 c3cd8ac0) (pid 1673) got c3d10000
binder_open(c394bcc8 c3cd8940) (pid 1680) got c0e19000
binder_open(c394bcc8 c3cd88c0) (pid 1691) got c2fa0000
binder_open(c394bcc8 c3d174a0) (pid 1592) got c25b8000
binder_release(c394bcc8 c3cd88c0) (pid 1691) pd c2fa0000
#

  • Do not make eac device file on /dev. It is for the audio
    on qemu. If it exists, the start up sequence will wait forever to
    finish writing some data to the sound device.
  • Use the Android init binary instead of manual startup. The manual
    start up will require the android power patch. In that case the start
    up sequence will access /sys/android_power/acquire_partial_wake_lock
    and wait.

To debug the Android system, use static compiled strace binary from http://benno.id.au/blog/2007/11/18/android-runtime-strace and run the Android manually.

#!/bin/sh
# set environment variables above example
...
/system/bin/app_process -Xzygote /system/bin --zygote &
/system/bin/dbus-daemon --system &
/system/bin/runtime

The above example shows manual startup sequence, use strace on run /system/bin/runtime binary.

./strace -ff -F -tt -s 200 -o /tmp/strace runtime

6.6 Screenshots

  • Skipped

7 Application Development

The Android applications use Java syntax and xml layouts, but it
is not a Java. Because they use their own virtual machine – dalvik –
and compiler for dex file format. And use package named apk such as
Home.apk, Phone.apk, ApiDemos.apk and etc.

The apk file is a Zip archive and it has four parts.

  • AndroidManifest.xml
  • classes.dex
  • resources.arsc
  • res directory

The Dex file format is explained on http://www.retrodev.com/android/dexformat.html.
And the contents of the files and directories are explained some day by
Google. It is not explained currently. We can just guess about it.

The Android SDK will create an *.apk file.

7.1 Install Eclipse IDE

1. Eclipse IDE for Java developer (JDT and WST plugins are included) from http://www.eclipse.org/downloads/

3. ADT (Android Development Tools) with a eclipse plugin including Apache Ant

7.2 Build and Run Sample Applications

1. Open sample projects and build

2. Run sample applications on emulator

7.3 Screenshots

  • Android Platform on Nokia’s N810 Product(arm1136jf-s)

  • Android Platform on arm1136jf-S for another CE Product.
  • invain님이 shot을 올려주셨군요. — 이광우

8 Epilogue

The Android system seems to be a new kind of a Linux based distribution for a mobile environment like Debian, RedHat, SuSE,
and etc. They just use the Linux kernel and a lot of different
libraries in the open source world. They offer a software based OpenGL-ES
library on a 3D acceleration currently, but they are developing on a
hardware accelerated baseband processor for it. The hardware
acceleration is necessary for fast UI rendering effects later.

The Android system on the sdk is not a completed one to port on a
real hardware, because some device – for example, camera – related
libraries and classes are not implemented yet and not opened for users.
It seems to be under the development stage. So we would better to wait
the Google announces the whole porting kit.

Until then, we should look for the business model with the Android
system. It requires a bit high cpu performance, so the carrier vendors
will require a cheap baseband processor (RF part) and a multimedia
co-processor, because the baseband processor including multimedia
features will be very expensive.

9 Links and References

Advertisements