Embedded Linux & DOOM

Linux framebuffers explained: DOOM on my Cisco phone

The documented game runs locally

The final v.666 game is documented running locally and offline on the phone. Installation and project downloads need their own transfer route. These small exercises upload no device data.

In this article
  1. A framebuffer is a display interface
  2. Why the final build really runs on the phone
  3. Understand resolution, pixel format and stride
  4. Try a calculation without accessing a device
  5. Inspect your Linux display without writing to it
  6. Physical keys need compatible event decoding
  7. Audio and persistent startup are separate tasks
  8. Project and references

A Cisco CP-9951 looks like an office phone, but an ARM Linux system works inside it. My DOOM project uses its local display and physical keys. The final v.666 build runs on the phone itself and is documented working after a cable-free cold boot.

The full story is in Field Note 7 in the Computer Museum. This article explains the technical core for beginners: what a framebuffer is, how a pixel's address is calculated and why input, audio and startup need separate work too.

A framebuffer is a display interface

Imagine a memory region whose values appear as pixels on a screen. Linux's fbdev interface lets a program query information about such a surface and access its image data.

A device such as /dev/fb1 is not a PNG or a ready-made windowing interface. The program needs the geometry, memory layout and pixel format. “32 bits” alone does not establish where the red and blue components belong.

Modern Linux desktops often use DRM/KMS and a compositor. An exposed /dev/fb0 may be a compatibility layer. This older embedded Cisco platform is therefore not a universal recipe for every current graphics card.

Why the final build really runs on the phone

The project distinguishes several development stages. Initially, the game ran on Cthulhu and the phone received a stream. A remote input bridge followed. These intermediate versions had different data flows from the final native port.

In the final architecture, the phone's ARM CPU runs the game process. Output goes to /dev/fb1, and local input comes through /dev/input/keypad0. The documentation identifies MontaVista Linux with a 2.6.18 kernel on ARMv6. This particular target shaped toolchain and runtime requirements.

Task Final project implementation
Game computation ARM process on the CP-9951
Video output Local /dev/fb1 framebuffer
Controls Local Raven keypad events
Startup Applications → Doom through a local endpoint

A photo of the display alone could not rule out streaming. The documented runtime paths and offline cold boot establish the more useful distinction.

Understand resolution, pixel format and stride

A renderer needs visible width and height, bits per pixel and the distance between image rows. That distance is often called stride or pitch; fbdev's fixed-information structure calls it line_length.

Rows can contain trailing padding. Therefore, width × bytes per pixel is not always the distance to the next row. For packed pixels, the byte offset generally combines row stride and horizontal position; virtual offsets and the actual format also need consideration.

The interface provides queries including FBIOGET_FSCREENINFO and FBIOGET_VSCREENINFO for fixed and variable display information. A port should validate its assumptions against the actual responses.

Try a calculation without accessing a device

The following numbers are invented exercise values, not Cisco measurements: eight pixels per row, four bytes per pixel and a 40-byte stride. The example writes nothing to a display device.

Save it as framebuffer-offset.py in an exercise directory:

width = 8
height = 6
bytes_per_pixel = 4
stride = 40
x, y = 3, 2

assert 0 <= x < width and 0 <= y < height
assert stride >= width * bytes_per_pixel
offset = y * stride + x * bytes_per_pixel
print(offset)

Run it:

python3 framebuffer-offset.py

The result is 92. Without padding, the address for this position would differ. Change only x or y and compare. This explains addressing in a simple packed layout; it does not implement colour conversion, device queries or safe memory mapping.

Inspect your Linux display without writing to it

On your own Linux machine, first check for registered fbdev devices:

cat /proc/fb

If fb0 and its sysfs files exist, inspect additional information:

cat /sys/class/graphics/fb0/virtual_size
cat /sys/class/graphics/fb0/bits_per_pixel

Missing files are expected on some systems. These values alone are not enough to implement a correct renderer. Do not pipe dd, cat or an unfamiliar test program into /dev/fb… for this exercise: that changes the active display before you understand its layout.

Physical keys need compatible event decoding

The Cisco project documents local 16-byte events for its 32-bit environment. They contain timestamp fields, type, key code and value. Measured values distinguish pressing and releasing; synchronization events are present too.

That structure size does not automatically apply to an arbitrary 64-bit desktop. A port must match the target ABI and read complete events. The measured Raven key map documents the phone-specific case.

Navigation and OK control movement and menus in the final game; * fires and # uses or opens doors. The red handset exits cleanly. Returning to the Cisco interface is part of the practical result, rather than merely another key mapping.

Audio and persistent startup are separate tasks

Drawing pixels does not produce sound. The final port uses a phone-local service/relay path to Cisco's media functionality. The documentation also explains the loopback handling needed for sound after a cable-free boot.

The persistent installation lives under /mnt/flash2/doom-v666. Its Applications entry uses http://127.0.0.1:8095/launch. A local HTTP endpoint does not mean an external web server computes the game. Distinguish the runtime from its launch mechanism.

Installation, rollback and package verification belong to the project instructions. Device and firmware version matter; this framebuffer guide is not a general installation procedure for other phones.

Project and references

Sources checked on 8 October 2026. If the path from display and input to a whole operating system interests you, continue with my BoringOS introduction.