Skip to content

Add automatic firmware loading for HP LaserJet 1018 - #2

Open
greenflute wants to merge 1 commit into
faradayfury:mainfrom
greenflute:feat/macos-lj1018-firmware-autoload
Open

Add automatic firmware loading for HP LaserJet 1018#2
greenflute wants to merge 1 commit into
faradayfury:mainfrom
greenflute:feat/macos-lj1018-firmware-autoload

Conversation

@greenflute

Copy link
Copy Markdown

Summary

Add an optional native macOS firmware autoloader for the HP LaserJet 1018.

The LaserJet 1018 stores its operating firmware in volatile memory. After every printer power cycle or USB re-enumeration, the firmware must be uploaded again before normal print jobs can be processed.

Without firmware, CUPS may successfully submit and complete a job while the printer remains unresponsive. The current workaround requires manually sending sihp1018.dl after every power cycle.

This change adds an Apple Silicon-native, event-driven firmware loader that automates that process.

Reported Symptom

On macOS Tahoe running on Apple Silicon:

  1. The ARM64 rastertozjs filter and CUPS queue work correctly.
  2. Printing works after manually uploading sihp1018.dl.
  3. After the LaserJet 1018 is powered off and on again, print jobs are sent and disappear from the queue, but the printer does not respond.
  4. Manually uploading the firmware restores printing.

The printer requires firmware after every power cycle because it does not store the firmware persistently.

Implementation

This PR adds foo2zjs-firmware-loader, a native C helper using macOS system frameworks only.

The helper:

  • Matches the HP LaserJet 1018 by USB VID:PID 03f0:4117.
  • Also validates the USB product string.
  • Uses IOKit notifications for USB device arrival and removal.
  • Handles a printer that is already connected when the daemon starts.
  • Reads the IEEE-1284 Device ID through the system CUPS USB backend.
  • Treats the presence of FWVER: as the firmware-loaded indicator.
  • Does not upload when firmware is already loaded.
  • Refuses to upload when the firmware state or DeviceURI is unknown.
  • Sends firmware directly through /usr/libexec/cups/backend/usb.
  • Does not create a fake CUPS print job.
  • Does not depend on a hard-coded CUPS queue name.
  • Verifies the device state after upload.
  • Uses finite verification retries and a single-instance lock.
  • Logs device arrival, removal, firmware state, upload, and verification.
  • Does not poll USB devices.

The first supported device is intentionally limited to:

  • HP LaserJet 1018
  • USB VID:PID 03f0:4117
  • Firmware file sihp1018.dl

Other firmware-dependent printers are not enabled until their identifiers and firmware-loaded behavior can be verified on real hardware.

State Detection

The loader uses the IEEE-1284 Device ID exposed by the macOS CUPS USB backend.

The state is interpreted as follows:

  • Device ID contains FWVER:: firmware is loaded.
  • A matching LaserJet 1018 Device ID without FWVER:: firmware is not loaded.
  • Device ID or DeviceURI cannot be determined: state is unknown and upload is refused.

This avoids treating USB presence alone as proof that firmware is loaded.

The implemented state flow is:

DEVICE_APPEARED
    |
    v
READ_DEVICE_STATE
    |
    v
firmware loaded?
    |-- yes --> READY
    |
    `-- no --> UPLOAD
                  |
                  v
                VERIFY
                  |-- success --> READY
                  `-- failure --> LOG ERROR

Command-Line Modes

Inspect state without making changes:

./foo2zjs-firmware-loader --status

Perform one check and upload if necessary:

sudo ./foo2zjs-firmware-loader --once

Run the foreground IOKit event monitor:

sudo ./foo2zjs-firmware-loader --daemon

Example status before upload:

device: HP LaserJet 1018
vid: 03f0
pid: 4117
connected: yes
product: HP LaserJet 1018
serial: KP3DDRP
firmware: not loaded
firmware file: /usr/local/share/foo2zjs/firmware/sihp1018.dl
device URI: usb://Hewlett-Packard/HP%20LaserJet%201018?serial=KP3DDRP
daemon: running

Example status after upload:

device: HP LaserJet 1018
vid: 03f0
pid: 4117
connected: yes
product: HP LaserJet 1018
serial: KP3DDRP
firmware: loaded
firmware file: /usr/local/share/foo2zjs/firmware/sihp1018.dl
device URI: usb://Hewlett-Packard/HP%20LaserJet%201018?serial=KP3DDRP
daemon: running

LaunchDaemon

The system LaunchDaemon is installed as:

/Library/LaunchDaemons/org.foo2zjs.firmware-loader.plist

The helper is installed as:

/usr/local/libexec/foo2zjs/foo2zjs-firmware-loader

The daemon:

  • Runs without requiring a user login.
  • Is supervised by launchd.
  • Uses IOKit arrival events rather than periodic polling.
  • Handles a printer that is already connected when it starts.
  • Handles printer power cycles and USB reconnects.
  • Skips upload when firmware is already loaded.
  • Runs as a foreground process under launchd.

Logs are written to:

/var/log/foo2zjs-firmware-loader.log

The service can be inspected with:

sudo launchctl print system/org.foo2zjs.firmware-loader
sudo tail -f /var/log/foo2zjs-firmware-loader.log

Installation

The loader remains an explicit opt-in because the main installer currently installs drivers for 89 printer models, while automatic firmware loading has only been verified for the LaserJet 1018.

Install it independently:

sudo ./install-firmware-loader.sh

Or select it as part of the main installation:

sudo ./install.sh --firmware-autoload

The default command remains unchanged:

sudo ./install.sh

It does not install a system daemon unless --firmware-autoload is specified.

The firmware-loader installer:

  • Builds the native helper.
  • Installs it with root:wheel ownership.
  • Applies an ad-hoc code signature.
  • Installs the LaunchDaemon plist.
  • Uses current launchctl bootstrap and kickstart commands.
  • Installs sihp1018.dl only when the firmware file is missing.
  • Does not run the main driver installer.
  • Does not install or replace CUPS filters.
  • Does not install or replace PPDs.
  • Does not create, remove, or modify CUPS queues.
  • Does not overwrite an existing sihp1018.dl.

Uninstallation

Remove the helper and LaunchDaemon with:

sudo ./uninstall-firmware-loader.sh

Uninstallation removes only:

/Library/LaunchDaemons/org.foo2zjs.firmware-loader.plist
/usr/local/libexec/foo2zjs/foo2zjs-firmware-loader

It leaves the following unchanged:

  • Firmware files
  • CUPS queues
  • CUPS filters
  • Installed PPDs
  • Other HP printer drivers

Hardware Testing

Tested with:

  • macOS Tahoe 26.x
  • Apple Silicon M5 Max
  • No Rosetta
  • HP LaserJet 1018
  • USB VID:PID 03f0:4117
  • Serial number KP3DDRP
  • DeviceURI usb://Hewlett-Packard/HP%20LaserJet%201018?serial=KP3DDRP

Verified behavior:

  1. --status detects the connected LaserJet 1018.
  2. Firmware state is reported as not loaded before upload.
  3. --once sends the complete 129143-byte firmware file.
  4. Post-upload verification detects FWVER: on the first attempt.
  5. Firmware state is subsequently reported as loaded.
  6. Starting the daemon with loaded firmware skips upload.
  7. Powering the printer off produces a removal event.
  8. Powering it back on produces a new arrival event.
  9. The daemon detects not loaded, uploads firmware once, and verifies it.
  10. Restarting the daemon with loaded firmware does not upload again.
  11. A normal CUPS print job does not trigger another firmware upload.
  12. Printing works after automatic firmware loading.
  13. The existing installed rastertozjs filter and queue PPD remain unchanged.

The automatic power-cycle sequence produced:

foo2zjs-fw: device removed: registry-id=4295095237
foo2zjs-fw: USB device matched: model=HP LaserJet 1018 VID=03f0 PID=4117 serial=KP3DDRP
foo2zjs-fw: current firmware state: not loaded
foo2zjs-fw: upload started via /usr/libexec/cups/backend/usb
foo2zjs-fw: upload transport completed; verifying device state
foo2zjs-fw: verification attempt 1/6: firmware=loaded
foo2zjs-fw: verification succeeded; firmware upload complete

Installed driver hashes before and after installing and testing the autoloader were identical:

d90a50dfd68de468fc954fdc1edb7d1dc9fdab2a27ef7b9e94b0eea24be47ab4  /usr/libexec/cups/filter/rastertozjs
4303ca02e4b517aa50a554b9fc1a9cada52a28db8c0fedf2707318fad223dc36  /etc/cups/ppd/HP_LaserJet_1018.ppd

This confirms that the firmware-loader installation did not overwrite the working driver or queue PPD.

Build Verification

The helper builds as a native Apple Silicon executable:

foo2zjs-firmware-loader: Mach-O 64-bit executable arm64

Dynamic dependencies:

/System/Library/Frameworks/IOKit.framework
/System/Library/Frameworks/CoreFoundation.framework
/usr/lib/libSystem.B.dylib

The helper has no dependency on:

  • Rosetta
  • x86_64 binaries
  • Intel Homebrew libraries
  • Third-party runtime frameworks
  • Python
  • Ruby
  • Node.js

Validation performed:

git diff --check
make clean
make
file foo2zjs-firmware-loader
otool -L foo2zjs-firmware-loader
plutil -lint macos/org.foo2zjs.firmware-loader.plist
sh -n install.sh install-firmware-loader.sh uninstall-firmware-loader.sh

All checks passed.

Why Not the Original macOS Hotplug Implementation?

The original foo2zjs macOS helper contains useful concepts, including:

  • IOKit USB matching
  • Device arrival and removal notifications
  • IEEE-1284 Device ID checks
  • FWVER detection
  • Direct firmware transfer

However, its deployment model relies on obsolete macOS mechanisms and lifecycle assumptions, including:

  • /etc/rc.local
  • One-shot launchd startup behavior
  • Backgrounding from the helper
  • killall
  • Temporary-file logging
  • Older IOKit APIs

This implementation retains the relevant USB event and firmware-state concepts while using:

  • A foreground daemon supervised by launchd
  • A system LaunchDaemon
  • Current launchctl bootstrap and bootout commands
  • kIOMainPortDefault
  • IOKit first-match notifications
  • Explicit firmware-state verification
  • Finite retry behavior

Safety and Scope

This change is intentionally separate from raster conversion.

It does not modify:

  • rastertozjs page processing
  • ZjStream page headers
  • JBIG encoding
  • Existing printer queues
  • Existing installed PPDs
  • Existing HP printer driver files

The first release enables only the hardware configuration that was tested.

Known Limitations

  • Only the HP LaserJet 1018 is currently enabled.
  • Sleep/wake behavior still needs dedicated long-duration testing.
  • Disconnect during an active firmware transfer needs additional hardware fault-injection testing.
  • Missing-firmware recovery is implemented but still needs a complete installed LaunchDaemon hardware test.
  • The main installer is not yet model-aware.
  • Other firmware-dependent foo2zjs printers require verified VID/PID and firmware-state data before they can be enabled.

Follow-up

The current installation is deliberately opt-in because install.sh installs all supported drivers and does not know which printer model the user intends to configure.

A follow-up should make firmware autoload part of the default installation for a selected firmware-dependent model once the installer becomes model-aware.

The intended future behavior is:

sudo ./install.sh --model HP-LaserJet_1018

That model-specific installation should install:

  1. The required ARM64 CUPS filter.
  2. The matching PPD.
  3. sihp1018.dl.
  4. The firmware autoloader and LaunchDaemon by default.

It should also provide an explicit opt-out, for example:

sudo ./install.sh \
    --model HP-LaserJet_1018 \
    --no-firmware-autoload

The future implementation must not decide solely from whether the printer is connected during installation, because the printer may be powered off or temporarily disconnected.

As support is added for other firmware-dependent models, their verified VID/PID values, firmware filenames, and loaded-state behavior should be added to the device table.

Autoload should become the normal default only for models with verified hardware behavior.

Related Driver Fix

This firmware-autoload change is intentionally independent from the separate HP LaserJet 1018 ARM64 raster/PPD resolution fix.

The autoloader does not replace or modify the raster filter or queue PPD. Hardware printing after automatic firmware upload was tested using the separately verified LaserJet 1018 ARM64 raster driver changes.

Both changes can be reviewed and merged independently, although the complete end-user installation experience requires both changes to be present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant