BitVisor の AHCI ドライバを Windows10 で動かす

# TL;DR

BitVisor のメーリングリストで質問させていただいた BitVisor の AHCI ドライバが Windows 10 で使えない問題を解決できましたのでご報告します。うちではちゃんと動いているんだけど...という方がいらっしゃいましたら環境依存の問題かもしれませんのでご容赦ください ;_;

# 症状

defconfig で ahci を有効にして Windows 10 を起動すると途中で停止する。

# 環境

マザーボードは ASRock H110M-HDV、SSD は Crucial CT250MX500SSD1、Windows 10 Enterprise

# 原因

学生のB君と一緒に原因を探したところ、以下に原因があるらしいことがわかりました。

- ahci_cmd_start 関数の ASSERT (totalsize <= 4 * 1024 * 1024); で止まっていた模様、ここをコメントアウトすると Windows 7 は動いた（prdt[0] のサイズを超えるのでおかしな動きをする可能性が高いが Windows 7 だと一応起動する）。Windows 10 はコメントアウトしても起動途中でとまった。
- command_table 構造体のメンバ prdt[1] の要素数が 1 となっているが、実際のリクエストでは要素数が 1 ではないことがあるらしく、そこで止まっていたらしい（実際に調べたら 32 とかもあったりした）。
- ahci_handle_cmd_invalid 関数で、cfis->fis_0x27.command の値が Trusted Computing 関係のコマンドのときに panic する（知らないコマンドは panic するようにちゃんと実装されている）。

# 解決策

とりあえず以下の処理を追加したところ Windows10 でも起動できるようになり、クリスタルベンチ（ベンチマーク）が動くくらいに安定して動いています。適当な直し方なので無保証です。

## command_table 構造体

prdt[1] だったのを prdt[64] など、大きくしました。

## ahci_handle_cmd_invalid 関数

Trusted Computing 関係のコマンド 0x5c, 0x5d, 0x5e, 0x5f を処理（というより無視）するようにしました。怪しい解決策ですみません。ちゃんとした目的では使わないように...。

詳細は以下の 7.52 TRUSTED RECEIVE – 5Ch, PIO Data-In あたりと思われます。
https://people.freebsd.org/~imp/asiabsdcon2015/works/d2161r5-ATAATAPI_Command_Set_-_3.pdf

## ahci_cmd_start  関数

問題の ASSERT の処理を変更し、prdt の最大要素数（prdtl）だけチェックするようにしました。また、prdt[1] だけで処理していたのを、複数の prdt[x] で処理するように変更しました。

# パッチ
2018年ころの ahci.c へのパッチになってます（"const struct mm_as *as_dma;" を使ってない古いソース）。それ以外は同じなので、最新の ahci.c にも手作業で移植すれば動くことを確認してます。

```
--- ahci.c.orig	2021-12-17 12:34:51.000000000 +0900
+++ ahci.c.new	2021-12-17 12:34:51.000000000 +0900
@@ -39,6 +39,12 @@
 #include "ata_cmd.h"
 #include "packet.h"
 
+// AHCI driver improvement for Windows 10
+#define AHCI_DRIVER_WITH_EXTENDED_PRDT
+#ifdef AHCI_DRIVER_WITH_EXTENDED_PRDT
+#define PRDTL_MAX 64
+#endif //AHCI_DRIVER_WITH_EXTENDED_PRDT
+
 #define NUM_OF_AHCI_PORTS	32
 #define PxCMD_ST_BIT		1
 #define PxCMD_FRE_BIT		0x10
@@ -139,7 +145,12 @@ struct command_table {
 	union cmdfis cfis;	/* Command FIS */
 	u8 acmd[16];		/* ATAPI Command */
 	u8 reserved[48];
+#ifdef AHCI_DRIVER_WITH_EXTENDED_PRDT
+	struct prdtbl prdt[PRDTL_MAX];	/* Physical Region Descriptor Table */
+#else
 	struct prdtbl prdt[1];	/* Physical Region Descriptor Table */
+#endif //AHCI_DRIVER_WITH_EXTENDED_PRDT
+
 } __attribute__ ((packed));
 
 struct command_header {
@@ -644,7 +655,17 @@ ahci_handle_cmd_invalid (struct ahci_dat
 {
 	port->my[cmdhdr_index].dmabuf_rwflag = 0;
 	port->my[cmdhdr_index].dmabuf_identify = IDENTIFY_NONE;
+#ifdef AHCI_DRIVER_WITH_EXTENDED_PRDT
+    int temp = cfis->fis_0x27.command;
+	// Ignore trusted computing ATA commands
+    if(temp == 0x5c || temp == 0x5d || temp == 0x5e || temp == 0x5f) {
+       printf("AHCI: Trusted computing ATA command ignored (0x%02X)\n",cfis->fis_0x27.command);
+    } else{
+       panic ("AHCI: Invalid ATA command 0x%02X", cfis->fis_0x27.command);
+    }
+#else
 	panic ("AHCI: Invalid ATA command 0x%02X", cfis->fis_0x27.command);
+#endif //AHCI_DRIVER_WITH_EXTENDED_PRDT
 }
 
 static void
@@ -969,7 +990,16 @@ ahci_cmd_start (struct ahci_data *ad, st
 			cmdtbl = mapmem_gphys (ctphys, cmdtbl_size (prdtl),
 					       MAPMEM_WRITE);
 			totalsize = ahci_get_dmalen (cmdtbl, prdtl, &intrflag);
+
+#ifdef AHCI_DRIVER_WITH_EXTENDED_PRDT
+			ASSERT (prdtl < PRDTL_MAX + 1);
+#else
+			// Original assertion
+			// If you did not use AHCI_DRIVER_WITH_EXTENDED_PRDT (improved AHCI 
+			// driver) on Windows 10 or later, you need to comment out the following assetion.
+			// Gurst AHCI driver on Windows 10 generates requests larger than 4 MiB.
 			ASSERT (totalsize <= 4 * 1024 * 1024);
+#endif //AHCI_DRIVER_WITH_EXTENDED_PRDT
 			if (pt->my[i].dmabuf != NULL)
 				panic ("pt->my[i].dmabuf=%p is not NULL!",
 				       pt->my[i].dmabuf);
@@ -980,6 +1010,26 @@ ahci_cmd_start (struct ahci_data *ad, st
 			pt->mycmdlist->cmdhdr[i].ctbau =
 				pt->my[i].cmdtbl_p >> 32;
 			memcpy (pt->my[i].cmdtbl, cmdtbl, 0x80);
+#ifdef AHCI_DRIVER_WITH_EXTENDED_PRDT
+			int dbc_next = 0;
+			int j;
+			for(j = 0;j < prdtl;j++) {
+        		int dbc_temp ;
+		        dbc_temp = cmdtbl->prdt[j].dbc;
+        		pt->my[i].cmdtbl->prdt[j].dba = pt->my[i].dmabuf_p + dbc_next;
+        		pt->my[i].cmdtbl->prdt[j].dbau = (pt->my[i].dmabuf_p + dbc_next) >> 32;
+        		dbc_next += ( dbc_temp + 1 );
+        		pt->my[i].cmdtbl->prdt[j].reserved1 = 0;
+        		pt->my[i].cmdtbl->prdt[j].reserved2 = 0;
+        		pt->my[i].cmdtbl->prdt[j].dbc = dbc_temp;
+        		if (j == prdtl){
+                	pt->my[i].cmdtbl->prdt[j].i = intrflag; //OR cmdtbl->prdt[j].i;
+        		} else {
+                	pt->my[i].cmdtbl->prdt[j].i = 0;
+        		}
+			}
+			pt->mycmdlist->cmdhdr[i].prdtl = prdtl;
+#else //not AHCI_DRIVER_WITH_EXTENDED_PRDT
 			pt->my[i].cmdtbl->prdt[0].dba = pt->my[i].dmabuf_p;
 			pt->my[i].cmdtbl->prdt[0].dbau =
 				pt->my[i].dmabuf_p >> 32;
@@ -988,6 +1038,8 @@ ahci_cmd_start (struct ahci_data *ad, st
 			pt->my[i].cmdtbl->prdt[0].dbc = (totalsize - 2) | 1;
 			pt->my[i].cmdtbl->prdt[0].i = intrflag;
 			pt->mycmdlist->cmdhdr[i].prdtl = 1;
+#endif //AHCI_DRIVER_WITH_EXTENDED_PRDT
+
 			if (pt->mycmdlist->cmdhdr[i].w) /* write */
 				ahci_copy_dmabuf (pt, i, true, cmdtbl, prdtl);
 			ahci_cmd_prehook (ad, pt, i);
```

# 参考文献

https://wiki.osdev.org/AHCI

https://www.intel.co.jp/content/www/jp/ja/io/serial-ata/serial-ata-ahci-spec-rev1-3-1.html