1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ubuntu 24.04 (Kernel 6.8) の録画サーバーで発生するブロックノイズのドライバ修正 [ PX-S1UD / PX-Q1UD ]

1
Last updated at Posted at 2026-08-23

はじめに

Ubuntu 22.04でPX-1SUDを使い、Mirakurun + EPGStation + KonomiTVを用いた録画サーバーを運用していましたが、LTSのサポート期限を見据えてUbuntu 24.04へアップグレードしました。

しかし、アップグレード後から録画データに大量のブロックノイズが乗るようになってしまいました。

調べてみたところ、Kernel 6.8のログ機能の変更により、ログの書き込みが輻輳してパケット欠落が発生していることが原因のようです。

環境

  • OS: Ubuntu 24.04 LTS (Kernel 6.8〜)
  • チューナー: Siano系 USBチューナー (PX-S1UD / PX-Q1UD など)
  • ソフトウェア: Mirakurun, EPGStation, KonomiTV

原因について

ネットの情報によると、Kernel 6.6以降の smsdvb ドライバに含まれるバグが影響しているとのことです。

Claudeに相談してコードを解析してもらったところ、smsdvb-debugfs.c 内で使われている sysfs_emit_at() がページ境界に揃っていないバッファに対してWARNを吐き、それが割り込みコンテキストから呼ばれることでUSBからのTS受信が一時停止し、ドロップ(ブロックノイズ)に繋がっているとのことでした。

解決策(修正スクリプト)

カーネル全体を再ビルドするのは手間なので、smsdvb ドライバの該当箇所をパッチで修正し、DKMSで対象のモジュールだけを再ビルドして導入するスクリプトをClaudeに作成してもらいました。

私の環境(Ubuntu 24.04)向けに作ってもらったものですが、同様の現象でお困りの方の参考になれば幸いです。

💡 補足
ご自身の環境でうまく動かない場合は、この記事のスクリプトをChatGPTやClaudeなどのAIに読み込ませて「自分の環境用に調整して」とお願いするとスムーズかもしれません。

スクリプト本体 (smsdvb-fix.sh)

任意の名前(例: smsdvb-fix.sh)で以下のスクリプトを保存し、実行権限を付与して使用します。

#!/usr/bin/env bash
#
# smsdvb-fix.sh
#
# Linux 6.6 以降の smsdvb ドライバ (Siano 系 USB チューナ: PX-S1UD / PX-Q1UD など)
# に含まれる sysfs_emit_at() 誤用バグを修正したモジュールを DKMS で導入する。
#
# バグの内容:
#   smsdvb-debugfs.c は debug_data->stats_data (kzalloc された構造体内の配列) へ
#   統計文字列を書き込むが、そこで使われている sysfs_emit_at() は sysfs の show()
#   専用 API で、バッファがページ境界に揃っていることを要求する。揃っていなければ
#   WARN を吐く (fs/sysfs/file.c)。この関数は URB 完了 = 割り込みコンテキストから
#   呼ばれるため、スタックダンプの出力中は USB からの TS 受信が止まり、パケットが
#   欠落してブロックノイズになる。
#
# 修正内容:
#   sysfs_emit_at() の呼び出しを、意味的に等価で WARN を伴わない自前実装
#   smsdvb_emit_at() に置き換えて smsdvb.ko だけを再ビルドする。
#   カーネル全体のビルドは不要。
#
# ライセンス: MIT
#
set -euo pipefail

readonly PKG_NAME="smsdvb-fix"
readonly PKG_VERSION="1.0"
readonly SRC_DIR="/usr/src/${PKG_NAME}-${PKG_VERSION}"
readonly REPO_BASE="https://raw.githubusercontent.com/torvalds/linux"
readonly SIANO_PATH="drivers/media/common/siano"
readonly MIN_REPLACEMENTS=10

readonly SIANO_FILES=(
    smsdvb-main.c
    smsdvb-debugfs.c
    smsdvb.h
    smscoreapi.h
    sms-cards.h
    smsendian.h
    smsir.h
)

KERNEL_RELEASE="$(uname -r)"
KERNEL_TAG=""
DO_RELOAD=0

# --- ユーティリティ --------------------------------------------------------

log()  { printf '==> %s\n' "$*"; }
warn() { printf 'WARN: %s\n' "$*" >&2; }
die()  { printf 'ERROR: %s\n' "$*" >&2; exit 1; }

usage() {
    cat <<'EOF'
smsdvb-fix.sh — smsdvb ドライバのバグ修正版を DKMS で導入する

使い方:
  sudo ./smsdvb-fix.sh                修正版を導入する
  sudo ./smsdvb-fix.sh --reload       導入後にモジュールを再読み込みする
  sudo ./smsdvb-fix.sh --tag v6.14    取得元カーネルのタグを明示指定する
       ./smsdvb-fix.sh --status       現在の状態を表示する
  sudo ./smsdvb-fix.sh --uninstall    撤去して標準モジュールに戻す

対象: Linux 6.6 以降 + Siano 系 USB チューナ (PX-S1UD / PX-Q1UD など)
必要: dkms / curl / build-essential / linux-headers-$(uname -r)
EOF
}

require_root() {
    if [ "$(id -u)" -ne 0 ]; then
        die "root で実行してください:  sudo $0 $*"
    fi
}

# --- 事前チェック ----------------------------------------------------------

check_prerequisites() {
    local missing=()

    command -v dkms >/dev/null || missing+=("dkms")
    command -v curl >/dev/null || missing+=("curl")
    command -v make >/dev/null || missing+=("build-essential")

    if [ ! -d "/lib/modules/${KERNEL_RELEASE}/build" ]; then
        missing+=("linux-headers-${KERNEL_RELEASE}")
    fi

    if [ "${#missing[@]}" -gt 0 ]; then
        warn "以下が不足しています: ${missing[*]}"
        printf '\n    sudo apt install -y %s\n\n' "${missing[*]}"
        die "上記を導入してから再実行してください"
    fi
}

# 実行中カーネルの major.minor から取得元タグを決める
# 例: 6.8.0-137-generic -> v6.8
detect_kernel_tag() {
    if [ -n "$KERNEL_TAG" ]; then
        log "取得元タグ (指定): ${KERNEL_TAG}"
        return 0
    fi

    KERNEL_TAG="v$(printf '%s' "$KERNEL_RELEASE" | cut -d. -f1,2)"
    log "取得元タグ (自動判定): ${KERNEL_TAG}  [実行中: ${KERNEL_RELEASE}]"

    if ! curl -fsSL -o /dev/null \
        "${REPO_BASE}/${KERNEL_TAG}/${SIANO_PATH}/smsdvb-debugfs.c"; then
        die "タグ ${KERNEL_TAG} のソースを取得できません。--tag で明示してください"
    fi
}

# --- ソース取得とパッチ ----------------------------------------------------

fetch_sources() {
    log "ソースを取得: ${KERNEL_TAG} -> ${SRC_DIR}"

    rm -rf "$SRC_DIR"
    mkdir -p "$SRC_DIR"

    local f
    for f in "${SIANO_FILES[@]}"; do
        if ! curl -fsSL -o "${SRC_DIR}/${f}" \
            "${REPO_BASE}/${KERNEL_TAG}/${SIANO_PATH}/${f}"; then
            die "取得に失敗: ${f}"
        fi
    done
}

# 呼び出し箇所だけを置換する。
#
# 【注意】マクロで sysfs_emit_at を潰してはいけない。
# -include したヘッダで #define すると、後から読まれる <linux/sysfs.h> の
# 「関数宣言」まで展開されて構文エラーになる。
patch_replace_calls() {
    local target="${SRC_DIR}/smsdvb-debugfs.c"
    local count

    sed -i 's/\bsysfs_emit_at(/smsdvb_emit_at(/g' "$target"
    count="$(grep -c 'smsdvb_emit_at(' "$target" || true)"

    if [ "$count" -eq 0 ]; then
        die "${KERNEL_TAG} の smsdvb-debugfs.c に sysfs_emit_at() がありません。
       上流で修正済みか対象外のバージョンです。このパッチは不要です。"
    fi

    if [ "$count" -lt "$MIN_REPLACEMENTS" ]; then
        die "置換数が異常に少ない (${count} 件)。ソース取得に失敗した可能性があります"
    fi

    log "sysfs_emit_at() の呼び出しを置換: ${count} 件"
}

# ヘルパー関数を #include 群の直後に挿入する。
#
# 【注意】ccflags-y += -include ... では動かない。
# カーネルヘッダより前に処理されるため、その時点では PAGE_SIZE すら未定義で
# コンパイルエラーになる。
patch_insert_helper() {
    local target="${SRC_DIR}/smsdvb-debugfs.c"
    local helper line

    helper="$(mktemp)"
    cat > "$helper" <<'EOF'

/*
 * sysfs_emit_at() の代替。
 *
 * debugfs のバッファはページ境界に揃っていないため sysfs_emit_at() は WARN を
 * 吐く。この経路は割り込みコンテキストから呼ばれるため、スタックダンプの出力中は
 * USB からの TS 受信が止まってパケットが落ちる。
 * 意味的に等価で WARN を伴わない実装に差し替える。
 */
static __printf(3, 4)
int smsdvb_emit_at(char *buf, int at, const char *fmt, ...)
{
	va_list args;
	int len;

	if (!buf || at < 0 || at >= PAGE_SIZE)
		return 0;

	va_start(args, fmt);
	len = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
	va_end(args);

	return len;
}
EOF

    line="$(grep -n '^#include' "$target" | tail -1 | cut -d: -f1)"
    if [ -z "$line" ]; then
        rm -f "$helper"
        die "smsdvb-debugfs.c に #include が見つかりません"
    fi

    sed -i "${line}r ${helper}" "$target"
    rm -f "$helper"

    log "ヘルパー関数を ${line} 行目の直後に挿入"
}

# --- ビルド設定 ------------------------------------------------------------

write_build_files() {
    cat > "${SRC_DIR}/Makefile" <<'EOF'
obj-m := smsdvb.o
smsdvb-y := smsdvb-main.o smsdvb-debugfs.o

KDIR ?= /lib/modules/$(shell uname -r)/build

all:
	$(MAKE) -C $(KDIR) M=$(CURDIR) modules

clean:
	$(MAKE) -C $(KDIR) M=$(CURDIR) clean
EOF

    cat > "${SRC_DIR}/dkms.conf" <<EOF
PACKAGE_NAME="${PKG_NAME}"
PACKAGE_VERSION="${PKG_VERSION}"
BUILT_MODULE_NAME[0]="smsdvb"
DEST_MODULE_LOCATION[0]="/updates"
AUTOINSTALL="yes"
EOF
}

# --- DKMS 操作 -------------------------------------------------------------

dkms_purge() {
    dkms remove -m "$PKG_NAME" -v "$PKG_VERSION" --all >/dev/null 2>&1 || true
}

dkms_build_install() {
    log "DKMS に登録してビルド"
    dkms add     -m "$PKG_NAME" -v "$PKG_VERSION"
    dkms build   -m "$PKG_NAME" -v "$PKG_VERSION"
    dkms install -m "$PKG_NAME" -v "$PKG_VERSION" --force
}

reload_module() {
    log "smsdvb を再読み込み"

    if ! modprobe -r smsdvb 2>/dev/null; then
        warn "smsdvb が使用中のため外せません。"
        warn "録画・視聴プロセスを停止してから再実行するか、再起動してください。"
        return 0
    fi

    modprobe smsdvb
}

# --- サブコマンド ----------------------------------------------------------

do_status() {
    local filename devices warns

    filename="$(modinfo -F filename smsdvb 2>/dev/null || true)"
    devices="$(ls /dev/dvb/ 2>/dev/null | tr '\n' ' ' || true)"
    warns="$(dmesg 2>/dev/null | grep -c 'invalid sysfs_emit_at' || true)"

    printf 'カーネル      : %s\n' "$KERNEL_RELEASE"
    printf 'smsdvb の実体 : %s\n' "${filename:-不明}"
    printf 'DVB デバイス  : %s\n' "${devices:-なし}"
    printf 'WARN 発生回数 : %s\n' "${warns:-取得不可 (sudo が必要)}"
    printf '\n'

    dkms status -m "$PKG_NAME" || true
}

do_uninstall() {
    require_root --uninstall

    log "DKMS から撤去して標準モジュールに戻します"
    dkms_purge
    rm -rf "$SRC_DIR"
    depmod -a

    log "完了。反映するには再起動するか smsdvb を再読み込みしてください"
}

do_install() {
    require_root

    check_prerequisites
    detect_kernel_tag

    dkms_purge
    fetch_sources
    patch_replace_calls
    patch_insert_helper
    write_build_files
    dkms_build_install

    if [ "$DO_RELOAD" -eq 1 ]; then
        reload_module
    fi

    printf '\n'
    log "導入完了"
    printf '    %s\n\n' "$(modinfo -F filename smsdvb)"

    cat <<'EOF'
確認手順:
  1. 上のパスが updates/dkms 配下になっていること
  2. 数分視聴してから、以下のカウントが増えないこと

       sudo dmesg | grep -c "invalid sysfs_emit_at"
EOF
}

# --- 引数処理 --------------------------------------------------------------

main() {
    local action="install"

    while [ $# -gt 0 ]; do
        case "$1" in
            --tag)
                if [ $# -lt 2 ]; then
                    die "--tag にはタグ名が必要です (例: --tag v6.14)"
                fi
                KERNEL_TAG="$2"
                shift 2
                ;;
            --reload)    DO_RELOAD=1;        shift ;;
            --status)    action="status";    shift ;;
            --uninstall) action="uninstall"; shift ;;
            -h|--help)   usage; exit 0 ;;
            *)           die "不明なオプション: $1  (--help で使い方を表示)" ;;
        esac
    done

    case "$action" in
        install)   do_install ;;
        status)    do_status ;;
        uninstall) do_uninstall ;;
    esac
}

main "$@"
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?