link もっと前
   2020年 3月 9日 -
      2020年 2月 29日  
link もっと後

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

日々

link permalink

link 編集する

TinkerBoard が起動しなくなった

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日の日記)がありました。

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

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

[編集者: すずき]
[更新: 2020年 3月 11日 02:13]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 6 - GCC の register_constraint

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__UNKNKNOWN が返ってきますが、なぜか 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 はこの手のピタゴラスイッチの塊で、何を変えると望みの機能が実装できるか、全くわかりません。こんなもの良くメンテナンスできるなあ、と思います。

[編集者: すずき]
[更新: 2020年 3月 11日 22:47]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 5 - RTL (Register Transfer Language) の定義

全ての RTL の定義は gcc/rtl.def に定義されています。例えば insn の定義は下記のとおりです。

insn の定義

/* An instruction that cannot jump.  */
DEF_RTL_EXPR(INSN, "insn", "uuBeiie", RTX_INSN)

何となくわかるけど、何だこれ?と思いますよね、私は思いました。それもそのはずで、実はこのファイルは単体では意味をなさず、C ファイルから何度も再利用されるからです。C ファイルから rtl.def をインクルードして使いますが、その際に DEF_RTL_EXPR() の意味が変わります。

比較的わかりやすい例を挙げるとしたら、gcc/rtl.c の rtx_format の定義です。DEF_RTL_EXPR() を定義してから #include "rtl.def" を行う様子がわかるかと思います。

rtl.def の利用方法の一例(gcc/rtl.c より)

/* Indexed by rtx code, gives a sequence of operand-types for
   rtx's of that code.  The sequence is a C string in which
   each character describes one operand.  */

const char * const rtx_format[NUM_RTX_CODE] = {
  /* "*" undefined.
         can cause a warning message
     "0" field is unused (or used in a phase-dependent manner)
         prints nothing
     "i" an integer
         prints the integer
     "n" like "i", but prints entries from `note_insn_name'
     "w" an integer of width HOST_BITS_PER_WIDE_INT
         prints the integer
     "s" a pointer to a string
         prints the string
     "S" like "s", but optional:
	 the containing rtx may end before this operand
     "T" like "s", but treated specially by the RTL reader;
         only found in machine description patterns.
     "e" a pointer to an rtl expression
         prints the expression
     "E" a pointer to a vector that points to a number of rtl expressions
         prints a list of the rtl expressions
     "V" like "E", but optional:
	 the containing rtx may end before this operand
     "u" a pointer to another insn
         prints the uid of the insn.
     "b" is a pointer to a bitmap header.
     "B" is a basic block pointer.
     "t" is a tree pointer.
     "r" a register.
     "p" is a poly_uint16 offset.  */

#define DEF_RTL_EXPR(ENUM, NAME, FORMAT, CLASS)   FORMAT ,
#include "rtl.def"		/* rtl expressions are defined here */
#undef DEF_RTL_EXPR
};

この rtx_format の場合は、第 3引数(FORMAT)とカンマを残して、他は無視するように DEF_RTL_EXPR() を定義しています。

コメントなどを無視して考えると rtl.def をインクルードしたあとは、コードは下記のようになります。

include 後の rtl.def のイメージ

const char * const rtx_format[NUM_RTX_CODE] = {
DEF_RTL_EXPR(UNKNOWN, "UnKnown", "*", RTX_EXTRA)

DEF_RTL_EXPR(VALUE, "value", "0", RTX_OBJ)

DEF_RTL_EXPR(DEBUG_EXPR, "debug_expr", "0", RTX_OBJ)

...
};

マクロ DEF_RTL_EXPR() の展開後はこうなります。

マクロ DEF_RTL_EXPR() 展開後の rtl.def のイメージ

const char * const rtx_format[NUM_RTX_CODE] = {
"*",

"0",

"0",

...
};

最終的に rtx_format は文字列の配列になります。

私も rtl.def の用途全てを理解しているわけではなく、全てを説明することもできないので、またわかったら書くことにします。

[編集者: すずき]
[更新: 2020年 3月 9日 11:45]

コメント一覧

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



link もっと前
   2020年 3月 9日 -
      2020年 2月 29日  
link もっと後

管理用メニュー

link 記事を新規作成

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDF ファイル RSS 1.0
QR コード QR コード

最終更新: 3/15 02:33

カレンダー

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

最近のコメント 5件

  • link 13年11月28日
    すずき 「ご指摘ありがとうございます。投稿の古さに...」
    (更新:03/08 17:39)
  • link 13年11月28日
    yut 「古いので、見てもらえるか不明ですので、、...」
    (更新:03/08 13:16)
  • link 13年11月28日
    yut シムズ 「古い投稿に対して、申し訳ありません。\n...」
    (更新:03/08 13:14)
  • link 19年09月01日
    すずき 「私も正直びっくりです。間違って違う製品を...」
    (更新:09/04 23:39)
  • link 19年09月01日
    hdk 「車向けの製品の中でも、車載コンピューター...」
    (更新:09/02 23:20)

最近の記事 3件

link もっとみる
  • link 20年03月10日
    すずき 「[誕生日] 37歳になりました。おめでとう俺、ありがとう俺。30代...」
    (更新:03/15 02:33)
  • link 20年03月14日
    すずき 「[GCC を調べる - その 7 - machine mode] ...」
    (更新:03/15 02:21)
  • link 20年03月06日
    すずき 「[GCC を調べる - その 6 - GCC の regist] ...」
    (更新:03/11 22:47)

こんてんつ

open/close wiki
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 過去日記について

その他の情報

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