コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2020年3月12日 >>> 2020年3月3日
link もっと後

2020年3月10日

誕生日

37歳になりました。おめでとう俺、ありがとう俺。30代も残りわずかです。

何歳になろうが、本人は特に何も変わってませんが、周りから見ると確実にオジサンです。若者へ説教と昔の自慢話だけは絶対しないように気をつけます。

今に始まったことじゃないんですが、いつも何歳だか良くわからなくなります。もちろん (現在の西暦) - (生まれた西暦) で、1月1日〜3月9日は -1すれば良いのはわかってますが、ぱっと答えられません。なぜなのか……。

編集者:すずき(2020/03/15 02:33)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年3月9日

TinkerBoardが起動しなくなった

目次: ROCK64/ROCKPro64

ASUS TinkerBoardで動かしているlinux-nextを最新版に更新したところ、下記のようなエラーを出力して起動しなくなりました。

linux-next起動時のエラー
[    0.000000] __clk_core_init: Failed to get phase for clk 'sdmmc_drv'
[    0.000000] rockchip_clk_register_branches: failed to register clock sdmmc_drv: -22
[    0.000000] rockchip_clk_register_branches: failed to register clock sdmmc_sample: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio0_drv: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio0_sample: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio1_drv: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio1_sample: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock emmc_drv: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock emmc_sample: -17

エラーメッセージで検索しても、特にめぼしい報告やパッチに引っかかりません。うーん。困りましたね。

原因解析

仕方ないので、更新履歴をgit bisectすると2/13から2/14の間にクロックドライバが変更されたことが原因だとわかりました。

git bisectで見つけた原因となる変更Commit:2760878662a2

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index dc8bdfbd6a0c..ed1797857bae 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -3457,7 +3457,12 @@ static int __clk_core_init(struct clk_core *core)
 	 * Since a phase is by definition relative to its parent, just
 	 * query the current clock phase, or just assume it's in phase.
 	 */
-	clk_core_get_phase(core);
+	ret = clk_core_get_phase(core);
+	if (ret < 0) {
+		pr_warn("%s: Failed to get phase for clk '%s'\n", __func__,
+			core->name);
+		goto out;
+	}
 
 	/*
 	 * Set clk's duty cycle.

しかしこの変更は真っ当に見えます。今までエラーを無視していたところをエラーチェックするようにしているだけだからです。

どの辺りでエラーが出るか調べるためprintkを適当に入れたりしながら追ってみると、下記の場所で躓いていました。

クロック周りのコード

// drivers/clk/clk.c

static int __clk_core_init(struct clk_core *core)
{
...
	/*
	 * Set clk's phase by clk_core_get_phase() caching the phase.
	 * Since a phase is by definition relative to its parent, just
	 * query the current clock phase, or just assume it's in phase.
	 */
	phase = clk_core_get_phase(core);  //★★変更が影響している場所
	if (phase < 0) {
		ret = phase;
		pr_warn("%s: Failed to get phase for clk '%s'\n", __func__,
			core->name);
		//goto out;
	}


// drivers/clk/clk.c

static int clk_core_get_phase(struct clk_core *core)
{
	int ret;

	lockdep_assert_held(&prepare_lock);
	if (!core->ops->get_phase)
		return 0;

	/* Always try to update cached phase if possible */
	ret = core->ops->get_phase(core->hw);  //★★ここでエラーが発生していた
	if (ret >= 0)
		core->phase = ret;

	return ret;
}

独自のget_phaseを持っているドライバがエラーを返すと、clk_core_get_phase() もエラーを返し、先程のエラーメッセージが表示されるみたいです。しかし、この処理自体におかしな箇所はないように思います。真っ当です。

おそらく実装がおかしいのはRockchipのMMCドライバのクロック周りの方でしょう。エラーメッセージに出ているsdmmc_drvを頼りにコードを追います。

Rockchip RK3228クロック周りのコード

// drivers/clk/rockchip/clk-rk3228.c

PNAME(mux_mmc_src_p)		= { "cpll", "gpll", "xin24m", "usb480m" };
...
	COMPOSITE(SCLK_SDMMC, "sclk_sdmmc", mux_mmc_src_p, 0,
			RK2928_CLKSEL_CON(11), 8, 2, MFLAGS, 0, 8, DFLAGS,
			RK2928_CLKGATE_CON(2), 11, GFLAGS),
	...
	//★★sdmmc_drvはこの部分由来です
	MMC(SCLK_SDMMC_DRV,    "sdmmc_drv",    "sclk_sdmmc", RK3228_SDMMC_CON0, 1),


// drivers/clk/rockchip/clk.h

#define MMC(_id, cname, pname, offset, shift)			\
	{							\
		.id		= _id,				\
		.branch_type	= branch_mmc,			\  //★★この値がカギかな?
		.name		= cname,			\
		.parent_names	= (const char *[]){ pname },	\
		.num_parents	= 1,				\
		.muxdiv_offset	= offset,			\
		.div_shift	= shift,			\
	}


// drivers/clk/rockchip/clk-rk3228.c

static void __init rk3228_clk_init(struct device_node *np)
{
...
	rockchip_clk_register_branches(ctx, rk3228_clk_branches,  //★★ここでbranch_typeを見ている箇所がある
				  ARRAY_SIZE(rk3228_clk_branches));
...
}
//★★注: rk3228_clk_initはRK3228のクロックコア初期化関数
//    デバイスツリー内のcompatible = "rockchip,rk3228-cru" を持つノードに応じて呼ばれる
CLK_OF_DECLARE(rk3228_cru, "rockchip,rk3228-cru", rk3228_clk_init);


// drivers/clk/rockchip/clk.c

void __init rockchip_clk_register_branches(
				      struct rockchip_clk_provider *ctx,
				      struct rockchip_clk_branch *list,
				      unsigned int nr_clk)
{
...
	for (idx = 0; idx < nr_clk; idx++, list++) {
		flags = list->flags;

		/* catch simple muxes */
		switch (list->branch_type) {
		...
		case branch_mmc:  //★★branch_type == branch_mmcだったら
			clk = rockchip_clk_register_mmc(  //★★クロックの登録をしている
				list->name,
				list->parent_names, list->num_parents,
				ctx->reg_base + list->muxdiv_offset,
				list->div_shift
			);
			break;
		...


// drivers/clk/rockchip/clk-mmc-phase.c

struct clk *rockchip_clk_register_mmc(const char *name,
				const char *const *parent_names, u8 num_parents,
				void __iomem *reg, int shift)
{
...
	init.name = name;
	init.flags = 0;
	init.num_parents = num_parents;
	init.parent_names = parent_names;
	init.ops = &rockchip_mmc_clk_ops;  //★★クロックの操作関数を登録している
...


// drivers/clk/rockchip/clk-mmc-phase.c

static const struct clk_ops rockchip_mmc_clk_ops = {
	.recalc_rate	= rockchip_mmc_recalc,
	.get_phase	= rockchip_mmc_get_phase,  //★★get_phaseはこの関数
	.set_phase	= rockchip_mmc_set_phase,
};


// drivers/clk/rockchip/clk-mmc-phase.c

static int rockchip_mmc_get_phase(struct clk_hw *hw)
{
	struct rockchip_mmc_clock *mmc_clock = to_mmc_clock(hw);
	unsigned long rate = clk_hw_get_rate(hw);
	u32 raw_value;
	u16 degrees;
	u32 delay_num = 0;

	/* See the comment for rockchip_mmc_set_phase below */
	if (!rate)
		return -EINVAL;  //★★エラーを返している
...

原因らしき箇所が見つかりました。クロック周波数(rate)が設定されていなくても、エラーを返す必要はなく、phaseの初期値0を返せば良いはずですから、return -EINVALをreturn 0にすれば良さそうです。

変更すると無事linux-nextが起動できるようになりました。良かった良かった。

またこのパターン!

こんなバグを2週間以上放置するなんてlinuxらしくないなと思って、パッチを送ろうとしましたが……、なんだかとっても嫌な予感がしたので、rochchip_mmc_get_phaseでLKMLを検索してみました。

検索してびっくり、なんと、全く同じ指摘をしているパッチが先週LKMLに送られています(LKMLへのリンク)。しかも既にclk-nextに取り込まれているじゃないですか。ですがlinux-nextへの反映はまだのようです。

明日まで待っていればclk-nextがlinux-nextに取り込まれるので、何もしなくても解決していたんです。パッチ投稿が3/4なので、今回は本当に気づくタイミングが悪かったです。

LKMLに投稿されていたパッチ

Author: Jerome Brunet < >
Date:   Tue Mar 3 20:29:56 2020 +0100

    clk: rockchip: fix mmc get phase
    
    If the mmc clock has no rate, it can be assumed to be constant.
    In such case, there is no measurable phase shift. Just return 0
    in this case instead of returning an error.
    
    Fixes: 2760878662a2 ("clk: Bail out when calculating phase fails during clk
    registration")
    Tested-by: Markus Reichl <m.reichl@fivetechno.de>
    Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>

diff --git a/drivers/clk/rockchip/clk-mmc-phase.c b/drivers/clk/rockchip/clk-mmc-phase.c
index 4abe7ff31f53..975454a3dd72 100644
--- a/drivers/clk/rockchip/clk-mmc-phase.c
+++ b/drivers/clk/rockchip/clk-mmc-phase.c
@@ -51,9 +51,9 @@ static int rockchip_mmc_get_phase(struct clk_hw *hw)
 	u16 degrees;
 	u32 delay_num = 0;
 
-	/* See the comment for rockchip_mmc_set_phase below */
+	/* Constant signal, no measurable phase shift */
 	if (!rate)
-		return -EINVAL;
+		return 0;
 
 	raw_value = readl(mmc_clock->reg) >> (mmc_clock->shift);

前にも、原因を突き止めたら、解決済みだったというパターン(2018年12月4日の日記2019年9月18日の日記)がありました。

またこの「鶏と卵のパターン」にやられました。ついてない。

コードに特攻する前に、ちゃんと探せば?って思われるかもしれませんが、困ったことにエラーメッセージで検索してもパッチに辿り着けないんです。調べに調べて原因と対策がわかった後に、はぁ?もうパッチあるじゃん!?と気づくから、徒労感が激しい……。

編集者:すずき(2023/09/24 13:22)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年3月6日

GCCを調べる - その6 - ベクトルレジスタ用のregister constraintを足す

目次: GCC

GCCのインラインアセンブラでは __asm__("ins %0" : "=r"(aaa) : : ); のように、"=r" という不思議な文字列が出てきます。

これはconstraintsと呼ばれ(GCCのマニュアルへのリンク)、引数がレジスタ(r)なのかメモリアドレス(m)なのか、書き換えられるのか(=)などを説明しています。

どんな文字でも書けるわけではなく、変な文字(例えば 'v')を指定すると「impossible constraint in 'asm'」と怒られます。これは一体どこでチェックしているのでしょう?また、どうやって足せばよいでしょうか?

エラーを出している箇所

このエラーを出すのは、パスでいうと234r.vregsです。

ちなみにbuild_gccというディレクトリ名はGCCのビルドディレクトリのことです。GCCは自動生成コードをかなりの量出力するので、そちらも合わせて見る必要があります。

エラーメッセージを出している箇所

// gcc/function.c

static void
instantiate_virtual_regs_in_insn (rtx_insn *insn)
{
...
  if (asm_noperands (PATTERN (insn)) >= 0)
    {
      if (!check_asm_operands (PATTERN (insn)))  //★★このエラーチェックに引っかかっている
	{
	  error_for_asm (insn, "impossible constraint in %<asm%>");  //★★このエラーメッセージが出ている


// gcc/recog.c

int
check_asm_operands (rtx x)
{
...
  for (i = 0; i < noperands; i++)
    {
      const char *c = constraints[i];
      if (c[0] == '%')
	c++;
      if (! asm_operand_ok (operands[i], c, constraints))  //★★このエラーチェックに引っかかっている
	return 0;
    }

このチェックは何をしているのかというと、lookup_constraint() で文字(例えば 'v')をテーブルから探し、constraintの種類に変換します。未知の文字の場合はCONSTRAINT_UNKNOWNになります。

次にget_constraint_type() でどのカテゴリに属するか見ます。この関数はおかしくて、なぜかCONSTRAINT__UNKNOWNをCT_REGISTERと判定します。レジスタじゃないですよね?意味不明です。

asm文のconstraintを見ている箇所

// gcc/recog.c

int
asm_operand_ok (rtx op, const char *constraint, const char **constraints)
{
  int result = 0;
...
  while (*constraint)
    {
      enum constraint_num cn;
      char c = *constraint;
      int len;
      switch (c)
	{
...
	default:
	  cn = lookup_constraint (constraint);  //★★文字からconstraint_numに変換
						//    未知の文字の場合CONSTRAINT__UNKNOWNになる
	  switch (get_constraint_type (cn))
	    {
	    case CT_REGISTER:  //★★なぜかここに行く
	      if (!result
		  && reg_class_for_constraint (cn) != NO_REGS  //★★レジスタのconstraintがNO_REGSになるとNG
		  && GET_MODE (op) != BLKmode
		  && register_operand (op, VOIDmode))
		result = 1;
	      break;


// build_gcc/gcc/tm-preds.h

static inline enum constraint_num
lookup_constraint (const char *p)
{
  unsigned int index = lookup_constraint_array[(unsigned char) *p];  //★★この配列がカギ
  return (index == UCHAR_MAX
          ? lookup_constraint_1 (p)
          : (enum constraint_num) index);
}

enum constraint_num
{
  CONSTRAINT__UNKNOWN = 0,
  CONSTRAINT_r,
  CONSTRAINT_f,
...

static inline enum constraint_type
get_constraint_type (enum constraint_num c)
{
  if (c >= CONSTRAINT_p)
    {
      if (c >= CONSTRAINT_G)
        return CT_FIXED_FORM;
      return CT_ADDRESS;
    }
  if (c >= CONSTRAINT_m)
    return CT_MEMORY;
  if (c >= CONSTRAINT_I)
    return CT_CONST_INT;
  return CT_REGISTER;  //★★cがCONSTRAINT__UNKNOWNつまり0の場合、どれにも当てはまらずCT_REGISTERと判断される
}


// build_gcc/insn-preds.c

const unsigned char lookup_constraint_array[] = {
  CONSTRAINT__UNKNOWN,
  CONSTRAINT__UNKNOWN,
...
  MIN ((int) CONSTRAINT_r, (int) UCHAR_MAX),
  MIN ((int) CONSTRAINT_s, (int) UCHAR_MAX),
  CONSTRAINT__UNKNOWN,
  CONSTRAINT__UNKNOWN,
  CONSTRAINT__UNKNOWN,  //★★'v' は未定義、未知の文字の場合は全てCONSTRAINT__UNKNOWNになる
  CONSTRAINT__UNKNOWN,
...


// build_gcc/gcc/tm-preds.h

static inline enum reg_class
reg_class_for_constraint (enum constraint_num c)
{
  if (insn_extra_register_constraint (c))
    return reg_class_for_constraint_1 (c);  //★★ここで見つからないとNO_REGSが返されてNG
  return NO_REGS;
}


// build_gcc/gcc/insn-preds.c

enum reg_class
reg_class_for_constraint_1 (enum constraint_num c)
{
  switch (c)
    {
    case CONSTRAINT_r: return GENERAL_REGS;
    case CONSTRAINT_f: return TARGET_HARD_FLOAT ? FP_REGS : NO_REGS;
    case CONSTRAINT_j: return SIBCALL_REGS;
    case CONSTRAINT_l: return JALR_REGS;
    default: break;  //★★CONSTRAINT__UNKNOWNはどのcaseにも当てはまらないのでNO_REGSが返されてNG
    }
  return NO_REGS;
}

明らかにレジスタとは思えないCONSTRAINT__UNKNOWNが返ってきますが、なぜかCT_REGISTERだと思って処理し始め、最終的にエラーとして弾きます。結果オーライですがこれで良いんでしょうか。GCCのコードは訳がわかりません……。

register_constraintの足し方

ではvを正当なconstraintの一員にするにはどうしたら良いでしょうか?

第一歩としてはGCCのconfigディレクトリの下にある *.mdファイル(MarkdownではなくMachine Descriptorです)を編集します。

define_register_constraintを足す

;; config/riscv/riscv.md

(include "predicates.md")
(include "constraints.md")


;; config/riscv/constraints.md

(define_register_constraint "v" "TARGET_VECTOR ? VP_REGS : NO_REGS"
  "A vector register (if available).")

これを足すと(他にも色々やらないといけないんですけど)、先程のreg_class_for_constraint_1() に変化が生じます。

asm文のconstraintチェックが変わる

// build_gcc/gcc/insn-preds.c

enum reg_class
reg_class_for_constraint_1 (enum constraint_num c)
{
  switch (c)
    {
    case CONSTRAINT_r: return GENERAL_REGS;
    case CONSTRAINT_f: return TARGET_HARD_FLOAT ? FP_REGS : NO_REGS;
    case CONSTRAINT_j: return SIBCALL_REGS;
    case CONSTRAINT_v: return TARGET_VECTOR ? VP_REGS : NO_REGS;  //★★これが足されて通過するようになる
    case CONSTRAINT_l: return JALR_REGS;
    default: break;
    }
  return NO_REGS;
}

GCCはこの手のピタゴラスイッチの塊で、何を変えると望みの機能が実装できるか、全くわかりません。こんなもの良くメンテナンスできるなあ、と思います。

編集者:すずき(2023/09/24 11:47)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2020年3月12日 >>> 2020年3月3日
link もっと後

管理用メニュー

link 記事を新規作成

<2020>
<<<03>>>
1234567
891011121314
15161718192021
22232425262728
293031----

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 24年3月25日
    すずき (03/26 03:20)
    「[Might and Magic Book One TASのその後] 目次: Might and Magicファミコン版以前(...」
  • link 21年10月4日
    すずき (03/26 03:14)
    「[Might and Magicファミコン版 - まとめリンク] 目次: Might and Magicファミコン版TASに挑...」
  • link 24年3月19日
    すずき (03/20 02:52)
    「[モジュラージャックの規格] 古くは電話線で、今だとEthernetで良く見かけるモジュラージャックというコネクタとレセプタク...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/26 03:20