Generate developer docs with Sphinx

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

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

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

Change-Id: I924ac9dc6e753887ab56f18a09bdb0a1e1793bfd
Reviewed-on: https://review.haiku-os.org/c/haiku/+/4370
Reviewed-by: Niels Sascha Reedijk <[email protected]>
This commit is contained in:
Adrien Destugues
2021-08-27 11:41:17 +00:00
committed by Adrien Destugues
parent 7f8195344a
commit a5061ecec5
146 changed files with 10937 additions and 12069 deletions
+19
View File
@@ -0,0 +1,19 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = generated
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+17
View File
@@ -0,0 +1,17 @@
Documents that still need to be converted to restructuredtext (or thrown away):
- kernel/vm*: it looks like these should be comments in the sourcecode?
- kits/*: TODO, some interesting info in there but also a lot of probably obsolete things
- media/*: some useful things, some sourcecode, and a PDF file about the echo audio driver
Other things to do:
- Organize the table of contents a bit. For now I just wanted to get all the existing files in,
in a mostly flat organization. But it makes things hard to follow.
- Reorganize the directories. For example move midi to kits/midi. Should we follow the layout of
the source tree? Or the hierarchy of the table of contents?
- There are doxyfiles for various components. Unlike the one used for API docs, they scan the cpp
source files and will extract some internals documentation from there. Decide what to do with that.
- Migrate some things from the website. Start with documentation about configure and jam, for example.
- There are TODO lists in various places in the docs, turn them into bugreports in the bugtracker
(including this one!)
-60
View File
@@ -1,60 +0,0 @@
# HaikuDepot and Server Interactions
## Introduction
This document aims to outline the general approach taken within the HaikuDepot application with regard to coordinating processes that relate to fetching and consuming data from remote systems.
There are two main sources of remote data that are downloaded and consumed from network sources into the HaikuDepot desktop application;
* Repository HPKR data from a Haiku mirror such as "HaikuPorts"
* Meta-data related to packages from [HaikuDepotServer](http://depot.haiku-os.org) (HDS) such as icons, localizations, ratings and so on.
## Process, ProcessNode and Coordinator
A _Process_ (root class ```AbstractProcess```) is a class that takes responsibility for some aspect of pulling material down from a network source and processing it.
A _ProcessNode_ is a holder for a Process, but also takes responsibility for the following;
* Maintaining the relationship between the Processes. For example, if Process A needs to complete before Process B then the ProcessNode would record this fact. It does this by storing _predecessor_ and _successor_ ProcessNodes.
* Starting the held Process in a newly spawned thread.
* Stopping the held Process.
A _Coordinator_ holds a list of ProcessNodes. It will start, stop and cancel nodes as necessary such that, in an ideal case, the various ProcessNodes are completed in the correct order.
The _ProcessCoordinatorFactory_ is able to create Coordinators.
## Bulk Load Processes
The following diagram shows the logical dependencies of the various Processes that are involved in refreshing the HPKR data from remote repositories and then loading data from the HDS system.
![Process Dependencies](images/processes.svg)
For example, the ```ServerRepositoryDataUpdateProcess``` must wait until the ```LocalRepositoryUpdateProcess``` has completed before it is able to be started. It is the reponsibility of the Coordinator to ensure that this sequencing is enforced. There are many instances of ```ServerPkgDataUpdateProcess``` shown because there will be one launched for each of the Repositories for which data will be downloaded; "HaikuDepot" etc...
## Process / ProcessNode / Coordinator
The following diagram shows the relationship and interplay between the various objects that are involved in running a larger task. Only fictional Processes are shown to keep the diagram tidy. See above for the actual Processes.
![Process Relationship and Interplay](images/process-interplay.svg)
Dotted lines show associations between elements and red lines show interaction or data-flow. Green arrows here demonstratively show some dependency; Process C cannot start until A and B are completed.
The MainWindow owns the Coordinator for the life-span of undertaking some larger task.
Each Process is coupled with a ProcessNode and then the Coordinator has a list of the ProcessNodes-s. The Processes are generally writing to the local disk system (often with compressed files) to cache data (see ```~/config/cache/HaikuDepot```) and also relay data into the ```Model``` object that maintains state for the HaikuDepot desktop application.
The Processes communicate when they have finished to the Coordinator and it is at these events that the Coordinator is able to introspect the state of the Processes in order to know what to do next.
The Coordinator also communicates with MainWindow. It communicates with the MainWindow in order to signal changes or progress in the overall larger task. The MainWindow also uses these events to discover when the Coordinator has completely finished.
## Failure
A Process may fail or be stopped. If a Process fails or is stopped then successor Processes, or those that would have run after the failed process, are stopped so that they will not run.
The Coordinator will still try to complete any other Processes that could still run or are running already.
Upon the Coordinator completing, the Coordinator will signal to the MainWindow client the change in state and then the MainWindow will be able to identify that the Coordinator has completed, but that something has gone wrong along the way.
## Concurrency
It is important to note that Processes may run concurrently. The Processes' are modelled by the Coordinator as a list rather than a tree. The dependencies are likely to form a tree or web of Processes that dictates the order of execution, but it is also quite possible to have multiple non-intersecting trees or webs such that Processes will execute independently.
+120
View File
@@ -0,0 +1,120 @@
HaikuDepot and Server Interactions
==================================
Introduction
------------
This document aims to outline the general approach taken within the
HaikuDepot application with regard to coordinating processes that relate
to fetching and consuming data from remote systems.
There are two main sources of remote data that are downloaded and
consumed from network sources into the HaikuDepot desktop application;
- Repository HPKR data from a Haiku mirror such as “HaikuPorts”
- Meta-data related to packages from
`HaikuDepotServer <http://depot.haiku-os.org>`__ (HDS) such as icons,
localizations, ratings and so on.
Process, ProcessNode and Coordinator
------------------------------------
A *Process* (root class ``AbstractProcess``) is a class that takes
responsibility for some aspect of pulling material down from a network
source and processing it.
A *ProcessNode* is a holder for a Process, but also takes responsibility
for the following;
- Maintaining the relationship between the Processes. For example, if
Process A needs to complete before Process B then the ProcessNode
would record this fact. It does this by storing *predecessor* and
*successor* ProcessNodes.
- Starting the held Process in a newly spawned thread.
- Stopping the held Process.
A *Coordinator* holds a list of ProcessNodes. It will start, stop and
cancel nodes as necessary such that, in an ideal case, the various
ProcessNodes are completed in the correct order.
The *ProcessCoordinatorFactory* is able to create Coordinators.
Bulk Load Processes
-------------------
The following diagram shows the logical dependencies of the various
Processes that are involved in refreshing the HPKR data from remote
repositories and then loading data from the HDS system.
.. figure:: images/processes.svg
:alt: Process Dependencies
Process Dependencies
For example, the ``ServerRepositoryDataUpdateProcess`` must wait until
the ``LocalRepositoryUpdateProcess`` has completed before it is able to
be started. It is the reponsibility of the Coordinator to ensure that
this sequencing is enforced. There are many instances of
``ServerPkgDataUpdateProcess`` shown because there will be one launched
for each of the Repositories for which data will be downloaded;
“HaikuDepot” etc…
Process / ProcessNode / Coordinator
-----------------------------------
The following diagram shows the relationship and interplay between the
various objects that are involved in running a larger task. Only
fictional Processes are shown to keep the diagram tidy. See above for
the actual Processes.
.. figure:: images/process-interplay.svg
:alt: Process Relationship and Interplay
Process Relationship and Interplay
Dotted lines show associations between elements and red lines show
interaction or data-flow. Green arrows here demonstratively show some
dependency; Process C cannot start until A and B are completed.
The MainWindow owns the Coordinator for the life-span of undertaking
some larger task.
Each Process is coupled with a ProcessNode and then the Coordinator has
a list of the ProcessNodes-s. The Processes are generally writing to the
local disk system (often with compressed files) to cache data (see
``~/config/cache/HaikuDepot``) and also relay data into the ``Model``
object that maintains state for the HaikuDepot desktop application.
The Processes communicate when they have finished to the Coordinator and
it is at these events that the Coordinator is able to introspect the
state of the Processes in order to know what to do next.
The Coordinator also communicates with MainWindow. It communicates with
the MainWindow in order to signal changes or progress in the overall
larger task. The MainWindow also uses these events to discover when the
Coordinator has completely finished.
Failure
-------
A Process may fail or be stopped. If a Process fails or is stopped then
successor Processes, or those that would have run after the failed
process, are stopped so that they will not run.
The Coordinator will still try to complete any other Processes that
could still run or are running already.
Upon the Coordinator completing, the Coordinator will signal to the
MainWindow client the change in state and then the MainWindow will be
able to identify that the Coordinator has completed, but that something
has gone wrong along the way.
Concurrency
-----------
It is important to note that Processes may run concurrently. The
Processes are modelled by the Coordinator as a list rather than a tree.
The dependencies are likely to form a tree or web of Processes that
dictates the order of execution, but it is also quite possible to have
multiple non-intersecting trees or webs such that Processes will execute
independently.
-48
View File
@@ -1,48 +0,0 @@
# HaikuPorts build-packages repository
The `build/jam/repositories/HaikuPorts` directory contains
RemotePackageRepository files which detail packages and
repositories leveraged during Haiku's build process.
> Warning: The URL packages are obtained from
> are determined by the sha256sum of the repository
> file.
## Updating
Each RemotePackageRepository jam file in this directory
is processed by src/tools/hardlink_packages.py on the
HaikuPorts package server.
1) Latest RemotePackageRepository jam file in git is downloaded on package server.
2) Packages are added to HaikuPorts by automatic or manual means.
3) hardlink_packages is provided all the relevant directories and RemotePackageRepository file
4) hardlink_packages performs additional modification of the RemotePackageRepository and creates
build repositories (https://eu.hpkg.haiku-os.org/haikuports/master/build-packages/)
5) The modified RemotePackageRepository file is copied back to the developers system and checked in to git.
## Container Process
Here is the fastest way to update this as of today.
Improvements are needed. Replace (ARCH) with architecture, (USER) with your non-root user.
## Prepare the build-packages repository
as root on limerick.ams3.haiku-os.org...
1) wget https://git.haiku-os.org/haiku/plain/build/jam/repositories/HaikuPorts/(ARCH) -O /var/lib/docker/volumes/ci_data_master_(ARCH)/_data/(ARCH)
2) Enter the buildmaster container:
docker exec -it $(docker ps | grep ci_buildmaster_master_(ARCH) | awk '{ print $1 }') /bin/bash -l
3) apt update; apt install -y vim python3 python3-pkg-resources
4) edit the repository define, add the needed packages, _devel packages, and add base package to source section.
5) ln -s /var/buildmaster/package_tools/package_repo /usr/bin/package_repo
6) export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/var/buildmaster/package_tools
7) ./package_tools/hardlink_packages.py (ARCH) ./(ARCH) /var/packages/repository/master/(ARCH)/current/packages/ /var/packages/build-packages/master/
8) exit; cp /var/lib/docker/volumes/ci_data_master_(ARCH)/_data/(ARCH) /home/(USER)/(ARCH); chown (USER) /home/(USER)/(ARCH);
## Pull the repostory file and commit it
From your local system...
1) scp -P2222 (USER)@limerick.ams3.haiku-os.org:./(ARCH) ./(ARCH)
2) commit the updated repostory define *without modifying it* in any way
@@ -0,0 +1,64 @@
HaikuPorts build-packages repository
====================================
The ``build/jam/repositories/HaikuPorts`` directory contains
RemotePackageRepository files which detail packages and repositories
leveraged during Haikus build process.
Warning: The URL packages are obtained from are determined by the
sha256sum of the repository file.
Updating
--------
Each RemotePackageRepository jam file in this directory is processed by
src/tools/hardlink_packages.py on the HaikuPorts package server.
1) Latest RemotePackageRepository jam file in git is downloaded on
package server.
2) Packages are added to HaikuPorts by automatic or manual means.
3) hardlink_packages is provided all the relevant directories and
RemotePackageRepository file
4) hardlink_packages performs additional modification of the
RemotePackageRepository and creates build repositories
(https://eu.hpkg.haiku-os.org/haikuports/master/build-packages/)
5) The modified RemotePackageRepository file is copied back to the
developers system and checked in to git.
Container Process
-----------------
Here is the fastest way to update this as of today. Improvements are
needed. Replace (ARCH) with architecture, (USER) with your non-root
user.
Prepare the build-packages repository
-------------------------------------
as root on limerick.ams3.haiku-os.org…
1) wget
https://git.haiku-os.org/haiku/plain/build/jam/repositories/HaikuPorts/(ARCH)
-O /var/lib/docker/volumes/ci_data_master_(ARCH)/_data/(ARCH)
2) Enter the buildmaster container: docker exec -it $(docker ps \| grep
ci_buildmaster_master_(ARCH) \| awk { print $1 }) /bin/bash -l
3) apt update; apt install -y vim python3 python3-pkg-resources
4) edit the repository define, add the needed packages, \_devel
packages, and add base package to source section.
5) ln -s /var/buildmaster/package_tools/package_repo
/usr/bin/package_repo
6) export
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/var/buildmaster/package_tools
7) ./package_tools/hardlink_packages.py (ARCH) ./(ARCH)
/var/packages/repository/master/(ARCH)/current/packages/
/var/packages/build-packages/master/
8) exit; cp /var/lib/docker/volumes/ci_data_master_(ARCH)/_data/(ARCH)
/home/(USER)/(ARCH); chown (USER) /home/(USER)/(ARCH);
Pull the repostory file and commit it
-------------------------------------
From your local system…
1) scp -P2222 (USER)@limerick.ams3.haiku-os.org:./(ARCH) ./(ARCH)
2) commit the updated repostory define *without modifying it* in any way
-22
View File
@@ -1,22 +0,0 @@
AGP (and PCI-express) Graphics Address Re-Mapping Table
=======================================================
The GART is an IO-MMU allowing the videocard and CPU to share some memory.
Either the CPU can access the video RAM directly ("aperture"), or the video
card can access the system RAM using DMA access.
The GART converts between physical addresses and virtual addresses on the
video card side. Of course, the CPU must then map these physical addresses
in its own address space to use them (using the MMU).
The GART works as you'd expect from an MMU. It has a page table (called GTT)
in RAM and walks it to figure out mappings. Since there cannot be page misses
(that would require exception handling on the GPU side), access to missing
pages are instead sent to a dedicated "scratch" page which is not used for
anything else.
Our driver implements the GART and GTT for Intel graphics card only, so far.
Since our videodrivers are only doing modesetting, they do not need much
support and other drivers implemented GTT management directly on their own
(it is usually enough to make the framebuffer accessible to the CPU). However,
this could be generalized into a more flexible iommu bus protocol.
+23
View File
@@ -0,0 +1,23 @@
AGP (and PCI-express) Graphics Address Re-Mapping Table
=======================================================
The GART is an IO-MMU allowing the videocard and CPU to share some
memory. Either the CPU can access the video RAM directly (“aperture”),
or the video card can access the system RAM using DMA access.
The GART converts between physical addresses and virtual addresses on
the video card side. Of course, the CPU must then map these physical
addresses in its own address space to use them (using the MMU).
The GART works as youd expect from an MMU. It has a page table (called
GTT) in RAM and walks it to figure out mappings. Since there cannot be
page misses (that would require exception handling on the GPU side),
access to missing pages are instead sent to a dedicated “scratch” page
which is not used for anything else.
Our driver implements the GART and GTT for Intel graphics card only, so
far. Since our videodrivers are only doing modesetting, they do not need
much support and other drivers implemented GTT management directly on
their own (it is usually enough to make the framebuffer accessible to
the CPU). However, this could be generalized into a more flexible iommu
bus protocol.
-17
View File
@@ -1,17 +0,0 @@
(Copied mostly from <http://urnenfeld.blogspot.de/2012/07/in-past-i-got-to-know-that-motivation.html>)
**L2cap under `network/protocols/l2cap`**: Provides socket interface to have l2cap channels. L2CAP offers connection oriented and connectionless sockets. But bluetooth stack as this point has no interchangeability with TCP/IP, A Higher level Bluetooth profile must be implemented
**HCI under `src/add-ons/kernel/bluetooth`**: Here we have 2 modules, one for handling global bluetooth data structures such as connection handles and L2cap channels, and frames
**H2generic under `src/add-ons/kernel/drivers/bluetooth`**: The USB driver, implementing the H2 transport.
**Bluetooth kit under `src/kit/bluetooth`**: C++ implementation based on JSR82 api.
**Bluetooth Server under `src/servers/bluetooth`**: Basically handling opened devices (local connected fisically in our system) and forwaring kit calls to them.
**Bluetooth Preferences under `src/preferences/bluetooth`**: Configuration using the kit
**Test applications under `src/tests/kits/bluetooth`**.
There is a small prototype component which is not here documented below src/add-ons/bluetooth/ResetLocalDevice. Its intention was to be an add-on of bluetooth preferences, So that new HCI commands could be customized by users or external developers. I did not like at the end the idea, I did not find the flexibility I wanted.
@@ -0,0 +1,36 @@
Bluetooth overview
==================
(Copied mostly from
http://urnenfeld.blogspot.de/2012/07/in-past-i-got-to-know-that-motivation.html)
**L2cap under ``network/protocols/l2cap``**: Provides socket interface
to have l2cap channels. L2CAP offers connection oriented and
connectionless sockets. But bluetooth stack as this point has no
interchangeability with TCP/IP, A Higher level Bluetooth profile must be
implemented
**HCI under ``src/add-ons/kernel/bluetooth``**: Here we have 2 modules,
one for handling global bluetooth data structures such as connection
handles and L2cap channels, and frames
**H2generic under ``src/add-ons/kernel/drivers/bluetooth``**: The USB
driver, implementing the H2 transport.
**Bluetooth kit under ``src/kit/bluetooth``**: C++ implementation based
on JSR82 api.
**Bluetooth Server under ``src/servers/bluetooth``**: Basically handling
opened devices (local connected fisically in our system) and forwaring
kit calls to them.
**Bluetooth Preferences under ``src/preferences/bluetooth``**:
Configuration using the kit
**Test applications under ``src/tests/kits/bluetooth``**.
There is a small prototype component which is not here documented below
src/add-ons/bluetooth/ResetLocalDevice. Its intention was to be an
add-on of bluetooth preferences, So that new HCI commands could be
customized by users or external developers. I did not like at the end
the idea, I did not find the flexibility I wanted.
@@ -1,227 +0,0 @@
# SDHCI MMC Driver
This driver project is a part of GSoC'18 and is aimed at providing support for
PCI devices with class 8 and subclass 5 over x86 architecture. This document
will make you familiar with the [code produced during GSoC](https://review.haiku-os.org/#/c/haiku/+/318/),
loading and testing the driver(including hardware emulation), insight into the
code and future tasks.
For detailed explanations about the project, you can refer the
[weekly reports](https://www.haiku-os.org/blog/krish_iyer) and comment issues
if any. For this project we have referred [SD Host Controller Spec Version 1.00](https://www.sdcard.org/downloads/pls/pdf/index.php?p=PartA2_SD_Host_Controller_Simplified_Specification_Ver1.00.jpg&f=PartA2_SD_Host_Controller_Simplified_Specification_Ver1.00.pdf&e=EN_A2100)
and [Physical Layer Spec Version 1.10](https://www.sdcard.org/downloads/pls/pdf/index.php?p=Part1_Physical_Layer_Simplified_Specification_Ver1.10.jpg&f=Part1_Physical_Layer_Simplified_Specification_Ver1.10.pdf&e=EN_P1110).
## Loading and testing the driver
### Emulating the hardware
We will emulate a SDHC device using qemu as all system may not have the device.
These days systems provide transfer to SD/ MMC card over USB. The document will
not instruct you on how to build haiku but you can refer the link to
[compile and build the haiku images](https://www.haiku-os.org/guides/building/)
or the [week #1 and #2](https://www.haiku-os.org/blog/krish_iyer/2018-05-06_gsoc_2018_sdhci_mmc_driver_week_1_and_2/)
project report will also work.
After building the image, we will emulate the hardware and host haiku on top of that.
#### Emulation
For emulating a sdhci-pci device
qemu-img create sd-card.img 32M
qemu-system-x86_64 -drive index=0,file=haiku-nightly-anyboot.iso,format=raw \
-device sdhci-pci -device sd-card,drive=mydrive \
-drive if=sd,index=1,file=sd-card.img,format=raw,id=mydrive
-m 512M -enable-kvm -usbdevice tablet -machine q35
This does the following:
- Create an SD card image of 32MB
- Run qemu with a bootable image in an IDE disk, and an SDHCI bus with an SD card in it
- Have enough memory to boot Haiku, use KVM mode for speed, and a tablet for ease of use
- Use the Q35 chipset so the mouse and SDHCI controllers don't share an interrupt (not strictly
required, but it avoids calls to the SDHCI interrupt handler on every mouse move).
Tracing of SD operations can also be added to see how qemu is interpreting our commands:
-trace sdhci* -trace sdbus* -trace sdcard*
### Testing and loading the driver
The code is merged and part of the default Haiku build.
## Insight into the code and future tasks
### Bus, bus manager, and drivers
The MMC stack is a device manager based "new style" driver. This requires splitting the driver
in different parts but allow easy reuse of each part (for example to support eMMC or SDIO with a
large part of the code in common with plain SD/MMC).
#### MMC Bus drivers (src/add-ons/kernel/busses/mmc)
The bus driver provides the low level aspects: interrupts management, DMA transfer, accessing the
hardware registers. It acts as a platform abstraction layer so that the bus manager and disk driver
can be written independently of the underlying hardare.
Currently there is a single implementation for SDHCI (MMC bus over PCI). Later on, other drivers
will be added for other ways to access the MMC bus (for example on ARM devices where it does not
live on a PCI bus, and may have a different register layout).
For this reason, the bus drivers should only do the most low-level things, trying to keep as
much code as possible in the upper layers.
One slightly confusing thing about SDHCI is that it allows a single PCI device to implement
multiple separate MMC busses (each of which could have multiple devices attached). For this reason
there is an SDHCI "device" that attaches to the PCI device node for the controller, and then
publishes multiple device nodes for each available bus. The nodes then work independently of each
other.
#### The Bus Manager (src/add-ons/kernel/bus_managers/mmc)
The bus manager is responsible for enumerating devices on the bus, assigning them addresses,
and keeping track of which card is active at any given time.
Essentially it has everything that requires collaboration between multiple MMC devices, as well
as things that are not specific to a device type (common to SDIO, SD and MMC cards, for example)
#### Disk Driver (src/add-ons/kernel/drivers/disk/mmc)
This is a mass storage driver for MMC, SD and SDHC cards. Currently only SD and SDHC are tested,
MMC and eMMC will have to be added (they are similar but there are some differences).
#### Wiring the driver in the device manager (src/system/kernel/device_manager/device_manager.cpp)
(note: possibly not accurate documentation, I did not check how things in the device manager are
actually implemented, but this is my understanding of it).
The device manager attempts to implement lazy, on-demand scanning of the devices. The idea is to
speed up booting by not spending a lot of time scanning everything first, and only scanning
small parts of the device tree as they are needed.
The trigger is accesses to the devfs. For example, when an application opens /dev/disk, the device
manager will start looking for disks so it can populate it. This means the device manager needs to
know which branches of the device tree to explore. Currently this knowledge is hardcoded into the
device tree sourcecode, and there's a TODO item about moving that knowledge to drivers instead. But
it's tricky, since the whole point is to avoid loading all the drivers.
Anyway, currently, the device manager is hardcoded to look for mass storage devices under SDHCI
busses, both standard ones and some non-standard ones (for example, Ricoh provides SDHCI implenentations
that are conform to the spec, except they don't have the right device type in the PCI registers).
### Insight into the code
#### MMC Bus management overview
The device tree for MMC support looks like this:
* PCI bus manager
* (other PCI devices)
* SDHCI controller
* SDHCI bus
* MMC bus manager
* MMC device
* mmc\_disk device
* MMC device
* (other SDIO driver)
* MMC bus manager (second MMC bus)
* MMC device
* mmc\_disk device
At the first level, the PCI bus manager publishes a device node for each device
found. One of them is our SDHCI controller, identified either by the PCI device
class and subclass, or for not completely SDHCI compatible device, by the
device and vendor IDs.
The SDHCI bus driver attaches to this device and publishes his own node. It
then scans the device and publishes an MMC bus node for each slot (there may
be multiple SD slots attached to a single PCI controller).
The MMC bus manager then attach to each of these slots, and send the appropriate
commands for enumerating the SD cards (there may be multiple cards in a "slot"),
and publishes a device node for each of them. Finally, the mmc\_disk driver can
bind itself to one of these device nodes, and publish the corresponding disk
node, which is also be made available in /dev/disk/mmc.
Currently the mmc bus does not publish anything in the devfs, but this could be
added if sending raw SD/MMC commands to SD cards from userland is considered
desirable.
#### SDHCI driver
The SDHCI driver is the lowest level of the MMC stack. It provides abstraction
of the SDHCI device. Later on, different way to access an SD bus may be added,
for example for ARM devices which decided to use a different register interface.
The entry point is as usual **supports\_device()**. This method is called only
for devices which may be SDHCI controllers, thanks to filtering done in the
device manager to probe only the relevant devices. The probing is done on-demand,
currently when the system is enumerating /dev/disk in the devfs. Later on, when
we have SDIO support, probing will also be triggered in other cases.
The function identifies the device by checking the class and subclass, as well
as a limited set of hardcoded PCI device and vendor IDs for devices that do not
use the assigned subclass.
Once a compatible device is found, **register\_child\_devices()** is used to
publish device nodes for each slot to be controlled by the mmc bus manager.
The registers for each device are mapped into virtual memory, using the
information from the PCI bar registers. **struct registers** is defined so that
it matches the register layout, and provide a little abstraction to raw register
access.
An SdhciBus object is created to manage each of these busses at the SDHCI level.
It will be responsible for executing SD commands on that bus, and dealing with
the resulting interrupts.
#### The Bus Manager
The MMC bus manager manages the MMC bus (duh). Its tasks are:
* enumerating SD cards on the bus
* assigning RCAs to the cards for identifying them when sending commands
* setting the bus clock speed according to what the cards can handle
* remember which SD card is currently active (CMD7)
* manage cards state
* publish device nodes for each card
#### Disk Driver
The disk driver is attached to devices implementing SDSC or SDHC/SDXC commands.
There will be other drivers for non-storage (SDIO) cards.
To help with this, the MMC bus manager provides the device with the information
it gathered while initializing the device. According to the commands recognized
by the card during the initialization sequence, it's possible to know if it's
SDSC, SDHC/SDXC, or something else (SDIO, legacy MMC, etc).
The disk driver publishes devfs entries in /dev/disk/mmc and implements the
usual interface for disk devices. From this point on, the device can be used
just like any other mass storage device.
#### Getting everything loaded
The device manager is not completely implemented yet. As a result, some
decisions about which drivers to load are hardcoded in device\_manager.cpp.
It has been adjusted to handover SDHCI devices to the MMC bus. Whenever a
"disk" device is requested, the MMC busses are searched, which results in
loading the SDHCI driver and probing for SD cards. When we get support for
other types of SDIO devices, we will need to adjust the device manager to
probe the SDHCI bus when these type of devices are requested, too.
### Tasks to be completed
The SDHCI driver is able to send and receive commands. However it does not
handle card insertion and removal interrupts yet, so the card must be already
inserted when the driver is loaded.
The mmc_disk driver is complete and working, but was not tested for MMC and eMMC
devices. Some changes may be needed.
There is also work to be done for better performance: making sure we switch to the
high-speed clock when an SD card supports it, and use the 4-bit data transfer mode
instead of the default 1-bit if possible.
Drivers for SDIO devices should also be added. The mmc_bus and SDHCI drivers have
been tested only with one card on the bus at a time (for lack of hardware allowing
more complex setups).
If you find it difficult to understand the driver development and it's
functioning and role, please refer *docs/develop/kernel/device_manager_introduction.html*
@@ -0,0 +1,291 @@
SDHCI MMC Driver
================
This driver project is a part of GSoC18 and is aimed at providing
support for PCI devices with class 8 and subclass 5 over x86
architecture. This document will make you familiar with the `code
produced during GSoC <https://review.haiku-os.org/#/c/haiku/+/318/>`__,
loading and testing the driver(including hardware emulation), insight
into the code and future tasks.
For detailed explanations about the project, you can refer the `weekly
reports <https://www.haiku-os.org/blog/krish_iyer>`__ and comment issues
if any. For this project we have referred `SD Host Controller Spec
Version
1.00 <https://www.sdcard.org/downloads/pls/pdf/index.php?p=PartA2_SD_Host_Controller_Simplified_Specification_Ver1.00.jpg&f=PartA2_SD_Host_Controller_Simplified_Specification_Ver1.00.pdf&e=EN_A2100>`__
and `Physical Layer Spec Version
1.10 <https://www.sdcard.org/downloads/pls/pdf/index.php?p=Part1_Physical_Layer_Simplified_Specification_Ver1.10.jpg&f=Part1_Physical_Layer_Simplified_Specification_Ver1.10.pdf&e=EN_P1110>`__.
Loading and testing the driver
------------------------------
Emulating the hardware
~~~~~~~~~~~~~~~~~~~~~~
We will emulate a SDHC device using qemu as all system may not have the
device. These days systems provide transfer to SD/ MMC card over USB.
The document will not instruct you on how to build haiku but you can
refer the link to `compile and build the haiku
images <https://www.haiku-os.org/guides/building/>`__ or the `week #1
and
#2 <https://www.haiku-os.org/blog/krish_iyer/2018-05-06_gsoc_2018_sdhci_mmc_driver_week_1_and_2/>`__
project report will also work.
After building the image, we will emulate the hardware and host haiku on
top of that.
Emulation
^^^^^^^^^
For emulating a sdhci-pci device
::
qemu-img create sd-card.img 32M
qemu-system-x86_64 -drive index=0,file=haiku-nightly-anyboot.iso,format=raw \
-device sdhci-pci -device sd-card,drive=mydrive \
-drive if=sd,index=1,file=sd-card.img,format=raw,id=mydrive
-m 512M -enable-kvm -usbdevice tablet -machine q35
This does the following: - Create an SD card image of 32MB - Run qemu
with a bootable image in an IDE disk, and an SDHCI bus with an SD card
in it - Have enough memory to boot Haiku, use KVM mode for speed, and a
tablet for ease of use - Use the Q35 chipset so the mouse and SDHCI
controllers dont share an interrupt (not strictly required, but it
avoids calls to the SDHCI interrupt handler on every mouse move).
Tracing of SD operations can also be added to see how qemu is
interpreting our commands:
::
-trace sdhci* -trace sdbus* -trace sdcard*
Testing and loading the driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The code is merged and part of the default Haiku build.
Insight into the code and future tasks
--------------------------------------
Bus, bus manager, and drivers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The MMC stack is a device manager based “new style” driver. This
requires splitting the driver in different parts but allow easy reuse of
each part (for example to support eMMC or SDIO with a large part of the
code in common with plain SD/MMC).
MMC Bus drivers (src/add-ons/kernel/busses/mmc)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The bus driver provides the low level aspects: interrupts management,
DMA transfer, accessing the hardware registers. It acts as a platform
abstraction layer so that the bus manager and disk driver can be written
independently of the underlying hardare.
Currently there is a single implementation for SDHCI (MMC bus over PCI).
Later on, other drivers will be added for other ways to access the MMC
bus (for example on ARM devices where it does not live on a PCI bus, and
may have a different register layout).
For this reason, the bus drivers should only do the most low-level
things, trying to keep as much code as possible in the upper layers.
One slightly confusing thing about SDHCI is that it allows a single PCI
device to implement multiple separate MMC busses (each of which could
have multiple devices attached). For this reason there is an SDHCI
“device” that attaches to the PCI device node for the controller, and
then publishes multiple device nodes for each available bus. The nodes
then work independently of each other.
The Bus Manager (src/add-ons/kernel/bus_managers/mmc)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The bus manager is responsible for enumerating devices on the bus,
assigning them addresses, and keeping track of which card is active at
any given time.
Essentially it has everything that requires collaboration between
multiple MMC devices, as well as things that are not specific to a
device type (common to SDIO, SD and MMC cards, for example)
Disk Driver (src/add-ons/kernel/drivers/disk/mmc)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is a mass storage driver for MMC, SD and SDHC cards. Currently only
SD and SDHC are tested, MMC and eMMC will have to be added (they are
similar but there are some differences).
Wiring the driver in the device manager (src/system/kernel/device_manager/device_manager.cpp)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(note: possibly not accurate documentation, I did not check how things
in the device manager are actually implemented, but this is my
understanding of it).
The device manager attempts to implement lazy, on-demand scanning of the
devices. The idea is to speed up booting by not spending a lot of time
scanning everything first, and only scanning small parts of the device
tree as they are needed.
The trigger is accesses to the devfs. For example, when an application
opens /dev/disk, the device manager will start looking for disks so it
can populate it. This means the device manager needs to know which
branches of the device tree to explore. Currently this knowledge is
hardcoded into the device tree sourcecode, and theres a TODO item about
moving that knowledge to drivers instead. But its tricky, since the
whole point is to avoid loading all the drivers.
Anyway, currently, the device manager is hardcoded to look for mass
storage devices under SDHCI busses, both standard ones and some
non-standard ones (for example, Ricoh provides SDHCI implenentations
that are conform to the spec, except they dont have the right device
type in the PCI registers).
Insight into the code
~~~~~~~~~~~~~~~~~~~~~
MMC Bus management overview
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The device tree for MMC support looks like this:
- PCI bus manager
- (other PCI devices)
- SDHCI controller
- SDHCI bus
- MMC bus manager
- MMC device
- mmc_disk device
- MMC device
- (other SDIO driver)
- MMC bus manager (second MMC bus)
- MMC device
- mmc_disk device
At the first level, the PCI bus manager publishes a device node for each
device found. One of them is our SDHCI controller, identified either by
the PCI device class and subclass, or for not completely SDHCI
compatible device, by the device and vendor IDs.
The SDHCI bus driver attaches to this device and publishes his own node.
It then scans the device and publishes an MMC bus node for each slot
(there may be multiple SD slots attached to a single PCI controller).
The MMC bus manager then attach to each of these slots, and send the
appropriate commands for enumerating the SD cards (there may be multiple
cards in a “slot”), and publishes a device node for each of them.
Finally, the mmc_disk driver can bind itself to one of these device
nodes, and publish the corresponding disk node, which is also be made
available in /dev/disk/mmc.
Currently the mmc bus does not publish anything in the devfs, but this
could be added if sending raw SD/MMC commands to SD cards from userland
is considered desirable.
SDHCI driver
^^^^^^^^^^^^
The SDHCI driver is the lowest level of the MMC stack. It provides
abstraction of the SDHCI device. Later on, different way to access an SD
bus may be added, for example for ARM devices which decided to use a
different register interface.
The entry point is as usual **supports_device()**. This method is called
only for devices which may be SDHCI controllers, thanks to filtering
done in the device manager to probe only the relevant devices. The
probing is done on-demand, currently when the system is enumerating
/dev/disk in the devfs. Later on, when we have SDIO support, probing
will also be triggered in other cases.
The function identifies the device by checking the class and subclass,
as well as a limited set of hardcoded PCI device and vendor IDs for
devices that do not use the assigned subclass.
Once a compatible device is found, **register_child_devices()** is used
to publish device nodes for each slot to be controlled by the mmc bus
manager. The registers for each device are mapped into virtual memory,
using the information from the PCI bar registers. **struct registers**
is defined so that it matches the register layout, and provide a little
abstraction to raw register access.
An SdhciBus object is created to manage each of these busses at the
SDHCI level. It will be responsible for executing SD commands on that
bus, and dealing with the resulting interrupts.
The Bus Manager
^^^^^^^^^^^^^^^
The MMC bus manager manages the MMC bus (duh). Its tasks are:
- enumerating SD cards on the bus
- assigning RCAs to the cards for identifying them when sending
commands
- setting the bus clock speed according to what the cards can handle
- remember which SD card is currently active (CMD7)
- manage cards state
- publish device nodes for each card
Disk Driver
^^^^^^^^^^^
The disk driver is attached to devices implementing SDSC or SDHC/SDXC
commands. There will be other drivers for non-storage (SDIO) cards.
To help with this, the MMC bus manager provides the device with the
information it gathered while initializing the device. According to the
commands recognized by the card during the initialization sequence, its
possible to know if its SDSC, SDHC/SDXC, or something else (SDIO,
legacy MMC, etc).
The disk driver publishes devfs entries in /dev/disk/mmc and implements
the usual interface for disk devices. From this point on, the device can
be used just like any other mass storage device.
Getting everything loaded
^^^^^^^^^^^^^^^^^^^^^^^^^
The device manager is not completely implemented yet. As a result, some
decisions about which drivers to load are hardcoded in
device_manager.cpp.
It has been adjusted to handover SDHCI devices to the MMC bus. Whenever
a “disk” device is requested, the MMC busses are searched, which results
in loading the SDHCI driver and probing for SD cards. When we get
support for other types of SDIO devices, we will need to adjust the
device manager to probe the SDHCI bus when these type of devices are
requested, too.
Tasks to be completed
~~~~~~~~~~~~~~~~~~~~~
The SDHCI driver is able to send and receive commands. However it does
not handle card insertion and removal interrupts yet, so the card must
be already inserted when the driver is loaded.
The mmc_disk driver is complete and working, but was not tested for MMC
and eMMC devices. Some changes may be needed.
There is also work to be done for better performance: making sure we
switch to the high-speed clock when an SD card supports it, and use the
4-bit data transfer mode instead of the default 1-bit if possible.
Drivers for SDIO devices should also be added. The mmc_bus and SDHCI
drivers have been tested only with one card on the bus at a time (for
lack of hardware allowing more complex setups).
If you find it difficult to understand the driver development and its
functioning and role, please refer
*docs/develop/kernel/device_manager_introduction.html*
@@ -1,4 +1,4 @@
INSIDE THE USB STACK
The USB stack
==============
For all of you who have been over my code and thought: "What is this guy doing?", I can firmly respond that I sometimes haven't a clue either. But when I'm actually reviewing my own code, I occassionally get the feeling that I do have a direction I'm persueing. In order to define this direction more, and share you in the fun, I have written this document. It's a rather superficial account (without actually quoting any code), but it should be enough to get you started.
+173
View File
@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'Haiku internals'
copyright = '2021, The Haiku development team'
author = 'The Haiku development team'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = ''
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'haiku'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Haikuinternalsdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Haikuinternals.tex', 'Haiku internals Documentation',
'The Haiku development team', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'haikuinternals', 'Haiku internals Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Haikuinternals', 'Haiku internals Documentation',
author, 'Haikuinternals', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
@@ -1,37 +1,48 @@
Disk driver ioctls
==================
Here is a list of ioctls usually implemented by disk devices.
B_GET_DEVICE_SIZE
-----------------
The parameter is a size_t and is filled with the disk size in bytes.
This is limited to 4GB and not very useful. B_GET_GEOMETRY is used instead.
B_GET_GEOMETRY
--------------
The parameter is a device_geometry structure to be filled with the device geometry.
B_GET_ICON_NAME
---------------
Deprecated. Get the name of an icon to use. The icons are hardcoded in Tracker.
B_GET_VECTOR_ICON
-----------------
The parameter is a device_icon structure to be populated with the icon data in HVIF format.
This icon is then used to show the disk in Tracker, for example.
B_EJECT_DEVICE
--------------
Eject the device (for removable devices).
B_LOAD_MEDIA
------------
Load the device (reverse of eject) if possible.
B_FLUSH_DRIVE_CACHE
-------------------
Make sure all data is stored on persistent storage and not in caches (including any caching inside
the device)
B_TRIM_DEVICE
-------------
The parameter is an fs_trim_data structure. It is guaranteed to be in kernel memory because
the partition manager pre-processes requests coming from userland and makes sure no sectors
@@ -1,5 +1,5 @@
Intel video hardware generations
================================
################################
This file summarizes the different generations of Intel hardware, because the
naming is a bit inconsistent and it's hard to follow which is which sometimes.
@@ -13,12 +13,12 @@ These are the i740 and i810 devices handled by intel_810. No further info will
be provided here.
Generation 2 / 2002
============
===================
i830, 845, 85x, 865
Generation 3 / 2004
============
===================
This is the first generation to be documented at intellinuxgraphics.org.
Generation 2 devices are quite similar for the modesetting part, but not
@@ -31,7 +31,7 @@ GMA 3100 (G31, G33, Q33 et Q35)
GMA 3150 (Pineview for Atom CPUs)
Generation 4 / 2006
============
===================
GMA X3000 (i965G)
GMA X3100 (i965GM)
@@ -41,7 +41,7 @@ GMA 4500M / 4500HD (GL40, GS45, GM45, GM47)
GMA X4500 / X4500HD (G41, G43 (X4500), G45 (X4500HD))
Generation 5 / 2010
============
===================
Westmere / Clarkdale, Arrandale / Iron Lake / Ibex Peak
@@ -50,14 +50,14 @@ Switches from the traditional northbridge / southbridge to the new
are now directly in the CPU package.
Generation 6 / 2011
============
===================
Sandybridge / Cougar Point
The northbridge and CPU are now even on the same die.
Generation 7 / 2012
============
===================
Ivy Bridge / Panther Point and Haswell / Lynx Point
@@ -73,12 +73,12 @@ This is also the first generation to support 3 independant displays, which
also impacts the register layout in many places.
Generation 8 / 2013
============
===================
Broadwell / Wildcat Point and Braswell
Generation 9 / 2015
============
===================
Skylake / Sunrise Point, Apollo Lake, Kaby Lake / Union Point
@@ -1,24 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
<TITLE>Resources for the Be File System</TITLE>
</HEAD>
<BODY text="#000000" link="#0000FF" bgcolor="#FFFFFF">
<H1>Resources for the Be File System</H1>
<p>Probably the best resource for a description of the inner workings of BFS is the
book "Practical File System Design with the Be File System" written by Dominic
Giampaolo and published by Morgan Kaufmann Publishers. Although that book is out of
print, it's available at the <A HREF="https://www.haiku-os.org/legacy-docs/practical-file-system-design.pdf">Haiku web site</A>.
<p>If you prefer to see how it works in code, please have a look at <A HREF="https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/file_systems/bfs">Haiku's BFS
implementation</A>.
<p>If you are interested in the Haiku file system API, please refer to its documentation
as part of the Haiku book.
</BODY>
</HTML>
@@ -0,0 +1,16 @@
The Be File System
================================
Probably the best resource for a description of the inner workings of
BFS is the book "Practical File System Design with the Be File System"
written by Dominic Giampaolo and published by Morgan Kaufmann
Publishers. Although that book is out of print, it's available at the
`Haiku web
site <https://www.haiku-os.org/legacy-docs/practical-file-system-design.pdf>`__.
If you prefer to see how it works in code, please have a look at
`Haiku's BFS
implementation <https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/file_systems/bfs>`__.
If you are interested in the Haiku file system API, please refer to its
documentation as part of the Haiku book.
@@ -1,4 +1,4 @@
Writing filesystem drivers for Haiku
Filesystem drivers
====================================
Filesystem drivers are in src/add-ons/kernel/file_system
@@ -8,6 +8,7 @@ required. For example, NFS is a network filesystem, so it doesn't need one.
Implementation notes
--------------------
Each filesystem driver must define a few structures which act as the
interface between the VFS and the filesystem implementation. These
structures contain function pointers, some of which are optional,
@@ -85,3 +86,8 @@ as kernel code is usually quite easy (and provides a performance boost)
Once the basic operations are working fine, it is a good idea to perform more
agressive testing. Examples of scripts doing this are available in
src/tests/add-ons/kernel/file_systems/ for the fat and ext2 filesystems.
.. toctree::
/file_systems/ufs2
/file_systems/befs/resources
@@ -1,4 +1,4 @@
Implementation of UFS2 on Haiku
The UFS2 filesystem
===============================
While making a device for testing I have used a usb drive and formatted it to
+69
View File
@@ -0,0 +1,69 @@
Welcome to Haiku internals's documentation!
===========================================
Target audience
---------------
This documentation is aimed at people who want to contribute to Haiku by modifying the operating
system itself. It covers various topics, both technical (how things work) and organizational
(patch submission process, for example).
This document might also be useful to application developers trying to understand the behavior of
the operating system in some specific cases, however, the `API documentation <https://api.haiku-os.org>`_ should answer most of
the questions in this area already.
This documentation assumes basic knowledge of C++ and the Be API, if you need more information
about that, please see the `Learning to program with Haiku <https://github.com/theclue/programming-with-haiku/releases/tag/v1.1>`_ book.
Status of this document
-----------------------
The work on this book has just started, many sections are incomplete or missing. Here is a list of
other resources that could be useful:
* The `Haiku website <https://www.haiku-os.org>`_ has several years of blog posts and articles
documenting many aspects of the system,
* The `Coding guidelines <https://www.haiku-os.org/development/coding-guidelines/>`_ describes how code should be formatted,
* The `User guide <https://www.haiku-os.org/docs/userguide/en/contents.html>`_ documents Haiku from the users' point of view and can be useful to understand how things are supposed to work,
* The `Haiku Interface Guidelines <https://www.haiku-os.org/docs/HIG/index.xml>`_ document graphical user interface conventions,
* The `Haiku Icon Guidelines <https://www.haiku-os.org/development/icon-guidelines>`_ gives some rules for making icons fitting with the style of the existing ones.
Table of contents
-----------------
* :ref:`search`
.. toctree::
:maxdepth: 2
:caption: Contents:
/build/repositories/README
/apps/haikudepot/server
/midi/index
/net/NetworkStackOverview
/net/HowTo-Synchronize_with_NetBSD
/packages/README
/servers/app_server/toc
/servers/registrar/Protocols
/kernel/device_manager_introduction
/kernel/obsolete_pnp_manager
/kernel/vm/swap_file_support
/kernel/arch/long_double
/kernel/arch/arm/overview
/kernel/arch/m68k/overview
/kernel/arch/ppc/overview
/kernel/arch/sparc/overview
/kernel/fs/node_monitoring
/file_systems/overview
/partitioning_systems/sun
/drivers/disk/ioctls
/drivers/intel_extreme/generations
/busses/agp_gart/ReadMe
/busses/bluetooth/overview
/busses/sdhci/sdhci_mmc_driver
/busses/usb/USB_stack_design
/kernel/boot/boot_process_specs.rst
/kernel/boot/Debugging_Bootloaders_GEF
/kernel/pci_serial_debug
@@ -1,84 +0,0 @@
# Allwinner A10
* http://linux-sunxi.org
# Hardware Information
The A10 is a system-on chip. There are many devices based on it, for example
the CubieBoard and the Rikomagic mk802 (versions I and II).
* ARMv7 Architecture (Cortex-A8)
* Mali 400MP GPU
* CedarX VPU
* SD Card Storage
* 1GB RAM (DDR)
* 4GB NAND Flash
* Video Outputs
* HDMI Video Output
* Ethernet
* USB
# Setting up the Haiku SD card
Not so fun layout here. The A10 boot ROM reads raw blocks from the SD card
(MBR style), so the bootloader can't just be dropped in a FAT32 partition.
* 8KB partition table
* 24KB SPL loader
* 512KB u-boot
* 128KB u-boot environment variables
* 352KB unused
* partition 1 -- FAT32 or ext2 (anything u-boot can read is fine)
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
Note this layout can be a bit different depending on the u-boot version used,
some versions will store the environment in uEnv.txt in the FAT32 partition
instead. Since everything is loaded from the SD Card, we are free to customize
the u-boot or even remove it and get haiku_loader booting directly.
## Boot Partition
### Required Files
* haiku_loader: Haiku Loader
* haiku-floppyboot.tgz: Compressed image with Haiku kernel
# Booting
1. SOC load SPL
2. SPL loads u-boot
2. u-boot loads and run the kernel
SPL is a small binary (24K) loaded from a fixed location on the SD card. It
does minimal hardware initializations, then loads u-boot, also from the SD
card. From there on things go as usual.
In the long term, we can make haiku_loader be an SPL executable on this
platform, if it fits the 24K size limit, or have a custom stage1 that loads it.
For now, u-boot can be an useful debugging tool.
## Script.bin
In order to work on different devices (RAM timings, PIO configs, ...), the
Linux kernels for Allwinner chips use a "script.bin" file. This is loaded to
RAM at a fixed address by u-boot, then the Kernel parses it and uses it to
configure the hardware (similar to FDT).
We should probably NOT use this, and convert the script.bin file to an FDT
instead. The format is known and there are tools to convert the binary file
to an editable text version and back (bin2fex and fex2bin).
This FEX stuff isn't merged in mainline Linux, and lives on as Allwinner
patches. The mainline Linux kernel has some A10 support, rewritten to use
FDT. We may use the FDT files from there for the most common boards.
# Emulation support
qemu 1.0 has a Cubieoard target which emulates this chip.
# Useful links
Arch Linux instructions on creating a bootable SD card (partition layout, etc)
http://archlinuxarm.org/platforms/armv7/allwinner/cubieboard#qt-platform_tabs-ui-tabs2
Linux SunXi: mainline Linux support for the Allwinner chips. Lots of docs on the hardware.
http://linux-sunxi.org/
@@ -0,0 +1,99 @@
Allwinner A10 hardware notes
############################
- http://linux-sunxi.org
Hardware Information
====================
The A10 is a system-on chip. There are many devices based on it, for
example the CubieBoard and the Rikomagic mk802 (versions I and II).
- ARMv7 Architecture (Cortex-A8)
- Mali 400MP GPU
- CedarX VPU
- SD Card Storage
- 1GB RAM (DDR)
- 4GB NAND Flash
- Video Outputs
- HDMI Video Output
- Ethernet
- USB
Setting up the Haiku SD card
============================
Not so fun layout here. The A10 boot ROM reads raw blocks from the SD
card (MBR style), so the bootloader cant just be dropped in a FAT32
partition.
- 8KB partition table
- 24KB SPL loader
- 512KB u-boot
- 128KB u-boot environment variables
- 352KB unused
- partition 1 FAT32 or ext2 (anything u-boot can read is fine)
- partition 2 BeFS, Haiku filesystem, type eb
Note this layout can be a bit different depending on the u-boot version
used, some versions will store the environment in uEnv.txt in the FAT32
partition instead. Since everything is loaded from the SD Card, we are
free to customize the u-boot or even remove it and get haiku_loader
booting directly.
Boot Partition
--------------
Required Files
~~~~~~~~~~~~~~
- haiku_loader: Haiku Loader
- haiku-floppyboot.tgz: Compressed image with Haiku kernel
Booting
=======
1. SOC load SPL
2. SPL loads u-boot
3. u-boot loads and run the kernel
SPL is a small binary (24K) loaded from a fixed location on the SD card.
It does minimal hardware initializations, then loads u-boot, also from
the SD card. From there on things go as usual.
In the long term, we can make haiku_loader be an SPL executable on this
platform, if it fits the 24K size limit, or have a custom stage1 that
loads it. For now, u-boot can be an useful debugging tool.
Script.bin
----------
In order to work on different devices (RAM timings, PIO configs, …), the
Linux kernels for Allwinner chips use a “script.bin” file. This is
loaded to RAM at a fixed address by u-boot, then the Kernel parses it
and uses it to configure the hardware (similar to FDT).
We should probably NOT use this, and convert the script.bin file to an
FDT instead. The format is known and there are tools to convert the
binary file to an editable text version and back (bin2fex and fex2bin).
This FEX stuff isnt merged in mainline Linux, and lives on as Allwinner
patches. The mainline Linux kernel has some A10 support, rewritten to
use FDT. We may use the FDT files from there for the most common boards.
Emulation support
=================
qemu 1.0 has a Cubieoard target which emulates this chip.
Useful links
============
Arch Linux instructions on creating a bootable SD card (partition
layout, etc)
http://archlinuxarm.org/platforms/armv7/allwinner/cubieboard#qt-platform_tabs-ui-tabs2
Linux SunXi: mainline Linux support for the Allwinner chips. Lots of
docs on the hardware. http://linux-sunxi.org/
-60
View File
@@ -1,60 +0,0 @@
# BeagleBone Black
* http://beagleboard.org
* TODO: This is a WIP
# Hardware information (Rev A5A)
* ARMv7 Architecture
* Sitara AM3359AZCZ100 Cortex-A8 CPU @ 1 Ghz
* PowerVR SGX530 3D GPU
* eMMC Onboard Storage 2GB (MMC1)
* SD Card Storage (MMC0)
* 512 MB DDR3L RAM
* Video Outputs
* HDMI Video Output (with audio)
* SMSC LAN8710A Ethernet
# Setting up the Haiku SD card
The BeagleBone Black supports booting from an microSD card while the boot switch is pressed at power on. A MBR file system layout is normally used as seen below. Partition 1 is all that is required to boot an OS.
* partition 1 -- FAT32, bootable flag, type 'c'
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
## Boot Partition
### Required files
* MLO
* u-boot.img: u-Boot image
* uEnv.txt: u-Boot Environment settings
### Optional files
* ID.txt: Unknown
# Compiling
* Create your work directory `mkdir generated.beagle; cd generated.beagle`
* Build an ARM toolchain using `../configure --build-cross-tools arm ../../buildtools --target-board beagle`
* TODO
# Booting
1. If the boot switch is not depressed:
MMC1, MMC0, UART0, USB0
2. If the boot switch is depressed:
SPI0, MMC0, USB0, UART0
# Emulation
The Linaro Fork of QEmu has beagle board (and other OMAP3) support.
https://launchpad.net/qemu-linaro
It seems you get this as the default QEmu install on some, but not all, Ubuntu
versions. For other distros (or Haiku), you'll have to compile it yourself.
# Additional information
* [CircutCo WikiPage](http://circuitco.com/support/index.php?title=BeagleBoneBlack)
* [BeagleBone Black A5A SRM](https://github.com/CircuitCo/BeagleBone-Black/blob/master/BBB_SRM.pdf?raw=true)
+78
View File
@@ -0,0 +1,78 @@
BeagleBone Black
================
- http://beagleboard.org
- TODO: This is a WIP
Hardware information (Rev A5A)
==============================
- ARMv7 Architecture
- Sitara AM3359AZCZ100 Cortex-A8 CPU @ 1 Ghz
- PowerVR SGX530 3D GPU
- eMMC Onboard Storage 2GB (MMC1)
- SD Card Storage (MMC0)
- 512 MB DDR3L RAM
- Video Outputs
- HDMI Video Output (with audio)
- SMSC LAN8710A Ethernet
Setting up the Haiku SD card
============================
The BeagleBone Black supports booting from an microSD card while the
boot switch is pressed at power on. A MBR file system layout is normally
used as seen below. Partition 1 is all that is required to boot an OS.
- partition 1 FAT32, bootable flag, type c
- partition 2 BeFS, Haiku filesystem, type eb
Boot Partition
--------------
Required files
~~~~~~~~~~~~~~
- MLO
- u-boot.img: u-Boot image
- uEnv.txt: u-Boot Environment settings
Optional files
~~~~~~~~~~~~~~
- ID.txt: Unknown
Compiling
=========
- Create your work directory
``mkdir generated.beagle; cd generated.beagle``
- Build an ARM toolchain using
``../configure --build-cross-tools arm ../../buildtools --target-board beagle``
- TODO
Booting
=======
1. If the boot switch is not depressed: MMC1, MMC0, UART0, USB0
2. If the boot switch is depressed: SPI0, MMC0, USB0, UART0
Emulation
=========
The Linaro Fork of QEmu has beagle board (and other OMAP3) support.
https://launchpad.net/qemu-linaro
It seems you get this as the default QEmu install on some, but not all,
Ubuntu versions. For other distros (or Haiku), youll have to compile it
yourself.
Additional information
======================
- `CircutCo
WikiPage <http://circuitco.com/support/index.php?title=BeagleBoneBlack>`__
- `BeagleBone Black A5A
SRM <https://github.com/CircuitCo/BeagleBone-Black/blob/master/BBB_SRM.pdf?raw=true>`__
+8
View File
@@ -0,0 +1,8 @@
Efika MX
========
FDT
---
* http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/imx51x.dtsi?revision=248557&view=markup
* http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/efikamx.dts?revision=248557&view=markup
-5
View File
@@ -1,5 +0,0 @@
Notes on Efika MX port -*- org -*-
* FDT
http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/imx51x.dtsi?revision=248557&view=markup
http://svnweb.freebsd.org/base/head/sys/boot/fdt/dts/efikamx.dts?revision=248557&view=markup
+20
View File
@@ -0,0 +1,20 @@
iPaq
====
* http://www.NetBSD.org/ports/hpcarm/
* http://www.ibm.com/developerworks/linux/library/l-ipaq/index.html
* http://mstempin.free.fr/linux-ipaq/html_nochunks/Linux-iPAQ-HOWTO-1.1.html#BACKING-UP-BOOTLOADER
* http://www.eecs.umich.edu/~panalyzer/
* http://www.eecs.umich.edu/~panalyzer/sim-ipaq/sim_ipaq_readme.html
* http://blogs.unbolt.net/index.php/brinley/2007/08/04/exploring_hp_ipaq_6515e_bootloader
* http://gert-menke.de/jtag-howto/
* http://ecos.sourceware.org/docs-3.0/redboot-guide/ipaq.html
* http://www.balloonboard.org/balloon/balloon3/distro/test-v0.2/sources/balloonsvn/bootldr295/doc/install-via-osloader.html
RS232
-----
* http://www.kronosrobotics.com/Zeus/IPAQcon.pdf
* http://bevhoward.com/serial.htm
* http://www.mail-archive.com/[email protected]/msg132363.html
* http://web.archive.org/web/20050408063754/http://www.handhelds.org/pipermail/ipaq/2000-August/000061.html
-15
View File
@@ -1,15 +0,0 @@
http://www.NetBSD.org/ports/hpcarm/
http://www.ibm.com/developerworks/linux/library/l-ipaq/index.html
http://mstempin.free.fr/linux-ipaq/html_nochunks/Linux-iPAQ-HOWTO-1.1.html#BACKING-UP-BOOTLOADER
http://www.eecs.umich.edu/~panalyzer/
http://www.eecs.umich.edu/~panalyzer/sim-ipaq/sim_ipaq_readme.html
http://blogs.unbolt.net/index.php/brinley/2007/08/04/exploring_hp_ipaq_6515e_bootloader
http://gert-menke.de/jtag-howto/
http://ecos.sourceware.org/docs-3.0/redboot-guide/ipaq.html
http://www.balloonboard.org/balloon/balloon3/distro/test-v0.2/sources/balloonsvn/bootldr295/doc/install-via-osloader.html
RS232:
http://www.kronosrobotics.com/Zeus/IPAQcon.pdf
http://bevhoward.com/serial.htm
http://www.mail-archive.com/[email protected]/msg132363.html
http://web.archive.org/web/20050408063754/http://www.handhelds.org/pipermail/ipaq/2000-August/000061.html
@@ -1,2 +0,0 @@
* Does not support < ARMv5
* Requires support for high vectors
+170
View File
@@ -0,0 +1,170 @@
The ARM port
============
Note: there are in fact two ports to the ARM architecture, one for 32-bit, and one for 64-bit
systems. They don't have a lot of shared code as the two architectures are very different from
one another.
ARM devices are very popular, and especially since the release of the Raspberry Pi, people have
been requesting that Haiku is ported to it. Unfortunately, limitations in the architecture itself
and the wide diversity of hardware have made this task more complicated, and progress has been
slow. For example, ARM has no standard like the PC is for x86, so concepts as basic as a system
timer, a bootloader, or a serial port, are different from one machine to another. The situation
has improved with the later generations, as more things were integrated in the CPU core, and u-boot
is now well established as the main bootloader for ARM devices.
Limitations
-----------
There will be no support for hardware using architectures older than ARMv5. There will probably be
no support for architectures before ARMv7, which require more work on the compiler and OS, for
example due to lack of atomic instructions.
Support for high vectors (interrupt vectors stored at the end of the memory space) is required.
Information about specific hardware targets
-------------------------------------------
Over the years, various possible ARM targets have been considered for the Haiku ARM port.
We have accumulated some notes and documentation on some of them.
.. toctree::
/kernel/arch/arm/allwinner_a10
/kernel/arch/arm/beagle
/kernel/arch/arm/efikamx
/kernel/arch/arm/ipaq
/kernel/arch/arm/rpi1
/kernel/arch/arm/rpi2
TODO list
---------
Fix pre-ARMv7 support
*********************
The ARM instruction set has evolved a lot over time, and we have to make a choice: use the oldest
versions of the instruction set gives us maximal compatibility, but at the cost of a large
performance hit on newer systems, as well as extra code being needed in the OS to compensate for
the missing instructions.
Currently the cross-tools are compiled to default to ARMv7, Cortex-A8, and
hardware floating point. This works around the missing atomic support, see
below. This should be done by setting the -mcpu,-march and -mfloat-abi
switches at build time, however, they aren't passed on to haikuporter
during the bootstrap build, leading to the ports failing to find the
gcc atomic ops again.
It seems this create other problems, mainly because the UEFI environment for ARM is not supposed to
handle floating point registers. So, the softfloat ABI should be used there instead. To be able
to build both "soft float" and "hard float" code, we need multilib support, see below.
Determine how to handle atomic functions on ARM
***********************************************
GCC inlines are not supported, since the instructionset is ill-equiped for
this on older (pre-ARMv7) architectures. We possibly have to do something
similar to the linux kernel helper functions for this....
On ARMv7 and later, this is not an issue. Not sure about ARMv6, we may get
it going there. ARMv5 definitely needs us to write some code, but is it
worth the trouble?
Fix multilib support
********************
ARM-targetting versions of gcc are usually built with multilib support, to
allow targetting architectures with or without FPU, and using either ARM
or Thumb instructions. This bascally means a different libgcc and libstdc++
are built for each combination.
The cross-tools can be built with multilib support. However, we do some
tricks to get a separate libgcc and libstdc++ for the kernel (without C++11
threads support, as that would not build in the kernel). Building this lib
is not done in a multilib-aware way, so you get one only for the default
arch/cpu/abi the compiler is targetting. This is good enough, as long as that
arch is the one we want to use for the kernel...
Later on, the bootstrap build of the native gcc compiler will fail, because
it tries to build its multilib library set by linking against the different
versions of libroot (with and without fpu, etc). We only build one libroot,
so this also fails.
The current version of the x86_64 compiler appears is using multilib (to build for both 32 and 64
bit targets) and is working fine, so it's possible that most of the issues in this area have
already been fixed.
Figure out how to get page flags (modified/accessed) and implement it
*********************************************************************
use unmapped/read-only mappings to trigger soft faults for tracking used/modified flags for ARMv5 and ARMv6
Fix serial port mapping
***********************
Currently kernel uses the haiku_loader identity
mapping for it, but this lives in user virtual address space...
(Need to not use identity mapping in haiku_loader but just
map_physical_memory() there too so it can be handed over without issues).
Seperate ARM architecture/System-On-Chip IP code
************************************************
The early work on the ARM port resulted in lots of board specific code being added to early stages
of the kernel. Ideally, this would not be needed, the kernel would manage to initialize itself
mostly in a platform independant way, and get the needed information from the FDT passed by the
bootloader. The difficulty is that on older ARM versions, even the interrupt controller and timers
can be different on each machine.
KDL disasm module
*****************
Currently it is not possible to disassemble code in the kernel debugger.
The `NetBSD disassembler <http://fxr.watson.org/fxr/source/arch/arm/arm/disassem.c?v=NETBSD>`_ could be ported and used for this.
Add KDL hangman to the boot image
*********************************
for more enjoyment during porting....
Userland
********
Even if KDL hangman is fun, users will want to run real applications someday.
Bootloader TODOs
****************
- Better handling of memory ranges. Currently no checks are done, and
memory is assumed to be a single contiguous range, and the "input"
ranges for mmu_init are setup, but never considered.
- Allocate the pagetable range using mmu_allocate() instead of identity
mapping it. That way, there's a bit more flexibility in where to place
it both physically and virtually. This will need a minor change on the
kernel side too (in the early pagetable allocator).
Other resources
---------------
About flatenned device trees
****************************
* http://www.denx.de/wiki/U-Boot/UBootFdtInfo
* http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
* http://elinux.org/images/4/4e/Glikely-powerpc-porting-guide.pdf
* http://ols.fedoraproject.org/OLS/Reprints-2008/likely2-reprint.pdf
* http://www.bsdcan.org/2010/schedule/events/171.en.html
* http://www.devicetree.org/ (unofficial bindings)
* http://www.devicetree.org/Device_Tree_Usage
* http://elinux.org/Device_Trees
About openfirmware
******************
http://www.openfirmware.info/Bindings
About floating point numbers handling on ARM
********************************************
https://wiki.debian.org/ArmHardFloatPort/VfpComparison
-74
View File
@@ -1,74 +0,0 @@
# Raspberry Pi
* http://raspberrypi.org
# Hardware Information
* ARMv6 Architecture
* Broadcom BCM2835 (SoC)
* Includes ARM1176JZF-S CPU @ 700 MHz
* Includes VideoCore IV GPU
* SD Card Storage
* 256 or 512 MB RAM (depending on revision)
* Video Outputs
* HDMI Video Output
* Composite Video Output
* Ethernet
# Setting up the Haiku SD card
The Raspberry Pi SD card generally uses the MBR file system layout below. Partition 1 is all that is required to boot an OS.
* partition 1 -- FAT32, bootable flag, type 'c'
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
## Boot Partition
### Required Files
* bootcode.bin : 2nd stage bootloader
* start.elf: The GPU binary firmware image
* config.txt: A configuration file read by the Pi to start u-boot.bin
* u-boot.bin: u-boot loader for the Pi 2
* bcm2835-rpi-b.dtb: FDT binary for the Raspberry Pi 2
* haiku_loader_linux.ub: Haiku Loader
* haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku kernel
### Optional Files
* vlls directory: Additional GPU code, e.g. extra codecs.
* uEnv.txt: u-boot configuration script to automate boot.
# Compiling
* Create your ARM work directory `mkdir generated.arm; cd generated.arm`
* Build an ARM toolchain using `../configure --build-cross-tools arm ../../buildtools --target-board=rpi1`
* Build our loader using `jam -q haiku_loader_linux.ub`
* Build our initial ram disk using `jam -q haiku-floppyboot.tgz.ub`
# Booting
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
## config.txt Options
kernel=u-boot.bin
## u-boot startup
These will be condensed and automated long-term via uEnv.txt :-)
* `fatload mmc 0 ${fdt_addr_r} bcm2835-rpi-b.dtb`
* `fdt addr ${fdt_addr_r}`
* `fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub`
* `fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub`
* `bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}`
# Additional Information
* [Latest Raspberry Pi firmware](http://github.com/raspberrypi/firmware/tree/master/boot)
* [config.txt options](http://www.elinux.org/RPiconfig)
+96
View File
@@ -0,0 +1,96 @@
Raspberry Pi
############
- http://raspberrypi.org
Hardware Information
====================
- ARMv6 Architecture
- Broadcom BCM2835 (SoC)
- Includes ARM1176JZF-S CPU @ 700 MHz
- Includes VideoCore IV GPU
- SD Card Storage
- 256 or 512 MB RAM (depending on revision)
- Video Outputs
- HDMI Video Output
- Composite Video Output
- Ethernet
Setting up the Haiku SD card
============================
The Raspberry Pi SD card generally uses the MBR file system layout
below. Partition 1 is all that is required to boot an OS.
- partition 1 FAT32, bootable flag, type c
- partition 2 BeFS, Haiku filesystem, type eb
Boot Partition
--------------
Required Files
~~~~~~~~~~~~~~
- bootcode.bin : 2nd stage bootloader
- start.elf: The GPU binary firmware image
- config.txt: A configuration file read by the Pi to start u-boot.bin
- u-boot.bin: u-boot loader for the Pi 2
- bcm2835-rpi-b.dtb: FDT binary for the Raspberry Pi 2
- haiku_loader_linux.ub: Haiku Loader
- haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku
kernel
Optional Files
~~~~~~~~~~~~~~
- vlls directory: Additional GPU code, e.g. extra codecs.
- uEnv.txt: u-boot configuration script to automate boot.
Compiling
=========
- Create your ARM work directory
``mkdir generated.arm; cd generated.arm``
- Build an ARM toolchain using
``../configure --build-cross-tools arm ../../buildtools --target-board=rpi1``
- Build our loader using ``jam -q haiku_loader_linux.ub``
- Build our initial ram disk using ``jam -q haiku-floppyboot.tgz.ub``
Booting
=======
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
config.txt Options
------------------
::
kernel=u-boot.bin
u-boot startup
--------------
These will be condensed and automated long-term via uEnv.txt :-)
- ``fatload mmc 0 ${fdt_addr_r} bcm2835-rpi-b.dtb``
- ``fdt addr ${fdt_addr_r}``
- ``fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub``
- ``fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub``
- ``bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}``
Additional Information
======================
- `Latest Raspberry Pi
firmware <http://github.com/raspberrypi/firmware/tree/master/boot>`__
- `config.txt options <http://www.elinux.org/RPiconfig>`__
-74
View File
@@ -1,74 +0,0 @@
# Raspberry Pi 2
* http://raspberrypi.org
# Hardware Information
* ARMv7 Architecture
* Broadcom BCM2836 (SoC)
* Includes Quad ARM1176JZF-S CPU @ 900 MHz
* Includes VideoCore IV GPU
* SD Card Storage
* 1 GB RAM
* Video Outputs
* HDMI Video Output
* Composite Video Output
* Ethernet
# Setting up the Haiku SD card
The Raspberry Pi SD card generally uses the MBR file system layout below. Partition 1 is all that is required to boot an OS.
* partition 1 -- FAT32, bootable flag, type 'c'
* partition 2 -- BeFS, Haiku filesystem, type 'eb'
## Boot Partition
### Required Files
* bootcode.bin : 2nd stage bootloader
* start.elf: The GPU binary firmware image
* config.txt: A configuration file read by the Pi to start u-boot.bin
* u-boot.bin: u-boot loader for the Pi 2
* bcm2836-rpi-2-b.dtb: FDT binary for the Raspberry Pi 2
* haiku_loader_linux.ub: Haiku Loader
* haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku kernel
### Optional Files
* vlls directory: Additional GPU code, e.g. extra codecs.
* uEnv.txt: u-boot configuration script to automate boot.
# Compiling
* Create your ARM work directory `mkdir generated.arm; cd generated.arm`
* Build an ARM toolchain using `../configure --build-cross-tools arm ../../buildtools --target-board=rpi2`
* Build our loader using `jam -q haiku_loader_linux.ub`
* Build our initial ram disk using `jam -q haiku-floppyboot.tgz.ub`
# Booting
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
## config.txt Options
kernel=u-boot.bin
## u-boot startup
These will be condensed and automated long-term via uEnv.txt :-)
* `fatload mmc 0 ${fdt_addr_r} bcm2836-rpi-2-b.dtb`
* `fdt addr ${fdt_addr_r}`
* `fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub`
* `fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub`
* `bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}`
# Additional Information
* [Latest Raspberry Pi firmware](http://github.com/raspberrypi/firmware/tree/master/boot)
* [config.txt options](http://www.elinux.org/RPiconfig)
+96
View File
@@ -0,0 +1,96 @@
Raspberry Pi 2
##############
- http://raspberrypi.org
Hardware Information
====================
- ARMv7 Architecture
- Broadcom BCM2836 (SoC)
- Includes Quad ARM1176JZF-S CPU @ 900 MHz
- Includes VideoCore IV GPU
- SD Card Storage
- 1 GB RAM
- Video Outputs
- HDMI Video Output
- Composite Video Output
- Ethernet
Setting up the Haiku SD card
============================
The Raspberry Pi SD card generally uses the MBR file system layout
below. Partition 1 is all that is required to boot an OS.
- partition 1 FAT32, bootable flag, type c
- partition 2 BeFS, Haiku filesystem, type eb
Boot Partition
--------------
Required Files
~~~~~~~~~~~~~~
- bootcode.bin : 2nd stage bootloader
- start.elf: The GPU binary firmware image
- config.txt: A configuration file read by the Pi to start u-boot.bin
- u-boot.bin: u-boot loader for the Pi 2
- bcm2836-rpi-2-b.dtb: FDT binary for the Raspberry Pi 2
- haiku_loader_linux.ub: Haiku Loader
- haiku-floppyboot.tgz.ub: Compressed initial ram image with Haiku
kernel
Optional Files
~~~~~~~~~~~~~~
- vlls directory: Additional GPU code, e.g. extra codecs.
- uEnv.txt: u-boot configuration script to automate boot.
Compiling
=========
- Create your ARM work directory
``mkdir generated.arm; cd generated.arm``
- Build an ARM toolchain using
``../configure --build-cross-tools arm ../../buildtools --target-board=rpi2``
- Build our loader using ``jam -q haiku_loader_linux.ub``
- Build our initial ram disk using ``jam -q haiku-floppyboot.tgz.ub``
Booting
=======
1. SOC finds bootcode.bin
2. bootcode.bin runs start.elf
3. start.elf reads config.txt and start u-boot
4. u-boot.bin starts the Haiku loader
5. Haiku loader boots Haiku kernel
config.txt Options
------------------
::
kernel=u-boot.bin
u-boot startup
--------------
These will be condensed and automated long-term via uEnv.txt :-)
- ``fatload mmc 0 ${fdt_addr_r} bcm2836-rpi-2-b.dtb``
- ``fdt addr ${fdt_addr_r}``
- ``fatload mmc 0 ${ramdisk_addr_r} haiku-floppyboot.tgz.ub``
- ``fatload mmc 0 ${kernel_addr_r} haiku_loader_linux.ub``
- ``bootm ${kernel_addr_r} ${ramdisk_addr_r} ${fdt_addr_r}``
Additional Information
======================
- `Latest Raspberry Pi
firmware <http://github.com/raspberrypi/firmware/tree/master/boot>`__
- `config.txt options <http://www.elinux.org/RPiconfig>`__
-64
View File
@@ -1,64 +0,0 @@
* Fix pre-ARMv7 support
Currently the cross-tools are compiled to default to ARMv7, Cortex-A8, and
hardware floating point. This works around the missing atomic support, see
below. This should be done by setting the -mcpu,-march and -mfloat-abi
switches at build time, however, they aren't passed on to haikuporter
during the bootstrap build, leading to the ports failing to find the
gcc atomic ops again.
* Determine how to handle atomic functions on ARM.
GCC inlines are not supported, since the instructionset is ill-equiped for
this on older (pre-ARMv7) architectures. We possibly have to do something
similar to the linux kernel helper functions for this....
On ARMv7 and later, this is not an issue. Not sure about ARMv6, we may get
it going there. ARMv5 definitely needs us to write some code, but is it
worth the trouble?
* Fix multilib support
ARM-targetting versions of gcc are usually built with multilib support, to
allow targetting architectures with or without FPU, and using either ARM
or Thumb instructions. This bascally means a different libgcc and libstdc++
are built for each combination.
The cross-tools can be built with multilib support. However, we do some
tricks to get a separate libgcc and libstdc++ for the kernel (without C++11
threads support, as that would not build in the kernel). Building this lib
is not done in a multilib-aware way, so you get one only for the default
arch/cpu/abi the compiler is targetting. This is good enough, as long as that
arch is the one we want to use for the kernel...
Later on, the bootstrap build of the native gcc compiler will fail, because
it tries to build its multilib library set by linking against the different
versions of libroot (with and without fpu, etc). We only build one libroot,
so this also fails.
* Figure out how to get page flags (modified/accessed) and implement it ;)
use unmapped/read-only mappings to trigger soft faults
for tracking used/modified flags for ARMv5 and ARMv6
* Fix serial port mapping. Currently kernel uses the haiku_loader identity
mapping for it, but this lives in user virtual address space...
(Need to not use identity mapping in haiku_loader but just
map_physical_memory() there too so it can be handed over without issues).
* Seperate ARM architecture/System-On-Chip IP code. Needed very early on
(Interrupt Controller, Timer IP block). Should use FDT for this too.
* Use FDT to remove all the seperate board definitions in the build. Use a
"minimal ARM architecture version" as the compile flag for ARM support.
This to be able to optimize a kernel build for a specific SoC (or family).
* KDL disasm module.
cf.
http://fxr.watson.org/fxr/source/arch/arm/arm/disassem.c?v=NETBSD
* Add KDL hangman to the boot floppy image for more enjoyment during porting....
* Userland...........
* Bootloader TODOs:
- Better handling of memory ranges. Currently no checks are done, and
memory is assumed to be a single contiguous range, and the "input"
ranges for mmu_init are setup, but never considered.
- Allocate the pagetable range using mmu_allocate() instead of identity
mapping it. That way, there's a bit more flexibility in where to place
it both physically and virtually. This will need a minor change on the
kernel side too (in the early pagetable allocator).
-13
View File
@@ -1,13 +0,0 @@
* FDT
http://www.denx.de/wiki/U-Boot/UBootFdtInfo
http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
http://elinux.org/images/4/4e/Glikely-powerpc-porting-guide.pdf
http://ols.fedoraproject.org/OLS/Reprints-2008/likely2-reprint.pdf
http://www.bsdcan.org/2010/schedule/events/171.en.html
http://www.devicetree.org/ (unofficial bindings)
http://www.devicetree.org/Device_Tree_Usage
http://elinux.org/Device_Trees
* OF
http://www.openfirmware.info/Bindings
* Floating Point; VFP
https://wiki.debian.org/ArmHardFloatPort/VfpComparison
-48
View File
@@ -1,48 +0,0 @@
Notes on long double support
============================
The "long double" type is different on each architecture. Depending on the
available hardware and ABI conventions, performance compromises, etc, there
may be many implementations of it. Here is a summary for our convenience.
128-bit IEEE
------------
Platforms: Sparc, ARM64, RISC-V
This is the standard long double type from IEEE754. It has 1 sign bit,
15 exponent bit, and 112 fractional part bits. It is the natural extension
of the 64bit double.
Sparc specifies this type in their ABI but no implementation actually has
the instructions, they instead trigger a trap which would software emulate
them. However, gcc short circuits this by default and calls C library
support functions directly.
64-bit IEEE
-----------
Platforms: ARM
This is the same representation as plain "double". ARM uses this for simplicity.
80-bit
------
Platform: x86, x86\_64, m68k
This intermediate format is used by x86 CPUs internally. It may end up being
faster than plain double there. It consists of a 64bit fractional part, 15
exponent bits, and 1 sign bit. This is convenient because the fractional part
is a relatively easy to handle 64bit number.
m68k uses a similar format, but padded to 96 bits (the extra 16 bits are unused).
double double
-------------
Platforms: PowerPC?
This is also a 128bit type, but the representation is just two 64bit doubles.
The value is the sum of the two halves. This format allows faster emulation
than a "true" 128bit long double, and the precision is almost as good.
+54
View File
@@ -0,0 +1,54 @@
Notes on long double support
============================
The “long double” type is different on each architecture. Depending on
the available hardware and ABI conventions, performance compromises,
etc, there may be many implementations of it. Here is a summary for our
convenience.
128-bit IEEE
------------
Platforms: Sparc, ARM64, RISC-V
This is the standard long double type from IEEE754. It has 1 sign bit,
15 exponent bit, and 112 fractional part bits. It is the natural
extension of the 64bit double.
Sparc specifies this type in their ABI but no implementation actually
has the instructions, they instead trigger a trap which would software
emulate them. However, gcc short circuits this by default and calls C
library support functions directly.
.. _bit-ieee-1:
64-bit IEEE
-----------
Platforms: ARM
This is the same representation as plain “double”. ARM uses this for
simplicity.
80-bit
------
Platform: x86, x86_64, m68k
This intermediate format is used by x86 CPUs internally. It may end up
being faster than plain double there. It consists of a 64bit fractional
part, 15 exponent bits, and 1 sign bit. This is convenient because the
fractional part is a relatively easy to handle 64bit number.
m68k uses a similar format, but padded to 96 bits (the extra 16 bits are
unused).
double double
-------------
Platforms: PowerPC?
This is also a 128bit type, but the representation is just two 64bit
doubles. The value is the sum of the two halves. This format allows
faster emulation than a “true” 128bit long double, and the precision is
almost as good.
-3
View File
@@ -1,3 +0,0 @@
- optimization: remove M68KPagingStructures[*]::UpdateAllPageDirs() and just allocate all the kernel page root entries at boot and be done with it. It's not very big anyway.
- possibly other optimizations in the VM code due to not supporting SMP?
+4
View File
@@ -0,0 +1,4 @@
The Amiga port
==============
* http://wandel.ca/homepage/execdis/
@@ -1 +0,0 @@
http://wandel.ca/homepage/execdis/
+108
View File
@@ -0,0 +1,108 @@
The Atari ST port
=================
Atari ST executables
--------------------
From: DaFi <[email protected]>
The specs for Atari ST executables (was listed as requested on www.wotsit.demon.co.uk/wanted.htm)...
applies for TOS, PRG, TTP, PRX, GTP, APP, ACC, ACX (different suffixes indicate different behavior of the program, i.e. TOS and TTP may not use the GEM GUI, while all the others may; only TTP and GTP can be called with parameters; ACC may be installed as desktop accessories; PRX and ACX mean the programs were disabled.
file structure:
+--------------------+---------------------------------------------------------------------------+
| [2] WORD PRG_magic | magic value 0x601a |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_tsize | size of text segment |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_dsize | size of data segment |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_bsize | size of bss segment |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_ssize | size of symbol table |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRG_res1 | reserved |
+--------------------+---------------------------------------------------------------------------+
| [4] LONG PRGFLAGS | bit vector that defines additional process characteristics, as follows: |
| | |
| | * **Bit 0 PF_FASTLOAD** - if set, only the BSS area is cleared, otherwise,|
| | the program's whole memory is cleared before loading |
| | * **Bit 1 PF_TTRAMLOAD** - if set, the program will be loaded into TT RAM |
| | * **Bit 2 PF_TTRAMMEM** - if set, the program will be allowed to allocate |
| | memory from TT RAM |
| | |
| | Bit 4 AND 5 as a two bit value with the following meanings: |
| | |
| | * 0 PF_PRIVATE - the processes entire memory space is considered private |
| | * 1 PF_GLOBAL - the processes memory will be r/w-allowed for others |
| | * 2 PF_SUPER - the memory will be r/w for itself and any supervisor proc |
| | * 3 PF_READ - the memory will be readable by others |
+--------------------+---------------------------------------------------------------------------+
| [2] WORD ABSFLAG | is NON-ZERO, if the program does not need to be relocated |
| | |
| | is ZERO, if the program needs to be relocated |
| | |
| | note: since some TOS versions handle files with ABSFLAG>0 incorrectly, |
| | this value should be set to ZERO also for programs that need to be |
| | relocated, and the FIXUP_offset should be set to 0. |
+--------------------+---------------------------------------------------------------------------+
From there on... (should be offset 0x1c)
[PRG_tsize] TEXT segment
[PRG_dsize] DATA segment
[PRG_ssize] Symbol table
[4] LONG FIXUP_offset - first LONG that needs to be relocated (offset to beginning of file)
From there on till the end of the file...
FIXUP table, with entries as follows:
[1] BYTE value
with value as follows:
- value=0 end of list
- value=1 advance 254 bytes
- value=2 to value=254 (only even values!) advance this many bytes and relocate the LONG found there.
That's it. You made it through to EOF.
A final note about fixing up (relocating) an executable: (pseudo-code)
The long value FIXUP_offset tells you your start adress. Let's call it "adr". So, now, that
you have adr, read the first byte of the table.
(*) loop
- if it's 0, stop relocating -> you're done!
- if it's 1, add 254 to adr and read the next byte, jump back to the asterisk (*)
- if it's any other even value, add the value to your adr, then relocate the LONG at adr.
(i.e. add the adress of the LONG to its value)
Useful resources
----------------
* http://toshyp.atari.org/en/index.html
* http://www.lysator.liu.se/~celeborn/sync/atari/misc.html
* http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/F30.ZIP
* http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCLIB6.ZIP
* http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCREGS.ZIP
* http://fxr.watson.org/fxr/source/include/asm-m68k/atarihw.h?v=linux-2.4.22
* http://lxr.linux.no/linux+v2.6.27/arch/m68k/atari/config.c#L664
* http://www.atari-forum.com/wiki/index.php/MFP_MK68901
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/ahdi-xxboot/xxboot.ahdi.S
AHDI args
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/wdboot/wdboot.S
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/sdboot/sdboot.S
* http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/fdboot/fdboot.S
@@ -1,57 +0,0 @@
Subject: Atari ST executables
From: DaFi <[email protected]>
The specs for Atari ST executables (was listed as requested on www.wotsit.demon.co.uk/wanted.htm)...
applies for TOS, PRG, TTP, PRX, GTP, APP, ACC, ACX (different suffixes indicate different behavior of the program, i.e. TOS and TTP may not use the GEM GUI, while all the others may; only TTP and GTP can be called with parameters; ACC may be installed as desktop accessories; PRX and ACX mean the programs were disabled.
file structure:
[2] WORD PRG_magic - magic value 0x601a
[4] LONG PRG_tsize - size of text segment
[4] LONG PRG_dsize - size of data segment
[4] LONG PRG_bsize - size of bss segment
[4] LONG PRG_ssize - size of symbol table
[4] LONG PRG_res1 - reserved
[4] LONG PRGFLAGS - bit vector that defines additional process characteristics, as follows:
Bit 0 PF_FASTLOAD - if set, only the BSS area is cleared, otherwise,
the program´s whole memory is cleared before loading
Bit 1 PF_TTRAMLOAD - if set, the program will be loaded into TT RAM
Bit 2 PF_TTRAMMEM - if set, the program will be allowed to allocate
memory from TT RAM
Bit 4 AND 5 as a two bit value with the following meanings:
0 PF_PRIVATE - the processes entire memory space is considered private
1 PF_GLOBAL - the processes memory will be r/w-allowed for others
2 PF_SUPER - the memory will be r/w for itself and any supervisor proc
3 PF_READ - the memory will be readable by others
[2] WORD ABSFLAG - is NON-ZERO, if the program does not need to be relocated
is ZERO, if the program needs to be relocated
note: since some TOS versions handle files with ABSFLAG>0 incorrectly,
this value should be set to ZERO also for programs that need to be
relocated, and the FIXUP_offset should be set to 0.
From there on... (should be offset 0x1c)
[PRG_tsize] TEXT segment
[PRG_dsize] DATA segment
[PRG_ssize] Symbol table
[4] LONG FIXUP_offset - first LONG that needs to be relocated (offset to beginning of file)
From there on till the end of the file...
FIXUP table, with entries as follows:
[1] BYTE value - with value as follows:
value=0 end of list
value=1 advance 254 bytes
value=2 to value=254 (only even values!) advance this many bytes and
relocate the LONG found there
That´s it. You made it through to EOF.
A final note about fixing up (relocating) an executable: (pseudo-code)
The long value FIXUP_offset tells you your start adress. Let´s call it "adr". So, now, that
you have adr, read the first byte of the table.
(*) loop
- if it´s 0, stop relocating -> you´re done!
- if it´s 1, add 254 to adr and read the next byte, jump back to the asterisk (*)
- if it´s any other even value, add the value to your adr, then relocate the LONG at adr.
(i.e. add the adress of the LONG to its value)
dafi
@@ -1,19 +0,0 @@
http://toshyp.atari.org/en/index.html
http://www.lysator.liu.se/~celeborn/sync/atari/misc.html
http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/F30.ZIP
http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCLIB6.ZIP
http://www.lysator.liu.se/~celeborn/sync/atari/ATARI/FALCREGS.ZIP
http://fxr.watson.org/fxr/source/include/asm-m68k/atarihw.h?v=linux-2.4.22
http://lxr.linux.no/linux+v2.6.27/arch/m68k/atari/config.c#L664
http://www.atari-forum.com/wiki/index.php/MFP_MK68901
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/ahdi-xxboot/xxboot.ahdi.S
AHDI args
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/wdboot/wdboot.S
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/sdboot/sdboot.S
http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/arch/atari/stand/xxboot/fdboot/fdboot.S
@@ -0,0 +1,21 @@
The m68k port
#############
The Motorola 68000 is an old CPU and not a reasonable target for Haiku. However, later models
which are equipped with a memory management unit could work (slowly).
There is work in progress to target Atari, Amiga, and NeXT hardware platforms.
Todo list
=========
- optimization: remove M68KPagingStructures[*]::UpdateAllPageDirs() and just allocate all the kernel page root entries at boot and be done with it. It's not very big anyway.
- possibly other optimizations in the VM code due to not supporting SMP?
Target platforms information
============================
.. toctree::
/kernel/arch/m68k/amiga
/kernel/arch/m68k/atari
+19
View File
@@ -0,0 +1,19 @@
Notes on a possible BeBox Haiku port
====================================
Bootloader
----------
The BeBox ROM expects the bootloader to be in PEF format, as was produced by the CodeWarrior
compiler used by Be. However, support for this format in binutils seems incomplete.
references
----------
* http://www.netbsd.org/ports/bebox/
* http://netbsd.2816.n7.nabble.com/BeBox-memory-configuration-td278318.html
QEMU target
-----------
http://qemu-project.org/Features/BeBox
-7
View File
@@ -1,7 +0,0 @@
Notes on a possible BeBox Haiku port -*- org -*-
* references
http://www.netbsd.org/ports/bebox/
http://netbsd.2816.n7.nabble.com/BeBox-memory-configuration-td278318.html
** QEMU target
http://qemu-project.org/Features/BeBox
+17
View File
@@ -0,0 +1,17 @@
The Apple Macintosh port
======================================
The current target is "new world" machines, which have a more complete and reliable implementation
of Open Firmware.
* http://www.debian.org/releases/stable/powerpc/ch05s01.html.en
* http://www.kernelthread.com/mac/osx/arch_boot.html
* http://playground.sun.com/1275/mejohnson/
* http://homepages.gold.ac.uk/suzanne/startup.html
* http://www.netbsd.org/ports/macppc/SystemDisk-tutorial/
* http://www.netneurotic.net/mac/openfirmware.html
* http://www.netbsd.org/ports/macppc/faq.html
* http://mail-index.netbsd.org/port-macppc/1999/03/21/0001.html
* http://mail-index.netbsd.org/port-macppc/1999/06/25/0006.html
* http://ps-2.kev009.com/solinno.co.uk/7043-140/files/docs/ {OF,PReP}
* http://www.openfirmware.org/1275/bindings/chrp/
-11
View File
@@ -1,11 +0,0 @@
http://www.debian.org/releases/stable/powerpc/ch05s01.html.en
http://www.kernelthread.com/mac/osx/arch_boot.html
http://playground.sun.com/1275/mejohnson/
http://homepages.gold.ac.uk/suzanne/startup.html
http://www.netbsd.org/ports/macppc/SystemDisk-tutorial/
http://www.netneurotic.net/mac/openfirmware.html
http://www.netbsd.org/ports/macppc/faq.html
http://mail-index.netbsd.org/port-macppc/1999/03/21/0001.html
http://mail-index.netbsd.org/port-macppc/1999/06/25/0006.html
http://ps-2.kev009.com/solinno.co.uk/7043-140/files/docs/ {OF,PReP}
http://www.openfirmware.org/1275/bindings/chrp/
+24
View File
@@ -0,0 +1,24 @@
The PowerPC port
================
PowerPC was the first non-x86architecture for which a port of Haiku was attempted. The initial
target was the (then recently released) Mac Mini, but of course the BeBox was in everyone's mind
as a possible target for this port.
This port went as far as starting the kernel, but then difficulties in implementing the Mac Mini
PCI bus driver stopped it.
Later on, some work as done on adding support for the Sam460ex development board, after a donation
of one to one of the Haiku developers.
Recently, the lack of easily available and affordable PowerPC hardware has reduced interest in this
port.
Platform specific details
-------------------------
.. toctree::
/kernel/arch/ppc/bebox
/kernel/arch/ppc/mac
/kernel/arch/ppc/sam460ex
+119
View File
@@ -0,0 +1,119 @@
The Sam460ex Haiku port
=======================
Complications for this port comes from the fact that the CPU used was designed for embedded devices,
and has a much simpler MMU than the one on desktop machines. As a result, completely different
memory management code needs to be written.
U-Boot commands
---------------
no-fdt no-initrd
****************
seems the U-Boot input buffer is quite limited, can't paste much more on single line in minicom
setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub; bootm 0x4000000
with FDT and tgz as initrd
**************************
setenv ipaddr 192.168.4.100
tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub
tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub
tftpboot 0xc000000 192.168.4.2:sam460ex.dtb
fdt addr 0xc000000
fdt header
bootm 0x4000000 0x8000000 0xc000000 plop
for environment
***************
setenv booth1 'setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub'
setenv booth2 'tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub'
setenv booth3 'tftpboot 0xc000000 192.168.4.2:sam460ex.dtb'
setenv booth4 'bootm 0x4000000 0x8000000 0xc000000 plop'
setenv booth 'run booth1; run booth2; run booth3; run booth4'
saveenv
run booth
TODOs
-----
* U-Boot API?
* move Partenope hack to proper official U-Boot API?
* reserved regs?
BoardSetup +=:?
TARGET_BOOT_CCFLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
TARGET_BOOT_C++FLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
* kdebug/disasm/ppc http://code.google.com/p/ppcd/
Other ports
-----------
* `AROS port <https://www.gitorious.org/aros/aros/commits/sam460>`_
* `Linux port <http://kernel.org/doc/ols/2003/ols2003-pages-340-350.pdf>`_
* `NetBSD <https://wiki.netbsd.org/users/rkujawa/sam4x0/>`_
PowerPC information
-------------------
Classic
*******
* http://class.ee.iastate.edu/cpre211/labs/quickrefPPC.html
* http://www.ibm.com/developerworks/library/l-ppc/
* http://www.csd.uwo.ca/~mburrel/stuff/ppc-asm.html
Book-E
******
* http://www.linux-kvm.org/page/PowerPC_Book_E_MMU
* http://wiki.freebsd.org/powerpc/BookE
* http://en.wikipedia.org/wiki/Memory_management_unit#PowerPC
ePAPR
*****
* https://www.power.org/wp-content/uploads/2012/06/Power_ePAPR_APPROVED_v1.1.pdf
* PPC440: http://elinux.org/Book_E_and_PPC_440
amcc 4x0
********
* http://c0ff33.net/drop/PPC440_UM2013.pdf
* http://www.embeddeddeveloper.com/assets/processors/amcc/datasheets/PP460EX_DS2063.pdf
Freescale 440
*************
This version has a different mmu!!
* http://www.freescale.com/files/32bit/doc/white_paper/POWRPCARCPRMRM.pdf
FDT
---
* http://www.denx.de/wiki/U-Boot/UBootFdtInfo
* http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
* (see also arm docs)
Sam440 dts
**********
* http://lxr.linux.no/linux+v3.4/arch/powerpc/boot/dts/sam440ep.dts
* Sam460ex dts: identical to amcc,Canyonlands !?
* http://www.denx.de/wiki/view/DULG/Appendix#Section_13.1.
OpenFirmware framebuffer
************************
(not really usable from U-Boot (yet?))
* http://www.feedface.com/howto/forth.html
* http://mail-index.netbsd.org/port-macppc/2004/12/13/0046.html
* http://lists.freebsd.org/pipermail/svn-src-user/2012-January/004806.html
* http://www.openfirmware.info/Bindings
@@ -1,76 +0,0 @@
Notes on Sam460ex Haiku port -*- org -*-
* U-Boot commands
** no-fdt no-initrd
# (seems the U-Boot input buffer is quite limited, can't paste much more on single line in minicom)
setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub; bootm 0x4000000
** with FDT and tgz as initrd
setenv ipaddr 192.168.4.100
tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub
tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub
tftpboot 0xc000000 192.168.4.2:sam460ex.dtb
fdt addr 0xc000000
fdt header
bootm 0x4000000 0x8000000 0xc000000 plop
** for environment:
setenv booth1 'setenv ipaddr 192.168.4.100; tftpboot 0x4000000 192.168.4.2:haiku_loader_linux.ub'
setenv booth2 'tftpboot 0x8000000 192.168.4.2:haiku_initrd.ub'
setenv booth3 'tftpboot 0xc000000 192.168.4.2:sam460ex.dtb'
setenv booth4 'bootm 0x4000000 0x8000000 0xc000000 plop'
setenv booth 'run booth1; run booth2; run booth3; run booth4'
saveenv
run booth
* TODO U-Boot API?
** TODO move Partenope hack to proper official U-Boot API?
** TODO reserved regs?
BoardSetup +=:?
TARGET_BOOT_CCFLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
TARGET_BOOT_C++FLAGS += -ffixed-r2 -ffixed-r14 -ffixed-r29 ;
* Other ports
** AROS port
https://www.gitorious.org/aros/aros/commits/sam460
** Linux port
http://kernel.org/doc/ols/2003/ols2003-pages-340-350.pdf
** NetBSD
https://wiki.netbsd.org/users/rkujawa/sam4x0/
* PPC
** Classic
http://class.ee.iastate.edu/cpre211/labs/quickrefPPC.html
http://www.ibm.com/developerworks/library/l-ppc/
http://www.csd.uwo.ca/~mburrel/stuff/ppc-asm.html
** Book-E
http://www.linux-kvm.org/page/PowerPC_Book_E_MMU
http://wiki.freebsd.org/powerpc/BookE
http://en.wikipedia.org/wiki/Memory_management_unit#PowerPC
** ePAPR
https://www.power.org/wp-content/uploads/2012/06/Power_ePAPR_APPROVED_v1.1.pdf
** 440
http://elinux.org/Book_E_and_PPC_440
*** amcc 4x0
http://c0ff33.net/drop/PPC440_UM2013.pdf
http://www.embeddeddeveloper.com/assets/processors/amcc/datasheets/PP460EX_DS2063.pdf
*** Freescale 440 (different mmu!!)
http://www.freescale.com/files/32bit/doc/white_paper/POWRPCARCPRMRM.pdf
* FDT
http://www.denx.de/wiki/U-Boot/UBootFdtInfo
http://wiki.freebsd.org/FlattenedDeviceTree#Supporting_library_.28libfdt.29
(see also arm docs)
** Sam440 dts
http://lxr.linux.no/linux+v3.4/arch/powerpc/boot/dts/sam440ep.dts
** Sam460ex dts: identical to amcc,Canyonlands !?
http://www.denx.de/wiki/view/DULG/Appendix#Section_13.1.
* OF framebuffer
(not really usable from U-Boot (yet?))
http://www.feedface.com/howto/forth.html
http://mail-index.netbsd.org/port-macppc/2004/12/13/0046.html
http://lists.freebsd.org/pipermail/svn-src-user/2012-January/004806.html
http://www.openfirmware.info/Bindings
* TODO kdebug/disasm/ppc
http://code.google.com/p/ppcd/
-32
View File
@@ -1,32 +0,0 @@
The SPARC architecture has 32 integer registers, divided as follows:
- global registers (g0-g7)
- input (i0-i7)
- local (l0-l7)
- output (o0-o7)
Parameter passing and return is done using the output registers, which are
generally considered scratch registers and can be corrupted by the callee. The
caller must take care of preserving them.
The input and local registers are callee-saved, but we have hardware assistance
in the form of a register window. There is an instruction to shift the registers
so that:
- o registers become i registers
- local and output registers are replaced with fresh sets, for use by the
current function
- global registers are not affected
Note that as a side-effect, o7 is moved to i7, this is convenient because these
are usually the stack and frame pointers, respectively. So basically this sets
the frame pointer for free.
Simple enough functions may end up using just the o registers, in that case
nothing special is necessary, of course.
When shifting the register window, the extra registers come from the register
stack in the CPU. This is not infinite, however, most implementations of SPARC
will only have 8 windows available. When the internal stack is full, an overflow
trap is raised, and the handler must free up old windows by storing them on the
stack, likewise, when the internal stack is empty, an underflow trap must fill
it back from the stack-saved data.
@@ -1,37 +0,0 @@
The SPARC CPU is not designed to gracefully handle misaligned accesses.
You can access a single byte at any address, but 16-bit access only at even
addresses, 32bit access at multiple of 4 addresses, etc.
For example, on x86, such accesses are not a problem, it is allowed and handled
directly by the instructions doing the access. So there is no performance cost.
On SPARC, however, such accesses will cause a SIGBUS. This means a trap handler
has to catch the misaligned access and do it in software, byte by byte, then
give back control to the application. This is, of course, very slow, so we
should avoid it when possible.
Fortunately, gcc knows about this, and will normally do the right thing:
- For usual variables and structures, it will make sure to lay them out so that
they are aligned. It relies on stack alignment, as well as malloc returning
sufficiently aligned memory (as required by the C standard).
- On packed structure, gcc knows the data is misaligned, and will automatically
use the appropriate way to access it (most likely, byte-by-byte).
This leaves us with two undesirable cases:
- Pointer arithmetics and casting. When computing addresses manually, it's
possible to generate a misaligned address and cast it to a type with a wider
alignment requirement. In this case, gcc may access the pointer using a
multi byte instruction and cause a SIGBUS. Solution: make sure the struct
is aligned, or declare it as packed so unaligned access are used instead.
- Access to hardware: it is a common pattern to declare a struct as packed,
and map it to hardware registers. If the alignment isn't known, gcc will use
byte by byte access. It seems volatile would cause gcc to use the proper way
to access the struct, assuming that a volatile value is necessarily
aligned as it should.
In the end, we just need to be careful about pointer math resulting in unalined
access. -Wcast-align helps with that, but it also raises a lot of false positives
(where the alignment is preserved even when casting to other types). So we
enable it only as a warning for now. We will need to ceck the sigbus handler to
identify places where we do a lot of misaligned accesses that trigger it, and
rework the code as needed. But in general, except for these cases, we're fine.
-116
View File
@@ -1,116 +0,0 @@
Notes on the Ultrasparc MMUs
============================
First, a word of warning: the MMU was different in SPARCv8 (32bit)
implementations, and it was changed again on newer CPUs.
The Ultrasparc-II we are supporting for now is documented in the Ultrasparc
user manual. There were some minor changes in the Ultrasparc-III to accomodate
larger physical addresses. This was then standardized as JPS1, and Fujitsu
also implemented it.
Later on, the design was changed again, for example Ultrasparc T2 (UA2005
architecture) uses a different data structure format to enlarge, again, the
physical and virtual address tags.
For now te implementation is focused on Ultrasparc-II because that's what I
have at hand, later on we will need support for the more recent systems.
Ultrasparc-II MMU
=================
There are actually two separate units for the instruction and data address
spaces, known as I-MMU and D-MMU. They each implement a TLB (translation
lookaside buffer) for the recently accessed pages.
This is pretty much all there is to the MMU hardware. No hardware page table
walk is provided. However, there is some support for implementing a TSB
(Translation Storage Buffer) in the form of providing a way to compute an
address into that buffer where the data for a missing page could be.
It is up to software to manage the TSB (globally or per-process) and in general
keep track of the mappings. This means we are relatively free to manage things
however we want, as long as eventually we can feed the iTLB and dTLB with the
relevant data from the MMU trap handler.
To make sure we can handle the fault without recursing, we need to pin a few
items in place:
In the TLB:
- TLB miss handler code
- TSB and any linked data that the TLB miss handler may need
- asynchronous trap handlers and data
In the TSB:
- TSB-miss handling code
- Interrupt handlers code and data
So, from a given virtual address (assuming we are using only 8K pages and a
512 entry TSB to keep things simple):
VA63-44 are unused and must be a sign extension of bit 43
VA43-22 are the 'tag' used to match a TSB entry with a virtual address
VA21-13 are the offset in the TSB at which to find a candidate entry
VA12-0 are the offset in the 8K page, and used to form PA12-0 for the access
Inside the TLBs, VA63-13 is stored, so there can be multiple entries matching
the same tag active at the same time, even when there is only one in the TSB.
The entries are rotated using a simple LRU scheme, unless they are locked of
course. Be careful to not fill a TLB with only locked entries! Also one must
take care of not inserting a new mapping for a given VA without first removing
any possible previous one (no need to worry about this when handling a TLB
miss however, as in that case we obviously know that there was no previous
entry).
Entries also have a "context". This could for example be mapped to the process
ID, allowing to easily clear all entries related to a specific context.
TSB entries format
==================
Each entry is composed of two 64bit values: "Tag" and "Data". The data uses the
same format as the TLB entries, however the tag is different.
They are as follow:
Tag
---
Bit 63: 'G' indicating a global entry, the context should be ignored.
Bits 60-48: context ID (13 bits)
Bits 41-0: VA63-22 as the 'tag' to identify this entry
Data
----
Bit 63: 'V' indicating a valid entry, if it's 0 the entry is unused.
Bits 62-61: size: 8K, 64K, 512K, 4MB
Bit 60: NFO, indicating No Fault Only
Bit 59: Invert Endianness of accesses to this page
Bits 58-50: reserved for use by software
Bits 49-41: reserved for diagnostics
Bits 40-13: Physical Address<40-13>
Bits 12-7: reserved for use by software
Bit 6: Lock in TLB
Bit 5: Cachable physical
Bit 4: Cachable virtual
Bit 3: Access has side effects (HW is mapped here, or DMA shared RAM)
Bit 2: Privileged
Bit 1: Writable
Bit 0: Global
TLB internal tag
----------------
Bits 63-13: VA<63-13>
Bits 12-0: context ID
Conveniently, a 512 entries TSB fits exactly in a 8K page, so it can be locked
in the TLB with a single entry there. However, it may be a wise idea to instead
map 64K (or more) of RAM locked as a single entry for all the things that needs
to be accessed by the TLB miss trap handler, so we minimize the use of TLB
entries.
Likewise, it may be useful to use 64K pages instead of 8K whenever possible.
The hardware provides some support for mixing the two sizes but it makes things
a bit more complex. Let's start out with simpler things.
@@ -1,97 +0,0 @@
Openboot is Sun's implementation of Open Firmware. So we should be able to share
a lot of code with the PowerPC port. There are some differences however.
Executable format
=================
PowerPC uses COFF. Sparc uses a.out, which is a lot simpler. According to the
spec, some fields should be zeroed out, but they say implementation may chose
to allow other values, so a standard a.out file works as well.
It used to be possible to generate one with objcopy, but support was removed,
so we now use elf2aout (imported from FreeBSD).
The file is first loaded at 4000, then relocated to its load address (we use
202000 and executed there)
Openfirmware prompt
===================
To get the prompt on display, use STOP+A at boot until you get the "ok" prompt.
On some machines, if no keyboard is detected, the ROM will assume it is set up
in headless mode, and will expect a BREAK+A on the serial port.
STOP+N resets all variables to default values (in case you messed up input or
output, for example).
Useful commands
===============
Disable autoboot to get to the openboot prompt and stop there
-------------------------------------------------------------
setenv auto-boot? false
Configuring for keyboard/framebuffer io
---------------------------------------
setenv screen-#columns 160
setenv screen-#rows 49
setenv output-device screen:r1920x1080x60
setenv input-device keyboard
Configuring openboot for serial port
------------------------------------
setenv ttya-mode 38400,8,n,1,-
setenv output-device ttya
setenv input-device ttya
reset
Boot from network
-----------------
static ip:
This currently works best, because rarp does not let the called binary know the
IP address. We need the IP address if we want to mount the root filesystem using
remote_disk server.
boot net:192.168.1.2,somefile,192.168.1.89
The first IP is the server from which to download (using TFTP), the second is
the client IP to use. Once the bootloader starts, it will detect that it is
booted from network and look for a the remote_disk_server on the same machine.
rarp:
This needs a reverse ARP server (easy to setup on any Linux system). You need
to list the MAC address of the SPARC machine in /etc/ethers on the server. The
machine will get its IP, and will use TFTP to the server which replied, to get
the boot file from there.
boot net:,somefile
(net is an alias to the network card and also sets the load address: /pci@1f,4000/network@1,1)
dhcp:
This needs a DHCP/BOOTP server configured to send the info about where to find
the file to load and boot.
boot net:dhcp
Debugging
---------
202000 dis (disassemble starting at 202000 until next return instruction)
4000 1000 dump (dump 1000 bytes from address 4000)
.registers (show global registers)
.locals (show local/windowed registers)
%pc dis (disassemble code being exectuted)
ctrace (backtrace)
+335
View File
@@ -0,0 +1,335 @@
The SPARC port
##############
The SPARC port targets various machines from Sun product lineup. The initial effort is on the
Ultra 60 and Ultra 5, with plans to latter add the Sun T5120 and its newer CPU. This may change
depending on hardware donations and developer interest.
Support for 32-bit versions of SPARC is currently not planned.
SPARC ABI
=========
The SPARC architecture has 32 integer registers, divided as follows:
- global registers (g0-g7)
- input (i0-i7)
- local (l0-l7)
- output (o0-o7)
Parameter passing and return is done using the output registers, which are
generally considered scratch registers and can be corrupted by the callee. The
caller must take care of preserving them.
The input and local registers are callee-saved, but we have hardware assistance
in the form of a register window. There is an instruction to shift the registers
so that:
- o registers become i registers
- local and output registers are replaced with fresh sets, for use by the
current function
- global registers are not affected
Note that as a side-effect, o7 is moved to i7, this is convenient because these
are usually the stack and frame pointers, respectively. So basically this sets
the frame pointer for free.
Simple enough functions may end up using just the o registers, in that case
nothing special is necessary, of course.
When shifting the register window, the extra registers come from the register
stack in the CPU. This is not infinite, however, most implementations of SPARC
will only have 8 windows available. When the internal stack is full, an overflow
trap is raised, and the handler must free up old windows by storing them on the
stack, likewise, when the internal stack is empty, an underflow trap must fill
it back from the stack-saved data.
Misaligned memory access
========================
The SPARC CPU is not designed to gracefully handle misaligned accesses.
You can access a single byte at any address, but 16-bit access only at even
addresses, 32bit access at multiple of 4 addresses, etc.
For example, on x86, such accesses are not a problem, it is allowed and handled
directly by the instructions doing the access. So there is no performance cost.
On SPARC, however, such accesses will cause a SIGBUS. This means a trap handler
has to catch the misaligned access and do it in software, byte by byte, then
give back control to the application. This is, of course, very slow, so we
should avoid it when possible.
Fortunately, gcc knows about this, and will normally do the right thing:
- For usual variables and structures, it will make sure to lay them out so that
they are aligned. It relies on stack alignment, as well as malloc returning
sufficiently aligned memory (as required by the C standard).
- On packed structure, gcc knows the data is misaligned, and will automatically
use the appropriate way to access it (most likely, byte-by-byte).
This leaves us with two undesirable cases:
- Pointer arithmetics and casting. When computing addresses manually, it's
possible to generate a misaligned address and cast it to a type with a wider
alignment requirement. In this case, gcc may access the pointer using a
multi byte instruction and cause a SIGBUS. Solution: make sure the struct
is aligned, or declare it as packed so unaligned access are used instead.
- Access to hardware: it is a common pattern to declare a struct as packed,
and map it to hardware registers. If the alignment isn't known, gcc will use
byte by byte access. It seems volatile would cause gcc to use the proper way
to access the struct, assuming that a volatile value is necessarily
aligned as it should.
In the end, we just need to be careful about pointer math resulting in unalined
access. -Wcast-align helps with that, but it also raises a lot of false positives
(where the alignment is preserved even when casting to other types). So we
enable it only as a warning for now. We will need to ceck the sigbus handler to
identify places where we do a lot of misaligned accesses that trigger it, and
rework the code as needed. But in general, except for these cases, we're fine.
The Ultrasparc MMUs
============================
First, a word of warning: the MMU was different in SPARCv8 (32bit)
implementations, and it was changed again on newer CPUs.
The Ultrasparc-II we are supporting for now is documented in the Ultrasparc
user manual. There were some minor changes in the Ultrasparc-III to accomodate
larger physical addresses. This was then standardized as JPS1, and Fujitsu
also implemented it.
Later on, the design was changed again, for example Ultrasparc T2 (UA2005
architecture) uses a different data structure format to enlarge, again, the
physical and virtual address tags.
For now te implementation is focused on Ultrasparc-II because that's what I
have at hand, later on we will need support for the more recent systems.
Ultrasparc-II MMU
-----------------
There are actually two separate units for the instruction and data address
spaces, known as I-MMU and D-MMU. They each implement a TLB (translation
lookaside buffer) for the recently accessed pages.
This is pretty much all there is to the MMU hardware. No hardware page table
walk is provided. However, there is some support for implementing a TSB
(Translation Storage Buffer) in the form of providing a way to compute an
address into that buffer where the data for a missing page could be.
It is up to software to manage the TSB (globally or per-process) and in general
keep track of the mappings. This means we are relatively free to manage things
however we want, as long as eventually we can feed the iTLB and dTLB with the
relevant data from the MMU trap handler.
To make sure we can handle the fault without recursing, we need to pin a few
items in place:
In the TLB:
- TLB miss handler code
- TSB and any linked data that the TLB miss handler may need
- asynchronous trap handlers and data
In the TSB:
- TSB-miss handling code
- Interrupt handlers code and data
So, from a given virtual address (assuming we are using only 8K pages and a
512 entry TSB to keep things simple):
VA63-44 are unused and must be a sign extension of bit 43
VA43-22 are the 'tag' used to match a TSB entry with a virtual address
VA21-13 are the offset in the TSB at which to find a candidate entry
VA12-0 are the offset in the 8K page, and used to form PA12-0 for the access
Inside the TLBs, VA63-13 is stored, so there can be multiple entries matching
the same tag active at the same time, even when there is only one in the TSB.
The entries are rotated using a simple LRU scheme, unless they are locked of
course. Be careful to not fill a TLB with only locked entries! Also one must
take care of not inserting a new mapping for a given VA without first removing
any possible previous one (no need to worry about this when handling a TLB
miss however, as in that case we obviously know that there was no previous
entry).
Entries also have a "context". This could for example be mapped to the process
ID, allowing to easily clear all entries related to a specific context.
TSB entries format
------------------
Each entry is composed of two 64bit values: "Tag" and "Data". The data uses the
same format as the TLB entries, however the tag is different.
They are as follow:
Tag
***
Bit 63: 'G' indicating a global entry, the context should be ignored.
Bits 60-48: context ID (13 bits)
Bits 41-0: VA63-22 as the 'tag' to identify this entry
Data
****
Bit 63: 'V' indicating a valid entry, if it's 0 the entry is unused.
Bits 62-61: size: 8K, 64K, 512K, 4MB
Bit 60: NFO, indicating No Fault Only
Bit 59: Invert Endianness of accesses to this page
Bits 58-50: reserved for use by software
Bits 49-41: reserved for diagnostics
Bits 40-13: Physical Address<40-13>
Bits 12-7: reserved for use by software
Bit 6: Lock in TLB
Bit 5: Cachable physical
Bit 4: Cachable virtual
Bit 3: Access has side effects (HW is mapped here, or DMA shared RAM)
Bit 2: Privileged
Bit 1: Writable
Bit 0: Global
TLB internal tag
****************
Bits 63-13: VA<63-13>
Bits 12-0: context ID
Conveniently, a 512 entries TSB fits exactly in a 8K page, so it can be locked
in the TLB with a single entry there. However, it may be a wise idea to instead
map 64K (or more) of RAM locked as a single entry for all the things that needs
to be accessed by the TLB miss trap handler, so we minimize the use of TLB
entries.
Likewise, it may be useful to use 64K pages instead of 8K whenever possible.
The hardware provides some support for mixing the two sizes but it makes things
a bit more complex. Let's start out with simpler things.
Software floating-point support
===============================
The SPARC instruction set specifies instruction for handling long double
values, however, no hardware implementation actually provides them. They
generate a trap, which is expected to be handled by the softfloat library.
Since traps are slow, and gcc knows better, it will never generate those
instructions. Instead it directly calls into the C library, to functions
specified in the ABI and used to do long double math using softfloats.
The support code for this is, in our case, compiled into both the kernel and
libroot. It lives in src/system/libroot/os/arch/sparc/softfloat.c (and other
support files). This code was extracted from FreeBSD, rather than the glibc,
because that made it much easier to get it building in the kernel.
Openboot bootloader
===================
Openboot is Sun's implementation of Open Firmware. So we should be able to share
a lot of code with the PowerPC port. There are some differences however.
Executable format
-----------------
PowerPC uses COFF. Sparc uses a.out, which is a lot simpler. According to the
spec, some fields should be zeroed out, but they say implementation may chose
to allow other values, so a standard a.out file works as well.
It used to be possible to generate one with objcopy, but support was removed,
so we now use elf2aout (imported from FreeBSD).
The file is first loaded at 4000, then relocated to its load address (we use
202000 and executed there)
Openfirmware prompt
-------------------
To get the prompt on display, use STOP+A at boot until you get the "ok" prompt.
On some machines, if no keyboard is detected, the ROM will assume it is set up
in headless mode, and will expect a BREAK+A on the serial port.
STOP+N resets all variables to default values (in case you messed up input or
output, for example).
Useful commands
---------------
Disable autoboot to get to the openboot prompt and stop there
.. code-block:: text
setenv auto-boot? false
Configuring for keyboard/framebuffer io
.. code-block:: text
setenv screen-#columns 160
setenv screen-#rows 49
setenv output-device screen:r1920x1080x60
setenv input-device keyboard
Configuring openboot for serial port
.. code-block:: text
setenv ttya-mode 38400,8,n,1,-
setenv output-device ttya
setenv input-device ttya
reset
Boot from network
-----------------
static ip
*********
This currently works best, because rarp does not let the called binary know the
IP address. We need the IP address if we want to mount the root filesystem using
remote_disk server.
.. code-block:: text
boot net:192.168.1.2,somefile,192.168.1.89
The first IP is the server from which to download (using TFTP), the second is
the client IP to use. Once the bootloader starts, it will detect that it is
booted from network and look for a the remote_disk_server on the same machine.
rarp
****
This needs a reverse ARP server (easy to setup on any Linux system). You need
to list the MAC address of the SPARC machine in /etc/ethers on the server. The
machine will get its IP, and will use TFTP to the server which replied, to get
the boot file from there.
.. code-block:: text
boot net:,somefile
(net is an alias to the network card and also sets the load address: /pci@1f,4000/network@1,1)
dhcp
****
This needs a DHCP/BOOTP server configured to send the info about where to find
the file to load and boot.
.. code-block:: text
boot net:dhcp
Debugging
---------
.. code-block:: text
202000 dis (disassemble starting at 202000 until next return instruction)
4000 1000 dump (dump 1000 bytes from address 4000)
.registers (show global registers)
.locals (show local/windowed registers)
%pc dis (disassemble code being exectuted)
ctrace (backtrace)
@@ -1,12 +0,0 @@
The SPARC instruction set specifies instruction for handling long double
values, however, no hardware implementation actually provides them. They
generate a trap, which is expected to be handled by the softfloat library.
Since traps are slow, and gcc knows better, it will never generate those
instructions. Instead it directly calls into the C library, to functions
specified in the ABI and used to do long double math using softfloats.
The support code for this is, in our case, compiled into both the kernel and
libroot. It lives in src/system/libroot/os/arch/sparc/softfloat.c (and other
support files). This code was extracted from FreeBSD, rather than the glibc,
because that made it much easier to get it building in the kernel.
@@ -1,93 +0,0 @@
# Bootloader debugging with GEF
When Haiku's early boot process is experiencing unknown crashes or faults, it can be extremely
difficult to troubleshoot (especially when serial, video, or other i/o devices are non-functional)
It **is** possible to step through the boot of any architecture of Haiku in a debugger if the system
boots and the issue can be reproduced in qemu.
> This works for any architecture and is _extremely_ helpful to trouble early platforms. Linux or Mac OS
> are requirements. You need a full POSIX environment.
## Building Haiku
On most non-x86 platforms, you will need a "kernel" (haiku_loader) and an "initrd" (haiku_floppyboot).
For arm/arm64: ```jam -q @minimum-mmc```
## Launching Haiku in QEMU
In the example below, we will prepare Haiku arm in QEMU for debugging.
```
qemu-system-arm -M raspi2 -kernel haiku_loader.u-boot -initrd haiku-floppyboot.tgz.u-boot -serial stdio -m 2G -dtb rpi2.dtb -s -S
```
**Key Flags:**
* **-s**
* Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234.
* **-S**
* Do not start CPU at startup (you must type 'c' in the monitor).
These simple flags will make qemu listen for a debugger connection on localhost:1234 and have the VM not start until you tell it to.
> In the example above, we are Emulating a Raspberry Pi 2, and using our Raspberry Pi 2 dtb. If you don't have a dtb for the machine
> you're emulating, you can dump qemu's internal dtb by adding ```-M dumpdtb=myboard.dtb``` to the end of your qemu command.
## Attaching GEF
[GEF](https://github.com/hugsy/gef) is an enhanced debugger which works extremely well for debugging code running in virtual machines.
It piggy-backs on gdb and offers a lot of valueable insight at a glance without requiring to know every gdb command.
Once GEF is installed, we can step through the process to attach gdb to qemu.
### Open gdb with our symbols.
First we run gdb pointed at our boot loader. We use the native ELF binary as that seems to give gdb/gef the most accurate knowledge
of our symbols. (the haiku_loader.u-boot is wrapped by u-boot's mkimage, your milage may vary based on platform)
```gdb objects/haiku/arm/release/system/boot/u-boot/boot_loader_u-boot```
### Set the architecture
This may not be required, but re-enforces to gef/gdb that we're working on arm.
```set architecture arm```
### Connect to QEMU
Now we tell gdb/gef about out running (but paused) QEMU instance.
```gef-remote -q localhost:1234```
A successful connection should occur.
### Step into debugging
Before you begin execution, it's handy to set a *breakpoint*. A *breakpoint* tells gdb/gef where it should pause execution to begin
the debugging process. All of our bootloaders start in a ```start_gen``` function, so this is a good place to start.
```breakpoint start_gen```
Now that a breakpoint is defined, lets run the virtual machine.
In gef, type ```continue```.
If everything is working as expected, you should now be "paused" at the ```start_gen``` function (hopefully showing the C/C++ code).
Now, you have a few commands to leverage:
* **step**
* Take a single step forward and execute the code listed.
* Does **not** step "into" functions, just over them getting the return from the code.
* Alias: s
* **stepi**
* step forward "into" the next code.
* If you're on a function it will enter the function and show the code executed.
* **break**
* add additional "breakpoints" where you can step through the code execution.
* **continue**
* Resume execution.
* If you have no additional breakpoints the code will "go do what it's supposed to"
* Alias: c
@@ -0,0 +1,137 @@
Bootloader debugging with GEF
=============================
When Haikus early boot process is experiencing unknown crashes or
faults, it can be extremely difficult to troubleshoot (especially when
serial, video, or other i/o devices are non-functional)
It **is** possible to step through the boot of any architecture of Haiku
in a debugger if the system boots and the issue can be reproduced in
qemu.
This works for any architecture and is *extremely* helpful to trouble
early platforms. Linux or Mac OS are requirements. You need a full
POSIX environment.
Building Haiku
--------------
On most non-x86 platforms, you will need a “kernel” (haiku_loader) and
an “initrd” (haiku_floppyboot).
For arm/arm64: ``jam -q @minimum-mmc``
Launching Haiku in QEMU
-----------------------
In the example below, we will prepare Haiku arm in QEMU for debugging.
::
qemu-system-arm -M raspi2 -kernel haiku_loader.u-boot -initrd haiku-floppyboot.tgz.u-boot -serial stdio -m 2G -dtb rpi2.dtb -s -S
**Key Flags:**
- **-s**
- Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port
1234.
- **-S**
- Do not start CPU at startup (you must type c in the monitor).
These simple flags will make qemu listen for a debugger connection on
localhost:1234 and have the VM not start until you tell it to.
In the example above, we are Emulating a Raspberry Pi 2, and using
our Raspberry Pi 2 dtb. If you dont have a dtb for the machine
youre emulating, you can dump qemus internal dtb by adding
``-M dumpdtb=myboard.dtb`` to the end of your qemu command.
Attaching GEF
-------------
`GEF <https://github.com/hugsy/gef>`__ is an enhanced debugger which
works extremely well for debugging code running in virtual machines. It
piggy-backs on gdb and offers a lot of valueable insight at a glance
without requiring to know every gdb command.
Once GEF is installed, we can step through the process to attach gdb to
qemu.
Open gdb with our symbols.
~~~~~~~~~~~~~~~~~~~~~~~~~~
First we run gdb pointed at our boot loader. We use the native ELF
binary as that seems to give gdb/gef the most accurate knowledge of our
symbols. (the haiku_loader.u-boot is wrapped by u-boots mkimage, your
milage may vary based on platform)
``gdb objects/haiku/arm/release/system/boot/u-boot/boot_loader_u-boot``
Set the architecture
~~~~~~~~~~~~~~~~~~~~
This may not be required, but re-enforces to gef/gdb that were working
on arm.
``set architecture arm``
Connect to QEMU
~~~~~~~~~~~~~~~
Now we tell gdb/gef about out running (but paused) QEMU instance.
``gef-remote -q localhost:1234``
A successful connection should occur.
Step into debugging
~~~~~~~~~~~~~~~~~~~
Before you begin execution, its handy to set a *breakpoint*. A
*breakpoint* tells gdb/gef where it should pause execution to begin the
debugging process. All of our bootloaders start in a ``start_gen``
function, so this is a good place to start.
``breakpoint start_gen``
Now that a breakpoint is defined, lets run the virtual machine.
In gef, type ``continue``.
If everything is working as expected, you should now be “paused” at the
``start_gen`` function (hopefully showing the C/C++ code).
Now, you have a few commands to leverage:
- **step**
- Take a single step forward and execute the code listed.
- Does **not** step “into” functions, just over them getting the
return from the code.
- Alias: s
- **stepi**
- step forward “into” the next code.
- If youre on a function it will enter the function and show the
code executed.
- **break**
- add additional “breakpoints” where you can step through the code
execution.
- **continue**
- Resume execution.
- If you have no additional breakpoints the code will “go do what
its supposed to”
- Alias: c
- **next**
- Resume execution until it reaches the next line of code.
- Useful for example to run until a loop is completed, and stop at the first line after that loop.
@@ -1,165 +0,0 @@
<body bgcolor=white>
<h1>Haiku boot process specification</h1>
<h6>
Creation Date: November 23, 2002<br>
Version: 2.0 (Jan 22, 2021)<br>
Status: documenting the current state of things<br>
Author(s): Axel D&ouml;rfler, Adrien Destugues
</h6>
<h2>Overview</h2>
<p>Unlike other systems, Haiku comes with its own user-friendly bootloader. The main task of
the bootloader is to load and start the kernel. We don't have a concept of an initramfs as
Linux does, instead our bootloader is able to find the kernel and modules in a BFS partition,
and even extract them from packages as needed. It also provides an early boot menu that can
be used to change settings, boot older versions of Haiku that were snapshotted by the package
system, and write boot logs to USB mass storage.</p>
<h2>Booting from BIOS</h2>
<p>
Haiku BIOS boot loader process is split into 3 different stages. Since the second
stage is bound tightly to both other stages (which are independent from each other),
it is referred to as stage 1.5, whereas the other stages are referred to as stage 1
and 2. This architecture is used because the BIOS booting process only loads a very
small piece of code from disk for booting, insufficient for the needs outlined above.</p>
<p>The following will explain all stages in detail.</p>
<h3>Stage 1</h3>
<p>
The first stage is responsible for loading the real boot loader from a BFS disk. It
will be loaded by the Master Boot Record (MBR) and executed in the x86 real mode.
It is only used if the system will be booted directly from a BFS partition, it won't
be used at all if it is booted from a floppy disk or CD-ROM (in this case, stage
1.5 is in charge immediately).
</p>
<p>
It resides in the first 1024 bytes of a BFS disk which usually refers to the
first two sectors of the partition in question. Since the BFS superblock is located
at byte offset 512, and about 170 bytes large, this section is already reserved,
and thus cannot be used by the loader itself.<br>
The MBR only loads the first sector of a partition into memory, so it has to load
the superblock (and the rest of its implementation) by itself.
</p>
<p>
The loader must be able to load the real boot loader from a certain path, and
execute it. In BeOS this boot loader would be in "/boot/beos/system/zbeos",
in Haiku this is haiku_loader.bios_ia32 found in the haiku_loader package.<br>
Theoretically, it is enough to load the first few blocks from the loader, and
let the next stage then load the whole thing (which it has to do anyway if it
has been written on a floppy). This would be one possible optimization
if the 850 bytes of space are filled too early, but would require that "zbeos"
is written in one sequential block (which should be always the case anyway).
</p>
<h3>haiku_loader.bios_ia32</h3>
<p>
Contains both the stage 1.5 boot loader, and the compressed stage 2 loader.
It's not an ELF executable file; i.e. it can be directly written to a floppy
disk which would cause the BIOS to load the first 512 bytes of that file and
execute it.
</p>
<p>
Therefore, it will start with the stage 1.5 boot loader which will be loaded
either by the BIOS when it directly resides on the disk (for example when
loaded from a floppy disk), or the stage 1 boot loader, although this one
could have a different entry point than the BIOS.
</p>
<h3>Stage 1.5</h3>
<p>
Will have to load the rest of haiku_loader into memory (if not already done by the
stage 1 loader in case it has been loaded from a BFS disk), set up the global
descriptor table, switch to x86 protected mode, uncompress stage 2, and execute it.
</p>
<p>
This part is very similar to the stage 1 boot loader from NewOS.
</p>
<h3>Stage 2</h3>
<p>
This is the most complex part of the boot loader. In short, it has to load
any modules and devices the kernel needs to access the boot device, set up
the system, load the kernel, and execute it.
</p>
<p>
The kernel, and the modules and drivers needed are loaded from the boot
disk - therefore the loader has to be able to access BFS disks. It also
has to be able to load and parse the settings of these drivers (and the
kernel) from the boot disk, some of them are already important for the
boot loader itself (like "don't call the BIOS"). Since this stage is already
executed in protected mode, it has to use the virtual-86 mode to call the
BIOS and access any disk.
</p>
<p>
Before loading those files from the boot disk, it should look for additional
files located on a specific disk location after the "zbeos" file (on floppy disk
or CD-ROM). This way, it could access disks that cannot be accessed by the
BIOS itself.
</p>
<p>
Setting up the system for the kernel also means initalizing PCI devices needed
during the boot process before the kernel is up. It must be able to do so since
the BIOS might not have set up those devices correctly or at all.
</p>
<p>
It also must calculate a check sum for the boot device which the kernel can then
use to identify the boot volume and partition with - there is no other reliable
way to map BIOS disk IDs to the /dev/disk/... tree the system itself is using.
</p>
<p>
After having loaded and relocated the kernel, it executes it by passing a special
structure which tells the kernel things like the boot device check sum, which
modules are already loaded and where they are.
</p>
<p>
The stage 2 boot loader also includes user interaction. If the user presses a
special key during the boot process (like the space key, or some others as well),
a menu will be presented where the user can select the boot device (if several,
the loader has to scan for options), safe mode options, VESA mode, etc.
</p>
<p>
This menu may also come up if an error occured during the execution of the stage
2 loader.
</p>
<h2>Open Firmware</h2>
<p>On Open Firmware based systems, there is no need for a stage 1.5 because the firmware
does not give us as many constraints. Instead, the stage 2 is loaded directly by the firmware.
This requires converting the haiku_loader executable to the appropriate executable format
(a.out on sparc, pef on powerpc). The conversion is done using custom tools because binutils
does not support these formats anymore.</p>
<p>There is no notion of real and protected mode on non-x86 architectures, and the bootloader
is able to easily call Open Firmware methods to perform most tasks (disk access, network booting,
setting up the framebuffer) in a largely hardware-independent way.</p>
<h2>U-Boot</h2>
<p>U-Boot is able to load the stage2 loader directly from an ELF file. However, it does not
provide any other features. It is not possible for the bootloader to call into U-Boot APIs
for disk access, displaying messages on screen etc (while possible in theory, these features
are often disabled in U-Boot). This means haiku_loader would need to parse the FDT (describing
the available hardware) and bundle its own drivers for using the hardware. This approach is
not easy to set up, and it is recommended to instead use the UEFI support in U-Boot where
possible.</p>
<h2>EFI</h2>
<p>On EFI systems, there is no need for a stage1 loader as there is for BIOS. Instead, our stage2
loader (haiku_loader) can be executed directly from the EFI firmware.</p>
<p>The EFI firmware only knows how to run executables in the PE format
(as used by Windows) because Microsoft was involved in specifying it.
On x86_64, we can use binutils to output a PE file directly. But on other platforms, this is not
supported by binutils. So, what we do is generate a "fake" PE header and wrap our elf file inside
it. The bootloader then parses the embedded ELF header and relocates itself, so the other parts
of the code can be run.</p>
<p>After this initial loading phase, the process is very similar to the Open Firmware one. EFI
provides us with all the tools we need to do disk access and both text mode and framebuffer
output.</p>
</body>
@@ -0,0 +1,174 @@
Haiku boot process specification
================================
Creation Date: November 23, 2002
Version: 2.0 (Jan 22, 2021)
Status: documenting the current state of things
Author(s): Axel Dörfler, Adrien Destugues
Overview
--------
Unlike other systems, Haiku comes with its own user-friendly bootloader.
The main task of the bootloader is to load and start the kernel. We
don't have a concept of an initramfs as Linux does, instead our
bootloader is able to find the kernel and modules in a BFS partition,
and even extract them from packages as needed. It also provides an early
boot menu that can be used to change settings, boot older versions of
Haiku that were snapshotted by the package system, and write boot logs
to USB mass storage.
Booting from BIOS
-----------------
Haiku BIOS boot loader process is split into 3 different stages. Since
the second stage is bound tightly to both other stages (which are
independent from each other), it is referred to as stage 1.5, whereas
the other stages are referred to as stage 1 and 2. This architecture is
used because the BIOS booting process only loads a very small piece of
code from disk for booting, insufficient for the needs outlined above.
The following will explain all stages in detail.
Stage 1
~~~~~~~
The first stage is responsible for loading the real boot loader from a
BFS disk. It will be loaded by the Master Boot Record (MBR) and executed
in the x86 real mode. It is only used if the system will be booted
directly from a BFS partition, it won't be used at all if it is booted
from a floppy disk or CD-ROM (in this case, stage 1.5 is in charge
immediately).
| It resides in the first 1024 bytes of a BFS disk which usually refers
to the first two sectors of the partition in question. Since the BFS
superblock is located at byte offset 512, and about 170 bytes large,
this section is already reserved, and thus cannot be used by the
loader itself.
| The MBR only loads the first sector of a partition into memory, so it
has to load the superblock (and the rest of its implementation) by
itself.
| The loader must be able to load the real boot loader from a certain
path, and execute it. In BeOS this boot loader would be in
"/boot/beos/system/zbeos", in Haiku this is haiku_loader.bios_ia32
found in the haiku_loader package.
| Theoretically, it is enough to load the first few blocks from the
loader, and let the next stage then load the whole thing (which it has
to do anyway if it has been written on a floppy). This would be one
possible optimization if the 850 bytes of space are filled too early,
but would require that "zbeos" is written in one sequential block
(which should be always the case anyway).
haiku_loader.bios_ia32
~~~~~~~~~~~~~~~~~~~~~~
Contains both the stage 1.5 boot loader, and the compressed stage 2
loader. It's not an ELF executable file; i.e. it can be directly written
to a floppy disk which would cause the BIOS to load the first 512 bytes
of that file and execute it.
Therefore, it will start with the stage 1.5 boot loader which will be
loaded either by the BIOS when it directly resides on the disk (for
example when loaded from a floppy disk), or the stage 1 boot loader,
although this one could have a different entry point than the BIOS.
Stage 1.5
~~~~~~~~~
Will have to load the rest of haiku_loader into memory (if not already
done by the stage 1 loader in case it has been loaded from a BFS disk),
set up the global descriptor table, switch to x86 protected mode,
uncompress stage 2, and execute it.
This part is very similar to the stage 1 boot loader from NewOS.
Stage 2
~~~~~~~
This is the most complex part of the boot loader. In short, it has to
load any modules and devices the kernel needs to access the boot device,
set up the system, load the kernel, and execute it.
The kernel, and the modules and drivers needed are loaded from the boot
disk - therefore the loader has to be able to access BFS disks. It also
has to be able to load and parse the settings of these drivers (and the
kernel) from the boot disk, some of them are already important for the
boot loader itself (like "don't call the BIOS"). Since this stage is
already executed in protected mode, it has to use the virtual-86 mode to
call the BIOS and access any disk.
Before loading those files from the boot disk, it should look for
additional files located on a specific disk location after the "zbeos"
file (on floppy disk or CD-ROM). This way, it could access disks that
cannot be accessed by the BIOS itself.
Setting up the system for the kernel also means initalizing PCI devices
needed during the boot process before the kernel is up. It must be able
to do so since the BIOS might not have set up those devices correctly or
at all.
It also must calculate a check sum for the boot device which the kernel
can then use to identify the boot volume and partition with - there is
no other reliable way to map BIOS disk IDs to the /dev/disk/... tree the
system itself is using.
After having loaded and relocated the kernel, it executes it by passing
a special structure which tells the kernel things like the boot device
check sum, which modules are already loaded and where they are.
The stage 2 boot loader also includes user interaction. If the user
presses a special key during the boot process (like the space key, or
some others as well), a menu will be presented where the user can select
the boot device (if several, the loader has to scan for options), safe
mode options, VESA mode, etc.
This menu may also come up if an error occured during the execution of
the stage 2 loader.
Open Firmware
-------------
On Open Firmware based systems, there is no need for a stage 1.5 because
the firmware does not give us as many constraints. Instead, the stage 2
is loaded directly by the firmware. This requires converting the
haiku_loader executable to the appropriate executable format (a.out on
sparc, pef on powerpc). The conversion is done using custom tools
because binutils does not support these formats anymore.
There is no notion of real and protected mode on non-x86 architectures,
and the bootloader is able to easily call Open Firmware methods to
perform most tasks (disk access, network booting, setting up the
framebuffer) in a largely hardware-independent way.
U-Boot
------
U-Boot is able to load the stage2 loader directly from an ELF file.
However, it does not provide any other features. It is not possible for
the bootloader to call into U-Boot APIs for disk access, displaying
messages on screen etc (while possible in theory, these features are
often disabled in U-Boot). This means haiku_loader would need to parse
the FDT (describing the available hardware) and bundle its own drivers
for using the hardware. This approach is not easy to set up, and it is
recommended to instead use the UEFI support in U-Boot where possible.
EFI
---
On EFI systems, there is no need for a stage1 loader as there is for
BIOS. Instead, our stage2 loader (haiku_loader) can be executed directly
from the EFI firmware.
The EFI firmware only knows how to run executables in the PE format (as
used by Windows) because Microsoft was involved in specifying it. On
x86_64, we can use binutils to output a PE file directly. But on other
platforms, this is not supported by binutils. So, what we do is generate
a "fake" PE header and wrap our elf file inside it. The bootloader then
parses the embedded ELF header and relocates itself, so the other parts
of the code can be run.
After this initial loading phase, the process is very similar to the
Open Firmware one. EFI provides us with all the tools we need to do disk
access and both text mode and framebuffer output.
@@ -1,315 +0,0 @@
<html>
<body bgcolor=white>
<h1>Introduction to Haiku's Device Driver Architecture</h1>
<p>This document tries to give you a short introduction into the new device
manager, and how to write drivers for it. Haiku still supports the legacy
device driver architecture introduced with BeOS.</p>
<p>The new device driver architecture of Haiku is still a moving target,
although most of its details are already specificed.</p>
<h2>1. The Basics</h2>
<p>The device manager functionality builds upon <i>device_node</i> objects.
Every driver in the system publishes one or more of such nodes, building a
tree of device nodes. This tree is in theory a dynamic representation of the
current hardware devices in the system, but in practice will also contain
implementation specific details; since every node comes with an API specific
to that node, you'll find device nodes that only come with a number of support
functions for a certain class of drivers.</p>
<p>Structurally, a <i>device_node</i> is a set of a module, attributes,
and resources, as well as a parent and children. At a minimum, a node must
have a module, all other components are optional.</p>
TODO: picture of the device node tree
<p>When the system starts, there is only a root node registered. Only primary
hardware busses register with the root node, such as PCI, and ISA on x86.
Since the PCI bus is an intelligent bus, it knows what hardware is installed,
and registers a child node for each device on the bus.</p>
<p>Every driver can also publish a device in <i>/dev</i> for communication with
userland applications. All drivers and devices are kernel modules.</p>
<h2>2. Exploring the Device Tree</h2>
<p>So how does it all work? When building the initial device tree, the system only
explores a minimum of device drivers only, resulting in a tree that basically
only shows the hardware found in the computer.</p>
<p>Now, if the system requires disk access, it will scan the device file system
for a driver that provides such functionality, in this case, it will look for
drivers under "/dev/disk/". The device manager has a set of built-in rules for
how to translate a device path into a device node, and vice versa: every node
representing a device of an intelligent bus (such as PCI) will also contain
device type information following the PCI definitions. In this case, the "disk"
sub-path will translate into the <i>PCI_mass_storage</i> type, and hence, the
device manager will then completely explore all device nodes of that type.</p>
<p>It will also use that path information to only ask drivers that actually
are in a matching module directory. In the above example of a disk driver, this
would be either in "busses/scsi", "busses/ide", "drivers/disk", ...</p>
<p>For untyped or generic busses, it will use the context information gained
from the devfs query directly, and will search for drivers in that sub directory
only. The only exception to this rule are the devfs directories "disk", "ports",
and "bus", which will also allow to search matching drivers in "busses". While
this is relatively limited, it is a good way to cut down the number of drivers
to be loaded.</p>
<h2>3. Writing a Driver</h2>
<p>The device manager assumes the following API from a driver module:</p>
<ul>
<li><b>supports_device()</b><br>
Determines wether or not the driver supports a given parent device node,
that is the hardware device it represents (if any), and the API the node
exports.</li>
<li><b>register_device()</b><br>
The driver should register its device node here. The parent driver is
always initialized at this point. When registering the node, the driver
can also attach certain I/O resources (like I/O ports, or memory ranges)
to the node -- the device manager will make sure that only one node can
claim these resources.</li>
<li><b>init_driver()</b><br>
Any initialization necessary to get the driver going. For most drivers,
this will be reduced to the creation of a private data structure that is
going to be used for all of the following functions.</li>
<li><b>uninit_driver()</b><br>
Uninitializes resources acquired by <b>init_driver()</b>.</li>
<li><b>register_child_devices()</b><br>
If the driver wants to register any child device nodes or to publish
any devices, it should do so here. This function is called only during
the initial registration process of the device node.</li>
<li><b>rescan_child_devices()</b><br>
Is called whenever a manual rescan is triggered.</li>
<li><b>device_removed()</b></br>
Is called when the device node is about to be unregistered when its
device is gone, for example when a USB device is unplugged.</li>
<li><b>suspend()</b><br>
Enters different sleep modes.</li>
<li><b>resume()</b><br>
Resumes a device from a previous sleep mode.</li>
</ul>
<p>To ensure that a module exports this API, it <b>must</b> end its module name
with "driver_v1" to denote the version of the API it supports. Note that
<b>suspend()</b> and <b>resume()</b> are currently never called, as Haiku has
no power management implemented yet.</p>
<p>If your driver can give the device it is attached to a nice name that can be
presented to the user, it should add the <b>B_DEVICE_PRETTY_NAME</b> attribute
to the device node.
<p>The <b>B_DEVICE_UNIQUE_ID</b> should be used in case the device has a unique
ID that can be used to identify it, and also differentiate it from other devices
of the same model and vendor. This information will be added to the file system
attributes of all devices published by your driver, so that user applications
can identify, say, a USB printer no matter what USB slot it is attached to, and
assign it additional data, like paper configuration, or recognize it as the
default printer.</p>
<p>If your driver implements an API that is used by a support or bus module, you
will usually use the <b>B_DEVICE_FIXED_CHILD</b> attribute to specify exactly
which child device node you will be talking to. If you support several child
nodes, you may want to have a closer look at the section explaining
<a href="#bus_driver">how to write a bus driver</a>.</p>
<p>In addition to the child nodes a driver registers itself, a driver can either
have dynamic children or fixed children, never both. Also, fixed children are
registered before <b>register_child_devices()</b> is called, while dynamic
children are registered afterwards.</p>
<h2>4. Publishing a Device</h2>
To publish a device entry in the device file system under <i>/dev</i>, all your
driver has to do is to call the
<pre>
publish_device(device_node *node, const char *path,
const char *deviceModuleName);
</pre>
function the device manager module exports. The <i>path</i> is the path
component that follows "/dev", for example "net/ipro1000/0". The
<i>deviceModuleName</i> is the module exporting the device functionality.
It should end with "device_v1" to show the device manager which protocol it
supports. If the device node your device belongs to is removed, your device
is removed automatically with it. On the other hand, you are allowed to
unpublish the device at any point using the <b>unpublish_device()</b> function
the device manager delivers for this.</p>
<p>A device module must export the following API:</p>
<ul>
<li><b>init_device()</b><br>
This is called when the open() is called on this device for the first
time. You may want to create a private data structure that is passed on
to all subsequent calls of the <b>open()</b> function that your device
exports.</li>
<li><b>uninit_device()</b><br>
Is called when the last file descriptor to the device had been closed.</li>
<li><b>device_removed()</b><br>
When the device node your device belongs to is going to be removed,
you're notified about this in this function.</li>
<li><b>open()</b><br>
Called whenever your device is opened.</li>
<li><b>close()</b><br>
</li>
<li><b>free()</b><br>
Free the private data structure you allocated in <b>open()</b>.</li>
<li><b>read()</b><br>
</li>
<li><b>write()</b><br>
</li>
<li><b>io()</b><br>
This is a replacement for the <b>read()</b>, and <b>write()</b> calls,
and allows, among other things, for asynchronous I/O. This functionality
has not yet been implemented, though (see below).</li>
<li><b>control()</b><br>
</li>
<li><b>select()</b><br>
</li>
<li><b>deselect()</b><br>
</li>
</ul>
<h2>5. <a name="bus_driver">Writing a Bus Driver</a></h2>
<p>A bus driver is a driver that represents a bus where one or more arbitrary
devices can be attached to.</p>
<p>There are two basic types of busses: intelligent busses like PCI or USB that
know a lot about the devices attached to it, like a generic device type, as
well as device and vendor ID information, and simple untyped/generic busses that
either have not all the information (like device type) or don't even know what
and if any devices are attached. The device manager has been written in such a
way that device exploration makes use of additional information the bus can
provide in order to find a responsible device driver faster, and with less
overhead.</p>
<h4>5.1. Writing an Intelligent Bus Driver</h4>
<p>If your bus knows what type of device is attached to, and also has vendor and
device ID information about that device, it is considered to be an intelligent
bus. The bus driver is supposed to have one parent node representing the bus,
and to create a child node for each device attached to the bus.</p>
<p>The additional information you have about the devices are attached to the
device node in the following attributes:</p>
<ul>
<li><b>B_DEVICE_VENDOR_ID</b><br>
The vendor ID - this ID has only to be valid in the namespace of your
bus.</li>
<li><b>B_DEVICE_ID</b><br>
The device ID.</li>
<li><b>B_DEVICE_TYPE</b><br>
The device type as defined by the PCI class base information.</li>
<li><b>B_DEVICE_SUB_TYPE</b><br>
The device sub type as defined by the PCI sub class information.</li>
<li><b>B_DEVICE_INTERFACE</b><br>
The device interface type as defined by the PCI class API information.</li>
</ul>
<p>You can use the <b>B_DEVICE_FLAGS</b> attribute to define how the device
manager finds the children of the devices you exported. For this kind of bus
drivers, you will usually only want to specify <b>B_FIND_CHILD_ON_DEMAND</b>
here, which causes the driver only to be searched when the system asks for it.
</p>
<h4>5.2. Writing a Simple Bus Driver</h4>
<p>A bus can be simple in a number of ways:</p>
<ol>
<li>It may not know how many or if any devices are attached to it</li>
<li>It cannot retrieve any type information about the devices it has, but
knows all devices that are attached to it</li>
</ol>
<p>An example of the latter would be the Zorro bus of the Amiga - it only has
information about the vendor and device ID, but no type information. It should
be implemented like an intelligent bus, though, with the type information simply
omitted.</p>
<p>Therefore, this section is about the former case, that is, a simple bus like
the ISA bus. Since it doesn't know anything about its children, it does not
publish any child nodes, instead, it will just specify the
B_FIND_MULTIPLE_CHILDREN and B_FIND_CHILD_ON_DEMAND flags for its device node.
Since there is no additional information about this bus, the device manager
will assume a simple bus, and will try to find drivers on demand only.</p>
<h2>The generic bus</h2>
Some devices are not tied to a specific bus. This is the case for all drivers
that do not relate to a physical device: /dev/null, /dev/zero, /dev/random,
etc.
A "generic" bus has been added, and these drivers can attach to it.
<h2>6. Open Issues</h2>
While most of the new device manager is fledged out, there are some areas that
could use improvements or are problematic under certain requirements. Also, some
parts just haven't been written yet.
<h4>6.1. generic/simple busses</h4>
<h4>6.2. Unpublishing</h4>
<h4>6.4. Versioning</h4>
<p>The way the device manager works, it makes versioning of modules (which are
supposed to be one of the strong points of the module system) much harder or
even impossible. While the device manager could introduce a new API and could
translate between a "driver_v1", and a "driver_v2" API on the fly, it's not
yet possible for a PCI sub module to do the same thing.</p>
<p><b>Proposed Solution:</b> Add attribute <b>B_DEVICE_ALTERNATE_VERSION</b>
that specifies alternate versions of the module API this device node supports.
We would then need a <b>request_version()</b> or <b>set_version()</b> function
(to be called from <b>supports_device()</b>) that allows to specify the version
of the parent node this device node wants to talk to.</p>
<h4>6.5. Unregistering Nodes</h4>
<h4>6.6. Support for generic drivers is missing</h4>
<p>This should probably be done by simply adding a simple bus driver named
"generic" that generic drivers need to ask for.</p>
<h4>6.7. Mappings, And Other Optimizations</h4>
<p>Due to the way the device tree is built, the device manager could remember
which driver served a given device node. That way, it wouldn't need to search
for a driver anymore, but could just pick it up. Practically, the device manager
should cache the type (and/or vendor/device) information of a node, and assign
one or more drivers (via module name) to this information. It should also
remember negative outcome, that is if there is no driver supporting the
hardware.</p>
<p>This way, only the first boot would require an actual search for drivers, as
subsequent boots would reuse the type-driver assignments. If a new driver is
installed, the cached assignments would need to be updated immediately. If a
driver has been installed outside of the running system, the device manager
might want to create a hash per module directory to see if anything changed to
flush the cache. Alternatively or additionally, the boot loader could have a
menu causing the cache to be ignored.</p>
<p>It would be nice to find a way for generic and simple busses to reduce the
amount of searching necessary for them. One way would be to remember which
driver supports which bus - but this information is currently only accessible
derived from what the driver does, and is therefore not reliable or complete.
A separately exported information would be necessary for this.</p>
<p>Also, when looking for a generic or simple bus driver, actual directories
could be omitted; currently, driver search is always recursive, as that's how
the module mechanism is working. Eventually, we might want to extend the
open_module_list_etc() call a bit more to accomplish that.</p>
</body>
</html>
@@ -0,0 +1,325 @@
Device Driver Architecture
==================================================
This document tries to give you a short introduction into the new device
manager, and how to write drivers for it. Haiku still supports the
legacy device driver architecture introduced with BeOS.
The new device driver architecture of Haiku is still a moving target,
although most of its details are already specificed.
1. The Basics
-------------
The device manager functionality builds upon *device_node* objects.
Every driver in the system publishes one or more of such nodes, building
a tree of device nodes. This tree is in theory a dynamic representation
of the current hardware devices in the system, but in practice will also
contain implementation specific details; since every node comes with an
API specific to that node, you'll find device nodes that only come with
a number of support functions for a certain class of drivers.
Structurally, a *device_node* is a set of a module, attributes, and
resources, as well as a parent and children. At a minimum, a node must
have a module, all other components are optional.
TODO: picture of the device node tree
When the system starts, there is only a root node registered. Only
primary hardware busses register with the root node, such as PCI, and
ISA on x86. Since the PCI bus is an intelligent bus, it knows what
hardware is installed, and registers a child node for each device on the
bus.
Every driver can also publish a device in */dev* for communication with
userland applications. All drivers and devices are kernel modules.
2. Exploring the Device Tree
----------------------------
So how does it all work? When building the initial device tree, the
system only explores a minimum of device drivers only, resulting in a
tree that basically only shows the hardware found in the computer.
Now, if the system requires disk access, it will scan the device file
system for a driver that provides such functionality, in this case, it
will look for drivers under "/dev/disk/". The device manager has a set
of built-in rules for how to translate a device path into a device node,
and vice versa: every node representing a device of an intelligent bus
(such as PCI) will also contain device type information following the
PCI definitions. In this case, the "disk" sub-path will translate into
the *PCI_mass_storage* type, and hence, the device manager will then
completely explore all device nodes of that type.
It will also use that path information to only ask drivers that actually
are in a matching module directory. In the above example of a disk
driver, this would be either in "busses/scsi", "busses/ide",
"drivers/disk", ...
For untyped or generic busses, it will use the context information
gained from the devfs query directly, and will search for drivers in
that sub directory only. The only exception to this rule are the devfs
directories "disk", "ports", and "bus", which will also allow to search
matching drivers in "busses". While this is relatively limited, it is a
good way to cut down the number of drivers to be loaded.
3. Writing a Driver
-------------------
The device manager assumes the following API from a driver module:
- **supports_device()**
Determines wether or not the driver supports a given parent device
node, that is the hardware device it represents (if any), and the API
the node exports.
- **register_device()**
The driver should register its device node here. The parent driver is
always initialized at this point. When registering the node, the
driver can also attach certain I/O resources (like I/O ports, or
memory ranges) to the node -- the device manager will make sure that
only one node can claim these resources.
- **init_driver()**
Any initialization necessary to get the driver going. For most
drivers, this will be reduced to the creation of a private data
structure that is going to be used for all of the following
functions.
- **uninit_driver()**
Uninitializes resources acquired by **init_driver()**.
- **register_child_devices()**
If the driver wants to register any child device nodes or to publish
any devices, it should do so here. This function is called only
during the initial registration process of the device node.
- **rescan_child_devices()**
Is called whenever a manual rescan is triggered.
- **device_removed()** Is called when the device node is about to be
unregistered when its device is gone, for example when a USB device
is unplugged.
- **suspend()**
Enters different sleep modes.
- **resume()**
Resumes a device from a previous sleep mode.
To ensure that a module exports this API, it **must** end its module
name with "driver_v1" to denote the version of the API it supports. Note
that **suspend()** and **resume()** are currently never called, as Haiku
has no power management implemented yet.
If your driver can give the device it is attached to a nice name that
can be presented to the user, it should add the **B_DEVICE_PRETTY_NAME**
attribute to the device node.
The **B_DEVICE_UNIQUE_ID** should be used in case the device has a
unique ID that can be used to identify it, and also differentiate it
from other devices of the same model and vendor. This information will
be added to the file system attributes of all devices published by your
driver, so that user applications can identify, say, a USB printer no
matter what USB slot it is attached to, and assign it additional data,
like paper configuration, or recognize it as the default printer.
If your driver implements an API that is used by a support or bus
module, you will usually use the **B_DEVICE_FIXED_CHILD** attribute to
specify exactly which child device node you will be talking to. If you
support several child nodes, you may want to have a closer look at the
section explaining `how to write a bus driver <#bus_driver>`__.
In addition to the child nodes a driver registers itself, a driver can
either have dynamic children or fixed children, never both. Also, fixed
children are registered before **register_child_devices()** is called,
while dynamic children are registered afterwards.
4. Publishing a Device
----------------------
To publish a device entry in the device file system under */dev*, all
your driver has to do is to call the
::
publish_device(device_node *node, const char *path,
const char *deviceModuleName);
function the device manager module exports. The *path* is the path
component that follows "/dev", for example "net/ipro1000/0". The
*deviceModuleName* is the module exporting the device functionality. It
should end with "device_v1" to show the device manager which protocol it
supports. If the device node your device belongs to is removed, your
device is removed automatically with it. On the other hand, you are
allowed to unpublish the device at any point using the
**unpublish_device()** function the device manager delivers for this.
A device module must export the following API:
- **init_device()**
This is called when the open() is called on this device for the first
time. You may want to create a private data structure that is passed
on to all subsequent calls of the **open()** function that your
device exports.
- **uninit_device()**
Is called when the last file descriptor to the device had been
closed.
- **device_removed()**
When the device node your device belongs to is going to be removed,
you're notified about this in this function.
- **open()**
Called whenever your device is opened.
- **close()**
- **free()**
Free the private data structure you allocated in **open()**.
- **read()**
- **write()**
- **io()**
This is a replacement for the **read()**, and **write()** calls, and
allows, among other things, for asynchronous I/O. This functionality
has not yet been implemented, though (see below).
- **control()**
- **select()**
- **deselect()**
5. Writing a Bus Driver
-----------------------
A bus driver is a driver that represents a bus where one or more
arbitrary devices can be attached to.
There are two basic types of busses: intelligent busses like PCI or USB
that know a lot about the devices attached to it, like a generic device
type, as well as device and vendor ID information, and simple
untyped/generic busses that either have not all the information (like
device type) or don't even know what and if any devices are attached.
The device manager has been written in such a way that device
exploration makes use of additional information the bus can provide in
order to find a responsible device driver faster, and with less
overhead.
5.1. Writing an Intelligent Bus Driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If your bus knows what type of device is attached to, and also has
vendor and device ID information about that device, it is considered to
be an intelligent bus. The bus driver is supposed to have one parent
node representing the bus, and to create a child node for each device
attached to the bus.
The additional information you have about the devices are attached to
the device node in the following attributes:
- **B_DEVICE_VENDOR_ID**
The vendor ID - this ID has only to be valid in the namespace of your
bus.
- **B_DEVICE_ID**
The device ID.
- **B_DEVICE_TYPE**
The device type as defined by the PCI class base information.
- **B_DEVICE_SUB_TYPE**
The device sub type as defined by the PCI sub class information.
- **B_DEVICE_INTERFACE**
The device interface type as defined by the PCI class API
information.
You can use the **B_DEVICE_FLAGS** attribute to define how the device
manager finds the children of the devices you exported. For this kind of
bus drivers, you will usually only want to specify
**B_FIND_CHILD_ON_DEMAND** here, which causes the driver only to be
searched when the system asks for it.
5.2. Writing a Simple Bus Driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A bus can be simple in a number of ways:
#. It may not know how many or if any devices are attached to it
#. It cannot retrieve any type information about the devices it has, but
knows all devices that are attached to it
An example of the latter would be the Zorro bus of the Amiga - it only
has information about the vendor and device ID, but no type information.
It should be implemented like an intelligent bus, though, with the type
information simply omitted.
Therefore, this section is about the former case, that is, a simple bus
like the ISA bus. Since it doesn't know anything about its children, it
does not publish any child nodes, instead, it will just specify the
B_FIND_MULTIPLE_CHILDREN and B_FIND_CHILD_ON_DEMAND flags for its device
node. Since there is no additional information about this bus, the
device manager will assume a simple bus, and will try to find drivers on
demand only.
The generic bus
---------------
Some devices are not tied to a specific bus. This is the case for all
drivers that do not relate to a physical device: /dev/null, /dev/zero,
/dev/random, etc. A "generic" bus has been added, and these drivers can
attach to it.
6. Open Issues
--------------
While most of the new device manager is fledged out, there are some
areas that could use improvements or are problematic under certain
requirements. Also, some parts just haven't been written yet.
6.1. generic/simple busses
^^^^^^^^^^^^^^^^^^^^^^^^^^
6.2. Unpublishing
^^^^^^^^^^^^^^^^^
6.4. Versioning
^^^^^^^^^^^^^^^
The way the device manager works, it makes versioning of modules (which
are supposed to be one of the strong points of the module system) much
harder or even impossible. While the device manager could introduce a
new API and could translate between a "driver_v1", and a "driver_v2" API
on the fly, it's not yet possible for a PCI sub module to do the same
thing.
**Proposed Solution:** Add attribute **B_DEVICE_ALTERNATE_VERSION** that
specifies alternate versions of the module API this device node
supports. We would then need a **request_version()** or
**set_version()** function (to be called from **supports_device()**)
that allows to specify the version of the parent node this device node
wants to talk to.
6.5. Unregistering Nodes
^^^^^^^^^^^^^^^^^^^^^^^^
6.6. Support for generic drivers is missing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This should probably be done by simply adding a simple bus driver named
"generic" that generic drivers need to ask for.
6.7. Mappings, And Other Optimizations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Due to the way the device tree is built, the device manager could
remember which driver served a given device node. That way, it wouldn't
need to search for a driver anymore, but could just pick it up.
Practically, the device manager should cache the type (and/or
vendor/device) information of a node, and assign one or more drivers
(via module name) to this information. It should also remember negative
outcome, that is if there is no driver supporting the hardware.
This way, only the first boot would require an actual search for
drivers, as subsequent boots would reuse the type-driver assignments. If
a new driver is installed, the cached assignments would need to be
updated immediately. If a driver has been installed outside of the
running system, the device manager might want to create a hash per
module directory to see if anything changed to flush the cache.
Alternatively or additionally, the boot loader could have a menu causing
the cache to be ignored.
It would be nice to find a way for generic and simple busses to reduce
the amount of searching necessary for them. One way would be to remember
which driver supports which bus - but this information is currently only
accessible derived from what the driver does, and is therefore not
reliable or complete. A separately exported information would be
necessary for this.
Also, when looking for a generic or simple bus driver, actual
directories could be omitted; currently, driver search is always
recursive, as that's how the module mechanism is working. Eventually, we
might want to extend the open_module_list_etc() call a bit more to
accomplish that.
-262
View File
@@ -1,262 +0,0 @@
<html>
<body bgcolor=white>
<h1>Node Monitoring</h1>
<h6>
Creation Date: January 16, 2003<br>
Author(s): Axel D&ouml;rfler
</h6>
This document describes the feature of the BeOS kernel to monitor nodes. First,
there is an explanation of what kind of functionality we have to reproduce (along
with the higher level API), then we will present the implementation in OpenBeOS.
<h2>Requirements - Exported Functionality in BeOS</h2>
From user-level, BeOS exports the following API as found in the storage/NodeMonitor.h
header file:
<pre>
status_t watch_node(const node_ref *node,
uint32 flags,
BMessenger target);
status_t watch_node(const node_ref *node,
uint32 flags,
const BHandler *handler,
const BLooper *looper = NULL);
status_t stop_watching(BMessenger target);
status_t stop_watching(const BHandler *handler,
const BLooper *looper = NULL);
</pre>
The kernel also exports two other functions to be used from file system add-ons
that causes the kernel to send out notification messages:
<pre>
int notify_listener(int op, nspace_id nsid,
vnode_id vnida, vnode_id vnidb,
vnode_id vnidc, const char *name);
int send_notification(port_id port, long token,
ulong what, long op, nspace_id nsida,
nspace_id nsidb, vnode_id vnida,
vnode_id vnidb, vnode_id vnidc,
const char *name);
</pre>
<p>
The latter is only used for live query updates, but is obviously called by
the former. The port/token pair identify a unique BLooper/BHandler pair, and
it used internally to address those high-level objects from the kernel.
</p>
<p>
When a file system calls the <code>notify_listener()</code> function, it will have
a look if there are monitors for that node which meet the specified constraints -
and it will call <code>send_notification()</code> for every single message to be send.
</p>
<p>
Each of the parameters <code>vnida - vnidc</code> has a dedicated meaning:
<ul>
<li><b>vnida:</b> the parent directory of the "main" node</li>
<li><b>vnidb:</b> the target parent directory for a move</li>
<li><b>vnidc:</b> the node that has triggered the notification to be send</li>
</ul>
</p>
<p>
The flags parameter in <code>watch_node()</code> understands the following constants:
</p>
<ul>
<li><b>B_STOP_WATCHING</b><br>
watch_node() will stop to watch the specified node.</li>
<li><b>B_WATCH_NAME</b><br>
name changes are notified through a B_ENTRY_MOVED opcode.</li>
<li><b>B_WATCH_STAT</b><br>
changes to the node's stat structure are notified with a B_STAT_CHANGED code.</li>
<li><b>B_WATCH_ATTR</b><br>
attribute changes will cause a B_ATTR_CHANGED to be send.</li>
<li><b>B_WATCH_DIRECTORY</b><br>
notifies on changes made to the specified directory, i.e. B_ENTRY_REMOVED, B_ENTRY_CREATED</li>
<li><b>B_WATCH_ALL</b><br>
is a short-hand for the flags above.</li>
<li><b>B_WATCH_MOUNT</b><br>
causes B_DEVICE_MOUNTED and B_DEVICE_UNMOUNTED to be send.</li>
</ul>
<p>
Node monitors are maintained per team - every team can have up to 4096 monitors, although
there exists a private kernel call to raise this limit (for example, Tracker is using it
intensively).
</p>
<p>
The kernel is able to send the BMessages directly to the specified BLooper and BHandler;
it achieves this using the application kit's token mechanism. The message is constructed
manually in the kernel, it doesn't use any application kit services.
</p>
<br>
<h2>Meeting the Requirements in an Optimal Way - Implementation in OpenBeOS</h2>
<p>
If you assume that every file operation could trigger a notification message to be send,
it's clear that the node monitoring system must be optimized for sending messages. For
every call to <code>notify_listener()</code>, the kernel must check if there are any
monitors for the node that was updated.
</p>
<p>
Those monitors are put into a hash table which has the device number and the vnode ID
as keys. Each of the monitors maintains a list of listeners which specify which port/token
pair should be notified for what change. Since the vnodes are created/deleted as needed
from the kernel, the node monitor is maintained independently from them; a simple pointer
from a vnode to its monitor is not possible.
</p>
<p>
The main structures that are involved in providing the node monitoring functionality
look like this:
</p>
<pre>
struct monitor_listener {
monitor_listener *next;
monitor_listener *prev;
list_link monitor_link;
port_id port;
int32 token;
uint32 flags;
node_monitor *monitor;
};
struct node_monitor {
node_monitor *next;
mount_id device;
vnode_id node;
struct list listeners;
};
</pre>
<p>
The relevant part of the I/O context structure is this:
</p>
<pre>
struct io_context {
...
struct list node_monitors;
uint32 num_monitors;
uint32 max_monitors;
};
</pre>
<p>
If you call <code>watch_node()</code> on a file with a flags parameter unequal to
B_STOP_WATCHING, the following will happen in the node monitor:
</p>
<ol>
<li>The <code>add_node_monitor()</code> function does a hash lookup for the
device/vnode pair. If there is no <code>node_monitor</code> yet for this pair,
a new one will be created.</li>
<li>The list of listeners is scanned for the provided port/token pair (the
BLooper/BHandler pointer will already be translated in user-space), and
the new flag is or'd to the old field, or a new <code>monitor_listener</code>
is created if necessary - in the latter case, the team's node monitor
counter is incremented.</li>
</ol>
<p>
If it's called with B_STOP_WATCHING defined, the reverse operation take effect, and
the <code>monitor</code> field is used to see if this monitor don't have any listeners
anymore, in which case it will be removed.
</p>
<p>
Note the presence of the <code>max_monitors</code> - there is no hard limit the kernel
exposes to userland applications; the listeners are maintained in a doubly-linked list.
</p>
<p>
If a team is shut down, all listeners from its I/O context will be removed - since every
listener stores a pointer to its monitor, determining the monitors that can be removed
because of this operation is very cheap.
</p>
<p>
The <code>notify_listener()</code> also only does a hash lookup for the device/node
pair it got from the file system, and sends out as many notifications as specified by
the listeners of the monitor that belong to that node.
</p>
<p>
If a node is deleted from the disk, the corresponding <code>node_monitor</code> and its
listeners will be removed as well, to prevent watching a new file that accidently happen
to have the same device/node pair (as is possible with BFS, for example).
</p>
<br>
<h2>Differences Between Both Implementations</h2>
<p>
Although the aim was to create a completely compatible monitoring implementation,
there are some notable differences between the two.
</p>
<p>
BeOS reserves a certain number of slots for calls to <code>watch_node()</code> - each
call to that function will use one slot, even if you call it twice for the same node.
OpenBeOS, however, will always use one slot per node - you could call <code>watch_node()</code>
several times, but you would waste only one slot.
</p>
<p>
While this is an implementational detail, it also causes a change in behaviour for
applications; in BeOS, applications will get one message for every <code>watch_node()</code>
call, in OpenBeOS, you'll get only one message per node. If an application relies
on this strange behaviour of the BeOS kernel, it will no longer work correctly.
</p>
<p>
The other difference is that OpenBeOS exports its node monitoring functionality to
kernel modules as well, and provides an extra plain C API for them to use.
</p>
<br>
<h2>And Beyond?</h2>
<p>
The current implementation directly iterates over all listeners and sends out notifications
as required synchronously in the context of the thread that triggered the notification to
be sent.
</p>
<p>
If a node monitor needs to send out several messages, this could theoretically greatly
decrease file system performance. To optimize for this case, the required data of the
notification could be put into a queue and be sent by a dedicated worker thread. Since
this requires an additional copy operation and a reserved address space for this queue,
this optimization could be more expensive than the current implementation, depending
on the usage pattern of the node monitoring mechanism.
</p>
<p>
With BFS, it would be possible to introduce the possibility to automatically watch all
files in a specified directory. While this would be very convenient at application level,
it comes with several disadvantages:
</p>
<ol>
<li>This feature might not be easily accomplishable for many file systems; a file system
must be able to retrieve a node by ID only - it might not be feasible to find
out about the parent directory for many file systems.</li>
<li>Although it could potentially safe node monitors, it might cause the kernel to
send out a lot more messages to the application than it needs. With the restriction
the kernel imposes to the number of watched nodes for a team, the application's
designer might try to be much stricter with the number of monitors his application
will consume.</li>
</ol>
<p>
While 1.) might be a real show stopper, 2.) is almost invalidated because of Tracker's
usage of node monitors; it consumes a monitor for every entry it displays, which might
be several thousands. Implementing this feature would not only greatly speed up maintaining
this massive need of monitors, and cut down memory usage, but also ease the implementation
at application level.
</p>
<p>
Even 1.) could be solved if the kernel could query a file system if it can support
this particular feature; it could then automatically monitor all files in that directory
without adding complexity to the application using this feature. Of course,
the effort to provide this functionality is much larger then - but for applications
like Tracker, the complexity would be removed from the application without extra cost.
</p>
<p>
However, none of the discussed feature extensions have been implemented for the currently
developed version R1 of OpenBeOS.
</p>
</body>
</html>
+257
View File
@@ -0,0 +1,257 @@
Node Monitoring
===============
Creation Date: January 16, 2003
Author(s): Axel Dörfler
This document describes the feature of the BeOS kernel to monitor nodes.
First, there is an explanation of what kind of functionality we have to
reproduce (along with the higher level API), then we will present the
implementation in OpenBeOS.
Requirements - Exported Functionality in BeOS
---------------------------------------------
From user-level, BeOS exports the following API as found in the
storage/NodeMonitor.h header file:
::
status_t watch_node(const node_ref *node,
uint32 flags,
BMessenger target);
status_t watch_node(const node_ref *node,
uint32 flags,
const BHandler *handler,
const BLooper *looper = NULL);
status_t stop_watching(BMessenger target);
status_t stop_watching(const BHandler *handler,
const BLooper *looper = NULL);
The kernel also exports two other functions to be used from file system
add-ons that causes the kernel to send out notification messages:
::
int notify_listener(int op, nspace_id nsid,
vnode_id vnida, vnode_id vnidb,
vnode_id vnidc, const char *name);
int send_notification(port_id port, long token,
ulong what, long op, nspace_id nsida,
nspace_id nsidb, vnode_id vnida,
vnode_id vnidb, vnode_id vnidc,
const char *name);
The latter is only used for live query updates, but is obviously called
by the former. The port/token pair identify a unique BLooper/BHandler
pair, and it used internally to address those high-level objects from
the kernel.
When a file system calls the ``notify_listener()`` function, it will
have a look if there are monitors for that node which meet the specified
constraints - and it will call ``send_notification()`` for every single
message to be send.
Each of the parameters ``vnida - vnidc`` has a dedicated meaning:
- **vnida:** the parent directory of the "main" node
- **vnidb:** the target parent directory for a move
- **vnidc:** the node that has triggered the notification to be send
The flags parameter in ``watch_node()`` understands the following
constants:
- **B_STOP_WATCHING**
watch_node() will stop to watch the specified node.
- **B_WATCH_NAME**
name changes are notified through a B_ENTRY_MOVED opcode.
- **B_WATCH_STAT**
changes to the node's stat structure are notified with a
B_STAT_CHANGED code.
- **B_WATCH_ATTR**
attribute changes will cause a B_ATTR_CHANGED to be send.
- **B_WATCH_DIRECTORY**
notifies on changes made to the specified directory, i.e.
B_ENTRY_REMOVED, B_ENTRY_CREATED
- **B_WATCH_ALL**
is a short-hand for the flags above.
- **B_WATCH_MOUNT**
causes B_DEVICE_MOUNTED and B_DEVICE_UNMOUNTED to be send.
Node monitors are maintained per team - every team can have up to 4096
monitors, although there exists a private kernel call to raise this
limit (for example, Tracker is using it intensively).
The kernel is able to send the BMessages directly to the specified
BLooper and BHandler; it achieves this using the application kit's token
mechanism. The message is constructed manually in the kernel, it doesn't
use any application kit services.
|
Meeting the Requirements in an Optimal Way - Implementation in OpenBeOS
-----------------------------------------------------------------------
If you assume that every file operation could trigger a notification
message to be send, it's clear that the node monitoring system must be
optimized for sending messages. For every call to ``notify_listener()``,
the kernel must check if there are any monitors for the node that was
updated.
Those monitors are put into a hash table which has the device number and
the vnode ID as keys. Each of the monitors maintains a list of listeners
which specify which port/token pair should be notified for what change.
Since the vnodes are created/deleted as needed from the kernel, the node
monitor is maintained independently from them; a simple pointer from a
vnode to its monitor is not possible.
The main structures that are involved in providing the node monitoring
functionality look like this:
::
struct monitor_listener {
monitor_listener *next;
monitor_listener *prev;
list_link monitor_link;
port_id port;
int32 token;
uint32 flags;
node_monitor *monitor;
};
struct node_monitor {
node_monitor *next;
mount_id device;
vnode_id node;
struct list listeners;
};
The relevant part of the I/O context structure is this:
::
struct io_context {
...
struct list node_monitors;
uint32 num_monitors;
uint32 max_monitors;
};
If you call ``watch_node()`` on a file with a flags parameter unequal to
B_STOP_WATCHING, the following will happen in the node monitor:
#. The ``add_node_monitor()`` function does a hash lookup for the
device/vnode pair. If there is no ``node_monitor`` yet for this pair,
a new one will be created.
#. The list of listeners is scanned for the provided port/token pair
(the BLooper/BHandler pointer will already be translated in
user-space), and the new flag is or'd to the old field, or a new
``monitor_listener`` is created if necessary - in the latter case,
the team's node monitor counter is incremented.
If it's called with B_STOP_WATCHING defined, the reverse operation take
effect, and the ``monitor`` field is used to see if this monitor don't
have any listeners anymore, in which case it will be removed.
Note the presence of the ``max_monitors`` - there is no hard limit the
kernel exposes to userland applications; the listeners are maintained in
a doubly-linked list.
If a team is shut down, all listeners from its I/O context will be
removed - since every listener stores a pointer to its monitor,
determining the monitors that can be removed because of this operation
is very cheap.
The ``notify_listener()`` also only does a hash lookup for the
device/node pair it got from the file system, and sends out as many
notifications as specified by the listeners of the monitor that belong
to that node.
If a node is deleted from the disk, the corresponding ``node_monitor``
and its listeners will be removed as well, to prevent watching a new
file that accidently happen to have the same device/node pair (as is
possible with BFS, for example).
|
Differences Between Both Implementations
----------------------------------------
Although the aim was to create a completely compatible monitoring
implementation, there are some notable differences between the two.
BeOS reserves a certain number of slots for calls to ``watch_node()`` -
each call to that function will use one slot, even if you call it twice
for the same node. OpenBeOS, however, will always use one slot per node
- you could call ``watch_node()`` several times, but you would waste
only one slot.
While this is an implementational detail, it also causes a change in
behaviour for applications; in BeOS, applications will get one message
for every ``watch_node()`` call, in OpenBeOS, you'll get only one
message per node. If an application relies on this strange behaviour of
the BeOS kernel, it will no longer work correctly.
The other difference is that OpenBeOS exports its node monitoring
functionality to kernel modules as well, and provides an extra plain C
API for them to use.
|
And Beyond?
-----------
The current implementation directly iterates over all listeners and
sends out notifications as required synchronously in the context of the
thread that triggered the notification to be sent.
If a node monitor needs to send out several messages, this could
theoretically greatly decrease file system performance. To optimize for
this case, the required data of the notification could be put into a
queue and be sent by a dedicated worker thread. Since this requires an
additional copy operation and a reserved address space for this queue,
this optimization could be more expensive than the current
implementation, depending on the usage pattern of the node monitoring
mechanism.
With BFS, it would be possible to introduce the possibility to
automatically watch all files in a specified directory. While this would
be very convenient at application level, it comes with several
disadvantages:
#. This feature might not be easily accomplishable for many file
systems; a file system must be able to retrieve a node by ID only -
it might not be feasible to find out about the parent directory for
many file systems.
#. Although it could potentially safe node monitors, it might cause the
kernel to send out a lot more messages to the application than it
needs. With the restriction the kernel imposes to the number of
watched nodes for a team, the application's designer might try to be
much stricter with the number of monitors his application will
consume.
While 1.) might be a real show stopper, 2.) is almost invalidated
because of Tracker's usage of node monitors; it consumes a monitor for
every entry it displays, which might be several thousands. Implementing
this feature would not only greatly speed up maintaining this massive
need of monitors, and cut down memory usage, but also ease the
implementation at application level.
Even 1.) could be solved if the kernel could query a file system if it
can support this particular feature; it could then automatically monitor
all files in that directory without adding complexity to the application
using this feature. Of course, the effort to provide this functionality
is much larger then - but for applications like Tracker, the complexity
would be removed from the application without extra cost.
However, none of the discussed feature extensions have been implemented
for the currently developed version R1 of OpenBeOS.
-406
View File
@@ -1,406 +0,0 @@
This file contains the documentation written by Thomas Kurschel that was originally
found in the headers of his pnp_manager.
It's outdated but could be used as a basis for the real documentation.
// former pnp_manager.h
/*
Copyright (c) 2003-04, Thomas Kurschel
PnP manager; Takes care of registration and loading of PnP drivers
Read pnp_driver.h first to understand the basic idea behind PnP drivers.
To register a driver node, use register_driver. If the device got lost,
use unregister_driver (note: if the parent node is removed, your node
get removed automatically as your driver has obviously nothing to work
with anymore). To get access to a (parent) device, use load_driver/
unload_driver.
To let the manager find a consumer (see pnp_driver.h), you can either
specify its name directly during registration, using a
PNP_DRIVER_FIXED_CONSUMER attribute, or let the manager search the
appropriate consumer(s) via a PNP_DRIVER_DYNAMIC_CONSUMER attribute.
Searching of dynamic consumers is done as follows:
- First, the manager searches for a Specific driver in the base
directory (see below)
- If no Specific driver is found, all Generic drivers stored under
"generic" sub-directory are informed in turn until one returns success
- Finally, _all_ Universal drivers, stored in the "universal" sub-
directory, are informed
Specification of the base directory and of the names of Specific
drivers is done via a file name pattern given by a
PNP_DRIVER_DYNAMIC_CONSUMER attribute.
First, all substrings of the form "%attribute_name%" are replaced by the
content of the attribute "attribute_name" as follows:
- if the attribute contains an integer value, its content is converted to hex
(lowercase) with a fixed length according to the attribute's value range
- the content of string attributes is quoted by " and invalid characters
(i.e. /%" and all characters outside 32..126) are replaced by their
unsigned decimal value, delimited by %
- other attribute types cannot be used
Second, the resulting name is split into chunks according to the presence
of | characters (you can escape % and | with a ^ character). These
characters are only delimiters and get removed before further processing.
The directory before the first | character is the base directory (see
above). It contains the "generic" and the "universal" subdirectories.
The names of the specific drivers are created by first taking the entire
file name, then by removing the last chunk, then by removing the last
two chunks and so on until only the first chunk is left.
As drivers can contain multiple modules, the module name is constructed
by appending the content of the PNP_DRIVER_TYPE attribute to the driver's file
name, seperated by a slash character (note: this only applies to dynamic
consumers; for fixed consumers, you specify the module name directly via
PNP_DRIVER_FIXED_CONSUMER).
E.g. given a dynamic consumer pattern of
"pci/vendor=%vendor_id%|, device=%device_id%" for a device with the
attributes vendor_id=0x123 and device_id=0xabcd (both being uint16), the
PnP manager tries the specific drivers "pci/vendor=0123, device=abcd" and
(if the first one fails/doesn't exist) "pci/vendor=0123". If they both
refuse to handle the device, all drivers under "pci/generic" are tried
until one accepts the device. Finally, all drivers under "pci/universal"
are loaded, whatever happened before.
In practise, you should try to use specific drivers as much as possible.
If detection based on device IDs is impossible (e.g. because the bus
doesn't support them at all), you can put the driver under "generic".
Generic drivers can also be used to specify wrappers that try to load old-
style drivers if no new driver can be found. Also, they can be used to
report an error or invoke an user program that tries downloading a
proper Specific driver. Universal drivers are mainly used for
informational purposes, e.g. to publish data about each found device,
or to provide raw access to all devices.
If the device uses physical address space or I/O space or ISA DMA
channels (called I/O resources), the driver has to acquire these
resources. During hardware detection (usually via probe()),
acquire_io_resources() must be called to get exclusive access.
If no hardware could be found, they must be released via
release_io_resources(). If detection was successful, the list of
the (acquired) resources must be passed to register_device().
Resources can either belong to one hardware detection or to a device.
If a hardware detection collides with another, it has to wait;
if it collides with a device whose driver is not loaded, the
driver loading is blocked. When detection fails, i.e. if
release_io_resources() is called, all blocked drivers can be loaded
again. If the detection fails, i.e. the resources are transferred
via register_device(), all blocked devices are unregistered and
pending load requests aborted. If a hardware detection collides
with a device whose driver is loaded, acquire_io_resources() fails
with B_BUSY. As this makes a hardware rescan impossible if the
driver is loaded, you should define PNP_DRIVER_NO_LIVE_RESCAN
for nodes that use I/O resources (see below).
To search for new drivers for a given device node, use rescan(). This
marks all consumer devices as being verified and calls probe()
of all consumers drivers (see above) to let them rescan the parent
for devices. The <depth> parameter determines the nesting level, e.g.
2 means that first the consumers are scanned and then the consumers
of the consumers.
Normally, all devices can be rescanned. If a driver cannot handle
a rescan safely when it is loaded (i.e. used by a consumer), it
must set PNP_DRIVER_NO_LIVE_RESCAN, in which case the device is
ignored during rescan if the driver is loaded and attempts
to load the driver during a rescan are blocked until the rescan
is finished. If rescanning a device is not possible at all, it must
have set PNP_DRIVER_NEVER_RESCAN to always ignore it.
To distinguish between new devices, lost devices and redetected
devices, consumer devices should provide a connection code and a
device identifier. They are specified by PNP_DRIVER_CONNECTION and
PNP_DRIVER_CONNECTION respectively, and are expanded in the same way
as PNP_DRIVER_DYNAMIC_CONSUMER. It is assumed that there can be only
one device per connection and that a device can be uniquely identify
by a device identifier. If a consumer device is registered on the
same connection as an existing device but with a different device
identifier, the old device gets unregistered automatically. If both
connection and device identifier are the same, registration is
handled as a redetection and ignored (unless a different type or
driver module is specified - in this case, the device is replaced).
Devices that were not redetected during a rescan get unregistered
unless they were ignored (see above).
*/
// interface of PnP manager
typedef struct device_manager_info {
module_info info;
// load driver
// node - node whos driver is to be loaded
// user_cookie - cookie to be passed to init_device of driver
// interface - interface of loaded driver
// cookie - device cookie issued by loaded driver
status_t (*init_driver)(device_node_handle node, void *userCookie,
driver_module_info **interface, void **cookie);
// unload driver
status_t (*uninit_driver)(device_node_handle node);
// rescan node for new dynamic drivers
// node - node whose dynamic drivers are to be scanned
status_t (*rescan)(device_node_handle node);
// register device
// parent - parent node
// attributes - NULL-terminated array of node attributes
// io_resources - NULL-terminated array of I/O resources (can be NULL)
// node - new node handle
// on return, io_resources are invalid: on success I/O resources belong
// to node, on fail they are released;
// if device is already registered, B_OK is returned but *node is NULL
status_t (*register_device)(device_node_handle parent,
const device_attr *attrs,
const io_resource_handle *io_resources,
device_node_handle *node);
// unregister device
// all nodes having this node as their parent are unregistered too.
// if the node contains PNP_MANAGER_ID_GENERATOR/PNP_MANAGER_AUTO_ID
// pairs, the id specified this way is freed too
status_t (*unregister_device)(device_node_handle node);
// find device by node content
// the given attributes must _uniquely_ identify a device node;
// parent - parent node (-1 for don't-care)
// attrs - list of attributes (can be NULL)
// The node you got will be automatically put on the next call
// to this function.
status_t (*get_next_child_device)(device_node_handle parent,
device_node_handle *_node, const device_attr *attrs);
// get parent device node
device_node_handle (*get_parent)(device_node_handle node);
// Must be called after get_next_child_device() (if you don't iterate through)
// and get_parent() to make sure the node is freed when it's not used anymore
void (*put_device_node)(device_node_handle node);
// acquire I/O resources
// resources - NULL-terminated array of resources to acquire
// handles - NULL-terminated array of handles (one per resource);
// array must be provided by caller
// return B_BUSY if a resource is used by a loaded driver
status_t (*acquire_io_resources)(io_resource *resources,
io_resource_handle *handles);
// release I/O resources
// handles - NULL-terminated array of handles
status_t (*release_io_resources)(const io_resource_handle *handles);
// create unique id
// generator - name of id set
// if result >= 0 - unique id
// result < 0 - error code
int32 (*create_id)(const char *generator);
// free unique id
status_t (*free_id)(const char *generator, uint32 id);
// helpers to extract attribute by name.
// if <recursive> is true, parent nodes are scanned if
// attribute isn't found in current node; unless you declared
// the attribute yourself, use recursive search to handle
// intermittent nodes, e.g. defined by filter drivers, transparently.
// for raw and string attributes, you get a copy that must
// be freed by caller
status_t (*get_attr_uint8)(device_node_handle node,
const char *name, uint8 *value, bool recursive);
status_t (*get_attr_uint16)(device_node_handle node,
const char *name, uint16 *value, bool recursive);
status_t (*get_attr_uint32)(device_node_handle node,
const char *name, uint32 *value, bool recursive);
status_t (*get_attr_uint64)(device_node_handle node,
const char *name, uint64 *value, bool recursive);
status_t (*get_attr_string)(device_node_handle node,
const char *name, char **value, bool recursive);
status_t (*get_attr_raw)(device_node_handle node,
const char *name, void **data, size_t *_size,
bool recursive);
// get next attribute of node;
// on call, *<attr_handle> must contain handle of an attribute;
// on return, *<attr_handle> is replaced by the next attribute or
// NULL if it was the last;
// to get the first attribute, <attr_handle> must point to NULL;
// the returned handle must be released by either passing it to
// another get_next_attr() call or by using release_attr()
// directly
status_t (*get_next_attr)(device_node_handle node,
device_attr_handle *attrHandle);
// release attribute handle <attr_handle> of <node>;
// see get_next_attr
status_t (*release_attr)(device_node_handle node,
device_attr_handle attr_handle);
// retrieve attribute data with handle given;
// <attr> is only valid as long as you don't release <attr_handle>
// implicitely or explicitely
status_t (*retrieve_attr)(device_attr_handle attr_handle,
const device_attr **attr);
// change/add attribute <attr> of/to node
status_t (*write_attr)(device_node_handle node,
const device_attr *attr);
// remove attribute of node by name
// <name> is name of attribute
status_t (*remove_attr)(device_node_handle node, const char *name);
} device_manager_info;
// former pnp_driver.h
/*
Copyright (c) 2003-04, Thomas Kurschel
Required interface of PnP drivers
In contrast to standard BeOS drivers, PnP drivers are normal modules
having the interface described below.
Every device is described by its driver via a PnP node with properties
described in PnP Node Attributes. Devices are organized in a hierarchy,
e.g. a devfs device is a hard disk device that is connected to a
controller, which is a PCI device, that is connected to a PCI bus.
Every device is connected to its lower-level device via a parent link
stored in its Node. The higher-level is called the consumer of the
lower-level device. If the lower-level device gets removed, all its
consumers are removed too.
In our example, the hierarchy is
devfs device -> hard disk -> controller -> PCI device -> PCI bus
If the PCI bus is removed, everything up to including the devfs device
is removed too.
The driver hierarchy is constructed bottom-up, i.e. the lower-level
driver searches for a corresponding consumer, which in turns searches
for its consumer and so on. The lowest driver is usually something like
a PCI bus, the highest driver is normally a devfs entry (see pnp_devfs.h).
Registration of devices and the search for appropriate consumers is
done via the pnp_manager (see pnp_manager.h).
When a potential consumer is found, it gets informed about the new
lower-level device and can either refuse its handling or accept it.
On accept, it has to create a new node with the lower-level device
node as its parent.
Loading of drivers is done on demand, i.e. if the consumer wants to
access its lower-level device, it explicitely loads the corresponding
driver, and once it doesn't need it anymore, the lower-level driver
must be unloaded. Usually, this process happens recursively, i.e. in
our example, the hard disk driver loads the controller driver, which
loads the PCI device driver which loads the PCI bus driver. The same
process applies to unloading.
Because of this dynamic loading, drivers must store persistent data
in the node of their devices. Please be aware that you cannot modify
a node once published.
If a device gets removed, you must unregister its node. As said, the
PnP manager will automatically unregister all consumers too. The
corresponding drivers are notified to stop talking to their lower-level
devices and to terminate running requests. Normally, you want to use a
dedicated variable that is verified at each call to make sure that the
parent is still there. The notification is done independantly of the
driver being loaded by its consumer(s) or not. If it isn't loaded,
the notification callback gets NULL as the device cookie; normally, the
driver returns immediately in this case. As soon as both the device
is removed and the driver is unloaded, device_cleanup gets called to
free resources that couldn't be safely removed in device_removed when
the driver was still loaded.
If a device has exactly one consumer, they often interact in some way.
To simplify that, the consumer can pass a user-cookie to its parent
during load. In this case, it's up to the parent driver to get a
pointer to the interface of the consumer. Effectively, such consumers
have one interface for their consumers (base on pnp_driver_info), and
a another for their parents (with a completely driver-specific
structure).
In terms of synchronization, loading/unloading/remove-notifications
are executed synchronously, i.e. if e.g. a device is to be unloaded
but the drive currently handles a remove-notification, the unloading
is delayed until the nofication callback returns. If multiple consumers
load a driver, the driver gets initialized only once; subsequent load
requests increase an internal load count only and return immediately.
In turn, unloading only happens once the load count reaches zero.
*/
struct driver_module_info {
module_info info;
float (*supports_device)(device_node_handle parent, bool *_noConnection);
// check whether this parent is supported
status_t (*register_device)(device_node_handle parent);
// Register your device node.
status_t (*init_driver)(device_node_handle node, void *user_cookie, void **_cookie);
// driver is loaded.
// node - node of device
// user_cookie - cookie passed by loading driver
// cookie - cookie issued by this driver
status_t (*uninit_driver)(void *cookie);
// driver gets unloaded.
void (*device_removed)(device_node_handle node, void *cookie);
// a device node, registered by this driver, got removed.
// if the driver wasn't loaded when this happenes, no (un)init_device
// is called and thus <cookie> is NULL;
void (*device_cleanup)(device_node_handle node);
// a device node, registered by this driver, got removed and
// the driver got unloaded
void (*get_supported_paths)(const char ***_busses, const char ***_devices);
};
// former pnp_bus.h
/*
Copyright (c) 2003-04, Thomas Kurschel
Required interface of PnP bus drivers
Busses consist of two node layers: the lower layer defines the bus,
the upper layer defines the abstract devices connected to the bus.
Both layers are handled by a bus manager. Actual device nodes are
on top of abstract device nodes.
E.g. if we have a PCI bus with an IDE controller on it, we get
IDE controller -> PCI device -> PCI bus
with:
IDE controller = actual device node
PCI device = abstract device node
PCI bus = bus node
The PCI bus manager establishes both the PCI devices and the PCI busses.
Abstract device nodes act as a gateway between actual device nodes
and the corresponding bus node. They are constructed by the bus
node driver via its rescan() hook. To identify a bus node, define
PNP_BUS_IS_BUS as an attribute of it. As a result, the PnP manager
will call the rescan() method of the bus driver whenever the
bus is to be rescanned. Afterwards, all possible dynamic consumers
are informed as done for normal nodes.
Normally, potential device drivers are notified immediately when
rescan() registers a new abstract device node. But sometimes, device
drivers need to know _all_ devices connected to the bus for correct
detection. To ensure this, the bus node must define
PNP_BUS_NOTIFY_CONSUMERS_AFTER_RESCAN. In this case, scanning for
consumers is postponed until rescan() has finished.
If hot-plugging of devices can be detected automatically (e.g. USB),
you should define PNP_DRIVER_ALWAYS_LOADED, so the bus driver is
always loaded and thus capable of handling hot-plug events generated
by the bus controller hardware.
*/
@@ -0,0 +1,408 @@
Plug and Play Manager
=====================
This file contains the documentation written by Thomas Kurschel that was originally
found in the headers of his pnp_manager.
It's outdated but could be used as a basis for the real documentation.
PNP Manager
-----------
PnP manager; Takes care of registration and loading of PnP drivers
Read pnp_driver.h first to understand the basic idea behind PnP drivers.
To register a driver node, use register_driver. If the device got lost,
use unregister_driver (note: if the parent node is removed, your node
get removed automatically as your driver has obviously nothing to work
with anymore). To get access to a (parent) device, use load_driver/
unload_driver.
To let the manager find a consumer (see pnp_driver.h), you can either
specify its name directly during registration, using a
PNP_DRIVER_FIXED_CONSUMER attribute, or let the manager search the
appropriate consumer(s) via a PNP_DRIVER_DYNAMIC_CONSUMER attribute.
Searching of dynamic consumers is done as follows:
- First, the manager searches for a Specific driver in the base
directory (see below)
- If no Specific driver is found, all Generic drivers stored under
"generic" sub-directory are informed in turn until one returns success
- Finally, _all_ Universal drivers, stored in the "universal" sub-
directory, are informed
Specification of the base directory and of the names of Specific
drivers is done via a file name pattern given by a
PNP_DRIVER_DYNAMIC_CONSUMER attribute.
First, all substrings of the form "%attribute_name%" are replaced by the
content of the attribute "attribute_name" as follows:
- if the attribute contains an integer value, its content is converted to hex
(lowercase) with a fixed length according to the attribute's value range
- the content of string attributes is quoted by " and invalid characters
(i.e. /%" and all characters outside 32..126) are replaced by their
unsigned decimal value, delimited by %
- other attribute types cannot be used
Second, the resulting name is split into chunks according to the presence
of | characters (you can escape % and | with a ^ character). These
characters are only delimiters and get removed before further processing.
The directory before the first | character is the base directory (see
above). It contains the "generic" and the "universal" subdirectories.
The names of the specific drivers are created by first taking the entire
file name, then by removing the last chunk, then by removing the last
two chunks and so on until only the first chunk is left.
As drivers can contain multiple modules, the module name is constructed
by appending the content of the PNP_DRIVER_TYPE attribute to the driver's file
name, seperated by a slash character (note: this only applies to dynamic
consumers; for fixed consumers, you specify the module name directly via
PNP_DRIVER_FIXED_CONSUMER).
E.g. given a dynamic consumer pattern of
"pci/vendor=%vendor_id%|, device=%device_id%" for a device with the
attributes vendor_id=0x123 and device_id=0xabcd (both being uint16), the
PnP manager tries the specific drivers "pci/vendor=0123, device=abcd" and
(if the first one fails/doesn't exist) "pci/vendor=0123". If they both
refuse to handle the device, all drivers under "pci/generic" are tried
until one accepts the device. Finally, all drivers under "pci/universal"
are loaded, whatever happened before.
In practise, you should try to use specific drivers as much as possible.
If detection based on device IDs is impossible (e.g. because the bus
doesn't support them at all), you can put the driver under "generic".
Generic drivers can also be used to specify wrappers that try to load old-
style drivers if no new driver can be found. Also, they can be used to
report an error or invoke an user program that tries downloading a
proper Specific driver. Universal drivers are mainly used for
informational purposes, e.g. to publish data about each found device,
or to provide raw access to all devices.
If the device uses physical address space or I/O space or ISA DMA
channels (called I/O resources), the driver has to acquire these
resources. During hardware detection (usually via probe()),
acquire_io_resources() must be called to get exclusive access.
If no hardware could be found, they must be released via
release_io_resources(). If detection was successful, the list of
the (acquired) resources must be passed to register_device().
Resources can either belong to one hardware detection or to a device.
If a hardware detection collides with another, it has to wait;
if it collides with a device whose driver is not loaded, the
driver loading is blocked. When detection fails, i.e. if
release_io_resources() is called, all blocked drivers can be loaded
again. If the detection fails, i.e. the resources are transferred
via register_device(), all blocked devices are unregistered and
pending load requests aborted. If a hardware detection collides
with a device whose driver is loaded, acquire_io_resources() fails
with B_BUSY. As this makes a hardware rescan impossible if the
driver is loaded, you should define PNP_DRIVER_NO_LIVE_RESCAN
for nodes that use I/O resources (see below).
To search for new drivers for a given device node, use rescan(). This
marks all consumer devices as being verified and calls probe()
of all consumers drivers (see above) to let them rescan the parent
for devices. The <depth> parameter determines the nesting level, e.g.
2 means that first the consumers are scanned and then the consumers
of the consumers.
Normally, all devices can be rescanned. If a driver cannot handle
a rescan safely when it is loaded (i.e. used by a consumer), it
must set PNP_DRIVER_NO_LIVE_RESCAN, in which case the device is
ignored during rescan if the driver is loaded and attempts
to load the driver during a rescan are blocked until the rescan
is finished. If rescanning a device is not possible at all, it must
have set PNP_DRIVER_NEVER_RESCAN to always ignore it.
To distinguish between new devices, lost devices and redetected
devices, consumer devices should provide a connection code and a
device identifier. They are specified by PNP_DRIVER_CONNECTION and
PNP_DRIVER_CONNECTION respectively, and are expanded in the same way
as PNP_DRIVER_DYNAMIC_CONSUMER. It is assumed that there can be only
one device per connection and that a device can be uniquely identify
by a device identifier. If a consumer device is registered on the
same connection as an existing device but with a different device
identifier, the old device gets unregistered automatically. If both
connection and device identifier are the same, registration is
handled as a redetection and ignored (unless a different type or
driver module is specified - in this case, the device is replaced).
Devices that were not redetected during a rescan get unregistered
unless they were ignored (see above).
.. code-block:: cpp
// interface of PnP manager
typedef struct device_manager_info {
module_info info;
// load driver
// node - node whos driver is to be loaded
// user_cookie - cookie to be passed to init_device of driver
// interface - interface of loaded driver
// cookie - device cookie issued by loaded driver
status_t (*init_driver)(device_node_handle node, void *userCookie,
driver_module_info **interface, void **cookie);
// unload driver
status_t (*uninit_driver)(device_node_handle node);
// rescan node for new dynamic drivers
// node - node whose dynamic drivers are to be scanned
status_t (*rescan)(device_node_handle node);
// register device
// parent - parent node
// attributes - NULL-terminated array of node attributes
// io_resources - NULL-terminated array of I/O resources (can be NULL)
// node - new node handle
// on return, io_resources are invalid: on success I/O resources belong
// to node, on fail they are released;
// if device is already registered, B_OK is returned but *node is NULL
status_t (*register_device)(device_node_handle parent,
const device_attr *attrs,
const io_resource_handle *io_resources,
device_node_handle *node);
// unregister device
// all nodes having this node as their parent are unregistered too.
// if the node contains PNP_MANAGER_ID_GENERATOR/PNP_MANAGER_AUTO_ID
// pairs, the id specified this way is freed too
status_t (*unregister_device)(device_node_handle node);
// find device by node content
// the given attributes must _uniquely_ identify a device node;
// parent - parent node (-1 for don't-care)
// attrs - list of attributes (can be NULL)
// The node you got will be automatically put on the next call
// to this function.
status_t (*get_next_child_device)(device_node_handle parent,
device_node_handle *_node, const device_attr *attrs);
// get parent device node
device_node_handle (*get_parent)(device_node_handle node);
// Must be called after get_next_child_device() (if you don't iterate through)
// and get_parent() to make sure the node is freed when it's not used anymore
void (*put_device_node)(device_node_handle node);
// acquire I/O resources
// resources - NULL-terminated array of resources to acquire
// handles - NULL-terminated array of handles (one per resource);
// array must be provided by caller
// return B_BUSY if a resource is used by a loaded driver
status_t (*acquire_io_resources)(io_resource *resources,
io_resource_handle *handles);
// release I/O resources
// handles - NULL-terminated array of handles
status_t (*release_io_resources)(const io_resource_handle *handles);
// create unique id
// generator - name of id set
// if result >= 0 - unique id
// result < 0 - error code
int32 (*create_id)(const char *generator);
// free unique id
status_t (*free_id)(const char *generator, uint32 id);
// helpers to extract attribute by name.
// if <recursive> is true, parent nodes are scanned if
// attribute isn't found in current node; unless you declared
// the attribute yourself, use recursive search to handle
// intermittent nodes, e.g. defined by filter drivers, transparently.
// for raw and string attributes, you get a copy that must
// be freed by caller
status_t (*get_attr_uint8)(device_node_handle node,
const char *name, uint8 *value, bool recursive);
status_t (*get_attr_uint16)(device_node_handle node,
const char *name, uint16 *value, bool recursive);
status_t (*get_attr_uint32)(device_node_handle node,
const char *name, uint32 *value, bool recursive);
status_t (*get_attr_uint64)(device_node_handle node,
const char *name, uint64 *value, bool recursive);
status_t (*get_attr_string)(device_node_handle node,
const char *name, char **value, bool recursive);
status_t (*get_attr_raw)(device_node_handle node,
const char *name, void **data, size_t *_size,
bool recursive);
// get next attribute of node;
// on call, *<attr_handle> must contain handle of an attribute;
// on return, *<attr_handle> is replaced by the next attribute or
// NULL if it was the last;
// to get the first attribute, <attr_handle> must point to NULL;
// the returned handle must be released by either passing it to
// another get_next_attr() call or by using release_attr()
// directly
status_t (*get_next_attr)(device_node_handle node,
device_attr_handle *attrHandle);
// release attribute handle <attr_handle> of <node>;
// see get_next_attr
status_t (*release_attr)(device_node_handle node,
device_attr_handle attr_handle);
// retrieve attribute data with handle given;
// <attr> is only valid as long as you don't release <attr_handle>
// implicitely or explicitely
status_t (*retrieve_attr)(device_attr_handle attr_handle,
const device_attr **attr);
// change/add attribute <attr> of/to node
status_t (*write_attr)(device_node_handle node,
const device_attr *attr);
// remove attribute of node by name
// <name> is name of attribute
status_t (*remove_attr)(device_node_handle node, const char *name);
} device_manager_info;
PNP Driver
----------
Required interface of PnP drivers
In contrast to standard BeOS drivers, PnP drivers are normal modules
having the interface described below.
Every device is described by its driver via a PnP node with properties
described in PnP Node Attributes. Devices are organized in a hierarchy,
e.g. a devfs device is a hard disk device that is connected to a
controller, which is a PCI device, that is connected to a PCI bus.
Every device is connected to its lower-level device via a parent link
stored in its Node. The higher-level is called the consumer of the
lower-level device. If the lower-level device gets removed, all its
consumers are removed too.
In our example, the hierarchy is
devfs device -> hard disk -> controller -> PCI device -> PCI bus
If the PCI bus is removed, everything up to including the devfs device
is removed too.
The driver hierarchy is constructed bottom-up, i.e. the lower-level
driver searches for a corresponding consumer, which in turns searches
for its consumer and so on. The lowest driver is usually something like
a PCI bus, the highest driver is normally a devfs entry (see pnp_devfs.h).
Registration of devices and the search for appropriate consumers is
done via the pnp_manager (see pnp_manager.h).
When a potential consumer is found, it gets informed about the new
lower-level device and can either refuse its handling or accept it.
On accept, it has to create a new node with the lower-level device
node as its parent.
Loading of drivers is done on demand, i.e. if the consumer wants to
access its lower-level device, it explicitely loads the corresponding
driver, and once it doesn't need it anymore, the lower-level driver
must be unloaded. Usually, this process happens recursively, i.e. in
our example, the hard disk driver loads the controller driver, which
loads the PCI device driver which loads the PCI bus driver. The same
process applies to unloading.
Because of this dynamic loading, drivers must store persistent data
in the node of their devices. Please be aware that you cannot modify
a node once published.
If a device gets removed, you must unregister its node. As said, the
PnP manager will automatically unregister all consumers too. The
corresponding drivers are notified to stop talking to their lower-level
devices and to terminate running requests. Normally, you want to use a
dedicated variable that is verified at each call to make sure that the
parent is still there. The notification is done independantly of the
driver being loaded by its consumer(s) or not. If it isn't loaded,
the notification callback gets NULL as the device cookie; normally, the
driver returns immediately in this case. As soon as both the device
is removed and the driver is unloaded, device_cleanup gets called to
free resources that couldn't be safely removed in device_removed when
the driver was still loaded.
If a device has exactly one consumer, they often interact in some way.
To simplify that, the consumer can pass a user-cookie to its parent
during load. In this case, it's up to the parent driver to get a
pointer to the interface of the consumer. Effectively, such consumers
have one interface for their consumers (base on pnp_driver_info), and
a another for their parents (with a completely driver-specific
structure).
In terms of synchronization, loading/unloading/remove-notifications
are executed synchronously, i.e. if e.g. a device is to be unloaded
but the drive currently handles a remove-notification, the unloading
is delayed until the nofication callback returns. If multiple consumers
load a driver, the driver gets initialized only once; subsequent load
requests increase an internal load count only and return immediately.
In turn, unloading only happens once the load count reaches zero.
.. code-block:: cpp
struct driver_module_info {
module_info info;
float (*supports_device)(device_node_handle parent, bool *_noConnection);
// check whether this parent is supported
status_t (*register_device)(device_node_handle parent);
// Register your device node.
status_t (*init_driver)(device_node_handle node, void *user_cookie, void **_cookie);
// driver is loaded.
// node - node of device
// user_cookie - cookie passed by loading driver
// cookie - cookie issued by this driver
status_t (*uninit_driver)(void *cookie);
// driver gets unloaded.
void (*device_removed)(device_node_handle node, void *cookie);
// a device node, registered by this driver, got removed.
// if the driver wasn't loaded when this happenes, no (un)init_device
// is called and thus <cookie> is NULL;
void (*device_cleanup)(device_node_handle node);
// a device node, registered by this driver, got removed and
// the driver got unloaded
void (*get_supported_paths)(const char ***_busses, const char ***_devices);
};
PNP Bus
-------
Required interface of PnP bus drivers
Busses consist of two node layers: the lower layer defines the bus,
the upper layer defines the abstract devices connected to the bus.
Both layers are handled by a bus manager. Actual device nodes are
on top of abstract device nodes.
E.g. if we have a PCI bus with an IDE controller on it, we get
IDE controller -> PCI device -> PCI bus
with:
* IDE controller = actual device node
* PCI device = abstract device node
* PCI bus = bus node
The PCI bus manager establishes both the PCI devices and the PCI busses.
Abstract device nodes act as a gateway between actual device nodes
and the corresponding bus node. They are constructed by the bus
node driver via its rescan() hook. To identify a bus node, define
PNP_BUS_IS_BUS as an attribute of it. As a result, the PnP manager
will call the rescan() method of the bus driver whenever the
bus is to be rescanned. Afterwards, all possible dynamic consumers
are informed as done for normal nodes.
Normally, potential device drivers are notified immediately when
rescan() registers a new abstract device node. But sometimes, device
drivers need to know _all_ devices connected to the bus for correct
detection. To ensure this, the bus node must define
PNP_BUS_NOTIFY_CONSUMERS_AFTER_RESCAN. In this case, scanning for
consumers is postponed until rescan() has finished.
If hot-plugging of devices can be detected automatically (e.g. USB),
you should define PNP_DRIVER_ALWAYS_LOADED, so the bus driver is
always loaded and thus capable of handling hot-plug events generated
by the bus controller hardware.
@@ -27,6 +27,8 @@ Configuring
- Install the card in your computer
- Boot Haiku and look in the syslog for the PCI bus scan for the device:
.. 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.
@@ -52,6 +54,8 @@ as well?)
Configure serial debug in config/settings/kernel/drivers/kernel:
.. code-block:: text
serial_debug_output true
serial_debug_speed 115200
serial_debug_port 0x4000
@@ -1,9 +1,11 @@
Haiku swap file support
Swap file
#######################
This article describes how to use swap file in Haiku and how the swap system
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
during the boot process and its size is twice the size of physical memory by
@@ -15,7 +17,8 @@ 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?
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.
@@ -41,9 +44,9 @@ 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____|
@@ -86,7 +89,8 @@ 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
Acknowledgement
---------------
Special thanks to my mentor Ingo. He is a knowledged person and always
gives me encouragement. Without his consistent and illuminating instructions,
@@ -95,6 +99,4 @@ this project would not have reached its present status.
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
-532
View File
@@ -1,532 +0,0 @@
<HTML>
<BODY>
<H1>Midi Kit design</H1>
<P>The Midi Kit consists of the midi_server and two shared libraries,
libmidi2.so and libmidi.so. The latter is the "old" pre-R5 Midi Kit and has
been re-implemented using the facilities from libmidi2, which makes it fully
compatible with the new kit. This document describes the design and
implementation of the OpenBeOS midi_server and libmidi2.so.</P>
<P>The midi_server has two jobs: it keeps track of the endpoints that the
client apps have created, and it publishes endpoints for the devices from
/dev/midi. (This last task could have been done by any other app, but it was
just as convenient to make the midi_server do that.) The libmidi2.so library
also has two jobs: it assists the midi_server with the housekeeping stuff, and
it allows endpoints to send and receive MIDI events. (That's right, the
midi_server has nothing to do with the actual MIDI data.)</P>
<HR SIZE="1">
<H2>Ooh, pictures</H2>
<P>The following image shows the center of Midi Kit activity, the midi_server,
and its data structures:</P>
<BLOCKQUOTE><IMG ALT="" SRC="midi_server.png"></BLOCKQUOTE>
<P>And here is the picture for libmidi2.so:</P>
<BLOCKQUOTE><IMG ALT="" SRC="libmidi2.png"></BLOCKQUOTE>
<P>Note that these diagrams give only a conceptual overview of who is
responsible for which bits of data. The actual implementation details of the
kit may differ.</P>
<HR SIZE="1">
<H2>Housekeeping</H2>
<UL>
<LI><P>The design for our implementation of the midi2 "housekeeping" protocol
roughly follows <A HREF="oldprotocol.html">what Be did</A>, although there are
some differences. In Be's implementation, the BMidiRosters only have
BMidiEndpoints for remote endpoints if they are registered. In our
implementation, the BMidiRosters have BMidiEndpoint objects for <I>all</I>
endpoints, including remote endpoints that aren't published at all. If there
are many unpublished endpoints in the system, our approach is less optimal.
However, it made the implementation of the Midi Kit much easier ;-)</P></LI>
<LI><P>Be's libmidi2.so exports the symbols "midi_debug_level" and
"midi_dispatcher_priority", both int32's. Our libmidi2 does not use either of
these. But even though these symbols are not present in the headers, some apps
may use them nonetheless. That's why our libmidi2 exports those symbols as
well.</P></LI>
<LI><P>The name of the message fields in Be's implementation of the protocol
had the "be:" prefix. Our fields have a "midi:" prefix instead. Except for the
fields in the B_MIDI_EVENT notification messages, because that would break
compatibility with existing apps.</P></LI>
</UL>
<H3>Initialization</H3>
<UL>
<LI><P>The first time an app uses a midi2 class, the BMidiRoster::MidiRoster()
method sends an 'Mapp' message to the midi_server, and blocks (on a semaphore).
This message includes a messenger to the app's BMidiRosterLooper object. The
server adds the app to its list of registered apps. Then the server
asynchronously sends back a series of 'mNEW' message notifications for all
endpoints on the roster, and 'mCON' messages for all existing connections. The
BMidiRosterLooper creates BMidiEndpoint objects for these endpoints and adds
them to its local roster; if the app is watching, it also sends out
corresponding B_MIDI_EVENT notifications. Finally, the midi_server sends an
'mAPP' message to notify the app that it has been successfully registered. Upon
receipt, BMidiRoster::MidiRoster() unblocks and returns control to the client
code. This handshake is the only asynchronous message exchange; all the other
requests have a synchronous reply.</P></LI>
<LI><P>If the server detects an error during any of this (incorrect message
format, delivery failure, etc.) it simply ignores the request and does not try
to send anything back to the client (which is most likely impossible anyway).
If the app detects an error (server sends back meaningless info, cannot connect
to server), it pretends that everything is hunkey dorey. (The API has no way of
letting the client know that the initialization succeeded.) Next time the app
tries something, the server either still does not respond, or it ignores the
request (because this app isn't properly registered). However, if the app does
not receive the 'mAPP' message, it will not unblock, and remains frozen for all
eternity.</P></LI>
<LI><P>BMidiRoster's MidiRoster() method creates the one and only BMidiRoster
instance on the heap the first time it is called. This instance is
automatically destroyed when the app quits.</P></LI>
</UL>
<H3>Error handling</H3>
<UL>
<LI><P>If some error occurs, then the reply message is only guaranteed to
contain the "midi:result" field with some non- zero error code. libmidi2 can
only assume that the reply contains other data on success (i.e. when
"midi:result" is B_OK).</P></LI>
<LI><P>The timeout for delivering and responding to a message is about 2
seconds. If the client receives no reply within that time, it assumes the
request failed. If the server cannot deliver a message within 2 seconds, it
assumes the client is dead and removes it (and its endpoints) from the roster.
Of course, these assumptions may be false. If the client wasn't dead and tries
to send another request to the server, then the server will now ignore it,
since the client app is no longer registered.</P></LI>
<LI><P>Because we work with timeouts, we must be careful to avoid
misunderstandings between the midi_server and the client app. Both sides must
recognize the timeout, so they both can ignore the operation. If, however, the
server thinks that everything went okay, but the client flags an error, then
the server and the client will have two different ideas of the current state of
the roster. Of course, those situations must be avoided.</P></LI>
<LI><P>Although apps register themselves with the midi_server, there is no
corresponding "unregister" message. The only way the server recognizes that an
app and its endpoints are no longer available is when it fails to deliver a
message to that app. In that case, we remove the app and all its endpoints from
the roster. To do this, the server sends "purge endpoint" messages to itself
for all of the app's endpoints. This means we don't immediately throw the app
away, but we schedule that for some time in the future. That makes the whole
event handling mechanism much cleaner. There is no reply to the purge request.
(Actually, we <I>do</I> immediately throw away the app_t object, since that
doesn't really interfere with anything.) (If there are other events pending in
the queue which also cause notifications, then the server may send multiple
purge messages for the same endpoints. That's no biggie, because a purge
message will be ignored if its endpoint no longer exists.)</P></LI>
<LI><P>As mentioned above, the midi_server ignores messages that do not come
from a registered app, although it does send back an error reply. In the case
of the "purge endpoint" message, the server makes sure the message was local
(i.e. sent by the midi_server itself).</P></LI>
<LI><P>Note: BMessage's SendReply() apparently succeeds even if you kill the
app that the reply is intended for. This is rather strange, and it means that
you can't test delivery error handling for replies by killing the app. (You
<I>can</I> kill the app for testing the error handling on notifications,
however.)</P></LI>
</UL>
<H3>Creating and deleting endpoints</H3>
<UL>
<LI><P>When client code creates a new BMidiLocalProducer or BMidiLocalConsumer
endpoint, we send an 'Mnew' message to the server. Unlike Be's implementation,
the "name" field is always present, even if the name is empty. After adding the
endpoint to the roster, the server sends 'mNEW' notifications to all other
applications. Upon receipt of this notification, the BMidiRosterLoopers of
these apps create a new BMidiEndpoint for the endpoint and add it to their
internal list of endpoints. The app that made the request receives a reply with
a single "midi:result" field.</P></LI>
<LI><P>When you "new" an endpoint, its refcount is 1, even if the creation
failed. (For example, if the midi_server does not run.) When you Acquire(), the
refcount is bumped. When you Release(), it is decremented. When refcount drops
to 0, the endpoint object "deletes" itself. (So client code should never use an
endpoint after having Release()'d it, because the object may have just been
killed.) When creation succeeds, IsValid() returns true and ID() returns a
valid ID (> 0). Upon failure, IsValid() is false and ID() returns 0.</P></LI>
<LI><P>After the last Release() of a local endpoint, we send 'Mdel' to let the
midi_server know the endpoint is now deleted. We don't expect a reply back. If
something goes wrong, the endpoint is deleted regardless. We do not send
separate "unregistered" notifications, because deleting an endpoint implies
that it is removed from the roster. For the same reason, we also don't send
separate "disconnected" notifications.</P></LI>
<LI><P>The 'mDEL' notification triggers a BMidiRosterLooper to remove the
corresponding BMidiEndpoint from its internal list. This object is always a
proxy for a remote endpoint. The remote endpoint is gone, but whether we can
also delete the proxy depends on its reference count. If no one is still using
the object, its refcount is zero, and we can safely delete the object.
Otherwise, we must defer destruction until the client Release()'s the
object.</P></LI>
<LI><P>If you "delete" an endpoint, your app drops into the debugger.</P></LI>
<LI><P>If you Release() an endpoint too many times, your app <I>could</I> drop
into the debugger. It might also crash, because you are now using a dead
object. It depends on whether the memory that was previously occupied by your
endpoint object was overwritten in the mean time.</P></LI>
<LI><P>You are allowed to pass NULL into the constructors of BMidiLocalConsumer
and BMidiLocalProducer, in which case the endpoint's name is simply an empty
string.</P></LI>
</UL>
<H3>Changing endpoint attributes</H3>
<UL>
<LI><P>An endpoint can be "invalid". In the case of a proxy this means that the
remote endpoint is unregistered or even deleted. Local endpoints can only be
invalid if something went wrong during their creation (no connection to server,
for example). You can get the attributes of invalid objects, but you cannot set
them. Any attempts to do so will return an error code.</P></LI>
<LI><P>For changing the name, latency, or properties of an endpoint, libmidi2
sends an 'Mchg' message with the fields that should be changed, "midi:name",
"midi:latency", or "midi:properties". Registering or unregistering an endpoint
also sends such an 'Mchg' message, because we consider the "registered" state
also an attribute, in "midi:registered". The message obviously also includes
the ID of the endpoint in question. Properties are sent using a different
message, because the properties are not stored inside the
BMidiEndpoints.</P></LI>
<LI><P>After handling the 'Mchg' request, the midi_server broadcasts an 'mCHG'
notification to all the other apps. This message has the same contents as the
original request.</P></LI>
<LI><P>If the 'Mchg' message contains an invalid "midi:id" (i.e. no such
endpoint exists or it does not belong to the app that sent the request), the
midi_server returns an error code, and it does not notify the other
apps.</P></LI>
<LI><P>If you try to Register() an endpoint that is already registered,
libmidi2 does not send a message to the midi_server but simply returns B_OK.
(Be's implementation <I>did</I> send a message, but our libmidi2 also keeps
track whether an endpoint is registered or not.) Although registering an
endpoint more than once doesn't make much sense, it is not considered an error.
Likewise for Unregister()ing an endpoint that is not registered.</P></LI>
<LI><P>If you try to Register() or Unregister() a remote endpoint, libmidi2
immediately returns an error code, and does not send a message to the server.
Likewise for a local endpoints that are invalid (i.e. whose IsValid() function
returns false).</P></LI>
<LI><P>BMidiRoster::Register() and Unregister() do the same thing as
BMidiEndpoint::Register() and Unregister(). If you pass NULL into these
functions, they return B_BAD_VALUE.</P></LI>
<LI><P>SetName() ignores NULL names. When you call it on a remote endpoint,
SetName() does nothing. SetName() does not send a message if the new name is
the same as the current name.</P></LI>
<LI><P>SetLatency() ignores negative values. SetLatency() does not send a
message if the new latency is the same as the current latency. (Since
SetLatency() lives in BMidiLocalConsumer, you can never use it on remote
endpoints.)</P></LI>
<LI><P>We store a copy of the endpoint properties in each BMidiEndpoint. The
properties of new endpoints are empty. GetProperties() copies this BMessage
into the client's BMessage. GetProperties() returns NULL if the message
parameter is NULL.</P></LI>
<LI><P>SetProperties() returns NULL if the message parameter is NULL. It
returns an error code if the endpoint is remote or invalid. SetProperties()
does <I>not</I> compare the contents of the new BMessage to the old, so it will
always send out the change request.</P></LI>
</UL>
<H3>Connections</H3>
<UL>
<LI><P>BMidiProducer::Connect() sends an 'Mcon' request to the midi_server.
This request contains the IDs of the producer and the consumer you want to
connect. The server sends back a reply with a result code. If it is possible to
make this connection, the server broadcasts an 'mCON' notification to all other
apps. In one of these apps the producer is local, so that app's libmidi2 calls
the BMidiLocalProducer::Connected() hook.</P></LI>
<LI><P>You are not allowed to connect the same producer and consumer more than
once. The midi_server checks for this. It also returns an error code if you try
to disconnect two endpoints that were not connected.</P></LI>
<LI><P>Disconnect() sends an 'Mdis' request to the server, which contains the
IDs of the producer and consumer that you want to disconnect. The server
replies with a result code. If the connection could be broken, it also sends an
'mDIS' notification to the other apps. libmidi2 calls the local producer's
BMidiLocalProducer::Disconnected() hook.</P></LI>
<LI><P>Connect() and Disconnect() immediately return an error code if you pass
a NULL argument, or if the producer or consumer is invalid.</P></LI>
<LI><P>When you Release() a local consumer that is connected, all apps will go
through their producers, and throw away this consumer from their connection
lists. If one of these producers is local, we call its Disconnected() hook. If
you release a local producer, this is not necessary.</P></LI>
</UL>
<H3>Watching</H3>
<UL>
<LI><P>When you call StartWatching(), the BMidiRosterLooper remembers the
BMessenger, and sends it B_MIDI_EVENT notifications for all registered remote
endpoints, and the current connections between them. It does not let you know
about local endpoints. When you call StartWatching() a second time with the
same BMessenger, you'll receive the whole bunch of notifications again.
StartWatching(NULL) is not allowed, and will be ignored (so it is not the same
as StopWatching()).</P></LI>
</UL>
<H3>Thread safety</H3>
<UL>
<LI><P>Within libmidi2 there are several possible race conditions, because we
are dealing with two threads: the one from BMidiRosterLooper and a thread from
the client app, most likely the BApplication's main thread. Both can access the
same data: BMidiEndpoint objects. To synchronize these threads, we lock the
BMidiRosterLooper, which is a normal BLooper. Anything happening in
BMidiRosterLooper's message handlers is safe, because BLoopers are
automatically locked when handling a message. Any other operations (which run
from a different thread) must first lock the looper if they access the list of
endpoints or certain BMidiEndpoint attributes (name, properties, etc).</P></LI>
<LI><P>What if you obtain a BMidiEndpoint object from FindEndpoint() and at the
same time the BMidiRosterLooper receives an 'mDEL' request to delete that
endpoint? FindEndpoint() locks the looper, and bumps the endpoint object before
giving it to you. Now the looper sees that the endpoint's refcount is larger
than 0, so it won't delete it (although it will remove the endpoint from its
internal list). What if you Acquire() or Release() a remote endpoint while it
is being deleted by the looper? That also won't happen, because if you have a
pointer to that endpoint, its refcount is at least 1 and the looper won't
delete it.</P></LI>
<LI><P>It is not safe to use a BMidiEndpoint and/or the BMidiRoster from more
than one client thread at a time; if you want to do that, you should
synchronize access to these objects yourself. The only exception is the Spray()
functions from BMidiLocalProducer, since most producers have a separate thread
to spray their MIDI events. This is fine, as long as that thread isn't used for
anything else, and it is the only one that does the spraying.</P></LI>
<LI><P>BMidiProducer objects keep a list of consumers they are connected to.
This list can be accessed by several threads at a time: the client's thread,
the BMidiRosterLooper thread, and possibly a separate thread that is spraying
MIDI events. We could have locked the producer using BMidiRosterLooper's lock,
but that would freeze everything else while the producer is spraying events.
Conversely, it would freeze all producers while the looper is talking to the
midi_server. To lock with a finer granularity, each BMidiProducer has its own
BLocker, which is used only to lock the list of connected consumers.</P></LI>
</UL>
<H3>Misc remarks</H3>
<UL>
<LI><P>BMidiEndpoint keeps track of its local/remote state with an "isLocal"
variable, and whether it is a producer/consumer with "isConsumer". It also has
an "isRegistered" field to remember whether this endpoint is registered or not.
Why not lump all these different states together into one "flags" bitmask? The
reason is that isLocal only makes sense to this application, not to others.
Also, the values of isLocal and isConsumer never change, but isRegistered does.
It made more sense (and clearer code) to separate them out. Finally,
isRegistered does not need to be protected by a lock, even though it can be
accessed by multiple threads at a time. Reading and writing a bool is atomic,
so this can't get messed up.</P></LI>
</UL>
<H3>The messages</H3>
<BLOCKQUOTE><PRE>
Message: Mapp (MSG_REGISTER_APP)
BMessenger midi:messenger
Reply:
(no reply)
Message: mAPP (MSG_APP_REGISTERED)
(no fields)
Message: Mnew (MSG_CREATE_ENDPOINT)
bool midi:consumer
bool midi:registered
char[] midi:name
BMessage midi:properties
int32 midi:port (consumer only)
int64 midi:latency (consumer only)
Reply:
int32 midi:result
int32 midi:id
Message: mNEW (MSG_ENPOINT_CREATED)
int32 midi:id
bool midi:consumer
bool midi:registered
char[] midi:name
BMessage midi:properties
int32 midi:port (consumer only)
int64 midi:latency (consumer only)
Message: Mdel (MSG_DELETE_ENDPOINT)
int32 midi:id
Reply:
(no reply)
Message: Mdie (MSG_PURGE_ENDPOINT)
int32 midi:id
Reply:
(no reply)
Message: mDEL (MSG_ENDPOINT_DELETED)
int32 midi:id
Message: Mchg (MSG_CHANGE_ENDPOINT)
int32 midi:id
int32 midi:registered (optional)
char[] midi:name (optional)
int64 midi:latency (optional)
BMessage midi:properties (optional)
Reply:
int32 midi:result
Message: mCHG (MSG_ENDPOINT_CHANGED)
int32 midi:id
int32 midi:registered (optional)
char[] midi:name (optional)
int64 midi:latency (optional)
BMessage midi:properties (optional)
</PRE></BLOCKQUOTE>
<HR SIZE="1">
<H2>MIDI events</H2>
<UL>
<LI><P>MIDI events are always sent from a BMidiLocalProducer to a
BMidiLocalConsumer. Proxy endpoint objects have nothing to do with this. During
its construction, the local consumer creates a kernel port. The ID of this port
is published, so everyone knows what it is. When a producer sprays an event, it
creates a message that it sends to the ports of all connected consumers.</P></LI>
<LI><P>This means that the Midi Kit considers MIDI messages as discrete events.
Hardware drivers chop the stream of incoming MIDI data into separate events
that they send out to one or more kernel ports. Consumers never have to worry
about parsing a stream of MIDI data, just about handling a bunch of separate
events.</P></LI>
<LI><P>Each BMidiLocalConsumer has a (realtime priority) thread associated with
it that waits for data to arrive at the port. As soon as a new MIDI message
comes in, the thread examines it and feeds it to the Data() hook. The Data()
hook ignores the message if the "atomic" flag is false, or passes it on to one
of the other hook functions otherwise. Incoming messages are also ignored if
their contents are not valid; for example, if they have too few or too many
bytes for a certain type of MIDI event.</P></LI>
<LI><P>Unlike the consumer, BMidiLocalProducer has no thread of its own. As a
result, spraying MIDI events always happens in the thread of the caller.
Because the consumer port's queue is only 1 message deep, spray functions will
block if the consumer thread is already busy handling another MIDI event. (For
this reason, the Midi Kit does not support interleaving of real time messages
with lower priority messages such as sysex dumps, except at the driver
level.)</P></LI>
<LI><P>The producer does not just send MIDI event data to the consumer, it also
sends a 20-byte header describing the event. The total message looks like
this:</P>
<BLOCKQUOTE><TABLE BORDER="1">
<TR><TD>4 bytes</TD><TD>ID of the producer</TD></TR>
<TR><TD>4 bytes</TD><TD>ID of the consumer</TD></TR>
<TR><TD>8 bytes</TD><TD>performance time</TD></TR>
<TR><TD>1 byte</TD><TD>atomic (1 = true, 0 = false)</TD></TR>
<TR><TD>3 bytes</TD><TD>padding (0)</TD></TR>
<TR><TD>x bytes</TD><TD>MIDI event data</TD></TR>
</TABLE></BLOCKQUOTE></LI>
<LI><P>In the case of a sysex event, the SystemExclusive() hook is only called
if the first byte of the message is 0xF0. The sysex end marker (0xF7) is
optional; only if the last byte is 0xF7 we strip it off. This is unlike Be's
implementation, which all always strips the last byte even when it is not 0xF7.
According to the MIDI spec, 0xF7 is not really required; any non-realtime
status byte ends a sysex message.</P></LI>
<LI><P>SprayTempoChange() sends 0xFF5103tttttt, where tttttt is 60,000,000/bpm.
This feature is not really part of the MIDI spec, but an extension from the SMF
(Standard MIDI File) format. Of course, the TempoChange() hook is called in
response to this message.</P></LI>
<LI><P>The MIDI spec allows for a number of shortcuts. A Note On event with
velocity 0 is supposed to be interpreted as a Note Off, for example. The Midi
Kit does not concern itself with these shortcuts. In this case, it still calls
the NoteOn() hook with a velocity parameter of 0.</P></LI>
<LI><P>The purpose of BMidiLocalConsumer's AllNotesOff() function is not
entirely clear. All Notes Off is a so-called "channel mode message" and is
generated by doing a SprayControlChange(channel, B_ALL_NOTES_OFF, 0). BMidi has
an AllNotesOff() function that sends an All Notes Off event to all channels,
and possible Note Off events to all keys on all channels as well. I suspect
someone at Be was confused by AllNotesOff() being declared "virtual", and
thought it was a hook function. Only that would explain it being in
BMidiLocalConsumer as opposed to BMidiLocalProducer, where it would have made
sense. The disassembly for Be's libmidi2.so shows that AllNotesOff() is empty,
so to cut a long story short, our AllNotesOff() simply does nothing and is
never invoked either.</P></LI>
<LI><P>There are several types of System Common events, each of which takes a
different number of data bytes (0, 1, or 2). But SpraySystemCommon() and the
SystemCommon() hook are always given 2 data parameters. The Midi Kit simply
ignores the extra data bytes; in fact, in our implementation it doesn't even
send them. (The Be implementation always sends 2 data bytes, but that will
confuse the Midi Kit if the client does a SprayData() of a common event
instead. In our case, that will still invoke the SystemCommon() hook, because
we are not as easily fooled.)</P></LI>
<LI><P>Handling of timeouts is fairly straightforward. When reading from the
port, we specify an absolute timeout. When the port function returns with a
B_TIMED_OUT error code, we call the Timeout() hook. Then we reset the timeout
value to -1, which means that timeouts are disabled (until the client calls
SetTimeout() again). This design means that a call to SetTimeout() only takes
effect the next time we read from the port, i.e. after at least one new MIDI
event is received (or the previous timeout is triggered). Even though
BMidiLocalConsumer's timeout and timeoutData values are accessed by two
different threads, I did not bother to protect this. Both values are int32's
and reading/writing them should be an atomic operation on most processors
anyway.</P></LI>
</UL>
</BODY>
</HTML>
+537
View File
@@ -0,0 +1,537 @@
Midi Kit design
===============
The Midi Kit consists of the midi_server and two shared libraries,
libmidi2.so and libmidi.so. The latter is the "old" pre-R5 Midi Kit and
has been re-implemented using the facilities from libmidi2, which makes
it fully compatible with the new kit. This document describes the design
and implementation of the OpenBeOS midi_server and libmidi2.so.
The midi_server has two jobs: it keeps track of the endpoints that the
client apps have created, and it publishes endpoints for the devices
from /dev/midi. (This last task could have been done by any other app,
but it was just as convenient to make the midi_server do that.) The
libmidi2.so library also has two jobs: it assists the midi_server with
the housekeeping stuff, and it allows endpoints to send and receive MIDI
events. (That's right, the midi_server has nothing to do with the actual
MIDI data.)
--------------
Ooh, pictures
-------------
The following image shows the center of Midi Kit activity, the
midi_server, and its data structures:
|image0|
And here is the picture for libmidi2.so:
|image1|
Note that these diagrams give only a conceptual overview of who is
responsible for which bits of data. The actual implementation details of
the kit may differ.
--------------
Housekeeping
------------
- The design for our implementation of the midi2 "housekeeping"
protocol roughly follows `what Be did <oldprotocol.html>`__, although
there are some differences. In Be's implementation, the BMidiRosters
only have BMidiEndpoints for remote endpoints if they are registered.
In our implementation, the BMidiRosters have BMidiEndpoint objects
for *all* endpoints, including remote endpoints that aren't published
at all. If there are many unpublished endpoints in the system, our
approach is less optimal. However, it made the implementation of the
Midi Kit much easier ;-)
- Be's libmidi2.so exports the symbols "midi_debug_level" and
"midi_dispatcher_priority", both int32's. Our libmidi2 does not use
either of these. But even though these symbols are not present in the
headers, some apps may use them nonetheless. That's why our libmidi2
exports those symbols as well.
- The name of the message fields in Be's implementation of the protocol
had the "be:" prefix. Our fields have a "midi:" prefix instead.
Except for the fields in the B_MIDI_EVENT notification messages,
because that would break compatibility with existing apps.
Initialization
~~~~~~~~~~~~~~
- The first time an app uses a midi2 class, the
BMidiRoster::MidiRoster() method sends an 'Mapp' message to the
midi_server, and blocks (on a semaphore). This message includes a
messenger to the app's BMidiRosterLooper object. The server adds the
app to its list of registered apps. Then the server asynchronously
sends back a series of 'mNEW' message notifications for all endpoints
on the roster, and 'mCON' messages for all existing connections. The
BMidiRosterLooper creates BMidiEndpoint objects for these endpoints
and adds them to its local roster; if the app is watching, it also
sends out corresponding B_MIDI_EVENT notifications. Finally, the
midi_server sends an 'mAPP' message to notify the app that it has
been successfully registered. Upon receipt, BMidiRoster::MidiRoster()
unblocks and returns control to the client code. This handshake is
the only asynchronous message exchange; all the other requests have a
synchronous reply.
- If the server detects an error during any of this (incorrect message
format, delivery failure, etc.) it simply ignores the request and
does not try to send anything back to the client (which is most
likely impossible anyway). If the app detects an error (server sends
back meaningless info, cannot connect to server), it pretends that
everything is hunkey dorey. (The API has no way of letting the client
know that the initialization succeeded.) Next time the app tries
something, the server either still does not respond, or it ignores
the request (because this app isn't properly registered). However, if
the app does not receive the 'mAPP' message, it will not unblock, and
remains frozen for all eternity.
- BMidiRoster's MidiRoster() method creates the one and only
BMidiRoster instance on the heap the first time it is called. This
instance is automatically destroyed when the app quits.
Error handling
~~~~~~~~~~~~~~
- If some error occurs, then the reply message is only guaranteed to
contain the "midi:result" field with some non- zero error code.
libmidi2 can only assume that the reply contains other data on
success (i.e. when "midi:result" is B_OK).
- The timeout for delivering and responding to a message is about 2
seconds. If the client receives no reply within that time, it assumes
the request failed. If the server cannot deliver a message within 2
seconds, it assumes the client is dead and removes it (and its
endpoints) from the roster. Of course, these assumptions may be
false. If the client wasn't dead and tries to send another request to
the server, then the server will now ignore it, since the client app
is no longer registered.
- Because we work with timeouts, we must be careful to avoid
misunderstandings between the midi_server and the client app. Both
sides must recognize the timeout, so they both can ignore the
operation. If, however, the server thinks that everything went okay,
but the client flags an error, then the server and the client will
have two different ideas of the current state of the roster. Of
course, those situations must be avoided.
- Although apps register themselves with the midi_server, there is no
corresponding "unregister" message. The only way the server
recognizes that an app and its endpoints are no longer available is
when it fails to deliver a message to that app. In that case, we
remove the app and all its endpoints from the roster. To do this, the
server sends "purge endpoint" messages to itself for all of the app's
endpoints. This means we don't immediately throw the app away, but we
schedule that for some time in the future. That makes the whole event
handling mechanism much cleaner. There is no reply to the purge
request. (Actually, we *do* immediately throw away the app_t object,
since that doesn't really interfere with anything.) (If there are
other events pending in the queue which also cause notifications,
then the server may send multiple purge messages for the same
endpoints. That's no biggie, because a purge message will be ignored
if its endpoint no longer exists.)
- As mentioned above, the midi_server ignores messages that do not come
from a registered app, although it does send back an error reply. In
the case of the "purge endpoint" message, the server makes sure the
message was local (i.e. sent by the midi_server itself).
- Note: BMessage's SendReply() apparently succeeds even if you kill the
app that the reply is intended for. This is rather strange, and it
means that you can't test delivery error handling for replies by
killing the app. (You *can* kill the app for testing the error
handling on notifications, however.)
Creating and deleting endpoints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- When client code creates a new BMidiLocalProducer or
BMidiLocalConsumer endpoint, we send an 'Mnew' message to the server.
Unlike Be's implementation, the "name" field is always present, even
if the name is empty. After adding the endpoint to the roster, the
server sends 'mNEW' notifications to all other applications. Upon
receipt of this notification, the BMidiRosterLoopers of these apps
create a new BMidiEndpoint for the endpoint and add it to their
internal list of endpoints. The app that made the request receives a
reply with a single "midi:result" field.
- When you "new" an endpoint, its refcount is 1, even if the creation
failed. (For example, if the midi_server does not run.) When you
Acquire(), the refcount is bumped. When you Release(), it is
decremented. When refcount drops to 0, the endpoint object "deletes"
itself. (So client code should never use an endpoint after having
Release()'d it, because the object may have just been killed.) When
creation succeeds, IsValid() returns true and ID() returns a valid ID
(> 0). Upon failure, IsValid() is false and ID() returns 0.
- After the last Release() of a local endpoint, we send 'Mdel' to let
the midi_server know the endpoint is now deleted. We don't expect a
reply back. If something goes wrong, the endpoint is deleted
regardless. We do not send separate "unregistered" notifications,
because deleting an endpoint implies that it is removed from the
roster. For the same reason, we also don't send separate
"disconnected" notifications.
- The 'mDEL' notification triggers a BMidiRosterLooper to remove the
corresponding BMidiEndpoint from its internal list. This object is
always a proxy for a remote endpoint. The remote endpoint is gone,
but whether we can also delete the proxy depends on its reference
count. If no one is still using the object, its refcount is zero, and
we can safely delete the object. Otherwise, we must defer destruction
until the client Release()'s the object.
- If you "delete" an endpoint, your app drops into the debugger.
- If you Release() an endpoint too many times, your app *could* drop
into the debugger. It might also crash, because you are now using a
dead object. It depends on whether the memory that was previously
occupied by your endpoint object was overwritten in the mean time.
- You are allowed to pass NULL into the constructors of
BMidiLocalConsumer and BMidiLocalProducer, in which case the
endpoint's name is simply an empty string.
Changing endpoint attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- An endpoint can be "invalid". In the case of a proxy this means that
the remote endpoint is unregistered or even deleted. Local endpoints
can only be invalid if something went wrong during their creation (no
connection to server, for example). You can get the attributes of
invalid objects, but you cannot set them. Any attempts to do so will
return an error code.
- For changing the name, latency, or properties of an endpoint,
libmidi2 sends an 'Mchg' message with the fields that should be
changed, "midi:name", "midi:latency", or "midi:properties".
Registering or unregistering an endpoint also sends such an 'Mchg'
message, because we consider the "registered" state also an
attribute, in "midi:registered". The message obviously also includes
the ID of the endpoint in question. Properties are sent using a
different message, because the properties are not stored inside the
BMidiEndpoints.
- After handling the 'Mchg' request, the midi_server broadcasts an
'mCHG' notification to all the other apps. This message has the same
contents as the original request.
- If the 'Mchg' message contains an invalid "midi:id" (i.e. no such
endpoint exists or it does not belong to the app that sent the
request), the midi_server returns an error code, and it does not
notify the other apps.
- If you try to Register() an endpoint that is already registered,
libmidi2 does not send a message to the midi_server but simply
returns B_OK. (Be's implementation *did* send a message, but our
libmidi2 also keeps track whether an endpoint is registered or not.)
Although registering an endpoint more than once doesn't make much
sense, it is not considered an error. Likewise for Unregister()ing an
endpoint that is not registered.
- If you try to Register() or Unregister() a remote endpoint, libmidi2
immediately returns an error code, and does not send a message to the
server. Likewise for a local endpoints that are invalid (i.e. whose
IsValid() function returns false).
- BMidiRoster::Register() and Unregister() do the same thing as
BMidiEndpoint::Register() and Unregister(). If you pass NULL into
these functions, they return B_BAD_VALUE.
- SetName() ignores NULL names. When you call it on a remote endpoint,
SetName() does nothing. SetName() does not send a message if the new
name is the same as the current name.
- SetLatency() ignores negative values. SetLatency() does not send a
message if the new latency is the same as the current latency. (Since
SetLatency() lives in BMidiLocalConsumer, you can never use it on
remote endpoints.)
- We store a copy of the endpoint properties in each BMidiEndpoint. The
properties of new endpoints are empty. GetProperties() copies this
BMessage into the client's BMessage. GetProperties() returns NULL if
the message parameter is NULL.
- SetProperties() returns NULL if the message parameter is NULL. It
returns an error code if the endpoint is remote or invalid.
SetProperties() does *not* compare the contents of the new BMessage
to the old, so it will always send out the change request.
Connections
~~~~~~~~~~~
- BMidiProducer::Connect() sends an 'Mcon' request to the midi_server.
This request contains the IDs of the producer and the consumer you
want to connect. The server sends back a reply with a result code. If
it is possible to make this connection, the server broadcasts an
'mCON' notification to all other apps. In one of these apps the
producer is local, so that app's libmidi2 calls the
BMidiLocalProducer::Connected() hook.
- You are not allowed to connect the same producer and consumer more
than once. The midi_server checks for this. It also returns an error
code if you try to disconnect two endpoints that were not connected.
- Disconnect() sends an 'Mdis' request to the server, which contains
the IDs of the producer and consumer that you want to disconnect. The
server replies with a result code. If the connection could be broken,
it also sends an 'mDIS' notification to the other apps. libmidi2
calls the local producer's BMidiLocalProducer::Disconnected() hook.
- Connect() and Disconnect() immediately return an error code if you
pass a NULL argument, or if the producer or consumer is invalid.
- When you Release() a local consumer that is connected, all apps will
go through their producers, and throw away this consumer from their
connection lists. If one of these producers is local, we call its
Disconnected() hook. If you release a local producer, this is not
necessary.
Watching
~~~~~~~~
- When you call StartWatching(), the BMidiRosterLooper remembers the
BMessenger, and sends it B_MIDI_EVENT notifications for all
registered remote endpoints, and the current connections between
them. It does not let you know about local endpoints. When you call
StartWatching() a second time with the same BMessenger, you'll
receive the whole bunch of notifications again. StartWatching(NULL)
is not allowed, and will be ignored (so it is not the same as
StopWatching()).
Thread safety
~~~~~~~~~~~~~
- Within libmidi2 there are several possible race conditions, because
we are dealing with two threads: the one from BMidiRosterLooper and a
thread from the client app, most likely the BApplication's main
thread. Both can access the same data: BMidiEndpoint objects. To
synchronize these threads, we lock the BMidiRosterLooper, which is a
normal BLooper. Anything happening in BMidiRosterLooper's message
handlers is safe, because BLoopers are automatically locked when
handling a message. Any other operations (which run from a different
thread) must first lock the looper if they access the list of
endpoints or certain BMidiEndpoint attributes (name, properties,
etc).
- What if you obtain a BMidiEndpoint object from FindEndpoint() and at
the same time the BMidiRosterLooper receives an 'mDEL' request to
delete that endpoint? FindEndpoint() locks the looper, and bumps the
endpoint object before giving it to you. Now the looper sees that the
endpoint's refcount is larger than 0, so it won't delete it (although
it will remove the endpoint from its internal list). What if you
Acquire() or Release() a remote endpoint while it is being deleted by
the looper? That also won't happen, because if you have a pointer to
that endpoint, its refcount is at least 1 and the looper won't delete
it.
- It is not safe to use a BMidiEndpoint and/or the BMidiRoster from
more than one client thread at a time; if you want to do that, you
should synchronize access to these objects yourself. The only
exception is the Spray() functions from BMidiLocalProducer, since
most producers have a separate thread to spray their MIDI events.
This is fine, as long as that thread isn't used for anything else,
and it is the only one that does the spraying.
- BMidiProducer objects keep a list of consumers they are connected to.
This list can be accessed by several threads at a time: the client's
thread, the BMidiRosterLooper thread, and possibly a separate thread
that is spraying MIDI events. We could have locked the producer using
BMidiRosterLooper's lock, but that would freeze everything else while
the producer is spraying events. Conversely, it would freeze all
producers while the looper is talking to the midi_server. To lock
with a finer granularity, each BMidiProducer has its own BLocker,
which is used only to lock the list of connected consumers.
Misc remarks
~~~~~~~~~~~~
- BMidiEndpoint keeps track of its local/remote state with an "isLocal"
variable, and whether it is a producer/consumer with "isConsumer". It
also has an "isRegistered" field to remember whether this endpoint is
registered or not. Why not lump all these different states together
into one "flags" bitmask? The reason is that isLocal only makes sense
to this application, not to others. Also, the values of isLocal and
isConsumer never change, but isRegistered does. It made more sense
(and clearer code) to separate them out. Finally, isRegistered does
not need to be protected by a lock, even though it can be accessed by
multiple threads at a time. Reading and writing a bool is atomic, so
this can't get messed up.
The messages
~~~~~~~~~~~~
::
Message: Mapp (MSG_REGISTER_APP)
BMessenger midi:messenger
Reply:
(no reply)
Message: mAPP (MSG_APP_REGISTERED)
(no fields)
Message: Mnew (MSG_CREATE_ENDPOINT)
bool midi:consumer
bool midi:registered
char[] midi:name
BMessage midi:properties
int32 midi:port (consumer only)
int64 midi:latency (consumer only)
Reply:
int32 midi:result
int32 midi:id
Message: mNEW (MSG_ENPOINT_CREATED)
int32 midi:id
bool midi:consumer
bool midi:registered
char[] midi:name
BMessage midi:properties
int32 midi:port (consumer only)
int64 midi:latency (consumer only)
Message: Mdel (MSG_DELETE_ENDPOINT)
int32 midi:id
Reply:
(no reply)
Message: Mdie (MSG_PURGE_ENDPOINT)
int32 midi:id
Reply:
(no reply)
Message: mDEL (MSG_ENDPOINT_DELETED)
int32 midi:id
Message: Mchg (MSG_CHANGE_ENDPOINT)
int32 midi:id
int32 midi:registered (optional)
char[] midi:name (optional)
int64 midi:latency (optional)
BMessage midi:properties (optional)
Reply:
int32 midi:result
Message: mCHG (MSG_ENDPOINT_CHANGED)
int32 midi:id
int32 midi:registered (optional)
char[] midi:name (optional)
int64 midi:latency (optional)
BMessage midi:properties (optional)
--------------
MIDI events
-----------
- MIDI events are always sent from a BMidiLocalProducer to a
BMidiLocalConsumer. Proxy endpoint objects have nothing to do with
this. During its construction, the local consumer creates a kernel
port. The ID of this port is published, so everyone knows what it is.
When a producer sprays an event, it creates a message that it sends
to the ports of all connected consumers.
- This means that the Midi Kit considers MIDI messages as discrete
events. Hardware drivers chop the stream of incoming MIDI data into
separate events that they send out to one or more kernel ports.
Consumers never have to worry about parsing a stream of MIDI data,
just about handling a bunch of separate events.
- Each BMidiLocalConsumer has a (realtime priority) thread associated
with it that waits for data to arrive at the port. As soon as a new
MIDI message comes in, the thread examines it and feeds it to the
Data() hook. The Data() hook ignores the message if the "atomic" flag
is false, or passes it on to one of the other hook functions
otherwise. Incoming messages are also ignored if their contents are
not valid; for example, if they have too few or too many bytes for a
certain type of MIDI event.
- Unlike the consumer, BMidiLocalProducer has no thread of its own. As
a result, spraying MIDI events always happens in the thread of the
caller. Because the consumer port's queue is only 1 message deep,
spray functions will block if the consumer thread is already busy
handling another MIDI event. (For this reason, the Midi Kit does not
support interleaving of real time messages with lower priority
messages such as sysex dumps, except at the driver level.)
- The producer does not just send MIDI event data to the consumer, it
also sends a 20-byte header describing the event. The total message
looks like this:
+---------+------------------------------+
| 4 bytes | ID of the producer |
+---------+------------------------------+
| 4 bytes | ID of the consumer |
+---------+------------------------------+
| 8 bytes | performance time |
+---------+------------------------------+
| 1 byte | atomic (1 = true, 0 = false) |
+---------+------------------------------+
| 3 bytes | padding (0) |
+---------+------------------------------+
| x bytes | MIDI event data |
+---------+------------------------------+
- In the case of a sysex event, the SystemExclusive() hook is only
called if the first byte of the message is 0xF0. The sysex end marker
(0xF7) is optional; only if the last byte is 0xF7 we strip it off.
This is unlike Be's implementation, which all always strips the last
byte even when it is not 0xF7. According to the MIDI spec, 0xF7 is
not really required; any non-realtime status byte ends a sysex
message.
- SprayTempoChange() sends 0xFF5103tttttt, where tttttt is
60,000,000/bpm. This feature is not really part of the MIDI spec, but
an extension from the SMF (Standard MIDI File) format. Of course, the
TempoChange() hook is called in response to this message.
- The MIDI spec allows for a number of shortcuts. A Note On event with
velocity 0 is supposed to be interpreted as a Note Off, for example.
The Midi Kit does not concern itself with these shortcuts. In this
case, it still calls the NoteOn() hook with a velocity parameter of
0.
- The purpose of BMidiLocalConsumer's AllNotesOff() function is not
entirely clear. All Notes Off is a so-called "channel mode message"
and is generated by doing a SprayControlChange(channel,
B_ALL_NOTES_OFF, 0). BMidi has an AllNotesOff() function that sends
an All Notes Off event to all channels, and possible Note Off events
to all keys on all channels as well. I suspect someone at Be was
confused by AllNotesOff() being declared "virtual", and thought it
was a hook function. Only that would explain it being in
BMidiLocalConsumer as opposed to BMidiLocalProducer, where it would
have made sense. The disassembly for Be's libmidi2.so shows that
AllNotesOff() is empty, so to cut a long story short, our
AllNotesOff() simply does nothing and is never invoked either.
- There are several types of System Common events, each of which takes
a different number of data bytes (0, 1, or 2). But
SpraySystemCommon() and the SystemCommon() hook are always given 2
data parameters. The Midi Kit simply ignores the extra data bytes; in
fact, in our implementation it doesn't even send them. (The Be
implementation always sends 2 data bytes, but that will confuse the
Midi Kit if the client does a SprayData() of a common event instead.
In our case, that will still invoke the SystemCommon() hook, because
we are not as easily fooled.)
- Handling of timeouts is fairly straightforward. When reading from the
port, we specify an absolute timeout. When the port function returns
with a B_TIMED_OUT error code, we call the Timeout() hook. Then we
reset the timeout value to -1, which means that timeouts are disabled
(until the client calls SetTimeout() again). This design means that a
call to SetTimeout() only takes effect the next time we read from the
port, i.e. after at least one new MIDI event is received (or the
previous timeout is triggered). Even though BMidiLocalConsumer's
timeout and timeoutData values are accessed by two different threads,
I did not bother to protect this. Both values are int32's and
reading/writing them should be an atomic operation on most processors
anyway.
.. |image0| image:: midi_server.png
.. |image1| image:: libmidi2.png
+11
View File
@@ -0,0 +1,11 @@
The MIDI Kit
============
.. toctree::
/midi/design
/midi/midi1
/midi/oldprotocol
/midi/stuff
/midi/testing
/midi/todo
-25
View File
@@ -1,25 +0,0 @@
<HTML>
<BODY>
<H3>How libmidi1 works</H3>
<P>Midi1 is implemented on top of midi2, which means that libmidi.so depends on libmidi2.so to do the real work. BeOS versions earlier than R5 did not include a midi_server, because midi1 itself did not need it. (A server is only really useful if data must be shared between teams, something that midi1 did not allow.)</P>
<P>Midi2 is backwards compatible with midi1: The old libmidi.so still exists so that applications using the old API will run (providing binary compatibility). The old <CODE>BMidiPort</CODE> object is now a wrapper that uses the new <CODE>BMidiRoster</CODE> to allow connections to any published MIDI producer or consumer. Published MIDI objects are presented to the old MIDI apps as if they were physical MIDI ports.</P>
<P>Here is a very nice picture of how <CODE>BMidiPort</CODE> works:</P>
<P><IMG ALT="" SRC="midiport.png"></P>
<H3>The softsynth</H3>
<P>The General MIDI synthesizer is implemented in <CODE>BSoftSynth</CODE>. This is a private class (not usable outside the API). It is not a real <CODE>BMidiEndpoint</CODE>, so you will not see it appear on Patchbay. I did this for simplicity's sake, for backwards compatibility (this is how the R5 synth worked too), and because we will have to give the Midi Kit a complete makeover for R2 anyway.</P>
<P>The <CODE>BMidiSynth</CODE> and <CODE>BSynth</CODE> classes delegate most of the work to <CODE>BSoftSynth</CODE>. Not all of their methods are implemented, since some of them are very obscure. It would be a lot of work to figure out what they do, while it is likely that no applications use them anyway. However, <CODE>BMidiSynth</CODE> and <CODE>BSynth</CODE> should perform most common tasks without problems.</P>
<P><CODE>BSamples</CODE> doesn't do anything; its functions are mostly empty. In other words, with the OpenBeOS Midi Kit you cannot push waveform data into the output of the softsynth.</P>
<P>For simplicity's sake, <CODE>BMidiSynthFile</CODE> is implemented using <CODE>BMidiStore</CODE>, since the latter already knows how to parse Standard MIDI Files. Duplicating that functionality elsewhere would be pointless. However, this makes the behavior of our <CODE>BMidiSynthFile</CODE> a little different from what the BeBook says &mdash; as long as your applications are written properly, you shouldn't notice any differences.</P>
</BODY>
</HTML>
+50
View File
@@ -0,0 +1,50 @@
How libmidi1 works
~~~~~~~~~~~~~~~~~~
Midi1 is implemented on top of midi2, which means that libmidi.so
depends on libmidi2.so to do the real work. BeOS versions earlier than
R5 did not include a midi_server, because midi1 itself did not need it.
(A server is only really useful if data must be shared between teams,
something that midi1 did not allow.)
Midi2 is backwards compatible with midi1: The old libmidi.so still
exists so that applications using the old API will run (providing binary
compatibility). The old ``BMidiPort`` object is now a wrapper that uses
the new ``BMidiRoster`` to allow connections to any published MIDI
producer or consumer. Published MIDI objects are presented to the old
MIDI apps as if they were physical MIDI ports.
Here is a very nice picture of how ``BMidiPort`` works:
|image0|
The softsynth
~~~~~~~~~~~~~
The General MIDI synthesizer is implemented in ``BSoftSynth``. This is a
private class (not usable outside the API). It is not a real
``BMidiEndpoint``, so you will not see it appear on Patchbay. I did this
for simplicity's sake, for backwards compatibility (this is how the R5
synth worked too), and because we will have to give the Midi Kit a
complete makeover for R2 anyway.
The ``BMidiSynth`` and ``BSynth`` classes delegate most of the work to
``BSoftSynth``. Not all of their methods are implemented, since some of
them are very obscure. It would be a lot of work to figure out what they
do, while it is likely that no applications use them anyway. However,
``BMidiSynth`` and ``BSynth`` should perform most common tasks without
problems.
``BSamples`` doesn't do anything; its functions are mostly empty. In
other words, with the OpenBeOS Midi Kit you cannot push waveform data
into the output of the softsynth.
For simplicity's sake, ``BMidiSynthFile`` is implemented using
``BMidiStore``, since the latter already knows how to parse Standard
MIDI Files. Duplicating that functionality elsewhere would be pointless.
However, this makes the behavior of our ``BMidiSynthFile`` a little
different from what the BeBook says — as long as your applications are
written properly, you shouldn't notice any differences.
.. |image0| image:: midiport.png
-660
View File
@@ -1,660 +0,0 @@
<HTML>
<BODY>
<H1>The BeOS R5 Midi Kit protocol</H1>
<P>In the course of writing the OpenBeOS Midi Kit, I spent some time looking at
how BeOS R5's libmidi2.so and midi_server communicate. Not out of a compulsion
to clone this protocol, but to learn from it. After all, the Be engineers spent
a lot of time thinking about this already, and it would be foolish not to build
on their experience. Here is what I have found out.</P>
<P>Two kinds of communication happen: administrative tasks and MIDI events. The
housekeeping stuff is done by sending BMessages between the BMidiRoster and the
midi_server. MIDI events are sent between producers and consumers using ports,
without intervention from the server.</P>
<P>This document describes the BMessage protocol. The protocol appears to be
asynchronous, which means that when BMidiRoster sends a message to the
midi_server, it does not wait around for a reply, even though the midi_server
replies to all messages. The libmidi2 functions <I>do</I> block until the reply
is received, though, so client code does not have to worry about any of
this.</P>
<P>Both BMidiRoster and the midi_server can initiate messages. BMidiRoster
typically sends a message when client code calls one of the functions from a
libmidi2 class. When the midi_server sends messages, it is to keep BMidiRoster
up-to-date about changes in the roster. BMidiRoster never replies to messages
from the server. The encoding of the BMessage 'what' codes indicates their
direction. The 'Mxxx' messages are sent from libmidi2 to the midi_server. The
'mXXX' messages go the other way around: from the server to a client.</P>
<HR SIZE="1">
<H2>Who does what?</H2>
<P>The players here are the midi_server, which is a normal BApplication, and
all the client apps, also BApplications. The client apps have loaded a copy of
libmidi2 into their own address space. The main class from libmidi2 is
BMidiRoster. The BMidiRoster has a BLooper that communicates with the
midi_server's BLooper.</P>
<P>The midi_server keeps a list of <I>all</I> endpoints in the system, even
local, nonpublished, ones. Each BMidiRoster instance keeps its own list of
remote published endpoints, and all endpoints local to this application. It
does not know about remote endpoints that are not published yet.</P>
<P>Whenever you make a change to one of your own endpoints, your BMidiRoster
notifies the midi_server. If your endpoint is published, the midi_server then
notifies all of the other BMidiRosters, so they can update their local rosters.
It does <I>not</I> notify your own app! (Sometimes, however, the midi_server
also notifies everyone else even if your local endpoint is <I>not</I>
published. The reason for this escapes me, because the other BMidiRosters have
no access to those endpoints anyway.)</P>
<P>By the way, "notification" here means the internal communications between
server and libmidi, not the B_MIDI_EVENT messages you receive when you call
BMidiRoster::StartWatching().</P>
<HR SIZE="1">
<H2>BMidiRoster::MidiRoster()</H2>
<P>The first time it is called, this function creates the one-and-only instance
of BMidiRoster. Even if you don't explicitly call it yourself, it is used
behind-the-scenes anyway by any of the other BMidiRoster functions.
MidiRoster() constructs a BLooper and gets it running. Then it sends a
BMessenger with the looper's address to the midi_server:</P>
<PRE><SMALL>
OUT BMessage: what = Mapp (0x4d617070, or 1298231408)
entry be:msngr, type='MSNG', c=1, size=24,
</SMALL></PRE>
<P>The server now responds with mOBJ messages for all <I>remote</I>
<I>published</I> producers and consumers. (Obviously, this list only contains
remote objects because by now you can't have created any local endpoints
yet.)</P>
<P>For a consumer this message looks like:</P>
<PRE><SMALL>
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x1 (1, '')
entry be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry be:port, type='LONG', c=1, size= 4, data[0]: 0x1dab (7595, '')
entry be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
</SMALL></PRE>
<P>(Oddness: why is be:latency a LONG and not a LLNG? Since latency is
expressed in microseconds using a 64-bit bigtime_t, you'd expect the
midi_server to send all 64 of those bits... In the 'Mnew' message, on the other
hand, be:latency <I>is</I> a LLGN.)</P>
<P>And for a producer:</P>
<PRE><SMALL>
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x2 (2, '')
entry be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
</SMALL></PRE>
<P>Note that the be:name field is not present if the endpoint has no name. That
is, if the endpoint was constructed by passing a NULL name into the
BMidiLocalConsumer() or BMidiLocalProducer() constructor.</P>
<P>Next up are notifications for <I>all</I> connections, even those between
endpoints that are not registered:</P>
<PRE><SMALL>
IN BMessage: what = mCON (0x6d434f4e, or 1833127758)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
</SMALL></PRE>
<P>These messages are followed by an Msyn message:</P>
<PRE><SMALL>
IN BMessage: what = Msyn (0x4d73796e, or 1299413358)
</SMALL></PRE>
<P>And finally the (asynchronous) reply:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>Only after this reply is received, MidiRoster() returns.</P>
<P>The purpose of the Msyn message is not entirely clear. (Without it, Be's
libmidi2 blocks in the MidiRoster() call.) Does it signify the end of the list
of endpoints? Why doesn't libmidi2 simply wait for the final reply?</P>
<HR SIZE="1">
<H2>BMidiLocalProducer constructor</H2>
<P>BMidiRoster, on behalf of the constructor, sends the following to the
midi_server:</P>
<PRE><SMALL>
OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
entry be:type, type='CSTR', c=1, size=9, data[0]: "producer"
entry be:name, type='CSTR', c=1, size=21, data[0]: "MIDI Keyboard output"
</SMALL></PRE>
<P>The be:name field is optional.</P>
<P>The reply includes the ID for the new endpoint. This means that the
midi_server assigns the IDs, and any endpoint gets an ID whether it is
published or not.</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>Unlike many other Be API classes, BMidiLocalProducer and BMidiLocalConsumer
don't have an InitCheck() method. But under certain odd circumstances (such as
the midi_server not running), creating the endpoint might fail. How does client
code check for that? Well, it turns out that upon failure, the endpoint is
assigned ID 0, so you can check for that. In that case, the endpoint's refcount
is 0 and you should not Release() it. (That is stupid, actually, because
Release() is the only way that you can destroy the object. Our implementation
should bump the endpoint to 1 even on failure!)</P>
<P>If another app creates a new endpoint, your BMidiRoster is not notified. The
remote endpoint is not published yet, so your app is not supposed to see
it.</P>
<HR SIZE="1">
<H2>BMidiLocalConsumer constructor</H2>
<P>This is similar to the BMidiLocalProducer constructor, although the contents
of the message differ slightly. Again, be:name is optional.</P>
<PRE><SMALL>
OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
entry be:type, type='CSTR', c=1, size=9, data[0]: "consumer"
entry be:latency, type='LLNG', c=1, size= 8, data[0]: 0x0 (0, '')
entry be:port, type='LONG', c=1, size= 4, data[0]: 0x4c0 (1216, '')
entry be:name, type='CSTR', c=1, size=13, data[0]: "InternalMIDI"
</SMALL></PRE>
<P>And the reply:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>Before it sends the message to the server, the constructor creates a new
port with the name "MidiEventPort" and a queue length (capacity) of 1.</P>
<HR SIZE="1">
<H2>BMidiEndpoint::Register()<BR>
BMidiRoster::Register()</H2>
<P>Sends the same message for producers and consumers:</P>
<PRE><SMALL>
OUT BMessage: what = Mreg (0x4d726567, or 1299342695)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
</SMALL></PRE>
<P>The reply:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>If you try to Register() an endpoint that is already registered, libmidi2
still sends the message. (Which could mean that BMidiRoster does not keep track
of this registered state.) The midi_server simply ignores that request, and
sends back error code 0 (B_OK). So the API does not flag this as an error.</P>
<P>If you send an invalid be:id, the midi_server returns error code -1 (General
OS Error, B_ERROR). If you try to Register() a remote endpoint, libmidi2
immediately returns error code -1, and does not send a message to the
server.</P>
<P>If another app Register()'s a producer, your BMidiRoster receives:</P>
<PRE><SMALL>
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "a name"
</SMALL></PRE>
<P>If the other app registers a consumer, your BMidiRoster
receives:</P>
<PRE><SMALL>
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x19 (25, '')
entry be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry be:port, type='LONG', c=1, size= 4, data[0]: 0xde9 (3561, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "a name"
</SMALL></PRE>
<P>These are the same messages you get when your BMidiRoster instance is
constructed. In both messages, the be:name field is optional again.</P>
<P>If the other app Register()'s the endpoint more than once, you still get
only one notification. So the midi_server simply ignores that second publish
request.</P>
<HR SIZE="1">
<H2>BMidiEndpoint::Unregister()<BR>
BMidiRoster::Unregister()</H2>
<P>Sends the same message for producers and consumers:</P>
<PRE><SMALL>
OUT BMessage: what = Munr (0x4d756e72, or 1299541618)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
</SMALL></PRE>
<P>The reply:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>If you try to Unregister() and endpoint that is already unregistered,
libmidi2 still sends the message. The midi_server simply ignores that request,
and sends back error code 0 (B_OK). So the API does not flag this as an error.
If you try to Unregister() a remote endpoint, libmidi2 immediately returns
error code -1, and does not send a message to the server.</P>
<P>When another app Unregister()'s one of its own endpoints, your BMidiRoster
receives:</P>
<PRE><SMALL>
IN BMessage: what = mDEL (0x6d44454c, or 1833190732)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')
</SMALL></PRE>
<P>When the other app deletes that endpoint (refcount is now 0) and it is not
unregistered yet, your BMidiRoster also receives that mDEL message. Multiple
Unregisters() are ignored again by the midi_server.</P>
<P>If an app quits without properly cleaning up, i.e. it does not Unregister()
and Release() its endpoints, then the midi_server's roster contains a stale
endpoint. As soon as the midi_server recognizes this (for example, when an
application tries to connect that endpoint), it sends all BMidiRosters an mDEL
message for this endpoint. (This message is sent whenever the midi_server feels
like it, so libmidi2 can receive this message while it is still waiting for a
reply to some other message.) If the stale endpoint is still on the roster and
you (re)start your app, then you receive an mOBJ message for this endpoint
during the startup handshake. A little later you will receive the mDEL.</P>
<HR SIZE="1">
<H2>BMidiEndpoint::Release()</H2>
<P>Only sends a message if the refcount of local objects (published or not)
becomes 0:</P>
<PRE><SMALL>
OUT BMessage: what = Mdel (0x4d64656c, or 1298425196)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
</SMALL></PRE>
<P>The corresponding reply:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>If you did not Unregister() a published endpoint before you Release()'d it,
no 'Munr' message is sent. Of course, the midi_server is smart enough to
realize that this endpoint should be wiped from the roster now. Likewise, if
this endpoint is connected to another endpoint, Release() will not send a
separate 'Mdis' message, but the server <I>will</I> disconnect them. (This, of
course, only happens when you Release() local objects. Releasing a proxy has no
impact on the connection with the real endpoint.)</P>
<P>When you Release() a proxy (a remote endpoint) and its refcount becomes 0,
libmidi2 does not send an 'Mdel' message to the server. After all, the object
is not deleted, just your proxy. If the remote endpoint still exists (i.e.
IsValid() returns true), the BMidiRoster actually keeps a cached copy of the
proxy object around, just in case you need it again. This means you can do
this: endp = NextEndpoint(); endp->Release(); (now refcount is 0) endp-
>Acquire(); (now refcount is 1 again). But I advice against that since it
doesn't work for all objects; local and dead remote endpoints <I>will</I> be
deleted when their refcount reaches zero.</P>
<P>In Be's implementation, if you Release() a local endpoint that already has a
zero refcount, libmidi still sends out the 'Mdel' message. It also drops you
into the debugger. (I think it should return an error code instead, it already
has a status_t.) However, if you Release() proxies a few times too many, your
app does not jump into the debugger. (Again, I think the return result should
be an error code here -- for OpenBeOS R1 I think we should jump into the
debugger just like with local objects). Hmm, actually, whether you end up in
the debugger depends on the contents of memory after the object is deleted,
because you perform the extra Release() on a dead object. Don't do that.</P>
<HR SIZE="1">
<H2>BMidiEndpoint::SetName()</H2>
<P>For local endpoints, both unpublished and published, libmidi2 sends:</P>
<PRE><SMALL>
OUT BMessage: what = Mnam (0x4d6e616d, or 1299079533)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "b name"
</SMALL></PRE>
<P>And receives:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>You cannot rename remote endpoints. If you try, libmidi2 will simply ignore
your request. It does not send a message to the midi_server.</P>
<P>If another application renames one of its own endpoints, all other
BMidiRosters receive:</P>
<PRE><SMALL>
IN BMessage: what = mREN (0x6d52454e, or 1834108238)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x5 (5, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "b name"
</SMALL></PRE>
<P>You receive this message even if the other app did not publish its endpoint.
This seems rather strange, because your BMidiRoster has no knowledge of this
particular endpoint yet, so what is it to do with this message? Ignore it, I
guess.</P>
<HR SIZE="1">
<H2>BMidiEndpoint::GetProperties()</H2>
<P>For <I>any</I> kind of endpoint (local non-published, local published,
remote) libmidi2 sends the following message to the server:</P>
<PRE><SMALL>
OUT BMessage: what = Mgpr (0x4d677072, or 1298624626)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x2b2 (690, '')
entry be:props, type='MSGG', c=1, size= 0,
</SMALL></PRE>
<P>(Why this "get properties" request includes a BMessage is a mistery to me.
The midi_server does not appear to copy its contents into the reply, which
would have made at least some sense. The BMessage from the client is completely
overwritten with the endpoint's properties.)</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:props, type='MSGG', c=1, size= 0,
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>This means that endpoint properties are stored in the server only, not
inside the BMidiEndpoints, and not by the local BMidiRosters.</P>
<HR SIZE="1">
<H2>BMidiEndpoint::SetProperties()</H2>
<P>For local endpoints, published or not, libmidi2 sends the following message
to the server:</P>
<PRE><SMALL>
OUT BMessage: what = Mspr (0x4d737072, or 1299411058)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
entry be:props, type='MSGG', c=1, size= 0,
</SMALL></PRE>
<P>And expects this back:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>You cannot change the properties of remote endpoints. If you try, libmidi2
will ignore your request. It does not send a message to the midi_server, and it
returns the -1 error code (B_ERROR).</P>
<P>If another application changes the properties of one of its own endpoints,
all other BMidiRosters receive:</P>
<PRE><SMALL>
IN BMessage: what = mPRP (0x6d505250, or 1833980496)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:properties, type='MSGG', c=1, size= 0,
</SMALL></PRE>
<P>You receive this message even if the other app did not publish its
endpoint.</P>
<HR SIZE="1">
<H2>BMidiLocalConsumer::SetLatency()</H2>
<P>For local endpoints, published or not, libmidi2 sends the following message
to the server:</P>
<PRE><SMALL>
OUT BMessage: what = Mlat (0x4d6c6174, or 1298948468)
entry be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x14f (335, '')
</SMALL></PRE>
<P>And receives:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>If another application changes the latency of one of its own consumers, all
other BMidiRosters receive:</P>
<PRE><SMALL>
IN BMessage: what = mLAT (0x6d4c4154, or 1833714004)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x15 (21, '')
entry be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
</SMALL></PRE>
<P>You receive this message even if the other app did not publish its
endpoint.</P>
<HR SIZE="1">
<H2>BMidiProducer::Connect()</H2>
<P>The message:</P>
<PRE><SMALL>
OUT BMessage: what = Mcon (0x4d636f6e, or 1298362222)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x376 (886, '')
</SMALL></PRE>
<P>The answer:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>The server sends back a B_ERROR result if you specify wrong ID's. When you
try to connect a producer and consumer that are already connected to each
other, libmidi2 still sends the 'Mcon' message to the server (even though it
could have known these endpoints are already connected). In that case, the
server responds with a B_ERROR code as well.</P>
<P>When another app makes the connection, your BMidiRoster receives:</P>
<PRE><SMALL>
IN BMessage: what = mCON (0x6d434f4e, or 1833127758)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
</SMALL></PRE>
<P>Note: your BMidiRoster receives this notification even if the producer or
the consumer (or both) are not registered endpoints.</P>
<HR SIZE="1">
<H2>BMidiProducer::Disconnect()</H2>
<P>The message:</P>
<PRE><SMALL>
OUT BMessage: what = Mdis (0x4d646973, or 1298426227)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x309 (777, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x393 (915, '')
</SMALL></PRE>
<P>The answer:</P>
<PRE><SMALL>
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
</SMALL></PRE>
<P>The server sends back a B_ERROR result if you specify wrong ID's. When you
try to disconnect a producer and consumer that are not connected to each other,
libmidi2 still sends the 'Mdis' message to the server (even though it could
have known these endpoints are not connected). In that case, the server
responds with a B_ERROR code as well.</P>
<P>When another app breaks the connection, your BMidiRoster receives:</P>
<PRE><SMALL>
IN BMessage: what = mDIS (0x6d444953, or 1833191763)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
</SMALL></PRE>
<P>Note: your BMidiRoster receives this notification even if the producer or
the consumer (or both) are not registered endpoints.</P>
<HR SIZE="1">
<H2>Watchin'</H2>
<P>BMidiRoster::StartWatching() and StopWatching() do not send messages to the
midi_server. This means that the BMidiRoster itself, and not the midi_server,
sends the notifications to the messenger. It does this whenever it receives a
message from the midi_server.</P>
<P>The relationship between midi_server messages and B_MIDI_EVENT notifications
is as follows:</P>
<BLOCKQUOTE>
<TABLE BORDER="1">
<TR><TH>message</TH><TH>notification</TH></TR>
<TR><TD>mOBJ</TD><TD>B_MIDI_REGISTERED</TD></TR>
<TR><TD>mDEL</TD><TD> B_MIDI_UNREGISTERED </TD></TR>
<TR><TD>mCON</TD><TD>B_MIDI_CONNECTED</TD></TR>
<TR><TD>mDIS</TD><TD>B_MIDI_DISCONNECTED</TD></TR>
<TR><TD>mREN</TD><TD>B_MIDI_CHANGED_NAME</TD></TR>
<TR><TD>mLAT</TD><TD>B_MIDI_CHANGED_LATENCY</TD></TR>
<TR><TD>mPRP</TD><TD>B_MIDI_CHANGED_PROPERTIES</TD></TR>
</TABLE>
</BLOCKQUOTE>
<P>For each message on the left, the watcher will receive the corresponding
notification on the right.</P>
<HR SIZE="1">
<H2>Other observations</H2>
<P>Operations that do not send messages to the midi_server:</P>
<UL>
<LI><P>BMidiEndpoint::Acquire(). This means reference counting is done locally
by BMidiRoster. Release() doesn't send a message either, unless the refcount
becomes 0 and the object is deleted. (Which suggests that it is actually the
destructor and not Release() that sends the message.)</P></LI>
<LI><P>BMidiRoster::NextEndpoint(), NextProducer(), NextConsumer(),
FindEndpoint(), FindProducer(), FindConsumer(). None of these functions send
messages to the midi_server. This means that each BMidiRoster instance keeps
its own list of available endpoints. This is why it receives 'mOBJ' messages
during the startup handshake, and whenever a new remote endpoint is registered,
and 'mDEL' messages for every endpoint that disappears. Even though the
NextXXX() functions do not return locally created objects, this "local roster"
<I>does</I> keep track of them, since FindXXX() <I>do</I> return local
endpoints.</P></LI>
<LI><P>BMidiEndpoint::Name(), ID(), IsProducer(), IsConsumer(), IsRemote(),
IsLocal() IsPersistent(). BMidiConsumer::Latency().
BMidiLocalConsumer::GetProducerID(), SetTimeout(). These all appear to consult
BMidiRoster's local roster.</P></LI>
<LI><P>BMidiEndpoint::IsValid(). This function simply looks at BMidiRoster's
local roster to see whether the remote endpoint is still visible, i.e. not
unregistered. It does not determine whether the endpoint's application is still
alive, or "ping" the endpoint or anything fancy like that.</P></LI>
<LI><P>BMidiProducer::IsConnected(), Connections(). This means that
BMidiRoster's local roster, or maybe the BMidiProducers themselves (including
the proxies) keep track of the various connections.</P></LI>
<LI><P>BMidiLocalProducer::Connected(), Disconnected(). These methods are
invoked when any app (including your own) makes or breaks a connection on one
of your local producers. These hooks are invoked before the B_MIDI_EVENT
messages are sent to any watchers.</P></LI>
<LI><P>Quitting your app. Even though the BMidiRoster instance is deleted when
the app quits, it does not let the midi_server know that the application in
question is now gone. Any endpoints you have registered are not automatically
unregistered. This means that the midi_server is left with some stale
information. Undoubtedly, there is a mechanism in place to clean this up. The
same mechanism would be used to clean up apps that did not exit cleanly, or
that crashed.</P></LI>
</UL>
<P>Other stuff:</P>
<UL>
<LI><P>libmidi2.so exports an int32 symbol called "midi_debug_level". If you
set it to a non-zero value, libmidi2 will dump a lot of interesting debug info
on stdout. To do this, declare the variable in your app with "extern int32
midi_debug_level;", and then set it to some high value later: "midi_debug_level
= 0x7FFFFFFF;" Now run your app from a Terminal and watch libmidi2 do its
thing.</P></LI>
<LI><P>libmidi2.so also exports an int32 symbol called
"midi_dispatcher_priority". This is the runtime priority of the thread that
fields MIDI events to consumers.</P></LI>
</UL>
</BODY>
</HTML>
+678
View File
@@ -0,0 +1,678 @@
The BeOS R5 Midi Kit protocol
=============================
In the course of writing the OpenBeOS Midi Kit, I spent some time
looking at how BeOS R5's libmidi2.so and midi_server communicate. Not
out of a compulsion to clone this protocol, but to learn from it. After
all, the Be engineers spent a lot of time thinking about this already,
and it would be foolish not to build on their experience. Here is what I
have found out.
Two kinds of communication happen: administrative tasks and MIDI events.
The housekeeping stuff is done by sending BMessages between the
BMidiRoster and the midi_server. MIDI events are sent between producers
and consumers using ports, without intervention from the server.
This document describes the BMessage protocol. The protocol appears to
be asynchronous, which means that when BMidiRoster sends a message to
the midi_server, it does not wait around for a reply, even though the
midi_server replies to all messages. The libmidi2 functions *do* block
until the reply is received, though, so client code does not have to
worry about any of this.
Both BMidiRoster and the midi_server can initiate messages. BMidiRoster
typically sends a message when client code calls one of the functions
from a libmidi2 class. When the midi_server sends messages, it is to
keep BMidiRoster up-to-date about changes in the roster. BMidiRoster
never replies to messages from the server. The encoding of the BMessage
'what' codes indicates their direction. The 'Mxxx' messages are sent
from libmidi2 to the midi_server. The 'mXXX' messages go the other way
around: from the server to a client.
--------------
Who does what?
--------------
The players here are the midi_server, which is a normal BApplication,
and all the client apps, also BApplications. The client apps have loaded
a copy of libmidi2 into their own address space. The main class from
libmidi2 is BMidiRoster. The BMidiRoster has a BLooper that communicates
with the midi_server's BLooper.
The midi_server keeps a list of *all* endpoints in the system, even
local, nonpublished, ones. Each BMidiRoster instance keeps its own list
of remote published endpoints, and all endpoints local to this
application. It does not know about remote endpoints that are not
published yet.
Whenever you make a change to one of your own endpoints, your
BMidiRoster notifies the midi_server. If your endpoint is published, the
midi_server then notifies all of the other BMidiRosters, so they can
update their local rosters. It does *not* notify your own app!
(Sometimes, however, the midi_server also notifies everyone else even if
your local endpoint is *not* published. The reason for this escapes me,
because the other BMidiRosters have no access to those endpoints
anyway.)
By the way, "notification" here means the internal communications
between server and libmidi, not the B_MIDI_EVENT messages you receive
when you call BMidiRoster::StartWatching().
--------------
BMidiRoster::MidiRoster()
-------------------------
The first time it is called, this function creates the one-and-only
instance of BMidiRoster. Even if you don't explicitly call it yourself,
it is used behind-the-scenes anyway by any of the other BMidiRoster
functions. MidiRoster() constructs a BLooper and gets it running. Then
it sends a BMessenger with the looper's address to the midi_server:
::
OUT BMessage: what = Mapp (0x4d617070, or 1298231408)
entry be:msngr, type='MSNG', c=1, size=24,
The server now responds with mOBJ messages for all *remote* *published*
producers and consumers. (Obviously, this list only contains remote
objects because by now you can't have created any local endpoints yet.)
For a consumer this message looks like:
::
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x1 (1, '')
entry be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry be:port, type='LONG', c=1, size= 4, data[0]: 0x1dab (7595, '')
entry be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
(Oddness: why is be:latency a LONG and not a LLNG? Since latency is
expressed in microseconds using a 64-bit bigtime_t, you'd expect the
midi_server to send all 64 of those bits... In the 'Mnew' message, on
the other hand, be:latency *is* a LLGN.)
And for a producer:
::
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x2 (2, '')
entry be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
Note that the be:name field is not present if the endpoint has no name.
That is, if the endpoint was constructed by passing a NULL name into the
BMidiLocalConsumer() or BMidiLocalProducer() constructor.
Next up are notifications for *all* connections, even those between
endpoints that are not registered:
::
IN BMessage: what = mCON (0x6d434f4e, or 1833127758)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
These messages are followed by an Msyn message:
::
IN BMessage: what = Msyn (0x4d73796e, or 1299413358)
And finally the (asynchronous) reply:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
Only after this reply is received, MidiRoster() returns.
The purpose of the Msyn message is not entirely clear. (Without it, Be's
libmidi2 blocks in the MidiRoster() call.) Does it signify the end of
the list of endpoints? Why doesn't libmidi2 simply wait for the final
reply?
--------------
BMidiLocalProducer constructor
------------------------------
BMidiRoster, on behalf of the constructor, sends the following to the
midi_server:
::
OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
entry be:type, type='CSTR', c=1, size=9, data[0]: "producer"
entry be:name, type='CSTR', c=1, size=21, data[0]: "MIDI Keyboard output"
The be:name field is optional.
The reply includes the ID for the new endpoint. This means that the
midi_server assigns the IDs, and any endpoint gets an ID whether it is
published or not.
::
IN BMessage: what = (0x0, or 0)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
Unlike many other Be API classes, BMidiLocalProducer and
BMidiLocalConsumer don't have an InitCheck() method. But under certain
odd circumstances (such as the midi_server not running), creating the
endpoint might fail. How does client code check for that? Well, it turns
out that upon failure, the endpoint is assigned ID 0, so you can check
for that. In that case, the endpoint's refcount is 0 and you should not
Release() it. (That is stupid, actually, because Release() is the only
way that you can destroy the object. Our implementation should bump the
endpoint to 1 even on failure!)
If another app creates a new endpoint, your BMidiRoster is not notified.
The remote endpoint is not published yet, so your app is not supposed to
see it.
--------------
BMidiLocalConsumer constructor
------------------------------
This is similar to the BMidiLocalProducer constructor, although the
contents of the message differ slightly. Again, be:name is optional.
::
OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
entry be:type, type='CSTR', c=1, size=9, data[0]: "consumer"
entry be:latency, type='LLNG', c=1, size= 8, data[0]: 0x0 (0, '')
entry be:port, type='LONG', c=1, size= 4, data[0]: 0x4c0 (1216, '')
entry be:name, type='CSTR', c=1, size=13, data[0]: "InternalMIDI"
And the reply:
::
IN BMessage: what = (0x0, or 0)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
Before it sends the message to the server, the constructor creates a new
port with the name "MidiEventPort" and a queue length (capacity) of 1.
--------------
BMidiEndpoint::Register()
BMidiRoster::Register()
-------------------------
Sends the same message for producers and consumers:
::
OUT BMessage: what = Mreg (0x4d726567, or 1299342695)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
The reply:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
If you try to Register() an endpoint that is already registered,
libmidi2 still sends the message. (Which could mean that BMidiRoster
does not keep track of this registered state.) The midi_server simply
ignores that request, and sends back error code 0 (B_OK). So the API
does not flag this as an error.
If you send an invalid be:id, the midi_server returns error code -1
(General OS Error, B_ERROR). If you try to Register() a remote endpoint,
libmidi2 immediately returns error code -1, and does not send a message
to the server.
If another app Register()'s a producer, your BMidiRoster receives:
::
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "a name"
If the other app registers a consumer, your BMidiRoster receives:
::
IN BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x19 (25, '')
entry be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry be:port, type='LONG', c=1, size= 4, data[0]: 0xde9 (3561, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "a name"
These are the same messages you get when your BMidiRoster instance is
constructed. In both messages, the be:name field is optional again.
If the other app Register()'s the endpoint more than once, you still get
only one notification. So the midi_server simply ignores that second
publish request.
--------------
BMidiEndpoint::Unregister()
BMidiRoster::Unregister()
---------------------------
Sends the same message for producers and consumers:
::
OUT BMessage: what = Munr (0x4d756e72, or 1299541618)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
The reply:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
If you try to Unregister() and endpoint that is already unregistered,
libmidi2 still sends the message. The midi_server simply ignores that
request, and sends back error code 0 (B_OK). So the API does not flag
this as an error. If you try to Unregister() a remote endpoint, libmidi2
immediately returns error code -1, and does not send a message to the
server.
When another app Unregister()'s one of its own endpoints, your
BMidiRoster receives:
::
IN BMessage: what = mDEL (0x6d44454c, or 1833190732)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')
When the other app deletes that endpoint (refcount is now 0) and it is
not unregistered yet, your BMidiRoster also receives that mDEL message.
Multiple Unregisters() are ignored again by the midi_server.
If an app quits without properly cleaning up, i.e. it does not
Unregister() and Release() its endpoints, then the midi_server's roster
contains a stale endpoint. As soon as the midi_server recognizes this
(for example, when an application tries to connect that endpoint), it
sends all BMidiRosters an mDEL message for this endpoint. (This message
is sent whenever the midi_server feels like it, so libmidi2 can receive
this message while it is still waiting for a reply to some other
message.) If the stale endpoint is still on the roster and you (re)start
your app, then you receive an mOBJ message for this endpoint during the
startup handshake. A little later you will receive the mDEL.
--------------
BMidiEndpoint::Release()
------------------------
Only sends a message if the refcount of local objects (published or not)
becomes 0:
::
OUT BMessage: what = Mdel (0x4d64656c, or 1298425196)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
The corresponding reply:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
If you did not Unregister() a published endpoint before you Release()'d
it, no 'Munr' message is sent. Of course, the midi_server is smart
enough to realize that this endpoint should be wiped from the roster
now. Likewise, if this endpoint is connected to another endpoint,
Release() will not send a separate 'Mdis' message, but the server *will*
disconnect them. (This, of course, only happens when you Release() local
objects. Releasing a proxy has no impact on the connection with the real
endpoint.)
When you Release() a proxy (a remote endpoint) and its refcount becomes
0, libmidi2 does not send an 'Mdel' message to the server. After all,
the object is not deleted, just your proxy. If the remote endpoint still
exists (i.e. IsValid() returns true), the BMidiRoster actually keeps a
cached copy of the proxy object around, just in case you need it again.
This means you can do this: endp = NextEndpoint(); endp->Release(); (now
refcount is 0) endp- >Acquire(); (now refcount is 1 again). But I advice
against that since it doesn't work for all objects; local and dead
remote endpoints *will* be deleted when their refcount reaches zero.
In Be's implementation, if you Release() a local endpoint that already
has a zero refcount, libmidi still sends out the 'Mdel' message. It also
drops you into the debugger. (I think it should return an error code
instead, it already has a status_t.) However, if you Release() proxies a
few times too many, your app does not jump into the debugger. (Again, I
think the return result should be an error code here -- for OpenBeOS R1
I think we should jump into the debugger just like with local objects).
Hmm, actually, whether you end up in the debugger depends on the
contents of memory after the object is deleted, because you perform the
extra Release() on a dead object. Don't do that.
--------------
BMidiEndpoint::SetName()
------------------------
For local endpoints, both unpublished and published, libmidi2 sends:
::
OUT BMessage: what = Mnam (0x4d6e616d, or 1299079533)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "b name"
And receives:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
You cannot rename remote endpoints. If you try, libmidi2 will simply
ignore your request. It does not send a message to the midi_server.
If another application renames one of its own endpoints, all other
BMidiRosters receive:
::
IN BMessage: what = mREN (0x6d52454e, or 1834108238)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x5 (5, '')
entry be:name, type='CSTR', c=1, size=7, data[0]: "b name"
You receive this message even if the other app did not publish its
endpoint. This seems rather strange, because your BMidiRoster has no
knowledge of this particular endpoint yet, so what is it to do with this
message? Ignore it, I guess.
--------------
BMidiEndpoint::GetProperties()
------------------------------
For *any* kind of endpoint (local non-published, local published,
remote) libmidi2 sends the following message to the server:
::
OUT BMessage: what = Mgpr (0x4d677072, or 1298624626)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x2b2 (690, '')
entry be:props, type='MSGG', c=1, size= 0,
(Why this "get properties" request includes a BMessage is a mistery to
me. The midi_server does not appear to copy its contents into the reply,
which would have made at least some sense. The BMessage from the client
is completely overwritten with the endpoint's properties.)
::
IN BMessage: what = (0x0, or 0)
entry be:props, type='MSGG', c=1, size= 0,
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
This means that endpoint properties are stored in the server only, not
inside the BMidiEndpoints, and not by the local BMidiRosters.
--------------
BMidiEndpoint::SetProperties()
------------------------------
For local endpoints, published or not, libmidi2 sends the following
message to the server:
::
OUT BMessage: what = Mspr (0x4d737072, or 1299411058)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
entry be:props, type='MSGG', c=1, size= 0,
And expects this back:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
You cannot change the properties of remote endpoints. If you try,
libmidi2 will ignore your request. It does not send a message to the
midi_server, and it returns the -1 error code (B_ERROR).
If another application changes the properties of one of its own
endpoints, all other BMidiRosters receive:
::
IN BMessage: what = mPRP (0x6d505250, or 1833980496)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:properties, type='MSGG', c=1, size= 0,
You receive this message even if the other app did not publish its
endpoint.
--------------
BMidiLocalConsumer::SetLatency()
--------------------------------
For local endpoints, published or not, libmidi2 sends the following
message to the server:
::
OUT BMessage: what = Mlat (0x4d6c6174, or 1298948468)
entry be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x14f (335, '')
And receives:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
If another application changes the latency of one of its own consumers,
all other BMidiRosters receive:
::
IN BMessage: what = mLAT (0x6d4c4154, or 1833714004)
entry be:id, type='LONG', c=1, size= 4, data[0]: 0x15 (21, '')
entry be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
You receive this message even if the other app did not publish its
endpoint.
--------------
BMidiProducer::Connect()
------------------------
The message:
::
OUT BMessage: what = Mcon (0x4d636f6e, or 1298362222)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x376 (886, '')
The answer:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
The server sends back a B_ERROR result if you specify wrong ID's. When
you try to connect a producer and consumer that are already connected to
each other, libmidi2 still sends the 'Mcon' message to the server (even
though it could have known these endpoints are already connected). In
that case, the server responds with a B_ERROR code as well.
When another app makes the connection, your BMidiRoster receives:
::
IN BMessage: what = mCON (0x6d434f4e, or 1833127758)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
Note: your BMidiRoster receives this notification even if the producer
or the consumer (or both) are not registered endpoints.
--------------
BMidiProducer::Disconnect()
---------------------------
The message:
::
OUT BMessage: what = Mdis (0x4d646973, or 1298426227)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x309 (777, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x393 (915, '')
The answer:
::
IN BMessage: what = (0x0, or 0)
entry be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
entry _previous_, ...
The server sends back a B_ERROR result if you specify wrong ID's. When
you try to disconnect a producer and consumer that are not connected to
each other, libmidi2 still sends the 'Mdis' message to the server (even
though it could have known these endpoints are not connected). In that
case, the server responds with a B_ERROR code as well.
When another app breaks the connection, your BMidiRoster receives:
::
IN BMessage: what = mDIS (0x6d444953, or 1833191763)
entry be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
entry be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
Note: your BMidiRoster receives this notification even if the producer
or the consumer (or both) are not registered endpoints.
--------------
Watchin'
--------
BMidiRoster::StartWatching() and StopWatching() do not send messages to
the midi_server. This means that the BMidiRoster itself, and not the
midi_server, sends the notifications to the messenger. It does this
whenever it receives a message from the midi_server.
The relationship between midi_server messages and B_MIDI_EVENT
notifications is as follows:
+---------+---------------------------+
| message | notification |
+=========+===========================+
| mOBJ | B_MIDI_REGISTERED |
+---------+---------------------------+
| mDEL | B_MIDI_UNREGISTERED |
+---------+---------------------------+
| mCON | B_MIDI_CONNECTED |
+---------+---------------------------+
| mDIS | B_MIDI_DISCONNECTED |
+---------+---------------------------+
| mREN | B_MIDI_CHANGED_NAME |
+---------+---------------------------+
| mLAT | B_MIDI_CHANGED_LATENCY |
+---------+---------------------------+
| mPRP | B_MIDI_CHANGED_PROPERTIES |
+---------+---------------------------+
For each message on the left, the watcher will receive the corresponding
notification on the right.
--------------
Other observations
------------------
Operations that do not send messages to the midi_server:
- BMidiEndpoint::Acquire(). This means reference counting is done
locally by BMidiRoster. Release() doesn't send a message either,
unless the refcount becomes 0 and the object is deleted. (Which
suggests that it is actually the destructor and not Release() that
sends the message.)
- BMidiRoster::NextEndpoint(), NextProducer(), NextConsumer(),
FindEndpoint(), FindProducer(), FindConsumer(). None of these
functions send messages to the midi_server. This means that each
BMidiRoster instance keeps its own list of available endpoints. This
is why it receives 'mOBJ' messages during the startup handshake, and
whenever a new remote endpoint is registered, and 'mDEL' messages for
every endpoint that disappears. Even though the NextXXX() functions
do not return locally created objects, this "local roster" *does*
keep track of them, since FindXXX() *do* return local endpoints.
- BMidiEndpoint::Name(), ID(), IsProducer(), IsConsumer(), IsRemote(),
IsLocal() IsPersistent(). BMidiConsumer::Latency().
BMidiLocalConsumer::GetProducerID(), SetTimeout(). These all appear
to consult BMidiRoster's local roster.
- BMidiEndpoint::IsValid(). This function simply looks at BMidiRoster's
local roster to see whether the remote endpoint is still visible,
i.e. not unregistered. It does not determine whether the endpoint's
application is still alive, or "ping" the endpoint or anything fancy
like that.
- BMidiProducer::IsConnected(), Connections(). This means that
BMidiRoster's local roster, or maybe the BMidiProducers themselves
(including the proxies) keep track of the various connections.
- BMidiLocalProducer::Connected(), Disconnected(). These methods are
invoked when any app (including your own) makes or breaks a
connection on one of your local producers. These hooks are invoked
before the B_MIDI_EVENT messages are sent to any watchers.
- Quitting your app. Even though the BMidiRoster instance is deleted
when the app quits, it does not let the midi_server know that the
application in question is now gone. Any endpoints you have
registered are not automatically unregistered. This means that the
midi_server is left with some stale information. Undoubtedly, there
is a mechanism in place to clean this up. The same mechanism would be
used to clean up apps that did not exit cleanly, or that crashed.
Other stuff:
- libmidi2.so exports an int32 symbol called "midi_debug_level". If you
set it to a non-zero value, libmidi2 will dump a lot of interesting
debug info on stdout. To do this, declare the variable in your app
with "extern int32 midi_debug_level;", and then set it to some high
value later: "midi_debug_level = 0x7FFFFFFF;" Now run your app from a
Terminal and watch libmidi2 do its thing.
- libmidi2.so also exports an int32 symbol called
"midi_dispatcher_priority". This is the runtime priority of the
thread that fields MIDI events to consumers.
-48
View File
@@ -1,48 +0,0 @@
<HTML>
<BODY>
<H1>Misc notes</H1>
<UL>
<LI><P><B>MPU401 kernel module.</B> If your soundcard supports MIDI input and
output, chances are that it is powered by an MPU401 chip. Because this
interface is so popular, BeOS comes with a kernel module that makes it easy to
write drivers for the MPU401. Thanks to Greg Crain, we now have an open source
version of this kernel module.</P>
<P>The mpu401 module lives in <CODE>src/add-ons/kernel/generic/mpu401</CODE>.
It supports both the v1 and (undocumented) v2 protocols, although v2 is not
complete since we don't really know how it works. Unfortunately, almost no
existing drivers use v1; most of the drivers provided by Be require v2.
Currently, the module returns B_ERROR when a MIDI device is opened with
v2.</P>
<P>For an example on how to use the MPU401 module in your own driver, see
the source code for the "emuxki" driver elsewhere in the source tree.</P></LI>
<LI><P><B>Clients without a BApplication.</B> Sometimes the midi_server's
debug output shows an "Application -1 not registered" error message. This
means it cannot figure out which app an incoming BMessage came from. The
server ignores those messages.</P>
<P>How can this happen? libmidi2 has two ways of sending messages to the
midi_server: it either expects a reply back or not. In the first case, it is
obvious to the midi_server what the reply address of the message is. In the
second case, even though it is not necessary for the server to send a message
back, it still uses the reply address to determine which app the message came
from. For this, BMessenger uses be_app_messenger of the client app.</P>
<P>However, if the client app has no BApplication object, there is no
be_app_messenger either. Now, the midi_server cannot determine where the
message came from and will ignore it. Is this important? For example, when such
a client app Release()'s its endpoints, it sends a message to the server
without a return address. Now the server ignores that message and does not
remove the endpoint from the roster. Of course, after the client app has died,
the endpoints will be removed eventually. Does all of this matter? Not really,
because only trivial apps will have no BApplication object.</P></LI>
</UL>
</BODY>
</HTML>
+42
View File
@@ -0,0 +1,42 @@
Misc notes
==========
- **MPU401 kernel module.** If your soundcard supports MIDI input and
output, chances are that it is powered by an MPU401 chip. Because
this interface is so popular, BeOS comes with a kernel module that
makes it easy to write drivers for the MPU401. Thanks to Greg Crain,
we now have an open source version of this kernel module.
The mpu401 module lives in ``src/add-ons/kernel/generic/mpu401``. It
supports both the v1 and (undocumented) v2 protocols, although v2 is
not complete since we don't really know how it works. Unfortunately,
almost no existing drivers use v1; most of the drivers provided by Be
require v2. Currently, the module returns B_ERROR when a MIDI device
is opened with v2.
For an example on how to use the MPU401 module in your own driver,
see the source code for the "emuxki" driver elsewhere in the source
tree.
- **Clients without a BApplication.** Sometimes the midi_server's debug
output shows an "Application -1 not registered" error message. This
means it cannot figure out which app an incoming BMessage came from.
The server ignores those messages.
How can this happen? libmidi2 has two ways of sending messages to the
midi_server: it either expects a reply back or not. In the first
case, it is obvious to the midi_server what the reply address of the
message is. In the second case, even though it is not necessary for
the server to send a message back, it still uses the reply address to
determine which app the message came from. For this, BMessenger uses
be_app_messenger of the client app.
However, if the client app has no BApplication object, there is no
be_app_messenger either. Now, the midi_server cannot determine where
the message came from and will ignore it. Is this important? For
example, when such a client app Release()'s its endpoints, it sends a
message to the server without a return address. Now the server
ignores that message and does not remove the endpoint from the
roster. Of course, after the client app has died, the endpoints will
be removed eventually. Does all of this matter? Not really, because
only trivial apps will have no BApplication object.
-566
View File
@@ -1,566 +0,0 @@
<HTML>
<BODY>
<H1>Testing the Midi Kit</H1>
<P>Most of the OpenBeOS source code has unit tests in the current/src/tests
directory. I looked into building CppUnit tests for the midi2 kit, but decided
that it doesn't really make much sense. Unit tests work best if you can test
something in isolation, but in the case of the midi2 kit this is very hard to
achieve. Because the classes from libmidi2.so always need to talk to the
midi_server, the tests depend on too many external factors. The available
endpoints, for example, will differ from system to system. The spray and hook
functions are difficult to test this way, too.</P>
<P>So instead of a CppUnit test suite, here is a list of manual tests that I
performed when developing the midi2 kit:</P>
<HR SIZE="1">
<H2>Registering the application</H2>
<P><I>Required:</I> Client app that calls BMidiRoster::MidiRoster()</P>
<UL>
<LI><P>When a client app starts, it should first receive mNEW notifications for
all endpoints in the system (even unregistered remotes), followed by mCON
notifications for all connections in the system (even those between two
unregistered local endpoints from another app).</P></LI>
<LI><P>Send invalid Mapp message (without messenger). The midi_server ignores
the request, and the client app blocks forever.</P></LI>
<LI><P>Fake a delivery error for the mNEW notifications and the mAPP reply.
(Add a snooze() in the midi_server's OnRegisterApplication(). While it is
snoozing, Ctrl-C the client app. Now the server can't deliver the message and
will unregister the application again.)</P></LI>
<LI><P>Kill the server. Start the client app. It should realize that the server
is not running, and return from MidiRoster(); it does not block
forever.</P></LI>
<LI><P>Note: The server does not protect against sending two or more Mapp
messages; it will add a new app_t object to the roster and it will also send
out the mNEW and mCON notifications again.</P></LI>
<LI><P>Verify that when the client app quits, the BMidiRoster instance is
destroyed by the BMidiRosterKiller. The BMidiRosterLooper is also destroyed,
along with any endpoint objects from its list. We don't destroy endpoints with
a refcount > 0, but print a warning message on stderr instead.</P></LI>
<LI><P>When the app quits before it has created a BMidiRoster instance, the
BMidiRosterKiller should do nothing.</P></LI>
</UL>
<HR SIZE="1">
<H2>Creating endpoints</H2>
<P><I>Required:</I> Client app that creates a new BMidiLocalProducer and/or
BMidiLocalConsumer</P>
<UL>
<LI><P>Send invalid Mnew message (missing fields). The server will return an
error code.</P></LI>
<LI><P>Don't send reply from midi_server. The client receives a B_NO_REPLY
error.</P></LI>
<LI><P>If something goes wrong creating a new local endpoint, you still get a
new BMidiEndpoint object (but it is not added to BMidiRosterLooper's internal
list of endpoints). Verify that its ID() function returns 0, and IsValid()
returns false. Verify that you can Release() it without crashing into the
debugger (i.e. the reference count of the new object should be 1).</P></LI>
<LI><P>Snooze in midi_server's OnCreateEndpoint() before sending reply to
client to simulate heavy processor load. Client should timeout. When done
snoozing, server fails to deliver the reply because the client is no longer
listening, and it unregisters the app.</P></LI>
<LI><P>Note: if you kill the client app with Ctrl-C before the server has sent
its reply, SendReply() still returns okay, and the midi_server adds the
endpoint, even though the corresponding app is dead. There is not much we can
do to prevent that (but it is not really a big deal).</P></LI>
<LI><P>Start the test app from two different Terminals. Verify that the new
local endpoint of app1 is added to the BMidiRosterLooper's list of endpoints,
and that its "isLocal" flag is true. Verify that when you start the second app,
it immediately receives mNEW notifications for the first app's endpoints. It
should also create BMidiEndpoint proxy objects for these endpoints with
"isLocal" set to false, and add them its own list. Vice versa for the endpoints
that app2 creates. Verify that the "registered" field in the mNEW notification
is false, because newly created endpoints are not registered yet. The
"properties" field should contain an empty message.</P></LI>
<LI><P>Start server. Start client app. The app makes new endpoints and the
server adds them to the roster. Ctrl-C the app. Start client app again. The new
client first receives mNEW notifications for the old app's endpoints. When the
new app tries to create its own endpoints, the server realizes that the old app
is dead, and sends mDEL notifications for the now-defunct endpoints.</P></LI>
<LI><P>The test app should now create 2 endpoints. Let the midi_server snooze
during the second create message, so the app times out. The server now
unregisters the app and purges its first endpoint (which was successfully
created).</P></LI>
<LI><P>The test app should now create 3 endpoints. Let the midi_server snooze
during the second create message, so the app times out. (It also times out when
sending the create request for the 3rd endpoint, because the server is still
snoozing.) Because it cannot send a reply for the 2nd create message, the
server now unregisters the app and purges its first endpoint (which was
successfully created). Then it processes the create request for the 3rd
endpoint, but ignores it because the app is now no longer registered with the
server.</P></LI>
<LI><P>Purging endpoints. The test app should now create 2 endpoints. Let the
midi_server snooze during the _fourth_ create message. Run the server. Run the
test app. Run the test app again in a second Terminal. The server times out,
and unregisters the second app. The first app should receive an mDEL
notification. Repeat, but now the test app should make 3 endpoints and the
server fails on the _sixth_ endpoint. The first app now receives 2 mDEL
notifications.</P></LI>
<LI><P>You should be allowed to pass NULL into the BMidiLocalProducer and
BMidiLocalConsumer constructor.</P></LI>
<LI><P>Let the midi_server assign random IDs to new endpoints; the
BMidiRosterLooper should sort the endpoints by their IDs when it adds them to
its internal list.</P></LI>
</UL>
<HR SIZE="1">
<H2>Deleting endpoints</H2>
<P><I>Required:</I> client app that creates one or more endpoints and
Release()'s them</P>
<UL>
<LI><P>Verify that Acquire() increments the endpoint's refcount and Release()
decrements it. When you Release() a local endpoint so its refcount becomes
zero, the client sends an Mdel request to the server. When you Release() a
local endpoint too many times, your app jumps into the debugger.</P></LI>
<LI><P>Send an Mdel request with an invalid ID to the server. Examples of
invalid IDs: -1, 0, 1000 (or any other large number).</P></LI>
<LI><P>Start the test app from two different Terminals. Note that when one of
the apps Release()'s its endpoints, the other receives corresponding mDEL
notifications.</P></LI>
<LI><P>Snooze in midi_server's OnCreateEndpoint() before sending reply to
"create endpoint" request. The client will timeout and the server will
unregister the app. Now have the client Release() the endpoint. This sends a
"delete endpoint" request to the server, which ignores the request because the
app is no longer registered.</P></LI>
<LI><P>Override BMidiLocalProducer and BMidiLocalConsumer, and provide a public
destructor. Call "delete prod; delete cons;" from your code, instead of using
Release(). Your app should drop into the debugger.</P></LI>
<LI><P>Start the client app and let it make its endpoints. Kill the server.
Release() the endpoints. The server doesn't run, so the Mdel request never
arrives, but the BMidiEndpoint objects should be deleted regardless.</P></LI>
<LI><P>Start the test app from two different Terminals, and let them make their
endpoints. Quit the apps (using the Deskbar's "Quit Application" menu item).
Verify that both clean up and exit correctly. App1 removes its own endpoint
from the BMidiRosterLooper's list of endpoints and sends an 'mDEL' message to
the server, which passes it on to app2. In response, app2 removes the proxy
object from its own list and deletes it. Again, vice versa for the endpoint
from app2.</P></LI>
<LI><P>Start both apps again and wait until they have notified each other about
the endpoints. Ctrl-C app1, and restart it. Verify that app1 receives the
'mNEW' messages and creates proxies for these remote endpoints. Both apps
should receive an 'mDEL' message for app1's old endpoint (because the
midi_server realizes it no longer exists and purges it), and remove it from
their lists accordingly.</P></LI>
</UL>
<HR SIZE="1">
<H2>Changing attributes</H2>
<P><I>Required:</I> Client app that creates an endpoint and calls Register(),
Unregister(), SetName(), and SetLatency()</P>
<UL>
<LI><P>Send an Mchg request with an invalid ID to the server.</P></LI>
<LI><P>Register() a local endpoint that is already registered. This does not
send a message to the server and always returns B_OK. Likewise for
Unregister()ing a local endpoint that is not registered.</P></LI>
<LI><P>Register() or Unregister() a remote endpoint, or an invalid local
endpoint. That should immediately return an error code.</P></LI>
<LI><P>Verify that BMidiRoster::Register() does the same thing as
BMidiEndpoint::Register(). Also for BMidiRoster::Unregister() and
BMidiEndpoint::Unregister().</P></LI>
<LI><P>If you pass NULL into BMidiRoster::Register() or Unregister(), the
functions immediately return with an error code.</P></LI>
<LI><P>SetName() should ignore NULL names. When you call it on a remote
endpoint, SetName() should do nothing. SetName() does not send a message if the
new name is the same as the current name.</P></LI>
<LI><P>SetLatency() should ignore negative values. SetLatency() does not send a
message if the new latency is the same as the current latency. (Since
SetLatency() lives in BMidiLocalConsumer, you can never use it on remote
endpoints.)</P></LI>
<LI><P>Kill the server after making the new endpoint, and call Register(). The
client app should return an error code. Also for Unregister(), SetName(),
SetLatency(), and SetProperties().</P></LI>
<LI><P>Snooze in the midi_server's OnChangeEndpoint() before sending the reply
to the client. Both sides will flag an error. No mCHG notifications will be
sent. The server unregisters the app and purges its endpoints.</P></LI>
<LI><P>Verify that other apps will receive mCHG notifications when the test app
successfully calls Register(), Unregister(), SetName(), and SetLatency(), and
that they modify the corresponding BMidiEndpoint objects accordingly. Since
clients are never notified when they change their own endpoints, they should
ignore the notifications that concern local endpoints. Latency changes should
be ignored if the endpoint is not a consumer.</P></LI>
<LI><P>Send an Mchg request with only the "midi:id" field, so no "midi:name",
"midi:registered", "midi:latency", or "midi:properties". The server will still
notify the other apps, although they will obviously ignore the notification,
because it doesn't contain any useful data.</P></LI>
<LI><P>The Mchg request is overloaded to change several attributes. Verify that
changing one of these attributes, such as the latency, does not overwrite/wipe
out the others.</P></LI>
<LI><P>Start app1. Wait until it has created and registered its endpoint. Start
app2. During the initial handshake, app2 should receive an 'mNEW' message for
app1's endpoint. Verify that the "refistered" field in this message is already
true, and that this is passed on correctly to the new BMidiEndpoint proxy
object.</P></LI>
<LI><P>GetProperties() should return NULL if the message parameter is
NULL.</P></LI>
<LI><P>The properties of new endpoints are empty. Create a new endpoint and
call GetProperties(). The BMessage that you receive should contain no
fields.</P></LI>
<LI><P>SetProperties() should return NULL if the message parameter is NULL. It
should return an error code if the endpoint is remote or invalid. It should
work fine on local endpoints, registered or not. SetProperties() does not
compare the contents of the new BMessage to the old, so it will always send out
the change request.</P></LI>
<LI><P>If you Unregister() an endpoint that is connected, the connection should
not be broken.</P></LI>
</UL>
<HR SIZE="1">
<H2>Consulting the roster</H2>
<P><I>Required:</I> Client app that creates several endpoints, and registers
some of them (not all), and uses the BMidiRoster::FindEndpoint() etc functions
to examine the roster.</P>
<UL>
<LI><P>Verify that FindEndpoint() returns NULL if you pass it:</P>
<UL>
<LI>invalid ID (localOnly = false)</LI>
<LI>invalid ID (localOnly = true)</LI>
<LI>remote non-registered endpoint (localOnly = false)</LI>
<LI>remote non-registered endpoint (localOnly = true)</LI>
<LI>remote registered endpoint (localOnly = true)</LI>
</UL><BR>
<P>Verify that FindEndpoint() returns a valid BMidiEndpoint object if you pass
it:</P>
<UL>
<LI>local non-registered endpoint (localOnly = false)</LI>
<LI>local non-registered endpoint (localOnly = true)</LI>
<LI>local registered endpoint (localOnly = false)</LI>
<LI>local registered endpoint (localOnly = true)</LI>
<LI>remote registered endpoint (localOnly = false)</LI>
</UL><BR>
</LI>
<LI><P>Verify that FindConsumer() works just like FindEndpoint(), but that it
also returns NULL if the endpoint with the specified ID is not a consumer.
Likewise for FindProducer().</P></LI>
<LI><P>Verify that NextEndpoint() returns NULL if you pass it NULL. It also
returns NULL if no more endpoints exist. Otherwise, it returns a BMidiEndpoint
object, bumps the endpoint's reference count, and sets the "id" parameter to
the ID of the endpoint. NextEndpoint() should never return local endpoints
(registered or not), nor unregistered remote endpoints. Verify that negative
"id" values also work.</P></LI>
<LI><P>Verify that you can safely call the Find and Next functions without
having somehow initialized the BMidiRoster first (by making a new endpoint, for
example). The functions themselves should call MidiRoster() and do the
handshake with the server.</P></LI>
<LI><P>The Find and Next functions should bump the reference count of the
BMidiEndpoint object that they return. However, they should not (inadvertently)
modify the refcounts of any other endpoint objects.</P></LI>
<LI><P>Get a BMidiEndpoint proxy for a remote published endpoint. Release().
Now it should not be removed from the endpoint list or even be deleted, even
though its reference count dropped to zero.</P></LI>
<LI><P>Start app1. Start app2. App2 gets a BMidiEndpoint proxy for a remote
endpoint from app1. Ctrl-C app1. Start app1 again. Now app2 receives an mDEL
message for app1's old endpoint. Verify that the endpoint is removed from the
endpoint list, but not deleted because its reference count isn't zero. If app2
now Release()s the endpoint, the BMidiEndpoint object should be deleted. Try
again, but now Release() the endpoint before you Ctrl-C; now it should be
deleted and removed from the list when you start app1 again.</P></LI>
</UL>
<HR SIZE="1">
<H2>Making/breaking connections</H2>
<P><I>Required:</I> Client app that creates a producer and consumer endpoint,
optionally registers them, consults the roster for remote endpoints, and makes
various kinds of connections.</P>
<UL>
<LI><P>Test the following for BMidiProducer::Connect():</P>
<UL>
<LI>Connect(NULL)</LI>
<LI>Connect(invalid consumer)</LI>
<LI>Connect() using an invalid producer</LI>
<LI>Send Mcon request with invalid IDs</LI>
<LI>Kill the midi_server just before you Connect()</LI>
<LI>Let the midi_server snooze, so the connect request times out</LI>
<LI>Have the midi_server return an error result code</LI>
<LI>On successful connect, verify that the consumer is added to the producer's
list of endpoints</LI>
<LI>Verify that you can make connections between 2 local endpoints, a local
producer and a remote consumer, a remote producer and a local consumer, and two
2 remote endpoints. Test the local endpoints both registered and
unregistered.</LI>
<LI>2x Connect() on same consumer should give an error</LI>
<LI>The other applications should receive an mCON notification, and adjust
their own local rosters accordingly</LI>
<LI>If you are calling Connect() on a local producer, its Connected() hook
should be called. If you are calling Connect() on a remote producer, then its
own application should call the Connected() hook.</LI>
</UL><BR></LI>
<LI><P>Test the following for BMidiProducer::Disconnect():</P>
<UL>
<LI>Disconnect(NULL)</LI>
<LI>Disconnect(invalid consumer)</LI>
<LI>Disconnect() using an invalid producer</LI>
<LI>Send Mdis request with invalid IDs</LI>
<LI>Kill the midi_server just before you Disconnect()</LI>
<LI>Let the midi_server snooze, so the disconnect request times out</LI>
<LI>Have the midi_server return an error result code</LI>
<LI>On successful disconnect, verify that the consumer is removed from the
producer's list of endpoints</LI>
<LI>Verify that you can break connections between 2 local endpoints, a local
producer and a remote consumer, a remote producer and a local consumer, and two
2 remote endpoints. Test the local endpoints both registered and
unregistered.</LI>
<LI>Disconnecting 2 endpoints that were not connected should give an error</LI>
<LI>The other applications should receive an mDIS notification, and adjust
their own local rosters accordingly</LI>
<LI>If you are calling Disconnect() on a local producer, its Disconnected()
hook should be called. If you are calling Disconnect() on a remote producer,
then its own application should call the Disconnected() hook.</LI>
</UL><BR></LI>
<LI><P>Make a connection on a local producer. Release() the producer. The other
app should only receive an mDEL notification. Likewise if you have a connection
with a local consumer and you Release() that. However, now all apps should
throw away this consumer from the connection lists, invoking the Disconnected()
hook of local producers. The same thing happens if you Ctrl-C the app and
restart it. (Now the old endpoints are purged.)</P></LI>
<LI><P>BMidiProducer::IsConnected() should return false if you pass NULL or an
invalid consumer.</P></LI>
<LI><P>BMidiProducer::Connections() should return a new BList every time you
call it. The objects in this list are the BMidiConsumers that are connected to
this producer; verify that their reference counts are bumped for every call to
Connections().</P></LI>
</UL>
<HR SIZE="1">
<H2>Watching</H2>
<P><I>Required:</I> Client app that creates local consumer and producer
endpoints, and calls Register(), Unregister(), SetName(), SetLatency(), and
SetProperties(). It should also make and break connections.</P>
<UL>
<LI><P>When you call StartWatching(), you should receive B_MIDI_EVENT
notifications for all remote registered endpoints and the connections between
them. You will get no notifications for local endpoints, or for any connections
that involve unregistered endpoints. The BMidiRosterLooper should make a copy
of the BMessenger, so when the client destroys the original messenger, you will
still receive notifications. Verify that calling StartWatching() with the same
BMessenger twice in a row will also send the initial set of notifications
twice. StartWatching(NULL) should be ignored and does not remove the current
messenger.</P></LI>
<LI><P>Run the client app from two different Terminals. Verify that you receive
properly formatted B_MIDI_EVENT notifications when the other app changes the
attributes of its <I>registered</I> endpoints with the various Set() functions.
You should also receive notifications if the app Register()s or Unregister()s
its endpoints. That app that makes these changes does not receive the
notifications.</P></LI>
<LI><P>Run the client app from two different Terminals. Verify that you receive
properly formatted B_MIDI_EVENT notifications when the apps make and break
connections. Every app receives these connection notifications, whether the
endpoints are published or not. The app that makes and breaks the connections
does not receive any notifications.</P></LI>
<LI><P>StopWatching() should delete BMidiRosterLooper's BMessenger copy, if
any. Verify that you no longer receive B_MIDI_EVENT notifications for remote
endpoints after you have called StopWatching().</P></LI>
<LI><P>If the client is watching, and the BMidiRosterLooper receives an mDEL
notification for a registered remote endpoint, it should also send an
"unregistered" B_MIDI_EVENT to let the client know that this endpoint is no
longer available. If the endpoint was connected to anything, you'll also
receive "disconnected" B_MIDI_EVENTs.</P></LI>
<LI><P>If you get a "registered" event, and you do FindEndpoint() for that id,
you'll get its BMidiEndpoint object. If you get an "unregistered" event, then
FindEndpoint() returns NULL. So the events are send <I>after</I> the roster is
modified.</P></LI>
</UL>
<HR SIZE="1">
<H2>Event tests</H2>
<P><I>Required:</I> Several client apps that create and register consumer
endpoints that override the various MIDI event hook functions, as well as
producer endpoints that spray MIDI events. Also useful is a tool that lets you
make connections between all these endpoints (PatchBay), and a tool that lets
you monitor the MIDI events (MidiMonitor).</P>
<UL>
<LI><P>BMidiLocalProducer's spray functions should only try to send something
if there is one or more connected consumer. If the spray functions cannot
deliver their events, they simply ignore that consumer until the next spray.
(No connections are broken or anything.)</P></LI>
<LI><P>All spray functions except SprayData() should set the atomic flag to
true, even SpraySystemExclusive().</P></LI>
<LI><P>When you send a sysex message using SpraySystemExclusive(), it should
add 0xF0 in front of your data and 0xF7 at the back. When you call SprayData()
instead, no bytes are added to the MIDI event data.</P></LI>
<LI><P>Verify that all events arrive correctly and that the latency is minimal,
even when the load is heavy (i.e. many events are being sprayed to many
different consumers).</P></LI>
<LI><P>Verify that the BMidiLocalConsumer destructor properly destroys the
corresponding port and event thread before it returns.</P></LI>
<LI><P>BMidiLocalConsumer should ignore messages that are too small, addressed
to another consumer, or otherwise invalid.</P></LI>
<LI><P>BMidiLocalConsumer's Data() hook should ignore all non-atomic events.
The rest of the events, provided they contain the correct number of bytes for
that kind of event, are passed on to the other hooks.</P></LI>
<LI><P>Hook a producer up to a consumer and call all SprayXXX() functions with
a variety of arguments to make sure the correct hooks are being called with the
correct values. Call SprayData() and SpraySystemExclusive() with NULL data
and/or length 0.</P></LI>
<LI><P>Call GetProducerID() from one of BMidiLocalConsumer's hooks to verify
that this indeed returns the ID of the producer that sprayed the
event.</P></LI>
<LI><P>To test timeouts, first call SetTimeout(system_time() + 2000000), spray
an event to the consumer, and wait 2 seconds. The consumer's Timeout() hook
should now be called. Try again, but now spray multiple events to the consumer.
The Timeout() hook should still be called after 2 seconds, measured from the
moment the timeout was set. Replace the call to SetTimeout() with
SetTimeout(0). After spraying the first event, you should immediately get the
Timeout() signal, because the target time was set in the past. Verify that
calling SetTimeout() only takes effect after at least one new event has been
received.</P></LI>
</UL>
<HR SIZE="1">
<H2>Other tests</H2>
<UL>
<LI><P>Kill the server. Now run a client app. It should recognize that the
server isn't running, and return error codes on all operations. Also kill the
server while the test app is running. From then on, the client app will return
error codes on all operations. Also bring it back up again while the test app
is still running. Now the client app's request messages will be delivered to
the server again, but the server will ignore them, because our app did not
register with this new instance of the server.</P></LI>
<LI><P>Start the midi_server and several client apps. Use PatchBay to make and
break a whole bunch of connections. Quit PatchBay. Start it again. Now the same
connections should show up. Run similar tests with MidiKeyboard. Also install
VirtualMidi (and run the old midi_server for the time being) to get a whole
bunch of fake MIDI devices.</P></LI>
<LI><P><I>Regression bug:</I> After you quit one client app, another app fails
to send request to the midi_server.</P>
<P><I>Required:</I> Client app that creates a new endpoint and registers it. In
the app's destructor, it unregisters and releases the endpoint.</P>
<P><I>How to reproduce:</I> Run the app from two different Terminals. Ctrl-C
app1. Start app1 again. From the Deskbar quit both apps at the same time (that
is possible because app1 and app2 both have the same signature). When it tries
to send the Unregister() request to the midi_server, app2 gives the error
"Cannot send msg to server". The error code is "Bad Port ID", which means that
the reply port is dead. The Mdel message from Release() is sent without any
problems, however, because that expects no reply back. This is not the only way
to reproduce the problem, but it seems to be the most reliable one.</P>
<P>The reason this happens is because you kill app1. When app2 sends a
synchronous request to the midi_server, the server re-used that same message to
notify the other apps. (Because it already contained all the necessary fields.)
But app1 is dead, the notification fails, and this (probably) wipes out the
reply address in the message. I changed the midi_server to create new BMessages
for the notifications, and was no longer able to reproduce the
problem.</P></LI>
</UL>
</BODY>
</HTML>
+555
View File
@@ -0,0 +1,555 @@
Testing the Midi Kit
====================
Most of the OpenBeOS source code has unit tests in the current/src/tests
directory. I looked into building CppUnit tests for the midi2 kit, but
decided that it doesn't really make much sense. Unit tests work best if
you can test something in isolation, but in the case of the midi2 kit
this is very hard to achieve. Because the classes from libmidi2.so
always need to talk to the midi_server, the tests depend on too many
external factors. The available endpoints, for example, will differ from
system to system. The spray and hook functions are difficult to test
this way, too.
So instead of a CppUnit test suite, here is a list of manual tests that
I performed when developing the midi2 kit:
--------------
Registering the application
---------------------------
*Required:* Client app that calls BMidiRoster::MidiRoster()
- When a client app starts, it should first receive mNEW notifications
for all endpoints in the system (even unregistered remotes), followed
by mCON notifications for all connections in the system (even those
between two unregistered local endpoints from another app).
- Send invalid Mapp message (without messenger). The midi_server
ignores the request, and the client app blocks forever.
- Fake a delivery error for the mNEW notifications and the mAPP reply.
(Add a snooze() in the midi_server's OnRegisterApplication(). While
it is snoozing, Ctrl-C the client app. Now the server can't deliver
the message and will unregister the application again.)
- Kill the server. Start the client app. It should realize that the
server is not running, and return from MidiRoster(); it does not
block forever.
- Note: The server does not protect against sending two or more Mapp
messages; it will add a new app_t object to the roster and it will
also send out the mNEW and mCON notifications again.
- Verify that when the client app quits, the BMidiRoster instance is
destroyed by the BMidiRosterKiller. The BMidiRosterLooper is also
destroyed, along with any endpoint objects from its list. We don't
destroy endpoints with a refcount > 0, but print a warning message on
stderr instead.
- When the app quits before it has created a BMidiRoster instance, the
BMidiRosterKiller should do nothing.
--------------
Creating endpoints
------------------
*Required:* Client app that creates a new BMidiLocalProducer and/or
BMidiLocalConsumer
- Send invalid Mnew message (missing fields). The server will return an
error code.
- Don't send reply from midi_server. The client receives a B_NO_REPLY
error.
- If something goes wrong creating a new local endpoint, you still get
a new BMidiEndpoint object (but it is not added to
BMidiRosterLooper's internal list of endpoints). Verify that its ID()
function returns 0, and IsValid() returns false. Verify that you can
Release() it without crashing into the debugger (i.e. the reference
count of the new object should be 1).
- Snooze in midi_server's OnCreateEndpoint() before sending reply to
client to simulate heavy processor load. Client should timeout. When
done snoozing, server fails to deliver the reply because the client
is no longer listening, and it unregisters the app.
- Note: if you kill the client app with Ctrl-C before the server has
sent its reply, SendReply() still returns okay, and the midi_server
adds the endpoint, even though the corresponding app is dead. There
is not much we can do to prevent that (but it is not really a big
deal).
- Start the test app from two different Terminals. Verify that the new
local endpoint of app1 is added to the BMidiRosterLooper's list of
endpoints, and that its "isLocal" flag is true. Verify that when you
start the second app, it immediately receives mNEW notifications for
the first app's endpoints. It should also create BMidiEndpoint proxy
objects for these endpoints with "isLocal" set to false, and add them
its own list. Vice versa for the endpoints that app2 creates. Verify
that the "registered" field in the mNEW notification is false,
because newly created endpoints are not registered yet. The
"properties" field should contain an empty message.
- Start server. Start client app. The app makes new endpoints and the
server adds them to the roster. Ctrl-C the app. Start client app
again. The new client first receives mNEW notifications for the old
app's endpoints. When the new app tries to create its own endpoints,
the server realizes that the old app is dead, and sends mDEL
notifications for the now-defunct endpoints.
- The test app should now create 2 endpoints. Let the midi_server
snooze during the second create message, so the app times out. The
server now unregisters the app and purges its first endpoint (which
was successfully created).
- The test app should now create 3 endpoints. Let the midi_server
snooze during the second create message, so the app times out. (It
also times out when sending the create request for the 3rd endpoint,
because the server is still snoozing.) Because it cannot send a reply
for the 2nd create message, the server now unregisters the app and
purges its first endpoint (which was successfully created). Then it
processes the create request for the 3rd endpoint, but ignores it
because the app is now no longer registered with the server.
- Purging endpoints. The test app should now create 2 endpoints. Let
the midi_server snooze during the \_fourth\_ create message. Run the
server. Run the test app. Run the test app again in a second
Terminal. The server times out, and unregisters the second app. The
first app should receive an mDEL notification. Repeat, but now the
test app should make 3 endpoints and the server fails on the
\_sixth\_ endpoint. The first app now receives 2 mDEL notifications.
- You should be allowed to pass NULL into the BMidiLocalProducer and
BMidiLocalConsumer constructor.
- Let the midi_server assign random IDs to new endpoints; the
BMidiRosterLooper should sort the endpoints by their IDs when it adds
them to its internal list.
--------------
Deleting endpoints
------------------
*Required:* client app that creates one or more endpoints and
Release()'s them
- Verify that Acquire() increments the endpoint's refcount and
Release() decrements it. When you Release() a local endpoint so its
refcount becomes zero, the client sends an Mdel request to the
server. When you Release() a local endpoint too many times, your app
jumps into the debugger.
- Send an Mdel request with an invalid ID to the server. Examples of
invalid IDs: -1, 0, 1000 (or any other large number).
- Start the test app from two different Terminals. Note that when one
of the apps Release()'s its endpoints, the other receives
corresponding mDEL notifications.
- Snooze in midi_server's OnCreateEndpoint() before sending reply to
"create endpoint" request. The client will timeout and the server
will unregister the app. Now have the client Release() the endpoint.
This sends a "delete endpoint" request to the server, which ignores
the request because the app is no longer registered.
- Override BMidiLocalProducer and BMidiLocalConsumer, and provide a
public destructor. Call "delete prod; delete cons;" from your code,
instead of using Release(). Your app should drop into the debugger.
- Start the client app and let it make its endpoints. Kill the server.
Release() the endpoints. The server doesn't run, so the Mdel request
never arrives, but the BMidiEndpoint objects should be deleted
regardless.
- Start the test app from two different Terminals, and let them make
their endpoints. Quit the apps (using the Deskbar's "Quit
Application" menu item). Verify that both clean up and exit
correctly. App1 removes its own endpoint from the BMidiRosterLooper's
list of endpoints and sends an 'mDEL' message to the server, which
passes it on to app2. In response, app2 removes the proxy object from
its own list and deletes it. Again, vice versa for the endpoint from
app2.
- Start both apps again and wait until they have notified each other
about the endpoints. Ctrl-C app1, and restart it. Verify that app1
receives the 'mNEW' messages and creates proxies for these remote
endpoints. Both apps should receive an 'mDEL' message for app1's old
endpoint (because the midi_server realizes it no longer exists and
purges it), and remove it from their lists accordingly.
--------------
Changing attributes
-------------------
*Required:* Client app that creates an endpoint and calls Register(),
Unregister(), SetName(), and SetLatency()
- Send an Mchg request with an invalid ID to the server.
- Register() a local endpoint that is already registered. This does not
send a message to the server and always returns B_OK. Likewise for
Unregister()ing a local endpoint that is not registered.
- Register() or Unregister() a remote endpoint, or an invalid local
endpoint. That should immediately return an error code.
- Verify that BMidiRoster::Register() does the same thing as
BMidiEndpoint::Register(). Also for BMidiRoster::Unregister() and
BMidiEndpoint::Unregister().
- If you pass NULL into BMidiRoster::Register() or Unregister(), the
functions immediately return with an error code.
- SetName() should ignore NULL names. When you call it on a remote
endpoint, SetName() should do nothing. SetName() does not send a
message if the new name is the same as the current name.
- SetLatency() should ignore negative values. SetLatency() does not
send a message if the new latency is the same as the current latency.
(Since SetLatency() lives in BMidiLocalConsumer, you can never use it
on remote endpoints.)
- Kill the server after making the new endpoint, and call Register().
The client app should return an error code. Also for Unregister(),
SetName(), SetLatency(), and SetProperties().
- Snooze in the midi_server's OnChangeEndpoint() before sending the
reply to the client. Both sides will flag an error. No mCHG
notifications will be sent. The server unregisters the app and purges
its endpoints.
- Verify that other apps will receive mCHG notifications when the test
app successfully calls Register(), Unregister(), SetName(), and
SetLatency(), and that they modify the corresponding BMidiEndpoint
objects accordingly. Since clients are never notified when they
change their own endpoints, they should ignore the notifications that
concern local endpoints. Latency changes should be ignored if the
endpoint is not a consumer.
- Send an Mchg request with only the "midi:id" field, so no
"midi:name", "midi:registered", "midi:latency", or "midi:properties".
The server will still notify the other apps, although they will
obviously ignore the notification, because it doesn't contain any
useful data.
- The Mchg request is overloaded to change several attributes. Verify
that changing one of these attributes, such as the latency, does not
overwrite/wipe out the others.
- Start app1. Wait until it has created and registered its endpoint.
Start app2. During the initial handshake, app2 should receive an
'mNEW' message for app1's endpoint. Verify that the "refistered"
field in this message is already true, and that this is passed on
correctly to the new BMidiEndpoint proxy object.
- GetProperties() should return NULL if the message parameter is NULL.
- The properties of new endpoints are empty. Create a new endpoint and
call GetProperties(). The BMessage that you receive should contain no
fields.
- SetProperties() should return NULL if the message parameter is NULL.
It should return an error code if the endpoint is remote or invalid.
It should work fine on local endpoints, registered or not.
SetProperties() does not compare the contents of the new BMessage to
the old, so it will always send out the change request.
- If you Unregister() an endpoint that is connected, the connection
should not be broken.
--------------
Consulting the roster
---------------------
*Required:* Client app that creates several endpoints, and registers
some of them (not all), and uses the BMidiRoster::FindEndpoint() etc
functions to examine the roster.
- Verify that FindEndpoint() returns NULL if you pass it:
- invalid ID (localOnly = false)
- invalid ID (localOnly = true)
- remote non-registered endpoint (localOnly = false)
- remote non-registered endpoint (localOnly = true)
- remote registered endpoint (localOnly = true)
|
Verify that FindEndpoint() returns a valid BMidiEndpoint object if
you pass it:
- local non-registered endpoint (localOnly = false)
- local non-registered endpoint (localOnly = true)
- local registered endpoint (localOnly = false)
- local registered endpoint (localOnly = true)
- remote registered endpoint (localOnly = false)
|
- Verify that FindConsumer() works just like FindEndpoint(), but that
it also returns NULL if the endpoint with the specified ID is not a
consumer. Likewise for FindProducer().
- Verify that NextEndpoint() returns NULL if you pass it NULL. It also
returns NULL if no more endpoints exist. Otherwise, it returns a
BMidiEndpoint object, bumps the endpoint's reference count, and sets
the "id" parameter to the ID of the endpoint. NextEndpoint() should
never return local endpoints (registered or not), nor unregistered
remote endpoints. Verify that negative "id" values also work.
- Verify that you can safely call the Find and Next functions without
having somehow initialized the BMidiRoster first (by making a new
endpoint, for example). The functions themselves should call
MidiRoster() and do the handshake with the server.
- The Find and Next functions should bump the reference count of the
BMidiEndpoint object that they return. However, they should not
(inadvertently) modify the refcounts of any other endpoint objects.
- Get a BMidiEndpoint proxy for a remote published endpoint. Release().
Now it should not be removed from the endpoint list or even be
deleted, even though its reference count dropped to zero.
- Start app1. Start app2. App2 gets a BMidiEndpoint proxy for a remote
endpoint from app1. Ctrl-C app1. Start app1 again. Now app2 receives
an mDEL message for app1's old endpoint. Verify that the endpoint is
removed from the endpoint list, but not deleted because its reference
count isn't zero. If app2 now Release()s the endpoint, the
BMidiEndpoint object should be deleted. Try again, but now Release()
the endpoint before you Ctrl-C; now it should be deleted and removed
from the list when you start app1 again.
--------------
Making/breaking connections
---------------------------
*Required:* Client app that creates a producer and consumer endpoint,
optionally registers them, consults the roster for remote endpoints, and
makes various kinds of connections.
- Test the following for BMidiProducer::Connect():
- Connect(NULL)
- Connect(invalid consumer)
- Connect() using an invalid producer
- Send Mcon request with invalid IDs
- Kill the midi_server just before you Connect()
- Let the midi_server snooze, so the connect request times out
- Have the midi_server return an error result code
- On successful connect, verify that the consumer is added to the
producer's list of endpoints
- Verify that you can make connections between 2 local endpoints, a
local producer and a remote consumer, a remote producer and a
local consumer, and two 2 remote endpoints. Test the local
endpoints both registered and unregistered.
- 2x Connect() on same consumer should give an error
- The other applications should receive an mCON notification, and
adjust their own local rosters accordingly
- If you are calling Connect() on a local producer, its Connected()
hook should be called. If you are calling Connect() on a remote
producer, then its own application should call the Connected()
hook.
|
- Test the following for BMidiProducer::Disconnect():
- Disconnect(NULL)
- Disconnect(invalid consumer)
- Disconnect() using an invalid producer
- Send Mdis request with invalid IDs
- Kill the midi_server just before you Disconnect()
- Let the midi_server snooze, so the disconnect request times out
- Have the midi_server return an error result code
- On successful disconnect, verify that the consumer is removed from
the producer's list of endpoints
- Verify that you can break connections between 2 local endpoints, a
local producer and a remote consumer, a remote producer and a
local consumer, and two 2 remote endpoints. Test the local
endpoints both registered and unregistered.
- Disconnecting 2 endpoints that were not connected should give an
error
- The other applications should receive an mDIS notification, and
adjust their own local rosters accordingly
- If you are calling Disconnect() on a local producer, its
Disconnected() hook should be called. If you are calling
Disconnect() on a remote producer, then its own application should
call the Disconnected() hook.
|
- Make a connection on a local producer. Release() the producer. The
other app should only receive an mDEL notification. Likewise if you
have a connection with a local consumer and you Release() that.
However, now all apps should throw away this consumer from the
connection lists, invoking the Disconnected() hook of local
producers. The same thing happens if you Ctrl-C the app and restart
it. (Now the old endpoints are purged.)
- BMidiProducer::IsConnected() should return false if you pass NULL or
an invalid consumer.
- BMidiProducer::Connections() should return a new BList every time you
call it. The objects in this list are the BMidiConsumers that are
connected to this producer; verify that their reference counts are
bumped for every call to Connections().
--------------
Watching
--------
*Required:* Client app that creates local consumer and producer
endpoints, and calls Register(), Unregister(), SetName(), SetLatency(),
and SetProperties(). It should also make and break connections.
- When you call StartWatching(), you should receive B_MIDI_EVENT
notifications for all remote registered endpoints and the connections
between them. You will get no notifications for local endpoints, or
for any connections that involve unregistered endpoints. The
BMidiRosterLooper should make a copy of the BMessenger, so when the
client destroys the original messenger, you will still receive
notifications. Verify that calling StartWatching() with the same
BMessenger twice in a row will also send the initial set of
notifications twice. StartWatching(NULL) should be ignored and does
not remove the current messenger.
- Run the client app from two different Terminals. Verify that you
receive properly formatted B_MIDI_EVENT notifications when the other
app changes the attributes of its *registered* endpoints with the
various Set() functions. You should also receive notifications if the
app Register()s or Unregister()s its endpoints. That app that makes
these changes does not receive the notifications.
- Run the client app from two different Terminals. Verify that you
receive properly formatted B_MIDI_EVENT notifications when the apps
make and break connections. Every app receives these connection
notifications, whether the endpoints are published or not. The app
that makes and breaks the connections does not receive any
notifications.
- StopWatching() should delete BMidiRosterLooper's BMessenger copy, if
any. Verify that you no longer receive B_MIDI_EVENT notifications for
remote endpoints after you have called StopWatching().
- If the client is watching, and the BMidiRosterLooper receives an mDEL
notification for a registered remote endpoint, it should also send an
"unregistered" B_MIDI_EVENT to let the client know that this endpoint
is no longer available. If the endpoint was connected to anything,
you'll also receive "disconnected" B_MIDI_EVENTs.
- If you get a "registered" event, and you do FindEndpoint() for that
id, you'll get its BMidiEndpoint object. If you get an "unregistered"
event, then FindEndpoint() returns NULL. So the events are send
*after* the roster is modified.
--------------
Event tests
-----------
*Required:* Several client apps that create and register consumer
endpoints that override the various MIDI event hook functions, as well
as producer endpoints that spray MIDI events. Also useful is a tool that
lets you make connections between all these endpoints (PatchBay), and a
tool that lets you monitor the MIDI events (MidiMonitor).
- BMidiLocalProducer's spray functions should only try to send
something if there is one or more connected consumer. If the spray
functions cannot deliver their events, they simply ignore that
consumer until the next spray. (No connections are broken or
anything.)
- All spray functions except SprayData() should set the atomic flag to
true, even SpraySystemExclusive().
- When you send a sysex message using SpraySystemExclusive(), it should
add 0xF0 in front of your data and 0xF7 at the back. When you call
SprayData() instead, no bytes are added to the MIDI event data.
- Verify that all events arrive correctly and that the latency is
minimal, even when the load is heavy (i.e. many events are being
sprayed to many different consumers).
- Verify that the BMidiLocalConsumer destructor properly destroys the
corresponding port and event thread before it returns.
- BMidiLocalConsumer should ignore messages that are too small,
addressed to another consumer, or otherwise invalid.
- BMidiLocalConsumer's Data() hook should ignore all non-atomic events.
The rest of the events, provided they contain the correct number of
bytes for that kind of event, are passed on to the other hooks.
- Hook a producer up to a consumer and call all SprayXXX() functions
with a variety of arguments to make sure the correct hooks are being
called with the correct values. Call SprayData() and
SpraySystemExclusive() with NULL data and/or length 0.
- Call GetProducerID() from one of BMidiLocalConsumer's hooks to verify
that this indeed returns the ID of the producer that sprayed the
event.
- To test timeouts, first call SetTimeout(system_time() + 2000000),
spray an event to the consumer, and wait 2 seconds. The consumer's
Timeout() hook should now be called. Try again, but now spray
multiple events to the consumer. The Timeout() hook should still be
called after 2 seconds, measured from the moment the timeout was set.
Replace the call to SetTimeout() with SetTimeout(0). After spraying
the first event, you should immediately get the Timeout() signal,
because the target time was set in the past. Verify that calling
SetTimeout() only takes effect after at least one new event has been
received.
--------------
Other tests
-----------
- Kill the server. Now run a client app. It should recognize that the
server isn't running, and return error codes on all operations. Also
kill the server while the test app is running. From then on, the
client app will return error codes on all operations. Also bring it
back up again while the test app is still running. Now the client
app's request messages will be delivered to the server again, but the
server will ignore them, because our app did not register with this
new instance of the server.
- Start the midi_server and several client apps. Use PatchBay to make
and break a whole bunch of connections. Quit PatchBay. Start it
again. Now the same connections should show up. Run similar tests
with MidiKeyboard. Also install VirtualMidi (and run the old
midi_server for the time being) to get a whole bunch of fake MIDI
devices.
- *Regression bug:* After you quit one client app, another app fails to
send request to the midi_server.
*Required:* Client app that creates a new endpoint and registers it.
In the app's destructor, it unregisters and releases the endpoint.
*How to reproduce:* Run the app from two different Terminals. Ctrl-C
app1. Start app1 again. From the Deskbar quit both apps at the same
time (that is possible because app1 and app2 both have the same
signature). When it tries to send the Unregister() request to the
midi_server, app2 gives the error "Cannot send msg to server". The
error code is "Bad Port ID", which means that the reply port is dead.
The Mdel message from Release() is sent without any problems,
however, because that expects no reply back. This is not the only way
to reproduce the problem, but it seems to be the most reliable one.
The reason this happens is because you kill app1. When app2 sends a
synchronous request to the midi_server, the server re-used that same
message to notify the other apps. (Because it already contained all
the necessary fields.) But app1 is dead, the notification fails, and
this (probably) wipes out the reply address in the message. I changed
the midi_server to create new BMessages for the notifications, and
was no longer able to reproduce the problem.
-45
View File
@@ -1,45 +0,0 @@
<html>
<body>
<h3>OpenBeOS Midi Kit TO DO List</h3>
<p>May 14, 2004</p>
<h3>Big Things</h3>
<p><b>Softsynth.</b> We need a General MIDI softsynth. We can either write it ourselves or port an existing one. I was thinking about FluidSynth (formerly IIWU Synth) since it is very good and supports SoundFonts, but I had trouble writing a BeOS audio driver for it. FluidSynth is also GPL, but that is not a major problem. Must be implemented in the BSoftSynth class from libmidi.so</p>
<p><b>Test libmidi.so</b> The BMidiSynth, BMidiSynthFile, BSamples, and BSynth classes have been implemented and briefly tested. They seemed to work fine, even with the R5 MidiPlayer app. However, the only proper way to test them is with sound output, and for that we need the softsynth.</p>
<h3>Small Things</h3>
<p><b>Communicating with device drivers.</b> The midi_server already has a pretty good parser that turns an incoming stream of bytes into MIDI messages. It uses read() to read a single byte at a time. However, the midi_driver.h file lists a number of ioctl() opcodes that we are currently not using. Should we? In addition, do we really need to spawn a new thread for each device? The R5 midi_server doesn't appear to do this. An optional feature is to implement "running status" for MIDI OUT ports (i.e. when writing to the device driver). This would be pretty simple to add.</p>
<p><b>BMidiStore is slow.</b> Importing a Standard MIDI File of a few hundred kilobytes takes too long for my taste. The one from R5 is at least twice as fast. It is important to speed this up since BMidiStore is used by BMidiSynthFile to play MIDI files. We don't want games to slow down too much.</p>
<p><b>MPU401 kernel module.</b> Greg Crain did a great job of writing this module. Unfortunately, we only know how the v1 interface works; v2 is not documented. What's worse, most Be R5 drivers use v2. Currently, the module returns B_ERROR when a device is opened with v2. Is this going to be a problem for us? It depends on whether we will be able to use the closed-source Be drivers with our own kernel &mdash; if not, then we can simply ignore v2.</p>
<p><b>BSynth::GetAudio()</b> This function fills up a user-provided buffer with recent sample data. Mostly used to display scopes and other visual trickery. Whether we will support this or not depends on the capabilities of the softsynth back-end.</p>
<p><b>Watching /dev/midi for changes.</b> Whenever a new device appears in /dev/midi, the midi_server must create and publish a new MidiProducer and MidiConsumer for that device. When a device disappears, its endpoints must be removed again. Philippe Houdoin suggested we use the device_watcher for this, but R5 doesn't appear to do it that way. Either it uses node monitoring or doesn't do this at all. Our midi_server already has a DeviceWatcher class, but it only examines the entries from /dev/midi when the server starts, not while the server is running.</p>
<p><b>BMidiSynthFile::Fade()</b> Right now this simply calls Stop(). We could set a flag in BMidiStore (which handles our playback), which would then slowly reduce the volume and abort the loop after a few seconds. But we need to have the softsynth in order to tune this properly.</p>
<p><b>Must be_synth be deleted when the app quits?</b> I have not found a word about this, nor a way to test what happens in R5. For example, the BMidiSynth constructor creates a BSynth object (if none already existed), but we cannot destroy be_synth from the BMidiSynth destructor because it may still be used in other places in the code (BSynth is not refcounted). We could add the following code to libmidi.so to clean up properly, but I don't know if it is really necessary:</p>
<blockquote><pre>namespace BPrivate
{
static struct BSynthKiller
{
~BSynthKiller()
{
delete be_synth;
}
}
synth_killer;
}</pre></blockquote>
<p><b>midiparser kernel module.</b> midi_driver.h (from the Sonic Vibes driver sample code) contains the definition of a "midiparser" kernel module. This is a very simple module that makes it easy to recognize where MIDI messages begin and end, but apparently doesn't tell you what they mean. In R5, this module lives in /boot/beos/system/add-ons/kernel/media. Does anyone use this module? Is it necessary for us to provide it? Personally, I'd say foggeddaboutit.</p>
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
Midi Kit TO DO List
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Communicating with device drivers.** The midi_server already has a
pretty good parser that turns an incoming stream of bytes into MIDI
messages. It uses read() to read a single byte at a time. However, the
midi_driver.h file lists a number of ioctl() opcodes that we are
currently not using. Should we? In addition, do we really need to spawn
a new thread for each device? The R5 midi_server doesn't appear to do
this. An optional feature is to implement "running status" for MIDI OUT
ports (i.e. when writing to the device driver). This would be pretty
simple to add.
**BMidiStore is slow.** Importing a Standard MIDI File of a few hundred
kilobytes takes too long for my taste. The one from R5 is at least twice
as fast. It is important to speed this up since BMidiStore is used by
BMidiSynthFile to play MIDI files. We don't want games to slow down too
much.
**MPU401 kernel module.** Greg Crain did a great job of writing this
module. Unfortunately, we only know how the v1 interface works; v2 is
not documented. What's worse, most Be R5 drivers use v2. Currently, the
module returns B_ERROR when a device is opened with v2. Is this going to
be a problem for us? It depends on whether we will be able to use the
closed-source Be drivers with our own kernel — if not, then we can
simply ignore v2.
**Watching /dev/midi for changes.** Whenever a new device appears in
/dev/midi, the midi_server must create and publish a new MidiProducer
and MidiConsumer for that device. When a device disappears, its
endpoints must be removed again. Philippe Houdoin suggested we use the
device_watcher for this, but R5 doesn't appear to do it that way. Either
it uses node monitoring or doesn't do this at all. Our midi_server
already has a DeviceWatcher class, but it only examines the entries from
/dev/midi when the server starts, not while the server is running.
**BMidiSynthFile::Fade()** Right now this simply calls Stop(). We could
set a flag in BMidiStore (which handles our playback), which would then
slowly reduce the volume and abort the loop after a few seconds. But we
need to have the softsynth in order to tune this properly.
**Must be_synth be deleted when the app quits?** I have not found a word
about this, nor a way to test what happens in R5. For example, the
BMidiSynth constructor creates a BSynth object (if none already
existed), but we cannot destroy be_synth from the BMidiSynth destructor
because it may still be used in other places in the code (BSynth is not
refcounted). We could add the following code to libmidi.so to clean up
properly, but I don't know if it is really necessary:
::
namespace BPrivate
{
static struct BSynthKiller
{
~BSynthKiller()
{
delete be_synth;
}
}
synth_killer;
}
**midiparser kernel module.** midi_driver.h (from the Sonic Vibes driver
sample code) contains the definition of a "midiparser" kernel module.
This is a very simple module that makes it easy to recognize where MIDI
messages begin and end, but apparently doesn't tell you what they mean.
In R5, this module lives in /boot/beos/system/add-ons/kernel/media. Does
anyone use this module? Is it necessary for us to provide it?
Personally, I'd say foggeddaboutit.
@@ -1,30 +0,0 @@
How to Merge Patches from NetBSD Trunk
============================================
Using the NetBSD CVS is a pain, so instead, the preferred thing to do is to use
[the official Git mirror](https://github.com/NetBSD/src). The code here is
in the tree at a few places:`inet` is at `lib/libc/inet`, irs is scattered
across the tree, and `resolv` is at `lib/libc/resolv`.
The preferable way to merge is to take the last commit merged from IIJ's mirror
(can be found in the merging commit in Haiku, if the merger has done their work
properly) and check all commits since then to see if they apply or not (some
apply to documentation we don't have, etc.) Cherry-pick the ones that do, and
download them as git-format-patch patches (by adding `.patch` onto the end of the
commit URL).
To convert the patches to have the correct paths to the resolv/inet/etc. code, use
`sed`:
```
sed s%lib/libc/resolv%src/kits/network/netresolv/resolv%g -i *.patch
```
(You'll need to use similar commands for the `inet` and `irs` code.)
Then apply the patches using `git apply --reject file.patch`. Git will spew a lot of
errors about files in the patch that aren't in the tree, and then it will warn that
some hunks are being rejected. Review the rejected hunks **VERY CAREFULLY**, as
some code in Haiku's NetResolv is not in NetBSD's and vice versa, and so some patches
may not apply cleanly because of that. You might have to resort to merging those
hunks by hand, if they apply at all to Haiku's code.
Commit the changes all at once, but list all the commits merged from NetBSD
in the commit message (see previous merges for the style to follow).
@@ -0,0 +1,36 @@
How to Merge Patches from NetBSD Trunk
======================================
Using the NetBSD CVS is a pain, so instead, the preferred thing to do is
to use `the official Git mirror <https://github.com/NetBSD/src>`__. The
code here is in the tree at a few places:\ ``inet`` is at
``lib/libc/inet``, irs is scattered across the tree, and ``resolv`` is
at ``lib/libc/resolv``.
The preferable way to merge is to take the last commit merged from IIJs
mirror (can be found in the merging commit in Haiku, if the merger has
done their work properly) and check all commits since then to see if
they apply or not (some apply to documentation we dont have, etc.)
Cherry-pick the ones that do, and download them as git-format-patch
patches (by adding ``.patch`` onto the end of the commit URL).
To convert the patches to have the correct paths to the resolv/inet/etc.
code, use ``sed``:
::
sed s%lib/libc/resolv%src/kits/network/netresolv/resolv%g -i *.patch
(Youll need to use similar commands for the ``inet`` and ``irs`` code.)
Then apply the patches using ``git apply --reject file.patch``. Git will
spew a lot of errors about files in the patch that arent in the tree,
and then it will warn that some hunks are being rejected. Review the
rejected hunks **VERY CAREFULLY**, as some code in Haikus NetResolv is
not in NetBSDs and vice versa, and so some patches may not apply
cleanly because of that. You might have to resort to merging those hunks
by hand, if they apply at all to Haikus code.
Commit the changes all at once, but list all the commits merged from
NetBSD in the commit message (see previous merges for the style to
follow).
@@ -1,218 +0,0 @@
<html>
<body bgcolor=white>
<h1>Haiku Network Stack Architecture</h1>
<p>
The Haiku Network Stack is a modular and layered networking stack, very
similar to what you may know as BONE.
</p>
<p>
The entry point when talking to the stack is through a dedicated device
driver that publish itself in /dev/net. The userland library libnetwork.so
(which combines libsocket.so, and libbind.so) directly talks to this
driver, mostly via ioctl()<sup><a href="#foot1">1</a></sup>.
</p>
The driver either creates sockets, or passes on every command to the socket
module<sup><a href="#foot2">2</a></sup>. Depending on the address family and
type of the sockets, the lower layers will be loaded and connected.
</p>
<p>
For example, with a TCP/IP socket, the stack could look like this:
<table cellspacing=1 cellpadding=5 border=0>
<tr><td colspan=2 bgcolor="#aaaadd">Socket</td></tr>
<tr><td bgcolor="#ccccff">TCP</td>
<td rowspan=2 bgcolor="#ddddff"><p>Protocols<br>
<font size="-2">defined by the socket (address family, type)</p>
(session, transport, network layers)</font></td>
</tr>
<tr><td bgcolor="#ccccff">IPv4</td></tr>
<tr><td colspan=2 bgcolor="#ddcc88">Datalink</td></tr>
<tr><td bgcolor="#ffee88">ARP</td>
<td rowspan=2 bgcolor="#ffee99"><p>Datalink Protocols<br>
<font size="-2">defined by the interface (IP address, device)</p>
(datalink layer)</font></td>
</tr>
<tr><td bgcolor="#ffee88">Ethernet framing</td></tr>
<tr><td bgcolor="#ffdd00">Ethernet device</td><td bgcolor="#ffdd55"><font size="-2">(physical layer)</font></tr>
</table>
Where TCP, and IPv4 are net_protocol modules, and ARP, and the Ethernet framing are
net_datalink_protocol modules. All modules are connected in a chain, even though the
datalink layer introduces more than one path (one for each interface).
</p>
<p>
When sending data through a socket, a net_buffer is created in the socket module, and passed
on to the lower levels where each protocol processes it, before passing it on to the next
protocol in the chain. The last protocol in the chain is always a domain protocol - it will
directly forward the buffers to the datalink module. When the buffer reaches the datalink
level, an accompanied net_route object will determine for which interface (which determines
the datalink protocols in the chain) the buffer is destined. The route has to be specified
by the upper protocols before the buffer gets into the datalink level - if a buffer comes
in without a valid route, it is discarded.
</p>
<p>
The protocol modules are loaded and unloaded as needed. The stack itself stays loaded
as long as there are interfaces defined - as soon as the last interface is removed,
the stack gets unloaded (which is, of course, not yet implemented).
</p>
<h3>The Structures and Classes</h3>
<h4>net_domain</h4>
<p>
Every supported address family gets its own domain. A domain comprises such a family,
a net_protocol module that handles this domain, and a list of interfaces and routes.
It also gets a name: for example, the IPv4 module registers the "internet" domain
(AF_INET).
</p>
<p>
The domain protocol module is responsible for managing the domain; it has to register
it when it's loaded, and it has to unregister it when it is unloaded by the networking
stack.
</p>
<h4>net_interface</h4>
<p>
An interface makes an underlying net_device accessible by the stack. When creating
a new interface, you have to specify a domain, and a device to be used. The stack
will then look through the registered datalink protocols, and builds a chain of
them for that interface.
</p>
<p>
The interface usually gets a network address, and a route that directs buffers to
be sent to it. If there is no route to an interface, it will never be used for
outgoing data, but may well receive data from other hosts.
</p>
<p>
An interface can be "up" (when <code>IFF_UP</code> is set in its <code>flags</code>
member) in which case it accepts data - when that flag is not set, it will discard
all data it gets. The interface also specifies the maximum buffer size that can be
sent over this interface (the <code>mtu</code> member, a.k.a. maximum transmission
unit).
</p>
<p>
Interfaces are configured via ioctl()s (SIOCAIFADDR, ...). You can use the command
line tool "ifconfig" to do this for you.
</p>
<h4>net_device</h4>
<p>
A networking device is used to actually send and receive the buffers. It either points
to an actual hardware device (in case of ethernet), or to a virtual device (in case of
loopback). Every device has a unique name that identifies it. When creating a device,
the name also decides which net_device module will be chosen; for example, everything
that starts with "loop" will end up in the loopback device, while the ethernet device
accepts names that start with "/dev/net/".
</p>
<p>
A device can be shared by many interfaces at the same time. The device to be used by
an interface is specified at the time an interface is created.
It also has an <code>mtu</code> member that determines the upper limit of an interface's
<code>mtu</code> as well.
</p>
<h4>net_buffer</h4>
<p>
A buffer holds exactly one packet, and has a source as well as a destination address.
The addresses may be changed in every layer the buffer passes through. For example,
the datalink protocols usually use sockaddr_dl structures with family AF_DLI, while
the upper levels may use sockaddr_in structures with family AF_INET. Every protocol
only supports a small number of address types, and it's the requirement of the upper
protocols to prepare the address for use in the lower protocols (and that's also a
reason why it wouldn't work to arbitrarily stack protocols onto each other).
</p>
<p>
The net_buffer module can be used to access the data within the buffer, append new
data to the buffer, or remove chunks of data from it. Internally, the buffer consists
of usually fixed size (2048 byte) buffers that can be shared or connected as needed.
</p>
<h4>net_socket</h4>
<p>
The socket is only of interest for the net_protocol modules, as it stores options
that may have an effect on the protocol's performance. It's the direct counterpart
to a socket file descriptor in userland, but it has only little logic bound to it.
</p>
<p>
When a socket is created, the networking stack creates a chain of net_protocol
modules for the socket that will then do the real work. When the socket is closed,
the net_protocol chain is freed, and the modules are eventually unloaded (if they
are no longer in use).
</p>
<h4>net_protocol</h4>
<p>
The protocols are bound to a specific socket, process the outgoing buffers as needed
(ie. add or remove headers, compute checksums, ...), and pass it on to the next
protocol. The last protocol in the chain is always a domain protocol that will forward
the calls to the datalink module directly, if needed.
</p>
<p>
A domain protocol is a net_protocol that registered a domain, ie. IPv4. Other than usual
protocols, domain protocols have some special requirements:
<ul>
<li>they need to be able to execute send_data(), and get_domain() without a pointer to
its net_protocol object, as those may be called outside of the socket context.</li>
<li>as mentioned, they also don't talk to the next protocol in the chain (as they are
always the last one), but to the datalink module directly.</li>
</ul>
</p>
<p>
Similar to the need to perform send_data() outside of the socket context, all protocols
that can receive data need to handle incoming data without the socket context: incoming
data is always handled outside of the socket context, as the actual target socket
is unknown during processing.
</p>
<p>
Only the top-most protocol will be able to forward the packet to the target socket(s).
To receive incoming data, a protocol must register itself as receiving protocol with
the networking stack. The domain protocol is usually registered automatically by a
net_datalink_protocol module that knows about both ends (for example, the ARP
module is both IPv4 and ethernet specific, and therefore registers the AF_INET
domain to receive ethernet packets of type IP).
</p>
<h4>net_datalink_protocol</h4>
<p>
The datalink protocols are bound to a specific net_interface, and therefore to a
specific net_device as well. Outgoing data is processed so that it can be sent
via the net_device. For example, the ARP protocol will replace sockaddr_in structures
in the buffer with sockaddr_dl structures describing the ethernet MAC address of
the source and destination hosts, the ethernet_frame protocol will add the usual
ethernet header, etc.
</p>
<p>
The last protocol in the chain is also a special device interface bridge protocol,
that redirects the calls to the underlying net_device.
</p>
<p>
Incoming data is handled differently again; when you want to receive data directly
coming from a device, you can either register a deframing function for it, or a
handler that will be called depending on what data type the deframing module reported.
For example, the ethernet_frame module registers an ethernet deframing function, while
the ARP module registers a handler for ethernet ARP packets with the device. When the
deframing function reports a <code>ETHER_TYPE_ARP</code> packet, the ARP receiving
function will be called.
</p>
<h4>net_route</h4>
<p>
A route determines the target interface of an outgoing packet. A route is always
owned by a specific domain, and the route is chosen by comparing the networking
address of the outgoing buffer with the mask and address of the route.
</p>
<p>
A protocol will usually not use the routes directly, but use a net_route_info
object (see below), that will make sure that the route is updated automatically
whenever the routing table is changed.
</p>
<h4>net_route_info</h4>
<p>
A routing helper for protocol usage: it stores the target address as well as the
route to be used, and has to be registered with the networking stack via
<code>register_route_info()</code>.
</p>
<p>
Then, the stack will automatically update the route as needed, whenever the
routing table of the domain changes; it will always matches the address specified
there. When the routing is no longer needed, you must unregister the net_route_info
again.
</p>
<hr>
<small>
<a name="foot1">1</a> You can find the definition of the driver interface
in <a href="https://git.haiku-os.org/haiku/tree/headers/private/net">headers/private/net/net_stack_interface.h</a>, as well as
the driver itself at <a href="https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/drivers/network">src/add-ons/kernel/drivers/network</a><br>
<a name="foot2">2</a><a href="https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/network/stack">src/add-ons/kernel/network/stack/</a>
</small>
</body>
</html>
+227
View File
@@ -0,0 +1,227 @@
Haiku Network Stack Architecture
================================
The Haiku Network Stack is a modular and layered networking stack, very
similar to what you may know as BONE.
The entry point when talking to the stack is through a dedicated device
driver that publish itself in /dev/net. The userland library
libnetwork.so (which combines libsocket.so, and libbind.so) directly
talks to this driver, mostly via ioctl()\ `1 <#foot1>`__.
The driver either creates sockets, or passes on every command to the
socket module\ `2 <#foot2>`__. Depending on the address family and type
of the sockets, the lower layers will be loaded and connected.
For example, with a TCP/IP socket, the stack could look like this:
+------------------+--------------------------------------------------------+
| **Socket** |
+------------------+--------------------------------------------------------+
| TCP | Protocols defined by the socket (address family, type) |
+------------------+ |
| IPv4 | (session, transport, network layers) |
+------------------+--------------------------------------------------------+
| **Datalink** |
+------------------+--------------------------------------------------------+
| ARP | Datalink Protocols defined by the interface |
| | (IP address, device) |
+------------------+ |
| Ethernet framing | (datalink layer) |
+------------------+--------------------------------------------------------+
| Ethernet device | (physical layer) |
+------------------+--------------------------------------------------------+
Where TCP, and IPv4 are net_protocol modules, and ARP, and the Ethernet
framing are net_datalink_protocol modules. All modules are connected in
a chain, even though the datalink layer introduces more than one path
(one for each interface).
When sending data through a socket, a net_buffer is created in the
socket module, and passed on to the lower levels where each protocol
processes it, before passing it on to the next protocol in the chain.
The last protocol in the chain is always a domain protocol - it will
directly forward the buffers to the datalink module. When the buffer
reaches the datalink level, an accompanied net_route object will
determine for which interface (which determines the datalink protocols
in the chain) the buffer is destined. The route has to be specified by
the upper protocols before the buffer gets into the datalink level - if
a buffer comes in without a valid route, it is discarded.
The protocol modules are loaded and unloaded as needed. The stack itself
stays loaded as long as there are interfaces defined - as soon as the
last interface is removed, the stack gets unloaded (which is, of course,
not yet implemented).
The Structures and Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~
net_domain
^^^^^^^^^^
Every supported address family gets its own domain. A domain comprises
such a family, a net_protocol module that handles this domain, and a
list of interfaces and routes. It also gets a name: for example, the
IPv4 module registers the "internet" domain (AF_INET).
The domain protocol module is responsible for managing the domain; it
has to register it when it's loaded, and it has to unregister it when it
is unloaded by the networking stack.
net_interface
^^^^^^^^^^^^^
An interface makes an underlying net_device accessible by the stack.
When creating a new interface, you have to specify a domain, and a
device to be used. The stack will then look through the registered
datalink protocols, and builds a chain of them for that interface.
The interface usually gets a network address, and a route that directs
buffers to be sent to it. If there is no route to an interface, it will
never be used for outgoing data, but may well receive data from other
hosts.
An interface can be "up" (when ``IFF_UP`` is set in its ``flags``
member) in which case it accepts data - when that flag is not set, it
will discard all data it gets. The interface also specifies the maximum
buffer size that can be sent over this interface (the ``mtu`` member,
a.k.a. maximum transmission unit).
Interfaces are configured via ioctl()s (SIOCAIFADDR, ...). You can use
the command line tool "ifconfig" to do this for you.
net_device
^^^^^^^^^^
A networking device is used to actually send and receive the buffers. It
either points to an actual hardware device (in case of ethernet), or to
a virtual device (in case of loopback). Every device has a unique name
that identifies it. When creating a device, the name also decides which
net_device module will be chosen; for example, everything that starts
with "loop" will end up in the loopback device, while the ethernet
device accepts names that start with "/dev/net/".
A device can be shared by many interfaces at the same time. The device
to be used by an interface is specified at the time an interface is
created. It also has an ``mtu`` member that determines the upper limit
of an interface's ``mtu`` as well.
net_buffer
^^^^^^^^^^
A buffer holds exactly one packet, and has a source as well as a
destination address. The addresses may be changed in every layer the
buffer passes through. For example, the datalink protocols usually use
sockaddr_dl structures with family AF_DLI, while the upper levels may
use sockaddr_in structures with family AF_INET. Every protocol only
supports a small number of address types, and it's the requirement of
the upper protocols to prepare the address for use in the lower
protocols (and that's also a reason why it wouldn't work to arbitrarily
stack protocols onto each other).
The net_buffer module can be used to access the data within the buffer,
append new data to the buffer, or remove chunks of data from it.
Internally, the buffer consists of usually fixed size (2048 byte)
buffers that can be shared or connected as needed.
net_socket
^^^^^^^^^^
The socket is only of interest for the net_protocol modules, as it
stores options that may have an effect on the protocol's performance.
It's the direct counterpart to a socket file descriptor in userland, but
it has only little logic bound to it.
When a socket is created, the networking stack creates a chain of
net_protocol modules for the socket that will then do the real work.
When the socket is closed, the net_protocol chain is freed, and the
modules are eventually unloaded (if they are no longer in use).
net_protocol
^^^^^^^^^^^^
The protocols are bound to a specific socket, process the outgoing
buffers as needed (ie. add or remove headers, compute checksums, ...),
and pass it on to the next protocol. The last protocol in the chain is
always a domain protocol that will forward the calls to the datalink
module directly, if needed.
A domain protocol is a net_protocol that registered a domain, ie. IPv4.
Other than usual protocols, domain protocols have some special
requirements:
- they need to be able to execute send_data(), and get_domain() without
a pointer to its net_protocol object, as those may be called outside
of the socket context.
- as mentioned, they also don't talk to the next protocol in the chain
(as they are always the last one), but to the datalink module
directly.
Similar to the need to perform send_data() outside of the socket
context, all protocols that can receive data need to handle incoming
data without the socket context: incoming data is always handled outside
of the socket context, as the actual target socket is unknown during
processing.
Only the top-most protocol will be able to forward the packet to the
target socket(s). To receive incoming data, a protocol must register
itself as receiving protocol with the networking stack. The domain
protocol is usually registered automatically by a net_datalink_protocol
module that knows about both ends (for example, the ARP module is both
IPv4 and ethernet specific, and therefore registers the AF_INET domain
to receive ethernet packets of type IP).
net_datalink_protocol
^^^^^^^^^^^^^^^^^^^^^
The datalink protocols are bound to a specific net_interface, and
therefore to a specific net_device as well. Outgoing data is processed
so that it can be sent via the net_device. For example, the ARP protocol
will replace sockaddr_in structures in the buffer with sockaddr_dl
structures describing the ethernet MAC address of the source and
destination hosts, the ethernet_frame protocol will add the usual
ethernet header, etc.
The last protocol in the chain is also a special device interface bridge
protocol, that redirects the calls to the underlying net_device.
Incoming data is handled differently again; when you want to receive
data directly coming from a device, you can either register a deframing
function for it, or a handler that will be called depending on what data
type the deframing module reported. For example, the ethernet_frame
module registers an ethernet deframing function, while the ARP module
registers a handler for ethernet ARP packets with the device. When the
deframing function reports a ``ETHER_TYPE_ARP`` packet, the ARP
receiving function will be called.
net_route
^^^^^^^^^
A route determines the target interface of an outgoing packet. A route
is always owned by a specific domain, and the route is chosen by
comparing the networking address of the outgoing buffer with the mask
and address of the route.
A protocol will usually not use the routes directly, but use a
net_route_info object (see below), that will make sure that the route is
updated automatically whenever the routing table is changed.
net_route_info
^^^^^^^^^^^^^^
A routing helper for protocol usage: it stores the target address as
well as the route to be used, and has to be registered with the
networking stack via ``register_route_info()``.
Then, the stack will automatically update the route as needed, whenever
the routing table of the domain changes; it will always matches the
address specified there. When the routing is no longer needed, you must
unregister the net_route_info again.
--------------
| 1 You can find the definition of the driver interface in
`headers/private/net/net_stack_interface.h <https://git.haiku-os.org/haiku/tree/headers/private/net>`__,
as well as the driver itself at
`src/add-ons/kernel/drivers/network <https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/drivers/network>`__
| 2\ `src/add-ons/kernel/network/stack/ <https://git.haiku-os.org/haiku/tree/src/add-ons/kernel/network/stack>`__
+1 -1
View File
@@ -135,6 +135,6 @@ alternative. A typical use would be to create a desktop icon that the user
can move around or delete.
Pre-Uninstallation Scripts
=========================
==========================
These undo the effects of a post-installation script and usually are put
into "boot/pre-uninstall". A typical use is to remove desktop icons.
+13
View File
@@ -90,3 +90,16 @@ Below are links to source code related to Haiku's package management.
- haikuporter_ is the tool to create binary packages from build recipes.
.. _haikuporter: https://github.com/haikuports/haikuporter
.. toctree::
/packages/BuildingPackages
/packages/DirectoryStructure
/packages/FileFormat
/packages/HybridBuilds
/packages/Infrastructure
/packages/Migration
/packages/PackagingPolicy
/packages/Bootstrapping
/packages/TODO
/packages/OldIdeas
+113
View File
@@ -0,0 +1,113 @@
Partitioning system for Sun Sparc machines
==========================================
Infos extracted from `File System Forensic Analysis, Brian Carrier <urn:isbn:0-134-43954-6>`_
and in particular the `online copy here <https://books.google.fr/books?id=Zpm9CgAAQBAJ&lpg=PT159&ots=6LIQ6blJCF&dq=solaris%20vtoc%20structure&hl=fr&pg=PT159#v=onepage&q=solaris%20vtoc%20structure&f=false>`_), tables 6.9 and 6.10.
The format is called VTOC (volume table of contents). It is stored at offset 0
on-disk. All values are big endian.
Note that the x86 version of Solaris uses a different layout.
+------------+--------------------------------+
| Byte offset|Description |
+============+================================+
| 0-127 |ASCII disk label |
+------------+--------------------------------+
| 128-261 |VTOC * |
+------------+--------------------------------+
| 262-263 |Sectors to skip when writing |
+------------+--------------------------------+
| 264-265 |Setors to skip when reading |
+------------+--------------------------------+
| 266-419 |Reserved |
+------------+--------------------------------+
| 420-421 |Disk speed |
+------------+--------------------------------+
| 422-423 |Number of cylinders |
+------------+--------------------------------+
| 424-425 |Alternates per cylinder |
+------------+--------------------------------+
| 426-429 |Reserved |
+------------+--------------------------------+
| 430-431 |Interleave |
+------------+--------------------------------+
| 432-433 |Number of data cylinders |
+------------+--------------------------------+
| 434-435 |Number of alternate cylinders |
+------------+--------------------------------+
| 436-437 |Number of heads |
+------------+--------------------------------+
| 438-439 |Number of sectors per track |
+------------+--------------------------------+
| 440-443 |Reserved |
+------------+--------------------------------+
| 444-451 |Partition 1 disk map |
+------------+--------------------------------+
| ... |More partition disk maps |
+------------+--------------------------------+
| 500-507 |Partition 8 disk map |
+------------+--------------------------------+
| 508-509 |Signature (0xDABE) |
+------------+--------------------------------+
| 510-511 |Checksum |
+------------+--------------------------------+
The VTOC itself:
+---------+-----------------------------------+
| 0-3 | Version |
+---------+-----------------------------------+
| 4-11 | Volume name |
+---------+-----------------------------------+
| 12-13 | Number of partitions |
+---------+-----------------------------------+
| 14-15 | Partition 1 type |
+---------+-----------------------------------+
| 16-17 | Partition 1 flags |
+---------+-----------------------------------+
| ... | More partition types and flags |
+---------+-----------------------------------+
| 42-45 | Partition 8 type and flags |
+---------+-----------------------------------+
| 46-57 | Boot info |
+---------+-----------------------------------+
| 58-59 | Reserved |
+---------+-----------------------------------+
| 60-63 | Signature 0x600DDEEE |
+---------+-----------------------------------+
| 64-101 | Reserved |
+---------+-----------------------------------+
| 102-105 | Partition 1 timestamp |
+---------+-----------------------------------+
| ... | More partition timestamps |
+---------+-----------------------------------+
| 130-133 | Parittion 8 timestamp |
+---------+-----------------------------------+
Partition types (informative):
0. unassigned
1. /boot
2. /
3. swap
4. /usr
5. entire disk
6. /stand
7. /var
8. /home
9. alternate sector
10. cachefs
Partition flags:
* 1 - Not mountable
* 128 - read only
Disk maps:
+-----+-------------------+
| 0-3 | Starting cylinder |
+-----+-------------------+
| 4-7 | Size (in sectors) |
+-----+-------------------+
-72
View File
@@ -1,72 +0,0 @@
Partitioning system for Sun Sparc machines
==========================================
Infos extracted from [File System Forensic Analysis, Brian Carrier](urn:isbn:0-134-43954-6)
and in particular the [online copy here](https://books.google.fr/books?id=Zpm9CgAAQBAJ&lpg=PT159&ots=6LIQ6blJCF&dq=solaris%20vtoc%20structure&hl=fr&pg=PT159#v=onepage&q=solaris%20vtoc%20structure&f=false), tables 6.9 and 6.10.
The format is called VTOC (volume table of contents). It is stored at offset 0
on-disk. All values are big endian.
Note that the x86 version of Solaris uses a different layout.
Byte offset|Description
0-127 |ASCII disk label
128-261 |VTOC *
262-263 |Sectors to skip when writing
264-265 |Setors to skip when reading
266-419 |Reserved
420-421 |Disk speed
422-423 |Number of cylinders
424-425 |Alternates per cylinder
426-429 |Reserved
430-431 |Interleave
432-433 |Number of data cylinders
434-435 |Number of alternate cylinders
436-437 |Number of heads
438-439 |Number of sectors per track
440-443 |Reserved
444-451 |Partition 1 disk map
... |More partition disk maps
500-507 |Partition 8 disk map
508-509 |Signature (0xDABE)
510-511 |Checksum
The VTOC itself:
0-3 Version
4-11 Volume name
12-13 Number of partitions
14-15 Partition 1 type
16-17 Partition 1 flags
... More partition types and flags
42-45 Partition 8 type and flags
46-57 Boot info
58-59 Reserved
60-63 Signature 0x600DDEEE
64-101 Reserved
102-105 Partition 1 timestamp
... More partition timestamps
130-133 Parittion 8 timestamp
Partition types (informative):
0 unassigned
1 /boot
2 /
3 swap
4 /usr
5 entire disk
6 /stand
7 /var
8 /home
9 alternate sector
10 cachefs
Partition flags:
1 Not mountable
128 read only
Disk maps:
0-3 Starting cylinder
4-7 Size (in sectors)
@@ -1,705 +0,0 @@
<HTML>
<HEAD>
<TITLE>AppServer.htm</TITLE>
<style type="text/css">
<!--
.Default {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 12pt}
.OBOS-Function-Def {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 16pt}
.OBOS-Title {background-color: rgb(255,255,255); color: rgb(0,128,0); font-family: 'Dutch801 Rm BT'; font-size: 24pt}
.Text-Background {background-color: rgb(255,255,255)}
.GR-Default {}
.Body {margin: 0px}
.Footer {margin: 0px}
.Header {margin: 0px}
.WP-Default {text-align: left; text-indent: 0px; margin-left: 0px; margin-right: 0px}
-->
</style>
</HEAD>
<BODY BGCOLOR="#ffffff">
<DIV class="sheet" id="Sheet 1">
<P class="Body" style="margin: 0px"><span class="OBOS-Title">AppServer class</span><span style="color: rgb(0,0,0); font-size: 24pt"></span></P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">The AppServer class sits at the top of the hierarchy, starting and stopping services, monitoring for messages, and so forth.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><BR>
Member Functions</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<TABLE WIDTH=617 HEIGHT=113 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=203 HEIGHT=17>
<P class="Body" style="margin: 0px">AppServer(void)</P>
</TD>
<TD WIDTH=260 HEIGHT=17>
<P class="Body" style="margin: 0px">~AppServer(void)</P>
</TD>
</TR>
<TR>
<TD WIDTH=203 HEIGHT=17>
<P class="Body" style="margin: 0px">static int32 Poller(void *data)</P>
</TD>
<TD WIDTH=260 HEIGHT=17>
<P class="Body" style="margin: 0px">static int32 Picasso(void *data)</P>
</TD>
</TR>
<TR>
<TD WIDTH=203 HEIGHT=17>
<P class="Body" style="margin: 0px">thread_id Run(void)</P>
</TD>
<TD WIDTH=260 HEIGHT=17>
<P class="Body" style="margin: 0px">void MainLoop(void)</P>
</TD>
</TR>
<TR>
<TD WIDTH=203 HEIGHT=17>
<P class="Body" style="margin: 0px">bool LoadDecorator(const char *path)</P>
</TD>
<TD WIDTH=260 HEIGHT=17>
<P class="Body" style="margin: 0px">void DispatchMessage(int32 code, int8 *buffer)</P>
</TD>
</TR>
<TR>
<TD WIDTH=203 HEIGHT=17>
<P class="Body" style="margin: 0px">void Broadcast(int32 code)</P>
</TD>
<TD WIDTH=260 HEIGHT=17>
<P class="Body" style="margin: 0px"><span style="color: rgb(0,0,0)">void HandleKeyMessage(int32 code, int8 *buffer)</span></P>
</TD>
</TR>
</TABLE>
<P class="Body" style="margin: 0px"><BR>
<BR>
Global Functions</P>
<P class="Body" style="margin: 0px"><BR>
Decorator * instantiate_decorator(Layer *owner, uint32 wflags, uint32 wlook)</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">AppServer(void)</span><span style="font-size: 16pt"></span></P>
<P class="Body" style="margin: 0px"><BR>
1) Create the message and input ports</P>
<P class="Body" style="margin: 0px">2) Create any necessary semaphores for regulating the 3 main threads</P>
<P class="Body" style="margin: 0px">3) Initialize all member variables</P>
<P class="Body" style="margin: 0px">4) Allocate the application BList</P>
<P class="Body" style="margin: 0px">5) Read in and process all configuration data</P>
<P class="Body" style="margin: 0px">6) Initialize the desktop</P>
<P class="Body" style="margin: 0px">7) Spawn the Picasso and Poller threads</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">~AppServer(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
1) Shut down the desktop</P>
<P class="Body" style="margin: 0px">2) Empty and delete the application list</P>
<P class="Body" style="margin: 0px">3) Wait for Picasso and Poller to exit</P>
<P class="Body" style="margin: 0px">4) Free any allocated heap space</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void MainLoop(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
MainLoop is one large loop used to monitor the main message port in the app_server thread. This is a standard port-monitoring loop code:</P>
<P class="Body" style="margin: 0px"><BR>
1) Call port_buffer_size - which will block if the port is empty</P>
<P class="Body" style="margin: 0px">2) Allocate a buffer on the heap if the port buffer size is greater than 0</P>
<P class="Body" style="margin: 0px">3) Read the port</P>
<P class="Body" style="margin: 0px">4) Pass specified messages to DispatchMessage() for processing, spitting out an error message to stderr if the message's code is unrecognized</P>
<P class="Body" style="margin: 0px">5) Return from DispatchMessage() and free the message buffer if one was allocated</P>
<P class="Body" style="margin: 0px">6) If the message code matches the B_QUIT_REQUESTED definition and the quit_server flag is true, fall out of the infinite message-monitoring loop</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void DispatchMessage(int32 code, int8 *buffer)</span></P>
<P class="Body" style="margin: 0px"><BR>
DispatchMessage implements all the code necessary to respond to a given message sent to the app_server on its main port. This allows for clearer and more manageable code.</P>
<P class="Body" style="margin: 0px"><BR>
CREATE_APP:</P>
<P class="Body" style="margin: 0px"><BR>
Sent by a new BApplication object via synchronous PortLink messaging. Set up the corresponding ServerApp and reply to the BApplication with the new port to which it will send future communications with the App Server.</P>
<P class="Body" style="margin: 0px"><BR>
Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=573 HEIGHT=110 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=112 HEIGHT=32>
<P class="Body" style="margin: 0px">port_id reply_port</P>
</TD>
<TD WIDTH=318 HEIGHT=32>
<P class="Body" style="margin: 0px">port to which the server is to reply in response to the current message</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">port_id app_port</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">message port for the requesting BApplication</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int16 sig_length</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">length of the following application signature</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">const char *signature</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">Signature of the requesting BApplication</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px"><BR>
1) Get all attached data</P>
<P class="Body" style="margin: 0px">2) Acquire the application list lock</P>
<P class="Body" style="margin: 0px">3) Allocate a ServerApp object and add it to the list</P>
<P class="Body" style="margin: 0px">4) Release application list lock</P>
<P class="Body" style="margin: 0px">5) Acquire active application pointer lock</P>
<P class="Body" style="margin: 0px">6) Update active application pointer</P>
<P class="Body" style="margin: 0px">7) Release active application lock</P>
<P class="Body" style="margin: 0px">8) Send the message SET_SERVER_PORT (with the ServerApp's receiver port attached) to the reply port</P>
<P class="Body" style="margin: 0px">9) Run() the new ServerApp instance</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
DELETE_APP:</P>
<P class="Body" style="margin: 0px"><BR>
Sent by a ServerApp when told to quit either by its BApplication or the Server itself (during shutdown). It is identified by the unique ID assigned to its thread.</P>
<P class="Body" style="margin: 0px"><BR>
Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=568 HEIGHT=22 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=132 HEIGHT=17>
<P class="Body" style="margin: 0px">thread_id app_thread</P>
</TD>
<TD WIDTH=294 HEIGHT=17>
<P class="Body" style="margin: 0px">Thread id of the ServerApp sending this message</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
1) Get app's thread_id</P>
<P class="Body" style="text-align: left; margin: 0px">2) Acquire application list lock</P>
<P class="Body" style="text-align: left; margin: 0px">3) Iterate through the application list, searching for the ServerApp object with the sent thread_id</P>
<P class="Body" style="text-align: left; margin: 0px">4) Remove the object from the list and delete it</P>
<P class="Body" style="text-align: left; margin: 0px">5) Acquire active application lock</P>
<P class="Body" style="text-align: left; margin: 0px">6) Check to see if the application is active</P>
<P class="Body" style="text-align: left; margin: 0px">7) If application is/was active, set it to the previous application in the list or NULL if there are no other active applications</P>
<P class="Body" style="text-align: left; margin: 0px">8) Release application list lock</P>
<P class="Body" style="text-align: left; margin: 0px">9) Release active application lock</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
<BR>
GET_SCREEN_MODE:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Received from the OpenBeOS Input Server when requesting the current screen settings via synchronous PortLink messaging. This is a temporary solution which will be deprecated as soon as the BScreen class is complete.</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=573 HEIGHT=42 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=112 HEIGHT=32>
<P class="Body" style="margin: 0px">port_id reply_port</P>
</TD>
<TD WIDTH=318 HEIGHT=32>
<P class="Body" style="margin: 0px">port to which the server is to reply in response to the current message</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
1) Get height, width, and color depth from the global graphics driver object</P>
<P class="Body" style="text-align: left; margin: 0px">2) Attach via PortLink and reply to sender</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
B_QUIT_REQUESTED:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Encountered only under testing situations where the Server is told to quit.</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Attached Data: None</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
1) Set quit_server flag to true</P>
<P class="Body" style="text-align: left; margin: 0px">2) Call Broadcast(QUIT_APP)</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
<BR>
SET_DECORATOR:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Received from just about anything when a new window decorator is chosen</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=548 HEIGHT=22 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=117 HEIGHT=17>
<P class="Body" style="margin: 0px">const char *path</P>
</TD>
<TD WIDTH=294 HEIGHT=17>
<P class="Body" style="margin: 0px">Path to the proposed new decorator</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
1) Get the path from the buffer</P>
<P class="Body" style="text-align: left; margin: 0px">2) Call LoadDecorator()</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void Run(void)</span></P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Run() exists mostly for consistency with other regular applications.</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
1) Call MainLoop()</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">bool LoadDecorator(const char *path)</span></P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Allows for a simple way to change the current window decorator systemwide simply by specifying the path to the desired Decorator addon.</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
1) Load the passed string as the path to an addon.</P>
<P class="Body" style="text-align: left; margin: 0px">2) Load all necessary symbols for the decorator</P>
<P class="Body" style="text-align: left; margin: 0px">3) Return false if things didn't go so well</P>
<P class="Body" style="text-align: left; margin: 0px">4) Call Broadcast(UPDATE_DECORATOR)</P>
<P class="Body" style="text-align: left; margin: 0px">5) Return true</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
<span class="OBOS-Function-Def">static int32 Picasso(void *data)</span></P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Picasso is a function, despite its name, dedicated to ensuring that the server deallocates resources to a dead application. It consists of a while(!quit_server) loop as follows:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
1) Acquire the appliction list lock</P>
<P class="Body" style="text-align: left; margin: 0px">2) Iterate through the list, calling each ServerApp object's PingTarget() method.</P>
<P class="Body" style="text-align: left; margin: 0px">3) If PingTarget returns false, remove the ServerApp from the list and delete it.</P>
<P class="Body" style="text-align: left; margin: 0px">4) Release the appliction list lock</P>
<P class="Body" style="text-align: left; margin: 0px">5) snooze for 3 seconds</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
<span class="OBOS-Function-Def">static int32 Poller(void *data)</span></P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
Poller is the main workhorse of the AppServer class, polling the Server's input port constantly for any messages from the Input Server and calling the appropriate handlers. Like Picasso, it, too, is mostly a while(!quit_server) loop.</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">1) Call port_buffer_size_etc() with a timeout of 3 seconds.</P>
<P class="Body" style="margin: 0px">2) Check to see if the port_buffer_size_etc() timed out and do a continue to next iteration if it did.</P>
<P class="Body" style="margin: 0px">3) Allocate a buffer on the heap if the port buffer size is greater than 0</P>
<P class="Body" style="margin: 0px">4) Read the port</P>
<P class="Body" style="margin: 0px">5) Pass specified messages to DispatchMessage() for processing, spitting out an error message to stderr if the message's code is unrecognized</P>
<P class="Body" style="margin: 0px">6) Return from DispatchMessage() and free the message buffer if one was allocated</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">Decorator * instantiate_decorator(Layer *owner, uint32 wflags, uint32 wlook)</span></P>
<P class="Body" style="margin: 0px"><BR>
instantiate_decorator returns a new instance of the decorator currently in use. The caller is responsible for the memory allocated for the returned object.</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire the decorator lock</P>
<P class="Body" style="margin: 0px">2) If create_decorator is NULL, create a new instance of the default decorator</P>
<P class="Body" style="margin: 0px">3) If create_decorator is non-NULL, create a new decorator instance by calling AppServer::create_decorator().</P>
<P class="Body" style="margin: 0px">4) Release the decorator lock</P>
<P class="Body" style="margin: 0px">5) Return the newly allocated instance</P>
<P class="Body" style="margin: 0px"><BR>
<span class="OBOS-Function-Def">void Broadcast(int32 code)</span></P>
<P class="Body" style="margin: 0px"><BR>
Broadcast() provides the AppServer class with an easy way to send a quick message to all ServerApps. Primarily, this is called when a font or decorator has changed, or when the server is shutting down. It is not intended to do anything except send a quick message which requires no extra data, such as for some upadate signalling.</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: left; margin: 0px">1) Acquire application list lock</P>
<P class="Body" style="text-align: left; margin: 0px">2) Create a PortLink instance and set its message code to the passed parameter.</P>
<P class="Body" style="text-align: left; margin: 0px">3) Iterate through the application list, targeting the PortLink instance to each ServerApp's message port and calling Flush().</P>
<P class="Body" style="text-align: left; margin: 0px">4) Release application list lock</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
<span class="OBOS-Function-Def">void HandleKeyMessage(int32 code, int8 *buffer)</span></P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">Called from DispatchMessage to filter out App Server events and otherwise send keystrokes to the active application. </P>
<P class="Body" style="margin: 0px"><BR>
B_KEY_DOWN:</P>
<P class="Body" style="margin: 0px"><BR>
Sent when the user presses (or holds down) a key that's been mapped to a character.</P>
<P class="Body" style="margin: 0px"><BR>
Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=573 HEIGHT=246 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int64 when</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">event time in seconds since 1/1/70</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 rawcode</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">code for the physical key pressed</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 repeat_count</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">number of times a key has been repeated</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 modifiers</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">flags signifying the states of the modifier keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 state_count</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">number of bytes to follow containing the state of all keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 *states</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">array of the state of all keys at the time of the event</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 utf8data[3]</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">UTF-8 data generated</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=32>
<P class="Body" style="margin: 0px">int8 charcount</P>
</TD>
<TD WIDTH=318 HEIGHT=32>
<P class="Body" style="margin: 0px">number of bytes to follow containing the string generated (usually 1)</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">const char *string</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">null-terminated string generated by the keystroke</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 raw_char</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">modifier-independent ASCII code for the character</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px"><BR>
1) Get all attached data</P>
<P class="Body" style="margin: 0px">2) If the command modifier is down, check for Left Ctrl+Left Alt+Left Shift+F12 and reset the workspace to 640 x 480 x 256 @ 60Hz and return if true</P>
<P class="Body" style="margin: 0px">3) If the command modifier is down, check for Alt+F1 through Alt+F12 and set workspace and return if true</P>
<P class="Body" style="margin: 0px">4) If the control modifier is true, check for B_CONTROL_KEY+Tab and, if true, find and send to the Deskbar.</P>
<P class="Body" style="margin: 0px">4) Acquire the active application lock</P>
<P class="Body" style="margin: 0px">5) Create a PortLink instance, target the active ServerApp's sender port, set the opcode to B_KEY_DOWN, attach the buffer <span style="font-style: italic">en masse</span>, and send it to the BApplication.</P>
<P class="Body" style="margin: 0px">6) Release the active application lock</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: left; margin: 0px">B_KEY_UP:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">Sent when the user releases a key that's been mapped to a character.</P>
<P class="Body" style="margin: 0px"><BR>
Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=573 HEIGHT=224 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int64 when</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">event time in seconds since 1/1/70</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 rawcode</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">code for the physical key pressed</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 modifiers</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">flags signifying the states of the modifier keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 state_count</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">number of bytes to follow containing the state of all keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 *states</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">array of the state of all keys at the time of the event</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 utf8data[3]</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">UTF-8 data generated</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=32>
<P class="Body" style="margin: 0px">int8 charcount</P>
</TD>
<TD WIDTH=318 HEIGHT=32>
<P class="Body" style="margin: 0px">number of bytes to follow containing the string generated (usually 1)</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">const char *string</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">null-terminated string generated by the keystroke</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 raw_char</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">modifier-independent ASCII code for the character</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px"><BR>
1) Get all attached data</P>
<P class="Body" style="margin: 0px">2) Acquire the active application lock</P>
<P class="Body" style="margin: 0px">3) Create a PortLink instance, target the active ServerApp's sender port, set the opcode to B_KEY_UP, attach the buffer <span style="font-style: italic">en masse</span>, and send it to the BApplication.</P>
<P class="Body" style="margin: 0px">4) Release the active application lock</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
B_UNMAPPED_KEY_DOWN:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">Sent when the user presses a key that has not been mapped to a character.</P>
<P class="Body" style="margin: 0px"><BR>
Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=573 HEIGHT=113 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int64 when</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">event time in seconds since 1/1/70</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 rawcode</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">code for the physical key pressed</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 modifiers</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">flags signifying the states of the modifier keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 state_count</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">number of bytes to follow containing the state of all keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 *states</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">array of the state of all keys at the time of the event</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire the active application lock</P>
<P class="Body" style="margin: 0px">2) Create a PortLink instance, target the active ServerApp's sender port, set the opcode to B_UNMAPPED_KEY_DOWN, attach the buffer <span style="font-style: italic">en masse</span>, and send it to the BApplication.</P>
<P class="Body" style="margin: 0px">3) Release the active application lock</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
B_UNMAPPED_KEY_UP:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">Sent when the user presses a key that has not been mapped to a character.</P>
<P class="Body" style="margin: 0px"><BR>
Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=573 HEIGHT=113 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int64 when</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">event time in seconds since 1/1/70</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 rawcode</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">code for the physical key pressed</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 modifiers</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">flags signifying the states of the modifier keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 state_count</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">number of bytes to follow containing the state of all keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 *states</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">array of the state of all keys at the time of the event</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire the active application lock</P>
<P class="Body" style="margin: 0px">2) Create a PortLink instance, target the active ServerApp's sender port, set the opcode to B_UNMAPPED_KEY_UP, attach the buffer <span style="font-style: italic">en masse</span>, and send it to the BApplication.</P>
<P class="Body" style="margin: 0px">3) Release the active application lock</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: left; margin: 0px">B_MODIFIERS_CHANGED:</P>
<P class="Body" style="text-align: left; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">Sent when the user presses or releases one of the modifier keys</P>
<P class="Body" style="margin: 0px"><BR>
Attached Data:</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="text-align: center; margin: 0px"></P>
<TABLE WIDTH=573 HEIGHT=113 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int64 when</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">event time in seconds since 1/1/70</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 modifiers</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">flags signifying the states of the modifier keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 old_modifiers</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">former states of the modifier keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 state_count</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">number of bytes to follow containing the state of all keys</P>
</TD>
</TR>
<TR>
<TD WIDTH=112 HEIGHT=17>
<P class="Body" style="margin: 0px">int8 *states</P>
</TD>
<TD WIDTH=318 HEIGHT=17>
<P class="Body" style="margin: 0px">array of the state of all keys at the time of the event</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="text-align: center; margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire the active application lock</P>
<P class="Body" style="margin: 0px">2) Create a PortLink instance, target the active ServerApp's sender port, set the opcode to B_MODIFIERS_CHANGED, attach the buffer <span style="font-style: italic">en masse</span>, and send it to the BApplication.</P>
<P class="Body" style="margin: 0px">3) Release the active application lock</P>
<DIV class="layer" id="Layer 1">
</DIV>
</DIV>
</BODY>
</HTML>
@@ -0,0 +1,456 @@
AppServer class
###############
The AppServer class sits at the top of the hierarchy, starting and
stopping services, monitoring for messages, and so forth.
Member Functions
================
- AppServer(void)
- ~AppServer(void)
- static int32 Poller(void \*data)
- static int32 Picasso(void \*data)
- thread_id Run(void)
- void MainLoop(void)
- bool LoadDecorator(const char\*path)
- void DispatchMessage(int32 code,int8 \*buffer)
- void Broadcast(int32 code)
- void HandleKeyMessage(int32 code, int8 \*buffer)
Global Functions
================
Decorator \* instantiate_decorator(Layer \*owner, uint32 wflags, uint32 wlook)
AppServer(void)
===============
1. Create the message and input ports
2. Create any necessary semaphores for regulating the 3 main threads
3. Initialize all member variables
4. Allocate the application BList
5. Read in and process all configuration data
6. Initialize the desktop
7. Spawn the Picasso and Poller threads
~AppServer(void)
================
1. Shut down the desktop
2. Empty and delete the application list
3. Wait for Picasso and Poller to exit
4. Free any allocated heap space
void MainLoop(void)
===================
MainLoop is one large loop used to monitor the main message port in
the app_server thread. This is a standard port-monitoring loop code:
1. Call port_buffer_size - which will block if the port is empty
2. Allocate a buffer on the heap if the port buffer size is greater than 0
3. Read the port
4. Pass specified messages to DispatchMessage() for processing, spitting
out an error message to stderr if the message's code is unrecognized
5. Return from DispatchMessage() and free the message buffer if one was
allocated
6. If the message code matches the B_QUIT_REQUESTED definition and the
quit_server flag is true, fall out of the infinite message-monitoring
loop
void DispatchMessage(int32 code, int8 \*buffer)
===============================================
DispatchMessage implements all the code necessary to respond to a
given message sent to the app_server on its main port. This allows for
clearer and more manageable code.
CREATE_APP
----------
Sent by a new BApplication object via synchronous PortLink messaging.
Set up the corresponding ServerApp and reply to the BApplication with
the new port to which it will send future communications with the App
Server.
Attached Data:
+-----------------------------------+-----------------------------------+
| port_id reply_port | port to which the server is to |
| | reply in response to the current |
| | message |
+-----------------------------------+-----------------------------------+
| port_id app_port | message port for the requesting |
| | BApplication |
+-----------------------------------+-----------------------------------+
| int16 sig_length | length of the following |
| | application signature |
+-----------------------------------+-----------------------------------+
| const char \*signature | Signature of the requesting |
| | BApplication |
+-----------------------------------+-----------------------------------+
1. Get all attached data
2. Acquire the application list lock
3. Allocate a ServerApp object and add it to the list
4. Release application list lock
5. Acquire active application pointer lock
6. Update active application pointer
7. Release active application lock
8. Send the message SET_SERVER_PORT (with the ServerApp's receiver port
attached) to the reply port
9. Run() the new ServerApp instance
DELETE_APP
----------
Sent by a ServerApp when told to quit either by its BApplication or
the Server itself (during shutdown). It is identified by the unique ID
assigned to its thread.
Attached Data:
+-----------------------------------+-----------------------------------+
| thread_id app_thread | Thread id of the ServerApp |
| | sending this message |
+-----------------------------------+-----------------------------------+
1. Get app's thread_id
2. Acquire application list lock
3. Iterate through the application list, searching for the ServerApp
object with the sent thread_id
4. Remove the object from the list and delete it
5. Acquire active application lock
6. Check to see if the application is active
7. If application is/was active, set it to the previous application in
the list or NULL if there are no other active applications
8. Release application list lock
9. Release active application lock
GET_SCREEN_MODE
---------------
Received from the OpenBeOS Input Server when requesting the current
screen settings via synchronous PortLink messaging. This is a
temporary solution which will be deprecated as soon as the BScreen
class is complete.
Attached Data:
+-----------------------------------+-----------------------------------+
| port_id reply_port | port to which the server is to |
| | reply in response to the current |
| | message |
+-----------------------------------+-----------------------------------+
1. Get height, width, and color depth from the global graphics driver
object
2. Attach via PortLink and reply to sender
B_QUIT_REQUESTED
----------------
Encountered only under testing situations where the Server is told to
quit.
Attached Data: None
1. Set quit_server flag to true
2. Call Broadcast(QUIT_APP)
SET_DECORATOR
-------------
Received from just about anything when a new window decorator is
chosen
Attached Data:
+-----------------------------------+-----------------------------------+
| const char \*path | Path to the proposed new |
| | decorator |
+-----------------------------------+-----------------------------------+
1. Get the path from the buffer
2. Call LoadDecorator()
void Run(void)
==============
Run() exists mostly for consistency with other regular applications.
1) Call MainLoop()
bool LoadDecorator(const char \*path)
=====================================
Allows for a simple way to change the current window decorator
systemwide simply by specifying the path to the desired Decorator
addon.
1. Load the passed string as the path to an addon.
2. Load all necessary symbols for the decorator
3. Return false if things didn't go so well
4. Call Broadcast(UPDATE_DECORATOR)
5. Return true
static int32 Picasso(void \*data)
=================================
Picasso is a function, despite its name, dedicated to ensuring that
the server deallocates resources to a dead application. It consists of
a while(!quit_server) loop as follows:
1) Acquire the appliction list lock
2) Iterate through the list, calling each ServerApp object's
PingTarget() method.
3) If PingTarget returns false, remove the ServerApp from the list and
delete it.
4) Release the appliction list lock
5) snooze for 3 seconds
static int32 Poller(void \*data)
================================
Poller is the main workhorse of the AppServer class, polling the
Server's input port constantly for any messages from the Input Server
and calling the appropriate handlers. Like Picasso, it, too, is mostly
a while(!quit_server) loop.
1. Call port_buffer_size_etc() with a timeout of 3 seconds.
2. Check to see if the port_buffer_size_etc() timed out and do a
continue to next iteration if it did.
3. Allocate a buffer on the heap if the port buffer size is greater than 0
4. Read the port
5. Pass specified messages to DispatchMessage() for processing, spitting
out an error message to stderr if the message's code is unrecognized
6. Return from DispatchMessage() and free the message buffer if one was
allocated
Decorator \* instantiate_decorator(Layer \*owner, uint32 wflags, uint32 wlook)
==============================================================================
instantiate_decorator returns a new instance of the decorator
currently in use. The caller is responsible for the memory allocated
for the returned object.
1. Acquire the decorator lock
2. If create_decorator is NULL, create a new instance of the default
decorator
3. If create_decorator is non-NULL, create a new decorator instance by
calling AppServer::create_decorator().
4. Release the decorator lock
5. Return the newly allocated instance
void Broadcast(int32 code)
==========================
Broadcast() provides the AppServer class with an easy way to send a
quick message to all ServerApps. Primarily, this is called when a font
or decorator has changed, or when the server is shutting down. It is
not intended to do anything except send a quick message which requires
no extra data, such as for some upadate signalling.
1. Acquire application list lock
2. Create a PortLink instance and set its message code to the passed
parameter.
3. Iterate through the application list, targeting the PortLink instance
to each ServerApp's message port and calling Flush().
4. Release application list lock
void HandleKeyMessage(int32 code, int8 \*buffer)
================================================
Called from DispatchMessage to filter out App Server events and
otherwise send keystrokes to the active application.
B_KEY_DOWN
----------
Sent when the user presses (or holds down) a key that's been mapped to
a character.
Attached Data:
+-----------------------------------+-----------------------------------+
| int64 when | event time in seconds since |
| | 1/1/70 |
+-----------------------------------+-----------------------------------+
| int32 rawcode | code for the physical key pressed |
+-----------------------------------+-----------------------------------+
| int32 repeat_count | number of times a key has been |
| | repeated |
+-----------------------------------+-----------------------------------+
| int32 modifiers | flags signifying the states of |
| | the modifier keys |
+-----------------------------------+-----------------------------------+
| int32 state_count | number of bytes to follow |
| | containing the state of all keys |
+-----------------------------------+-----------------------------------+
| int8 \*states | array of the state of all keys at |
| | the time of the event |
+-----------------------------------+-----------------------------------+
| int8 utf8data[3] | UTF-8 data generated |
+-----------------------------------+-----------------------------------+
| int8 charcount | number of bytes to follow |
| | containing the string generated |
| | (usually 1) |
+-----------------------------------+-----------------------------------+
| const char \*string | null-terminated string generated |
| | by the keystroke |
+-----------------------------------+-----------------------------------+
| int32 raw_char | modifier-independent ASCII code |
| | for the character |
+-----------------------------------+-----------------------------------+
1. Get all attached data
2. If the command modifier is down, check for Left Ctrl+Left Alt+Left
Shift+F12 and reset the workspace to 640 x 480 x 256 @ 60Hz and return
if true
3. If the command modifier is down, check for Alt+F1 through Alt+F12 and
set workspace and return if true
4. If the control modifier is true, check for B_CONTROL_KEY+Tab and, if
true, find and send to the Deskbar.
5. Acquire the active application lock
6. Create a PortLink instance, target the active ServerApp's sender
port, set the opcode to B_KEY_DOWN, attach the buffer en masse, and send
it to the BApplication.
7. Release the active application lock
B_KEY_UP
--------
Sent when the user releases a key that's been mapped to a character.
Attached Data:
+-----------------------------------+-----------------------------------+
| int64 when | event time in seconds since |
| | 1/1/70 |
+-----------------------------------+-----------------------------------+
| int32 rawcode | code for the physical key pressed |
+-----------------------------------+-----------------------------------+
| int32 modifiers | flags signifying the states of |
| | the modifier keys |
+-----------------------------------+-----------------------------------+
| int32 state_count | number of bytes to follow |
| | containing the state of all keys |
+-----------------------------------+-----------------------------------+
| int8 \*states | array of the state of all keys at |
| | the time of the event |
+-----------------------------------+-----------------------------------+
| int8 utf8data[3] | UTF-8 data generated |
+-----------------------------------+-----------------------------------+
| int8 charcount | number of bytes to follow |
| | containing the string generated |
| | (usually 1) |
+-----------------------------------+-----------------------------------+
| const char \*string | null-terminated string generated |
| | by the keystroke |
+-----------------------------------+-----------------------------------+
| int32 raw_char | modifier-independent ASCII code |
| | for the character |
+-----------------------------------+-----------------------------------+
1. Get all attached data
2. Acquire the active application lock
3. Create a PortLink instance, target the active ServerApp's sender
port, set the opcode to B_KEY_UP, attach the buffer en masse, and send
it to the BApplication.
4. Release the active application lock
B_UNMAPPED_KEY_DOWN
-------------------
Sent when the user presses a key that has not been mapped to a
character.
Attached Data:
+-----------------------------------+-----------------------------------+
| int64 when | event time in seconds since |
| | 1/1/70 |
+-----------------------------------+-----------------------------------+
| int32 rawcode | code for the physical key pressed |
+-----------------------------------+-----------------------------------+
| int32 modifiers | flags signifying the states of |
| | the modifier keys |
+-----------------------------------+-----------------------------------+
| int8 state_count | number of bytes to follow |
| | containing the state of all keys |
+-----------------------------------+-----------------------------------+
| int8 \*states | array of the state of all keys at |
| | the time of the event |
+-----------------------------------+-----------------------------------+
1. Acquire the active application lock
2. Create a PortLink instance, target the active ServerApp's sender
port, set the opcode to B_UNMAPPED_KEY_DOWN, attach the buffer en masse,
and send it to the BApplication.
3. Release the active application lock
B_UNMAPPED_KEY_UP
-----------------
Sent when the user presses a key that has not been mapped to a
character.
Attached Data:
+-----------------------------------+-----------------------------------+
| int64 when | event time in seconds since |
| | 1/1/70 |
+-----------------------------------+-----------------------------------+
| int32 rawcode | code for the physical key pressed |
+-----------------------------------+-----------------------------------+
| int32 modifiers | flags signifying the states of |
| | the modifier keys |
+-----------------------------------+-----------------------------------+
| int8 state_count | number of bytes to follow |
| | containing the state of all keys |
+-----------------------------------+-----------------------------------+
| int8 \*states | array of the state of all keys at |
| | the time of the event |
+-----------------------------------+-----------------------------------+
1. Acquire the active application lock
2. Create a PortLink instance, target the active ServerApp's sender
port, set the opcode to B_UNMAPPED_KEY_UP, attach the buffer en masse,
and send it to the BApplication.
3. Release the active application lock
B_MODIFIERS_CHANGED
-------------------
Sent when the user presses or releases one of the modifier keys
Attached Data:
+-----------------------------------+-----------------------------------+
| int64 when | event time in seconds since |
| | 1/1/70 |
+-----------------------------------+-----------------------------------+
| int32 modifiers | flags signifying the states of |
| | the modifier keys |
+-----------------------------------+-----------------------------------+
| int32 old_modifiers | former states of the modifier |
| | keys |
+-----------------------------------+-----------------------------------+
| int8 state_count | number of bytes to follow |
| | containing the state of all keys |
+-----------------------------------+-----------------------------------+
| int8 \*states | array of the state of all keys at |
| | the time of the event |
+-----------------------------------+-----------------------------------+
1. Acquire the active application lock
2. Create a PortLink instance, target the active ServerApp's sender
port, set the opcode to B_MODIFIERS_CHANGED, attach the buffer en masse,
and send it to the BApplication.
3. Release the active application lock
@@ -1,123 +0,0 @@
<HTML>
<HEAD>
<TITLE>BitmapManager.htm</TITLE>
<style type="text/css">
<!--
.Default {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 12pt}
.OBOS-Function-Def {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 16pt}
.OBOS-Title {background-color: rgb(255,255,255); color: rgb(0,128,0); font-family: 'Dutch801 Rm BT'; font-size: 24pt}
.Text-Background {background-color: rgb(255,255,255)}
.GR-Default {}
.Body {margin: 0px}
.Footer {margin: 0px}
.Header {margin: 0px}
.WP-Default {text-align: left; text-indent: 0px; margin-left: 0px; margin-right: 0px}
-->
</style>
</HEAD>
<BODY BGCOLOR="#ffffff">
<DIV class="sheet" id="Sheet 1">
<P class="Body" style="margin: 0px"><span class="OBOS-Title">BitmapManager class</span><span style="color: rgb(0,0,0); font-size: 24pt"></span></P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">The BitmapManager object handles all ServerBitmap allocation and deallocation. The rest of the server uses CreateBitmap and DeleteBitmap instead of new and delete. It utilizes the outside pool manager BGET.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><BR>
Member Functions</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<TABLE WIDTH=621 HEIGHT=105 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=238 HEIGHT=17>
<P class="Body" style="margin: 0px">BitmapManager(void)</P>
</TD>
<TD WIDTH=228 HEIGHT=17>
<P class="Body" style="margin: 0px">~BitmapManager(void)</P>
</TD>
</TR>
<TR>
<TD WIDTH=238 HEIGHT=62>
<P class="Body" style="margin: 0px">ServerBitmap *CreateBitmap(BRect bounds, color_space space, int32 flags, int32 bytes_per_row=-1, screen_id screen=B_MAIN_SCREEN_ID)</P>
</TD>
<TD WIDTH=228 HEIGHT=62>
<P class="Body" style="margin: 0px">void DeleteBitmap(ServerBitmap *bitmap)</P>
</TD>
</TR>
</TABLE>
<P class="Body" style="margin: 0px"><BR>
<BR>
Global Functions</P>
<P class="Body" style="margin: 0px"><BR>
extern "C" void set_area_buffer_management(void);</P>
<P class="Body" style="margin: 0px">void * expand_area_storage(long size)</P>
<P class="Body" style="margin: 0px">void contract_area_storage(void *buffer)</P>
<P class="Body" style="margin: 0px"><BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">BitmapManager(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
1) Create the bitmap list</P>
<P class="Body" style="margin: 0px">2) Create the bitmap area</P>
<P class="Body" style="margin: 0px">3) Allocate the access semaphore</P>
<P class="Body" style="margin: 0px">4) Call set_buffer_area_management</P>
<P class="Body" style="margin: 0px">5) Set up the buffer pool via bpool</P>
<P class="Body" style="margin: 0px"><BR>
<span class="OBOS-Function-Def">~BitmapManager(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
1) Iterate over each item in the bitmap list, removing each item, calling brel() on its buffer, and deleting it.</P>
<P class="Body" style="margin: 0px">2) Delete the bitmap list</P>
<P class="Body" style="margin: 0px">3) Delete the bitmap area</P>
<P class="Body" style="margin: 0px">4) Free the access semaphore</P>
<P class="Body" style="margin: 0px"><BR>
<span class="OBOS-Function-Def">ServerBitmap *CreateBitmap(BRect bounds, color_space space, int32 flags, int32 bytes_per_row=-1, screen_id screen=B_MAIN_SCREEN_ID)</span></P>
<P class="Body" style="margin: 0px"><BR>
CreateBitmap is called by outside objects to allocate a ServerBitmap object. If a problem occurs, it returns NULL.</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire the access semaphore</P>
<P class="Body" style="margin: 0px">2) Verify parameters and if any are invalid, spew an error to stderr and return NULL</P>
<P class="Body" style="margin: 0px">3) Allocate a new ServerBitmap</P>
<P class="Body" style="margin: 0px">4) Allocate a buffer for the bitmap via bget() with the bitmap's theoretical buffer length</P>
<P class="Body" style="margin: 0px">5) If NULL, delete the bitmap and return NULL</P>
<P class="Body" style="margin: 0px">6) Set the bitmap's area and buffer to the appropriate values (area_for buffer and buffer)</P>
<P class="Body" style="margin: 0px">7) Add the bitmap to the bitmap list</P>
<P class="Body" style="margin: 0px">8) Release the access semaphore</P>
<P class="Body" style="margin: 0px">9) Return the bitmap</P>
<P class="Body" style="margin: 0px"><BR>
<span class="OBOS-Function-Def">void DeleteBitmap(ServerBitmap *bitmap)</span></P>
<P class="Body" style="margin: 0px"><span class="Default"><BR>
Frees a ServerBitmap allocated by CreateBitmap()</span></P>
<P class="Body" style="margin: 0px"><span class="Default"><BR>
1) Acquire the access semaphore</span></P>
<P class="Body" style="margin: 0px"><span class="Default">2) Find the bitmap in the list</span></P>
<P class="Body" style="margin: 0px"><span class="Default">3) Remove the bitmap from the list or release the semaphore and return if not found</span></P>
<P class="Body" style="margin: 0px"><span class="Default">4) call brel() on the bitmap's buffer if it is non-NULL</span></P>
<P class="Body" style="margin: 0px"><span class="Default">5) delete the bitmap</span></P>
<P class="Body" style="margin: 0px"><span class="Default">6) Release the access semaphore</span></P>
<P class="Body" style="margin: 0px"><span class="Default"><BR>
</span><span class="OBOS-Function-Def">extern "C" void set_area_buffer_management(void)</span></P>
<P class="Body" style="margin: 0px"><span class="Default"><BR>
C function defined externally to point the BGET manager to our homegrown area allocation and deallocation functions.</span></P>
<P class="Body" style="margin: 0px"><span class="Default"><BR>
</span><span class="OBOS-Function-Def">void * expand_area_storage(long size)</span></P>
<P class="Body" style="margin: 0px"><BR>
"Internal" global function accessed only by set_area_buffer_management and BGET to handle the task of adding more area space to make room for more bitmaps.</P>
<P class="Body" style="margin: 0px"><BR>
1) If size is less than B_PAGE_SIZE, set the area size to B_PAGE_SIZE</P>
<P class="Body" style="margin: 0px">2) If size % B_PAGE_SIZE, set area size to (size/B_PAGE_SIZE)+1)*B_PAGE_SIZE, otherwise setting it to the given size</P>
<P class="Body" style="margin: 0px">3) Call create_area with the area size.</P>
<P class="Body" style="margin: 0px">4) If it couldn't allocate an area, write a panic message to stderr and return NULL, otherwise, return the pointer to the area.</P>
<P class="Body" style="margin: 0px"><BR>
<span class="OBOS-Function-Def">void contract_area_storage(void *buffer)</span></P>
<P class="Body" style="margin: 0px"><span class="Default"><BR>
</span>"Internal" global function accessed only by set_area_buffer_management and BGET to <span class="Default">remove the area which was previously used for the bitmap pool</span></P>
<P class="Body" style="margin: 0px"><span class="Default"><BR>
1) Call area_for on the buffer</span></P>
<P class="Body" style="margin: 0px"><span class="Default">2) If the area_id is not B_ERROR, call delete_area on its area_id.</span></P>
<DIV class="layer" id="Layer 1">
</DIV>
</DIV>
</BODY>
</HTML>
@@ -0,0 +1,60 @@
BitmapManager class
###################
The BitmapManager object handles all ServerBitmap allocation and
deallocation. The rest of the server uses CreateBitmap and DeleteBitmap
instead of new and delete.
Member Functions
================
BitmapManager(void)
-------------------
1. Create the bitmap list
2. Create the bitmap area
3. Allocate the access semaphore
4. Call set_buffer_area_management
5. Set up the buffer pool via bpool
~BitmapManager(void)
--------------------
1. Iterate over each item in the bitmap list, removing each item,
calling brel() on its buffer, and deleting it.
2. Delete the bitmap list
3. Delete the bitmap area
4. Free the access semaphore
ServerBitmap \*CreateBitmap(BRect bounds, color_space space, int32 flags, int32 bytes_per_row=-1, screen_id screen=B_MAIN_SCREEN_ID)
------------------------------------------------------------------------------------------------------------------------------------
CreateBitmap is called by outside objects to allocate a ServerBitmap
object. If a problem occurs, it returns NULL.
1. Acquire the access semaphore
2. Verify parameters and if any are invalid, spew an error to stderr and
return NULL
3. Allocate a new ServerBitmap
4. Allocate a buffer for the bitmap with the bitmap's
theoretical buffer length
5. If NULL, delete the bitmap and return NULL
6. Set the bitmap's area and buffer to the appropriate values (area_for
buffer and buffer)
7. Add the bitmap to the bitmap list
8. Release the access semaphore
9. Return the bitmap
void DeleteBitmap(ServerBitmap \*bitmap)
----------------------------------------
Frees a ServerBitmap allocated by CreateBitmap()
1. Acquire the access semaphore
2. Find the bitmap in the list
3. Remove the bitmap from the list or release the semaphore and return
if not found
4. call brel() on the bitmap's buffer if it is non-NULL
5. delete the bitmap
6. Release the access semaphore
@@ -1,112 +0,0 @@
<HTML>
<HEAD>
<TITLE>ColorSet.htm</TITLE>
<style type="text/css">
<!--
.Default {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 12pt}
.OBOS-Function-Def {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 16pt}
.OBOS-Title {background-color: rgb(255,255,255); color: rgb(0,128,0); font-family: 'Dutch801 Rm BT'; font-size: 24pt}
.Text-Background {background-color: rgb(255,255,255)}
.GR-Default {}
.Body {margin: 0px}
.Footer {margin: 0px}
.Header {margin: 0px}
.WP-Default {text-align: left; text-indent: 0px; margin-left: 0px; margin-right: 0px}
-->
</style>
</HEAD>
<BODY BGCOLOR="#ffffff">
<DIV class="sheet" id="Sheet 1">
<P class="Body" style="margin: 0px"><span class="OBOS-Title">ColorSet class</span><span style="color: rgb(0,0,0); font-size: 24pt"></span></P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">The ColorSet class provides an easy manner to manage system attribute colors, such as window tabs, panel background colors, etc. Each member is an RGBColor. The attributes are list below and are publicly accessible.</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">panel_background</P>
<P class="Body" style="margin: 0px">panel_text</P>
<P class="Body" style="margin: 0px">document_background</P>
<P class="Body" style="margin: 0px">document_text</P>
<P class="Body" style="margin: 0px">control_background</P>
<P class="Body" style="margin: 0px">control_text</P>
<P class="Body" style="margin: 0px">control_border</P>
<P class="Body" style="margin: 0px">control_highlight</P>
<P class="Body" style="margin: 0px">tooltip_background</P>
<P class="Body" style="margin: 0px">tooltip_text</P>
<P class="Body" style="margin: 0px">menu_background</P>
<P class="Body" style="margin: 0px">menu_selected_background</P>
<P class="Body" style="margin: 0px">menu_text</P>
<P class="Body" style="margin: 0px">menu_selected_text</P>
<P class="Body" style="margin: 0px">menu_separator_high</P>
<P class="Body" style="margin: 0px">menu_separator_low</P>
<P class="Body" style="margin: 0px">menu_triggers</P>
<P class="Body" style="margin: 0px">window_tab</P>
<P class="Body" style="margin: 0px">window_tab_text</P>
<P class="Body" style="margin: 0px">inactive_window_tab</P>
<P class="Body" style="margin: 0px">inactive_window_tab_text</P>
<P class="Body" style="margin: 0px">keyboard_navigation</P>
<P class="Body" style="margin: 0px">desktop</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><BR>
Member Functions</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<TABLE WIDTH=463 HEIGHT=72 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=203 HEIGHT=17>
<P class="Body" style="margin: 0px">ColorSet(void)</P>
</TD>
<TD WIDTH=260 HEIGHT=17>
<P class="Body" style="margin: 0px">ColorSet(const ColorSet &amp;cs)</P>
</TD>
</TR>
<TR>
<TD WIDTH=203 HEIGHT=38>
<P class="Body" style="margin: 0px">ColorSet &amp; operator=(const ColorSet &amp;cs)</P>
</TD>
<TD WIDTH=260 HEIGHT=38>
<P class="Body" style="margin: 0px">void SetColors(const ColorSet &amp;cs)</P>
</TD>
</TR>
<TR>
<TD WIDTH=203 HEIGHT=17>
<P class="Body" style="margin: 0px">void PrintToStream(void)</P>
</TD>
<TD WIDTH=260 HEIGHT=17>
&nbsp;
</TD>
</TR>
</TABLE>
<P class="Body" style="margin: 0px"><BR>
<BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><BR>
<span class="OBOS-Function-Def">ColorSet(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
Does nothing.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">ColorSet(const ColorSet &amp;cs)</span></P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">ColorSet &amp; operator=(const ColorSet &amp;cs)</span></P>
<P class="Body" style="margin: 0px"><BR>
Calls SetColors on the passed ColorSet object</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void SetColors(const ColorSet &amp;cs)</span></P>
<P class="Body" style="margin: 0px"><BR>
Copies all data members of the passed color set to those of the current one</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void PrintToStream(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
Prints the name of each attribute color and then calls the color's PrintToStream member.</P>
<DIV class="layer" id="Layer 1">
</DIV>
</DIV>
</BODY>
</HTML>
@@ -1,123 +0,0 @@
<HTML>
<HEAD>
<TITLE>ColorUtils.htm</TITLE>
<style type="text/css">
<!--
.Default {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 12pt}
.OBOS-Function-Def {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 16pt}
.OBOS-Title {background-color: rgb(255,255,255); color: rgb(0,128,0); font-family: 'Dutch801 Rm BT'; font-size: 24pt}
.Text-Background {background-color: rgb(255,255,255)}
.GR-Default {}
.Body {margin: 0px}
.Footer {margin: 0px}
.Header {margin: 0px}
.WP-Default {text-align: left; text-indent: 0px; margin-left: 0px; margin-right: 0px}
-->
</style>
</HEAD>
<BODY BGCOLOR="#ffffff">
<DIV class="sheet" id="Sheet 1">
<P class="Body" style="margin: 0px"><span class="OBOS-Title">ColorUtils</span><span style="color: rgb(0,0,0); font-size: 24pt"></span></P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">These functions are used for general purpose color-related tasks.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px">Global Functions</P>
<P class="Body" style="margin: 0px"><BR>
void SetRGBColor32(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a=255)</P>
<P class="Body" style="margin: 0px">void SetRGBAColor32(rgb_color *col, uint16 color16)</P>
<P class="Body" style="margin: 0px">void SetRGBColor16(uint16 *col, uint8 r, uint8 g, uint8 b)</P>
<P class="Body" style="margin: 0px">void SetRGBAColor15(uint16 *col, uint8 r, uint8 g, uint8 b, bool opaque=true)</P>
<P class="Body" style="margin: 0px"><BR>
uint8 FindClosestColor(rgb_color *palette,rgb_color col)</P>
<P class="Body" style="margin: 0px">uint16 FindClosestColor16(rgb_color col)</P>
<P class="Body" style="margin: 0px">uint16 FindClosestColor15(rgb_color col)</P>
<P class="Body" style="margin: 0px"><BR>
rgb_color MakeBlendColor(rgb_color col, rgb_color col2, float position)</P>
<P class="Body" style="margin: 0px"><BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">void SetRGBColor32(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a=255)</span></P>
<P class="Body" style="margin: 0px"><BR>
Simply assigns the passed parameters to the internal members of the passed color</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void SetRGBAColor32(rgb_color *col, uint16 color16)</span></P>
<P class="Body" style="margin: 0px"><BR>
Maps a 16-bit color to a 32-bit one.</P>
<P class="Body" style="margin: 0px"><BR>
gggbbbbb arrrrrgg</P>
<P class="Body" style="margin: 0px">1) Extract component values using the following calculations:</P>
<P class="Body" style="margin: 0px"><BR>
red16 = (uint8[1] &amp; 124) &gt;&gt; 2</P>
<P class="Body" style="margin: 0px"><BR>
green16 = ((uint8[0] &amp; 224) &gt;&gt; 5) | ((uint8[1] &amp; 3) &lt;&lt; 3)</P>
<P class="Body" style="margin: 0px"><BR>
blue16 = uint8[0] &amp; 31</P>
<P class="Body" style="margin: 0px">2) Use cross-multiplication to map each 16-bit color component from 0-31 space to 0-255 space, i.e. red32 = (red16 / 31) * 255</P>
<P class="Body" style="margin: 0px">3) Assign mapped values to the rgb_color passed to the function</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void SetRGBColor16(uint16 *col, uint8 r, uint8 g, uint8 b)</span></P>
<P class="Body" style="margin: 0px"><BR>
Used for easy assignment of opaque (B_RGB16) 16-bit colors.</P>
<P class="Body" style="margin: 0px"><BR>
1) Clip parameters via a bitwise AND with 31 (var &amp;=31)</P>
<P class="Body" style="margin: 0px">2) Create a uint8 * to the passed color</P>
<P class="Body" style="margin: 0px">3) Assign as follows and return:</P>
<P class="Body" style="margin: 0px"> a) uint8[0] = ( (g &amp; 7) &lt;&lt; 5) | (b &amp; 31)</P>
<P class="Body" style="margin: 0px"> b) uint8[1] = ( (r &amp; 31) &lt;&lt; 3) | ( (g &amp; 56) &gt;&gt; 3)</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void SetRGBAColor15(uint16 *col, uint8 r, uint8 g, uint8 b, bool opaque=true)</span></P>
<P class="Body" style="margin: 0px"><BR>
Used for easy assignment of alpha-aware (B_RGBA16) 16-bit colors.</P>
<P class="Body" style="margin: 0px"><BR>
1) Clip parameters via a bitwise AND with 31 (var &amp;=31)</P>
<P class="Body" style="margin: 0px">2) Create a uint8 * to the passed color</P>
<P class="Body" style="margin: 0px">3) Assign as follows and return:</P>
<P class="Body" style="margin: 0px"> a) uint8[0] = ( (g &amp; 7) &lt;&lt; 5) | (b &amp; 31)</P>
<P class="Body" style="margin: 0px"> b) uint8[1] = ( (r &amp; 31) &lt;&lt; 2) | ( (g &amp; 24) &gt;&gt; 3) | (a) ? 128 : 0</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">uint8 FindClosestColor(rgb_color *palette,rgb_color col)</span></P>
<P class="Body" style="margin: 0px"><BR>
Finds the color which most closely resembles the given one in the given palette.</P>
<P class="Body" style="margin: 0px"><BR>
1) Set the saved delta value to 765 (maximum difference)</P>
<P class="Body" style="margin: 0px">2) Loop through all the colors in the palette. For each color,</P>
<P class="Body" style="margin: 0px"> a) calculate the delta value for each color component and add them together</P>
<P class="Body" style="margin: 0px"> b) compare the new combined delta with the saved one</P>
<P class="Body" style="margin: 0px"> c) if the delta is 0, immediately return the current index</P>
<P class="Body" style="margin: 0px"> d) if the new one is smaller, save it and also the palette index</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">uint16 FindClosestColor16(rgb_color col)</span></P>
<P class="Body" style="margin: 0px"><BR>
Returns a 16-bit approximation of the given 32-bit color. Alpha values are ignored.</P>
<P class="Body" style="margin: 0px">1) Create an opaque, 16-bit approximation of col using the following calculations:</P>
<P class="Body" style="margin: 0px"> r16=(31*col.red)/255</P>
<P class="Body" style="margin: 0px"> g16=(31*col.green)/255</P>
<P class="Body" style="margin: 0px"> b16=(31*col.blue)/255</P>
<P class="Body" style="margin: 0px">2) Assign it to a uint16 using the same code as in SetRGBColor16() and return it.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">uint16 FindClosestColor15(rgb_color col)</span></P>
<P class="Body" style="margin: 0px"><BR>
This functions almost exactly like the 16-bit version, but this also takes into account the alpha transparency bit and works in the color space B_RGBA15. Follow the same algorithm as FindClosestColor16(), but assign the return value using SetRGBColor15.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">rgb_color MakeBlendColor(rgb_color col, rgb_color col2, float position)</span></P>
<P class="Body" style="margin: 0px"><BR>
MakeBlendColor calculates a color that is somewhere between start color col and end color col2, based on position, where 0&lt;= position &lt;= 1. If position is out of these bounds, a color of {0,0,0,0} is returned. If position is 0, the start color is returned. If position is 1, col2 is returned. Otherwise, the color is calculated thus:</P>
<P class="Body" style="margin: 0px"><BR>
1) calculate delta values for each channel, i.e. int16 delta_r=col.red-col2.red</P>
<P class="Body" style="margin: 0px">2) Based on these delta values, calculate the blend values for each channel, i.e. blend_color.red=uint8(col1.red - (delta_r * position) )</P>
<DIV class="layer" id="Layer 1">
</DIV>
</DIV>
</BODY>
</HTML>
@@ -0,0 +1,114 @@
ColorUtils
##########
These functions are used for general purpose color-related tasks.
Global Functions
================
void SetRGBColor32(rgb_color \*col, uint8 r, uint8 g, uint8 b, uint8 a=255)
---------------------------------------------------------------------------
Simply assigns the passed parameters to the internal members of the
passed color
void SetRGBAColor32(rgb_color \*col, uint16 color16)
----------------------------------------------------
Maps a 16-bit color to a 32-bit one.
gggbbbbb arrrrrgg
1. Extract component values using the following calculations:
.. code-block:: cpp
red16 = (uint8[1] & 124) >> 2;
green16 = ((uint8[0] & 224) >> 5) | ((uint8[1] & 3) << 3);
blue16 = uint8[0] & 31;
2. Use cross-multiplication to map each 16-bit color component from 0-31
space to 0-255 space, i.e. red32 = (red16 / 31) \* 255
3. Assign mapped values to the rgb_color passed to the function
void SetRGBColor16(uint16 \*col, uint8 r, uint8 g, uint8 b)
-----------------------------------------------------------
Used for easy assignment of opaque (B_RGB16) 16-bit colors.
1. Clip parameters via a bitwise AND with 31 (var &=31)
2. Create a uint8 * to the passed color
3. Assign as follows and return:
.. code-block:: cpp
uint8[0] = ( (g & 7) << 5) | (b & 31);
uint8[1] = ( (r & 31) << 3) | ( (g & 56) >> 3);
void SetRGBAColor15(uint16 \*col, uint8 r, uint8 g, uint8 b, bool opaque=true)
------------------------------------------------------------------------------
Used for easy assignment of alpha-aware (B_RGBA16) 16-bit colors.
1. Clip parameters via a bitwise AND with 31 (var &=31)
2. Create a uint8 * to the passed color
3. Assign as follows and return:
.. code-block:: cpp
uint8[0] = ( (g & 7) << 5) | (b & 31);
uint8[1] = ( (r & 31) << 2) | ( (g & 24) >> 3) | (a) ? 128 : 0;
uint8 FindClosestColor(rgb_color \*palette,rgb_color col)
---------------------------------------------------------
Finds the color which most closely resembles the given one in the
given palette.
1. Set the saved delta value to 765 (maximum difference)
2. Loop through all the colors in the palette. For each color,
a. calculate the delta value for each color component and add them
together
b. compare the new combined delta with the saved one
c. if the delta is 0, immediately return the current index
d. if the new one is smaller, save it and also the palette index
uint16 FindClosestColor16(rgb_color col)
----------------------------------------
Returns a 16-bit approximation of the given 32-bit color. Alpha values
are ignored.
1. Create an opaque, 16-bit approximation of col using the following
calculations:
.. code-block:: cpp
r16 = (31 * col.red) / 255;
g16 = (31 * col.green) / 255;
b16 = (31 * col.blue) / 255;
2. Assign it to a uint16 using the same code as in SetRGBColor16() and
return it.
uint16 FindClosestColor15(rgb_color col)
----------------------------------------
This functions almost exactly like the 16-bit version, but this also
takes into account the alpha transparency bit and works in the color
space B_RGBA15. Follow the same algorithm as FindClosestColor16(), but
assign the return value using SetRGBColor15.
rgb_color MakeBlendColor(rgb_color col, rgb_color col2, float position)
-----------------------------------------------------------------------
MakeBlendColor calculates a color that is somewhere between start
color col and end color col2, based on position, where 0<= position <=
1. If position is out of these bounds, a color of {0,0,0,0} is
returned. If position is 0, the start color is returned. If position
is 1, col2 is returned. Otherwise, the color is calculated thus:
1. calculate delta values for each channel, i.e. int16 delta_r=col.red-col2.red
2. Based on these delta values, calculate the blend values for each
channel, i.e. blend_color.red=uint8(col1.red - (delta_r \* position) )
@@ -1,215 +0,0 @@
<HTML>
<HEAD>
<TITLE>CursorManager.htm</TITLE>
<style type="text/css">
<!--
.Default {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 12pt}
.OBOS-Function-Def {background-color: rgb(255,255,255); color: rgb(0,0,0); font-family: 'Dutch801 Rm BT'; font-size: 16pt}
.OBOS-Title {background-color: rgb(255,255,255); color: rgb(0,128,0); font-family: 'Dutch801 Rm BT'; font-size: 24pt}
.Text-Background {background-color: rgb(255,255,255)}
.GR-Default {}
.Body {margin: 0px}
.Footer {margin: 0px}
.Header {margin: 0px}
.WP-Default {text-align: left; text-indent: 0px; margin-left: 0px; margin-right: 0px}
-->
</style>
</HEAD>
<BODY BGCOLOR="#ffffff">
<DIV class="sheet" id="Sheet 1">
<P class="Body" style="margin: 0px"><span class="OBOS-Title">CursorManager class</span><span style="color: rgb(0,0,0); font-size: 24pt"></span></P>
<P class="Body" style="margin: 0px"><BR>
</P>
<P class="Body" style="margin: 0px">The CursorManager class handles token creation, calling the cursor-related graphics driver functions, and freeing heap memory for all ServerCursor instances.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><BR>
Member Functions</P>
<P class="Body" style="margin: 0px"><BR>
</P>
<TABLE WIDTH=620 HEIGHT=178 BORDER=1 CELLPADDING=1 CELLSPACING=2>
<TR>
<TD WIDTH=215 HEIGHT=17>
<P class="Body" style="margin: 0px">CursorManager(void)</P>
</TD>
<TD WIDTH=250 HEIGHT=17>
<P class="Body" style="margin: 0px">~CursorManager(void)</P>
</TD>
</TR>
<TR>
<TD WIDTH=215 HEIGHT=17>
<P class="Body" style="margin: 0px">int32 AddCursor(ServerCursor *c)</P>
</TD>
<TD WIDTH=250 HEIGHT=17>
<P class="Body" style="margin: 0px">void DeleteCursor(int32 token)</P>
</TD>
</TR>
<TR>
<TD WIDTH=215 HEIGHT=17>
<P class="Body" style="margin: 0px">void RemoveAppCursors(ServerApp *a)</P>
</TD>
<TD WIDTH=250 HEIGHT=17>
<P class="Body" style="margin: 0px">void ShowCursor(void)</P>
</TD>
</TR>
<TR>
<TD WIDTH=215 HEIGHT=17>
<P class="Body" style="margin: 0px">void HideCursor(void)</P>
</TD>
<TD WIDTH=250 HEIGHT=17>
<P class="Body" style="margin: 0px">void ObscureCursor(void)</P>
</TD>
</TR>
<TR>
<TD WIDTH=215 HEIGHT=17>
<P class="Body" style="margin: 0px">void SetCursor(int32 token)</P>
</TD>
<TD WIDTH=250 HEIGHT=17>
<P class="Body" style="margin: 0px">ServerCursor *GetCursor(cursor_which which)</P>
</TD>
</TR>
<TR>
<TD WIDTH=215 HEIGHT=32>
<P class="Body" style="margin: 0px">void SetCursor(cursor_which which)</P>
</TD>
<TD WIDTH=250 HEIGHT=32>
<P class="Body" style="margin: 0px">void ChangeCursor(cursor_which which,</P>
<P class="Body" style="margin: 0px">int32 token)</P>
</TD>
</TR>
<TR>
<TD WIDTH=215 HEIGHT=17>
<P class="Body" style="margin: 0px">cursor_which GetCursorWhich(void)</P>
</TD>
<TD WIDTH=250 HEIGHT=17>
&nbsp;
</TD>
</TR>
</TABLE>
<P class="Body" style="margin: 0px"><BR>
<BR>
Enumerated Types:</P>
<P class="Body" style="margin: 0px"><BR>
cursor_which {</P>
<P class="Body" style="margin: 0px"> CURSOR_DEFAULT</P>
<P class="Body" style="margin: 0px"> CURSOR_TEXT</P>
<P class="Body" style="margin: 0px"> CURSOR_MOVE</P>
<P class="Body" style="margin: 0px"> CURSOR_DRAG</P>
<P class="Body" style="margin: 0px"> CURSOR_RESIZE</P>
<P class="Body" style="margin: 0px"> CURSOR_RESIZE_NW</P>
<P class="Body" style="margin: 0px"> CURSOR_RESIZE_SE</P>
<P class="Body" style="margin: 0px"> CURSOR_RESIZE_NS</P>
<P class="Body" style="margin: 0px"> CURSOR_RESIZE_EW</P>
<P class="Body" style="margin: 0px"> CURSOR_OTHER</P>
<P class="Body" style="margin: 0px">}</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<HR>
</P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">CursorManager(void)</span><span style="font-size: 16pt"></span></P>
<P class="Body" style="margin: 0px"><BR>
<BR>
1) Create the cursor list empty</P>
<P class="Body" style="margin: 0px">2) Set the token index to 0</P>
<P class="Body" style="margin: 0px">3) Allocate the default system cursor and pass it to AddCursor</P>
<P class="Body" style="margin: 0px">4) Initialize the member pointer for the graphics driver</P>
<P class="Body" style="margin: 0px">5) Create the cursorlock semaphore</P>
<P class="Body" style="margin: 0px">6) Call SetDefaultCursor</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">~CursorManager(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
1) Empty and delete the cursor list</P>
<P class="Body" style="margin: 0px">2) Delete the cursorlock semaphore</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">int32 AddCursor(ServerCursor *sc)</span></P>
<P class="Body" style="margin: 0px"><BR>
AddCursor() is used to register the cursor in question with the manager, allowing for the user application to have the identifying token, if necessary. The cursor becomes the property of the manager. If a user application deletes a BCursor, its ServerApp will call DeleteCursor().</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire cursor lock</P>
<P class="Body" style="margin: 0px">2) Add *sc to the cursor list</P>
<P class="Body" style="margin: 0px">3) Set sc-&gt;token to the current token index value</P>
<P class="Body" style="margin: 0px">4) Increment the token index</P>
<P class="Body" style="margin: 0px">5) Assign sc-&gt;token to temporary variable</P>
<P class="Body" style="margin: 0px">6) Release cursor lock</P>
<P class="Body" style="margin: 0px">7) Return the saved token value</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void DeleteCursor(int32 ctoken)</span></P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire cursor lock</P>
<P class="Body" style="margin: 0px">2) Iterate through the cursor list, looking for ctoken</P>
<P class="Body" style="margin: 0px">3) If any ServerCursor-&gt;token equals ctoken, remove and delete it</P>
<P class="Body" style="margin: 0px">4) Release cursor lock</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void RemoveAppCursors(ServerApp *app)</span></P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire cursor lock</P>
<P class="Body" style="margin: 0px">2) Iterate through the cursor list, checking each cursor's ServerApp pointer</P>
<P class="Body" style="margin: 0px">3) If any have a ServerApp pointer which matches the passed pointer, remove and delete them</P>
<P class="Body" style="margin: 0px">4) Release cursor lock</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void ShowCursor(void)</span></P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">void HideCursor(void)</span></P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">void ObscureCursor(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
Simple pass-through functions which call the graphics driver's functions. Note that acquiring the cursor lock will be necessary for all three calls.</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void SetCursor(int32 token)</span></P>
<P class="Body" style="margin: 0px"><span class="OBOS-Function-Def">void SetCursor(cursor_which cursor)</span></P>
<P class="Body" style="margin: 0px"><BR>
These set the current cursor for the graphics driver to the passed cursor, either one previously added via AddCursor or a system cursor.</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire cursor lock</P>
<P class="Body" style="margin: 0px"><BR>
Token version:</P>
<P class="Body" style="margin: 0px">2) Find the cursor in the cursor list and call the graphics driver if non-NULL</P>
<P class="Body" style="margin: 0px">3) Iterate through list of system cursor tokens and see if there's a match. If so, set the internal cursor_which to the match.</P>
<P class="Body" style="margin: 0px"><BR>
cursor_which version:</P>
<P class="Body" style="margin: 0px">2) determine which cursor to use via a switch statement and call the graphics driver with the internal pointer for the appropriate cursor</P>
<P class="Body" style="margin: 0px">3) set the internal cursor_which to the one passed to the function</P>
<P class="Body" style="margin: 0px"><BR>
4) Release cursor lock</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">ServerCursor *GetCursor(cursor_which which)</span></P>
<P class="Body" style="margin: 0px"><BR>
GetCursor is intended for use in figuring out what cursor is in use for a particular system cursor.</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire cursor lock</P>
<P class="Body" style="margin: 0px">2) use a switch statement to figure which cursor to return and assign a temporary pointer its value</P>
<P class="Body" style="margin: 0px">3) Release cursor lock</P>
<P class="Body" style="margin: 0px">4) Return the temporary pointer</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">void ChangeCursor(cursor_which which, int32 token)</span></P>
<P class="Body" style="margin: 0px"><BR>
Calling ChangeCursor will allow a user to change a system cursor's appearance. Note that in calling this, the cursor changes ownership and belongs to the system. Thus, the BCursor destructor will not ultimately cause the cursor to be deleted.</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire cursor lock</P>
<P class="Body" style="margin: 0px">2) Call FindCursor and, if NULL, release the cursor lock and return</P>
<P class="Body" style="margin: 0px">3) Look up the pointer for the system cursor in question and check to see if it is active. If active, then set the local active flag to true. Set the system cursor pointer to the one looked up.</P>
<P class="Body" style="margin: 0px">4) If active flag is true, call SetCursor()</P>
<P class="Body" style="margin: 0px">5) Release cursor lock</P>
<P class="Body" style="margin: 0px"><BR>
<BR>
<span class="OBOS-Function-Def">cursor_which GetCursorWhich(void)</span></P>
<P class="Body" style="margin: 0px"><BR>
Returns the current cursor_which attribute which describes the currently active cursor. If the active cursor is not a system cursor, it will return CURSOR_OTHER.</P>
<P class="Body" style="margin: 0px"><BR>
1) Acquire cursor lock</P>
<P class="Body" style="margin: 0px">2) Create a local cursor_which and assign it the value of the CursorManager's cursor_which</P>
<P class="Body" style="margin: 0px">3) Release cursor lock</P>
<P class="Body" style="margin: 0px">4) Return the local copy</P>
<DIV class="layer" id="Layer 1">
</DIV>
</DIV>
</BODY>
</HTML>

Some files were not shown because too many files have changed in this diff Show More