Generate developer docs with Sphinx

An effort was started some time ago to consolidate all internal
documentation in the git tree. However, this was just an accumulation of
files in various formats without any strucutre or way to browse it,
which results in no one even knowing that we have docs here.

This converts most of the files to restructuredtext and uses Sphinx to
generate an HTML browsable user manual (with a table of content and a
first attempt to put things in a global hierarchy).

There are almost no changes to the documentation content in this commit
(some obviously obsolete things were removed). The plan is to get the
toolchain up and running to make these docs easily available, and only
then see about improving the content. We can migrate some things off the
wiki and website, and rework the table of contents to have some more
hierarchy levels because currently it's a bit messy.

Change-Id: I924ac9dc6e753887ab56f18a09bdb0a1e1793bfd
Reviewed-on: https://review.haiku-os.org/c/haiku/+/4370
Reviewed-by: Niels Sascha Reedijk <[email protected]>
This commit is contained in:
Adrien Destugues
2021-08-27 11:41:17 +00:00
committed by Adrien Destugues
parent 7f8195344a
commit a5061ecec5
146 changed files with 10937 additions and 12069 deletions
@@ -1,84 +0,0 @@
# Allwinner A10
* http://linux-sunxi.org
# Hardware Information
The A10 is a system-on chip. There are many devices based on it, for example
the CubieBoard and the Rikomagic mk802 (versions I and II).
* ARMv7 Architecture (Cortex-A8)
* Mali 400MP GPU
* CedarX VPU
* SD Card Storage
* 1GB RAM (DDR)
* 4GB NAND Flash
* Video Outputs
* HDMI Video Output
* Ethernet
* USB
# Setting up the Haiku SD card
Not so fun layout here. The A10 boot ROM reads raw blocks from the SD card
(MBR style), so the bootloader can't just be dropped in a FAT32 partition.
* 8KB partition table
* 24KB SPL loader
* 512KB u-boot
* 128KB u-boot environment variables
* 352KB unused
* partition 1 -- FAT32 or ext2 (anything u-boot can read is fine)
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
Note this layout can be a bit different depending on the u-boot version used,
some versions will store the environment in uEnv.txt in the FAT32 partition
instead. Since everything is loaded from the SD Card, we are free to customize
the u-boot or even remove it and get haiku_loader booting directly.
## Boot Partition
### Required Files
* haiku_loader: Haiku Loader
* haiku-floppyboot.tgz: Compressed image with Haiku kernel
# Booting
1. SOC load SPL
2. SPL loads u-boot
2. u-boot loads and run the kernel
SPL is a small binary (24K) loaded from a fixed location on the SD card. It
does minimal hardware initializations, then loads u-boot, also from the SD
card. From there on things go as usual.
In the long term, we can make haiku_loader be an SPL executable on this
platform, if it fits the 24K size limit, or have a custom stage1 that loads it.
For now, u-boot can be an useful debugging tool.
## Script.bin
In order to work on different devices (RAM timings, PIO configs, ...), the
Linux kernels for Allwinner chips use a "script.bin" file. This is loaded to
RAM at a fixed address by u-boot, then the Kernel parses it and uses it to
configure the hardware (similar to FDT).
We should probably NOT use this, and convert the script.bin file to an FDT
instead. The format is known and there are tools to convert the binary file
to an editable text version and back (bin2fex and fex2bin).
This FEX stuff isn't merged in mainline Linux, and lives on as Allwinner
patches. The mainline Linux kernel has some A10 support, rewritten to use
FDT. We may use the FDT files from there for the most common boards.
# Emulation support
qemu 1.0 has a Cubieoard target which emulates this chip.
# Useful links
Arch Linux instructions on creating a bootable SD card (partition layout, etc)
http://archlinuxarm.org/platforms/armv7/allwinner/cubieboard#qt-platform_tabs-ui-tabs2
Linux SunXi: mainline Linux support for the Allwinner chips. Lots of docs on the hardware.
http://linux-sunxi.org/
@@ -0,0 +1,99 @@
Allwinner A10 hardware notes
############################
- http://linux-sunxi.org
Hardware Information
====================
The A10 is a system-on chip. There are many devices based on it, for
example the CubieBoard and the Rikomagic mk802 (versions I and II).
- ARMv7 Architecture (Cortex-A8)
- Mali 400MP GPU
- CedarX VPU
- SD Card Storage
- 1GB RAM (DDR)
- 4GB NAND Flash
- Video Outputs
- HDMI Video Output
- Ethernet
- USB
Setting up the Haiku SD card
============================
Not so fun layout here. The A10 boot ROM reads raw blocks from the SD
card (MBR style), so the bootloader cant just be dropped in a FAT32
partition.
- 8KB partition table
- 24KB SPL loader
- 512KB u-boot
- 128KB u-boot environment variables
- 352KB unused
- partition 1 FAT32 or ext2 (anything u-boot can read is fine)
- partition 2 BeFS, Haiku filesystem, type eb
Note this layout can be a bit different depending on the u-boot version
used, some versions will store the environment in uEnv.txt in the FAT32
partition instead. Since everything is loaded from the SD Card, we are
free to customize the u-boot or even remove it and get haiku_loader
booting directly.
Boot Partition
--------------
Required Files
~~~~~~~~~~~~~~
- haiku_loader: Haiku Loader
- haiku-floppyboot.tgz: Compressed image with Haiku kernel
Booting
=======
1. SOC load SPL
2. SPL loads u-boot
3. u-boot loads and run the kernel
SPL is a small binary (24K) loaded from a fixed location on the SD card.
It does minimal hardware initializations, then loads u-boot, also from
the SD card. From there on things go as usual.
In the long term, we can make haiku_loader be an SPL executable on this
platform, if it fits the 24K size limit, or have a custom stage1 that
loads it. For now, u-boot can be an useful debugging tool.
Script.bin
----------
In order to work on different devices (RAM timings, PIO configs, …), the
Linux kernels for Allwinner chips use a “script.bin” file. This is
loaded to RAM at a fixed address by u-boot, then the Kernel parses it
and uses it to configure the hardware (similar to FDT).
We should probably NOT use this, and convert the script.bin file to an
FDT instead. The format is known and there are tools to convert the
binary file to an editable text version and back (bin2fex and fex2bin).
This FEX stuff isnt merged in mainline Linux, and lives on as Allwinner
patches. The mainline Linux kernel has some A10 support, rewritten to
use FDT. We may use the FDT files from there for the most common boards.
Emulation support
=================
qemu 1.0 has a Cubieoard target which emulates this chip.
Useful links
============
Arch Linux instructions on creating a bootable SD card (partition
layout, etc)
http://archlinuxarm.org/platforms/armv7/allwinner/cubieboard#qt-platform_tabs-ui-tabs2
Linux SunXi: mainline Linux support for the Allwinner chips. Lots of
docs on the hardware. http://linux-sunxi.org/
-60
View File
@@ -1,60 +0,0 @@
# BeagleBone Black
* http://beagleboard.org
* TODO: This is a WIP
# Hardware information (Rev A5A)
* ARMv7 Architecture
* Sitara AM3359AZCZ100 Cortex-A8 CPU @ 1 Ghz
* PowerVR SGX530 3D GPU
* eMMC Onboard Storage 2GB (MMC1)
* SD Card Storage (MMC0)
* 512 MB DDR3L RAM
* Video Outputs
* HDMI Video Output (with audio)
* SMSC LAN8710A Ethernet
# Setting up the Haiku SD card
The BeagleBone Black supports booting from an microSD card while the boot switch is pressed at power on. A MBR file system layout is normally used as seen below. Partition 1 is all that is required to boot an OS.
* partition 1 -- FAT32, bootable flag, type 'c'
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
## Boot Partition
### Required files
* MLO
* u-boot.img: u-Boot image
* uEnv.txt: u-Boot Environment settings
### Optional files
* ID.txt: Unknown
# Compiling
* Create your work directory `mkdir generated.beagle; cd generated.beagle`
* Build an ARM toolchain using `../configure --build-cross-tools arm ../../buildtools --target-board beagle`
* TODO
# Booting
1. If the boot switch is not depressed:
MMC1, MMC0, UART0, USB0
2. If the boot switch is depressed:
SPI0, MMC0, USB0, UART0
# Emulation
The Linaro Fork of QEmu has beagle board (and other OMAP3) support.
https://launchpad.net/qemu-linaro
It seems you get this as the default QEmu install on some, but not all, Ubuntu
versions. For other distros (or Haiku), you'll have to compile it yourself.
# Additional information
* [CircutCo WikiPage](http://circuitco.com/support/index.php?title=BeagleBoneBlack)
* [BeagleBone Black A5A SRM](https://github.com/CircuitCo/BeagleBone-Black/blob/master/BBB_SRM.pdf?raw=true)
+78
View File
@@ -0,0 +1,78 @@
BeagleBone Black
================
- http://beagleboard.org
- TODO: This is a WIP
Hardware information (Rev A5A)
==============================
- ARMv7 Architecture
- Sitara AM3359AZCZ100 Cortex-A8 CPU @ 1 Ghz
- PowerVR SGX530 3D GPU
- eMMC Onboard Storage 2GB (MMC1)
- SD Card Storage (MMC0)
- 512 MB DDR3L RAM
- Video Outputs
- HDMI Video Output (with audio)
- SMSC LAN8710A Ethernet
Setting up the Haiku SD card
============================
The BeagleBone Black supports booting from an microSD card while the
boot switch is pressed at power on. A MBR file system layout is normally
used as seen below. Partition 1 is all that is required to boot an OS.
- partition 1 FAT32, bootable flag, type c
- partition 2 BeFS, Haiku filesystem, type eb
Boot Partition
--------------
Required files
~~~~~~~~~~~~~~
- MLO
- u-boot.img: u-Boot image
- uEnv.txt: u-Boot Environment settings
Optional files
~~~~~~~~~~~~~~
- ID.txt: Unknown
Compiling
=========
- Create your work directory
``mkdir generated.beagle; cd generated.beagle``
- Build an ARM toolchain using
``../configure --build-cross-tools arm ../../buildtools --target-board beagle``
- TODO
Booting
=======
1. If the boot switch is not depressed: MMC1, MMC0, UART0, USB0
2. If the boot switch is depressed: SPI0, MMC0, USB0, UART0
Emulation
=========
The Linaro Fork of QEmu has beagle board (and other OMAP3) support.
https://launchpad.net/qemu-linaro
It seems you get this as the default QEmu install on some, but not all,
Ubuntu versions. For other distros (or Haiku), youll have to compile it
yourself.
Additional information
======================
- `CircutCo
WikiPage <http://circuitco.com/support/index.php?title=BeagleBoneBlack>`__
- `BeagleBone Black A5A
SRM <https://github.com/CircuitCo/BeagleBone-Black/blob/master/BBB_SRM.pdf?raw=true>`__
+8
View File
@@ -0,0 +1,8 @@
Efika MX
========
FDT
---
* http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/imx51x.dtsi?revision=248557&view=markup
* http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/efikamx.dts?revision=248557&view=markup
-5
View File
@@ -1,5 +0,0 @@
Notes on Efika MX port -*- org -*-
* FDT
http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/imx51x.dtsi?revision=248557&view=markup
http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/efikamx.dts?revision=248557&view=markup
+20
View File
@@ -0,0 +1,20 @@
iPaq
====
* http://www.NetBSD.org/ports/hpcarm/
* http://www.ibm.com/developerworks/linux/library/l-ipaq/index.html
* http://mstempin.free.fr/linux-ipaq/html_nochunks/Linux-iPAQ-HOWTO-1.1.html#BACKING-UP-BOOTLOADER
* http://www.eecs.umich.edu/~panalyzer/
* http://www.eecs.umich.edu/~panalyzer/sim-ipaq/sim_ipaq_readme.html
* http://blogs.unbolt.net/index.php/brinley/2007/08/04/exploring_hp_ipaq_6515e_bootloader
* http://gert-menke.de/jtag-howto/
* http://ecos.sourceware.org/docs-3.0/redboot-guide/ipaq.html
* http://www.balloonboard.org/balloon/balloon3/distro/test-v0.2/sources/balloonsvn/bootldr295/doc/install-via-osloader.html
RS232
-----
* http://www.kronosrobotics.com/Zeus/IPAQcon.pdf
* http://bevhoward.com/serial.htm
* http://www.mail-archive.com/[email protected]/msg132363.html
* http://web.archive.org/web/20050408063754/http://www.handhelds.org/pipermail/ipaq/2000-August/000061.html
-15
View File
@@ -1,15 +0,0 @@
http://www.NetBSD.org/ports/hpcarm/
http://www.ibm.com/developerworks/linux/library/l-ipaq/index.html
http://mstempin.free.fr/linux-ipaq/html_nochunks/Linux-iPAQ-HOWTO-1.1.html#BACKING-UP-BOOTLOADER
http://www.eecs.umich.edu/~panalyzer/
http://www.eecs.umich.edu/~panalyzer/sim-ipaq/sim_ipaq_readme.html
http://blogs.unbolt.net/index.php/brinley/2007/08/04/exploring_hp_ipaq_6515e_bootloader
http://gert-menke.de/jtag-howto/
http://ecos.sourceware.org/docs-3.0/redboot-guide/ipaq.html
http://www.balloonboard.org/balloon/balloon3/distro/test-v0.2/sources/balloonsvn/bootldr295/doc/install-via-osloader.html
RS232:
http://www.kronosrobotics.com/Zeus/IPAQcon.pdf
http://bevhoward.com/serial.htm
http://www.mail-archive.com/[email protected]/msg132363.html
http://web.archive.org/web/20050408063754/http://www.handhelds.org/pipermail/ipaq/2000-August/000061.html
@@ -1,2 +0,0 @@
* Does not support < ARMv5
* Requires support for high vectors
+170
View File
@@ -0,0 +1,170 @@
The ARM port
============
Note: there are in fact two ports to the ARM architecture, one for 32-bit, and one for 64-bit
systems. They don't have a lot of shared code as the two architectures are very different from
one another.
ARM devices are very popular, and especially since the release of the Raspberry Pi, people have
been requesting that Haiku is ported to it. Unfortunately, limitations in the architecture itself
and the wide diversity of hardware have made this task more complicated, and progress has been
slow. For example, ARM has no standard like the PC is for x86, so concepts as basic as a system
timer, a bootloader, or a serial port, are different from one machine to another. The situation
has improved with the later generations, as more things were integrated in the CPU core, and u-boot
is now well established as the main bootloader for ARM devices.
Limitations
-----------
There will be no support for hardware using architectures older than ARMv5. There will probably be
no support for architectures before ARMv7, which require more work on the compiler and OS, for
example due to lack of atomic instructions.
Support for high vectors (interrupt vectors stored at the end of the memory space) is required.
Information about specific hardware targets
-------------------------------------------
Over the years, various possible ARM targets have been considered for the Haiku ARM port.
We have accumulated some notes and documentation on some of them.
.. toctree::
/kernel/arch/arm/allwinner_a10
/kernel/arch/arm/beagle
/kernel/arch/arm/efikamx
/kernel/arch/arm/ipaq
/kernel/arch/arm/rpi1
/kernel/arch/arm/rpi2
TODO list
---------
Fix pre-ARMv7 support
*********************
The ARM instruction set has evolved a lot over time, and we have to make a choice: use the oldest
versions of the instruction set gives us maximal compatibility, but at the cost of a large
performance hit on newer systems, as well as extra code being needed in the OS to compensate for
the missing instructions.
Currently the cross-tools are compiled to default to ARMv7, Cortex-A8, and
hardware floating point. This works around the missing atomic support, see
below. This should be done by setting the -mcpu,-march and -mfloat-abi
switches at build time, however, they aren't passed on to haikuporter
during the bootstrap build, leading to the ports failing to find the
gcc atomic ops again.
It seems this create other problems, mainly because the UEFI environment for ARM is not supposed to
handle floating point registers. So, the softfloat ABI should be used there instead. To be able
to build both "soft float" and "hard float" code, we need multilib support, see below.
Determine how to handle atomic functions on ARM
***********************************************
GCC inlines are not supported, since the instructionset is ill-equiped for
this on older (pre-ARMv7) architectures. We possibly have to do something
similar to the linux kernel helper functions for this....
On ARMv7 and later, this is not an issue. Not sure about ARMv6, we may get
it going there. ARMv5 definitely needs us to write some code, but is it
worth the trouble?
Fix multilib support
********************
ARM-targetting versions of gcc are usually built with multilib support, to
allow targetting architectures with or without FPU, and using either ARM
or Thumb instructions. This bascally means a different libgcc and libstdc++
are built for each combination.
The cross-tools can be built with multilib support. However, we do some
tricks to get a separate libgcc and libstdc++ for the kernel (without C++11
threads support, as that would not build in the kernel). Building this lib
is not done in a multilib-aware way, so you get one only for the default
arch/cpu/abi the compiler is targetting. This is good enough, as long as that
arch is the one we want to use for the kernel...
Later on, the bootstrap build of the native gcc compiler will fail, because
it tries to build its multilib library set by linking against the different
versions of libroot (with and without fpu, etc). We only build one libroot,
so this also fails.
The current version of the x86_64 compiler appears is using multilib (to build for both 32 and 64
bit targets) and is working fine, so it's possible that most of the issues in this area have
already been fixed.
Figure out how to get page flags (modified/accessed) and implement it
*********************************************************************
use unmapped/read-only mappings to trigger soft faults for tracking used/modified flags for ARMv5 and ARMv6
Fix serial port mapping
***********************
Currently kernel uses the haiku_loader identity
mapping for it, but this lives in user virtual address space...
(Need to not use identity mapping in haiku_loader but just
map_physical_memory() there too so it can be handed over without issues).
Seperate ARM architecture/System-On-Chip IP code
************************************************
The early work on the ARM port resulted in lots of board specific code being added to early stages
of the kernel. Ideally, this would not be needed, the kernel would manage to initialize itself
mostly in a platform independant way, and get the needed information from the FDT passed by the
bootloader. The difficulty is that on older ARM versions, even the interrupt controller and timers
can be different on each machine.
KDL disasm module
*****************
Currently it is not possible to disassemble code in the kernel debugger.
The `NetBSD disassembler <http://fxr.watson.org/fxr/source/arch/arm/arm/disassem.c?v=NETBSD>`_ could be ported and used for this.
Add KDL hangman to the boot image
*********************************
for more enjoyment during porting....
Userland
********
Even if KDL hangman is fun, users will want to run real applications someday.
Bootloader TODOs
****************
- Better handling of memory ranges. Currently no checks are done, and
memory is assumed to be a single contiguous range, and the "input"
ranges for mmu_init are setup, but never considered.
- Allocate the pagetable range using mmu_allocate() instead of identity
mapping it. That way, there's a bit more flexibility in where to place
it both physically and virtually. This will need a minor change on the
kernel side too (in the early pagetable allocator).
Other resources
---------------
About flatenned device trees
****************************
* http://www.denx.de/wiki/U-Boot/UBootFdtInfo
* http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
* http://elinux.org/images/4/4e/Glikely-powerpc-porting-guide.pdf
* http://ols.fedoraproject.org/OLS/Reprints-2008/likely2-reprint.pdf
* http://www.bsdcan.org/2010/schedule/events/171.en.html
* http://www.devicetree.org/ (unofficial bindings)
* http://www.devicetree.org/Device_Tree_Usage
* http://elinux.org/Device_Trees
About openfirmware
******************
http://www.openfirmware.info/Bindings
About floating point numbers handling on ARM
********************************************
https://wiki.debian.org/ArmHardFloatPort/VfpComparison
-74
View File
@@ -1,74 +0,0 @@
# Raspberry Pi
* http://raspberrypi.org
# Hardware Information
* ARMv6 Architecture
* Broadcom BCM2835 (SoC)
* Includes ARM1176JZF-S CPU @ 700 MHz
* Includes VideoCore IV GPU
* SD Card Storage
* 256 or 512 MB RAM (depending on revision)
* Video Outputs
* HDMI Video Output
* Composite Video Output
* Ethernet
# Setting up the Haiku SD card
The Raspberry Pi SD card generally uses the MBR file system layout below. Partition 1 is all that is required to boot an OS.
* partition 1 -- FAT32, bootable flag, type 'c'
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
## Boot Partition
### Required Files
* bootcode.bin : 2nd stage bootloader
* start.elf: The GPU binary firmware image
* config.txt: A configuration file read by the Pi to start u-boot.bin
* u-boot.bin: u-boot loader for the Pi 2
* bcm2835-rpi-b.dtb: FDT binary for the Raspberry Pi 2
* haiku_loader_linux.ub: Haiku Loader
* haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku kernel
### Optional Files
* vlls directory: Additional GPU code, e.g. extra codecs.
* uEnv.txt: u-boot configuration script to automate boot.
# Compiling
* Create your ARM work directory `mkdir generated.arm; cd generated.arm`
* Build an ARM toolchain using `../configure --build-cross-tools arm ../../buildtools --target-board=rpi1`
* Build our loader using `jam -q haiku_loader_linux.ub`
* Build our initial ram disk using `jam -q haiku-floppyboot.tgz.ub`
# Booting
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
## config.txt Options
kernel=u-boot.bin
## u-boot startup
These will be condensed and automated long-term via uEnv.txt :-)
* `fatload mmc 0 ${fdt_addr_r} bcm2835-rpi-b.dtb`
* `fdt addr ${fdt_addr_r}`
* `fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub`
* `fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub`
* `bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}`
# Additional Information
* [Latest Raspberry Pi firmware](http://github.com/raspberrypi/firmware/tree/master/boot)
* [config.txt options](http://www.elinux.org/RPiconfig)
+96
View File
@@ -0,0 +1,96 @@
Raspberry Pi
############
- http://raspberrypi.org
Hardware Information
====================
- ARMv6 Architecture
- Broadcom BCM2835 (SoC)
- Includes ARM1176JZF-S CPU @ 700 MHz
- Includes VideoCore IV GPU
- SD Card Storage
- 256 or 512 MB RAM (depending on revision)
- Video Outputs
- HDMI Video Output
- Composite Video Output
- Ethernet
Setting up the Haiku SD card
============================
The Raspberry Pi SD card generally uses the MBR file system layout
below. Partition 1 is all that is required to boot an OS.
- partition 1 FAT32, bootable flag, type c
- partition 2 BeFS, Haiku filesystem, type eb
Boot Partition
--------------
Required Files
~~~~~~~~~~~~~~
- bootcode.bin : 2nd stage bootloader
- start.elf: The GPU binary firmware image
- config.txt: A configuration file read by the Pi to start u-boot.bin
- u-boot.bin: u-boot loader for the Pi 2
- bcm2835-rpi-b.dtb: FDT binary for the Raspberry Pi 2
- haiku_loader_linux.ub: Haiku Loader
- haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku
kernel
Optional Files
~~~~~~~~~~~~~~
- vlls directory: Additional GPU code, e.g. extra codecs.
- uEnv.txt: u-boot configuration script to automate boot.
Compiling
=========
- Create your ARM work directory
``mkdir generated.arm; cd generated.arm``
- Build an ARM toolchain using
``../configure --build-cross-tools arm ../../buildtools --target-board=rpi1``
- Build our loader using ``jam -q haiku_loader_linux.ub``
- Build our initial ram disk using ``jam -q haiku-floppyboot.tgz.ub``
Booting
=======
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
config.txt Options
------------------
::
kernel=u-boot.bin
u-boot startup
--------------
These will be condensed and automated long-term via uEnv.txt :-)
- ``fatload mmc 0 ${fdt_addr_r} bcm2835-rpi-b.dtb``
- ``fdt addr ${fdt_addr_r}``
- ``fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub``
- ``fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub``
- ``bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}``
Additional Information
======================
- `Latest Raspberry Pi
firmware <http://github.com/raspberrypi/firmware/tree/master/boot>`__
- `config.txt options <http://www.elinux.org/RPiconfig>`__
-74
View File
@@ -1,74 +0,0 @@
# Raspberry Pi 2
* http://raspberrypi.org
# Hardware Information
* ARMv7 Architecture
* Broadcom BCM2836 (SoC)
* Includes Quad ARM1176JZF-S CPU @ 900 MHz
* Includes VideoCore IV GPU
* SD Card Storage
* 1 GB RAM
* Video Outputs
* HDMI Video Output
* Composite Video Output
* Ethernet
# Setting up the Haiku SD card
The Raspberry Pi SD card generally uses the MBR file system layout below. Partition 1 is all that is required to boot an OS.
* partition 1 -- FAT32, bootable flag, type 'c'
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
## Boot Partition
### Required Files
* bootcode.bin : 2nd stage bootloader
* start.elf: The GPU binary firmware image
* config.txt: A configuration file read by the Pi to start u-boot.bin
* u-boot.bin: u-boot loader for the Pi 2
* bcm2836-rpi-2-b.dtb: FDT binary for the Raspberry Pi 2
* haiku_loader_linux.ub: Haiku Loader
* haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku kernel
### Optional Files
* vlls directory: Additional GPU code, e.g. extra codecs.
* uEnv.txt: u-boot configuration script to automate boot.
# Compiling
* Create your ARM work directory `mkdir generated.arm; cd generated.arm`
* Build an ARM toolchain using `../configure --build-cross-tools arm ../../buildtools --target-board=rpi2`
* Build our loader using `jam -q haiku_loader_linux.ub`
* Build our initial ram disk using `jam -q haiku-floppyboot.tgz.ub`
# Booting
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
## config.txt Options
kernel=u-boot.bin
## u-boot startup
These will be condensed and automated long-term via uEnv.txt :-)
* `fatload mmc 0 ${fdt_addr_r} bcm2836-rpi-2-b.dtb`
* `fdt addr ${fdt_addr_r}`
* `fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub`
* `fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub`
* `bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}`
# Additional Information
* [Latest Raspberry Pi firmware](http://github.com/raspberrypi/firmware/tree/master/boot)
* [config.txt options](http://www.elinux.org/RPiconfig)
+96
View File
@@ -0,0 +1,96 @@
Raspberry Pi 2
##############
- http://raspberrypi.org
Hardware Information
====================
- ARMv7 Architecture
- Broadcom BCM2836 (SoC)
- Includes Quad ARM1176JZF-S CPU @ 900 MHz
- Includes VideoCore IV GPU
- SD Card Storage
- 1 GB RAM
- Video Outputs
- HDMI Video Output
- Composite Video Output
- Ethernet
Setting up the Haiku SD card
============================
The Raspberry Pi SD card generally uses the MBR file system layout
below. Partition 1 is all that is required to boot an OS.
- partition 1 FAT32, bootable flag, type c
- partition 2 BeFS, Haiku filesystem, type eb
Boot Partition
--------------
Required Files
~~~~~~~~~~~~~~
- bootcode.bin : 2nd stage bootloader
- start.elf: The GPU binary firmware image
- config.txt: A configuration file read by the Pi to start u-boot.bin
- u-boot.bin: u-boot loader for the Pi 2
- bcm2836-rpi-2-b.dtb: FDT binary for the Raspberry Pi 2
- haiku_loader_linux.ub: Haiku Loader
- haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku
kernel
Optional Files
~~~~~~~~~~~~~~
- vlls directory: Additional GPU code, e.g. extra codecs.
- uEnv.txt: u-boot configuration script to automate boot.
Compiling
=========
- Create your ARM work directory
``mkdir generated.arm; cd generated.arm``
- Build an ARM toolchain using
``../configure --build-cross-tools arm ../../buildtools --target-board=rpi2``
- Build our loader using ``jam -q haiku_loader_linux.ub``
- Build our initial ram disk using ``jam -q haiku-floppyboot.tgz.ub``
Booting
=======
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
config.txt Options
------------------
::
kernel=u-boot.bin
u-boot startup
--------------
These will be condensed and automated long-term via uEnv.txt :-)
- ``fatload mmc 0 ${fdt_addr_r} bcm2836-rpi-2-b.dtb``
- ``fdt addr ${fdt_addr_r}``
- ``fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub``
- ``fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub``
- ``bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}``
Additional Information
======================
- `Latest Raspberry Pi
firmware <http://github.com/raspberrypi/firmware/tree/master/boot>`__
- `config.txt options <http://www.elinux.org/RPiconfig>`__
-64
View File
@@ -1,64 +0,0 @@
* Fix pre-ARMv7 support
Currently the cross-tools are compiled to default to ARMv7, Cortex-A8, and
hardware floating point. This works around the missing atomic support, see
below. This should be done by setting the -mcpu,-march and -mfloat-abi
switches at build time, however, they aren't passed on to haikuporter
during the bootstrap build, leading to the ports failing to find the
gcc atomic ops again.
* Determine how to handle atomic functions on ARM.
GCC inlines are not supported, since the instructionset is ill-equiped for
this on older (pre-ARMv7) architectures. We possibly have to do something
similar to the linux kernel helper functions for this....
On ARMv7 and later, this is not an issue. Not sure about ARMv6, we may get
it going there. ARMv5 definitely needs us to write some code, but is it
worth the trouble?
* Fix multilib support
ARM-targetting versions of gcc are usually built with multilib support, to
allow targetting architectures with or without FPU, and using either ARM
or Thumb instructions. This bascally means a different libgcc and libstdc++
are built for each combination.
The cross-tools can be built with multilib support. However, we do some
tricks to get a separate libgcc and libstdc++ for the kernel (without C++11
threads support, as that would not build in the kernel). Building this lib
is not done in a multilib-aware way, so you get one only for the default
arch/cpu/abi the compiler is targetting. This is good enough, as long as that
arch is the one we want to use for the kernel...
Later on, the bootstrap build of the native gcc compiler will fail, because
it tries to build its multilib library set by linking against the different
versions of libroot (with and without fpu, etc). We only build one libroot,
so this also fails.
* Figure out how to get page flags (modified/accessed) and implement it ;)
use unmapped/read-only mappings to trigger soft faults
for tracking used/modified flags for ARMv5 and ARMv6
* Fix serial port mapping. Currently kernel uses the haiku_loader identity
mapping for it, but this lives in user virtual address space...
(Need to not use identity mapping in haiku_loader but just
map_physical_memory() there too so it can be handed over without issues).
* Seperate ARM architecture/System-On-Chip IP code. Needed very early on
(Interrupt Controller, Timer IP block). Should use FDT for this too.
* Use FDT to remove all the seperate board definitions in the build. Use a
"minimal ARM architecture version" as the compile flag for ARM support.
This to be able to optimize a kernel build for a specific SoC (or family).
* KDL disasm module.
cf.
http://fxr.watson.org/fxr/source/arch/arm/arm/disassem.c?v=NETBSD
* Add KDL hangman to the boot floppy image for more enjoyment during porting....
* Userland...........
* Bootloader TODOs:
- Better handling of memory ranges. Currently no checks are done, and
memory is assumed to be a single contiguous range, and the "input"
ranges for mmu_init are setup, but never considered.
- Allocate the pagetable range using mmu_allocate() instead of identity
mapping it. That way, there's a bit more flexibility in where to place
it both physically and virtually. This will need a minor change on the
kernel side too (in the early pagetable allocator).
-13
View File
@@ -1,13 +0,0 @@
* FDT
http://www.denx.de/wiki/U-Boot/UBootFdtInfo
http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
http://elinux.org/images/4/4e/Glikely-powerpc-porting-guide.pdf
http://ols.fedoraproject.org/OLS/Reprints-2008/likely2-reprint.pdf
http://www.bsdcan.org/2010/schedule/events/171.en.html
http://www.devicetree.org/ (unofficial bindings)
http://www.devicetree.org/Device_Tree_Usage
http://elinux.org/Device_Trees
* OF
http://www.openfirmware.info/Bindings
* Floating Point; VFP
https://wiki.debian.org/ArmHardFloatPort/VfpComparison
-48
View File
@@ -1,48 +0,0 @@
Notes on long double support
============================
The "long double" type is different on each architecture. Depending on the
available hardware and ABI conventions, performance compromises, etc, there
may be many implementations of it. Here is a summary for our convenience.
128-bit IEEE
------------
Platforms: Sparc, ARM64, RISC-V
This is the standard long double type from IEEE754. It has 1 sign bit,
15 exponent bit, and 112 fractional part bits. It is the natural extension
of the 64bit double.
Sparc specifies this type in their ABI but no implementation actually has
the instructions, they instead trigger a trap which would software emulate
them. However, gcc short circuits this by default and calls C library
support functions directly.
64-bit IEEE
-----------
Platforms: ARM
This is the same representation as plain "double". ARM uses this for simplicity.
80-bit
------
Platform: x86, x86\_64, m68k
This intermediate format is used by x86 CPUs internally. It may end up being
faster than plain double there. It consists of a 64bit fractional part, 15
exponent bits, and 1 sign bit. This is convenient because the fractional part
is a relatively easy to handle 64bit number.
m68k uses a similar format, but padded to 96 bits (the extra 16 bits are unused).
double double
-------------
Platforms: PowerPC?
This is also a 128bit type, but the representation is just two 64bit doubles.
The value is the sum of the two halves. This format allows faster emulation
than a "true" 128bit long double, and the precision is almost as good.
+54
View File
@@ -0,0 +1,54 @@
Notes on long double support
============================
The “long double” type is different on each architecture. Depending on
the available hardware and ABI conventions, performance compromises,
etc, there may be many implementations of it. Here is a summary for our
convenience.
128-bit IEEE
------------
Platforms: Sparc, ARM64, RISC-V
This is the standard long double type from IEEE754. It has 1 sign bit,
15 exponent bit, and 112 fractional part bits. It is the natural
extension of the 64bit double.
Sparc specifies this type in their ABI but no implementation actually
has the instructions, they instead trigger a trap which would software
emulate them. However, gcc short circuits this by default and calls C
library support functions directly.
.. _bit-ieee-1:
64-bit IEEE
-----------
Platforms: ARM
This is the same representation as plain “double”. ARM uses this for
simplicity.
80-bit
------
Platform: x86, x86_64, m68k
This intermediate format is used by x86 CPUs internally. It may end up
being faster than plain double there. It consists of a 64bit fractional
part, 15 exponent bits, and 1 sign bit. This is convenient because the
fractional part is a relatively easy to handle 64bit number.
m68k uses a similar format, but padded to 96 bits (the extra 16 bits are
unused).
double double
-------------
Platforms: PowerPC?
This is also a 128bit type, but the representation is just two 64bit
doubles. The value is the sum of the two halves. This format allows
faster emulation than a “true” 128bit long double, and the precision is
almost as good.
-3
View File
@@ -1,3 +0,0 @@
- optimization: remove M68KPagingStructures[*]::UpdateAllPageDirs() and just allocate all the kernel page root entries at boot and be done with it. It's not very big anyway.
- possibly other optimizations in the VM code due to not supporting SMP?
+4
View File
@@ -0,0 +1,4 @@
The Amiga port
==============
* http://wandel.ca/homepage/execdis/
@@ -1 +0,0 @@
http://wandel.ca/homepage/execdis/
+108
View File
@@ -0,0 +1,108 @@
The Atari ST port
=================
Atari ST executables
--------------------
From: DaFi <[email protected]>
The specs for Atari ST executables (was listed as requested on www.wotsit.demon.co.uk/wanted.htm)...
applies for TOS, PRG, TTP, PRX, GTP, APP, ACC, ACX (different suffixes indicate different behavior of the program, i.e. TOS and TTP may not use the GEM GUI, while all the others may; only TTP and GTP can be called with parameters; ACC may be installed as desktop accessories; PRX and ACX mean the programs were disabled.
file structure:
+--------------------+---------------------------------------------------------------------------+
| [2] WORD PRG_magic | magic value 0x601a |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_tsize | size of text segment |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_dsize | size of data segment |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_bsize | size of bss segment |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_ssize | size of symbol table |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_res1 | reserved |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRGFLAGS | bit vector that defines additional process characteristics, as follows: |
| | |
| | * **Bit 0 PF_FASTLOAD** - if set, only the BSS area is cleared, otherwise,|
| | the program's whole memory is cleared before loading |
| | * **Bit 1 PF_TTRAMLOAD** - if set, the program will be loaded into TT RAM |
| | * **Bit 2 PF_TTRAMMEM** - if set, the program will be allowed to allocate |
| | memory from TT RAM |
| | |
| | Bit 4 AND 5 as a two bit value with the following meanings: |
| | |
| | * 0 PF_PRIVATE - the processes entire memory space is considered private |
| | * 1 PF_GLOBAL - the processes memory will be r/w-allowed for others |
| | * 2 PF_SUPER - the memory will be r/w for itself and any supervisor proc |
| | * 3 PF_READ - the memory will be readable by others |
+--------------------+---------------------------------------------------------------------------+
| [2] WORD ABSFLAG | is NON-ZERO, if the program does not need to be relocated |
| | |
| | is ZERO, if the program needs to be relocated |
| | |
| | note: since some TOS versions handle files with ABSFLAG>0 incorrectly, |
| | this value should be set to ZERO also for programs that need to be |
| | relocated, and the FIXUP_offset should be set to 0. |
+--------------------+---------------------------------------------------------------------------+
From there on... (should be offset 0x1c)
[PRG_tsize] TEXT segment
[PRG_dsize] DATA segment
[PRG_ssize] Symbol table
[4] LONG FIXUP_offset - first LONG that needs to be relocated (offset to beginning of file)
From there on till the end of the file...
FIXUP table, with entries as follows:
[1] BYTE value
with value as follows:
- value=0 end of list
- value=1 advance 254 bytes
- value=2 to value=254 (only even values!) advance this many bytes and relocate the LONG found there.
That's it. You made it through to EOF.
A final note about fixing up (relocating) an executable: (pseudo-code)
The long value FIXUP_offset tells you your start adress. Let's call it "adr". So, now, that
you have adr, read the first byte of the table.
(*) loop
- if it's 0, stop relocating -> you're done!
- if it's 1, add 254 to adr and read the next byte, jump back to the asterisk (*)
- if it's any other even value, add the value to your adr, then relocate the LONG at adr.
(i.e. add the adress of the LONG to its value)
Useful resources
----------------
* http://toshyp.atari.org/en/index.html
* http://www.lysator.liu.se/~celeborn/sync/atari/misc.html
* http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/F30.ZIP
* http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCLIB6.ZIP
* http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCREGS.ZIP
* http://fxr.watson.org/fxr/source/include/asm-m68k/atarihw.h?v=linux-2.4.22
* http://lxr.linux.no/linux+v2.6.27/arch/m68k/atari/config.c#L664
* http://www.atari-forum.com/wiki/index.php/MFP_MK68901
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/ahdi-xxboot/xxboot.ahdi.S
AHDI args
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/wdboot/wdboot.S
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/sdboot/sdboot.S
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/fdboot/fdboot.S
@@ -1,57 +0,0 @@
Subject: Atari ST executables
From: DaFi <[email protected]>
The specs for Atari ST executables (was listed as requested on www.wotsit.demon.co.uk/wanted.htm)...
applies for TOS, PRG, TTP, PRX, GTP, APP, ACC, ACX (different suffixes indicate different behavior of the program, i.e. TOS and TTP may not use the GEM GUI, while all the others may; only TTP and GTP can be called with parameters; ACC may be installed as desktop accessories; PRX and ACX mean the programs were disabled.
file structure:
[2] WORD PRG_magic - magic value 0x601a
[4] LONG PRG_tsize - size of text segment
[4] LONG PRG_dsize - size of data segment
[4] LONG PRG_bsize - size of bss segment
[4] LONG PRG_ssize - size of symbol table
[4] LONG PRG_res1 - reserved
[4] LONG PRGFLAGS - bit vector that defines additional process characteristics, as follows:
Bit 0 PF_FASTLOAD - if set, only the BSS area is cleared, otherwise,
the program´s whole memory is cleared before loading
Bit 1 PF_TTRAMLOAD - if set, the program will be loaded into TT RAM
Bit 2 PF_TTRAMMEM - if set, the program will be allowed to allocate
memory from TT RAM
Bit 4 AND 5 as a two bit value with the following meanings:
0 PF_PRIVATE - the processes entire memory space is considered private
1 PF_GLOBAL - the processes memory will be r/w-allowed for others
2 PF_SUPER - the memory will be r/w for itself and any supervisor proc
3 PF_READ - the memory will be readable by others
[2] WORD ABSFLAG - is NON-ZERO, if the program does not need to be relocated
is ZERO, if the program needs to be relocated
note: since some TOS versions handle files with ABSFLAG>0 incorrectly,
this value should be set to ZERO also for programs that need to be
relocated, and the FIXUP_offset should be set to 0.
From there on... (should be offset 0x1c)
[PRG_tsize] TEXT segment
[PRG_dsize] DATA segment
[PRG_ssize] Symbol table
[4] LONG FIXUP_offset - first LONG that needs to be relocated (offset to beginning of file)
From there on till the end of the file...
FIXUP table, with entries as follows:
[1] BYTE value - with value as follows:
value=0 end of list
value=1 advance 254 bytes
value=2 to value=254 (only even values!) advance this many bytes and
relocate the LONG found there
That´s it. You made it through to EOF.
A final note about fixing up (relocating) an executable: (pseudo-code)
The long value FIXUP_offset tells you your start adress. Let´s call it "adr". So, now, that
you have adr, read the first byte of the table.
(*) loop
- if it´s 0, stop relocating -> you´re done!
- if it´s 1, add 254 to adr and read the next byte, jump back to the asterisk (*)
- if it´s any other even value, add the value to your adr, then relocate the LONG at adr.
(i.e. add the adress of the LONG to its value)
dafi
@@ -1,19 +0,0 @@
http://toshyp.atari.org/en/index.html
http://www.lysator.liu.se/~celeborn/sync/atari/misc.html
http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/F30.ZIP
http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCLIB6.ZIP
http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCREGS.ZIP
http://fxr.watson.org/fxr/source/include/asm-m68k/atarihw.h?v=linux-2.4.22
http://lxr.linux.no/linux+v2.6.27/arch/m68k/atari/config.c#L664
http://www.atari-forum.com/wiki/index.php/MFP_MK68901
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/ahdi-xxboot/xxboot.ahdi.S
AHDI args
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/wdboot/wdboot.S
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/sdboot/sdboot.S
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/fdboot/fdboot.S
@@ -0,0 +1,21 @@
The m68k port
#############
The Motorola 68000 is an old CPU and not a reasonable target for Haiku. However, later models
which are equipped with a memory management unit could work (slowly).
There is work in progress to target Atari, Amiga, and NeXT hardware platforms.
Todo list
=========
- optimization: remove M68KPagingStructures[*]::UpdateAllPageDirs() and just allocate all the kernel page root entries at boot and be done with it. It's not very big anyway.
- possibly other optimizations in the VM code due to not supporting SMP?
Target platforms information
============================
.. toctree::
/kernel/arch/m68k/amiga
/kernel/arch/m68k/atari
+19
View File
@@ -0,0 +1,19 @@
Notes on a possible BeBox Haiku port
====================================
Bootloader
----------
The BeBox ROM expects the bootloader to be in PEF format, as was produced by the CodeWarrior
compiler used by Be. However, support for this format in binutils seems incomplete.
references
----------
* http://www.netbsd.org/ports/bebox/
* http://netbsd.2816.n7.nabble.com/BeBox-memory-configuration-td278318.html
QEMU target
-----------
http://qemu-project.org/Features/BeBox
-7
View File
@@ -1,7 +0,0 @@
Notes on a possible BeBox Haiku port -*- org -*-
* references
http://www.netbsd.org/ports/bebox/
http://netbsd.2816.n7.nabble.com/BeBox-memory-configuration-td278318.html
** QEMU target
http://qemu-project.org/Features/BeBox
+17
View File
@@ -0,0 +1,17 @@
The Apple Macintosh port
======================================
The current target is "new world" machines, which have a more complete and reliable implementation
of Open Firmware.
* http://www.debian.org/releases/stable/powerpc/ch05s01.html.en
* http://www.kernelthread.com/mac/osx/arch_boot.html
* http://playground.sun.com/1275/mejohnson/
* http://homepages.gold.ac.uk/suzanne/startup.html
* http://www.netbsd.org/ports/macppc/SystemDisk-tutorial/
* http://www.netneurotic.net/mac/openfirmware.html
* http://www.netbsd.org/ports/macppc/faq.html
* http://mail-index.netbsd.org/port-macppc/1999/03/21/0001.html
* http://mail-index.netbsd.org/port-macppc/1999/06/25/0006.html
* http://ps-2.kev009.com/solinno.co.uk/7043-140/files/docs/ {OF,PReP}
* http://www.openfirmware.org/1275/bindings/chrp/
-11
View File
@@ -1,11 +0,0 @@
http://www.debian.org/releases/stable/powerpc/ch05s01.html.en
http://www.kernelthread.com/mac/osx/arch_boot.html
http://playground.sun.com/1275/mejohnson/
http://homepages.gold.ac.uk/suzanne/startup.html
http://www.netbsd.org/ports/macppc/SystemDisk-tutorial/
http://www.netneurotic.net/mac/openfirmware.html
http://www.netbsd.org/ports/macppc/faq.html
http://mail-index.netbsd.org/port-macppc/1999/03/21/0001.html
http://mail-index.netbsd.org/port-macppc/1999/06/25/0006.html
http://ps-2.kev009.com/solinno.co.uk/7043-140/files/docs/ {OF,PReP}
http://www.openfirmware.org/1275/bindings/chrp/
+24
View File
@@ -0,0 +1,24 @@
The PowerPC port
================
PowerPC was the first non-x86architecture for which a port of Haiku was attempted. The initial
target was the (then recently released) Mac Mini, but of course the BeBox was in everyone's mind
as a possible target for this port.
This port went as far as starting the kernel, but then difficulties in implementing the Mac Mini
PCI bus driver stopped it.
Later on, some work as done on adding support for the Sam460ex development board, after a donation
of one to one of the Haiku developers.
Recently, the lack of easily available and affordable PowerPC hardware has reduced interest in this
port.
Platform specific details
-------------------------
.. toctree::
/kernel/arch/ppc/bebox
/kernel/arch/ppc/mac
/kernel/arch/ppc/sam460ex
+119
View File
@@ -0,0 +1,119 @@
The Sam460ex Haiku port
=======================
Complications for this port comes from the fact that the CPU used was designed for embedded devices,
and has a much simpler MMU than the one on desktop machines. As a result, completely different
memory management code needs to be written.
U-Boot commands
---------------
no-fdt no-initrd
****************
seems the U-Boot input buffer is quite limited, can't paste much more on single line in minicom
setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub; bootm 0x4000000
with FDT and tgz as initrd
**************************
setenv ipaddr 192.168.4.100
tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub
tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub
tftpboot 0xc000000 192.168.4.2:sam460ex.dtb
fdt addr 0xc000000
fdt header
bootm 0x4000000 0x8000000 0xc000000 plop
for environment
***************
setenv booth1 'setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub'
setenv booth2 'tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub'
setenv booth3 'tftpboot 0xc000000 192.168.4.2:sam460ex.dtb'
setenv booth4 'bootm 0x4000000 0x8000000 0xc000000 plop'
setenv booth 'run booth1; run booth2; run booth3; run booth4'
saveenv
run booth
TODOs
-----
* U-Boot API?
* move Partenope hack to proper official U-Boot API?
* reserved regs?
BoardSetup +=:?
TARGET_BOOT_CCFLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
TARGET_BOOT_C++FLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
* kdebug/disasm/ppc http://code.google.com/p/ppcd/
Other ports
-----------
* `AROS port <https://www.gitorious.org/aros/aros/commits/sam460>`_
* `Linux port <http://kernel.org/doc/ols/2003/ols2003-pages-340-350.pdf>`_
* `NetBSD <https://wiki.netbsd.org/users/rkujawa/sam4x0/>`_
PowerPC information
-------------------
Classic
*******
* http://class.ee.iastate.edu/cpre211/labs/quickrefPPC.html
* http://www.ibm.com/developerworks/library/l-ppc/
* http://www.csd.uwo.ca/~mburrel/stuff/ppc-asm.html
Book-E
******
* http://www.linux-kvm.org/page/PowerPC_Book_E_MMU
* http://wiki.freebsd.org/powerpc/BookE
* http://en.wikipedia.org/wiki/Memory_management_unit#PowerPC
ePAPR
*****
* https://www.power.org/wp-content/uploads/2012/06/Power_ePAPR_APPROVED_v1.1.pdf
* PPC440: http://elinux.org/Book_E_and_PPC_440
amcc 4x0
********
* http://c0ff33.net/drop/PPC440_UM2013.pdf
* http://www.embeddeddeveloper.com/assets/processors/amcc/datasheets/PP460EX_DS2063.pdf
Freescale 440
*************
This version has a different mmu!!
* http://www.freescale.com/files/32bit/doc/white_paper/POWRPCARCPRMRM.pdf
FDT
---
* http://www.denx.de/wiki/U-Boot/UBootFdtInfo
* http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
* (see also arm docs)
Sam440 dts
**********
* http://lxr.linux.no/linux+v3.4/arch/powerpc/boot/dts/sam440ep.dts
* Sam460ex dts: identical to amcc,Canyonlands !?
* http://www.denx.de/wiki/view/DULG/Appendix#Section_13.1.
OpenFirmware framebuffer
************************
(not really usable from U-Boot (yet?))
* http://www.feedface.com/howto/forth.html
* http://mail-index.netbsd.org/port-macppc/2004/12/13/0046.html
* http://lists.freebsd.org/pipermail/svn-src-user/2012-January/004806.html
* http://www.openfirmware.info/Bindings
@@ -1,76 +0,0 @@
Notes on Sam460ex Haiku port -*- org -*-
* U-Boot commands
** no-fdt no-initrd
# (seems the U-Boot input buffer is quite limited, can't paste much more on single line in minicom)
setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub; bootm 0x4000000
** with FDT and tgz as initrd
setenv ipaddr 192.168.4.100
tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub
tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub
tftpboot 0xc000000 192.168.4.2:sam460ex.dtb
fdt addr 0xc000000
fdt header
bootm 0x4000000 0x8000000 0xc000000 plop
** for environment:
setenv booth1 'setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub'
setenv booth2 'tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub'
setenv booth3 'tftpboot 0xc000000 192.168.4.2:sam460ex.dtb'
setenv booth4 'bootm 0x4000000 0x8000000 0xc000000 plop'
setenv booth 'run booth1; run booth2; run booth3; run booth4'
saveenv
run booth
* TODO U-Boot API?
** TODO move Partenope hack to proper official U-Boot API?
** TODO reserved regs?
BoardSetup +=:?
TARGET_BOOT_CCFLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
TARGET_BOOT_C++FLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
* Other ports
** AROS port
https://www.gitorious.org/aros/aros/commits/sam460
** Linux port
http://kernel.org/doc/ols/2003/ols2003-pages-340-350.pdf
** NetBSD
https://wiki.netbsd.org/users/rkujawa/sam4x0/
* PPC
** Classic
http://class.ee.iastate.edu/cpre211/labs/quickrefPPC.html
http://www.ibm.com/developerworks/library/l-ppc/
http://www.csd.uwo.ca/~mburrel/stuff/ppc-asm.html
** Book-E
http://www.linux-kvm.org/page/PowerPC_Book_E_MMU
http://wiki.freebsd.org/powerpc/BookE
http://en.wikipedia.org/wiki/Memory_management_unit#PowerPC
** ePAPR
https://www.power.org/wp-content/uploads/2012/06/Power_ePAPR_APPROVED_v1.1.pdf
** 440
http://elinux.org/Book_E_and_PPC_440
*** amcc 4x0
http://c0ff33.net/drop/PPC440_UM2013.pdf
http://www.embeddeddeveloper.com/assets/processors/amcc/datasheets/PP460EX_DS2063.pdf
*** Freescale 440 (different mmu!!)
http://www.freescale.com/files/32bit/doc/white_paper/POWRPCARCPRMRM.pdf
* FDT
http://www.denx.de/wiki/U-Boot/UBootFdtInfo
http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
(see also arm docs)
** Sam440 dts
http://lxr.linux.no/linux+v3.4/arch/powerpc/boot/dts/sam440ep.dts
** Sam460ex dts: identical to amcc,Canyonlands !?
http://www.denx.de/wiki/view/DULG/Appendix#Section_13.1.
* OF framebuffer
(not really usable from U-Boot (yet?))
http://www.feedface.com/howto/forth.html
http://mail-index.netbsd.org/port-macppc/2004/12/13/0046.html
http://lists.freebsd.org/pipermail/svn-src-user/2012-January/004806.html
http://www.openfirmware.info/Bindings
* TODO kdebug/disasm/ppc
http://code.google.com/p/ppcd/
-32
View File
@@ -1,32 +0,0 @@
The SPARC architecture has 32 integer registers, divided as follows:
- global registers (g0-g7)
- input (i0-i7)
- local (l0-l7)
- output (o0-o7)
Parameter passing and return is done using the output registers, which are
generally considered scratch registers and can be corrupted by the callee. The
caller must take care of preserving them.
The input and local registers are callee-saved, but we have hardware assistance
in the form of a register window. There is an instruction to shift the registers
so that:
- o registers become i registers
- local and output registers are replaced with fresh sets, for use by the
current function
- global registers are not affected
Note that as a side-effect, o7 is moved to i7, this is convenient because these
are usually the stack and frame pointers, respectively. So basically this sets
the frame pointer for free.
Simple enough functions may end up using just the o registers, in that case
nothing special is necessary, of course.
When shifting the register window, the extra registers come from the register
stack in the CPU. This is not infinite, however, most implementations of SPARC
will only have 8 windows available. When the internal stack is full, an overflow
trap is raised, and the handler must free up old windows by storing them on the
stack, likewise, when the internal stack is empty, an underflow trap must fill
it back from the stack-saved data.
@@ -1,37 +0,0 @@
The SPARC CPU is not designed to gracefully handle misaligned accesses.
You can access a single byte at any address, but 16-bit access only at even
addresses, 32bit access at multiple of 4 addresses, etc.
For example, on x86, such accesses are not a problem, it is allowed and handled
directly by the instructions doing the access. So there is no performance cost.
On SPARC, however, such accesses will cause a SIGBUS. This means a trap handler
has to catch the misaligned access and do it in software, byte by byte, then
give back control to the application. This is, of course, very slow, so we
should avoid it when possible.
Fortunately, gcc knows about this, and will normally do the right thing:
- For usual variables and structures, it will make sure to lay them out so that
they are aligned. It relies on stack alignment, as well as malloc returning
sufficiently aligned memory (as required by the C standard).
- On packed structure, gcc knows the data is misaligned, and will automatically
use the appropriate way to access it (most likely, byte-by-byte).
This leaves us with two undesirable cases:
- Pointer arithmetics and casting. When computing addresses manually, it's
possible to generate a misaligned address and cast it to a type with a wider
alignment requirement. In this case, gcc may access the pointer using a
multi byte instruction and cause a SIGBUS. Solution: make sure the struct
is aligned, or declare it as packed so unaligned access are used instead.
- Access to hardware: it is a common pattern to declare a struct as packed,
and map it to hardware registers. If the alignment isn't known, gcc will use
byte by byte access. It seems volatile would cause gcc to use the proper way
to access the struct, assuming that a volatile value is necessarily
aligned as it should.
In the end, we just need to be careful about pointer math resulting in unalined
access. -Wcast-align helps with that, but it also raises a lot of false positives
(where the alignment is preserved even when casting to other types). So we
enable it only as a warning for now. We will need to ceck the sigbus handler to
identify places where we do a lot of misaligned accesses that trigger it, and
rework the code as needed. But in general, except for these cases, we're fine.
-116
View File
@@ -1,116 +0,0 @@
Notes on the Ultrasparc MMUs
============================
First, a word of warning: the MMU was different in SPARCv8 (32bit)
implementations, and it was changed again on newer CPUs.
The Ultrasparc-II we are supporting for now is documented in the Ultrasparc
user manual. There were some minor changes in the Ultrasparc-III to accomodate
larger physical addresses. This was then standardized as JPS1, and Fujitsu
also implemented it.
Later on, the design was changed again, for example Ultrasparc T2 (UA2005
architecture) uses a different data structure format to enlarge, again, the
physical and virtual address tags.
For now te implementation is focused on Ultrasparc-II because that's what I
have at hand, later on we will need support for the more recent systems.
Ultrasparc-II MMU
=================
There are actually two separate units for the instruction and data address
spaces, known as I-MMU and D-MMU. They each implement a TLB (translation
lookaside buffer) for the recently accessed pages.
This is pretty much all there is to the MMU hardware. No hardware page table
walk is provided. However, there is some support for implementing a TSB
(Translation Storage Buffer) in the form of providing a way to compute an
address into that buffer where the data for a missing page could be.
It is up to software to manage the TSB (globally or per-process) and in general
keep track of the mappings. This means we are relatively free to manage things
however we want, as long as eventually we can feed the iTLB and dTLB with the
relevant data from the MMU trap handler.
To make sure we can handle the fault without recursing, we need to pin a few
items in place:
In the TLB:
- TLB miss handler code
- TSB and any linked data that the TLB miss handler may need
- asynchronous trap handlers and data
In the TSB:
- TSB-miss handling code
- Interrupt handlers code and data
So, from a given virtual address (assuming we are using only 8K pages and a
512 entry TSB to keep things simple):
VA63-44 are unused and must be a sign extension of bit 43
VA43-22 are the 'tag' used to match a TSB entry with a virtual address
VA21-13 are the offset in the TSB at which to find a candidate entry
VA12-0 are the offset in the 8K page, and used to form PA12-0 for the access
Inside the TLBs, VA63-13 is stored, so there can be multiple entries matching
the same tag active at the same time, even when there is only one in the TSB.
The entries are rotated using a simple LRU scheme, unless they are locked of
course. Be careful to not fill a TLB with only locked entries! Also one must
take care of not inserting a new mapping for a given VA without first removing
any possible previous one (no need to worry about this when handling a TLB
miss however, as in that case we obviously know that there was no previous
entry).
Entries also have a "context". This could for example be mapped to the process
ID, allowing to easily clear all entries related to a specific context.
TSB entries format
==================
Each entry is composed of two 64bit values: "Tag" and "Data". The data uses the
same format as the TLB entries, however the tag is different.
They are as follow:
Tag
---
Bit 63: 'G' indicating a global entry, the context should be ignored.
Bits 60-48: context ID (13 bits)
Bits 41-0: VA63-22 as the 'tag' to identify this entry
Data
----
Bit 63: 'V' indicating a valid entry, if it's 0 the entry is unused.
Bits 62-61: size: 8K, 64K, 512K, 4MB
Bit 60: NFO, indicating No Fault Only
Bit 59: Invert Endianness of accesses to this page
Bits 58-50: reserved for use by software
Bits 49-41: reserved for diagnostics
Bits 40-13: Physical Address<40-13>
Bits 12-7: reserved for use by software
Bit 6: Lock in TLB
Bit 5: Cachable physical
Bit 4: Cachable virtual
Bit 3: Access has side effects (HW is mapped here, or DMA shared RAM)
Bit 2: Privileged
Bit 1: Writable
Bit 0: Global
TLB internal tag
----------------
Bits 63-13: VA<63-13>
Bits 12-0: context ID
Conveniently, a 512 entries TSB fits exactly in a 8K page, so it can be locked
in the TLB with a single entry there. However, it may be a wise idea to instead
map 64K (or more) of RAM locked as a single entry for all the things that needs
to be accessed by the TLB miss trap handler, so we minimize the use of TLB
entries.
Likewise, it may be useful to use 64K pages instead of 8K whenever possible.
The hardware provides some support for mixing the two sizes but it makes things
a bit more complex. Let's start out with simpler things.
@@ -1,97 +0,0 @@
Openboot is Sun's implementation of Open Firmware. So we should be able to share
a lot of code with the PowerPC port. There are some differences however.
Executable format
=================
PowerPC uses COFF. Sparc uses a.out, which is a lot simpler. According to the
spec, some fields should be zeroed out, but they say implementation may chose
to allow other values, so a standard a.out file works as well.
It used to be possible to generate one with objcopy, but support was removed,
so we now use elf2aout (imported from FreeBSD).
The file is first loaded at 4000, then relocated to its load address (we use
202000 and executed there)
Openfirmware prompt
===================
To get the prompt on display, use STOP+A at boot until you get the "ok" prompt.
On some machines, if no keyboard is detected, the ROM will assume it is set up
in headless mode, and will expect a BREAK+A on the serial port.
STOP+N resets all variables to default values (in case you messed up input or
output, for example).
Useful commands
===============
Disable autoboot to get to the openboot prompt and stop there
-------------------------------------------------------------
setenv auto-boot? false
Configuring for keyboard/framebuffer io
---------------------------------------
setenv screen-#columns 160
setenv screen-#rows 49
setenv output-device screen:r1920x1080x60
setenv input-device keyboard
Configuring openboot for serial port
------------------------------------
setenv ttya-mode 38400,8,n,1,-
setenv output-device ttya
setenv input-device ttya
reset
Boot from network
-----------------
static ip:
This currently works best, because rarp does not let the called binary know the
IP address. We need the IP address if we want to mount the root filesystem using
remote_disk server.
boot net:192.168.1.2,somefile,192.168.1.89
The first IP is the server from which to download (using TFTP), the second is
the client IP to use. Once the bootloader starts, it will detect that it is
booted from network and look for a the remote_disk_server on the same machine.
rarp:
This needs a reverse ARP server (easy to setup on any Linux system). You need
to list the MAC address of the SPARC machine in /etc/ethers on the server. The
machine will get its IP, and will use TFTP to the server which replied, to get
the boot file from there.
boot net:,somefile
(net is an alias to the network card and also sets the load address: /pci@1f,4000/network@1,1)
dhcp:
This needs a DHCP/BOOTP server configured to send the info about where to find
the file to load and boot.
boot net:dhcp
Debugging
---------
202000 dis (disassemble starting at 202000 until next return instruction)
4000 1000 dump (dump 1000 bytes from address 4000)
.registers (show global registers)
.locals (show local/windowed registers)
%pc dis (disassemble code being exectuted)
ctrace (backtrace)
+335
View File
@@ -0,0 +1,335 @@
The SPARC port
##############
The SPARC port targets various machines from Sun product lineup. The initial effort is on the
Ultra 60 and Ultra 5, with plans to latter add the Sun T5120 and its newer CPU. This may change
depending on hardware donations and developer interest.
Support for 32-bit versions of SPARC is currently not planned.
SPARC ABI
=========
The SPARC architecture has 32 integer registers, divided as follows:
- global registers (g0-g7)
- input (i0-i7)
- local (l0-l7)
- output (o0-o7)
Parameter passing and return is done using the output registers, which are
generally considered scratch registers and can be corrupted by the callee. The
caller must take care of preserving them.
The input and local registers are callee-saved, but we have hardware assistance
in the form of a register window. There is an instruction to shift the registers
so that:
- o registers become i registers
- local and output registers are replaced with fresh sets, for use by the
current function
- global registers are not affected
Note that as a side-effect, o7 is moved to i7, this is convenient because these
are usually the stack and frame pointers, respectively. So basically this sets
the frame pointer for free.
Simple enough functions may end up using just the o registers, in that case
nothing special is necessary, of course.
When shifting the register window, the extra registers come from the register
stack in the CPU. This is not infinite, however, most implementations of SPARC
will only have 8 windows available. When the internal stack is full, an overflow
trap is raised, and the handler must free up old windows by storing them on the
stack, likewise, when the internal stack is empty, an underflow trap must fill
it back from the stack-saved data.
Misaligned memory access
========================
The SPARC CPU is not designed to gracefully handle misaligned accesses.
You can access a single byte at any address, but 16-bit access only at even
addresses, 32bit access at multiple of 4 addresses, etc.
For example, on x86, such accesses are not a problem, it is allowed and handled
directly by the instructions doing the access. So there is no performance cost.
On SPARC, however, such accesses will cause a SIGBUS. This means a trap handler
has to catch the misaligned access and do it in software, byte by byte, then
give back control to the application. This is, of course, very slow, so we
should avoid it when possible.
Fortunately, gcc knows about this, and will normally do the right thing:
- For usual variables and structures, it will make sure to lay them out so that
they are aligned. It relies on stack alignment, as well as malloc returning
sufficiently aligned memory (as required by the C standard).
- On packed structure, gcc knows the data is misaligned, and will automatically
use the appropriate way to access it (most likely, byte-by-byte).
This leaves us with two undesirable cases:
- Pointer arithmetics and casting. When computing addresses manually, it's
possible to generate a misaligned address and cast it to a type with a wider
alignment requirement. In this case, gcc may access the pointer using a
multi byte instruction and cause a SIGBUS. Solution: make sure the struct
is aligned, or declare it as packed so unaligned access are used instead.
- Access to hardware: it is a common pattern to declare a struct as packed,
and map it to hardware registers. If the alignment isn't known, gcc will use
byte by byte access. It seems volatile would cause gcc to use the proper way
to access the struct, assuming that a volatile value is necessarily
aligned as it should.
In the end, we just need to be careful about pointer math resulting in unalined
access. -Wcast-align helps with that, but it also raises a lot of false positives
(where the alignment is preserved even when casting to other types). So we
enable it only as a warning for now. We will need to ceck the sigbus handler to
identify places where we do a lot of misaligned accesses that trigger it, and
rework the code as needed. But in general, except for these cases, we're fine.
The Ultrasparc MMUs
============================
First, a word of warning: the MMU was different in SPARCv8 (32bit)
implementations, and it was changed again on newer CPUs.
The Ultrasparc-II we are supporting for now is documented in the Ultrasparc
user manual. There were some minor changes in the Ultrasparc-III to accomodate
larger physical addresses. This was then standardized as JPS1, and Fujitsu
also implemented it.
Later on, the design was changed again, for example Ultrasparc T2 (UA2005
architecture) uses a different data structure format to enlarge, again, the
physical and virtual address tags.
For now te implementation is focused on Ultrasparc-II because that's what I
have at hand, later on we will need support for the more recent systems.
Ultrasparc-II MMU
-----------------
There are actually two separate units for the instruction and data address
spaces, known as I-MMU and D-MMU. They each implement a TLB (translation
lookaside buffer) for the recently accessed pages.
This is pretty much all there is to the MMU hardware. No hardware page table
walk is provided. However, there is some support for implementing a TSB
(Translation Storage Buffer) in the form of providing a way to compute an
address into that buffer where the data for a missing page could be.
It is up to software to manage the TSB (globally or per-process) and in general
keep track of the mappings. This means we are relatively free to manage things
however we want, as long as eventually we can feed the iTLB and dTLB with the
relevant data from the MMU trap handler.
To make sure we can handle the fault without recursing, we need to pin a few
items in place:
In the TLB:
- TLB miss handler code
- TSB and any linked data that the TLB miss handler may need
- asynchronous trap handlers and data
In the TSB:
- TSB-miss handling code
- Interrupt handlers code and data
So, from a given virtual address (assuming we are using only 8K pages and a
512 entry TSB to keep things simple):
VA63-44 are unused and must be a sign extension of bit 43
VA43-22 are the 'tag' used to match a TSB entry with a virtual address
VA21-13 are the offset in the TSB at which to find a candidate entry
VA12-0 are the offset in the 8K page, and used to form PA12-0 for the access
Inside the TLBs, VA63-13 is stored, so there can be multiple entries matching
the same tag active at the same time, even when there is only one in the TSB.
The entries are rotated using a simple LRU scheme, unless they are locked of
course. Be careful to not fill a TLB with only locked entries! Also one must
take care of not inserting a new mapping for a given VA without first removing
any possible previous one (no need to worry about this when handling a TLB
miss however, as in that case we obviously know that there was no previous
entry).
Entries also have a "context". This could for example be mapped to the process
ID, allowing to easily clear all entries related to a specific context.
TSB entries format
------------------
Each entry is composed of two 64bit values: "Tag" and "Data". The data uses the
same format as the TLB entries, however the tag is different.
They are as follow:
Tag
***
Bit 63: 'G' indicating a global entry, the context should be ignored.
Bits 60-48: context ID (13 bits)
Bits 41-0: VA63-22 as the 'tag' to identify this entry
Data
****
Bit 63: 'V' indicating a valid entry, if it's 0 the entry is unused.
Bits 62-61: size: 8K, 64K, 512K, 4MB
Bit 60: NFO, indicating No Fault Only
Bit 59: Invert Endianness of accesses to this page
Bits 58-50: reserved for use by software
Bits 49-41: reserved for diagnostics
Bits 40-13: Physical Address<40-13>
Bits 12-7: reserved for use by software
Bit 6: Lock in TLB
Bit 5: Cachable physical
Bit 4: Cachable virtual
Bit 3: Access has side effects (HW is mapped here, or DMA shared RAM)
Bit 2: Privileged
Bit 1: Writable
Bit 0: Global
TLB internal tag
****************
Bits 63-13: VA<63-13>
Bits 12-0: context ID
Conveniently, a 512 entries TSB fits exactly in a 8K page, so it can be locked
in the TLB with a single entry there. However, it may be a wise idea to instead
map 64K (or more) of RAM locked as a single entry for all the things that needs
to be accessed by the TLB miss trap handler, so we minimize the use of TLB
entries.
Likewise, it may be useful to use 64K pages instead of 8K whenever possible.
The hardware provides some support for mixing the two sizes but it makes things
a bit more complex. Let's start out with simpler things.
Software floating-point support
===============================
The SPARC instruction set specifies instruction for handling long double
values, however, no hardware implementation actually provides them. They
generate a trap, which is expected to be handled by the softfloat library.
Since traps are slow, and gcc knows better, it will never generate those
instructions. Instead it directly calls into the C library, to functions
specified in the ABI and used to do long double math using softfloats.
The support code for this is, in our case, compiled into both the kernel and
libroot. It lives in src/system/libroot/os/arch/sparc/softfloat.c (and other
support files). This code was extracted from FreeBSD, rather than the glibc,
because that made it much easier to get it building in the kernel.
Openboot bootloader
===================
Openboot is Sun's implementation of Open Firmware. So we should be able to share
a lot of code with the PowerPC port. There are some differences however.
Executable format
-----------------
PowerPC uses COFF. Sparc uses a.out, which is a lot simpler. According to the
spec, some fields should be zeroed out, but they say implementation may chose
to allow other values, so a standard a.out file works as well.
It used to be possible to generate one with objcopy, but support was removed,
so we now use elf2aout (imported from FreeBSD).
The file is first loaded at 4000, then relocated to its load address (we use
202000 and executed there)
Openfirmware prompt
-------------------
To get the prompt on display, use STOP+A at boot until you get the "ok" prompt.
On some machines, if no keyboard is detected, the ROM will assume it is set up
in headless mode, and will expect a BREAK+A on the serial port.
STOP+N resets all variables to default values (in case you messed up input or
output, for example).
Useful commands
---------------
Disable autoboot to get to the openboot prompt and stop there
.. code-block:: text
setenv auto-boot? false
Configuring for keyboard/framebuffer io
.. code-block:: text
setenv screen-#columns 160
setenv screen-#rows 49
setenv output-device screen:r1920x1080x60
setenv input-device keyboard
Configuring openboot for serial port
.. code-block:: text
setenv ttya-mode 38400,8,n,1,-
setenv output-device ttya
setenv input-device ttya
reset
Boot from network
-----------------
static ip
*********
This currently works best, because rarp does not let the called binary know the
IP address. We need the IP address if we want to mount the root filesystem using
remote_disk server.
.. code-block:: text
boot net:192.168.1.2,somefile,192.168.1.89
The first IP is the server from which to download (using TFTP), the second is
the client IP to use. Once the bootloader starts, it will detect that it is
booted from network and look for a the remote_disk_server on the same machine.
rarp
****
This needs a reverse ARP server (easy to setup on any Linux system). You need
to list the MAC address of the SPARC machine in /etc/ethers on the server. The
machine will get its IP, and will use TFTP to the server which replied, to get
the boot file from there.
.. code-block:: text
boot net:,somefile
(net is an alias to the network card and also sets the load address: /pci@1f,4000/network@1,1)
dhcp
****
This needs a DHCP/BOOTP server configured to send the info about where to find
the file to load and boot.
.. code-block:: text
boot net:dhcp
Debugging
---------
.. code-block:: text
202000 dis (disassemble starting at 202000 until next return instruction)
4000 1000 dump (dump 1000 bytes from address 4000)
.registers (show global registers)
.locals (show local/windowed registers)
%pc dis (disassemble code being exectuted)
ctrace (backtrace)
@@ -1,12 +0,0 @@
The SPARC instruction set specifies instruction for handling long double
values, however, no hardware implementation actually provides them. They
generate a trap, which is expected to be handled by the softfloat library.
Since traps are slow, and gcc knows better, it will never generate those
instructions. Instead it directly calls into the C library, to functions
specified in the ABI and used to do long double math using softfloats.
The support code for this is, in our case, compiled into both the kernel and
libroot. It lives in src/system/libroot/os/arch/sparc/softfloat.c (and other
support files). This code was extracted from FreeBSD, rather than the glibc,
because that made it much easier to get it building in the kernel.
@@ -1,93 +0,0 @@
# Bootloader debugging with GEF
When Haiku's early boot process is experiencing unknown crashes or faults, it can be extremely
difficult to troubleshoot (especially when serial, video, or other i/o devices are non-functional)
It **is** possible to step through the boot of any architecture of Haiku in a debugger if the system
boots and the issue can be reproduced in qemu.
> This works for any architecture and is _extremely_ helpful to trouble early platforms. Linux or Mac OS
> are requirements. You need a full POSIX environment.
## Building Haiku
On most non-x86 platforms, you will need a "kernel" (haiku_loader) and an "initrd" (haiku_floppyboot).
For arm/arm64: ```jam -q @minimum-mmc```
## Launching Haiku in QEMU
In the example below, we will prepare Haiku arm in QEMU for debugging.
```
qemu-system-arm -M raspi2 -kernel haiku_loader.u-boot -initrd haiku-floppyboot.tgz.u-boot -serial stdio -m 2G -dtb rpi2.dtb -s -S
```
**Key Flags:**
* **-s**
* Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234.
* **-S**
* Do not start CPU at startup (you must type 'c' in the monitor).
These simple flags will make qemu listen for a debugger connection on localhost:1234 and have the VM not start until you tell it to.
> In the example above, we are Emulating a Raspberry Pi 2, and using our Raspberry Pi 2 dtb. If you don't have a dtb for the machine
> you're emulating, you can dump qemu's internal dtb by adding ```-M dumpdtb=myboard.dtb``` to the end of your qemu command.
## Attaching GEF
[GEF](https://github.com/hugsy/gef) is an enhanced debugger which works extremely well for debugging code running in virtual machines.
It piggy-backs on gdb and offers a lot of valueable insight at a glance without requiring to know every gdb command.
Once GEF is installed, we can step through the process to attach gdb to qemu.
### Open gdb with our symbols.
First we run gdb pointed at our boot loader. We use the native ELF binary as that seems to give gdb/gef the most accurate knowledge
of our symbols. (the haiku_loader.u-boot is wrapped by u-boot's mkimage, your milage may vary based on platform)
```gdb objects/haiku/arm/release/system/boot/u-boot/boot_loader_u-boot```
### Set the architecture
This may not be required, but re-enforces to gef/gdb that we're working on arm.
```set architecture arm```
### Connect to QEMU
Now we tell gdb/gef about out running (but paused) QEMU instance.
```gef-remote -q localhost:1234```
A successful connection should occur.
### Step into debugging
Before you begin execution, it's handy to set a *breakpoint*. A *breakpoint* tells gdb/gef where it should pause execution to begin
the debugging process. All of our bootloaders start in a ```start_gen``` function, so this is a good place to start.
```breakpoint start_gen```
Now that a breakpoint is defined, lets run the virtual machine.
In gef, type ```continue```.
If everything is working as expected, you should now be "paused" at the ```start_gen``` function (hopefully showing the C/C++ code).
Now, you have a few commands to leverage:
* **step**
* Take a single step forward and execute the code listed.
* Does **not** step "into" functions, just over them getting the return from the code.
* Alias: s
* **stepi**
* step forward "into" the next code.
* If you're on a function it will enter the function and show the code executed.
* **break**
* add additional "breakpoints" where you can step through the code execution.
* **continue**
* Resume execution.
* If you have no additional breakpoints the code will "go do what it's supposed to"
* Alias: c
@@ -0,0 +1,137 @@
Bootloader debugging with GEF
=============================
When Haikus early boot process is experiencing unknown crashes or
faults, it can be extremely difficult to troubleshoot (especially when
serial, video, or other i/o devices are non-functional)
It **is** possible to step through the boot of any architecture of Haiku
in a debugger if the system boots and the issue can be reproduced in
qemu.
This works for any architecture and is *extremely* helpful to trouble
early platforms. Linux or Mac OS are requirements. You need a full
POSIX environment.
Building Haiku
--------------
On most non-x86 platforms, you will need a “kernel” (haiku_loader) and
an “initrd” (haiku_floppyboot).
For arm/arm64: ``jam -q @minimum-mmc``
Launching Haiku in QEMU
-----------------------
In the example below, we will prepare Haiku arm in QEMU for debugging.
::
qemu-system-arm -M raspi2 -kernel haiku_loader.u-boot -initrd haiku-floppyboot.tgz.u-boot -serial stdio -m 2G -dtb rpi2.dtb -s -S
**Key Flags:**
- **-s**
- Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port
1234.
- **-S**
- Do not start CPU at startup (you must type c in the monitor).
These simple flags will make qemu listen for a debugger connection on
localhost:1234 and have the VM not start until you tell it to.
In the example above, we are Emulating a Raspberry Pi 2, and using
our Raspberry Pi 2 dtb. If you dont have a dtb for the machine
youre emulating, you can dump qemus internal dtb by adding
``-M dumpdtb=myboard.dtb`` to the end of your qemu command.
Attaching GEF
-------------
`GEF <https://github.com/hugsy/gef>`__ is an enhanced debugger which
works extremely well for debugging code running in virtual machines. It
piggy-backs on gdb and offers a lot of valueable insight at a glance
without requiring to know every gdb command.
Once GEF is installed, we can step through the process to attach gdb to
qemu.
Open gdb with our symbols.
~~~~~~~~~~~~~~~~~~~~~~~~~~
First we run gdb pointed at our boot loader. We use the native ELF
binary as that seems to give gdb/gef the most accurate knowledge of our
symbols. (the haiku_loader.u-boot is wrapped by u-boots mkimage, your
milage may vary based on platform)
``gdb objects/haiku/arm/release/system/boot/u-boot/boot_loader_u-boot``
Set the architecture
~~~~~~~~~~~~~~~~~~~~
This may not be required, but re-enforces to gef/gdb that were working
on arm.
``set architecture arm``
Connect to QEMU
~~~~~~~~~~~~~~~
Now we tell gdb/gef about out running (but paused) QEMU instance.
``gef-remote -q localhost:1234``
A successful connection should occur.
Step into debugging
~~~~~~~~~~~~~~~~~~~
Before you begin execution, its handy to set a *breakpoint*. A
*breakpoint* tells gdb/gef where it should pause execution to begin the
debugging process. All of our bootloaders start in a ``start_gen``
function, so this is a good place to start.
``breakpoint start_gen``
Now that a breakpoint is defined, lets run the virtual machine.
In gef, type ``continue``.
If everything is working as expected, you should now be “paused” at the
``start_gen`` function (hopefully showing the C/C++ code).
Now, you have a few commands to leverage:
- **step**
- Take a single step forward and execute the code listed.
- Does **not** step “into” functions, just over them getting the
return from the code.
- Alias: s
- **stepi**
- step forward “into” the next code.
- If youre on a function it will enter the function and show the
code executed.
- **break**
- add additional “breakpoints” where you can step through the code
execution.
- **continue**
- Resume execution.
- If you have no additional breakpoints the code will “go do what
its supposed to”
- Alias: c
- **next**
- Resume execution until it reaches the next line of code.
- Useful for example to run until a loop is completed, and stop at the first line after that loop.
@@ -1,165 +0,0 @@
<body bgcolor=white>
<h1>Haiku boot process specification</h1>
<h6>
Creation Date: November 23, 2002<br>
Version: 2.0 (Jan 22, 2021)<br>
Status: documenting the current state of things<br>
Author(s): Axel D&ouml;rfler, Adrien Destugues
</h6>
<h2>Overview</h2>
<p>Unlike other systems, Haiku comes with its own user-friendly bootloader. The main task of
the bootloader is to load and start the kernel. We don't have a concept of an initramfs as
Linux does, instead our bootloader is able to find the kernel and modules in a BFS partition,
and even extract them from packages as needed. It also provides an early boot menu that can
be used to change settings, boot older versions of Haiku that were snapshotted by the package
system, and write boot logs to USB mass storage.</p>
<h2>Booting from BIOS</h2>
<p>
Haiku BIOS boot loader process is split into 3 different stages. Since the second
stage is bound tightly to both other stages (which are independent from each other),
it is referred to as stage 1.5, whereas the other stages are referred to as stage 1
and 2. This architecture is used because the BIOS booting process only loads a very
small piece of code from disk for booting, insufficient for the needs outlined above.</p>
<p>The following will explain all stages in detail.</p>
<h3>Stage 1</h3>
<p>
The first stage is responsible for loading the real boot loader from a BFS disk. It
will be loaded by the Master Boot Record (MBR) and executed in the x86 real mode.
It is only used if the system will be booted directly from a BFS partition, it won't
be used at all if it is booted from a floppy disk or CD-ROM (in this case, stage
1.5 is in charge immediately).
</p>
<p>
It resides in the first 1024 bytes of a BFS disk which usually refers to the
first two sectors of the partition in question. Since the BFS superblock is located
at byte offset 512, and about 170 bytes large, this section is already reserved,
and thus cannot be used by the loader itself.<br>
The MBR only loads the first sector of a partition into memory, so it has to load
the superblock (and the rest of its implementation) by itself.
</p>
<p>
The loader must be able to load the real boot loader from a certain path, and
execute it. In BeOS this boot loader would be in "/boot/beos/system/zbeos",
in Haiku this is haiku_loader.bios_ia32 found in the haiku_loader package.<br>
Theoretically, it is enough to load the first few blocks from the loader, and
let the next stage then load the whole thing (which it has to do anyway if it
has been written on a floppy). This would be one possible optimization
if the 850 bytes of space are filled too early, but would require that "zbeos"
is written in one sequential block (which should be always the case anyway).
</p>
<h3>haiku_loader.bios_ia32</h3>
<p>
Contains both the stage 1.5 boot loader, and the compressed stage 2 loader.
It's not an ELF executable file; i.e. it can be directly written to a floppy
disk which would cause the BIOS to load the first 512 bytes of that file and
execute it.
</p>
<p>
Therefore, it will start with the stage 1.5 boot loader which will be loaded
either by the BIOS when it directly resides on the disk (for example when
loaded from a floppy disk), or the stage 1 boot loader, although this one
could have a different entry point than the BIOS.
</p>
<h3>Stage 1.5</h3>
<p>
Will have to load the rest of haiku_loader into memory (if not already done by the
stage 1 loader in case it has been loaded from a BFS disk), set up the global
descriptor table, switch to x86 protected mode, uncompress stage 2, and execute it.
</p>
<p>
This part is very similar to the stage 1 boot loader from NewOS.
</p>
<h3>Stage 2</h3>
<p>
This is the most complex part of the boot loader. In short, it has to load
any modules and devices the kernel needs to access the boot device, set up
the system, load the kernel, and execute it.
</p>
<p>
The kernel, and the modules and drivers needed are loaded from the boot
disk - therefore the loader has to be able to access BFS disks. It also
has to be able to load and parse the settings of these drivers (and the
kernel) from the boot disk, some of them are already important for the
boot loader itself (like "don't call the BIOS"). Since this stage is already
executed in protected mode, it has to use the virtual-86 mode to call the
BIOS and access any disk.
</p>
<p>
Before loading those files from the boot disk, it should look for additional
files located on a specific disk location after the "zbeos" file (on floppy disk
or CD-ROM). This way, it could access disks that cannot be accessed by the
BIOS itself.
</p>
<p>
Setting up the system for the kernel also means initalizing PCI devices needed
during the boot process before the kernel is up. It must be able to do so since
the BIOS might not have set up those devices correctly or at all.
</p>
<p>
It also must calculate a check sum for the boot device which the kernel can then
use to identify the boot volume and partition with - there is no other reliable
way to map BIOS disk IDs to the /dev/disk/... tree the system itself is using.
</p>
<p>
After having loaded and relocated the kernel, it executes it by passing a special
structure which tells the kernel things like the boot device check sum, which
modules are already loaded and where they are.
</p>
<p>
The stage 2 boot loader also includes user interaction. If the user presses a
special key during the boot process (like the space key, or some others as well),
a menu will be presented where the user can select the boot device (if several,
the loader has to scan for options), safe mode options, VESA mode, etc.
</p>
<p>
This menu may also come up if an error occured during the execution of the stage
2 loader.
</p>
<h2>Open Firmware</h2>
<p>On Open Firmware based systems, there is no need for a stage 1.5 because the firmware
does not give us as many constraints. Instead, the stage 2 is loaded directly by the firmware.
This requires converting the haiku_loader executable to the appropriate executable format
(a.out on sparc, pef on powerpc). The conversion is done using custom tools because binutils
does not support these formats anymore.</p>
<p>There is no notion of real and protected mode on non-x86 architectures, and the bootloader
is able to easily call Open Firmware methods to perform most tasks (disk access, network booting,
setting up the framebuffer) in a largely hardware-independent way.</p>
<h2>U-Boot</h2>
<p>U-Boot is able to load the stage2 loader directly from an ELF file. However, it does not
provide any other features. It is not possible for the bootloader to call into U-Boot APIs
for disk access, displaying messages on screen etc (while possible in theory, these features
are often disabled in U-Boot). This means haiku_loader would need to parse the FDT (describing
the available hardware) and bundle its own drivers for using the hardware. This approach is
not easy to set up, and it is recommended to instead use the UEFI support in U-Boot where
possible.</p>
<h2>EFI</h2>
<p>On EFI systems, there is no need for a stage1 loader as there is for BIOS. Instead, our stage2
loader (haiku_loader) can be executed directly from the EFI firmware.</p>
<p>The EFI firmware only knows how to run executables in the PE format
(as used by Windows) because Microsoft was involved in specifying it.
On x86_64, we can use binutils to output a PE file directly. But on other platforms, this is not
supported by binutils. So, what we do is generate a "fake" PE header and wrap our elf file inside
it. The bootloader then parses the embedded ELF header and relocates itself, so the other parts
of the code can be run.</p>
<p>After this initial loading phase, the process is very similar to the Open Firmware one. EFI
provides us with all the tools we need to do disk access and both text mode and framebuffer
output.</p>
</body>
@@ -0,0 +1,174 @@
Haiku boot process specification
================================
Creation Date: November 23, 2002
Version: 2.0 (Jan 22, 2021)
Status: documenting the current state of things
Author(s): Axel Dörfler, Adrien Destugues
Overview
--------
Unlike other systems, Haiku comes with its own user-friendly bootloader.
The main task of the bootloader is to load and start the kernel. We
don't have a concept of an initramfs as Linux does, instead our
bootloader is able to find the kernel and modules in a BFS partition,
and even extract them from packages as needed. It also provides an early
boot menu that can be used to change settings, boot older versions of
Haiku that were snapshotted by the package system, and write boot logs
to USB mass storage.
Booting from BIOS
-----------------
Haiku BIOS boot loader process is split into 3 different stages. Since
the second stage is bound tightly to both other stages (which are
independent from each other), it is referred to as stage 1.5, whereas
the other stages are referred to as stage 1 and 2. This architecture is
used because the BIOS booting process only loads a very small piece of
code from disk for booting, insufficient for the needs outlined above.
The following will explain all stages in detail.
Stage 1
~~~~~~~
The first stage is responsible for loading the real boot loader from a
BFS disk. It will be loaded by the Master Boot Record (MBR) and executed
in the x86 real mode. It is only used if the system will be booted
directly from a BFS partition, it won't be used at all if it is booted
from a floppy disk or CD-ROM (in this case, stage 1.5 is in charge
immediately).
| It resides in the first 1024 bytes of a BFS disk which usually refers
to the first two sectors of the partition in question. Since the BFS
superblock is located at byte offset 512, and about 170 bytes large,
this section is already reserved, and thus cannot be used by the
loader itself.
| The MBR only loads the first sector of a partition into memory, so it
has to load the superblock (and the rest of its implementation) by
itself.
| The loader must be able to load the real boot loader from a certain
path, and execute it. In BeOS this boot loader would be in
"/boot/beos/system/zbeos", in Haiku this is haiku_loader.bios_ia32
found in the haiku_loader package.
| Theoretically, it is enough to load the first few blocks from the
loader, and let the next stage then load the whole thing (which it has
to do anyway if it has been written on a floppy). This would be one
possible optimization if the 850 bytes of space are filled too early,
but would require that "zbeos" is written in one sequential block
(which should be always the case anyway).
haiku_loader.bios_ia32
~~~~~~~~~~~~~~~~~~~~~~
Contains both the stage 1.5 boot loader, and the compressed stage 2
loader. It's not an ELF executable file; i.e. it can be directly written
to a floppy disk which would cause the BIOS to load the first 512 bytes
of that file and execute it.
Therefore, it will start with the stage 1.5 boot loader which will be
loaded either by the BIOS when it directly resides on the disk (for
example when loaded from a floppy disk), or the stage 1 boot loader,
although this one could have a different entry point than the BIOS.
Stage 1.5
~~~~~~~~~
Will have to load the rest of haiku_loader into memory (if not already
done by the stage 1 loader in case it has been loaded from a BFS disk),
set up the global descriptor table, switch to x86 protected mode,
uncompress stage 2, and execute it.
This part is very similar to the stage 1 boot loader from NewOS.
Stage 2
~~~~~~~
This is the most complex part of the boot loader. In short, it has to
load any modules and devices the kernel needs to access the boot device,
set up the system, load the kernel, and execute it.
The kernel, and the modules and drivers needed are loaded from the boot
disk - therefore the loader has to be able to access BFS disks. It also
has to be able to load and parse the settings of these drivers (and the
kernel) from the boot disk, some of them are already important for the
boot loader itself (like "don't call the BIOS"). Since this stage is
already executed in protected mode, it has to use the virtual-86 mode to
call the BIOS and access any disk.
Before loading those files from the boot disk, it should look for
additional files located on a specific disk location after the "zbeos"
file (on floppy disk or CD-ROM). This way, it could access disks that
cannot be accessed by the BIOS itself.
Setting up the system for the kernel also means initalizing PCI devices
needed during the boot process before the kernel is up. It must be able
to do so since the BIOS might not have set up those devices correctly or
at all.
It also must calculate a check sum for the boot device which the kernel
can then use to identify the boot volume and partition with - there is
no other reliable way to map BIOS disk IDs to the /dev/disk/... tree the
system itself is using.
After having loaded and relocated the kernel, it executes it by passing
a special structure which tells the kernel things like the boot device
check sum, which modules are already loaded and where they are.
The stage 2 boot loader also includes user interaction. If the user
presses a special key during the boot process (like the space key, or
some others as well), a menu will be presented where the user can select
the boot device (if several, the loader has to scan for options), safe
mode options, VESA mode, etc.
This menu may also come up if an error occured during the execution of
the stage 2 loader.
Open Firmware
-------------
On Open Firmware based systems, there is no need for a stage 1.5 because
the firmware does not give us as many constraints. Instead, the stage 2
is loaded directly by the firmware. This requires converting the
haiku_loader executable to the appropriate executable format (a.out on
sparc, pef on powerpc). The conversion is done using custom tools
because binutils does not support these formats anymore.
There is no notion of real and protected mode on non-x86 architectures,
and the bootloader is able to easily call Open Firmware methods to
perform most tasks (disk access, network booting, setting up the
framebuffer) in a largely hardware-independent way.
U-Boot
------
U-Boot is able to load the stage2 loader directly from an ELF file.
However, it does not provide any other features. It is not possible for
the bootloader to call into U-Boot APIs for disk access, displaying
messages on screen etc (while possible in theory, these features are
often disabled in U-Boot). This means haiku_loader would need to parse
the FDT (describing the available hardware) and bundle its own drivers
for using the hardware. This approach is not easy to set up, and it is
recommended to instead use the UEFI support in U-Boot where possible.
EFI
---
On EFI systems, there is no need for a stage1 loader as there is for
BIOS. Instead, our stage2 loader (haiku_loader) can be executed directly
from the EFI firmware.
The EFI firmware only knows how to run executables in the PE format (as
used by Windows) because Microsoft was involved in specifying it. On
x86_64, we can use binutils to output a PE file directly. But on other
platforms, this is not supported by binutils. So, what we do is generate
a "fake" PE header and wrap our elf file inside it. The bootloader then
parses the embedded ELF header and relocates itself, so the other parts
of the code can be run.
After this initial loading phase, the process is very similar to the
Open Firmware one. EFI provides us with all the tools we need to do disk
access and both text mode and framebuffer output.
@@ -1,315 +0,0 @@
<html>
<body bgcolor=white>
<h1>Introduction to Haiku's Device Driver Architecture</h1>
<p>This document tries to give you a short introduction into the new device
manager, and how to write drivers for it. Haiku still supports the legacy
device driver architecture introduced with BeOS.</p>
<p>The new device driver architecture of Haiku is still a moving target,
although most of its details are already specificed.</p>
<h2>1. The Basics</h2>
<p>The device manager functionality builds upon <i>device_node</i> objects.
Every driver in the system publishes one or more of such nodes, building a
tree of device nodes. This tree is in theory a dynamic representation of the
current hardware devices in the system, but in practice will also contain
implementation specific details; since every node comes with an API specific
to that node, you'll find device nodes that only come with a number of support
functions for a certain class of drivers.</p>
<p>Structurally, a <i>device_node</i> is a set of a module, attributes,
and resources, as well as a parent and children. At a minimum, a node must
have a module, all other components are optional.</p>
TODO: picture of the device node tree
<p>When the system starts, there is only a root node registered. Only primary
hardware busses register with the root node, such as PCI, and ISA on x86.
Since the PCI bus is an intelligent bus, it knows what hardware is installed,
and registers a child node for each device on the bus.</p>
<p>Every driver can also publish a device in <i>/dev</i> for communication with
userland applications. All drivers and devices are kernel modules.</p>
<h2>2. Exploring the Device Tree</h2>
<p>So how does it all work? When building the initial device tree, the system only
explores a minimum of device drivers only, resulting in a tree that basically
only shows the hardware found in the computer.</p>
<p>Now, if the system requires disk access, it will scan the device file system
for a driver that provides such functionality, in this case, it will look for
drivers under "/dev/disk/". The device manager has a set of built-in rules for
how to translate a device path into a device node, and vice versa: every node
representing a device of an intelligent bus (such as PCI) will also contain
device type information following the PCI definitions. In this case, the "disk"
sub-path will translate into the <i>PCI_mass_storage</i> type, and hence, the
device manager will then completely explore all device nodes of that type.</p>
<p>It will also use that path information to only ask drivers that actually
are in a matching module directory. In the above example of a disk driver, this
would be either in "busses/scsi", "busses/ide", "drivers/disk", ...</p>
<p>For untyped or generic busses, it will use the context information gained
from the devfs query directly, and will search for drivers in that sub directory
only. The only exception to this rule are the devfs directories "disk", "ports",
and "bus", which will also allow to search matching drivers in "busses". While
this is relatively limited, it is a good way to cut down the number of drivers
to be loaded.</p>
<h2>3. Writing a Driver</h2>
<p>The device manager assumes the following API from a driver module:</p>
<ul>
<li><b>supports_device()</b><br>
Determines wether or not the driver supports a given parent device node,
that is the hardware device it represents (if any), and the API the node
exports.</li>
<li><b>register_device()</b><br>
The driver should register its device node here. The parent driver is
always initialized at this point. When registering the node, the driver
can also attach certain I/O resources (like I/O ports, or memory ranges)
to the node -- the device manager will make sure that only one node can
claim these resources.</li>
<li><b>init_driver()</b><br>
Any initialization necessary to get the driver going. For most drivers,
this will be reduced to the creation of a private data structure that is
going to be used for all of the following functions.</li>
<li><b>uninit_driver()</b><br>
Uninitializes resources acquired by <b>init_driver()</b>.</li>
<li><b>register_child_devices()</b><br>
If the driver wants to register any child device nodes or to publish
any devices, it should do so here. This function is called only during
the initial registration process of the device node.</li>
<li><b>rescan_child_devices()</b><br>
Is called whenever a manual rescan is triggered.</li>
<li><b>device_removed()</b></br>
Is called when the device node is about to be unregistered when its
device is gone, for example when a USB device is unplugged.</li>
<li><b>suspend()</b><br>
Enters different sleep modes.</li>
<li><b>resume()</b><br>
Resumes a device from a previous sleep mode.</li>
</ul>
<p>To ensure that a module exports this API, it <b>must</b> end its module name
with "driver_v1" to denote the version of the API it supports. Note that
<b>suspend()</b> and <b>resume()</b> are currently never called, as Haiku has
no power management implemented yet.</p>
<p>If your driver can give the device it is attached to a nice name that can be
presented to the user, it should add the <b>B_DEVICE_PRETTY_NAME</b> attribute
to the device node.
<p>The <b>B_DEVICE_UNIQUE_ID</b> should be used in case the device has a unique
ID that can be used to identify it, and also differentiate it from other devices
of the same model and vendor. This information will be added to the file system
attributes of all devices published by your driver, so that user applications
can identify, say, a USB printer no matter what USB slot it is attached to, and
assign it additional data, like paper configuration, or recognize it as the
default printer.</p>
<p>If your driver implements an API that is used by a support or bus module, you
will usually use the <b>B_DEVICE_FIXED_CHILD</b> attribute to specify exactly
which child device node you will be talking to. If you support several child
nodes, you may want to have a closer look at the section explaining
<a href="#bus_driver">how to write a bus driver</a>.</p>
<p>In addition to the child nodes a driver registers itself, a driver can either
have dynamic children or fixed children, never both. Also, fixed children are
registered before <b>register_child_devices()</b> is called, while dynamic
children are registered afterwards.</p>
<h2>4. Publishing a Device</h2>
To publish a device entry in the device file system under <i>/dev</i>, all your
driver has to do is to call the
<pre>
publish_device(device_node *node, const char *path,
const char *deviceModuleName);
</pre>
function the device manager module exports. The <i>path</i> is the path
component that follows "/dev", for example "net/ipro1000/0". The
<i>deviceModuleName</i> is the module exporting the device functionality.
It should end with "device_v1" to show the device manager which protocol it
supports. If the device node your device belongs to is removed, your device
is removed automatically with it. On the other hand, you are allowed to
unpublish the device at any point using the <b>unpublish_device()</b> function
the device manager delivers for this.</p>
<p>A device module must export the following API:</p>
<ul>
<li><b>init_device()</b><br>
This is called when the open() is called on this device for the first
time. You may want to create a private data structure that is passed on
to all subsequent calls of the <b>open()</b> function that your device
exports.</li>
<li><b>uninit_device()</b><br>
Is called when the last file descriptor to the device had been closed.</li>
<li><b>device_removed()</b><br>
When the device node your device belongs to is going to be removed,
you're notified about this in this function.</li>
<li><b>open()</b><br>
Called whenever your device is opened.</li>
<li><b>close()</b><br>
</li>
<li><b>free()</b><br>
Free the private data structure you allocated in <b>open()</b>.</li>
<li><b>read()</b><br>
</li>
<li><b>write()</b><br>
</li>
<li><b>io()</b><br>
This is a replacement for the <b>read()</b>, and <b>write()</b> calls,
and allows, among other things, for asynchronous I/O. This functionality
has not yet been implemented, though (see below).</li>
<li><b>control()</b><br>
</li>
<li><b>select()</b><br>
</li>
<li><b>deselect()</b><br>
</li>
</ul>
<h2>5. <a name="bus_driver">Writing a Bus Driver</a></h2>
<p>A bus driver is a driver that represents a bus where one or more arbitrary
devices can be attached to.</p>
<p>There are two basic types of busses: intelligent busses like PCI or USB that
know a lot about the devices attached to it, like a generic device type, as
well as device and vendor ID information, and simple untyped/generic busses that
either have not all the information (like device type) or don't even know what
and if any devices are attached. The device manager has been written in such a
way that device exploration makes use of additional information the bus can
provide in order to find a responsible device driver faster, and with less
overhead.</p>
<h4>5.1. Writing an Intelligent Bus Driver</h4>
<p>If your bus knows what type of device is attached to, and also has vendor and
device ID information about that device, it is considered to be an intelligent
bus. The bus driver is supposed to have one parent node representing the bus,
and to create a child node for each device attached to the bus.</p>
<p>The additional information you have about the devices are attached to the
device node in the following attributes:</p>
<ul>
<li><b>B_DEVICE_VENDOR_ID</b><br>
The vendor ID - this ID has only to be valid in the namespace of your
bus.</li>
<li><b>B_DEVICE_ID</b><br>
The device ID.</li>
<li><b>B_DEVICE_TYPE</b><br>
The device type as defined by the PCI class base information.</li>
<li><b>B_DEVICE_SUB_TYPE</b><br>
The device sub type as defined by the PCI sub class information.</li>
<li><b>B_DEVICE_INTERFACE</b><br>
The device interface type as defined by the PCI class API information.</li>
</ul>
<p>You can use the <b>B_DEVICE_FLAGS</b> attribute to define how the device
manager finds the children of the devices you exported. For this kind of bus
drivers, you will usually only want to specify <b>B_FIND_CHILD_ON_DEMAND</b>
here, which causes the driver only to be searched when the system asks for it.
</p>
<h4>5.2. Writing a Simple Bus Driver</h4>
<p>A bus can be simple in a number of ways:</p>
<ol>
<li>It may not know how many or if any devices are attached to it</li>
<li>It cannot retrieve any type information about the devices it has, but
knows all devices that are attached to it</li>
</ol>
<p>An example of the latter would be the Zorro bus of the Amiga - it only has
information about the vendor and device ID, but no type information. It should
be implemented like an intelligent bus, though, with the type information simply
omitted.</p>
<p>Therefore, this section is about the former case, that is, a simple bus like
the ISA bus. Since it doesn't know anything about its children, it does not
publish any child nodes, instead, it will just specify the
B_FIND_MULTIPLE_CHILDREN and B_FIND_CHILD_ON_DEMAND flags for its device node.
Since there is no additional information about this bus, the device manager
will assume a simple bus, and will try to find drivers on demand only.</p>
<h2>The generic bus</h2>
Some devices are not tied to a specific bus. This is the case for all drivers
that do not relate to a physical device: /dev/null, /dev/zero, /dev/random,
etc.
A "generic" bus has been added, and these drivers can attach to it.
<h2>6. Open Issues</h2>
While most of the new device manager is fledged out, there are some areas that
could use improvements or are problematic under certain requirements. Also, some
parts just haven't been written yet.
<h4>6.1. generic/simple busses</h4>
<h4>6.2. Unpublishing</h4>
<h4>6.4. Versioning</h4>
<p>The way the device manager works, it makes versioning of modules (which are
supposed to be one of the strong points of the module system) much harder or
even impossible. While the device manager could introduce a new API and could
translate between a "driver_v1", and a "driver_v2" API on the fly, it's not
yet possible for a PCI sub module to do the same thing.</p>
<p><b>Proposed Solution:</b> Add attribute <b>B_DEVICE_ALTERNATE_VERSION</b>
that specifies alternate versions of the module API this device node supports.
We would then need a <b>request_version()</b> or <b>set_version()</b> function
(to be called from <b>supports_device()</b>) that allows to specify the version
of the parent node this device node wants to talk to.</p>
<h4>6.5. Unregistering Nodes</h4>
<h4>6.6. Support for generic drivers is missing</h4>
<p>This should probably be done by simply adding a simple bus driver named
"generic" that generic drivers need to ask for.</p>
<h4>6.7. Mappings, And Other Optimizations</h4>
<p>Due to the way the device tree is built, the device manager could remember
which driver served a given device node. That way, it wouldn't need to search
for a driver anymore, but could just pick it up. Practically, the device manager
should cache the type (and/or vendor/device) information of a node, and assign
one or more drivers (via module name) to this information. It should also
remember negative outcome, that is if there is no driver supporting the
hardware.</p>
<p>This way, only the first boot would require an actual search for drivers, as
subsequent boots would reuse the type-driver assignments. If a new driver is
installed, the cached assignments would need to be updated immediately. If a
driver has been installed outside of the running system, the device manager
might want to create a hash per module directory to see if anything changed to
flush the cache. Alternatively or additionally, the boot loader could have a
menu causing the cache to be ignored.</p>
<p>It would be nice to find a way for generic and simple busses to reduce the
amount of searching necessary for them. One way would be to remember which
driver supports which bus - but this information is currently only accessible
derived from what the driver does, and is therefore not reliable or complete.
A separately exported information would be necessary for this.</p>
<p>Also, when looking for a generic or simple bus driver, actual directories
could be omitted; currently, driver search is always recursive, as that's how
the module mechanism is working. Eventually, we might want to extend the
open_module_list_etc() call a bit more to accomplish that.</p>
</body>
</html>
@@ -0,0 +1,325 @@
Device Driver Architecture
==================================================
This document tries to give you a short introduction into the new device
manager, and how to write drivers for it. Haiku still supports the
legacy device driver architecture introduced with BeOS.
The new device driver architecture of Haiku is still a moving target,
although most of its details are already specificed.
1. The Basics
-------------
The device manager functionality builds upon *device_node* objects.
Every driver in the system publishes one or more of such nodes, building
a tree of device nodes. This tree is in theory a dynamic representation
of the current hardware devices in the system, but in practice will also
contain implementation specific details; since every node comes with an
API specific to that node, you'll find device nodes that only come with
a number of support functions for a certain class of drivers.
Structurally, a *device_node* is a set of a module, attributes, and
resources, as well as a parent and children. At a minimum, a node must
have a module, all other components are optional.
TODO: picture of the device node tree
When the system starts, there is only a root node registered. Only
primary hardware busses register with the root node, such as PCI, and
ISA on x86. Since the PCI bus is an intelligent bus, it knows what
hardware is installed, and registers a child node for each device on the
bus.
Every driver can also publish a device in */dev* for communication with
userland applications. All drivers and devices are kernel modules.
2. Exploring the Device Tree
----------------------------
So how does it all work? When building the initial device tree, the
system only explores a minimum of device drivers only, resulting in a
tree that basically only shows the hardware found in the computer.
Now, if the system requires disk access, it will scan the device file
system for a driver that provides such functionality, in this case, it
will look for drivers under "/dev/disk/". The device manager has a set
of built-in rules for how to translate a device path into a device node,
and vice versa: every node representing a device of an intelligent bus
(such as PCI) will also contain device type information following the
PCI definitions. In this case, the "disk" sub-path will translate into
the *PCI_mass_storage* type, and hence, the device manager will then
completely explore all device nodes of that type.
It will also use that path information to only ask drivers that actually
are in a matching module directory. In the above example of a disk
driver, this would be either in "busses/scsi", "busses/ide",
"drivers/disk", ...
For untyped or generic busses, it will use the context information
gained from the devfs query directly, and will search for drivers in
that sub directory only. The only exception to this rule are the devfs
directories "disk", "ports", and "bus", which will also allow to search
matching drivers in "busses". While this is relatively limited, it is a
good way to cut down the number of drivers to be loaded.
3. Writing a Driver
-------------------
The device manager assumes the following API from a driver module:
- **supports_device()**
Determines wether or not the driver supports a given parent device
node, that is the hardware device it represents (if any), and the API
the node exports.
- **register_device()**
The driver should register its device node here. The parent driver is
always initialized at this point. When registering the node, the
driver can also attach certain I/O resources (like I/O ports, or
memory ranges) to the node -- the device manager will make sure that
only one node can claim these resources.
- **init_driver()**
Any initialization necessary to get the driver going. For most
drivers, this will be reduced to the creation of a private data
structure that is going to be used for all of the following
functions.
- **uninit_driver()**
Uninitializes resources acquired by **init_driver()**.
- **register_child_devices()**
If the driver wants to register any child device nodes or to publish
any devices, it should do so here. This function is called only
during the initial registration process of the device node.
- **rescan_child_devices()**
Is called whenever a manual rescan is triggered.
- **device_removed()** Is called when the device node is about to be
unregistered when its device is gone, for example when a USB device
is unplugged.
- **suspend()**
Enters different sleep modes.
- **resume()**
Resumes a device from a previous sleep mode.
To ensure that a module exports this API, it **must** end its module
name with "driver_v1" to denote the version of the API it supports. Note
that **suspend()** and **resume()** are currently never called, as Haiku
has no power management implemented yet.
If your driver can give the device it is attached to a nice name that
can be presented to the user, it should add the **B_DEVICE_PRETTY_NAME**
attribute to the device node.
The **B_DEVICE_UNIQUE_ID** should be used in case the device has a
unique ID that can be used to identify it, and also differentiate it
from other devices of the same model and vendor. This information will
be added to the file system attributes of all devices published by your
driver, so that user applications can identify, say, a USB printer no
matter what USB slot it is attached to, and assign it additional data,
like paper configuration, or recognize it as the default printer.
If your driver implements an API that is used by a support or bus
module, you will usually use the **B_DEVICE_FIXED_CHILD** attribute to
specify exactly which child device node you will be talking to. If you
support several child nodes, you may want to have a closer look at the
section explaining `how to write a bus driver <#bus_driver>`__.
In addition to the child nodes a driver registers itself, a driver can
either have dynamic children or fixed children, never both. Also, fixed
children are registered before **register_child_devices()** is called,
while dynamic children are registered afterwards.
4. Publishing a Device
----------------------
To publish a device entry in the device file system under */dev*, all
your driver has to do is to call the
::
publish_device(device_node *node, const char *path,
const char *deviceModuleName);
function the device manager module exports. The *path* is the path
component that follows "/dev", for example "net/ipro1000/0". The
*deviceModuleName* is the module exporting the device functionality. It
should end with "device_v1" to show the device manager which protocol it
supports. If the device node your device belongs to is removed, your
device is removed automatically with it. On the other hand, you are
allowed to unpublish the device at any point using the
**unpublish_device()** function the device manager delivers for this.
A device module must export the following API:
- **init_device()**
This is called when the open() is called on this device for the first
time. You may want to create a private data structure that is passed
on to all subsequent calls of the **open()** function that your
device exports.
- **uninit_device()**
Is called when the last file descriptor to the device had been
closed.
- **device_removed()**
When the device node your device belongs to is going to be removed,
you're notified about this in this function.
- **open()**
Called whenever your device is opened.
- **close()**
- **free()**
Free the private data structure you allocated in **open()**.
- **read()**
- **write()**
- **io()**
This is a replacement for the **read()**, and **write()** calls, and
allows, among other things, for asynchronous I/O. This functionality
has not yet been implemented, though (see below).
- **control()**
- **select()**
- **deselect()**
5. Writing a Bus Driver
-----------------------
A bus driver is a driver that represents a bus where one or more
arbitrary devices can be attached to.
There are two basic types of busses: intelligent busses like PCI or USB
that know a lot about the devices attached to it, like a generic device
type, as well as device and vendor ID information, and simple
untyped/generic busses that either have not all the information (like
device type) or don't even know what and if any devices are attached.
The device manager has been written in such a way that device
exploration makes use of additional information the bus can provide in
order to find a responsible device driver faster, and with less
overhead.
5.1. Writing an Intelligent Bus Driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If your bus knows what type of device is attached to, and also has
vendor and device ID information about that device, it is considered to
be an intelligent bus. The bus driver is supposed to have one parent
node representing the bus, and to create a child node for each device
attached to the bus.
The additional information you have about the devices are attached to
the device node in the following attributes:
- **B_DEVICE_VENDOR_ID**
The vendor ID - this ID has only to be valid in the namespace of your
bus.
- **B_DEVICE_ID**
The device ID.
- **B_DEVICE_TYPE**
The device type as defined by the PCI class base information.
- **B_DEVICE_SUB_TYPE**
The device sub type as defined by the PCI sub class information.
- **B_DEVICE_INTERFACE**
The device interface type as defined by the PCI class API
information.
You can use the **B_DEVICE_FLAGS** attribute to define how the device
manager finds the children of the devices you exported. For this kind of
bus drivers, you will usually only want to specify
**B_FIND_CHILD_ON_DEMAND** here, which causes the driver only to be
searched when the system asks for it.
5.2. Writing a Simple Bus Driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A bus can be simple in a number of ways:
#. It may not know how many or if any devices are attached to it
#. It cannot retrieve any type information about the devices it has, but
knows all devices that are attached to it
An example of the latter would be the Zorro bus of the Amiga - it only
has information about the vendor and device ID, but no type information.
It should be implemented like an intelligent bus, though, with the type
information simply omitted.
Therefore, this section is about the former case, that is, a simple bus
like the ISA bus. Since it doesn't know anything about its children, it
does not publish any child nodes, instead, it will just specify the
B_FIND_MULTIPLE_CHILDREN and B_FIND_CHILD_ON_DEMAND flags for its device
node. Since there is no additional information about this bus, the
device manager will assume a simple bus, and will try to find drivers on
demand only.
The generic bus
---------------
Some devices are not tied to a specific bus. This is the case for all
drivers that do not relate to a physical device: /dev/null, /dev/zero,
/dev/random, etc. A "generic" bus has been added, and these drivers can
attach to it.
6. Open Issues
--------------
While most of the new device manager is fledged out, there are some
areas that could use improvements or are problematic under certain
requirements. Also, some parts just haven't been written yet.
6.1. generic/simple busses
^^^^^^^^^^^^^^^^^^^^^^^^^^
6.2. Unpublishing
^^^^^^^^^^^^^^^^^
6.4. Versioning
^^^^^^^^^^^^^^^
The way the device manager works, it makes versioning of modules (which
are supposed to be one of the strong points of the module system) much
harder or even impossible. While the device manager could introduce a
new API and could translate between a "driver_v1", and a "driver_v2" API
on the fly, it's not yet possible for a PCI sub module to do the same
thing.
**Proposed Solution:** Add attribute **B_DEVICE_ALTERNATE_VERSION** that
specifies alternate versions of the module API this device node
supports. We would then need a **request_version()** or
**set_version()** function (to be called from **supports_device()**)
that allows to specify the version of the parent node this device node
wants to talk to.
6.5. Unregistering Nodes
^^^^^^^^^^^^^^^^^^^^^^^^
6.6. Support for generic drivers is missing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This should probably be done by simply adding a simple bus driver named
"generic" that generic drivers need to ask for.
6.7. Mappings, And Other Optimizations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Due to the way the device tree is built, the device manager could
remember which driver served a given device node. That way, it wouldn't
need to search for a driver anymore, but could just pick it up.
Practically, the device manager should cache the type (and/or
vendor/device) information of a node, and assign one or more drivers
(via module name) to this information. It should also remember negative
outcome, that is if there is no driver supporting the hardware.
This way, only the first boot would require an actual search for
drivers, as subsequent boots would reuse the type-driver assignments. If
a new driver is installed, the cached assignments would need to be
updated immediately. If a driver has been installed outside of the
running system, the device manager might want to create a hash per
module directory to see if anything changed to flush the cache.
Alternatively or additionally, the boot loader could have a menu causing
the cache to be ignored.
It would be nice to find a way for generic and simple busses to reduce
the amount of searching necessary for them. One way would be to remember
which driver supports which bus - but this information is currently only
accessible derived from what the driver does, and is therefore not
reliable or complete. A separately exported information would be
necessary for this.
Also, when looking for a generic or simple bus driver, actual
directories could be omitted; currently, driver search is always
recursive, as that's how the module mechanism is working. Eventually, we
might want to extend the open_module_list_etc() call a bit more to
accomplish that.
-262
View File
@@ -1,262 +0,0 @@
<html>
<body bgcolor=white>
<h1>Node Monitoring</h1>
<h6>
Creation Date: January 16, 2003<br>
Author(s): Axel D&ouml;rfler
</h6>
This document describes the feature of the BeOS kernel to monitor nodes. First,
there is an explanation of what kind of functionality we have to reproduce (along
with the higher level API), then we will present the implementation in OpenBeOS.
<h2>Requirements - Exported Functionality in BeOS</h2>
From user-level, BeOS exports the following API as found in the storage/NodeMonitor.h
header file:
<pre>
status_t watch_node(const node_ref *node,
uint32 flags,
BMessenger target);
status_t watch_node(const node_ref *node,
uint32 flags,
const BHandler *handler,
const BLooper *looper = NULL);
status_t stop_watching(BMessenger target);
status_t stop_watching(const BHandler *handler,
const BLooper *looper = NULL);
</pre>
The kernel also exports two other functions to be used from file system add-ons
that causes the kernel to send out notification messages:
<pre>
int notify_listener(int op, nspace_id nsid,
vnode_id vnida, vnode_id vnidb,
vnode_id vnidc, const char *name);
int send_notification(port_id port, long token,
ulong what, long op, nspace_id nsida,
nspace_id nsidb, vnode_id vnida,
vnode_id vnidb, vnode_id vnidc,
const char *name);
</pre>
<p>
The latter is only used for live query updates, but is obviously called by
the former. The port/token pair identify a unique BLooper/BHandler pair, and
it used internally to address those high-level objects from the kernel.
</p>
<p>
When a file system calls the <code>notify_listener()</code> function, it will have
a look if there are monitors for that node which meet the specified constraints -
and it will call <code>send_notification()</code> for every single message to be send.
</p>
<p>
Each of the parameters <code>vnida - vnidc</code> has a dedicated meaning:
<ul>
<li><b>vnida:</b> the parent directory of the "main" node</li>
<li><b>vnidb:</b> the target parent directory for a move</li>
<li><b>vnidc:</b> the node that has triggered the notification to be send</li>
</ul>
</p>
<p>
The flags parameter in <code>watch_node()</code> understands the following constants:
</p>
<ul>
<li><b>B_STOP_WATCHING</b><br>
watch_node() will stop to watch the specified node.</li>
<li><b>B_WATCH_NAME</b><br>
name changes are notified through a B_ENTRY_MOVED opcode.</li>
<li><b>B_WATCH_STAT</b><br>
changes to the node's stat structure are notified with a B_STAT_CHANGED code.</li>
<li><b>B_WATCH_ATTR</b><br>
attribute changes will cause a B_ATTR_CHANGED to be send.</li>
<li><b>B_WATCH_DIRECTORY</b><br>
notifies on changes made to the specified directory, i.e. B_ENTRY_REMOVED, B_ENTRY_CREATED</li>
<li><b>B_WATCH_ALL</b><br>
is a short-hand for the flags above.</li>
<li><b>B_WATCH_MOUNT</b><br>
causes B_DEVICE_MOUNTED and B_DEVICE_UNMOUNTED to be send.</li>
</ul>
<p>
Node monitors are maintained per team - every team can have up to 4096 monitors, although
there exists a private kernel call to raise this limit (for example, Tracker is using it
intensively).
</p>
<p>
The kernel is able to send the BMessages directly to the specified BLooper and BHandler;
it achieves this using the application kit's token mechanism. The message is constructed
manually in the kernel, it doesn't use any application kit services.
</p>
<br>
<h2>Meeting the Requirements in an Optimal Way - Implementation in OpenBeOS</h2>
<p>
If you assume that every file operation could trigger a notification message to be send,
it's clear that the node monitoring system must be optimized for sending messages. For
every call to <code>notify_listener()</code>, the kernel must check if there are any
monitors for the node that was updated.
</p>
<p>
Those monitors are put into a hash table which has the device number and the vnode ID
as keys. Each of the monitors maintains a list of listeners which specify which port/token
pair should be notified for what change. Since the vnodes are created/deleted as needed
from the kernel, the node monitor is maintained independently from them; a simple pointer
from a vnode to its monitor is not possible.
</p>
<p>
The main structures that are involved in providing the node monitoring functionality
look like this:
</p>
<pre>
struct monitor_listener {
monitor_listener *next;
monitor_listener *prev;
list_link monitor_link;
port_id port;
int32 token;
uint32 flags;
node_monitor *monitor;
};
struct node_monitor {
node_monitor *next;
mount_id device;
vnode_id node;
struct list listeners;
};
</pre>
<p>
The relevant part of the I/O context structure is this:
</p>
<pre>
struct io_context {
...
struct list node_monitors;
uint32 num_monitors;
uint32 max_monitors;
};
</pre>
<p>
If you call <code>watch_node()</code> on a file with a flags parameter unequal to
B_STOP_WATCHING, the following will happen in the node monitor:
</p>
<ol>
<li>The <code>add_node_monitor()</code> function does a hash lookup for the
device/vnode pair. If there is no <code>node_monitor</code> yet for this pair,
a new one will be created.</li>
<li>The list of listeners is scanned for the provided port/token pair (the
BLooper/BHandler pointer will already be translated in user-space), and
the new flag is or'd to the old field, or a new <code>monitor_listener</code>
is created if necessary - in the latter case, the team's node monitor
counter is incremented.</li>
</ol>
<p>
If it's called with B_STOP_WATCHING defined, the reverse operation take effect, and
the <code>monitor</code> field is used to see if this monitor don't have any listeners
anymore, in which case it will be removed.
</p>
<p>
Note the presence of the <code>max_monitors</code> - there is no hard limit the kernel
exposes to userland applications; the listeners are maintained in a doubly-linked list.
</p>
<p>
If a team is shut down, all listeners from its I/O context will be removed - since every
listener stores a pointer to its monitor, determining the monitors that can be removed
because of this operation is very cheap.
</p>
<p>
The <code>notify_listener()</code> also only does a hash lookup for the device/node
pair it got from the file system, and sends out as many notifications as specified by
the listeners of the monitor that belong to that node.
</p>
<p>
If a node is deleted from the disk, the corresponding <code>node_monitor</code> and its
listeners will be removed as well, to prevent watching a new file that accidently happen
to have the same device/node pair (as is possible with BFS, for example).
</p>
<br>
<h2>Differences Between Both Implementations</h2>
<p>
Although the aim was to create a completely compatible monitoring implementation,
there are some notable differences between the two.
</p>
<p>
BeOS reserves a certain number of slots for calls to <code>watch_node()</code> - each
call to that function will use one slot, even if you call it twice for the same node.
OpenBeOS, however, will always use one slot per node - you could call <code>watch_node()</code>
several times, but you would waste only one slot.
</p>
<p>
While this is an implementational detail, it also causes a change in behaviour for
applications; in BeOS, applications will get one message for every <code>watch_node()</code>
call, in OpenBeOS, you'll get only one message per node. If an application relies
on this strange behaviour of the BeOS kernel, it will no longer work correctly.
</p>
<p>
The other difference is that OpenBeOS exports its node monitoring functionality to
kernel modules as well, and provides an extra plain C API for them to use.
</p>
<br>
<h2>And Beyond?</h2>
<p>
The current implementation directly iterates over all listeners and sends out notifications
as required synchronously in the context of the thread that triggered the notification to
be sent.
</p>
<p>
If a node monitor needs to send out several messages, this could theoretically greatly
decrease file system performance. To optimize for this case, the required data of the
notification could be put into a queue and be sent by a dedicated worker thread. Since
this requires an additional copy operation and a reserved address space for this queue,
this optimization could be more expensive than the current implementation, depending
on the usage pattern of the node monitoring mechanism.
</p>
<p>
With BFS, it would be possible to introduce the possibility to automatically watch all
files in a specified directory. While this would be very convenient at application level,
it comes with several disadvantages:
</p>
<ol>
<li>This feature might not be easily accomplishable for many file systems; a file system
must be able to retrieve a node by ID only - it might not be feasible to find
out about the parent directory for many file systems.</li>
<li>Although it could potentially safe node monitors, it might cause the kernel to
send out a lot more messages to the application than it needs. With the restriction
the kernel imposes to the number of watched nodes for a team, the application's
designer might try to be much stricter with the number of monitors his application
will consume.</li>
</ol>
<p>
While 1.) might be a real show stopper, 2.) is almost invalidated because of Tracker's
usage of node monitors; it consumes a monitor for every entry it displays, which might
be several thousands. Implementing this feature would not only greatly speed up maintaining
this massive need of monitors, and cut down memory usage, but also ease the implementation
at application level.
</p>
<p>
Even 1.) could be solved if the kernel could query a file system if it can support
this particular feature; it could then automatically monitor all files in that directory
without adding complexity to the application using this feature. Of course,
the effort to provide this functionality is much larger then - but for applications
like Tracker, the complexity would be removed from the application without extra cost.
</p>
<p>
However, none of the discussed feature extensions have been implemented for the currently
developed version R1 of OpenBeOS.
</p>
</body>
</html>
+257
View File
@@ -0,0 +1,257 @@
Node Monitoring
===============
Creation Date: January 16, 2003
Author(s): Axel Dörfler
This document describes the feature of the BeOS kernel to monitor nodes.
First, there is an explanation of what kind of functionality we have to
reproduce (along with the higher level API), then we will present the
implementation in OpenBeOS.
Requirements - Exported Functionality in BeOS
---------------------------------------------
From user-level, BeOS exports the following API as found in the
storage/NodeMonitor.h header file:
::
status_t watch_node(const node_ref *node,
uint32 flags,
BMessenger target);
status_t watch_node(const node_ref *node,
uint32 flags,
const BHandler *handler,
const BLooper *looper = NULL);
status_t stop_watching(BMessenger target);
status_t stop_watching(const BHandler *handler,
const BLooper *looper = NULL);
The kernel also exports two other functions to be used from file system
add-ons that causes the kernel to send out notification messages:
::
int notify_listener(int op, nspace_id nsid,
vnode_id vnida, vnode_id vnidb,
vnode_id vnidc, const char *name);
int send_notification(port_id port, long token,
ulong what, long op, nspace_id nsida,
nspace_id nsidb, vnode_id vnida,
vnode_id vnidb, vnode_id vnidc,
const char *name);
The latter is only used for live query updates, but is obviously called
by the former. The port/token pair identify a unique BLooper/BHandler
pair, and it used internally to address those high-level objects from
the kernel.
When a file system calls the ``notify_listener()`` function, it will
have a look if there are monitors for that node which meet the specified
constraints - and it will call ``send_notification()`` for every single
message to be send.
Each of the parameters ``vnida - vnidc`` has a dedicated meaning:
- **vnida:** the parent directory of the "main" node
- **vnidb:** the target parent directory for a move
- **vnidc:** the node that has triggered the notification to be send
The flags parameter in ``watch_node()`` understands the following
constants:
- **B_STOP_WATCHING**
watch_node() will stop to watch the specified node.
- **B_WATCH_NAME**
name changes are notified through a B_ENTRY_MOVED opcode.
- **B_WATCH_STAT**
changes to the node's stat structure are notified with a
B_STAT_CHANGED code.
- **B_WATCH_ATTR**
attribute changes will cause a B_ATTR_CHANGED to be send.
- **B_WATCH_DIRECTORY**
notifies on changes made to the specified directory, i.e.
B_ENTRY_REMOVED, B_ENTRY_CREATED
- **B_WATCH_ALL**
is a short-hand for the flags above.
- **B_WATCH_MOUNT**
causes B_DEVICE_MOUNTED and B_DEVICE_UNMOUNTED to be send.
Node monitors are maintained per team - every team can have up to 4096
monitors, although there exists a private kernel call to raise this
limit (for example, Tracker is using it intensively).
The kernel is able to send the BMessages directly to the specified
BLooper and BHandler; it achieves this using the application kit's token
mechanism. The message is constructed manually in the kernel, it doesn't
use any application kit services.
|
Meeting the Requirements in an Optimal Way - Implementation in OpenBeOS
-----------------------------------------------------------------------
If you assume that every file operation could trigger a notification
message to be send, it's clear that the node monitoring system must be
optimized for sending messages. For every call to ``notify_listener()``,
the kernel must check if there are any monitors for the node that was
updated.
Those monitors are put into a hash table which has the device number and
the vnode ID as keys. Each of the monitors maintains a list of listeners
which specify which port/token pair should be notified for what change.
Since the vnodes are created/deleted as needed from the kernel, the node
monitor is maintained independently from them; a simple pointer from a
vnode to its monitor is not possible.
The main structures that are involved in providing the node monitoring
functionality look like this:
::
struct monitor_listener {
monitor_listener *next;
monitor_listener *prev;
list_link monitor_link;
port_id port;
int32 token;
uint32 flags;
node_monitor *monitor;
};
struct node_monitor {
node_monitor *next;
mount_id device;
vnode_id node;
struct list listeners;
};
The relevant part of the I/O context structure is this:
::
struct io_context {
...
struct list node_monitors;
uint32 num_monitors;
uint32 max_monitors;
};
If you call ``watch_node()`` on a file with a flags parameter unequal to
B_STOP_WATCHING, the following will happen in the node monitor:
#. The ``add_node_monitor()`` function does a hash lookup for the
device/vnode pair. If there is no ``node_monitor`` yet for this pair,
a new one will be created.
#. The list of listeners is scanned for the provided port/token pair
(the BLooper/BHandler pointer will already be translated in
user-space), and the new flag is or'd to the old field, or a new
``monitor_listener`` is created if necessary - in the latter case,
the team's node monitor counter is incremented.
If it's called with B_STOP_WATCHING defined, the reverse operation take
effect, and the ``monitor`` field is used to see if this monitor don't
have any listeners anymore, in which case it will be removed.
Note the presence of the ``max_monitors`` - there is no hard limit the
kernel exposes to userland applications; the listeners are maintained in
a doubly-linked list.
If a team is shut down, all listeners from its I/O context will be
removed - since every listener stores a pointer to its monitor,
determining the monitors that can be removed because of this operation
is very cheap.
The ``notify_listener()`` also only does a hash lookup for the
device/node pair it got from the file system, and sends out as many
notifications as specified by the listeners of the monitor that belong
to that node.
If a node is deleted from the disk, the corresponding ``node_monitor``
and its listeners will be removed as well, to prevent watching a new
file that accidently happen to have the same device/node pair (as is
possible with BFS, for example).
|
Differences Between Both Implementations
----------------------------------------
Although the aim was to create a completely compatible monitoring
implementation, there are some notable differences between the two.
BeOS reserves a certain number of slots for calls to ``watch_node()`` -
each call to that function will use one slot, even if you call it twice
for the same node. OpenBeOS, however, will always use one slot per node
- you could call ``watch_node()`` several times, but you would waste
only one slot.
While this is an implementational detail, it also causes a change in
behaviour for applications; in BeOS, applications will get one message
for every ``watch_node()`` call, in OpenBeOS, you'll get only one
message per node. If an application relies on this strange behaviour of
the BeOS kernel, it will no longer work correctly.
The other difference is that OpenBeOS exports its node monitoring
functionality to kernel modules as well, and provides an extra plain C
API for them to use.
|
And Beyond?
-----------
The current implementation directly iterates over all listeners and
sends out notifications as required synchronously in the context of the
thread that triggered the notification to be sent.
If a node monitor needs to send out several messages, this could
theoretically greatly decrease file system performance. To optimize for
this case, the required data of the notification could be put into a
queue and be sent by a dedicated worker thread. Since this requires an
additional copy operation and a reserved address space for this queue,
this optimization could be more expensive than the current
implementation, depending on the usage pattern of the node monitoring
mechanism.
With BFS, it would be possible to introduce the possibility to
automatically watch all files in a specified directory. While this would
be very convenient at application level, it comes with several
disadvantages:
#. This feature might not be easily accomplishable for many file
systems; a file system must be able to retrieve a node by ID only -
it might not be feasible to find out about the parent directory for
many file systems.
#. Although it could potentially safe node monitors, it might cause the
kernel to send out a lot more messages to the application than it
needs. With the restriction the kernel imposes to the number of
watched nodes for a team, the application's designer might try to be
much stricter with the number of monitors his application will
consume.
While 1.) might be a real show stopper, 2.) is almost invalidated
because of Tracker's usage of node monitors; it consumes a monitor for
every entry it displays, which might be several thousands. Implementing
this feature would not only greatly speed up maintaining this massive
need of monitors, and cut down memory usage, but also ease the
implementation at application level.
Even 1.) could be solved if the kernel could query a file system if it
can support this particular feature; it could then automatically monitor
all files in that directory without adding complexity to the application
using this feature. Of course, the effort to provide this functionality
is much larger then - but for applications like Tracker, the complexity
would be removed from the application without extra cost.
However, none of the discussed feature extensions have been implemented
for the currently developed version R1 of OpenBeOS.
-406
View File
@@ -1,406 +0,0 @@
This file contains the documentation written by Thomas Kurschel that was originally
found in the headers of his pnp_manager.
It's outdated but could be used as a basis for the real documentation.
// former pnp_manager.h
/*
Copyright (c) 2003-04, Thomas Kurschel
PnP manager; Takes care of registration and loading of PnP drivers
Read pnp_driver.h first to understand the basic idea behind PnP drivers.
To register a driver node, use register_driver. If the device got lost,
use unregister_driver (note: if the parent node is removed, your node
get removed automatically as your driver has obviously nothing to work
with anymore). To get access to a (parent) device, use load_driver/
unload_driver.
To let the manager find a consumer (see pnp_driver.h), you can either
specify its name directly during registration, using a
PNP_DRIVER_FIXED_CONSUMER attribute, or let the manager search the
appropriate consumer(s) via a PNP_DRIVER_DYNAMIC_CONSUMER attribute.
Searching of dynamic consumers is done as follows:
- First, the manager searches for a Specific driver in the base
directory (see below)
- If no Specific driver is found, all Generic drivers stored under
"generic" sub-directory are informed in turn until one returns success
- Finally, _all_ Universal drivers, stored in the "universal" sub-
directory, are informed
Specification of the base directory and of the names of Specific
drivers is done via a file name pattern given by a
PNP_DRIVER_DYNAMIC_CONSUMER attribute.
First, all substrings of the form "%attribute_name%" are replaced by the
content of the attribute "attribute_name" as follows:
- if the attribute contains an integer value, its content is converted to hex
(lowercase) with a fixed length according to the attribute's value range
- the content of string attributes is quoted by " and invalid characters
(i.e. /%" and all characters outside 32..126) are replaced by their
unsigned decimal value, delimited by %
- other attribute types cannot be used
Second, the resulting name is split into chunks according to the presence
of | characters (you can escape % and | with a ^ character). These
characters are only delimiters and get removed before further processing.
The directory before the first | character is the base directory (see
above). It contains the "generic" and the "universal" subdirectories.
The names of the specific drivers are created by first taking the entire
file name, then by removing the last chunk, then by removing the last
two chunks and so on until only the first chunk is left.
As drivers can contain multiple modules, the module name is constructed
by appending the content of the PNP_DRIVER_TYPE attribute to the driver's file
name, seperated by a slash character (note: this only applies to dynamic
consumers; for fixed consumers, you specify the module name directly via
PNP_DRIVER_FIXED_CONSUMER).
E.g. given a dynamic consumer pattern of
"pci/vendor=%vendor_id%|, device=%device_id%" for a device with the
attributes vendor_id=0x123 and device_id=0xabcd (both being uint16), the
PnP manager tries the specific drivers "pci/vendor=0123, device=abcd" and
(if the first one fails/doesn't exist) "pci/vendor=0123". If they both
refuse to handle the device, all drivers under "pci/generic" are tried
until one accepts the device. Finally, all drivers under "pci/universal"
are loaded, whatever happened before.
In practise, you should try to use specific drivers as much as possible.
If detection based on device IDs is impossible (e.g. because the bus
doesn't support them at all), you can put the driver under "generic".
Generic drivers can also be used to specify wrappers that try to load old-
style drivers if no new driver can be found. Also, they can be used to
report an error or invoke an user program that tries downloading a
proper Specific driver. Universal drivers are mainly used for
informational purposes, e.g. to publish data about each found device,
or to provide raw access to all devices.
If the device uses physical address space or I/O space or ISA DMA
channels (called I/O resources), the driver has to acquire these
resources. During hardware detection (usually via probe()),
acquire_io_resources() must be called to get exclusive access.
If no hardware could be found, they must be released via
release_io_resources(). If detection was successful, the list of
the (acquired) resources must be passed to register_device().
Resources can either belong to one hardware detection or to a device.
If a hardware detection collides with another, it has to wait;
if it collides with a device whose driver is not loaded, the
driver loading is blocked. When detection fails, i.e. if
release_io_resources() is called, all blocked drivers can be loaded
again. If the detection fails, i.e. the resources are transferred
via register_device(), all blocked devices are unregistered and
pending load requests aborted. If a hardware detection collides
with a device whose driver is loaded, acquire_io_resources() fails
with B_BUSY. As this makes a hardware rescan impossible if the
driver is loaded, you should define PNP_DRIVER_NO_LIVE_RESCAN
for nodes that use I/O resources (see below).
To search for new drivers for a given device node, use rescan(). This
marks all consumer devices as being verified and calls probe()
of all consumers drivers (see above) to let them rescan the parent
for devices. The <depth> parameter determines the nesting level, e.g.
2 means that first the consumers are scanned and then the consumers
of the consumers.
Normally, all devices can be rescanned. If a driver cannot handle
a rescan safely when it is loaded (i.e. used by a consumer), it
must set PNP_DRIVER_NO_LIVE_RESCAN, in which case the device is
ignored during rescan if the driver is loaded and attempts
to load the driver during a rescan are blocked until the rescan
is finished. If rescanning a device is not possible at all, it must
have set PNP_DRIVER_NEVER_RESCAN to always ignore it.
To distinguish between new devices, lost devices and redetected
devices, consumer devices should provide a connection code and a
device identifier. They are specified by PNP_DRIVER_CONNECTION and
PNP_DRIVER_CONNECTION respectively, and are expanded in the same way
as PNP_DRIVER_DYNAMIC_CONSUMER. It is assumed that there can be only
one device per connection and that a device can be uniquely identify
by a device identifier. If a consumer device is registered on the
same connection as an existing device but with a different device
identifier, the old device gets unregistered automatically. If both
connection and device identifier are the same, registration is
handled as a redetection and ignored (unless a different type or
driver module is specified - in this case, the device is replaced).
Devices that were not redetected during a rescan get unregistered
unless they were ignored (see above).
*/
// interface of PnP manager
typedef struct device_manager_info {
module_info info;
// load driver
// node - node whos driver is to be loaded
// user_cookie - cookie to be passed to init_device of driver
// interface - interface of loaded driver
// cookie - device cookie issued by loaded driver
status_t (*init_driver)(device_node_handle node, void *userCookie,
driver_module_info **interface, void **cookie);
// unload driver
status_t (*uninit_driver)(device_node_handle node);
// rescan node for new dynamic drivers
// node - node whose dynamic drivers are to be scanned
status_t (*rescan)(device_node_handle node);
// register device
// parent - parent node
// attributes - NULL-terminated array of node attributes
// io_resources - NULL-terminated array of I/O resources (can be NULL)
// node - new node handle
// on return, io_resources are invalid: on success I/O resources belong
// to node, on fail they are released;
// if device is already registered, B_OK is returned but *node is NULL
status_t (*register_device)(device_node_handle parent,
const device_attr *attrs,
const io_resource_handle *io_resources,
device_node_handle *node);
// unregister device
// all nodes having this node as their parent are unregistered too.
// if the node contains PNP_MANAGER_ID_GENERATOR/PNP_MANAGER_AUTO_ID
// pairs, the id specified this way is freed too
status_t (*unregister_device)(device_node_handle node);
// find device by node content
// the given attributes must _uniquely_ identify a device node;
// parent - parent node (-1 for don't-care)
// attrs - list of attributes (can be NULL)
// The node you got will be automatically put on the next call
// to this function.
status_t (*get_next_child_device)(device_node_handle parent,
device_node_handle *_node, const device_attr *attrs);
// get parent device node
device_node_handle (*get_parent)(device_node_handle node);
// Must be called after get_next_child_device() (if you don't iterate through)
// and get_parent() to make sure the node is freed when it's not used anymore
void (*put_device_node)(device_node_handle node);
// acquire I/O resources
// resources - NULL-terminated array of resources to acquire
// handles - NULL-terminated array of handles (one per resource);
// array must be provided by caller
// return B_BUSY if a resource is used by a loaded driver
status_t (*acquire_io_resources)(io_resource *resources,
io_resource_handle *handles);
// release I/O resources
// handles - NULL-terminated array of handles
status_t (*release_io_resources)(const io_resource_handle *handles);
// create unique id
// generator - name of id set
// if result >= 0 - unique id
// result < 0 - error code
int32 (*create_id)(const char *generator);
// free unique id
status_t (*free_id)(const char *generator, uint32 id);
// helpers to extract attribute by name.
// if <recursive> is true, parent nodes are scanned if
// attribute isn't found in current node; unless you declared
// the attribute yourself, use recursive search to handle
// intermittent nodes, e.g. defined by filter drivers, transparently.
// for raw and string attributes, you get a copy that must
// be freed by caller
status_t (*get_attr_uint8)(device_node_handle node,
const char *name, uint8 *value, bool recursive);
status_t (*get_attr_uint16)(device_node_handle node,
const char *name, uint16 *value, bool recursive);
status_t (*get_attr_uint32)(device_node_handle node,
const char *name, uint32 *value, bool recursive);
status_t (*get_attr_uint64)(device_node_handle node,
const char *name, uint64 *value, bool recursive);
status_t (*get_attr_string)(device_node_handle node,
const char *name, char **value, bool recursive);
status_t (*get_attr_raw)(device_node_handle node,
const char *name, void **data, size_t *_size,
bool recursive);
// get next attribute of node;
// on call, *<attr_handle> must contain handle of an attribute;
// on return, *<attr_handle> is replaced by the next attribute or
// NULL if it was the last;
// to get the first attribute, <attr_handle> must point to NULL;
// the returned handle must be released by either passing it to
// another get_next_attr() call or by using release_attr()
// directly
status_t (*get_next_attr)(device_node_handle node,
device_attr_handle *attrHandle);
// release attribute handle <attr_handle> of <node>;
// see get_next_attr
status_t (*release_attr)(device_node_handle node,
device_attr_handle attr_handle);
// retrieve attribute data with handle given;
// <attr> is only valid as long as you don't release <attr_handle>
// implicitely or explicitely
status_t (*retrieve_attr)(device_attr_handle attr_handle,
const device_attr **attr);
// change/add attribute <attr> of/to node
status_t (*write_attr)(device_node_handle node,
const device_attr *attr);
// remove attribute of node by name
// <name> is name of attribute
status_t (*remove_attr)(device_node_handle node, const char *name);
} device_manager_info;
// former pnp_driver.h
/*
Copyright (c) 2003-04, Thomas Kurschel
Required interface of PnP drivers
In contrast to standard BeOS drivers, PnP drivers are normal modules
having the interface described below.
Every device is described by its driver via a PnP node with properties
described in PnP Node Attributes. Devices are organized in a hierarchy,
e.g. a devfs device is a hard disk device that is connected to a
controller, which is a PCI device, that is connected to a PCI bus.
Every device is connected to its lower-level device via a parent link
stored in its Node. The higher-level is called the consumer of the
lower-level device. If the lower-level device gets removed, all its
consumers are removed too.
In our example, the hierarchy is
devfs device -> hard disk -> controller -> PCI device -> PCI bus
If the PCI bus is removed, everything up to including the devfs device
is removed too.
The driver hierarchy is constructed bottom-up, i.e. the lower-level
driver searches for a corresponding consumer, which in turns searches
for its consumer and so on. The lowest driver is usually something like
a PCI bus, the highest driver is normally a devfs entry (see pnp_devfs.h).
Registration of devices and the search for appropriate consumers is
done via the pnp_manager (see pnp_manager.h).
When a potential consumer is found, it gets informed about the new
lower-level device and can either refuse its handling or accept it.
On accept, it has to create a new node with the lower-level device
node as its parent.
Loading of drivers is done on demand, i.e. if the consumer wants to
access its lower-level device, it explicitely loads the corresponding
driver, and once it doesn't need it anymore, the lower-level driver
must be unloaded. Usually, this process happens recursively, i.e. in
our example, the hard disk driver loads the controller driver, which
loads the PCI device driver which loads the PCI bus driver. The same
process applies to unloading.
Because of this dynamic loading, drivers must store persistent data
in the node of their devices. Please be aware that you cannot modify
a node once published.
If a device gets removed, you must unregister its node. As said, the
PnP manager will automatically unregister all consumers too. The
corresponding drivers are notified to stop talking to their lower-level
devices and to terminate running requests. Normally, you want to use a
dedicated variable that is verified at each call to make sure that the
parent is still there. The notification is done independantly of the
driver being loaded by its consumer(s) or not. If it isn't loaded,
the notification callback gets NULL as the device cookie; normally, the
driver returns immediately in this case. As soon as both the device
is removed and the driver is unloaded, device_cleanup gets called to
free resources that couldn't be safely removed in device_removed when
the driver was still loaded.
If a device has exactly one consumer, they often interact in some way.
To simplify that, the consumer can pass a user-cookie to its parent
during load. In this case, it's up to the parent driver to get a
pointer to the interface of the consumer. Effectively, such consumers
have one interface for their consumers (base on pnp_driver_info), and
a another for their parents (with a completely driver-specific
structure).
In terms of synchronization, loading/unloading/remove-notifications
are executed synchronously, i.e. if e.g. a device is to be unloaded
but the drive currently handles a remove-notification, the unloading
is delayed until the nofication callback returns. If multiple consumers
load a driver, the driver gets initialized only once; subsequent load
requests increase an internal load count only and return immediately.
In turn, unloading only happens once the load count reaches zero.
*/
struct driver_module_info {
module_info info;
float (*supports_device)(device_node_handle parent, bool *_noConnection);
// check whether this parent is supported
status_t (*register_device)(device_node_handle parent);
// Register your device node.
status_t (*init_driver)(device_node_handle node, void *user_cookie, void **_cookie);
// driver is loaded.
// node - node of device
// user_cookie - cookie passed by loading driver
// cookie - cookie issued by this driver
status_t (*uninit_driver)(void *cookie);
// driver gets unloaded.
void (*device_removed)(device_node_handle node, void *cookie);
// a device node, registered by this driver, got removed.
// if the driver wasn't loaded when this happenes, no (un)init_device
// is called and thus <cookie> is NULL;
void (*device_cleanup)(device_node_handle node);
// a device node, registered by this driver, got removed and
// the driver got unloaded
void (*get_supported_paths)(const char ***_busses, const char ***_devices);
};
// former pnp_bus.h
/*
Copyright (c) 2003-04, Thomas Kurschel
Required interface of PnP bus drivers
Busses consist of two node layers: the lower layer defines the bus,
the upper layer defines the abstract devices connected to the bus.
Both layers are handled by a bus manager. Actual device nodes are
on top of abstract device nodes.
E.g. if we have a PCI bus with an IDE controller on it, we get
IDE controller -> PCI device -> PCI bus
with:
IDE controller = actual device node
PCI device = abstract device node
PCI bus = bus node
The PCI bus manager establishes both the PCI devices and the PCI busses.
Abstract device nodes act as a gateway between actual device nodes
and the corresponding bus node. They are constructed by the bus
node driver via its rescan() hook. To identify a bus node, define
PNP_BUS_IS_BUS as an attribute of it. As a result, the PnP manager
will call the rescan() method of the bus driver whenever the
bus is to be rescanned. Afterwards, all possible dynamic consumers
are informed as done for normal nodes.
Normally, potential device drivers are notified immediately when
rescan() registers a new abstract device node. But sometimes, device
drivers need to know _all_ devices connected to the bus for correct
detection. To ensure this, the bus node must define
PNP_BUS_NOTIFY_CONSUMERS_AFTER_RESCAN. In this case, scanning for
consumers is postponed until rescan() has finished.
If hot-plugging of devices can be detected automatically (e.g. USB),
you should define PNP_DRIVER_ALWAYS_LOADED, so the bus driver is
always loaded and thus capable of handling hot-plug events generated
by the bus controller hardware.
*/
@@ -0,0 +1,408 @@
Plug and Play Manager
=====================
This file contains the documentation written by Thomas Kurschel that was originally
found in the headers of his pnp_manager.
It's outdated but could be used as a basis for the real documentation.
PNP Manager
-----------
PnP manager; Takes care of registration and loading of PnP drivers
Read pnp_driver.h first to understand the basic idea behind PnP drivers.
To register a driver node, use register_driver. If the device got lost,
use unregister_driver (note: if the parent node is removed, your node
get removed automatically as your driver has obviously nothing to work
with anymore). To get access to a (parent) device, use load_driver/
unload_driver.
To let the manager find a consumer (see pnp_driver.h), you can either
specify its name directly during registration, using a
PNP_DRIVER_FIXED_CONSUMER attribute, or let the manager search the
appropriate consumer(s) via a PNP_DRIVER_DYNAMIC_CONSUMER attribute.
Searching of dynamic consumers is done as follows:
- First, the manager searches for a Specific driver in the base
directory (see below)
- If no Specific driver is found, all Generic drivers stored under
"generic" sub-directory are informed in turn until one returns success
- Finally, _all_ Universal drivers, stored in the "universal" sub-
directory, are informed
Specification of the base directory and of the names of Specific
drivers is done via a file name pattern given by a
PNP_DRIVER_DYNAMIC_CONSUMER attribute.
First, all substrings of the form "%attribute_name%" are replaced by the
content of the attribute "attribute_name" as follows:
- if the attribute contains an integer value, its content is converted to hex
(lowercase) with a fixed length according to the attribute's value range
- the content of string attributes is quoted by " and invalid characters
(i.e. /%" and all characters outside 32..126) are replaced by their
unsigned decimal value, delimited by %
- other attribute types cannot be used
Second, the resulting name is split into chunks according to the presence
of | characters (you can escape % and | with a ^ character). These
characters are only delimiters and get removed before further processing.
The directory before the first | character is the base directory (see
above). It contains the "generic" and the "universal" subdirectories.
The names of the specific drivers are created by first taking the entire
file name, then by removing the last chunk, then by removing the last
two chunks and so on until only the first chunk is left.
As drivers can contain multiple modules, the module name is constructed
by appending the content of the PNP_DRIVER_TYPE attribute to the driver's file
name, seperated by a slash character (note: this only applies to dynamic
consumers; for fixed consumers, you specify the module name directly via
PNP_DRIVER_FIXED_CONSUMER).
E.g. given a dynamic consumer pattern of
"pci/vendor=%vendor_id%|, device=%device_id%" for a device with the
attributes vendor_id=0x123 and device_id=0xabcd (both being uint16), the
PnP manager tries the specific drivers "pci/vendor=0123, device=abcd" and
(if the first one fails/doesn't exist) "pci/vendor=0123". If they both
refuse to handle the device, all drivers under "pci/generic" are tried
until one accepts the device. Finally, all drivers under "pci/universal"
are loaded, whatever happened before.
In practise, you should try to use specific drivers as much as possible.
If detection based on device IDs is impossible (e.g. because the bus
doesn't support them at all), you can put the driver under "generic".
Generic drivers can also be used to specify wrappers that try to load old-
style drivers if no new driver can be found. Also, they can be used to
report an error or invoke an user program that tries downloading a
proper Specific driver. Universal drivers are mainly used for
informational purposes, e.g. to publish data about each found device,
or to provide raw access to all devices.
If the device uses physical address space or I/O space or ISA DMA
channels (called I/O resources), the driver has to acquire these
resources. During hardware detection (usually via probe()),
acquire_io_resources() must be called to get exclusive access.
If no hardware could be found, they must be released via
release_io_resources(). If detection was successful, the list of
the (acquired) resources must be passed to register_device().
Resources can either belong to one hardware detection or to a device.
If a hardware detection collides with another, it has to wait;
if it collides with a device whose driver is not loaded, the
driver loading is blocked. When detection fails, i.e. if
release_io_resources() is called, all blocked drivers can be loaded
again. If the detection fails, i.e. the resources are transferred
via register_device(), all blocked devices are unregistered and
pending load requests aborted. If a hardware detection collides
with a device whose driver is loaded, acquire_io_resources() fails
with B_BUSY. As this makes a hardware rescan impossible if the
driver is loaded, you should define PNP_DRIVER_NO_LIVE_RESCAN
for nodes that use I/O resources (see below).
To search for new drivers for a given device node, use rescan(). This
marks all consumer devices as being verified and calls probe()
of all consumers drivers (see above) to let them rescan the parent
for devices. The <depth> parameter determines the nesting level, e.g.
2 means that first the consumers are scanned and then the consumers
of the consumers.
Normally, all devices can be rescanned. If a driver cannot handle
a rescan safely when it is loaded (i.e. used by a consumer), it
must set PNP_DRIVER_NO_LIVE_RESCAN, in which case the device is
ignored during rescan if the driver is loaded and attempts
to load the driver during a rescan are blocked until the rescan
is finished. If rescanning a device is not possible at all, it must
have set PNP_DRIVER_NEVER_RESCAN to always ignore it.
To distinguish between new devices, lost devices and redetected
devices, consumer devices should provide a connection code and a
device identifier. They are specified by PNP_DRIVER_CONNECTION and
PNP_DRIVER_CONNECTION respectively, and are expanded in the same way
as PNP_DRIVER_DYNAMIC_CONSUMER. It is assumed that there can be only
one device per connection and that a device can be uniquely identify
by a device identifier. If a consumer device is registered on the
same connection as an existing device but with a different device
identifier, the old device gets unregistered automatically. If both
connection and device identifier are the same, registration is
handled as a redetection and ignored (unless a different type or
driver module is specified - in this case, the device is replaced).
Devices that were not redetected during a rescan get unregistered
unless they were ignored (see above).
.. code-block:: cpp
// interface of PnP manager
typedef struct device_manager_info {
module_info info;
// load driver
// node - node whos driver is to be loaded
// user_cookie - cookie to be passed to init_device of driver
// interface - interface of loaded driver
// cookie - device cookie issued by loaded driver
status_t (*init_driver)(device_node_handle node, void *userCookie,
driver_module_info **interface, void **cookie);
// unload driver
status_t (*uninit_driver)(device_node_handle node);
// rescan node for new dynamic drivers
// node - node whose dynamic drivers are to be scanned
status_t (*rescan)(device_node_handle node);
// register device
// parent - parent node
// attributes - NULL-terminated array of node attributes
// io_resources - NULL-terminated array of I/O resources (can be NULL)
// node - new node handle
// on return, io_resources are invalid: on success I/O resources belong
// to node, on fail they are released;
// if device is already registered, B_OK is returned but *node is NULL
status_t (*register_device)(device_node_handle parent,
const device_attr *attrs,
const io_resource_handle *io_resources,
device_node_handle *node);
// unregister device
// all nodes having this node as their parent are unregistered too.
// if the node contains PNP_MANAGER_ID_GENERATOR/PNP_MANAGER_AUTO_ID
// pairs, the id specified this way is freed too
status_t (*unregister_device)(device_node_handle node);
// find device by node content
// the given attributes must _uniquely_ identify a device node;
// parent - parent node (-1 for don't-care)
// attrs - list of attributes (can be NULL)
// The node you got will be automatically put on the next call
// to this function.
status_t (*get_next_child_device)(device_node_handle parent,
device_node_handle *_node, const device_attr *attrs);
// get parent device node
device_node_handle (*get_parent)(device_node_handle node);
// Must be called after get_next_child_device() (if you don't iterate through)
// and get_parent() to make sure the node is freed when it's not used anymore
void (*put_device_node)(device_node_handle node);
// acquire I/O resources
// resources - NULL-terminated array of resources to acquire
// handles - NULL-terminated array of handles (one per resource);
// array must be provided by caller
// return B_BUSY if a resource is used by a loaded driver
status_t (*acquire_io_resources)(io_resource *resources,
io_resource_handle *handles);
// release I/O resources
// handles - NULL-terminated array of handles
status_t (*release_io_resources)(const io_resource_handle *handles);
// create unique id
// generator - name of id set
// if result >= 0 - unique id
// result < 0 - error code
int32 (*create_id)(const char *generator);
// free unique id
status_t (*free_id)(const char *generator, uint32 id);
// helpers to extract attribute by name.
// if <recursive> is true, parent nodes are scanned if
// attribute isn't found in current node; unless you declared
// the attribute yourself, use recursive search to handle
// intermittent nodes, e.g. defined by filter drivers, transparently.
// for raw and string attributes, you get a copy that must
// be freed by caller
status_t (*get_attr_uint8)(device_node_handle node,
const char *name, uint8 *value, bool recursive);
status_t (*get_attr_uint16)(device_node_handle node,
const char *name, uint16 *value, bool recursive);
status_t (*get_attr_uint32)(device_node_handle node,
const char *name, uint32 *value, bool recursive);
status_t (*get_attr_uint64)(device_node_handle node,
const char *name, uint64 *value, bool recursive);
status_t (*get_attr_string)(device_node_handle node,
const char *name, char **value, bool recursive);
status_t (*get_attr_raw)(device_node_handle node,
const char *name, void **data, size_t *_size,
bool recursive);
// get next attribute of node;
// on call, *<attr_handle> must contain handle of an attribute;
// on return, *<attr_handle> is replaced by the next attribute or
// NULL if it was the last;
// to get the first attribute, <attr_handle> must point to NULL;
// the returned handle must be released by either passing it to
// another get_next_attr() call or by using release_attr()
// directly
status_t (*get_next_attr)(device_node_handle node,
device_attr_handle *attrHandle);
// release attribute handle <attr_handle> of <node>;
// see get_next_attr
status_t (*release_attr)(device_node_handle node,
device_attr_handle attr_handle);
// retrieve attribute data with handle given;
// <attr> is only valid as long as you don't release <attr_handle>
// implicitely or explicitely
status_t (*retrieve_attr)(device_attr_handle attr_handle,
const device_attr **attr);
// change/add attribute <attr> of/to node
status_t (*write_attr)(device_node_handle node,
const device_attr *attr);
// remove attribute of node by name
// <name> is name of attribute
status_t (*remove_attr)(device_node_handle node, const char *name);
} device_manager_info;
PNP Driver
----------
Required interface of PnP drivers
In contrast to standard BeOS drivers, PnP drivers are normal modules
having the interface described below.
Every device is described by its driver via a PnP node with properties
described in PnP Node Attributes. Devices are organized in a hierarchy,
e.g. a devfs device is a hard disk device that is connected to a
controller, which is a PCI device, that is connected to a PCI bus.
Every device is connected to its lower-level device via a parent link
stored in its Node. The higher-level is called the consumer of the
lower-level device. If the lower-level device gets removed, all its
consumers are removed too.
In our example, the hierarchy is
devfs device -> hard disk -> controller -> PCI device -> PCI bus
If the PCI bus is removed, everything up to including the devfs device
is removed too.
The driver hierarchy is constructed bottom-up, i.e. the lower-level
driver searches for a corresponding consumer, which in turns searches
for its consumer and so on. The lowest driver is usually something like
a PCI bus, the highest driver is normally a devfs entry (see pnp_devfs.h).
Registration of devices and the search for appropriate consumers is
done via the pnp_manager (see pnp_manager.h).
When a potential consumer is found, it gets informed about the new
lower-level device and can either refuse its handling or accept it.
On accept, it has to create a new node with the lower-level device
node as its parent.
Loading of drivers is done on demand, i.e. if the consumer wants to
access its lower-level device, it explicitely loads the corresponding
driver, and once it doesn't need it anymore, the lower-level driver
must be unloaded. Usually, this process happens recursively, i.e. in
our example, the hard disk driver loads the controller driver, which
loads the PCI device driver which loads the PCI bus driver. The same
process applies to unloading.
Because of this dynamic loading, drivers must store persistent data
in the node of their devices. Please be aware that you cannot modify
a node once published.
If a device gets removed, you must unregister its node. As said, the
PnP manager will automatically unregister all consumers too. The
corresponding drivers are notified to stop talking to their lower-level
devices and to terminate running requests. Normally, you want to use a
dedicated variable that is verified at each call to make sure that the
parent is still there. The notification is done independantly of the
driver being loaded by its consumer(s) or not. If it isn't loaded,
the notification callback gets NULL as the device cookie; normally, the
driver returns immediately in this case. As soon as both the device
is removed and the driver is unloaded, device_cleanup gets called to
free resources that couldn't be safely removed in device_removed when
the driver was still loaded.
If a device has exactly one consumer, they often interact in some way.
To simplify that, the consumer can pass a user-cookie to its parent
during load. In this case, it's up to the parent driver to get a
pointer to the interface of the consumer. Effectively, such consumers
have one interface for their consumers (base on pnp_driver_info), and
a another for their parents (with a completely driver-specific
structure).
In terms of synchronization, loading/unloading/remove-notifications
are executed synchronously, i.e. if e.g. a device is to be unloaded
but the drive currently handles a remove-notification, the unloading
is delayed until the nofication callback returns. If multiple consumers
load a driver, the driver gets initialized only once; subsequent load
requests increase an internal load count only and return immediately.
In turn, unloading only happens once the load count reaches zero.
.. code-block:: cpp
struct driver_module_info {
module_info info;
float (*supports_device)(device_node_handle parent, bool *_noConnection);
// check whether this parent is supported
status_t (*register_device)(device_node_handle parent);
// Register your device node.
status_t (*init_driver)(device_node_handle node, void *user_cookie, void **_cookie);
// driver is loaded.
// node - node of device
// user_cookie - cookie passed by loading driver
// cookie - cookie issued by this driver
status_t (*uninit_driver)(void *cookie);
// driver gets unloaded.
void (*device_removed)(device_node_handle node, void *cookie);
// a device node, registered by this driver, got removed.
// if the driver wasn't loaded when this happenes, no (un)init_device
// is called and thus <cookie> is NULL;
void (*device_cleanup)(device_node_handle node);
// a device node, registered by this driver, got removed and
// the driver got unloaded
void (*get_supported_paths)(const char ***_busses, const char ***_devices);
};
PNP Bus
-------
Required interface of PnP bus drivers
Busses consist of two node layers: the lower layer defines the bus,
the upper layer defines the abstract devices connected to the bus.
Both layers are handled by a bus manager. Actual device nodes are
on top of abstract device nodes.
E.g. if we have a PCI bus with an IDE controller on it, we get
IDE controller -> PCI device -> PCI bus
with:
* IDE controller = actual device node
* PCI device = abstract device node
* PCI bus = bus node
The PCI bus manager establishes both the PCI devices and the PCI busses.
Abstract device nodes act as a gateway between actual device nodes
and the corresponding bus node. They are constructed by the bus
node driver via its rescan() hook. To identify a bus node, define
PNP_BUS_IS_BUS as an attribute of it. As a result, the PnP manager
will call the rescan() method of the bus driver whenever the
bus is to be rescanned. Afterwards, all possible dynamic consumers
are informed as done for normal nodes.
Normally, potential device drivers are notified immediately when
rescan() registers a new abstract device node. But sometimes, device
drivers need to know _all_ devices connected to the bus for correct
detection. To ensure this, the bus node must define
PNP_BUS_NOTIFY_CONSUMERS_AFTER_RESCAN. In this case, scanning for
consumers is postponed until rescan() has finished.
If hot-plugging of devices can be detected automatically (e.g. USB),
you should define PNP_DRIVER_ALWAYS_LOADED, so the bus driver is
always loaded and thus capable of handling hot-plug events generated
by the bus controller hardware.
@@ -27,23 +27,25 @@ Configuring
- Install the card in your computer
- Boot Haiku and look in the syslog for the PCI bus scan for the device:
KERN: PCI: [dom 0, bus 5] bus 5, device 0, function 0: vendor 9710, device 9922, revision 00
KERN: PCI: class_base 07, class_function 00, class_api 02
KERN: PCI: vendor 9710: MosChip Semiconductor Technology Ltd.
KERN: PCI: device 9922: MCS9922 PCIe Multi-I/O Controller
KERN: PCI: info: Communication controller (Serial controller, 16550)
KERN: PCI: line_size 10, latency 00, header_type 80, BIST 00
KERN: PCI: ROM base host 00000000, pci 00000000, size 00000000
KERN: PCI: cardbus_CIS 00000000, subsystem_id 1000, subsystem_vendor_id a000
KERN: PCI: interrupt_line 0b, interrupt_pin 01, min_grant 00, max_latency 00
KERN: PCI: base reg 0: host 00004000, pci 0000KERN: 4000, size 00000008, flags 01
KERN: PCI: base reg 1: host f1c01000, pci f1c01000, size 00001000, flags 00
KERN: PCI: base reg 2: host 00000000, pci 00000000, size 00000000, flags 00
KERN: PCI: base reg 3: host 00000000, pci 00000000, size 00000000, flags 00
KERN: PCI: base reg 4: host 00000000, pci 00000000, size 00000000, flags 00
KERN: PCI: base reg 5: host f1c00000, pci f1c00000, size 00001000, flags 00
KERN: PCI: Capabilities: MSI, PM, PCIe
KERN: PCI: Extended capabilities: Virtual Channel, Advanced Error Reporting
.. code-block:: text
KERN: PCI: [dom 0, bus 5] bus 5, device 0, function 0: vendor 9710, device 9922, revision 00
KERN: PCI: class_base 07, class_function 00, class_api 02
KERN: PCI: vendor 9710: MosChip Semiconductor Technology Ltd.
KERN: PCI: device 9922: MCS9922 PCIe Multi-I/O Controller
KERN: PCI: info: Communication controller (Serial controller, 16550)
KERN: PCI: line_size 10, latency 00, header_type 80, BIST 00
KERN: PCI: ROM base host 00000000, pci 00000000, size 00000000
KERN: PCI: cardbus_CIS 00000000, subsystem_id 1000, subsystem_vendor_id a000
KERN: PCI: interrupt_line 0b, interrupt_pin 01, min_grant 00, max_latency 00
KERN: PCI: base reg 0: host 00004000, pci 0000KERN: 4000, size 00000008, flags 01
KERN: PCI: base reg 1: host f1c01000, pci f1c01000, size 00001000, flags 00
KERN: PCI: base reg 2: host 00000000, pci 00000000, size 00000000, flags 00
KERN: PCI: base reg 3: host 00000000, pci 00000000, size 00000000, flags 00
KERN: PCI: base reg 4: host 00000000, pci 00000000, size 00000000, flags 00
KERN: PCI: base reg 5: host f1c00000, pci f1c00000, size 00001000, flags 00
KERN: PCI: Capabilities: MSI, PM, PCIe
KERN: PCI: Extended capabilities: Virtual Channel, Advanced Error Reporting
Write down the address of "base reg 0", this is where the serial port registers
@@ -52,9 +54,11 @@ as well?)
Configure serial debug in config/settings/kernel/drivers/kernel:
serial_debug_output true
serial_debug_speed 115200
serial_debug_port 0x4000
.. code-block:: text
serial_debug_output true
serial_debug_speed 115200
serial_debug_port 0x4000
Now your kernel is configured to send its output to the serial port. You can
connect another machine to it (using an USB to serial adapter and a NULL modem
@@ -1,31 +1,34 @@
Haiku swap file support
Swap file
#######################
This article describes how to use swap file in Haiku and how the swap system
works.
This section describes how to use swap file in Haiku and how the swap system
works.
1. How to use a swap file?
How to use a swap file?
=======================
Like BeOS, Haiku uses "/var/swap" as default swap file. It is created
Like BeOS, Haiku uses "/var/swap" as default swap file. It is created
during the boot process and its size is twice the size of physical memory by
default. You can change its size through the VirtualMemory preference
application and your settings will take effect after restarting the system.
The default swap file "/var/swap" may not satisfy your need. Haiku allows
The default swap file "/var/swap" may not satisfy your need. Haiku allows
adding/removing a swap file dynamically. (This is *NOT* implemented yet, since
I do not know how to add bin commands "swapon" and "swapoff" in the system.
It needs to be done in the future.)
2. How swap system works?
The virtual memory subsystem of Haiku is very similar to that of FreeBSD,
How swap system works?
======================
The virtual memory subsystem of Haiku is very similar to that of FreeBSD,
therefore our swap system implementation is borrowed from FreeBSD.
A swap system has two main functions: (1) maintain a map between anonymous
A swap system has two main functions: (1) maintain a map between anonymous
pages and swap space, so we can page in/out when needed. (2) manage the
allocation/deallocation of swap space. Let's see how these are implemented in
Haiku.
In order to maintain a map between pages and swap space, we need to record
In order to maintain a map between pages and swap space, we need to record
the pages' swap address somewhere. Here we use swap blocks. A "swap_block"
structure contains swap address information for 32 (value of SWAP_BLOCK_PAGES)
consecutive pages from a same cache. So whenever we look for a page in swap
@@ -34,33 +37,33 @@ Here we use hash table. All swap blocks in the system are arranged into a global
hash table. The hash table uses a cache's address and page index in this cache
as hash key.
Here is an example. Suppose a page has been paged out to swap space and now
its cache wants to page it in. It works as follows: look up the swap hash table
using address of the cache and page index as hash key, if successful, we get
the swap block containing the this page's swap address. Then search the swap
block to get the exact swap address of this page. After that, we can read the
Here is an example. Suppose a page has been paged out to swap space and now
its cache wants to page it in. It works as follows: look up the swap hash table
using address of the cache and page index as hash key, if successful, we get
the swap block containing the this page's swap address. Then search the swap
block to get the exact swap address of this page. After that, we can read the
page from swap file using vfs functions.
I draw a picture and hope it could help you understand the above words. If
the pic becomes a mess on your computer, please set the tab width of your text
editor to 4.
I draw a picture and hope it could help you understand the above words.
.. code-block:: text
___________________________________________________________
sSwapHashTable |__________|___NULL___|___NULL___|___________|____NULL____|
| |
| |
| |
| |
___V___ ___V___
swap_block /----|__0__| /--------|__5__|
swap_block /----|__0__| /--------|__5__|
| |__3__|--------\ | /---|__6__|
| |_..._| | | | |_..._|
| |__2__|----\ | | | |__20_|--------------->
| |_..._| | | | |_..._|
| |__2__|----\ | | | |__20_|--------------->
| | | | |
| _____________V___V_________V____V_________________________
swap_file `->|slot|slot|slot|slot|slot|slot|slot|slot|slot|slot|....|
|_0__|_1__|_2__|_3__|_4__|_5__|_6__|_7__|_8__|_9__|____|__
| _____________V___V_________V____V_________________________
swap_file `->|slot|slot|slot|slot|slot|slot|slot|slot|slot|slot|....|
|_0__|_1__|_2__|_3__|_4__|_5__|_6__|_7__|_8__|_9__|____|__
The swap system also manages allocation/deallocation of swap space. In our
The swap system also manages allocation/deallocation of swap space. In our
implementation, each swap file is divided into page-sized slots(called "swap
pages") and a swap file can be seen as an array of many swap pages(see the
above picture). Swap page is the unit for swap space allocation/deallocation
@@ -72,29 +75,28 @@ swap file is: 0-99, 101-200, 202-301) Why leave a page gap between swap files?
Because in this way, we can easily tell if two adjacent pages are in a same
swap file. (See the code in VMAnonymousCache::Read()).
The efficiency of the FreeBSD swap system lies in a special data structure:
The efficiency of the FreeBSD swap system lies in a special data structure:
radix bitmap(i.e. bitmap using radix tree for hinting.) It can operate well no
matter how much fragmentation there is and no matter how large a bitmap is
used. I have ported the radix bitmap structure to Haiku, so our swap system
will have a good performance. More information on radix bitmap, please look
at the source code.
Swap space allocation takes place when we swap anonymous pages out.
Swap space allocation takes place when we swap anonymous pages out.
In order to make the allocation less probable to fail, anonymous cache will
reserve swap space when it is initialized. If there is not enough swap space
left, physical memory will be reserved. Swap space deallocation happens when
available swap space is low. The page daemon will scan a number of pages and
if the scanned page has swap space assigned, its swap space will be freed.
3. Acknowledgement
Special thanks to my mentor Ingo. He is a knowledged person and always
Acknowledgement
---------------
Special thanks to my mentor Ingo. He is a knowledged person and always
gives me encouragement. Without his consistent and illuminating instructions,
this project would not have reached its present status.
If you find bugs or have suggestions for swap system, you can contact me
If you find bugs or have suggestions for swap system, you can contact me
via [email protected]. Thanks in advance.
Zhao Shuai
[email protected]
2008-08-21
Zhao Shuai - [email protected] - 2008-08-21