はじめに
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 "$@"