Vinod Halaharvi

Independent consultant. I build the operating system layer for embedded hardware — custom Linux images, Zephyr firmware, board bring-up, and the industrial protocols that run on top.


A device, running

A Raspberry Pi 5 booting a Buildroot image built from a one-line description, then answering Modbus, OPC UA, MQTT, HTTP, CAN, DNP3, BACnet/IP and IEC 61850 off a single shared tag table:

Terminal recording: a Raspberry Pi 5 boots a Buildroot image in a few seconds, then responds to Modbus, OPC UA, MQTT, HTTP, CAN, DNP3, BACnet and IEC 61850 requests, and a port scan shows only the nine ports those services need
One tag table, eight protocols, nine ports — and nothing else listening.

The last part is the one integrators care about. A general-purpose distro with the same services installed opens dozens of ports nobody asked for. This image opens the nine it needs, because nothing else was ever put in it.

What I do

Custom Linux images

Buildroot images that boot in seconds and contain only what you chose. Reproducible, auditable, and small enough that two copies fit on one card.

Board bring-up

New silicon, a vendor BSP that nearly works, a device tree that does not match the hardware. Serial console, a logic analyser, and the patience to find the one line that matters.

Industrial protocols

Modbus, OPC UA, DNP3, BACnet/IP, IEC 61850, CAN and MQTT — gateways, simulators and device-side servers that an integrator's existing tools can talk to.

Field updates

A/B partitions with automatic rollback, signed images, and a device that can tell you what it is running. The difference between a product and a truck roll.

Zephyr firmware

RTOS work where Linux is too large or the timing too tight, including mixed designs where a Cortex-A runs Linux and a Cortex-M runs the control loop.

License compliance & SBOM

A machine-readable bill of materials, every license in the image, the source archives the GPL obliges you to hand over, and an inventory of what is actually on the device — not what the build intended.

Making it repeatable

Build systems, CI that produces the same image twice, and tooling that catches a broken configuration before it reaches a board.

Hardware I work with

I keep a bench of real boards and bring each one up properly rather than trusting a vendor image:

Across those: arm64 and armv7, eMMC and SD and NVMe, device tree overlays, silicon stepping differences, PCIe, CAN and RS485, camera and display pipelines.

How I work

Most embedded problems are not hard, they are invisible. A symbol nobody set, a device tree for the wrong silicon revision, a driver built as a module that nothing loads. The board boots, something is missing, and nothing says what.

So I build the instrumentation first: a serial console on the firmware UART, a way to ask a running board what it actually brought up, and checks that compare what a build claimed against what the hardware needs. Then the debugging is reading rather than guessing.

Everything I deliver comes with the reasoning written down — why a symbol is set, which failure taught it, and what to check if it breaks again.

Knowing what is on the device

Most teams shipping embedded Linux cannot produce a complete list of what is on their device. The image was built from a vendor BSP that was forked from another BSP, something was copied in during manufacturing, and the honest answer to "what version of OpenSSL is on that board" is that nobody knows.

That used to be an embarrassment. Since 11 September 2026 it is a reporting obligation: the EU Cyber Resilience Act now requires manufacturers to report actively exploited vulnerabilities in their products, and from 11 December 2027 the full obligations apply — including identifying and documenting the components in a product, with a software bill of materials in a commonly used, machine-readable format. You cannot report on a vulnerability in a component you cannot prove you ship.

Three artefacts come out of the work, and they answer different questions:

The license manifest — what you are allowed to ship

Every package in the image with its version, its license, the text of that license, and — for the licenses that demand it — the source archive you are obliged to hand to anyone who asks. Buildroot will generate this (make legal-info) for an image that was built properly; the work is in getting there, because a package with an unclear license or a vendor blob with no license at all is exactly what the manifest exposes, and it is much cheaper to find that before a product ships than during a customer's legal review.

The SBOM — what you are shipping

The same inventory in CycloneDX or SPDX, which is what a vulnerability scanner, a procurement portal or a market surveillance authority will actually accept. Generated from the build (make show-info | utils/generate-cyclonedx) rather than written by hand, so it is correct by construction and regenerates with every image instead of going stale the week after it is signed off.

The device triage — what is really there

The first two describe what the build claimed. This one describes the board in front of you, and the two are not always the same: every binary on the root filesystem and every shared library it needs resolved against what is actually installed, every kernel module present and which of them loaded, every device node that bound to a driver and every one that did not.

That last point is where the interesting failures live. A driver built as a module that nothing ever loads looks identical to a working system in the manifest, and identical to broken hardware on the bench. I build the tooling that tells the two apart, and publish it.

On the legal side I can produce the evidence and tell you what the obligations look like in practice. I am not a lawyer, and for the question of whether a particular license obligation has been discharged you want one.

What you get

The same shape every time, whatever the board. The point of the last three directories is that someone who is not me can rebuild the image, prove what is in it, and fix it after I am gone:

delivery/
├── image/
│   ├── product-1.4.0.img         the image that ships
│   ├── product-1.4.0.img.sha256  what it must hash to
│   └── update-1.4.0.raucb        signed A/B bundle
├── compliance/
│   ├── manifest.csv              package, version, license
│   ├── licenses/                 full text of every one
│   ├── sources/                  the archives GPL owes
│   └── sbom.cdx.json             CycloneDX, machine-readable
├── triage/
│   ├── libraries.txt             each binary, each .so
│   ├── modules.txt               built, present, loaded
│   └── devices.txt               node to driver, or not
├── config/
│   ├── defconfig                 rebuilds it from source
│   ├── kernel.config             every symbol, and why
│   └── device-tree/              base .dts and overlays
└── docs/
    ├── BRINGUP.md                what broke, what fixed it
    ├── REBUILD.md                checkout to identical image
    └── RUNBOOK.md                flash, recover, roll back

No part of that is a deliverable I add at the end to pad an invoice. Each one is a by-product of building the image correctly in the first place — which is the whole argument for doing it this way.

Open source

I write about this work and publish the tooling at github.com/vinodhalaharvi, including a tool that checks an embedded Linux configuration against the board it is meant to run on, before the image is built.

Contact

Available for short engagements and longer projects. Happy to start with a conversation about whether I am the right person for the problem.

vinod.halaharvi@gmail.com LinkedIn GitHub

More of what I work on at vinodhalaharvi.com