コンテンツにスキップ

Markdown版はこちら

グレースケール印字フルワイド化・高速化 Hack案(カーネル非改造)

0x67(16階調グレースケール)印字モードには「印字幅が240pxに制限される」「階調段数が16固定」という制約があるが、これをカーネル改造・署名鍵の入手・カスタムビルドなしに、ユーザーランドからGPIO/SPIを直接叩いて自前実装することで回避できる可能性がある、という改造案をまとめる。

実機検証済み: この端末では§4.2の前提が成立しません

2026-09-15、実機(spi7.0, カーネル Linux 4.9.193)で検証した結果、driver_overrideファイル自体が 存在せず、§4.2の「spidev強制bind」は不可能と判明しました。加えてunbind/bindを短時間に 繰り返すとGPIOが-EBUSYのまま解放されず/dev/prn-devが消失し、再起動でしか復旧できない 事象も発生しています。詳細と代替案(DTBOオーバーレイでの多重compatible化とそのリスク)は kernel_analysis.md §7 を参照してください。本ページの§1〜§4は 「当初の設計メモ」として、§4.4のGPIOシーケンス/タイミング定数(実装コードにも反映済み)は 参考価値がありますが、§4.1〜4.3の手順そのものはこのままでは実行できません。


1. 背景 — 何が制約になっているか

カーネル内蔵の uyu_printer ドライバ(/dev/prn-dev)の ioctl(0x67) 経路(グレースケール印字)は、以下がバイナリにハードコードされている:

制約 内容
印字幅 240px固定(ヘッド物理幅384pxのうち中央240pxのみ。左右72pxずつ常時消灯)
階調段数 16段(15パス)固定(呼び出し元がarg1=0x10を決め打ちで渡している)
ペイロード長 ioctlハンドラがmemcpyのコピー量を0x78(120バイト)に固定している

これらは uyu_printer.c 側(カーネル内、r2ghidraで逆コンパイル済み)の複数箇所に散らばった定数で、1関数だけパッチしても直らない。詳細な解析結果は逆コンパイル調査ブランチ(kernel_analysis.md / uyu_printer_decompiled.c、別ブランチで作業中・未マージ)を参照。

素直に直すには「カーネルソースを入手してビルドし直す」か「同等機能を持つカーネルモジュールを自作する」必要があるが、いずれも以下のハードルがある:

  • OEM(MeiG)固有のカーネルソースは現時点で未入手(GPL開示請求中)
  • カーネルモジュール署名(CONFIG_MODULE_SIG_FORCE=y)が有効。デフォルト鍵("Build time autogenerated kernel key")のままではあるが、秘密鍵の現物は未入手
  • CONFIG_KPROBES が無効化されており、実行時の関数フックも不可

2. 発想の転換 — カーネルドライバを経由しない

サーマルヘッドの制御に使われているのは、突き詰めると GPIO 8本 + SPI 1本 だけである(詳細はプリンター制御仕様・逆コンパイル解析を参照)。

信号 役割
PRN_STB ストローブ(発熱パルス)。SPI転送直後にHigh→ウェイト→Lowで1パス分の加熱
PRN_MT_A / PRN_MT_B / PRN_MT_A_0 / PRN_MT_B_0 ステッピングモーター4相(紙送り)
Heat_En / Print_En 発熱・印字系の元電源スイッチ
P_Paper 用紙検知
SPI(384bit/48バイト) 1ライン分のドットパターン転送

これらは全て Linuxの汎用サブシステム(GPIOLIB, SPIコア)で標準的に制御できる種類のI/Oであり、uyu_printerという専用ドライバでなければ制御できないわけではない。既存ドライバがこれらのGPIO/SPIをgpio_request/spi_setupで専有しているのが唯一の障壁であり、そこを解放してやればユーザーランドから直接叩ける。


3. 前提条件(確認済み)

この端末で本Hackが成立する根拠となる設定値は以下の通り確認済み。

3.1 カーネルコンフィグ(kernel-conf.txt)

CONFIG_SPI=y
CONFIG_SPI_QUP=y
CONFIG_SPI_SPIDEV=y      # 汎用SPIユーザーランドドライバがビルドイン済み
CONFIG_GPIOLIB=y
CONFIG_GPIO_SYSFS=y      # sysfs経由のユーザーランドGPIO制御が有効

spidev・GPIO sysfsインターフェースは追加のカーネルビルド無しで既に使える状態にある。

3.2 ソフトウェア的な制約の緩さ

端末のセキュリティ機能の調査結果より:

  • SELinux: Permissive(ポリシー違反はログのみ、ブロックされない)
  • dm-verity: Disabled
  • ブートローダー: アンロック済み(verifiedbootstate: orange)
  • fastboot getvar all: unlocked:yes / secure:no(Qualcomm Secure Bootも無効)
  • root: suバイナリ経由で確保可能(プリンター制御仕様参照)

この組み合わせにより、rootさえ取れれば sysfs 操作は無制限に行える(SELinuxに止められない)。


4. 手順

すべて実機上のシェル操作のみで完結し、リブート・カーネル再ビルド・DTB書き換えは不要。

4.1 既存ドライバから GPIO / SPI の専有を解放

# uyu_prn デバイスを platform driver から unbind
# (デバイス名・ドライバ名は /sys/bus/platform/drivers/ 配下で要確認)
echo <uyu-prnのデバイス名> > /sys/bus/platform/drivers/<uyu_prnドライバ名>/unbind

これで uyu_prn_remove() が呼ばれ、GPIO 8本・SPIデバイス・/dev/prn-dev (misc device) が全て解放される。この間、既存の1bit印字パス(0x66)も使えなくなる点に注意。

4.2 SPIデバイスを spidev に driver_override で強制バインド

# SPIデバイスノードを特定(例: spiX.Y)
ls /sys/bus/spi/devices/

# devicetreeのcompatible文字列("qcom,prn-dev")を無視して
# driver_override で spidev を強制指定
echo spidev > /sys/bus/spi/devices/spiX.Y/driver_override
echo spiX.Y > /sys/bus/spi/drivers/spidev/bind

成功すると /dev/spidevX.Y が生成され、ioctl(SPI_IOC_MESSAGE) で生のSPI転送がユーザーランドから可能になる。driver_override はLinux標準の仕組みで、devicetreeのcompatible一致チェックをバイパスして任意のドライバに強制bindできる。

4.3 GPIO 8本を sysfs 経由で制御

echo <N> > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio<N>/direction
echo 1   > /sys/class/gpio/gpio<N>/value

対象GPIO番号は devicetree(uyu-prnノード)から確認済み。

4.4 印字プロトコルをユーザーランドで再実装

逆コンパイル解析で判明している LineColorRank_Print のロジック(疑似コード、フル幅・可変段数版に拡張):

// N = 任意の階調段数(元は16固定)、WIDTH = 384(元は240固定)
for (pass = 1; pass < N; pass++) {
    // WIDTHドットぶん、「そのドットの階調値 >= pass」でビットを立てる
    for (byte_i = 0; byte_i < WIDTH/8; byte_i++) {
        packed[byte_i] = pack_8_bits(dot => (pass <= gray_data[dot]));
    }
    spi_transfer(fd_spidev, packed, WIDTH/8);   // ioctl(SPI_IOC_MESSAGE)

    gpio_write(PRN_STB, 1);
    usleep(strobe_us);                          // 元はTK/TB(144/6us)相当
    gpio_write(PRN_STB, 0);
}
motor_forward_one_step();  // GPIO 4相を自前でシーケンス制御

段数Nとストローブ幅strobe_usを自由なパラメータにできるので、階調数を落として高速化する運用も、そのままここに組み込める。


5. リスク・未検証事項

5.1 タイミング精度

カーネル内の __udelay() は割込み無効のビジーウェイトで確実に指定時間止まるが、ユーザーランドの usleep() はスケジューラの影響を受け、数十µs単位でブレる可能性がある。ストローブ幅(144〜150µs相当)に対してどの程度のジッタが許容されるかは実機で試すまで不明。

対策候補: - プロセスを SCHED_FIFO のリアルタイム優先度にする(chrt -f 99 ...) - mlockall() でページフォルト由来の遅延を排除 - それでも精度が足りなければ、ストローブのHigh/Low自体をSPI転送に相乗りさせる(SPIクロックは正確なので、ダミーバイトの転送時間でウェイトを代用する)等の工夫が要る

5.2 既存機能との排他

uyu_prnをunbindしている間は /dev/prn-dev 経由の通常の1bit印字(0x66)・用紙検知(0x77)が使えなくなる。運用上は「グレースケール印字ジョブの間だけunbind→終わったらbindし直して元のドライバに戻す」という排他制御が必要。

# 元に戻す
echo <uyu-prnのデバイス名> > /sys/bus/platform/drivers/<uyu_prnドライバ名>/bind

5.3 モーター位相のずれ

Motor_Cycle(ステッピングモーターの半歩位相トグル)は元ドライバ内のstatic変数で管理されている。unbind/自前実装/再bindを繰り返すと、機械的な位相が元ドライバの想定とずれる可能性がある。実害は「多少のガタつき」程度と思われるが未検証。

5.4 未確認事項 → 実機確認済み(結果は§6参照)

  • ~~uyu-prnデバイスの正確な platform driver 名・sysfsパス~~ → 前提が誤り。prn-devは platform driverではなくSPIドライバとしてspi7.0に直接bindされている (readlink /sys/bus/spi/devices/spi7.0/driver → .../bus/spi/drivers/prn-dev)。
  • ~~SPIデバイスのバス番号・チップセレクト~~ → spi7.0(/dev/spidev7.0)と確認済み。
  • ~~spidevのof_match_tableに許可されたcompatible文字列がdriver_override経由でも通るか~~ → そもそもdriver_overrideファイルがこの端末のカーネルに存在しないため検証不能。詳細は§6。

6. 実機検証結果(2026-09-15)と代替案

6.1 §4.2は成立しない

/sys/bus/spi/devices/spi7.0/driver_override が存在しない(SPIバスのdriver_override対応は mainlineで4.9より新しいカーネルの機能で、この端末(Linux 4.9.193)にはバックポートされていない)。 driver_override無しでecho spi7.0 > /sys/bus/spi/drivers/spidev/bindを直接試しても、 compatible = "qcom,prn-dev"がspidevのof_device_id/spi_device_idテーブル (rohm,dh2228fv等の標準プレースホルダーのみ)にマッチせず、probe()が呼ばれないまま失敗する。

6.2 unbind/bindの反復自体が危険

unbind→(spidev bind失敗)→再度unbind→prn-devへbind、を短時間に繰り返したところ、 2回目の再bindでgpio_initがHeat_En/Print_Enのgpio_requestで-EBUSY(-16)を返し、 /dev/prn-devが消失。sysfs操作だけでは復旧できず、実機の再起動が必要だった。 PrinterServerAppがステータスポーリング等で/dev/prn-devを掴んだままの状態でunbindすると GPIO解放処理が正しく完了しない可能性がある(未検証の推測)。テストする際は必ず am force-stop com.example.printerserverしてから行うこと。

6.3 代替案: DTBOオーバーレイでの多重compatible化(未検証・要実機テスト)

boot.imgのベースDTを触らず、dtbo.imgに新しいfragmentを追加して target-path = "/soc/spi@7af7000/uyu-prn";で直接ターゲット指定し、compatibleに 元の文字列を残したまま追加する:

compatible = "qcom,prn-dev", "rohm,dh2228fv";

こうすればunbind後にspidevへのbindが(標準のcompatibleマッチで)成功するはずだが、 この変更は「起動時に毎回評価される」静的なものなので、通常起動時にprn-devとspidevの どちらが自動bindを勝ち取るかが問題になる。両ドライバともdevice_initcall(レベル6)で 登録されるため、勝敗はdrivers/uyu/とdrivers/spi/spidev.cのカーネルリンク順次第で決まり、 机上では確定できない。もしspidevが勝つと、通常の1bit印字(0x66)が起動するたびに 使えなくなる。実機でdtboを焼いて再起動し、readlink /sys/bus/spi/devices/spi7.0/driverで どちらが勝つか確認するまでは採用の可否が判断できない。

DTBO抽出・書き換えの一般的な手順(パーティション位置の特定、ddによるフル解凍なしの 部分抽出など)は パーティションテーブル解析 を参照。


7. 関連ページ