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:
committed by
Adrien Destugues
parent
7f8195344a
commit
a5061ecec5
@@ -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 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
|
||||
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 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/
|
||||
@@ -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)
|
||||
@@ -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), 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>`__
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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>`__
|
||||
@@ -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)
|
||||
|
||||
@@ -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>`__
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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?
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
The Amiga port
|
||||
==============
|
||||
|
||||
* http://wandel.ca/homepage/execdis/
|
||||
@@ -1 +0,0 @@
|
||||
http://wandel.ca/homepage/execdis/
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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/
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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 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
|
||||
|
||||
- **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ö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.
|
||||
@@ -1,262 +0,0 @@
|
||||
<html>
|
||||
<body bgcolor=white>
|
||||
<h1>Node Monitoring</h1>
|
||||
<h6>
|
||||
Creation Date: January 16, 2003<br>
|
||||
Author(s): Axel Dö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>
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
+24
-20
@@ -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
|
||||
+42
-40
@@ -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
|
||||
Reference in New Issue
Block a user