Generate developer docs with Sphinx
An effort was started some time ago to consolidate all internal documentation in the git tree. However, this was just an accumulation of files in various formats without any strucutre or way to browse it, which results in no one even knowing that we have docs here. This converts most of the files to restructuredtext and uses Sphinx to generate an HTML browsable user manual (with a table of content and a first attempt to put things in a global hierarchy). There are almost no changes to the documentation content in this commit (some obviously obsolete things were removed). The plan is to get the toolchain up and running to make these docs easily available, and only then see about improving the content. We can migrate some things off the wiki and website, and rework the table of contents to have some more hierarchy levels because currently it's a bit messy. Change-Id: I924ac9dc6e753887ab56f18a09bdb0a1e1793bfd Reviewed-on: https://review.haiku-os.org/c/haiku/+/4370 Reviewed-by: Niels Sascha Reedijk <[email protected]>
This commit is contained in:
committed by
Adrien Destugues
parent
7f8195344a
commit
a5061ecec5
@@ -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)
|
||||
@@ -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!)
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

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