B2G/Architecture: Difference between revisions

From MozillaWiki
< B2G
Jump to navigation Jump to search
Line 349: Line 349:


WiFi is implemented by a worker that uses jsctypes to open an interface to the wpa_supplicant.  The worker scans for available networks, chooses one to connect to, and then lets the higher-level network compontents configure the network.
WiFi is implemented by a worker that uses jsctypes to open an interface to the wpa_supplicant.  The worker scans for available networks, chooses one to connect to, and then lets the higher-level network compontents configure the network.
[https://github.com/cgjones/mozilla-central/blob/master/dom/wifi/nsWifiWorker.js WiFi worker code]
'''TODO'''


== Network Manager ==
== Network Manager ==


The network manager configures network interfaces opened by the 3g-data and wifi components.  '''TODO'''
The network manager configures network interfaces opened by the 3g-data and wifi components.  '''TODO'''

Revision as of 15:46, 29 March 2012

This document describes at a high level how Gaia and B2G work in the port of Gecko to Gonk. If you don't know what these things are yet, don't worry, they'll be introduced below.

Gaia/B2G/Gecko are under heavy development, so some items below discuss how the code will work, instead of how it currently works. This will be noted.

Terminology

Gaia : The user interface of b2g. Everything drawn to screen after b2g starts up is some part of Gaia. Gaia implements a lock screen, home screen, telephone dialer, text-messaging application, camera app, ... and many more. Gaia is written entirely in HTML, CSS, and JavaScript. Its only interface to the underlying operating system is through Open Web APIs, which are implemented by Gecko. Gaia works well when run on top of b2g; however, since it only uses standard web APIs, it works on other OSes and in other web browsers (albeit with degraded functionality). Third-party applications can be installed alongside Gaia.

Gecko : The "application runtime" of b2g. At a high level, Gecko implements the open standards for HTML, CSS, and JS and makes those interfaces run well on all the OSes that Gecko supports. This means that Gecko consists of, among other things, a networking stack, graphics stack, layout engine, virtual machine (for JS), and porting layers.

Gonk : The lower-level "operating system" of b2g. Gonk consists of a linux kernel and userspace hardware abstraction layer (HAL). The kernel and several userspace libraries are common open-source projects: linux, libusb, bluez, etc. Some other parts of the HAL are shared with the android project: GPS, camera, among others. You could say that Gonk is an extremely simple linux distribution. Gonk is a porting target of Gecko; there is a port of Gecko to Gonk, just like there is a port of Gecko to OS X, and a port of Gecko to Android. Since the b2g project has full control over Gonk, we can expose interfaces to Gecko that aren't possible to expose on other OSes. For example, Gecko has direct access to the full telephony stack and display framebuffer on Gonk, but doesn't have this access on any other OS.

Booting

After turning on a b2g phone, execution starts in the primary bootloader. From there, the process of loading the main OS kernel happens in the usual way: a succession of higher-level bootloaders bootstrap the next loader in the chain. At the end of the process, execution is handed off to the linux kernel.

There's not a lot to say about the boot process, but there are a few things worth knowing

  • The bootloaders usually show the first "splash screen" seen during device boot, which usually displays a vendor logo.
  • The bootloaders implement flashing an image onto the device. Different devices use different protocols. Most phones use the fastboot protocol, but the Galaxy S II uses the "odin" protocol.
  • By the end of the bootstrapping process, the modem image is usually loaded and running on the modem processor. How this happens is highly device-specific and possibly proprietary.

Kernel (Linux)

The linux kernel(s) in Gonk is reasonably close to upstream linux. There are a few modifications made by AOSP that are not in upstream yet. Vendors also modify the linux kernel and upstream those modifications on their own schedule. But in general, the linux kernel is close to stock.

The startup process for linux is well documented elsewhere on the internet, so it's not covered here. At the end of kernel startup, a userspace "init" process is launched, like in most other UNIX-like OSes. At this point in execution, only a ramdisk is mounted. The ramdisk is built during the b2g build process, and consists of critical utilities (like init), other startup scripts, and loadable kernel modules.

After launching the init process, the linux kernel services system calls from userspace and interrupts etc. from hardware devices. Many devices are exposed to userspace through sysfs (documented elsewhere on the internet). For example, here's some code that reads the battery state in Gecko (link to original code)

  FILE *capacityFile = fopen("/sys/class/power_supply/battery/capacity", "r");
  double capacity = dom::battery::kDefaultLevel * 100;
  if (capacityFile) {
    fscanf(capacityFile, "%lf", &capacity);
    fclose(capacityFile);
  }

init

The init process in Gonk mounts necessary file systems, spawns system services, and acts a process manager after that. This is very similar to init on other UNIX-like OSes. init "interprets" scripts (init*.rc), that consist of commands. Here's the main init script for the Galaxy S II

Main init script for the Galaxy S II

In particular, init is responsible for launching the b2g process, described below. Here's the snippet of code that launches b2g

service b2g /system/b2g/b2g
    onrestart restart media

Userspace process architecture

At this point, it's best to step back a bit and look at how the various components of b2g fit together, at a very high level.

B2G processes

This diagram shows the main userspace processes in b2g. (NOTE: this diagram is subject to change, and not guaranteed to be 100% accurate.) The dotted lines show processes that are spawned by init. The solid lines show other communication channels.

The b2g process is the main system process. It runs with high privileges: it has access to most hardware devices. b2g communicates with the modem, draws to the display framebuffer, and talks to GPS, cameras, and other devices. Internally, b2g runs Gecko code (libxul.so). The details of how it communicates with hardware are discussed below.

The b2g process may spawn a number of low-rights "content processes". These are where web applications and other web content are loaded. These process communicate with the main Gecko server process through IPDL, a message-passing system.

The rild process is the interface to the modem processor. "RIL" is the "radio interface layer", "rild" is the "RIL daemon". It's a proprietary "blob" of code implemented by hardware vendors. rild allows a client to connect to a UNIX-domain socket it binds to.

service ril-daemon /system/bin/rild
    socket rild stream 660 root radio

In b2g, the rild client is the rilproxy process. It just acts as a dumb forwarding proxy between rild and b2g. The reason we need this proxy is an implementation detail, not important. The code lives here.

The mediaserver process controls audio and video playback. Gecko talks to it through an android RPC mechanism. The code for mediaserver lives here. Some of the media that Gecko can play (OGG Vorbis audio, OGG Theora video, and WebM video) are decoded by Gecko and sent directly to the mediaserver. Other media files are decoded by libstagefright, which is capable of accessing proprietary codecs and hardware decoders. (NOTE: B2G will not use the mediaserver process, in its current incarnation, in the long run.)

The netd process is used to configure network interfaces. wpa_supplicant is the standard UNIX-ish daemon that connects to WiFi access points. TODO: document other processes

Gecko: Processing input events

Most action inside of Gecko is triggered by input events (button presses, touch-screen touches, and so forth). Input events enter Gecko through the Gonk "app shell" (on b2g). For example

void
GeckoInputDispatcher::notifyKey(nsecs_t eventTime,
                                int32_t deviceId,
                                int32_t source,
                                uint32_t policyFlags,
                                int32_t action,
                                int32_t flags,
                                int32_t keyCode,
                                int32_t scanCode,
                                int32_t metaState,
                                nsecs_t downTime)
{
    UserInputData data;
    data.timeMs = nanosecsToMillisecs(eventTime);
    data.type = UserInputData::KEY_DATA;
    data.action = action;
    data.flags = flags;
    data.metaState = metaState;
    data.key.keyCode = keyCode;
    data.key.scanCode = scanCode;
    {
        MutexAutoLock lock(mQueueLock);
        mEventQueue.push(data);
    }
    gAppShell->NotifyNativeEvent();
}

These input events originate from the standard linux input_event system, dispatched by input-device drivers. We use a light abstraction over that which provides some nice features like filtering of events. Here's where input events originate

            if (pfd.revents & POLLIN) {
                int32_t readSize = read(pfd.fd, mInputBufferData,
                        sizeof(struct input_event) * INPUT_BUFFER_SIZE);
                if (readSize < 0) {
                    if (errno != EAGAIN && errno != EINTR) {
                        LOGW("could not get event (errno=%d)", errno);
                    }
                } else if ((readSize % sizeof(struct input_event)) != 0) {
                    LOGE("could not get event (wrong size: %d)", readSize);
                } else {
                    mInputBufferCount = readSize / sizeof(struct input_event);
                    mInputBufferIndex = 0;
                }
            }
        }

After being read by Gecko, inputs are dispatched into the DOM from here

static nsEventStatus
sendKeyEventWithMsg(PRUint32 keyCode,
                    PRUint32 msg,
                    uint64_t timeMs,
                    PRUint32 flags)
{
    nsKeyEvent event(true, msg, NULL);
    event.keyCode = keyCode;
    event.time = timeMs;
    event.flags |= flags;
    return nsWindow::DispatchInputEvent(event);
}

From here on, the events are either consumed by Gecko internally, or dispatched to web applications as DOM events.

Gecko: Graphics

At the very lowest level, Gecko uses OpenGL ES 2.0 to draw to a glcontext that wraps the hardware framebuffers. Gecko sets up its graphics context here

        gNativeWindow = new android::FramebufferNativeWindow();
        sGLContext = GLContextProvider::CreateForWindow(this);

The FramebufferNativeWindow is implemented here. It uses the "gralloc" hardware interface to the graphics driver to map buffers from the framebuffer device.

Gecko uses its Layers system to composite drawn content to the screen. A full description of layers is beyond the scope of this document, but what approximately happens is

  • Gecko draws separate regions of pages into memory buffers. Sometimes, these buffers are system memory. Other times, they're textures mapped into Gecko's address space, meaning Gecko draws directly to VRAM. Drawing happens here, in the common case.
  • Gecko composites these textures to screen using GL commands. Composition of the pixels painted above would happen here.

The details of how Gecko actually draws web content are beyond the scope of this document.

Gecko: Hardware Abstraction Layer (hal)

(The Gecko hardware abstraction layer is not to be confused with the Gonk hardware abstraction layers. Where possible, the Gecko hardware abstraction layer will be referred to as "hal", and the Gonk hardware abstraction layers will be referred to as "HAL".)

"hal" is one on the porting layers of Gecko. It's used for low-level access to system interfaces across multiple platforms. hal offers a cross-platform C++ API to the higher levels of Gecko, with implementations of those APIs per platform inside of hal itself. hal is not exposed directly to anything other than C++ code in Gecko.

It's easiest to understand hal through an example. We'll use the vibration API. The hal API for vibration is here

void Vibrate(const nsTArray<uint32>& pattern);

(Note: the real API is slightly more complicated, for reasons that aren't relevant to this discussion. The complexities are omitted in this presentation.)

This API tells the vibration motor to turn on/off according to the pattern specified in pattern. The API is implemented for Gonk here

void
Vibrate(const nsTArray<uint32> &pattern)
{
  EnsureVibratorThreadInitialized();
  sVibratorRunnable->Vibrate(pattern);
}

which sends the request off to another thread here, where the real work is done

  while (!mShuttingDown) {
    if (mIndex < mPattern.Length()) {
      uint32 duration = mPattern[mIndex];
      if (mIndex % 2 == 0) {
        vibrator_on(duration);
      }
      mIndex++;
      mMonitor.Wait(PR_MillisecondsToInterval(duration));
    }
    else {
      mMonitor.Wait();
    }
  }

vibrate_on() is the Gonk HAL API for turning on the vibrator motor. Internally, vibrate_on() writes a value to a kernel object exposed through sysfs, which in effect sends the kernel driver a message.

The hal APIs are supported across all platforms. When Gecko is built for a platform that doesn't expose an interface to vibrator motors, then the "fallback" implementation of the hal API is used. For vibration, it's here

void
Vibrate(const nsTArray<uint32>& pattern)
{}

Most web content runs in low-rights content processes (see above). We can't assume those content processes have the necessary privileges to turn on the vibration motor, and in addition we want to resolve race conditions in a central location. In hal, this is done through the "sandbox" implementation of hal. The sandbox implementation merely proxies requests from content processes to the "Gecko server" process. The proxy requests are sent through IPDL. For vibration, the request originates here

void
Vibrate(const nsTArray<uint32>& pattern)
{
  Hal()->SendVibrate(pattern);
}

which sends a message that's defined by this interface

    Vibrate(uint32[] pattern);

to a receiver of the message defined here

  NS_OVERRIDE virtual bool
  RecvVibrate(const InfallibleTArray<unsigned int>& pattern)
  {
    // Forward to hal::, not hal_impl::, because we might be a
    // subprocess of another sandboxed process. The hal:: entry point
    // will do the right thing.
    hal::Vibrate(pattern);
    return true;

(omitting some details that aren't relevant to this discussion). So a vibration request sent by a content process on the Gecko port to Gonk eventually ends up in the GonkHal implementation of Vibrate(), discussed above.

Gecko: DOM APIs

"DOM interfaces", approximately, are how web content communicates with Gecko. There's a lot more to the DOM than that, but the longer discussion is beyond the scope of this document. DOM interfaces are defined in IDL, which comprises both a foreign function interface (ffi) and object model (OM) between JavaScript and C++. Again, there are more details that could be discussed here which are beyond the scope of this document. Let's learn a bit of IDL by example.

The very simple vibration API is exposed to web content through an IDL interface. That interface is here

  [implicit_jscontext]
  void mozVibrate(in jsval aPattern);

The jsval argument indicates that mozVibrate (our vendor-prefixed implementation of the vibration specification) accepts any JS value. Details of working with jsvals are beyond the scope of this document. The IDL compiler generates a C++ interface that's implemented here

NS_IMETHODIMP
Navigator::MozVibrate(const jsval& aPattern, JSContext* cx)
{
  // ...
  hal::Vibrate(pattern);
  return NS_OK;
}

There's quite a lot of code that's hidden here by the ellipsis ("..."), but let's ignore for it now.

The call to hal::Vibrate() transfers control from the DOM to hal. From there, we enter the hal implementation discussed above. A key point to note here is that the DOM implementation doesn't care what platform it's running on (Gonk or Windows or OS X or ...), nor does it care whether the code is running in a "content process" or the Gecko "server process". This is all left to lower levels of the system.

The vibration API happens to be quite simple, so it's a good example. The SMS API, which is much more complicated and has its own "remoting" layer from content processes to the server, is here.

RIL: Telephony

The RIL was discussed briefly above. In this section, we'll how the various pieces interact in a little more detail. The main actors are

  • rild : the proprietary bit of code that talks to the proprietary modem firmware
  • rilproxy : the daemon that proxies messages between rild and Gecko (the b2g process)
  • Gecko (the b2g process) : implements the higher-level telephony stack

Let's start with an example that demonstrates the lower-level parts of the system. When the modem receives an incoming call, it notifies the rild using a proprietary mechanism. The rild then prepares a message for its client according to the "open" protocol here. In this case, an incoming call generates the RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED message. This message is sent by rild to its client and received by rilproxy here

          ret = read(rilproxy_rw, data, 1024);
          if(ret > 0) {
            writeToSocket(rild_rw, data, ret);
          }

where it's forwarded along to Gecko, on the socket connecting rilproxy and Gecko. Gecko receives these forwarded bytes here

            int ret = read(fd, mIncoming->mData, 1024);
            // [handle errors]
            mIncoming->mSize = ret;
            sConsumer->MessageReceived(mIncoming.forget());

The consumer is here, and repackages the message and dispatches it to a "worker thread" that implements the RIL state machine

  virtual void MessageReceived(RilRawData *aMessage) {
    nsRefPtr<DispatchRILEvent> dre(new DispatchRILEvent(aMessage));
    mDispatcher->PostTask(dre);
  }

The task posted to that thread is sent into the JS code on the RIL worker here

  return JS_CallFunctionName(aCx, obj, "onRILMessage", NS_ARRAY_LENGTH(argv),
                             argv, argv);

which calls a function onRILMessage() in JS. That function is defined here. The message bytes are processed a bit, until finally we dispatch the message here

  handleParcel: function handleParcel(request_type, length) {
    let method = this[request_type];
    if (typeof method == "function") {
      if (DEBUG) debug("Handling parcel as " + method.name);
      method.call(this, length);
    }
  }
};

which calls into a function defined here

RIL[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
  Phone.onCallStateChanged();
};

and then we bounce around for a bit and get to

  getCurrentCalls: function getCurrentCalls() {
    Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS);
  },

This sends a request back to the rild to request the state of all currently-active calls. The request follows a similar path back to rild (out from ril_worker.js, to SystemWorkerManager.cpp, off to Ril.cpp, then to rilproxy.c and finally written to the rild socket). The response from rild is processed similarly to above, and then finally we detect that a new call has been received and notify the DOM here

  _handleChangedCallState: function _handleChangedCallState(changedCall) {
    let message = {type: "callStateChange",
                   call: {callIndex: changedCall.callIndex,
                          state: changedCall.state,
                          number: changedCall.number,
                          name: changedCall.name}};
    this.sendDOMMessage(message);
  },

This sets off a chain of events that results in the currently-active dialer application being notified of an incoming call. In gaia, this notification is received here

  handleEvent: function fm_handleEvent(evt) {
    console.log('Call changed state: ' + evt.call.state);
    switch (evt.call.state) {
      case 'incoming':
        console.log('incoming call from ' + evt.call.number);
        this.incoming(evt.call);
        break;
      case 'connected':
        this.connected();
        break;
      case 'disconnected':
        this.disconnected();
        break;
      default:
        break;
    }
  },

at the 'incoming' case in the switch statement.

RIL: 3G Data

There is a RIL message that places a "data call" to the cellular tower, which enables data-transfer mode in the modem. This data call ends up creating/activating a PPP interface device in the linux kernel that can be configured through usual interfaces. TODO

WiFi

WiFi is implemented by a worker that uses jsctypes to open an interface to the wpa_supplicant. The worker scans for available networks, chooses one to connect to, and then lets the higher-level network compontents configure the network.

Network Manager

The network manager configures network interfaces opened by the 3g-data and wifi components. TODO