2019/10/22

I2Cのその後・・・

あれからどうやってもI2Cがうまくいかない。
ゴチャゴチャとハンダ付けしているのがよくないので、取り付けていたユニバーサル基板から外そう。

 

image

最初はハンダ付けした面から取り外そうとしたのだが、両側からハンダ付けしている場合はほぼ無理なのよね。。。
もしかしたら物理的にハンダを吸い取るタイプの装置があればよかったのかもしれないが、私が持っているのはハンダ吸い取り線だ。
表面は吸い取れても、スルーホールの中までは無理だった。

 

では、と上から吸い取る方式に変更した。
こっちも似たようなものではあるが、ピンが飛び出している距離が短いので、ハンダが溶けている間に基板を引っ剥がせるかもしれない。

が、結局それも難しい。そりゃそうだ。

ピンが出ている距離を短くするため、今度はハンダごてで温めながら力を入れてピンを引き抜こうとした。
もちろん無理。
TXB0104の基板とユニバーサル基板の2箇所がハンダ付けされているのだ。そう簡単にはいかない。

 

で、最終的にはニッパでピンを折った。
折った後でハンダ吸い取りしながらピンを抜く。
その結果が写真だ。

いやあ、ボロボロですな。
温めすぎたせいか、TXB104の基板も多少溶けてしまっている。
ICとスルーホールとの接触が切れているところもあるので、そこは直接ピンとハンダ付けし直すしかないか。。。

 

争いは何も生まない、みたいな言葉があるが、基板からの部品引き剥がしも何も生まないと思った。

2019/10/21

[arduino]I2Cがうまく動かん

FeliCa LinkをArduinoから動かそうとした。
ブレッドボードで適当につないだときは動いたのだが、シールドとして同じように配線してみると、なぜか動かない。。。

 

FeliCa LinkとArduinoは電圧が違うので、レベルコンバータを間に入れている。

TXB0104
https://www.switch-science.com/catalog/1466/

これが、GPIOでHI/LOしているときは動いているのだが、I2C masterとして動かすと反応してくれないのだ。


まず、SDA, SCLのpinModeを見てみたい。

main.cppはここのようだ。
C:\Program Files\WindowsApps\ArduinoLLC.ArduinoIDE_1.8.21.0_x86__mdqgnx93n4wtt\hardware\arduino\avr\cores\arduino\main.cpp

たぶん、同じディレクトリないのwiring.cにあるinit()でピンの初期化をしている。
が、アセンブラだか組み込み命令だか知らんが、Arduinoの関数じゃないので放置だ。
たぶん、なんかうまいことやってるのだろう。

 

あきらめてWire::begin()を見てみる。
C:\Program Files\WindowsApps\ArduinoLLC.ArduinoIDE_1.8.21.0_x86__mdqgnx93n4wtt\hardware\arduino\avr\libraries\Wire\src\Wire.cpp

twi_init()を呼んでいるが、これは同じディレクトリにあるutility/twi.cだろう。
ここで、こうやっている。

  // activate internal pullups for twi.
  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);

pinMode()など出てこなかったので、main.cppから呼んでるinit()でうまいことやってるか、内部のプルアップ抵抗を変更するだけならこれでよいようになってるとか、そんなところなのか。

 

なので、変なことをしなければ、ArduinoのI2Cは内部でプルアップしてくれるのだと思う。
この状態でSDA-GND間をテスターで測ってみると、2Vくらいあった。1.8Vくらいかな。
微妙な電圧値だ。。。
だが、Wire.begin()の後でdigitalWrite(SDA, 0)とすると0Vになるので、これがプルアップ効果なのだろう。

 

レベルコンバータを使った例のサイトを見ると、ここはプルアップ抵抗をつないでいない。

https://learn.sparkfun.com/tutorials/txb0104-level-shifter-hookup-guide

ADXL345のページまで飛んだが、そっちはプルアップ抵抗を付けるような説明になっていたので、Arduinoの内蔵プルアップを使っているのだろう。
しかし、1.8V程度でやっていけるのだろうか。。。

 

あ、計測するのをテスターからオシロに変更したのだが、オシロだと5Vになってた。
もしかして私のテスター・・・値が不正確すぎ??
もう何十年も使ってるアナログテスターで、しかもそのときですら千円ちょっとで購入した気がするので、そろそろ引退してもよいのかもしれない。


5Vちゃんとあるなら、いいだろう。

しかし、だ。
これをTXB0104につなぐと、どっちも0Vになってしまうのだ。
SDA, SCLをpinMode()でOUTPUTにしてdigitalWrite()すると、ちゃんと制御できている。

image

 

というのが、今の悩みなのだ。
レベルコンバータを挟んでいるから、3.3V側でもプルアップしないとダメというだけなのだろうか。

いや、さっきまではプルアップ抵抗を付けていたのだけど、うまくいかないので全部外したのだ。
ただ、外してあれこれ試している途中で、変なところがショートしているのに気付いて、元に戻すかどうか考えているところなのだ。

そもそも、まだ3.3V側は何も取り付けていないのだ。オープンなのだ。
だからArduino側がそのまま突き抜けてくれてもいいと思うのだよ。
でもなぁ、私の今までの経験上、そういう適当な判断は間違っていることが多いのだった。。。

 

試しに、3.3V側のピンに適当な抵抗をつけてプルアップしてみた。
そうすると、ちゃんとHIになった。そして、プルアップを外してもHIが維持されている。
うーん、つまり、3.3Vにもプルアップがいるということか。

 

プルアップ抵抗として4.7kΩのをつないでみたのだが・・・スタートコンディションっぽい動作はするものの、一度立ち下がってから立ち上がる様子がない。

以前動いたときは1kΩだったので変更したが、これはこれで動きが変だ。
4.7kΩのときはLOに張り付いていたが、1kΩだとときどきHIになったりして、多少は動こうとしているように見える、というところだ。

 

うーん、なんかもう、ハンダ付けがうまくいってないとか、変なところがショートしてるとか、そういうのを心配するべきなんだろうか。。。。
実績が無ければ最初からやり直そうとするのだが、ブレッドボードの時に動いていたというのが気になってしまい、どうしても見直しをしてしまうのだよなぁ。。。

2019/10/19

SlimBlade Trackball

image

以前買ったOrbit Scroll Ring Trackballと、SlimBlade Trackballを並べてみた。

 

そう、実はずいぶん前にSlimBladeを購入していたのだ。
Orbitを購入したのは2017年10月17日らしい。ブログはあるのだが、画像が残っていなかった。

 

SlimBladeの購入は、かなり悩んだ。
値段が1万円以上するというのもあるし、トラックボールを2つ買ってどうするんだ、というのもあった。
でも、会社に家のOrbitを何度も持っていくのが面倒になり、どうせ買うなら大きい方がいいや、とあきらめて購入
あまり当てにしづらいAmazonのレビューを見たが、評価がずいぶん割れているようだったのも心配な要因だった。

 

数ヶ月使っていて、なんとなくレビューで言われている内容も意味が分かってきた。
今回は2つめのトラックボールという人の意見として読んでほしい。


まず、ボールが大きいのは良いことだ。
会社の方が作業時間が長いのでSlimBladeは会社に置いているのだが、家でOrbitを使うとボールの小ささがものすごく気になってしまう。
細かい制御をしないから気にならんだろうと思っていたのだが、思いのほかボールの大きさは重要だった。
Orbitだと指先でボールを回す感じだが、SlimBladeだと手の平で回すこともできる。その力強さが非常に助かるのだった。

 

ボタンが4つあるのも助かっている。
ボタンが2つだと、中クリックに相当するものを両ボタンクリックでまかなっていたのだが、それが1つのボタンで割り当てられるのだ。それでもまだボタンが1つあるという余裕。以外と中クリック使うのよねぇ。
デフォルトでは「戻る」になっていて、私はそのまま使っている。

 

評価が分かれるのはホイール動作だろう。
SlimBladeではボールを回すのだが、楽かどうかで言えばOrbitの方が楽だ。ボールを回すと、カーソルが動いたりするのよね。ぐりぐり回しているのにスクロールしてくれないと思ったらカーソルがずれている、というのはよくあることだ。
ただまあ、物理部品は壊れやすいということを考えると、それよりはいいのかもしれないな、と思ってしまうのだ。リングが故障したらたぶんまるごと買い替えるだろうから。

 

あと、ホイール音。
これはソフトウェアでSlimBladeが直接音を出している。つまり、通電状態じゃなければ音はしないのだ。
で、この音をやめる方法がない。何かスピーカーのようなものがついているだろうから取り外せばいいんだろうけど、そこまでやる気力は無い。

音が気になるかどうかは、個人差だろう。
今の職場は音楽を流していても良いようなところなので、SlimBladeのホイール音くらいなら気にならない。ただ、その音が気になるかもしれない、と思い始めると気になってしまう、というものだ。
静音マウスというものがあるくらいだから、音を切ることができるようになっておくとよいとは思う。

 

有線がいいのかどうかは、私には分からん。
ノートPCじゃないし、移動することもないので有線で充分なのだ。


こんなところか。

トラックボールだけじゃないけど、ディスプレイとかキーボードとかマウスのようなものって、実際に自分の環境である程度の時間を掛けて使わないとわからないことがしばしばある。
かなり個人の感性に依存してしまうものだから、私の感想もその程度に受け取ってもらえると助かる。

2019/10/14

[arm]Armv8-MのTrustZone

nRF9160で気になっていたのが、TrustZoneだ。
Cortex-AのTrustZoneを知らないので比較できないのだが、この記事によるとGCCから扱えるようだ。

https://www.lobaro.com/using-the-armv8-m-trustzone-with-gcc/


Introduction & Motivation

Cortex-M33プロセッサにはARM TrustZoneと呼ばれる機能がある。
この記事では、GNU ARM GCC compilerとCMSE(Cortex M Security Extensions)を使ってTrustZoneついての私見を述べる。

これはARMv8-MならnRF9160じゃなくてもいいし、GCC以外のコンパイラでもいい。

 

Secure & Non Secure CPU Operationg States

今まではThreadモード(通常モード)とHandlerモード(割込みモード)だけだったが、ARMv8-Mになるとsecure / non-secureという属性が加わる。

電源を入れたときはsecureモードで始まる。このときはメモリも周辺機能も全部使える。これは今までのM3やM4などと同じ。

TrustZoneの考え方は、セキュリティ関連のコードやデータをデバイス内の小さな特定領域に隠すということだ。その領域は"secure"と呼ばれ、そうじゃないところは"non-secure"と呼ばれる。メモリ、割込み、周辺機器をsecure/non-secureに分割するのはユーザが決める。電源ON時のコードはその分割をする処理から始めるのが普通だ。nRF9160のコード例(Secure Partition Managerと呼ぶらしい)では、FLASHの256KBをsecureに、768KBをnon-secureに割り当てている。

CPUは実行関係のレジスタ(スタックポインタとSystem Control Block)として2つのバンク(secure/non-secure)を持つ。今のCPU動作がsecureかnon-secureかというのは、どっちのバンクを使っているかということと同じ意味である。バンクのメカニズムはハードウェアで2回実装されている一部の周辺機器(SysTickなど)でも同様です【私:2回(twice)って何??】。secureなプログラムは常にどちら側にもアクセスできる。non-secureなスタックポインタとnon-secureなベクタアドレスの配置を設定した後、secureなブートコードはnon-secureなベクタテーブルの関数ポインタを使用する。そして、CPUはnon-secureなファームウェアとして動作し始める。

non-secureファームウェアは、secureファームウェアの存在を認識してはならない。TrustZoneがない時代と同じように普通に開発して、普通にnon-secureなメモリ領域とリンクするようにする。CPUがnon-secureに動き始めると、security exceptionが発生しない限りsecureなコード、データ、周辺機器にに直接アクセスできなくなる。non-secureコードからsecureな機能にアクセスする唯一の正当な方法は、secure側が提供することになる特別なゲートウェイ/エントリー/veneer機能を使うことである。では、ARM GCCとCMSE(Cortex M Security-Extensions)を使ってやってみよう。

 

Non-secure callable(NSC) memory regions and the secure gateway(SG) ASM instruction

non-secureからsecureな機能を呼び出せるようにするため、secureファームウェアのコードに"Non-secure callable(NSC)"といういくつかの小さなFLASH領域を設定しなくてはならない。nRF9160ではSPU(System Protection Unit。PDFのp.257参照)が担当している。

secureメモリの一部をNSCとして指定することにより、最初のOPコードとしてアセンブラの"SG(Secure Gateway)"命令を呼び出すことで、non-secureから呼び出せないという制限を緩和できる。SG命令を実行すると、プログラムはより制限されたsecureメモリ内にある=NSCではないメモリにあるsecure機能実装に分岐する。このようにしてnon-secureコードからsecure機能を呼び出すことで、まずNSCにジャンプし、続けてsecureメモリ内にある実際の機能にジャンプする。こういう過程を経ることにより、secureメモリへのアクセスは残りのsecureメモリから切り離される。詳しいことはこちらを読め:Reasons for introducing the NSC regions

 

Defining non-secure callable functions in secure firmware

幸いなことに、non-secureファームウェアからsecure機能を呼び出す過程はARMv8-Mコンパイラによってプログラマからは隠されている。non-secureファームウェアから呼び出すsecure機能のエントリーポイントはsecureファームウェアの定義をした後でcmse_nonsecure_entry属性をつける。

//some c file of secure firmware project defining veneer gateway functions
(ソース例は原文を見よう)

GCCでコンパイルするときにはオプションとして"-mcmse"がいる。

// GCC COMPILER flags used during secure side building
arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]

関数本体は通常secureメモリの.textセクションに配置される。が、SGと分岐する命令は.gnu.sgstubsセクション(GCCの場合。GCC以外は違う名前かもしれない)。

NSC領域の小さなredirect機能は、SGを実行して分岐する処理しかやらないので、"veneer"機能や"SG stubs"などと呼ばれる【私:blogでは"SG stups"と書いてあったけど、typoよね? それと、ここまでfunctionを「機能」と書いてきたけど、C言語の関数という意味で使われたりもする? redirect functionってなんだろう? non-secureからsecureに遷移するってこと?】。secureファームウェアのリンカスクリプトファイルは、non-secureファームウェアにジャンプする前にsecureブートプロセスの一部のNSCとして宣言するveneerコードをsecure領域に配置しなくてはならない。

// Linkerscript Section for TrustZone Secure Gateway veneers
(リンカスクリプトは原文を見よう)

【私:何が言いたいのかわからなかった。リンカスクリプトの見方が分かってない。】

 

Calling secure functions from non-secure firmware (NS->S)

通常、secureファームウェアはnon-secureファームウェアイメージから独立して開発&FLASH書込みされる。secureファームウェアの機能を使うために、secure側の関数を呼び出せるように書かれたヘッダファイル(.h)はnon-secureソースファイル側でincludeする。加えて、non-secureファームウェアはsecureメモリのNSC部分の前にveneerエントリーポイントスタブがどこにあるかを知っていないといけない【私:「NSC部分の前に」はbeforeだから時間的に前。FLASHのsecureとnon-secureの設定を行う前に決定していないといけない、という意味か?】。これは特殊なオブジェクトファイル(CMSE_importLib.o)とアドレス情報を必要としているnon-secureファームウェアをリンクすることで解決する【私:done、は、何とかしてくれる、くらいの意味だろうか?】。このオブジェクトファイルは以下のcmseリンカオプションを使用してsecureファームウェアがリンクされる間に生成されなくてはならない【私:CMSE_importLib.oはここで生成されているのだろうか?】。

// GCC LINKER flags used during SECURE side linking
arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]

オプション"-in-implib=CMSE_importLib.o"は、過去バージョン(TrustZone未対応)で既に定義された同じメモリに展開されるオブジェクトファイルを新しいバージョン(TrustZone対応)でも維持できるようにする【私:-in-implibではなく-out-implibの間違いでは?】。こうすることで(過去バージョンで作成済みのimport libにリンクされている可能性がある)non-secureイメージの更新をせずにsecureイメージに追加できるようになる。

.hファイルを使用してCMSE_importLib.oオブジェクトをリンクするために、non-secureファームウェアはControlCriticalIO()関数を呼び出す【私:the example aboveは、たぶん「原文を見よう」のコードだ。】。

// GCC LINKER flags used during non-secure side linking
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

 

Calling non-secure (callback) functions from secure firmware (S->NS)

どのようにしてTrustZoneが2つの隔離されたファームウェアをgatewayやveneer機能で接続するのかを見てみよう。これはsecureファームウェアがnon-secureファームウェアから呼び出される機能を許可しているからだ。

もう1つの方向、すなわちsecureファームウェアからnon-secure関数を呼び出す場合、なんらかの方法でsecureコードにnon-secure関数のアドレスが与えられたならば、実行することができる。通常、これはsecure gateway functionが引数にnon-secureコールバックポインタを与えることで行われる。

(サンプルは原文を見よう)

"nonsecure_call()"属性は、潜在的なセキュリティリスクを割けるためにnon-secureコードにジャンプする前にバンクじゃないgeneral purposeレジスタをクリアするようsecureコンパイラに指示するために必要だ。そしてまた、ブランチアセンブラ命令の関数ポインタアドレスLSBをクリアする指示にも使われる。こういうことを行って、CPUはsecure状態からnon-secure状態に戻ってくる。

non-secureコールバックはvolatile値のように扱わねばならないので、non-secure割込みによって変更される可能性があるため、関数ポインタは"cb"にコピーが必要となる【私:cbはサンプルコードの中にあるfuncptr_ns型の変数か】。コールバックを実行する前に、ポインタがnon-secureメモリのみを挿しているのかチェックしなくてはならない。secureメモリを挿しているなら、それはセキュリティリスクとなりうる【私:ポインタ変数が4byteだとして、それが全部non-secure側にあるかどうかをチェックするのだろうか?】。

 

Hints and further reading

secure側のリンカスクリプトはveneer関数のセクションを考慮し、non-secure側のリンカスクリプトはsecureファームウェアのメモリ領域を反映しなくてはならない、ということを覚えておこう。archiveファイル(*.a)がnon-secureの呼び出し可能な関数を含んでいるなら、secureファームウェアのリンカオプションとして"whole-archive"を付けなくてはならない。この方法でのみCMSE import libが必要とするveneer関数に含めることができる。

 


けっこうがんばって読んでみたのだが、どうだろうか?
というか・・・私やっぱり英語力ないな。。。
ただ、この人のはGoogle翻訳などできれいに和訳されやすかった。がんばったのは、Google翻訳に通す前に自分でやってみたということについてだ。自信がないときはその後でGoogle翻訳して「ああやっぱり間違ってた」となるのだがね。

 

 

さて、整理しよう。

【NSC】
Non-Secure Callableの略。
non-secure状態からsecure状態に遷移するときに使うsecure側の領域。NSC memory regionsやNSC regionなどという使い方。

【SG】
Secure Gatewayの略。
non-secure状態からsecure状態に遷移したいときに使うアセンブラ命令。

 

まず、Defining non-secure callable functions in secure firmwareのところ。
ControlCriticalIO()という関数をcmse_nonsecure_entry属性つきで作る。
これはnon-secureファームウェアから呼び出されることになるのだが、その前にSGオペコードを呼び出したりなどをやってくれるのだろう。
これをsecureFirmware.elfという名前で生成している。-mcmseオプション付きだとsecure用になるのか。

 

次に出てきたのはリンカスクリプトで、.gnu.sgstubsが載っている。
これは自作が必要なのかもしれない。

 

次はCalling secure functions from non-secure firmwareのところ。
--Xlinkerを使ってリンカに指示している。オプションは--cmse-implib, --out-implib, --sort-section。
--out-implibはファイル名を付けているので、これが生成されるのだろう。

そして最後にCMSE_importLib.oと一緒にリンクしてnon-secureFirmware.elfを生成している。

 

最後の話で、callback_fnをcbにコピーするところがよくわからなかった。
C言語だから、引数は値渡しだよな? ポインタだから、アドレス値を渡しているはずだ。
じゃあ、コピーしなくてもいいんじゃないのかと思ったが、レジスタじゃなくてスタックで渡された場合だろうか。
うーん、答えが出ない。。

 

あと、secureとnon-secureにメモリのパーティションを分ける話があったが、FLASHはいいとして、SRAMの方はどうなってるのだろうか?
nRF9160のSPMを読むと、SRAMも分けるようだ。分けるというか、強制的に分けられるというべきか。だからSRAMについての記述がないのか。

 

nRFのFLASHって、自分で書き換えができたんだっけ?
OTAがあるのは覚えているが、BondingなんかはAPIを使ってしかやってないので、今ひとつ・・・いや全然記憶にない。


むー、もっと調査が必要か。

しかしまあ、しばらく触っていないうちにずいぶん状況が変わってきたな。
特にセキュリティ周り。
nRF51822だとBLEで手一杯だったので考慮しなかったが、nRF9160はTrustZoneがいるくらいのレベルが要求されているのだろう。まあ、nRF9160はBLEじゃなくてインターネット接続と直接接続される前提になっているからかもしれないが。

今までインターネットとは距離を置くことでセキュリティのことは放置してきたのだが、そういう時代ではなくなったのだな。。。

2019/09/28

gitのrebase

gitを使っている。

gitの説明って、だいたいコマンドラインで行われている。
コマンドを覚えるのが面倒なのでツールで済ませてしまいたいのだが、ずっと使っていてわかった。
なんだかんだで、コマンドラインでやらないと細かいことがやりづらいのだ。

いつか「gitの困ったときだけ使うGUIツール」みたいなのを作ろうかと考えていたのだけど、コマンドラインには叶わんのだよなぁ。。。

 

さて、gitを自分だけで使う分にはそこまで気を配らないのだが、チームで使うとなるとそうもいかん。
気にするのは、こんなところだろうか。

  • 自分に変なことが起きないようにする
  • 相手に変なことが起きないようにする

まあ、他に何があるんだっていわれそうだが、まずはこの2点に気を配るだろう。

 

GitHubでgitを使っているので、本体のリポジトリをforkして、branchを作って作業して、pull requestして本体にマージ、という流れにしている。
作業しているbranchは自分のものだから、好き勝手にいじっても問題が無い。
これで「相手に変なことが起きないようにする」は解決だ。
Pull Requestしてコンフリクトすることもあるだろうけど、そこはそこで解決すればいいだけのことだ。

 

ああ、「相手に」という意味では、commit履歴というものがある。
branchをつくって、今日の分、みたいな感じでcommitしていると、本体にマージしたときもその履歴が残ってしまう。
まあ、Pull RequestのマージでSquashというやり方もあるのだろうけど、1つにまとめていいかどうかPull Requestする人が悩むこともあるだろう。
そうならないように、Pull Requestする段階できれいな履歴にしておきたい。

 

で、ここからが本題だ。

いままで、私はbranchで作業したcommit履歴をまとめるために、別のbranchを作ってsquashさせていた。
これでよいときもあるのだが、単にこことここだけまとめたい、という場合もある。

そうなると、たぶんrebaseを使うはずだ。
rebaseすると、pushするときにforceで行うことになるが、まあ自分のbranchだから問題なかろう。
一度Pull Requestして修正するときも、rebaseしてpush forceしてしまえば履歴がきれいだし。


git rebase HEAD~2 と指定すると、直近のcommit履歴2つが出てくる。
上から古い順番で並んでいる。

ここでpickのところをsquashとかsとか書き換えると、commitがまとめられる。
では、sにした行の上にまとめられるのか、下にまとめられるのか。

試そう。

$ git log --oneline
741e4b2 (HEAD -> master) second
48697ff first

$ git rebase master
Current branch master is up to date.

あれ?? ダメなの??
じゃあ、branchを作って。。。

$ git checkout –b rb
$ 編集
$ git add xxxx
$ git commit
$ 編集
$ git add xxxx
$ git commit
$ git log --oneline
12d8146 (HEAD -> rb) forth
3ef2f57 third
741e4b2 (master) second
48697ff first

$ git rebase HEAD~2
Current branch rb is up to date.

うーん、rebase、つまりre-baseで、基点を変更するコマンドだ。
だから、masterが最新で、そこから延びただけだったら使えないのだろうか?

$ git rebase –i HEAD~2

あ、出てきた。

pick 3ef2f57 third
pick 12d8146 forth

これを、こうする。

s 3ef2f57 third
pick 12d8146 forth

You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
Or you can abort the rebase with 'git rebase --abort'.

怒られた。

pick 3ef2f57 third
s 12d8146 forth

これはOKで、続けてcommit logの入力を促す画面になる。

$ git log --oneline
354f949 (HEAD -> rb) third and forth
741e4b2 (master) second
48697ff first

 

つまり、上にまとめられる、と覚えておけば良かろうか。
よくないのかもしれないが、私にはそれで事足りそうだ。