2021/12/31(金)Windowsのウィンドウ再配置問題を切替器で打開する

2021/12/31 16:47 PC(全般)

概要

よく知られている問題として、Windowsにはウィンドウの再配置問題がある。

HDMI・DisplayPortでモニタを接続している際に、モニタの電源を切ったりケーブルの挿抜を行うとOS側がこれを検知する。
この時、マルチディスプレイ(拡張)で使用している場合は、接続が生きている側のモニタにウィンドウが再配置されてしまう。

この問題を切替器で解決したので下記にあらましを記載する。

問題の内容

上でケーブルの挿抜と書いたが、物理的なスイッチ(あるいは相当)を行う切替器でも発生する。
ソース1,2とモニタを繋げている切替器で、ソース1&モニタ→ソース2&モニタと切り替えた場合ソース1側では挿抜検知となる。

この問題PnPの仕様としてはごく自然だが、日常的にはこの仕様が邪魔になることが多い。

初めてモニタを繋いだ時・構成を変えた時には有用だが、接続モニタの切り替えや電源OFFを日常的に行う運用をする場合はかなり煩わしいことになる。

特に、資料閲覧や動画視聴などいずれかのディスプレイのウィンドウ配置を決めて使用しているユーザにとってはかなり痛い。
こういった用途の場合、左右のディスプレイの解像度や縦横配置を揃えていない場合も多く、再配置によるウィンドウの乱れ方が大きい。

OS側

Windowsには現状この問題の挙動を変える方法が存在していない認識。
WindowsのInside Previewにこの問題のパッチが来ているという話があるが、完全な機能ではないと言われている。リリース時期も(リリースされるかも)不明。

PersistentWindows, MonitorKeeperなどウィンドウの位置を記憶・復帰するツールは多数あるが、アプリケーションごとの仕様・使い方・権限などの違いにより完璧な動作は難しい。
ヒューリスティック的な挙動にならざるを得ない。

モニタ側

さて、この問題はOS側以外にモニタ・ドライバの作りや設定によって挙動がある程度変わる。

モニタでは、EIZOのモニタはこの問題に対応するためにCompatible Modeというモードを備えている。
EIZO以外に明示的にこの機能を謳っているモニタは発見できていない。

この設定を変えるとモニタの電源を切った際にモニタ側が切断信号をOS側に送らなくなる。
電源を切るケースについてはモニタを選ぶことで対応可能である(EV2495で確認)。

ただし、EIZOはこういったOSDの機能では優秀なものの、パネル周りはピンキリで価格も割高。
EV2495についてもオレンジや水色でかなり不快な縦線が見えるパネルが使用されていたりする(EV2456から継続している問題なので、EV2456 縦縞などで検索するとサンプルがでてくる)。

モニタの選択肢があまりに少ないのは辛いので、できればモニタに依存しない対応方法にしたい。

グラボ側

グラフィックボード(おそらくドライバの作り)も影響するようだ。

私の場合、今年の8月にRadeon HD6450からRyzen 5700Gの内蔵GPUに変更した。
同じRadeon系列であるにも関わらず、HD6450では発生しなかった再配置問題が5700Gだと発生するように。
しかもHDMI・DisplayPort問わず発生する。

体験する前までこの問題はDisplayPort限定と認識していたため、かなり想定外だった。

実質同一チップメーカーでも発生するしないがある上、グラボの機能として有無を探すのがすごく難しい。

グラボでなんとかするのもつらそうなので、グラボに依存しない対応方法にしたい。

課題と制約

あわせて、モニタの接続周りでいくつか課題を抱えていたのでそれもいっぺんに解決してしまいたい。

・ゲーム機をつなぎたいがモニタ側にHDMI端子が足りない
・モニタ側の接続切り替え操作が機種によっては操作しづらいので簡単に操作できるようにしたい。最近のモニタではボタンが背面にあるものすらある。
・リモートワークをする機会が多く、私用PCと業務PC(ノート)をいずれもモニタに繋ぐ。素早く切り替えられるようにしたい。

構成的な制約は以下の通り。

・モニタは2枚(Display1~2)。一旦ともにWUXGA横配置とする。機種・解像度・縦横配置は自由に変更可能にしたい。

・PC1(私用PC)。2枚のディスプレイにつなぎ、メイン・拡張で使用。絶対に再配置されたくない。ほぼサーバなので恒常的に起動状態。

・PC2(業務PC)。ノート。2枚のディスプレイにつなぎ、メイン・拡張で使用。本体モニタは使用しない。業務時間中は再配置されたくない。業務が終われば電源OFF・ケーブル取り外しで、業務開始時に再配置されるのは特に問題ない。

・Switch。Switchにこだわらずゲーム機など、モニタ1枚を使用するHDMI機器を最大2台くらい繋げられると理想。

つまりソースが5~6本、シンクが2本。これを上手く接続したい。

対応手段

課題を整理すると、PCとモニタの間になにか問題を解決するための仲介役が必須になるようだ。

EDID保持・固定などで調べるといくつか製品が見つかる。

大まかに2系統あるようだ。

・アダプタ(ドングル):HDMI->HDMIかDP->DP。HDMIのソース側の電源で動く。
アダプタの機能として、モニタ側の電源OFFをソース側に伝えない。モニタのEDIDを学習してそれをソース側に伝える、もしくは設定した固定EDIDをソース側に伝える。製品ごとの固定EDIDを持つものも(この場合、モニタの解像度ごとにアダプタが必要)。
amazonで売っている製品名不明のものや、同人ハードのDPHPDMAなど。

・切替器:HDMIのものが多くDPのものは少なかったり高かったり。外部電源が必要。
切替器の機能として、モニタ側の電源OFFをソース側に伝えない。モニタのEDIDを学習してそれをソース側に伝える、もしくは設定した固定EDIDをソース側に伝える。
TESmartの切替器など。

これを組み合わせて問題を何とかする。

実構成

いろいろ考えた挙げ句、TESmartの切替器を軸に以下の構成に落ち着いた。

 +----------+             +----------+
 | Display1 |             | Display2 |
 +----+-----+             +----+-----+
      |                        |
      +-------+           +----+
              |           |
         +----+-----------+---+          +----------+
         |  OUT1        OUT2  |          |          |
         |                    |          |      +---+------------+
         |  TESmart           |          |      |  OUT1          |
         |   TES-HMA0402A1U   |          |      |                |
         |                    |          |      |  SANWA SUPPLY  |
         | IN1  IN2  IN3  IN4 |          |      |   SW-HD31ML    |
         +--+----+----+----+--+          |      |                |
            |    |    |    |             |      | IN1  IN2  IN3  |
   +--------+    |    |    +-------------+      +--+----+--------+
   |             |    +-+                          |    |
   |    +--------+--+   |                          |    +--------+
   |    | BENFEI    |   |                          |             |
   |    |  DP->HDMI |   |    +------------+    +---+--------+    |
   |    +---+-------+   |    | Plugable   +----+ PCduoduo   |    |
   |        |           |    |  UGA-4KDP  | DP |  DP->HDMI  |    |
   |        |DP         |    +-+----------+    +------------+    |
   |        |           |      | USB                             |
+--+--------+---+    +--+------+-----+                           |
| HDMI     DP   |    | HDMI   USB    |                      +----+-----+
|               |    |               |                      | Nintendo |
|      PC1      |    |  PC2(Laptop)  |                      |  Switch  |
|               |    |               |                      +----------+
+---------------+    +---------------+

以下に、接続している機器についての詳細を記載する。

TESmart TES-HMA0402A1U
4入力2出力のHDMI切替器。切替器の電源が生きている間は切断信号をソース側に伝達しない。OUTにつないだモニタ側のEDIDを記憶し、IN側によしなに流してくれるようだ。細かい挙動を突き詰めていないが不満に感じるポイントがない。切り替えはモニタ側でやるより早いケースが多いと思う。物理ボタンはOUTに対してIN1~4をローテーションで切り替えるボタンが2つ(OUT1, OUT2用)。物理ボタンだと一発で指定ソースを選べないが、リモコンが付属しておりそちらではソースを直接指定できる。6入力だったら最高だった。現状のゲーム機器等のコネクタを考えるとDPではなくHDMI切替器である必要がある。

BENFEI DP->HDMIアダプタ
amazonで売っている安売りのDisplayPort→HDMI変換アダプタ。製品名不明で、別ブランドから同じ形のものがたくさん出ている。かなりの曲者で、3台入手したうち2台はPlugable UGA-4KDPからの変換に使うとNo Monitor 640x480固定になってしまう(EDIDが取れない?)。インピーダンスがばらついてるとか、チップが変わったとかそういうもののような気がする。PC1のDisplayPortにつなぐ分にはいずれも問題なかった。
この手の変換アダプタは国内サプライメーカーで販売しているものも中身同じだったりして、金を出せば失敗しないかというとなんとも言えないのが辛い。

Plugable UGA-4kDP
もともとモニタに空いているポートがDisplayPortしかなかったのでこれを使用していた。新構成でも流用。

DisplayLinkのチップを使用しており、挙動はやや怪しい(たまに止まる)。
DisplayLinkは製品の質でユーザからの不満が多い印象もあるが、現状USB単独でモニタを増やせる機器(チップ)自体があまり選択肢がない。その中ではPnPでドライバが入るのでDisplayLinkの信用度はけっこうある。

PCduoduo DP->HDMIアダプタ
amazonで売っている安売りのDisplayPort→HDMI変換アダプタ。製品名不明で、別ブランドから同じ形のものがいくつか出ている。BENFEIで2連続失敗したので購入。こいつはPlugableにつないでも問題なかった(EDIDをちゃんと取れた)。

SANWA SUPPLY SW-HD31ML
以前から使っていた切替器。TESmart切替器と合わせて、6入力を実現するために使用。IN切り替えがローテーションではなく直接ボタンでやれるのが良い。内部的には物理切替器とほぼ同じなので、この切替器につないだ機器は再配置問題が発生し得る。一緒に使う可能性がない機器の組み合わせ、もしくはモニタ1枚を使用する機器をつなぐ。

ついでにHDMIケーブルをすべて、細い割に不具合もなさそうなELECOMのCAC-HD14USシリーズで統一。

ケーブルの取り回しも楽になって(本数は増えたが)楽になった。

結果

メリット

・再配置問題を気にせずにモニタの電源を切れる。

・モニタの切り替えで本体操作不要。リモコンですばやく楽に。どのように切り替えても、PC1では再配置問題は発生しない。

・HDMIを軸に統一したので配線の考え方がシンプルに。HDMIに変換してHDMIで配線すればよい。ケーブルの流用が効くし、取り回しも楽。

・これからは、モニタはHDMI一口あれば何を買っても大丈夫。モニタ購入時に端子の個数・形状を気にしなくてよい。またウィンドウ再配置問題の対応(EIZOのCompatible Mode相当)も気にしなくてよい。

・これからは、PC1用のグラボはHDMIもしくはHDMIに変換できるコネクタ(DVIかDisplayPort)が計2個あれば何を買っても大丈夫。グラボ購入時にウィンドウ再配置問題を気にしなくてよい。

デメリット・課題

・ケーブルが増えた

・SW-HD31MLにつないでいる機器は再配置問題が発生しうる。今の用途だと回避しなくても不満がないが、回避したいとなるとTESmart切替器2台以上の組み合わせか50k以上する業務用機器になるかも

・TESmart切替器の消費電力(測っていない)

・TESmart切替器が単一障害点になってしまうので、今後故障時に同等製品がないと(あるいはやたらと高くなってたりすると)死ぬ。

余談

そのうちUSB-C(DP Alt)だらけになってしまいそうな気がするが、Windows側がなんとかしないとデイジーチェーンだろうが何だろうがこの問題は改善しない(はず)。

しばらくこの構成でつなぎつつ、OS側が何とかしてくれるのを期待したい。
結局、OS側ではなんともならずDP時代にはTESmartのDP切替器を使ってそうな気もする。

2021/09/19(日)pycronが動かない

2021/09/19 25:39 PC(全般)
Windowsのタスクスケジューラにどうも馴染めないので、pycronというWindows用cron実装ソフトを利用している。

システムをクリーンインストールした際に、pycronが動かなくなってしまったのでその解決方法についてメモ。

なお、かなり古いソフトで公式サイトも消失している。
同名のソフトやライブラリもあって検索がまともに機能しない。
なかなかに厳しい。

問題は、pycronを標準のc:\Program Files (x86)\pycronなどにインストールした場合に発生する。
サービスとして起動したpycronが一切のタスクを実行しない。

原因はpycronサービスがcron設定ファイルcrontab.txtを読めていないから。
これはWindowsのVirtualStore機能でハマりが発生している。

pycronは、付属のcrontab.txt Editor(GUIのcrontab編集ツール)でcrontab.txtを作ったり、スクリプトのテスト実行ができる。
便利なのだが、これが問題を起こす。

crontab.txt Editorを非管理者権限で動かして設定を保存すると、自動的にWindowsのVirtualStore機能が働き、

C:\Users\********\AppData\Local\VirtualStore\Program Files (x86)\pycron

にcrontab.txtを出力する。

しかし、pycronはデフォルトで、pycron.exeと同一ディレクトリにcrontab.txtが存在する想定で動く。

サービスとして動くpycron.exeはローカルシステムアカウント(SYSTEM)で動くため、
c:\Program Files (x86)\pycron\crontab.txt
を直接読みに行き、crontab.txtが存在しないので動かないという事象になる。

混乱を招くポイントとして、非管理者権限で動かしたファイラーなどからは、
c:\Program Files (x86)\pycron
の中身は、実際のディレクトリ内容にVirtualStoreディレクトリの内容を上書きしたような状態で表示される。
同一名ファイルが有る場合でも、VirtualStore側が優先ということ。

ログファイルについても、crontab.txt Editorでテスト実行をするとまずVirtualStore側にpycron.logが出力される。
サービスのpycron.exeはc:\Program Files (x86)\pycron\pycron.logを出力するので、
手順と閲覧方法によってはVirtualStore側に隠されてしまってサービスのログファイルが見えないことになったりする。

なお、管理者権限で動かしたプログラムからは、素のc:\Program Files (x86)\pycronの内容が見える。

解決手段は下記どれか。
a) Program Files (x86)以外にインストールする
b) crontab.txt Editorを管理者権限で実行して、c:\Program Files (x86)\pycronにcrontab.txtを出力する
c) pycron.cfgを書いて、VirtualStore側のcrontab.txtを読むようにする
d) crontab.txtを手動でVirtualStore側からc:\Program Files (x86)\pycronにコピー

ただ、a)以外は、crontab.txt Editorの実行権限を間違えるたびにトラブルになりそうなのがなんとも嫌。
なので、a)案を採用した。

タスクスケジューラはcronと比べUIや設定オプションの煩雑さの割に、スケジュールの細かいコントロールができないのが不満。
WSLを使えばよいのだろうが、こっちはこっちでHyper-Vとサードパーティ仮想化ソフトの共存が難しい問題がある。
VMWareやVirtualBox資産が結構あるので切り替えが難しい。

ここらへんあと2,3年の技術進化で共存なり寄せなりスッキリできるようになるんじゃないかと期待してるけど、
今のところはpycronで凌ぐ方向で。

2020/12/23(水)Windows10 で拡張子に対して関連付けができない

2020/12/23 26:28 PC(全般)
Windows10 で関連付けができない問題。

環境はWindows10 Pro 1803 17134.471

設定→アプリ→規定のアプリからデフォルトアプリケーションを変えようとすると、指定後に設定画面が若干フリーズして消える。

再度設定画面を開いてもデフォルトアプリケーションが変わっていない。

下記にあるように、WindowsのKBのバグらしい。

既定のアプリが変更できないのはKBの不具合だった
Windows 10:"KB4462919"適用後、規定のアプリの変更ができなくなる - Microsoft コミュニティ

ということでWindows Updateが簡単な解決方法になりそうだが、当該PCは現在Windows Updateができない別の問題にあたっておりWindows UpdateがNG。

それ以外の方法でとりあえず解決したい。

今回のケースの場合、関連付けを変えたい拡張子のファイルに対して、

コンテキストメニュー(右クリック)→プログラムから開く→別のプログラムを選択→「このファイルを開く方法を選んでください」ダイアログで「常にこのアプリを使って.~ファイルを開く」にチェックを入れて、開きたいプログラムをクリック

で、とりあえず変更できた。

なお、当該情報はレジストリに保存されているらしい。

例としてMP4ファイルであれば、

\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.mp4\UserChoice

になる(直接書き換える手もあり)。
このあたりを確認しながら設定をすれば良さそうだ。

2020/12/06(日)Thunderbirdでメール中のリンクが開かない

2020/12/06 20:07 PC(全般)
メーラーは未だにThunderbirdを使っているが、どこかのタイミングからメール中のリンクが下記メッセージで開かなくなった。

「このファイルには、この操作を実行するように関連付けられたアプリがありません。アプリをインストールするか、または既にインストールされている場合は、規定のアプリの設定ページで関連付けを作成してください。」

セキュリティ関連かと思ったが流石に不便。
開けるように改善したい。

Thunderbird - ふなWiki

にあるように、一度オプション
network.protocol-handler.warn-external.https
network.protocol-handler.warn-external.http
をtrueに設定すると、リンククリック時にブラウザを選択できる。
以降はリンクがブラウザで開くようになる。

デフォルトブラウザを設定したら、上のオプションはfalseに戻して良いようだ。

2020/11/03(火)Windows8.1でWindows Defenderが有効にできない

2020/11/03 16:12 PC(全般)
Windows8.1でWindows Defenderが有効にできない問題。

環境はWindows8.1 64bit。

長期に渡ってAvira Antivirusを入れて使っていたPCで、Aviraが暴走するケースが増えてきたのでAvira→Windows Defenderに切り替えようとした。

Aviraのアンインストールにも一苦労したのだが、アンインストール後でもWindows Defenderが有効にできない。

Windows Defenderを起動しようとすると、
「このアプリは無効になっており、コンピューターを監視していません。」
とダイアログが出て進まない。

【Windows 8.1】 マカフィーを削除後、 Windows Defender - マイクロソフト コミュニティ

が該当しそうに見えるのだが、どこからたどっても状況は同じ。
解決しない。

日本語answers.microsoft.comはノイジーである。

結局、下記手順を順に試して、
Problems starting Windows Defender in Windows 8/8.1/10 - Microsoft Community

「8. Enabling Windows Defender from Group Policy」
でやっと解決。

HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender
キーがなかったので作成し、DWORD値で
DisableAntiSpyware:0
を作成したらWindows Defenderの画面が開くようになった。

その後は定義更新したら素直に監視状態までこぎつけた。

Windows8→8.1の過程でキーが作られなかったりしたのだろうか。謎。

Windows8.1自体がもうあまり寿命がないが、とりあえずこれで凌げそうである。

2020/05/04(月)TodoistでGoogle日本語入力が使えない

2020/05/04 18:47 PC(全般)
Todoistのタスク入力欄で、Google日本語入力での日本語入力が有効にならない問題。
環境はWindows10 64bit。Todoistはストアアプリ版。

OneNoteでGoogle日本語入力の半角全角を切り替えられない問題【ALL APPLICATION PACKAGE】 | umashii blog
にあるように、
C:\Program Files (x86)\Google\Google Japanese Input
に、
ALL APPLICATION PACKAGE
アクセス権を付与すると解消した。

2020/03/20(金)コード: 0x80D02017でMicrosoft ストアアプリがインストールできない

2020/03/20 27:24 PC(全般)
Wunderlistからの乗り換え用としてMicrosoft To-DoをMicrosoft Storeでインストールしようとした際に、表題の現象が発生。

環境はWindows10 x64 Pro 1803。

インストールが完了せずループしてしまう。
エラー詳細は以下の内容。
予期しない問題が発生しました
~~
コード: 0x80D02017
自分の場合、ネットワークアダプタのIPv6オプションを無効にしたらインストールが正常完了した。

うーんルータが自作だからIPv6のルーティングがおかしいのかな?

とはいえストアアプリの挙動もあまりよろしくない気がする。

[参考]
ストアアプリ エラー インストールできず - マイクロソフト コミュニティ
IPV

2019/11/11(月)特定のドライブ・フォルダを開いているプロセスの特定

2019/11/11 26:16 PC(全般)
毎回忘れてしまうのでメモ。

ドライブの取り出しする際に使ってるプロセスが~と言われる系の対処。
環境はWindows10。

タスクマネージャ→パフォーマンスタブ→リソースモニターを開くでパフォーマンスモニターを起動
パフォーマンスモニターのCPUタブ→関連付けられたハンドルのハンドルの検索でドライブ名ないしフォルダ名を検索

リソースモニターの起動はコマンドプロンプトから
perfmon.exe /res
でも良い。

最近だと書き込みをしてない限り直で抜いてもいいっぽいんだけど、「書き込みをしてない限り」のとこ悩ましい。

2019/10/18(金)ASUS Xonar U7の点滅死問題

2019/10/18 24:22 PC(全般)
ASUS Xonar U7には、BLINK OF DEATH不具合が頻発しているらしい。
これが発症するとOSから認識せずSpeaker LEDが点滅しっ放しになる。

多分コンデンサ周りの不具合。

この状態からの(根本的でない)復旧方法があるのでメモ。

(286) Xonar U7 - BLINKING LED ISSUES - YouTube

ここのトップコメントにある通り、
USBチャージャーにつなぐ→ヘアドライヤーでUSB端子付近を20秒加熱→1分放置→PCに繋ぎ替え

で、自分のXonar U7も復活した。

一旦はコレでいいとして、再発したらXonar U7 MkⅡを買うのかなあ。

USBバスパワーで動いて、ヘッドホン端子とアナログ端子があるUSBサウンドデバイスはなかなかない。ヘッドホン端子が表側、USBとアナログ端子が裏側という配置も用途にジャストなのでできればこのまま使っていきたい。

<2019/11/10追記>
上記で直らない場合、蓋を開けてX2とプリントされている横のコンデンサを触れば直る(USB端子近辺)。蓋を開けるには底面ゴム足4箇所剥がして普通のミリネジ。

2019/02/17(日)Raspberry Pi Zero WからUSBケーブル1本でWindows PCをwakeupする(後編)

2019/02/22 14:59 PC(全般)
Raspberry Pi Zero WからUSBケーブル1本でWindows PCをwakeupする(前編) - 色々日記(ざ・めも)の続き。

前回の記事で、Raspberry Pi Zero Wによる無線LAN経由のPCのwakeup(WOL)までは実現できた。

■課題
さて、これには2つ課題があった。

・USB-有線LAN変換アダプタの電力消費
 100BASEでも0.2W~1.2W。

・電源系と有線LANでRas Piからのケーブルが2本必要
 実際に繋いでみると変換ケーブルやらアダプタでかなり邪魔くさい

今は殆どのM/Bはスタンバイ時にUSBから電源供給ができるわけだし、OTGならデータも通るはずなのでUSBケーブル1本でできる構成を考えたい。

つまり、こう。
+----------+
|    PC    |
|  Windows |
+-----+----+
 USB  |
      |                  +-------------+
      +------------------+Ras Pi Zero W|
                 OTG Port|             |
                         +-------------+

■考察
考えた結果、Ras Pi Zero WがUSBの有線キーボード(マウス)と同じ振る舞いができればこれが達成できるという結論になった。

USBキーボードでPCをwakeupさせるのと同じイメージになる。

■HIDキーボード化&設定
ということで、下記参考サイト通りにRas Pi Zero WをUSB HIDキーボード化した。
Raspberry Pi Zero (W) をUSBキーボード(USBガジェット)にする - Qiita

他にもこことかが参考になる。
Raspberry Piを遠隔入力キーボードにする - とうふ荘の手記てき!

今回は文字列そのものを送信したいわけではないので、hardpass-sendHIDは不要。

で、wakeupさせたいので、bmAttributesをいじる必要がある。
$ sudo echo 0xa0 > /sys/kernel/config/usb_gadget/isticktoit/configs/c.1/bmAttributes
とする(初期値は0x80)。

[参考]
コンフィグレーションディスクリプタ - おなかすいたWiki!

USBからのwakeupはWOLのときと同様で、Windowsがサスペンドに入る際にそのポートからのwakeupを受け付ける状態にする必要がある模様。

・bmAttributesでbit 5が立っている
・Windowsのデバイスマネージャで「このデバイスでコンピュータのスタンバイ状態を解除できるようにする」にチェック

が必要条件。

また、当たり前だがこの構成だとサスペンド時にRaspberry Pi Zero Wに電源が供給されている必要がある。電源の設定で「USBセレクティブサスペンド」はOFFにしておく必要がありそう。

■ソース読んでパッチ当て
しかし、これだけではスタンバイ時にキーを送ってもホストPCはwakeupしなかった。

ここ、根本的に勘違いしていたのだが、そもそもUSBの場合はホスト側がデバイス側にキー情報を取りに来る。

当然サスペンド中には取りに来ないので、キーをバッファに貯めるだけではだめ。
wakeupさせる場合、USBの規格にあるwakeup用の信号パターンを送る必要がある。

昔BIOSとかで特定キーによるwakeup設定が存在していてそれが頭の中にあったのだが、PS/2限定だったのかも。追求していない。

[参考]
デカスギ電源ボタン - PukiWiki
↑Arduinoでやりたいことを実現できている!
USB Made Simple - Part 3
USB1.1仕様書

USBキーボードの実装では、USBポートの信号状態をチェック→ホストがサスペンドの場合はwakeup特殊信号を送る、と理解した。

もちろんGPIOとかを介せばやれることはわかるのだが、標準のUSBポートでやりたい。
だが、ガジェットHIDからwakeupを呼ぶ機能が見つからない。

しょうがないのでソースを読むところから。

Raspberry Piでカーネルをカスタムして構築 - karaage. [からあげ]
Kernel building - Raspberry Pi Documentation

を参考に、カーネルソースを取得する。

まずはgit, bc入れるところから。
$ sudo apt-get install git bc
ディレクトリ作って
$ cd ~
$ mkdir kernel
$ cd kernel
カーネル落とした。
この時点では、60feca6ea3fabfe870a731a48c254c6ca1593e0d(rpi-4.14.yブランチ)。
$ git clone --depth=1 https://github.com/raspberrypi/linux
最初に、config作ってやる。Pi Zero Wなので、bcmrpi_defconfigの方。
cd linux
KERNEL=kernel
make bcmrpi_defconfig

で、ソースはココらへんを読めば良いらしい。
$ lsmod
~~
udc_core               38945  3 dwc2,libcomposite,usb_f_hid
~~
ソースを読むと、hidg0に値を書き込んだときの動きは、HIDドライバ->gadget共通ドライバ->dwc2(DesignWaveのUSB2かな)ドライバだと思う。

結論から言うと、
・HIDドライバに実装がないし送る方法もなさそう
・gadget共通ドライバにはI/Fがある
・dwc2のガジェット部に実装がない

今回はキーを送る必要はなくwakeup専用デバイスにしたいので、WRITEする箇所で必ずwakeupを送る動きに変えてしまうことにする。

パッチを当てる。
当てる必要があるのは2ファイル。

HIDドライバのWRITE時に、gadgetドライバのAPIを呼び出す
--- ~/kernel/linux/drivers/usb/gadget/function/f_hid.c.orig        2019-02-17 13:21:42.845143807 +0000
+++ ~/kernel/linux/drivers/usb/gadget/function/f_hid.c     2019-02-17 13:26:25.554009039 +0000
@@ -339,10 +339,14 @@
                            size_t count, loff_t *offp)
 {
        struct f_hidg *hidg  = file->private_data;
+       struct usb_composite_dev *cdev = hidg->func.config->cdev;
        struct usb_request *req;
        unsigned long flags;
        ssize_t status = -ENOMEM;

+       usb_gadget_wakeup(cdev->gadget);
+       pr_info("Wakeup!\n");
+
        if (!access_ok(VERIFY_READ, buffer, count))
                return -EFAULT;

dwc2ドライバのガジェット部にwakeup用の関数を追加してポインタにセット。
実装はLinux本家のdwc2_gadget_exit_hibernation()を参考にした。
https://github.com/torvalds/linux/blob/master/drivers/usb/dwc2/gadget.c
--- /home/pi/kernel/linux/drivers/usb/dwc2/gadget.c.orig        2019-02-09 06:00:07.027365942 +0000
+++ /home/pi/kernel/linux/drivers/usb/dwc2/gadget.c     2019-02-17 13:37:07.611431738 +0000
@@ -4445,6 +4445,30 @@
        return usb_phy_set_power(hsotg->uphy, mA);
 }

+static int dwc2_hsotg_wakeup(struct usb_gadget *gadget)
+{
+       u32 dctl;
+       struct dwc2_dregs_backup *dr;
+        struct dwc2_hsotg *dev;
+        unsigned long flags;
+        dev = container_of(gadget, struct dwc2_hsotg, gadget);
+       dr = &dev->dr_backup;
+
+       spin_lock_irqsave(&dev->lock, flags);
+       udelay(10);
+
+       /* Start Remote Wakeup Signaling */
+       dwc2_writel(dr->dctl | DCTL_RMTWKUPSIG, dev->regs + DCTL);
+       mdelay(12);
+       dctl = dwc2_readl(dev->regs + DCTL);
+       dctl &= ~DCTL_RMTWKUPSIG;
+       dwc2_writel(dctl, dev->regs + DCTL);
+
+       spin_unlock_irqrestore(&dev->lock, flags);
+
+       return 0;
+}
+
 static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = {
        .get_frame      = dwc2_hsotg_gadget_getframe,
        .udc_start              = dwc2_hsotg_udc_start,
@@ -4452,6 +4476,7 @@
        .pullup                 = dwc2_hsotg_pullup,
        .vbus_session           = dwc2_hsotg_vbus_session,
        .vbus_draw              = dwc2_hsotg_vbus_draw,
+       .wakeup                 = dwc2_hsotg_wakeup,
 };

 /**
多少ソースが変わっても再パッチは簡単そう。

■ソースメモ
なお、usb_gadget_wakeup()の実装はdrivers/usb/gadget/udc/core.cで、usb_gadget_opsのwakeupを呼んでいる。

で呼び出し側の実体はチップごとのドライバ(Ras Pi Zero Wの場合dwc2配下のgadget.c)にあるということのようだ。

読むには、
~/kernel/linux/include/linux/usb/gadget.h
と、
~/kernel/linux/drivers/usb/dwc2/hw.h
あたりに定義がたくさんあるので参照しながら進める必要がある。

■カーネル再構築
パッチを当てたら、カーネル作り直す。
$ make -j4 zImage modules dtbs
$ sudo make modules_install
$ sudo cp arch/arm/boot/dts/*.dtb /boot/
$ sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
$ sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
$ sudo cp arch/arm/boot/zImage /boot/$KERNEL.img
最後にリブート
$ sudo reboot

■カーネル再構築(部分コンパイルする場合)
今回、カーネルまるごと作り直したけど、Pi Zeroだと一晩かかるので部分モジュールコンパイルのほうが良かったかも。

部分コンパイルする場合は、インストールされているカーネルのバージョン調べてダウンロードした後、パッチ当てて、
$ cd ~/kernel/linux
$ make drivers/usb/gadget/function/usb_f_hid.ko
$ make drivers/usb/dwc2/dwc2.ko
でパッチ対象モジュールを作れる。
あとは手動コピーで良いはず(試してない)。

■使い方
これで、次回起動後、
$ echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0
でwakeupがホストに行くようになる(hidg0の権限次第でsudoが必要)。

前編で作ったauthorized_keysの中のコマンドをこれに差し替えることで、同じ手順でUSB wakeupができるようになった。

キーを送る機能と共存できるかは試していないので、そこは各自研究してほしい。

なお、wakeup時に/var/log/messagesに"Wakeup!"が出力される。

気に食わない人は、f_hid.cパッチのpr_infoをコメントアウトなり好きなメッセージに変えること。
うまく動かないときは、dwc2側にもpr_infoを追加して動きを確認していくといいかもしれない。

なお、くれぐれもセキュリティにはご用心&自己責任で。

■おまけ
wakeup周りの実装がないのは多分Linuxから呼ぶIFが決まってないからだと思う。

今回、規格書見た段階ではD+ D-をちょろっと動かせばいいだけだと思ってなめてかかったのだが、dwc2ドライバの中身的にはもう一段階抽象化されていてチップのレジスタに何セットして送るの世界だった。

これだとデータシートかオシロがないと厳しい。
実装するにしても、既存ソースの識別子名から勘でトライ&エラーになってしまう。

データシートについては、個人では入手できないぽいとのこと。
Linux本家にwakeup絡みの実装が見つからなかったら取りあえず動くところまでたどり着けてなかったと思う。

何はともあれ動いてよかった。

動いちゃいるけど正しい信号の動きじゃないのでは疑惑が強いので、知見がある人がいたらツッコんでほしい。
OK キャンセル 確認 その他