├── 01.md ├── 02.md ├── 03.md ├── 04.md ├── 05.md ├── 06.md ├── LICENSE └── README.md /01.md: -------------------------------------------------------------------------------- 1 | # 1. 導入 2 | 3 | ## 1.1. xv6について 4 | 5 | xv6は、UNIX Version 6 (V6)をIntel x86アーキテクチャ向けにANSI Cで再実装したもので、MIT(マサチューセッツ工科大学)の「6.828 Operating System Engineering」コースのために開発されたマルチプロセッサ対応の教育用OSです。 6 | 7 | - MIT 6.828 (2018): https://pdos.csail.mit.edu/6.828/2018/xv6.html 8 | - 解説テキスト: https://pdos.csail.mit.edu/6.828/2018/xv6/book-rev11.pdf 9 | 10 | なお、x86向けの開発は既に終了しており、現在はRISC-V向けの「xv6-riscv」に置き換えられています。 11 | 12 | - MIT 6.1810 (2023): https://pdos.csail.mit.edu/6.1810/2023/xv6.html 13 | - 解説テキスト: https://pdos.csail.mit.edu/6.1810/2023/xv6/book-riscv-rev3.pdf 14 | 15 | 本講義ではx86向けのxv6を対象にしています。実機での動作もサポートしていますが、CPUエミュレータのQEMUを利用して手軽に開発できます。 16 | 17 | ## 1.2. コードの取得 18 | 19 | xv6のコードはGitHubで公開されています。 20 | 21 | + xv6-public: https://github.com/mit-pdos/xv6-public 22 | 23 | まず、各自の開発環境(Linux)にxv6のコードを取得します。`$WORKDIR`には各自の環境に合わせてコードを保存するディレクトリを指定してください。 24 | 25 | ``` 26 | $ export WORKDIR=/path/to/dir 27 | $ mkdir -p $WORKDIR 28 | $ git clone git@github.com:mit-pdos/xv6-public.git $WORKDIR 29 | $ cd $WORKDIR 30 | ``` 31 | 32 | この先の作業は全て`$WOKDIR`の中で実施します。 33 | 34 | ## 1.3. ビルド 35 | 36 | xv6はビルドツールに「Make」を採用しています。ビルドのルールは`Makefile`にあらかじめ記述されているので、`make`コマンドを実行するだけでビルドが始まります。 37 | 38 | ``` 39 | $ make 40 | ``` 41 | 42 | ビルドに成功すると、起動用のディスクイメージ(`xv6.img`)が生成されます。 43 | 44 | ``` 45 | $ ls -l xv6.img 46 | -rw-rw-r-- 1 pandax381 pandax381 5120000 8月 3 02:19 xv6.img 47 | ``` 48 | 49 | > NOTE: 推奨環境のUbuntuでは、`build-essential`のパッケージがインストールされていないと`make`コマンドやコンパイラの`gcc`などが見つからずエラーとなります。その場合は`apt install`でパッケージをインストール後、あらためて`make`コマンドでビルドを実行してください。 50 | 51 | ## 1.4. xv6の起動 52 | 53 | QEMUを利用してxv6を起動します。`Makefile`の中にQEMUのパラメータ等を指定したターゲット(`qemu-nox`)が用意されているので、これを指定して`make`コマンドを実行します。 54 | 55 | ``` 56 | $ make qemu-nox 57 | ``` 58 | 59 | CUI版のQEMUが起動し、ディスクイメージからxv6がブートします。 60 | 61 | ``` 62 | SeaBIOS (version 1.15.0-1) 63 | 64 | 65 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 66 | 67 | 68 | 69 | Booting from Hard Disk..xv6... 70 | 2024/08/02 15:07:21 71 | cpu0: starting 0 72 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 73 | init: starting sh 74 | $ 75 | ``` 76 | 77 | ログイン認証はなく、そのままコンソールでシェルが起動します。xv6のシェルは最低限の機能しか備えていないため、TABキーによる入力補完はできません。`ls`コマンドを実行すると実行可能なバイナリ等が確認できます。 78 | 79 | ``` 80 | $ ls 81 | . 1 1 512 82 | .. 1 1 512 83 | README 2 2 2286 84 | cat 2 3 15584 85 | echo 2 4 14468 86 | forktest 2 5 8892 87 | grep 2 6 18428 88 | init 2 7 15088 89 | kill 2 8 14552 90 | ln 2 9 14448 91 | ls 2 10 17016 92 | mkdir 2 11 14576 93 | rm 2 12 14556 94 | sh 2 13 28620 95 | stressfs 2 14 15484 96 | usertests 2 15 62968 97 | wc 2 16 16012 98 | zombie 2 17 14140 99 | console 3 18 0 100 | $ 101 | ``` 102 | 103 | ## 1.5. QEMUモニタ 104 | 105 | xv6のコンソールで`Ctrl+A`に続けて`C`を入力(以降、`Ctrl+A C`と表記)するとQEMUモニタに切り替わります。 106 | 107 | ``` 108 | $ QEMU 6.2.0 monitor - type 'help' for more information 109 | (qemu) 110 | ``` 111 | 112 | QEMUモニタでは、レジスタやメモリの内容を確認できたりデバッグに役立つ機能が盛り沢山です。 113 | 114 | - QEMUモニタのヘルプ: https://qemu-project.gitlab.io/qemu/system/monitor.html 115 | 116 | 再度`Ctrl+A C`を入力するとQEMUモニタからxv6のコンソールに切り替わります(トグル操作)。 117 | 118 | ## 1.6. xv6の終了 119 | 120 | xv6にはシステム終了のためのコマンドが存在しません。コンソールで`Ctrl+A X`を入力するか、QEMUモニタで`quit`を実行してQEMUを終了させます。 121 | 122 | ## 1.7. 再ビルド 123 | 124 | 今後、再ビルドを行う際には次の手順で実施してください。 125 | 126 | ``` 127 | $ make clean 128 | $ make 129 | ``` 130 | 131 | `make clean`は、ビルドで生成したオブジェクトファイル等を削除するために用意されているターゲットです。一部ファイルの変更が反映されない等の無用なトラブルを避けるためにも、必ず`make clean`でクリーンな状態にした後にビルドを実施することを推奨します。 132 | -------------------------------------------------------------------------------- /02.md: -------------------------------------------------------------------------------- 1 | # 2. 下準備 2 | 3 | ## 2.1. 型定義の追加 4 | 5 | ビット幅指定の数値型などの型定義を追加しておきます。xv6では型定義を`types.h`に集約しているので、このファイルに追加します。 6 | 7 |
8 | types.h 9 | 10 | ```diff 11 | typedef unsigned int uint; 12 | typedef unsigned short ushort; 13 | typedef unsigned char uchar; 14 | typedef uint pde_t; 15 | + 16 | +#ifndef BUILD_MKFS 17 | + 18 | +#ifndef NULL 19 | +#define NULL ((void *)0) 20 | +#endif 21 | + 22 | +typedef char int8_t; 23 | +typedef unsigned char uint8_t; 24 | +typedef short int16_t; 25 | +typedef unsigned short uint16_t; 26 | +typedef int int32_t; 27 | +typedef unsigned int uint32_t; 28 | +typedef long long int64_t; 29 | +typedef unsigned long long uint64_t; 30 | + 31 | +typedef int32_t intptr_t; 32 | +typedef uint32_t uintptr_t; 33 | + 34 | +typedef int32_t ssize_t; 35 | +typedef uint32_t size_t; 36 | + 37 | +typedef __builtin_va_list va_list; 38 | + 39 | +#endif 40 | ``` 41 |
42 | 43 | xv6を起動する`make qemu-nox`を実行した際に、コマンドの実行ファイルなどを格納したディスクイメージ(`fs.img`)を作製するプログラム(`mkfs`)がコンパイルおよび実行されます。この`mkfs`は、xv6ではなくホストで実行するプログラムであるため、コンパイル時にはホスト環境のヘッダファイルが読み込まれます。ホスト環境のヘッダファイルが読み込まれると、ここで追加している型定義との衝突が発生してしまうため、`mkfs`のコンパイル時のみ`BUILD_MKFS`を定義して追加の型定義が読み込まれないようにします。 44 | 45 |
46 | Makefile 47 | 48 | ```diff 49 | ... 50 | 51 | mkfs: mkfs.c fs.h 52 | - gcc -Werror -Wall -o mkfs mkfs.c 53 | + gcc -Werror -Wall -DBUILD_MKFS -o mkfs mkfs.c 54 | 55 | ... 56 | ``` 57 |
58 | 59 | ## 2.2. コンソール出力の改良 60 | 61 | ### 2.2.1. コンソール出力 62 | 63 | xv6のカーネル内からコンソールへ文字列を出力するには`cprintf()`を使用します。 64 | 65 | ```c 66 | cprintf("Hello, world!\n"); 67 | ``` 68 | 69 | 標準ライブラリの`printf()`とよく似ていますが、フォーマット文字列のサポートが限定的です。 70 | 71 | - サポートしている変換指定子は `%d`、`%x`、`%p`、`%s` のみ 72 | - フラグ文字やフィールド幅、精度、長さ修飾子などもサポートしていない 73 | 74 | `cprintf()`のコードは`console.c`に含まれています。 75 | 76 |
77 | console.c 78 | 79 | ```c 80 | // Print to the console. only understands %d, %x, %p, %s. 81 | void 82 | cprintf(char *fmt, ...) 83 | { 84 | int i, c, locking; 85 | uint *argp; 86 | char *s; 87 | 88 | locking = cons.locking; 89 | if(locking) 90 | acquire(&cons.lock); 91 | 92 | if (fmt == 0) 93 | panic("null fmt"); 94 | 95 | argp = (uint*)(void*)(&fmt + 1); 96 | for(i = 0; (c = fmt[i] & 0xff) != 0; i++){ 97 | if(c != '%'){ 98 | consputc(c); 99 | continue; 100 | } 101 | c = fmt[++i] & 0xff; 102 | if(c == 0) 103 | break; 104 | switch(c){ 105 | case 'd': 106 | printint(*argp++, 10, 1); 107 | break; 108 | case 'x': 109 | case 'p': 110 | printint(*argp++, 16, 0); 111 | break; 112 | case 's': 113 | if((s = (char*)*argp++) == 0) 114 | s = "(null)"; 115 | for(; *s; s++) 116 | consputc(*s); 117 | break; 118 | case '%': 119 | consputc('%'); 120 | break; 121 | default: 122 | // Print unknown % sequence to draw attention. 123 | consputc('%'); 124 | consputc(c); 125 | break; 126 | } 127 | } 128 | 129 | if(locking) 130 | release(&cons.lock); 131 | } 132 | ``` 133 |
134 | 135 | ### 2.2.2. 改良版のコンソール出力 136 | 137 | xv6のコンソール出力は簡素すぎるため、より多くのフォーマット文字列をサポートする実装に差し替えます。今回は「JOS」という、xv6と同じくMITの6.828コースで利用されている教育用のOSからコードを拝借します。 138 | 139 | + Lab1 - MIT 6.828 (2018): https://pdos.csail.mit.edu/6.828/2018/labs/lab1/ 140 | + Gitリポジトリ: https://pdos.csail.mit.edu/6.828/2018/jos.git 141 | 142 | 143 | 上記のGitリポジトリから抽出した2つのファイルを、xv6の作業ディレクトリへ追加します。 144 | 145 | > NOTE: 下記のコードをそのままコピー&ペーストしてください。 146 | 147 |
148 | printfmt.c 149 | 150 | ```c 151 | // Stripped-down primitive printf-style formatting routines, 152 | // used in common by printf, sprintf, fprintf, etc. 153 | // This code is also used by both the kernel and user programs. 154 | 155 | #include "types.h" 156 | #include "defs.h" 157 | #include "error.h" 158 | 159 | /* 160 | * Space or zero padding and a field width are supported for the numeric 161 | * formats only. 162 | * 163 | * The special format %e takes an integer error code 164 | * and prints a string describing the error. 165 | * The integer may be positive or negative, 166 | * so that -E_NO_MEM and E_NO_MEM are equivalent. 167 | */ 168 | 169 | static const char * const error_string[MAXERROR] = 170 | { 171 | [E_UNSPECIFIED] = "unspecified error", 172 | [E_BAD_ENV] = "bad environment", 173 | [E_INVAL] = "invalid parameter", 174 | [E_NO_MEM] = "out of memory", 175 | [E_NO_FREE_ENV] = "out of environments", 176 | [E_FAULT] = "segmentation fault", 177 | [E_IPC_NOT_RECV]= "env is not recving", 178 | [E_EOF] = "unexpected end of file", 179 | [E_NO_DISK] = "no free space on disk", 180 | [E_MAX_OPEN] = "too many files are open", 181 | [E_NOT_FOUND] = "file or block not found", 182 | [E_BAD_PATH] = "invalid path", 183 | [E_FILE_EXISTS] = "file already exists", 184 | [E_NOT_EXEC] = "file is not a valid executable", 185 | [E_NOT_SUPP] = "operation not supported", 186 | }; 187 | 188 | /* 189 | * Print a number (base <= 16) in reverse order, 190 | * using specified putch function and associated pointer putdat. 191 | */ 192 | static void 193 | printnum(void (*putch)(int, void*), void *putdat, 194 | unsigned long long num, unsigned base, int width, int padc) 195 | { 196 | // first recursively print all preceding (more significant) digits 197 | if (num >= base) { 198 | printnum(putch, putdat, num / base, base, width - 1, padc); 199 | } else { 200 | // print any needed pad characters before first digit 201 | while (--width > 0) 202 | putch(padc, putdat); 203 | } 204 | 205 | // then print this (the least significant) digit 206 | putch("0123456789abcdef"[num % base], putdat); 207 | } 208 | 209 | // Get an unsigned int of various possible sizes from a varargs list, 210 | // depending on the lflag parameter. 211 | static unsigned long long 212 | getuint(va_list *ap, int lflag) 213 | { 214 | if (lflag >= 2) 215 | return va_arg(*ap, unsigned long long); 216 | else if (lflag) 217 | return va_arg(*ap, unsigned long); 218 | else 219 | return va_arg(*ap, unsigned int); 220 | } 221 | 222 | // Same as getuint but signed - can't use getuint 223 | // because of sign extension 224 | static long long 225 | getint(va_list *ap, int lflag) 226 | { 227 | if (lflag >= 2) 228 | return va_arg(*ap, long long); 229 | else if (lflag) 230 | return va_arg(*ap, long); 231 | else 232 | return va_arg(*ap, int); 233 | } 234 | 235 | 236 | // Main function to format and print a string. 237 | void printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...); 238 | 239 | void 240 | vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) 241 | { 242 | register const char *p; 243 | register int ch, err; 244 | unsigned long long num; 245 | int base, lflag, width, precision, altflag; 246 | char padc; 247 | 248 | while (1) { 249 | while ((ch = *(unsigned char *) fmt++) != '%') { 250 | if (ch == '\0') 251 | return; 252 | putch(ch, putdat); 253 | } 254 | 255 | // Process a %-escape sequence 256 | padc = ' '; 257 | width = -1; 258 | precision = -1; 259 | lflag = 0; 260 | altflag = 0; 261 | reswitch: 262 | switch (ch = *(unsigned char *) fmt++) { 263 | 264 | // flag to pad on the right 265 | case '-': 266 | padc = '-'; 267 | goto reswitch; 268 | 269 | // flag to pad with 0's instead of spaces 270 | case '0': 271 | padc = '0'; 272 | goto reswitch; 273 | 274 | // width field 275 | case '1': 276 | case '2': 277 | case '3': 278 | case '4': 279 | case '5': 280 | case '6': 281 | case '7': 282 | case '8': 283 | case '9': 284 | for (precision = 0; ; ++fmt) { 285 | precision = precision * 10 + ch - '0'; 286 | ch = *fmt; 287 | if (ch < '0' || ch > '9') 288 | break; 289 | } 290 | goto process_precision; 291 | 292 | case '*': 293 | precision = va_arg(ap, int); 294 | goto process_precision; 295 | 296 | case '.': 297 | if (width < 0) 298 | width = 0; 299 | goto reswitch; 300 | 301 | case '#': 302 | altflag = 1; 303 | goto reswitch; 304 | 305 | process_precision: 306 | if (width < 0) 307 | width = precision, precision = -1; 308 | goto reswitch; 309 | 310 | // long flag (doubled for long long) 311 | case 'l': 312 | lflag++; 313 | goto reswitch; 314 | 315 | // character 316 | case 'c': 317 | putch(va_arg(ap, int), putdat); 318 | break; 319 | 320 | // error message 321 | case 'e': 322 | err = va_arg(ap, int); 323 | if (err < 0) 324 | err = -err; 325 | if (err >= MAXERROR || (p = error_string[err]) == NULL) 326 | printfmt(putch, putdat, "error %d", err); 327 | else 328 | printfmt(putch, putdat, "%s", p); 329 | break; 330 | 331 | // string 332 | case 's': 333 | if ((p = va_arg(ap, char *)) == NULL) 334 | p = "(null)"; 335 | if (width > 0 && padc != '-') 336 | for (width -= strnlen(p, precision); width > 0; width--) 337 | putch(padc, putdat); 338 | for (; (ch = *p++) != '\0' && (precision < 0 || --precision >= 0); width--) 339 | if (altflag && (ch < ' ' || ch > '~')) 340 | putch('?', putdat); 341 | else 342 | putch(ch, putdat); 343 | for (; width > 0; width--) 344 | putch(' ', putdat); 345 | break; 346 | 347 | // (signed) decimal 348 | case 'd': 349 | num = getint(&ap, lflag); 350 | if ((long long) num < 0) { 351 | putch('-', putdat); 352 | num = -(long long) num; 353 | } 354 | base = 10; 355 | goto number; 356 | 357 | // unsigned decimal 358 | case 'u': 359 | num = getuint(&ap, lflag); 360 | base = 10; 361 | goto number; 362 | 363 | // (unsigned) octal 364 | case 'o': 365 | // Replace this with your code. 366 | putch('X', putdat); 367 | putch('X', putdat); 368 | putch('X', putdat); 369 | break; 370 | 371 | // pointer 372 | case 'p': 373 | putch('0', putdat); 374 | putch('x', putdat); 375 | num = (unsigned long long) 376 | (uintptr_t) va_arg(ap, void *); 377 | base = 16; 378 | goto number; 379 | 380 | // (unsigned) hexadecimal 381 | case 'x': 382 | num = getuint(&ap, lflag); 383 | base = 16; 384 | number: 385 | printnum(putch, putdat, num, base, width, padc); 386 | break; 387 | 388 | // escaped '%' character 389 | case '%': 390 | putch(ch, putdat); 391 | break; 392 | 393 | // unrecognized escape sequence - just print it literally 394 | default: 395 | putch('%', putdat); 396 | for (fmt--; fmt[-1] != '%'; fmt--) 397 | /* do nothing */; 398 | break; 399 | } 400 | } 401 | } 402 | 403 | void 404 | printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) 405 | { 406 | va_list ap; 407 | 408 | va_start(ap, fmt); 409 | vprintfmt(putch, putdat, fmt, ap); 410 | va_end(ap); 411 | } 412 | 413 | struct sprintbuf { 414 | char *buf; 415 | char *ebuf; 416 | int cnt; 417 | }; 418 | 419 | static void 420 | sprintputch(int ch, struct sprintbuf *b) 421 | { 422 | b->cnt++; 423 | if (b->buf < b->ebuf) 424 | *b->buf++ = ch; 425 | } 426 | 427 | int 428 | vsnprintf(char *buf, int n, const char *fmt, va_list ap) 429 | { 430 | struct sprintbuf b = {buf, buf+n-1, 0}; 431 | 432 | if (buf == NULL || n < 1) 433 | return -E_INVAL; 434 | 435 | // print the string to the buffer 436 | vprintfmt((void*)sprintputch, &b, fmt, ap); 437 | 438 | // null terminate the buffer 439 | *b.buf = '\0'; 440 | 441 | return b.cnt; 442 | } 443 | 444 | int 445 | snprintf(char *buf, int n, const char *fmt, ...) 446 | { 447 | va_list ap; 448 | int rc; 449 | 450 | va_start(ap, fmt); 451 | rc = vsnprintf(buf, n, fmt, ap); 452 | va_end(ap); 453 | 454 | return rc; 455 | } 456 | ``` 457 |
458 | 459 |
460 | error.h 461 | 462 | ```c 463 | /* See COPYRIGHT for copyright information. */ 464 | 465 | #ifndef JOS_INC_ERROR_H 466 | #define JOS_INC_ERROR_H 467 | 468 | enum { 469 | // Kernel error codes -- keep in sync with list in lib/printfmt.c. 470 | E_UNSPECIFIED = 1, // Unspecified or unknown problem 471 | E_BAD_ENV , // Environment doesn't exist or otherwise 472 | // cannot be used in requested action 473 | E_INVAL , // Invalid parameter 474 | E_NO_MEM , // Request failed due to memory shortage 475 | E_NO_FREE_ENV , // Attempt to create a new environment beyond 476 | // the maximum allowed 477 | E_FAULT , // Memory fault 478 | 479 | E_IPC_NOT_RECV , // Attempt to send to env that is not recving 480 | E_EOF , // Unexpected end of file 481 | 482 | // File system error codes -- only seen in user-level 483 | E_NO_DISK , // No free space left on disk 484 | E_MAX_OPEN , // Too many files are open 485 | E_NOT_FOUND , // File or block not found 486 | E_BAD_PATH , // Bad path 487 | E_FILE_EXISTS , // File already exists 488 | E_NOT_EXEC , // File not a valid executable 489 | E_NOT_SUPP , // Operation not supported 490 | 491 | MAXERROR 492 | }; 493 | 494 | #endif // !JOS_INC_ERROR_H */ 495 | ``` 496 | 497 |
498 | 499 | #### ✅ 不足している標準ライブラリ関数の追加 500 | 501 | 上記の`printfmt.c`に含まれる`vprintfmt()`は`strnlen()`を必要としますが、この関数はxv6には含まれていません。文字列関連をまとめている`string.c`へ`strnlen()`を追加します。 502 | 503 |
504 | string.c 505 | 506 | ```diff 507 | ... 508 | 509 | +int 510 | +strnlen(const char *s, uint z) 511 | +{ 512 | + int n; 513 | + 514 | + for (n = 0; z > 0 && s[n]; n++, z--) 515 | + ; 516 | + return n; 517 | +} 518 | ``` 519 |
520 | 521 | `strnlen()`は終端文字が含まれない可能性のある文字列の長さを計測する関数です。引数で指定した最大バイト数に達しても終端文字が現れなかった場合にはそこで計測を打ち切ります。 522 | 523 | #### ✅ プロトタイプ宣言の追加 524 | 525 | ソースファイル外に公開する関数のプロトタイプ宣言を追加します。xv6では関数のプロトタイプ宣言を`defs.h`に集約しているので、このファイルに追加します。 526 | 527 |
528 | defs.h 529 | 530 | ```diff 531 | ... 532 | 533 | // pipe.c 534 | int pipealloc(struct file**, struct file**); 535 | void pipeclose(struct pipe*, int); 536 | int piperead(struct pipe*, char*, int); 537 | int pipewrite(struct pipe*, char*, int); 538 | 539 | +// printfmt.c 540 | +void vprintfmt(void (*)(int, void*), void*, const char*, va_list); 541 | +void printfmt(void (*)(int, void*), void*, const char*, ...); 542 | +int vsnprintf(char*, int, const char*, va_list); 543 | +int snprintf(char*, int, const char*, ...); 544 | + 545 | ... 546 | 547 | // string.c 548 | int memcmp(const void*, const void*, uint); 549 | void* memmove(void*, const void*, uint); 550 | void* memset(void*, int, uint); 551 | char* safestrcpy(char*, const char*, int); 552 | int strlen(const char*); 553 | +int strnlen(const char*, uint); 554 | int strncmp(const char*, const char*, uint); 555 | char* strncpy(char*, const char*, int); 556 | 557 | ... 558 | 559 | // number of elements in fixed-size array 560 | #define NELEM(x) (sizeof(x)/sizeof((x)[0])) 561 | + 562 | +// variable length arguments 563 | +#define va_start(ap, last) __builtin_va_start(ap, last) 564 | +#define va_arg(ap, type) __builtin_va_arg(ap, type) 565 | +#define va_end(ap) __builtin_va_end(ap) 566 | ``` 567 |
568 | 569 | 可変長引数を扱うための関数についてはGCCのビルトイン機能を利用するので、そのための定義も追加しています。 570 | 571 | #### ✅ Makefileの修正 572 | 573 | ビルド用の`Makefile`を修正します。GCCのビルトイン機能を使用するために`libgcc`をリンクするようにしています。 574 | 575 |
576 | Makefile 577 | 578 | ```diff 579 | OBJS = \ 580 | bio.o\ 581 | console.o\ 582 | exec.o\ 583 | file.o\ 584 | fs.o\ 585 | ide.o\ 586 | ioapic.o\ 587 | kalloc.o\ 588 | kbd.o\ 589 | lapic.o\ 590 | log.o\ 591 | main.o\ 592 | mp.o\ 593 | picirq.o\ 594 | pipe.o\ 595 | + printfmt.o\ 596 | proc.o\ 597 | sleeplock.o\ 598 | spinlock.o\ 599 | string.o\ 600 | swtch.o\ 601 | syscall.o\ 602 | sysfile.o\ 603 | sysproc.o\ 604 | trapasm.o\ 605 | trap.o\ 606 | uart.o\ 607 | vectors.o\ 608 | vm.o\ 609 | 610 | ... 611 | 612 | +GCC_LIB := $(shell $(CC) $(CFLAGS) -print-libgcc-file-name) 613 | + 614 | xv6.img: bootblock kernel 615 | dd if=/dev/zero of=xv6.img count=10000 616 | dd if=bootblock of=xv6.img conv=notrunc 617 | dd if=kernel of=xv6.img seek=1 conv=notrunc 618 | 619 | ... 620 | 621 | kernel: $(OBJS) entry.o entryother initcode kernel.ld 622 | - $(LD) $(LDFLAGS) -T kernel.ld -o kernel entry.o $(OBJS) -b binary initcode entryother 623 | + $(LD) $(LDFLAGS) -T kernel.ld -o kernel entry.o $(OBJS) $(GCC_LIB) -b binary initcode entryother 624 | $(OBJDUMP) -S kernel > kernel.asm 625 | $(OBJDUMP) -t kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > kernel.sym 626 | 627 | ... 628 | ``` 629 |
630 | 631 | 一旦、この状態で追加ファイルを含めてビルドが通るか確認しておきます。 632 | 633 | ``` 634 | $ make clean 635 | $ make 636 | ``` 637 | 638 | #### ✅ コンソール出力関数の差し替え 639 | 640 | コンソール出力関数(`cprintf`)を差し替える準備ができたので、`console.c`を次のように変更します。 641 | 642 |
643 | console.c 644 | 645 | ```diff 646 | ... 647 | 648 | -static void 649 | -printint(int xx, int base, int sign) 650 | -{ 651 | - static char digits[] = "0123456789abcdef"; 652 | - char buf[16]; 653 | - int i; 654 | - uint x; 655 | - 656 | - if(sign && (sign = xx < 0)) 657 | - x = -xx; 658 | - else 659 | - x = xx; 660 | - 661 | - i = 0; 662 | - do{ 663 | - buf[i++] = digits[x % base]; 664 | - }while((x /= base) != 0); 665 | - 666 | - if(sign) 667 | - buf[i++] = '-'; 668 | - 669 | - while(--i >= 0) 670 | - consputc(buf[i]); 671 | -} 672 | -//PAGEBREAK: 50 673 | - 674 | -// Print to the console. only understands %d, %x, %p, %s. 675 | -void 676 | -cprintf(char *fmt, ...) 677 | -{ 678 | - int i, c, locking; 679 | - uint *argp; 680 | - char *s; 681 | - 682 | - locking = cons.locking; 683 | - if(locking) 684 | - acquire(&cons.lock); 685 | - 686 | - if (fmt == 0) 687 | - panic("null fmt"); 688 | - 689 | - argp = (uint*)(void*)(&fmt + 1); 690 | - for(i = 0; (c = fmt[i] & 0xff) != 0; i++){ 691 | - if(c != '%'){ 692 | - consputc(c); 693 | - continue; 694 | - } 695 | - c = fmt[++i] & 0xff; 696 | - if(c == 0) 697 | - break; 698 | - switch(c){ 699 | - case 'd': 700 | - printint(*argp++, 10, 1); 701 | - break; 702 | - case 'x': 703 | - case 'p': 704 | - printint(*argp++, 16, 0); 705 | - break; 706 | - case 's': 707 | - if((s = (char*)*argp++) == 0) 708 | - s = "(null)"; 709 | - for(; *s; s++) 710 | - consputc(*s); 711 | - break; 712 | - case '%': 713 | - consputc('%'); 714 | - break; 715 | - default: 716 | - // Print unknown % sequence to draw attention. 717 | - consputc('%'); 718 | - consputc(c); 719 | - break; 720 | - } 721 | - } 722 | - 723 | - if(locking) 724 | - release(&cons.lock); 725 | -} 726 | +static void 727 | +putch(int ch, int *cnt) 728 | +{ 729 | + consputc(ch); 730 | + (void)*cnt++; 731 | +} 732 | + 733 | +int 734 | +vcprintf(const char *fmt, va_list ap) 735 | +{ 736 | + int cnt = 0; 737 | + 738 | + vprintfmt((void*)putch, &cnt, fmt, ap); 739 | + return cnt; 740 | +} 741 | + 742 | +int 743 | +cprintf(const char *fmt, ...) 744 | +{ 745 | + int locking, cnt; 746 | + va_list ap; 747 | + 748 | + locking = cons.locking; 749 | + if(locking) 750 | + acquire(&cons.lock); 751 | + 752 | + va_start(ap, fmt); 753 | + cnt = vcprintf(fmt, ap); 754 | + va_end(ap); 755 | + 756 | + if(locking) 757 | + release(&cons.lock); 758 | + 759 | + return cnt; 760 | +} 761 | 762 | ... 763 | ``` 764 |
765 | 766 | 既存の`cprintf()`と付随する`printint()`は使用しないので削除してしまいます。新しい`cprintf()`は戻り値が`int`に変更になるため、合わせてプロトタイプ宣言も変更します。 767 | 768 |
769 | defs.h 770 | 771 | ```diff 772 | ... 773 | 774 | // console.c 775 | void consoleinit(void); 776 | +int vcprintf(const char*, va_list); 777 | -void cprintf(char*, ...); 778 | +int cprintf(const char*, ...); 779 | void consoleintr(int(*)(void)); 780 | void panic(char*) __attribute__((noreturn)); 781 | 782 | ... 783 | ``` 784 |
785 | 786 | ## 2.3. 現在時刻の取得 787 | 788 | ### 2.3.1. RTCの利用 789 | 790 | xv6には、RTC(Real Time Clock)から現在時刻を取得する`cmostime()`が用意されています。なお、`cmostime()`に対応するシステムコールは(システムコール追加の演習のために)わざと実装されていないため、カーネル内でのみ使用可能です。 791 | 792 |
793 | lapic.c 794 | 795 | ```c 796 | ... 797 | 798 | #define CMOS_PORT 0x70 799 | #define CMOS_RETURN 0x71 800 | 801 | ... 802 | 803 | #define CMOS_STATA 0x0a 804 | #define CMOS_STATB 0x0b 805 | #define CMOS_UIP (1 << 7) // RTC update in progress 806 | 807 | #define SECS 0x00 808 | #define MINS 0x02 809 | #define HOURS 0x04 810 | #define DAY 0x07 811 | #define MONTH 0x08 812 | #define YEAR 0x09 813 | 814 | static uint 815 | cmos_read(uint reg) 816 | { 817 | outb(CMOS_PORT, reg); 818 | microdelay(200); 819 | 820 | return inb(CMOS_RETURN); 821 | } 822 | 823 | static void 824 | fill_rtcdate(struct rtcdate *r) 825 | { 826 | r->second = cmos_read(SECS); 827 | r->minute = cmos_read(MINS); 828 | r->hour = cmos_read(HOURS); 829 | r->day = cmos_read(DAY); 830 | r->month = cmos_read(MONTH); 831 | r->year = cmos_read(YEAR); 832 | } 833 | 834 | // qemu seems to use 24-hour GWT and the values are BCD encoded 835 | void 836 | cmostime(struct rtcdate *r) 837 | { 838 | struct rtcdate t1, t2; 839 | int sb, bcd; 840 | 841 | sb = cmos_read(CMOS_STATB); 842 | 843 | bcd = (sb & (1 << 2)) == 0; 844 | 845 | // make sure CMOS doesn't modify time while we read it 846 | for(;;) { 847 | fill_rtcdate(&t1); 848 | if(cmos_read(CMOS_STATA) & CMOS_UIP) 849 | continue; 850 | fill_rtcdate(&t2); 851 | if(memcmp(&t1, &t2, sizeof(t1)) == 0) 852 | break; 853 | } 854 | 855 | // convert 856 | if(bcd) { 857 | #define CONV(x) (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf)) 858 | CONV(second); 859 | CONV(minute); 860 | CONV(hour ); 861 | CONV(day ); 862 | CONV(month ); 863 | CONV(year ); 864 | #undef CONV 865 | } 866 | 867 | *r = t1; 868 | r->year += 2000; 869 | } 870 | ``` 871 |
872 | 873 | RTCから時刻情報を読み出すには、`CMOS_PORT`と`CMOS_RETURN`の2つのI/Oポートを次のように操作します。 874 | 875 | 1. `CMOS_PORT`に対してレジスタ番号を書き込む 876 | 2. `CMOS_RETURN`から1で指定したレジスタの値を読み出す 877 | 878 | 879 | `CMOS_STATA`と`CMOS_STATB`はどちらもステータスレジスタで、次のような情報が含まれています。 880 | 881 | ``` 882 | 0A status register A 883 | bit 7 = 1 update in progress 884 | bit 6-4 divider that identifies the time-based frequency 885 | bit 3-0 rate selection output frequency and int. rate 886 | 0B status register B 887 | bit 7 = 0 run 888 | = 1 halt 889 | bit 6 = 1 enable periodic interrupt 890 | bit 5 = 1 enable alarm interrupt 891 | bit 4 = 1 enable update-ended interrupt 892 | bit 3 = 1 enable square wave interrupt 893 | bit 2 = 1 calendar is in binary format 894 | = 0 calendar is in BCD format 895 | bit 1 = 1 24-hour mode 896 | = 0 12-hour mode 897 | bit 0 = 1 enable daylight savings time. only in USA. 898 | useless in Europe. Some DOS versions clear 899 | this bit when you use the DAT/TIME command. 900 | ``` 901 | 902 | 時刻情報は複数のレジスタに分散しているため更新中に読み出すと正しい値を取得できません。ステータスレジスタAにて更新中であることを検知したら読み出しをやりなおします。更に、時刻情報を2回読み出して値が一致した場合に読み込み成功とみなしています。また、ステータスレジスタBにてBCDエンコードされていることが示されていたらデコードして本来の値を求めます。 903 | 904 | > BCD(Binary-Coded Decimal)では、10進数の1桁(0-9)を2進数4桁(16進数1桁)で表します。例えば、10進数の「31」をBCDでは「0011 0001(0x31)」と表現します。((bcd >> 4) * 10) + (bcd & 0xf) で本来の10進数が得られます。 905 | 906 | 時刻情報を扱うための構造体(`struct rtcdate`)は`date.h`に定義されています。 907 | 908 |
909 | date.h 910 | 911 | ```c 912 | struct rtcdate { 913 | uint second; 914 | uint minute; 915 | uint hour; 916 | uint day; 917 | uint month; 918 | uint year; 919 | }; 920 | ``` 921 |
922 | 923 | `struct tm`とフィールド構成が似ていますが、曜日や年初からの経過日数などのフィールドがありません。 924 | 925 | #### ✅ 動作確認 926 | 927 | xv6の起動時に、RTCから現在時刻を取得してコンソールへ出力するコードを追加します。 928 | 929 |
930 | main.c 931 | 932 | ```diff 933 | #include "types.h" 934 | #include "defs.h" 935 | #include "param.h" 936 | #include "memlayout.h" 937 | #include "mmu.h" 938 | #include "proc.h" 939 | #include "x86.h" 940 | +#include "date.h" 941 | 942 | ... 943 | 944 | int 945 | main(void) 946 | { 947 | kinit1(end, P2V(4*1024*1024)); // phys page allocator 948 | kvmalloc(); // kernel page table 949 | mpinit(); // detect other processors 950 | lapicinit(); // interrupt controller 951 | seginit(); // segment descriptors 952 | picinit(); // disable pic 953 | ioapicinit(); // another interrupt controller 954 | consoleinit(); // console hardware 955 | uartinit(); // serial port 956 | pinit(); // process table 957 | tvinit(); // trap vectors 958 | binit(); // buffer cache 959 | fileinit(); // file table 960 | ideinit(); // disk 961 | startothers(); // start other processors 962 | kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers() 963 | userinit(); // first user process 964 | + struct rtcdate rd; 965 | + cmostime(&rd); 966 | + cprintf("%04d/%02d/%02d %02d:%02d:%02d\n", 967 | + rd.year, rd.month, rd.day, rd.hour, rd.minute, rd.second); 968 | mpmain(); // finish this processor's setup 969 | } 970 | 971 | ... 972 | ``` 973 |
974 | 975 | 再ビルドした後、`make qemu-nox`を実行してxv6を起動させます。 976 | 977 | ``` 978 | SeaBIOS (version 1.15.0-1) 979 | 980 | 981 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 982 | 983 | 984 | 985 | Booting from Hard Disk..xv6... 986 | 2024/08/02 15:07:21 987 | cpu0: starting 0 988 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 989 | init: starting sh 990 | $ 991 | ``` 992 | 993 | 起動時に現在時刻が出力されるようになったはずです。 994 | 995 | ### 2.3.2. UNIXタイムへの変換 996 | 997 | プログラムから時刻の比較や演算を行うには`struct rtcdate`のような構造体よりもUNIXタイムの方が扱いやすいため、`struct rtcdate`をUNIXタイムへ変換する関数(およびその逆の動作をする関数)を作ります。 998 | 999 |
1000 | time.c 1001 | 1002 | ```c 1003 | #include "types.h" 1004 | #include "defs.h" 1005 | #include "date.h" 1006 | 1007 | static int 1008 | isleapyear(int y) 1009 | { 1010 | return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); 1011 | } 1012 | 1013 | static int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1014 | 1015 | static int 1016 | ndays(int y, int m) 1017 | { 1018 | int n = days[m]; 1019 | 1020 | if (m == 2 && isleapyear(y)) { 1021 | n++; 1022 | } 1023 | return n; 1024 | } 1025 | 1026 | long 1027 | rtcdate2unixtime(struct rtcdate *r) 1028 | { 1029 | const int epoch = 1970; 1030 | int y, m; 1031 | long unixtime = 0; 1032 | 1033 | for (y = epoch; y < r->year; y++) { 1034 | unixtime += (isleapyear(y) ? 366 : 365) * 24 * 3600; 1035 | } 1036 | for (m = 1; m < r->month; m++) { 1037 | unixtime += ndays(r->year, m) * 24 * 3600; 1038 | } 1039 | unixtime += (r->day - 1) * 24 * 3600; 1040 | unixtime += r->hour * 3600; 1041 | unixtime += r->minute * 60; 1042 | unixtime += r->second; 1043 | return unixtime; 1044 | } 1045 | 1046 | struct rtcdate * 1047 | unixtime2rtcdate(long unixtime, struct rtcdate *r) 1048 | { 1049 | r->second = unixtime % 60; 1050 | unixtime /= 60; 1051 | r->minute = unixtime % 60; 1052 | unixtime /= 60; 1053 | r->hour = unixtime % 24; 1054 | unixtime /= 24; 1055 | 1056 | int days = unixtime; 1057 | 1058 | int y = 1970; 1059 | while (1) { 1060 | int n = isleapyear(y) ? 366 : 365; 1061 | if (days < n) { 1062 | break; 1063 | } 1064 | days -= n; 1065 | y++; 1066 | } 1067 | r->year = y; 1068 | 1069 | int m = 1; 1070 | while (1) { 1071 | int n = ndays(y, m); 1072 | if (days < n) { 1073 | break; 1074 | } 1075 | days -= n; 1076 | m++; 1077 | } 1078 | r->month = m; 1079 | r->day = days + 1; 1080 | return r; 1081 | } 1082 | ``` 1083 |
1084 | 1085 | UNIXタイムはUNIXエポック(`1970/01/01 00:00:00`)からの経過秒です。閏年は考慮しますが閏秒は無視します。 1086 | 1087 | #### ✅ プロトタイプ宣言の追加 1088 | 1089 | 追加した関数のプロトタイプ宣言を`defs.h`に追加します。 1090 | 1091 |
1092 | defs.h 1093 | 1094 | ```diff 1095 | ... 1096 | 1097 | // syscall.c 1098 | int argint(int, int*); 1099 | int argptr(int, char**, int); 1100 | int argstr(int, char**); 1101 | int fetchint(uint, int*); 1102 | int fetchstr(uint, char**); 1103 | void syscall(void); 1104 | 1105 | +// time.c 1106 | +long rtcdate2unixtime(struct rtcdate*); 1107 | +struct rtcdate* unixtime2rtcdate(long, struct rtcdate*); 1108 | 1109 | // timer.c 1110 | void timerinit(void); 1111 | 1112 | ... 1113 | ``` 1114 |
1115 | 1116 | #### ✅ Makefileの修正 1117 | 1118 | 新しくソースファイルを追加したので、オブジェクトファイルのリスト(`OBJS`)に定義を追加します。 1119 | 1120 |
1121 | Makefile 1122 | 1123 | ```diff 1124 | OBJS = \ 1125 | ... 1126 | sysproc.o\ 1127 | + time.o\ 1128 | trapasm.o\ 1129 | trap.o\ 1130 | uart.o\ 1131 | vectors.o\ 1132 | vm.o\ 1133 | 1134 | ... 1135 | ``` 1136 |
1137 | 1138 | #### ✅ 動作確認 1139 | 1140 | `struct rtcdate`からUNIXタイムに変換した値をコンソールに出力するコードを追加します。 1141 | 1142 |
1143 | main.c 1144 | 1145 | ```diff 1146 | ... 1147 | 1148 | int 1149 | main(void) 1150 | { 1151 | ... 1152 | userinit(); // first user process 1153 | struct rtcdate rd; 1154 | + long unixtime; 1155 | cmostime(&rd); 1156 | cprintf("%04d/%02d/%02d %02d:%02d:%02d\n", 1157 | rd.year, rd.month, rd.day, rd.hour, rd.minute, rd.second); 1158 | + unixtime = rtcdate2unixtime(&rd); 1159 | + cprintf("unixtime: %d\n", unixtime); 1160 | + unixtime2rtcdate(unixtime, &rd); 1161 | + cprintf("%04d/%02d/%02d %02d:%02d:%02d\n", 1162 | + rd.year, rd.month, rd.day, rd.hour, rd.minute, rd.second); 1163 | mpmain(); // finish this processor's setup 1164 | } 1165 | 1166 | ... 1167 | ``` 1168 |
1169 | 1170 | 再ビルドした後、`make qemu-nox`を実行してxv6を起動させます。 1171 | 1172 | ``` 1173 | SeaBIOS (version 1.15.0-1) 1174 | 1175 | 1176 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 1177 | 1178 | 1179 | 1180 | Booting from Hard Disk..xv6... 1181 | 2024/08/09 18:08:26 1182 | unixtime: 1723226906 1183 | 2024/08/09 18:08:26 1184 | cpu0: starting 0 1185 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 1186 | init: starting sh 1187 | $ 1188 | ``` 1189 | 1190 | カレンダー形式の時刻に続けてUNIXタイムが出力されるようになったはずです。なお、`date`コマンドを利用するとUNIXタイムを任意の書式に変換の時刻できます。これを利用して出力されているUNIXタイムが正しい値かどうか検証できます。開発環境のシェルで次のコマンドを実行してください。 1191 | 1192 | ``` 1193 | $ date -d @1723226906 +"%Y/%m/%d %T" 1194 | ``` 1195 | 1196 | `struct rtcdate`の内容を`cprintf()`で出力したのと同じ結果が得られたら正しくUNIXタイムに変換できています。 1197 | 1198 | ### 2.3.3. 時刻関連の関数の追加 1199 | 1200 | #### ✅ 時刻を扱う型の追加 1201 | 1202 |
1203 | types.h 1204 | 1205 | ```diff 1206 | ... 1207 | 1208 | #ifndef BUILD_MKFS 1209 | 1210 | ... 1211 | 1212 | typedef __builtin_va_list va_list; 1213 | 1214 | +typedef long time_t; 1215 | 1216 | #endif 1217 | ``` 1218 |
1219 | 1220 |
1221 | date.h 1222 | 1223 | ```diff 1224 | struct rtcdate { 1225 | uint second; 1226 | uint minute; 1227 | uint hour; 1228 | uint day; 1229 | uint month; 1230 | uint year; 1231 | }; 1232 | 1233 | +struct timeval { 1234 | + long tv_sec; 1235 | + long tv_usec; 1236 | +}; 1237 | ``` 1238 |
1239 | 1240 | #### ✅ 現在時刻を得る関数の追加 1241 | 1242 | 現在時刻を得る関数、具体的には`time()`と`gettimeofday()`の2つの関数を追加します。 1243 | 1244 |
1245 | time.c 1246 | 1247 | ```diff 1248 | ... 1249 | 1250 | long 1251 | rtcdate2unixtime(struct rtcdate *r) 1252 | { 1253 | ... 1254 | } 1255 | 1256 | +time_t 1257 | +time(time_t *t) 1258 | +{ 1259 | + struct rtcdate r; 1260 | + time_t _t; 1261 | + 1262 | + cmostime(&r); 1263 | + if (!t) 1264 | + t = &_t; 1265 | + *t = (time_t)rtcdate2unixtime(&r); 1266 | + return *t; 1267 | +} 1268 | + 1269 | +int 1270 | +gettimeofday(struct timeval *tv, void *tz) 1271 | +{ 1272 | + (void)tz; 1273 | + tv->tv_sec = time(NULL); 1274 | + tv->tv_usec = 0; 1275 | + return 0; 1276 | +} 1277 | ``` 1278 |
1279 | 1280 | #### ✅ プロトタイプ宣言の追加 1281 | 1282 | 追加した関数のプロトタイプ宣言を`defs.h`に追加します。 1283 | 1284 |
1285 | defs.h 1286 | 1287 | ```diff 1288 | struct buf; 1289 | struct context; 1290 | struct file; 1291 | struct inode; 1292 | struct pipe; 1293 | struct proc; 1294 | struct rtcdate; 1295 | struct spinlock; 1296 | struct sleeplock; 1297 | struct stat; 1298 | struct superblock; 1299 | +struct timeval; 1300 | 1301 | ... 1302 | 1303 | // time.c 1304 | long rtcdate2unixtime(struct rtcdate *); 1305 | struct rtcdate* unixtime2rtcdate(long, struct rtcdate*); 1306 | +time_t time(time_t*); 1307 | +int gettimeofday(struct timeval*, void*); 1308 | 1309 | ... 1310 | ``` 1311 |
1312 | 1313 | #### ✅ 動作確認 1314 | 1315 | `gettimeofday()`を呼び出すコードを追加します。`gettimeofday()`は内部で`time()`を呼び出すようにしているため、追加した2つの関数のが両方とも呼び出されます。 1316 | 1317 |
1318 | main.c 1319 | 1320 | ```diff 1321 | ... 1322 | 1323 | int 1324 | main(void) 1325 | { 1326 | ... 1327 | userinit(); // first user process 1328 | struct rtcdate rd; 1329 | long unixtime; 1330 | + struct timeval tv; 1331 | cmostime(&rd); 1332 | cprintf("%04d/%02d/%02d %02d:%02d:%02d\n", 1333 | rd.year, rd.month, rd.day, rd.hour, rd.minute, rd.second); 1334 | unixtime = rtcdate2unixtime(&rd); 1335 | cprintf("unixtime: %d\n", unixtime); 1336 | unixtime2rtcdate(unixtime, &rd); 1337 | cprintf("%04d/%02d/%02d %02d:%02d:%02d\n", 1338 | rd.year, rd.month, rd.day, rd.hour, rd.minute, rd.second); 1339 | + gettimeofday(&tv, NULL); 1340 | + cprintf("tv: {sec: %d, usec: %d}\n", tv.tv_sec, tv.tv_usec); 1341 | mpmain(); // finish this processor's setup 1342 | } 1343 | 1344 | ... 1345 | ``` 1346 |
1347 | 1348 | 再ビルドした後、`make qemu-nox`を実行してxv6を起動させます。 1349 | 1350 | ``` 1351 | SeaBIOS (version 1.15.0-1) 1352 | 1353 | 1354 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 1355 | 1356 | 1357 | 1358 | Booting from Hard Disk..xv6... 1359 | 2024/08/11 02:03:41 1360 | unixtime: 1723341821 1361 | 2024/08/11 02:03:41 1362 | tv: {sec: 1723341821, usec: 0} 1363 | cpu0: starting 0 1364 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 1365 | init: starting sh 1366 | $ 1367 | ``` 1368 | 1369 | `tv: {sec: xxx, usec: xxx}` が`struct timeval`の内容を出力したものです。`sec`が先に出力しているUNIXタイムの値と一致していれば正しく動作しています。`usec`は常に`0`になるはずです。 1370 | 1371 | ## 2.4. 便利ライブラリの移植 1372 | 1373 | 自作プロトコルスタックの実装をサポートするための便利ライブラリ(`util.c`、`util.h`)を先に移植しておきます。 1374 | 1375 | #### ✅ ディレクトリの作成 1376 | 1377 | xv6のコードはフラットに配置されていますが、プロトコルスタックのコードは`net`ディレクトリを作成してその中に配置します。プラットフォーム依存のコードを配置するためのディレクトリまで一括で作成します。 1378 | 1379 | ``` 1380 | $ mkdir -p net/platform/xv6 1381 | ``` 1382 | 1383 | ヘッダファイルを正しく検出できるように、`net`ディレクトリと`net/platform/xv6`ディレクトリをインクルードパスに追加します。 1384 | 1385 |
1386 | Makefile 1387 | 1388 | ```diff 1389 | ... 1390 | 1391 | CC = $(TOOLPREFIX)gcc 1392 | AS = $(TOOLPREFIX)gas 1393 | LD = $(TOOLPREFIX)ld 1394 | OBJCOPY = $(TOOLPREFIX)objcopy 1395 | OBJDUMP = $(TOOLPREFIX)objdump 1396 | -CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer 1397 | +CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer -I . -I net -I net/platform/xv6 1398 | CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) 1399 | ASFLAGS = -m32 -gdwarf-2 -Wa,-divide 1400 | # FreeBSD ld wants ``elf_i386_fbsd'' 1401 | LDFLAGS += -m $(shell $(LD) -V | grep elf_i386 2>/dev/null | head -n 1) 1402 | 1403 | ... 1404 | 1405 | clean: 1406 | rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ 1407 | + net/*.o net/*.d net/platform/xv6/*.o net/platform/xv6/*.d \ 1408 | *.o *.d *.asm *.sym vectors.S bootblock entryother \ 1409 | initcode initcode.out kernel xv6.img fs.img kernelmemfs \ 1410 | xv6memfs.img mkfs .gdbinit \ 1411 | $(UPROGS) 1412 | 1413 | ... 1414 | ``` 1415 |
1416 | 1417 | #### ✅ プラットフォーム依存コードの追加 1418 | 1419 | まず、プラットフォーム依存のコードを追加します。いまの時点ではメモリ関連のコードだけ含めています。その他は必要になったタイミングで随時追加していきます。 1420 | 1421 |
1422 | net/platform/xv6/platform.h 1423 | 1424 | ```c 1425 | #ifndef PLATFORM_H 1426 | #define PLATFORM_H 1427 | 1428 | #include "types.h" 1429 | #include "defs.h" 1430 | 1431 | /* 1432 | * Memory 1433 | */ 1434 | 1435 | #include "mmu.h" 1436 | 1437 | static inline void * 1438 | memory_alloc(size_t size) 1439 | { 1440 | void *p; 1441 | 1442 | if (PGSIZE < size) { 1443 | return NULL; 1444 | } 1445 | p = kalloc(); 1446 | if (p) { 1447 | memset(p, 0, size); 1448 | } 1449 | return p; 1450 | } 1451 | 1452 | static inline void 1453 | memory_free(void *ptr) 1454 | { 1455 | kfree(ptr); 1456 | } 1457 | 1458 | /* 1459 | * Mutex 1460 | */ 1461 | 1462 | #include "param.h" 1463 | #include "spinlock.h" 1464 | #include "proc.h" 1465 | 1466 | 1467 | 1468 | /* 1469 | * Interrupt 1470 | */ 1471 | 1472 | #include "traps.h" 1473 | 1474 | 1475 | 1476 | /* 1477 | * Scheduler 1478 | */ 1479 | 1480 | 1481 | 1482 | #endif 1483 | ``` 1484 |
1485 | 1486 | xv6のカーネル内ではメモリは`kalloc()`で確保します。確保するメモリのサイズは指定できず、常にページサイズ(4KB)のメモリが返されます。自作プロトコルスタックでは4KBを超えるメモリを確保することはないので問題ないはずです。`kalloc()`で確保したメモリは`kfree()`で解放します。 1487 | 1488 | #### ✅ 便利ライブラリの配置 1489 | 1490 | 便利ライブラリのファイルを`net`ディレクトリの下に配置します。 1491 | 1492 |
1493 | net/util.c 1494 | 1495 | ```c 1496 | #include "platform.h" 1497 | 1498 | #include "util.h" 1499 | 1500 | /* 1501 | * Logging 1502 | */ 1503 | 1504 | int 1505 | lprintf(FILE *fp, int level, const char *file, int line, const char *func, const char *fmt, ...) 1506 | { 1507 | struct timeval tv; 1508 | struct tm tm; 1509 | char timestamp[32]; 1510 | int n = 0; 1511 | va_list ap; 1512 | 1513 | flockfile(fp); 1514 | gettimeofday(&tv, NULL); 1515 | strftime(timestamp, sizeof(timestamp), "%T", localtime_r(&tv.tv_sec, &tm)); 1516 | n += fprintf(fp, "%s.%03d [%c] %s: ", timestamp, (int)(tv.tv_usec / 1000), level, func); 1517 | va_start(ap, fmt); 1518 | n += vfprintf(fp, fmt, ap); 1519 | va_end(ap); 1520 | n += fprintf(fp, " (%s:%d)\n", file, line); 1521 | funlockfile(fp); 1522 | return n; 1523 | } 1524 | 1525 | void 1526 | hexdump(FILE *fp, const void *data, size_t size) 1527 | { 1528 | unsigned char *src; 1529 | int offset, index; 1530 | 1531 | flockfile(fp); 1532 | src = (unsigned char *)data; 1533 | fprintf(fp, "+------+-------------------------------------------------+------------------+\n"); 1534 | for(offset = 0; offset < (int)size; offset += 16) { 1535 | fprintf(fp, "| %04x | ", offset); 1536 | for(index = 0; index < 16; index++) { 1537 | if(offset + index < (int)size) { 1538 | fprintf(fp, "%02x ", 0xff & src[offset + index]); 1539 | } else { 1540 | fprintf(fp, " "); 1541 | } 1542 | } 1543 | fprintf(fp, "| "); 1544 | for(index = 0; index < 16; index++) { 1545 | if(offset + index < (int)size) { 1546 | if(isascii(src[offset + index]) && isprint(src[offset + index])) { 1547 | fprintf(fp, "%c", src[offset + index]); 1548 | } else { 1549 | fprintf(fp, "."); 1550 | } 1551 | } else { 1552 | fprintf(fp, " "); 1553 | } 1554 | } 1555 | fprintf(fp, " |\n"); 1556 | } 1557 | fprintf(fp, "+------+-------------------------------------------------+------------------+\n"); 1558 | funlockfile(fp); 1559 | } 1560 | 1561 | /* 1562 | * Queue 1563 | */ 1564 | 1565 | struct queue_entry { 1566 | struct queue_entry *next; 1567 | void *data; 1568 | }; 1569 | 1570 | void 1571 | queue_init(struct queue_head *queue) 1572 | { 1573 | queue->head = NULL; 1574 | queue->tail = NULL; 1575 | queue->num = 0; 1576 | } 1577 | 1578 | void * 1579 | queue_push(struct queue_head *queue, void *data) 1580 | { 1581 | struct queue_entry *entry; 1582 | 1583 | if (!queue) { 1584 | return NULL; 1585 | } 1586 | entry = memory_alloc(sizeof(*entry)); 1587 | if (!entry) { 1588 | return NULL; 1589 | } 1590 | entry->next = NULL; 1591 | entry->data = data; 1592 | if (queue->tail) { 1593 | queue->tail->next = entry; 1594 | } 1595 | queue->tail = entry; 1596 | if (!queue->head) { 1597 | queue->head = entry; 1598 | } 1599 | queue->num++; 1600 | return data; 1601 | } 1602 | 1603 | void * 1604 | queue_pop(struct queue_head *queue) 1605 | { 1606 | struct queue_entry *entry; 1607 | void *data; 1608 | 1609 | if (!queue || !queue->head) { 1610 | return NULL; 1611 | } 1612 | entry = queue->head; 1613 | queue->head = entry->next; 1614 | if (!queue->head) { 1615 | queue->tail = NULL; 1616 | } 1617 | queue->num--; 1618 | data = entry->data; 1619 | memory_free(entry); 1620 | return data; 1621 | } 1622 | 1623 | void * 1624 | queue_peek(struct queue_head *queue) 1625 | { 1626 | if (!queue || !queue->head) { 1627 | return NULL; 1628 | } 1629 | return queue->head->data; 1630 | } 1631 | 1632 | void 1633 | queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), void *arg) 1634 | { 1635 | struct queue_entry *entry; 1636 | 1637 | if (!queue || !func) { 1638 | return; 1639 | } 1640 | for (entry = queue->head; entry; entry = entry->next) { 1641 | func(arg, entry->data); 1642 | } 1643 | } 1644 | 1645 | /* 1646 | * Byteorder 1647 | */ 1648 | 1649 | #ifndef __BIG_ENDIAN 1650 | #define __BIG_ENDIAN 4321 1651 | #endif 1652 | #ifndef __LITTLE_ENDIAN 1653 | #define __LITTLE_ENDIAN 1234 1654 | #endif 1655 | 1656 | static int endian; 1657 | 1658 | static int 1659 | byteorder(void) { 1660 | uint32_t x = 0x00000001; 1661 | 1662 | return *(uint8_t *)&x ? __LITTLE_ENDIAN : __BIG_ENDIAN; 1663 | } 1664 | 1665 | static uint16_t 1666 | byteswap16(uint16_t v) 1667 | { 1668 | return (v & 0x00ff) << 8 | (v & 0xff00 ) >> 8; 1669 | } 1670 | 1671 | static uint32_t 1672 | byteswap32(uint32_t v) 1673 | { 1674 | return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; 1675 | } 1676 | 1677 | uint16_t 1678 | hton16(uint16_t h) 1679 | { 1680 | if (!endian) { 1681 | endian = byteorder(); 1682 | } 1683 | return endian == __LITTLE_ENDIAN ? byteswap16(h) : h; 1684 | } 1685 | 1686 | uint16_t 1687 | ntoh16(uint16_t n) 1688 | { 1689 | if (!endian) { 1690 | endian = byteorder(); 1691 | } 1692 | return endian == __LITTLE_ENDIAN ? byteswap16(n) : n; 1693 | } 1694 | 1695 | uint32_t 1696 | hton32(uint32_t h) 1697 | { 1698 | if (!endian) { 1699 | endian = byteorder(); 1700 | } 1701 | return endian == __LITTLE_ENDIAN ? byteswap32(h) : h; 1702 | } 1703 | 1704 | uint32_t 1705 | ntoh32(uint32_t n) 1706 | { 1707 | if (!endian) { 1708 | endian = byteorder(); 1709 | } 1710 | return endian == __LITTLE_ENDIAN ? byteswap32(n) : n; 1711 | } 1712 | 1713 | /* 1714 | * Checksum 1715 | */ 1716 | 1717 | uint16_t 1718 | cksum16(uint16_t *addr, uint16_t count, uint32_t init) 1719 | { 1720 | uint32_t sum; 1721 | 1722 | sum = init; 1723 | while (count > 1) { 1724 | sum += *(addr++); 1725 | count -= 2; 1726 | } 1727 | if (count > 0) { 1728 | sum += *(uint8_t *)addr; 1729 | } 1730 | while (sum >> 16) { 1731 | sum = (sum & 0xffff) + (sum >> 16); 1732 | } 1733 | return ~(uint16_t)sum; 1734 | } 1735 | ``` 1736 |
1737 | 1738 | 1739 |
1740 | net/util.h 1741 | 1742 | ```c 1743 | #ifndef UTIL_H 1744 | #define UTIL_H 1745 | 1746 | #include "platform.h" 1747 | 1748 | /* 1749 | * Compare 1750 | */ 1751 | 1752 | #ifndef MAX 1753 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 1754 | #endif 1755 | #ifndef MIN 1756 | #define MIN(x, y) ((x) < (y) ? (x) : (y)) 1757 | #endif 1758 | 1759 | /* 1760 | * Array 1761 | */ 1762 | 1763 | #define countof(x) ((sizeof(x) / sizeof(*x))) 1764 | #define tailof(x) (x + countof(x)) 1765 | #define indexof(x, y) (((uintptr_t)y - (uintptr_t)x) / sizeof(*y)) 1766 | 1767 | /* 1768 | * Time 1769 | */ 1770 | 1771 | #define timeval_add_usec(x, y) \ 1772 | do { \ 1773 | (x)->tv_sec += y / 1000000; \ 1774 | (x)->tv_usec += y % 1000000; \ 1775 | if ((x)->tv_usec >= 1000000) { \ 1776 | (x)->tv_sec += 1; \ 1777 | (x)->tv_usec -= 1000000; \ 1778 | } \ 1779 | } while(0); 1780 | 1781 | #define timespec_add_nsec(x, y) \ 1782 | do { \ 1783 | (x)->tv_sec += y / 1000000000; \ 1784 | (x)->tv_nsec += y % 1000000000; \ 1785 | if ((x)->tv_nsec >= 1000000000) { \ 1786 | (x)->tv_sec += 1; \ 1787 | (x)->tv_nsec -= 1000000000; \ 1788 | } \ 1789 | } while(0); 1790 | 1791 | /* 1792 | * Logging 1793 | */ 1794 | 1795 | #define errorf(...) lprintf(stderr, 'E', __FILE__, __LINE__, __func__, __VA_ARGS__) 1796 | #define warnf(...) lprintf(stderr, 'W', __FILE__, __LINE__, __func__, __VA_ARGS__) 1797 | #define infof(...) lprintf(stderr, 'I', __FILE__, __LINE__, __func__, __VA_ARGS__) 1798 | #define debugf(...) lprintf(stderr, 'D', __FILE__, __LINE__, __func__, __VA_ARGS__) 1799 | 1800 | #ifdef HEXDUMP 1801 | #define debugdump(...) hexdump(stderr, __VA_ARGS__) 1802 | #else 1803 | #define debugdump(...) 1804 | #endif 1805 | 1806 | extern int 1807 | lprintf(FILE *fp, int level, const char *file, int line, const char *func, const char *fmt, ...); 1808 | extern void 1809 | hexdump(FILE *fp, const void *data, size_t size); 1810 | 1811 | /* 1812 | * Queue 1813 | */ 1814 | 1815 | struct queue_entry; 1816 | 1817 | struct queue_head { 1818 | struct queue_entry *head; 1819 | struct queue_entry *tail; 1820 | unsigned int num; 1821 | }; 1822 | 1823 | extern void 1824 | queue_init(struct queue_head *queue); 1825 | extern void * 1826 | queue_push(struct queue_head *queue, void *data); 1827 | extern void * 1828 | queue_pop(struct queue_head *queue); 1829 | extern void * 1830 | queue_peek(struct queue_head *queue); 1831 | extern void 1832 | queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), void *arg); 1833 | 1834 | /* 1835 | * Byteorder 1836 | */ 1837 | 1838 | extern uint16_t 1839 | hton16(uint16_t h); 1840 | extern uint16_t 1841 | ntoh16(uint16_t n); 1842 | extern uint32_t 1843 | hton32(uint32_t h); 1844 | extern uint32_t 1845 | ntoh32(uint32_t n); 1846 | 1847 | /* 1848 | * Checksum 1849 | */ 1850 | 1851 | extern uint16_t 1852 | cksum16(uint16_t *addr, uint16_t count, uint32_t init); 1853 | 1854 | #endif 1855 | ``` 1856 |
1857 | 1858 | xv6のコンパイルでは`-nostdinc`が指定されるため、`#include <>`でシステムヘッダをインクルードしている箇所でエラーが発生してしまいます。これに対処するために、システムヘッダのインクルードを全て削除しています。この先で追加するプロトコルスタック関連のファイルについても、システムヘッダのインクルードを取り除き、替わりに`platform.h`を冒頭でインクルードするように変更します。 1859 | 1860 | #### ✅ 不足している標準ライブラリ関数の追加 1861 | 1862 | xv6には標準ライブラリが実装されていないため、必要な関数は自分で追加する必要があります。ここでは、便利ライブラリが必要とする関数に加えて自作プロトコルスタックの移植で必要になる関数を追加しておきます。 1863 | 1864 |
1865 | net/platform/xv6/std.h 1866 | 1867 | ```c 1868 | #ifndef STD_H 1869 | #define STD_H 1870 | 1871 | #include "date.h" 1872 | 1873 | #define UINT16_MAX 65535 1874 | 1875 | #define isascii(x) ((x >= 0x00) && (x <= 0x7f)) 1876 | #define isprint(x) ((x >= 0x20) && (x <= 0x7e)) 1877 | 1878 | #define EINTR 1 1879 | 1880 | extern int errno; 1881 | 1882 | /* 1883 | * STDIO 1884 | */ 1885 | 1886 | typedef struct { 1887 | /* dummy */ 1888 | } FILE; 1889 | 1890 | extern FILE *stderr; 1891 | 1892 | #define fprintf(fp, ...) cprintf(__VA_ARGS__) 1893 | #define vfprintf(fp, ...) vcprintf(__VA_ARGS__) 1894 | 1895 | extern void 1896 | flockfile(FILE *fp); 1897 | extern void 1898 | funlockfile(FILE *fp); 1899 | extern int 1900 | vfprintf(FILE *fp, const char *fmt, va_list ap); 1901 | 1902 | /* 1903 | * Time 1904 | */ 1905 | 1906 | struct timespec { 1907 | /* dummy */ 1908 | }; 1909 | 1910 | struct tm { 1911 | struct rtcdate r; 1912 | }; 1913 | 1914 | extern size_t 1915 | strftime(char *s, size_t max, const char *format, const struct tm *tm); 1916 | extern struct tm * 1917 | localtime_r(const time_t *timep, struct tm *result); 1918 | extern void 1919 | timersub(struct timeval *a, struct timeval *b, struct timeval *res); 1920 | extern void 1921 | timerclear(struct timeval *tv); 1922 | 1923 | #define timercmp(a, b, cmp) \ 1924 | ((a)->tv_sec == (b)->tv_sec ? (a)->tv_usec cmp (b)->tv_usec : (a)->tv_sec cmp (b)->tv_sec) 1925 | 1926 | /* 1927 | * Random 1928 | */ 1929 | 1930 | extern void 1931 | srand(unsigned int newseed); 1932 | extern long 1933 | random(void); 1934 | 1935 | /* 1936 | * String 1937 | */ 1938 | 1939 | extern void * 1940 | memcpy(void *dst, const void *src, uint n); 1941 | extern long 1942 | strtol(const char *s, char **endptr, int base); 1943 | extern char * 1944 | strrchr(const char *cp, int ch); 1945 | 1946 | #endif 1947 | ``` 1948 |
1949 | 1950 |
1951 | net/platform/xv6/std.c 1952 | 1953 | ```c 1954 | #include "platform.h" 1955 | 1956 | int errno; 1957 | 1958 | /* 1959 | * STDIO 1960 | */ 1961 | 1962 | FILE *stderr; 1963 | 1964 | void 1965 | flockfile(FILE *fp) 1966 | { 1967 | /* dummy */ 1968 | } 1969 | 1970 | void 1971 | funlockfile(FILE *fp) 1972 | { 1973 | /* dummy */ 1974 | } 1975 | 1976 | /* 1977 | * Time 1978 | */ 1979 | 1980 | size_t 1981 | strftime(char *s, size_t max, const char *format, const struct tm *tm) 1982 | { 1983 | (void)format; /* force HH:MM:SS */ 1984 | return snprintf(s, max, "%02d:%02d:%02d", tm->r.hour, tm->r.minute, tm->r.second); 1985 | } 1986 | 1987 | struct tm * 1988 | localtime_r(const time_t *timep, struct tm *result) 1989 | { 1990 | /* ignore timezone */ 1991 | unixtime2rtcdate(*timep, &result->r); 1992 | return result; 1993 | } 1994 | 1995 | void 1996 | timersub(struct timeval *a, struct timeval *b, struct timeval *res) 1997 | { 1998 | res->tv_sec = a->tv_sec - b->tv_sec; 1999 | res->tv_usec = a->tv_usec - b->tv_usec; 2000 | if (res->tv_usec < 0) { 2001 | --res->tv_sec; 2002 | res->tv_usec += 1000*1000; 2003 | } 2004 | return; 2005 | } 2006 | 2007 | void 2008 | timerclear(struct timeval *tv) 2009 | { 2010 | tv->tv_sec = 0; 2011 | tv->tv_usec = 0; 2012 | } 2013 | 2014 | /* 2015 | * Random 2016 | */ 2017 | 2018 | static unsigned int seed = 1; 2019 | 2020 | void 2021 | srand(unsigned int newseed) 2022 | { 2023 | seed = newseed; 2024 | } 2025 | 2026 | long 2027 | random(void) 2028 | { 2029 | /* Linear Congruential Generator (LCG) */ 2030 | seed = (seed * 1103515245 + 12345) % 0x7fffffff; 2031 | return seed; 2032 | } 2033 | 2034 | /* 2035 | * String 2036 | */ 2037 | 2038 | long 2039 | strtol(const char *s, char **endptr, int base) 2040 | { 2041 | int neg = 0; 2042 | long val = 0; 2043 | 2044 | // gobble initial whitespace 2045 | while (*s == ' ' || *s == '\t') 2046 | s++; 2047 | 2048 | // plus/minus sign 2049 | if (*s == '+') 2050 | s++; 2051 | else if (*s == '-') 2052 | s++, neg = 1; 2053 | 2054 | // hex or octal base prefix 2055 | if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) 2056 | s += 2, base = 16; 2057 | else if (base == 0 && s[0] == '0') 2058 | s++, base = 8; 2059 | else if (base == 0) 2060 | base = 10; 2061 | 2062 | // digits 2063 | while (1) { 2064 | int dig; 2065 | 2066 | if (*s >= '0' && *s <= '9') 2067 | dig = *s - '0'; 2068 | else if (*s >= 'a' && *s <= 'z') 2069 | dig = *s - 'a' + 10; 2070 | else if (*s >= 'A' && *s <= 'Z') 2071 | dig = *s - 'A' + 10; 2072 | else 2073 | break; 2074 | if (dig >= base) 2075 | break; 2076 | s++, val = (val * base) + dig; 2077 | // we don't properly detect overflow! 2078 | } 2079 | 2080 | if (endptr) 2081 | *endptr = (char *) s; 2082 | return (neg ? -val : val); 2083 | } 2084 | 2085 | char * 2086 | strrchr(const char *cp, int ch) 2087 | { 2088 | char *save; 2089 | char c; 2090 | 2091 | for (save = (char *) 0; (c = *cp); cp++) { 2092 | if (c == ch) { 2093 | save = (char *) cp; 2094 | } 2095 | } 2096 | return save; 2097 | } 2098 | ``` 2099 |
2100 | 2101 | 一部、ダミーの関数も含んでいますが主に下記のカテゴリの関数を追加しています。 2102 | 2103 | + 標準入出力 2104 | + 時刻関連 2105 | + ランダム関数 2106 | + 文字列操作 2107 | 2108 | `platform.h`をインクルードすれば`std.h`も自動的に読み込まれるようにしておきます。 2109 | 2110 |
2111 | net/platform/xv6/platform.h 2112 | 2113 | ```diff 2114 | #ifndef PLATFORM_H 2115 | #define PLATFORM_H 2116 | 2117 | #include "types.h" 2118 | #include "defs.h" 2119 | 2120 | +#include "std.h" 2121 | + 2122 | ... 2123 | ``` 2124 |
2125 | 2126 | #### ✅ 動作確認 2127 | 2128 | 動作確認用に、自作プロトコルスタックのメインモジュールに相当するファイルを追加します(このファイルは後に削除します)。 2129 | 2130 |
2131 | net/net.c 2132 | 2133 | ```c 2134 | #include "platform.h" 2135 | 2136 | #include "util.h" 2137 | 2138 | void 2139 | netinit(void) 2140 | { 2141 | char msg[] = "Hello, SecCamp2024!"; 2142 | 2143 | debugf("%s", msg); 2144 | debugdump(msg, sizeof(msg)); 2145 | } 2146 | ``` 2147 |
2148 | 2149 | 便利ライブラリの機能を使ってログ出力と16進ダンプをテストします。`netinit()`はxv6の`main()`関数から呼び出すので、`defs.h`にプロトタイプ宣言を追加しておきます。 2150 | 2151 |
2152 | defs.h 2153 | 2154 | ```diff 2155 | ... 2156 | 2157 | // vm.c 2158 | void seginit(void); 2159 | void kvmalloc(void); 2160 | pde_t* setupkvm(void); 2161 | char* uva2ka(pde_t*, char*); 2162 | int allocuvm(pde_t*, uint, uint); 2163 | int deallocuvm(pde_t*, uint, uint); 2164 | void freevm(pde_t*); 2165 | void inituvm(pde_t*, char*, uint); 2166 | int loaduvm(pde_t*, char*, struct inode*, uint, uint); 2167 | pde_t* copyuvm(pde_t*, uint); 2168 | void switchuvm(struct proc*); 2169 | void switchkvm(void); 2170 | int copyout(pde_t*, uint, void*, uint); 2171 | void clearpteu(pde_t *pgdir, char *uva); 2172 | 2173 | +// net/net.c 2174 | +void netinit(void); 2175 | + 2176 | // number of elements in fixed-size array 2177 | #define NELEM(x) (sizeof(x)/sizeof((x)[0])) 2178 | 2179 | ... 2180 | ``` 2181 |
2182 | 2183 | 追加したソースファイルに関する定義を`Makefile`に追加します。また、`debugdump()`マクロを有効にするために`CFLAGS`に`-DHEXDUMP`を追加します。 2184 | 2185 |
2186 | Makefile 2187 | 2188 | ```diff 2189 | OBJS = \ 2190 | ... 2191 | uart.o\ 2192 | vectors.o\ 2193 | vm.o\ 2194 | + net/platform/xv6/std.o\ 2195 | + net/util.o\ 2196 | + net/net.o\ 2197 | 2198 | ... 2199 | 2200 | CC = $(TOOLPREFIX)gcc 2201 | AS = $(TOOLPREFIX)gas 2202 | LD = $(TOOLPREFIX)ld 2203 | OBJCOPY = $(TOOLPREFIX)objcopy 2204 | OBJDUMP = $(TOOLPREFIX)objdump 2205 | -CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer -I . -I net -I net/platform/xv6 2206 | +CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -O2 -Wall -MD -ggdb -m32 -Werror -fno-omit-frame-pointer -I . -I net -I net/platform/xv6 -DHEXDUMP 2207 | CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) 2208 | ASFLAGS = -m32 -gdwarf-2 -Wa,-divide 2209 | # FreeBSD ld wants ``elf_i386_fbsd'' 2210 | LDFLAGS += -m $(shell $(LD) -V | grep elf_i386 2>/dev/null | head -n 1) 2211 | 2212 | ... 2213 | ``` 2214 |
2215 | 2216 | xv6の`main()`関数から`netinit()`を呼び出すようにします。加えて、時刻のテストで追加したコードを削除します。 2217 | 2218 |
2219 | main.c 2220 | 2221 | ```diff 2222 | int 2223 | main(void) 2224 | { 2225 | ... 2226 | binit(); // buffer cache 2227 | fileinit(); // file table 2228 | ideinit(); // disk 2229 | + netinit(); // network stack 2230 | startothers(); // start other processors 2231 | kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers() 2232 | userinit(); // first user process 2233 | - struct rtcdate rd; 2234 | - long unixtime; 2235 | - struct timeval tv; 2236 | - cmostime(&rd); 2237 | - cprintf("%04d/%02d/%02d %02d:%02d:%02d\n", 2238 | - rd.year, rd.month, rd.day, rd.hour, rd.minute, rd.second); 2239 | - unixtime = rtcdate2unixtime(&rd); 2240 | - cprintf("unixtime: %d\n", unixtime); 2241 | - unixtime2rtcdate(unixtime, &rd); 2242 | - cprintf("%04d/%02d/%02d %02d:%02d:%02d\n", 2243 | - rd.year, rd.month, rd.day, rd.hour, rd.minute, rd.second); 2244 | - gettimeofday(&tv, NULL); 2245 | - cprintf("tv: {sec: %d, usec: %d}\n", tv.tv_sec, tv.tv_usec); 2246 | mpmain(); // finish this processor's setup 2247 | } 2248 | 2249 | ``` 2250 |
2251 | 2252 | 再ビルドした後、`make qemu-nox`を実行してxv6を起動させます。 2253 | 2254 | ``` 2255 | SeaBIOS (version 1.15.0-1) 2256 | 2257 | 2258 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 2259 | 2260 | 2261 | 2262 | Booting from Hard Disk..xv6... 2263 | 05:00:47.000 [D] netinit: Hello, SecCamp2024! (net/net.c:10) 2264 | +------+-------------------------------------------------+------------------+ 2265 | | 0000 | 48 65 6c 6c 6f 2c 20 53 65 63 43 61 6d 70 32 30 | Hello, SecCamp20 | 2266 | | 0010 | 32 34 21 00 | 24!. | 2267 | +------+-------------------------------------------------+------------------+ 2268 | cpu0: starting 0 2269 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 2270 | init: starting sh 2271 | $ 2272 | ``` 2273 | 2274 | 見覚えのある書式のログメッセージと16進ダンプが出力されるはずです。 2275 | 2276 | これでプロトコルスタックを移植する準備が整いました。 -------------------------------------------------------------------------------- /03.md: -------------------------------------------------------------------------------- 1 | # 3. ネットワークデバイス 2 | 3 | ## 3.1. PCIデバイスの検出 4 | 5 | QEMUが提供するネットワークデバイスはPCIバスに接続されています。xv6にはPCI関連の機能を備えていないため、PCIバスを走査してデバイスを検出する仕組みから追加していきます。 6 | 7 | 参考資料 8 | 9 | + https://wiki.osdev.org/PCI 10 | + https://github.com/osdev-jp/osdev-jp.github.io/wiki/PCI-Memo 11 | 12 | #### ✅ PCI関連コードの追加 13 | 14 | PCI関連のコードを追加します。こちらは改良版のコンソール出力と同様にJOSからコードを借用しています。 15 | 16 |
17 | pci.h 18 | 19 | ```c 20 | #ifndef JOS_KERN_PCI_H 21 | #define JOS_KERN_PCI_H 22 | 23 | // PCI subsystem interface 24 | enum { pci_res_bus, pci_res_mem, pci_res_io, pci_res_max }; 25 | 26 | struct pci_bus; 27 | 28 | struct pci_func { 29 | struct pci_bus *bus; // Primary bus for bridges 30 | 31 | uint32_t dev; 32 | uint32_t func; 33 | 34 | uint32_t dev_id; 35 | uint32_t dev_class; 36 | 37 | uint32_t reg_base[6]; 38 | uint32_t reg_size[6]; 39 | uint8_t irq_line; 40 | }; 41 | 42 | struct pci_bus { 43 | struct pci_func *parent_bridge; 44 | uint32_t busno; 45 | }; 46 | 47 | #endif 48 | ``` 49 |
50 | 51 |
52 | pcireg.h 53 | 54 | ```c 55 | /* $NetBSD: pcireg.h,v 1.45 2004/02/04 06:58:24 soren Exp $ */ 56 | 57 | /* 58 | * Copyright (c) 1995, 1996, 1999, 2000 59 | * Christopher G. Demetriou. All rights reserved. 60 | * Copyright (c) 1994, 1996 Charles M. Hannum. All rights reserved. 61 | * 62 | * Redistribution and use in source and binary forms, with or without 63 | * modification, are permitted provided that the following conditions 64 | * are met: 65 | * 1. Redistributions of source code must retain the above copyright 66 | * notice, this list of conditions and the following disclaimer. 67 | * 2. Redistributions in binary form must reproduce the above copyright 68 | * notice, this list of conditions and the following disclaimer in the 69 | * documentation and/or other materials provided with the distribution. 70 | * 3. All advertising materials mentioning features or use of this software 71 | * must display the following acknowledgement: 72 | * This product includes software developed by Charles M. Hannum. 73 | * 4. The name of the author may not be used to endorse or promote products 74 | * derived from this software without specific prior written permission. 75 | * 76 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 77 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 78 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 79 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 80 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 81 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 82 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 83 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 84 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 85 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 86 | */ 87 | 88 | #ifndef _DEV_PCI_PCIREG_H_ 89 | #define _DEV_PCI_PCIREG_H_ 90 | 91 | /* 92 | * Standardized PCI configuration information 93 | * 94 | * XXX This is not complete. 95 | */ 96 | 97 | /* 98 | * Device identification register; contains a vendor ID and a device ID. 99 | */ 100 | #define PCI_ID_REG 0x00 101 | 102 | typedef uint16_t pci_vendor_id_t; 103 | typedef uint16_t pci_product_id_t; 104 | 105 | #define PCI_VENDOR_SHIFT 0 106 | #define PCI_VENDOR_MASK 0xffff 107 | #define PCI_VENDOR(id) \ 108 | (((id) >> PCI_VENDOR_SHIFT) & PCI_VENDOR_MASK) 109 | 110 | #define PCI_PRODUCT_SHIFT 16 111 | #define PCI_PRODUCT_MASK 0xffff 112 | #define PCI_PRODUCT(id) \ 113 | (((id) >> PCI_PRODUCT_SHIFT) & PCI_PRODUCT_MASK) 114 | 115 | #define PCI_ID_CODE(vid,pid) \ 116 | ((((vid) & PCI_VENDOR_MASK) << PCI_VENDOR_SHIFT) | \ 117 | (((pid) & PCI_PRODUCT_MASK) << PCI_PRODUCT_SHIFT)) \ 118 | 119 | /* 120 | * Command and status register. 121 | */ 122 | #define PCI_COMMAND_STATUS_REG 0x04 123 | #define PCI_COMMAND_SHIFT 0 124 | #define PCI_COMMAND_MASK 0xffff 125 | #define PCI_STATUS_SHIFT 16 126 | #define PCI_STATUS_MASK 0xffff 127 | 128 | #define PCI_COMMAND_STATUS_CODE(cmd,stat) \ 129 | ((((cmd) & PCI_COMMAND_MASK) >> PCI_COMMAND_SHIFT) | \ 130 | (((stat) & PCI_STATUS_MASK) >> PCI_STATUS_SHIFT)) \ 131 | 132 | #define PCI_COMMAND_IO_ENABLE 0x00000001 133 | #define PCI_COMMAND_MEM_ENABLE 0x00000002 134 | #define PCI_COMMAND_MASTER_ENABLE 0x00000004 135 | #define PCI_COMMAND_SPECIAL_ENABLE 0x00000008 136 | #define PCI_COMMAND_INVALIDATE_ENABLE 0x00000010 137 | #define PCI_COMMAND_PALETTE_ENABLE 0x00000020 138 | #define PCI_COMMAND_PARITY_ENABLE 0x00000040 139 | #define PCI_COMMAND_STEPPING_ENABLE 0x00000080 140 | #define PCI_COMMAND_SERR_ENABLE 0x00000100 141 | #define PCI_COMMAND_BACKTOBACK_ENABLE 0x00000200 142 | 143 | #define PCI_STATUS_CAPLIST_SUPPORT 0x00100000 144 | #define PCI_STATUS_66MHZ_SUPPORT 0x00200000 145 | #define PCI_STATUS_UDF_SUPPORT 0x00400000 146 | #define PCI_STATUS_BACKTOBACK_SUPPORT 0x00800000 147 | #define PCI_STATUS_PARITY_ERROR 0x01000000 148 | #define PCI_STATUS_DEVSEL_FAST 0x00000000 149 | #define PCI_STATUS_DEVSEL_MEDIUM 0x02000000 150 | #define PCI_STATUS_DEVSEL_SLOW 0x04000000 151 | #define PCI_STATUS_DEVSEL_MASK 0x06000000 152 | #define PCI_STATUS_TARGET_TARGET_ABORT 0x08000000 153 | #define PCI_STATUS_MASTER_TARGET_ABORT 0x10000000 154 | #define PCI_STATUS_MASTER_ABORT 0x20000000 155 | #define PCI_STATUS_SPECIAL_ERROR 0x40000000 156 | #define PCI_STATUS_PARITY_DETECT 0x80000000 157 | 158 | /* 159 | * PCI Class and Revision Register; defines type and revision of device. 160 | */ 161 | #define PCI_CLASS_REG 0x08 162 | 163 | typedef uint8_t pci_class_t; 164 | typedef uint8_t pci_subclass_t; 165 | typedef uint8_t pci_interface_t; 166 | typedef uint8_t pci_revision_t; 167 | 168 | #define PCI_CLASS_SHIFT 24 169 | #define PCI_CLASS_MASK 0xff 170 | #define PCI_CLASS(cr) \ 171 | (((cr) >> PCI_CLASS_SHIFT) & PCI_CLASS_MASK) 172 | 173 | #define PCI_SUBCLASS_SHIFT 16 174 | #define PCI_SUBCLASS_MASK 0xff 175 | #define PCI_SUBCLASS(cr) \ 176 | (((cr) >> PCI_SUBCLASS_SHIFT) & PCI_SUBCLASS_MASK) 177 | 178 | #define PCI_INTERFACE_SHIFT 8 179 | #define PCI_INTERFACE_MASK 0xff 180 | #define PCI_INTERFACE(cr) \ 181 | (((cr) >> PCI_INTERFACE_SHIFT) & PCI_INTERFACE_MASK) 182 | 183 | #define PCI_REVISION_SHIFT 0 184 | #define PCI_REVISION_MASK 0xff 185 | #define PCI_REVISION(cr) \ 186 | (((cr) >> PCI_REVISION_SHIFT) & PCI_REVISION_MASK) 187 | 188 | #define PCI_CLASS_CODE(mainclass, subclass, interface) \ 189 | ((((mainclass) & PCI_CLASS_MASK) << PCI_CLASS_SHIFT) | \ 190 | (((subclass) & PCI_SUBCLASS_MASK) << PCI_SUBCLASS_SHIFT) | \ 191 | (((interface) & PCI_INTERFACE_MASK) << PCI_INTERFACE_SHIFT)) 192 | 193 | /* base classes */ 194 | #define PCI_CLASS_PREHISTORIC 0x00 195 | #define PCI_CLASS_MASS_STORAGE 0x01 196 | #define PCI_CLASS_NETWORK 0x02 197 | #define PCI_CLASS_DISPLAY 0x03 198 | #define PCI_CLASS_MULTIMEDIA 0x04 199 | #define PCI_CLASS_MEMORY 0x05 200 | #define PCI_CLASS_BRIDGE 0x06 201 | #define PCI_CLASS_COMMUNICATIONS 0x07 202 | #define PCI_CLASS_SYSTEM 0x08 203 | #define PCI_CLASS_INPUT 0x09 204 | #define PCI_CLASS_DOCK 0x0a 205 | #define PCI_CLASS_PROCESSOR 0x0b 206 | #define PCI_CLASS_SERIALBUS 0x0c 207 | #define PCI_CLASS_WIRELESS 0x0d 208 | #define PCI_CLASS_I2O 0x0e 209 | #define PCI_CLASS_SATCOM 0x0f 210 | #define PCI_CLASS_CRYPTO 0x10 211 | #define PCI_CLASS_DASP 0x11 212 | #define PCI_CLASS_UNDEFINED 0xff 213 | 214 | /* 0x00 prehistoric subclasses */ 215 | #define PCI_SUBCLASS_PREHISTORIC_MISC 0x00 216 | #define PCI_SUBCLASS_PREHISTORIC_VGA 0x01 217 | 218 | /* 0x01 mass storage subclasses */ 219 | #define PCI_SUBCLASS_MASS_STORAGE_SCSI 0x00 220 | #define PCI_SUBCLASS_MASS_STORAGE_IDE 0x01 221 | #define PCI_SUBCLASS_MASS_STORAGE_FLOPPY 0x02 222 | #define PCI_SUBCLASS_MASS_STORAGE_IPI 0x03 223 | #define PCI_SUBCLASS_MASS_STORAGE_RAID 0x04 224 | #define PCI_SUBCLASS_MASS_STORAGE_ATA 0x05 225 | #define PCI_SUBCLASS_MASS_STORAGE_SATA 0x06 226 | #define PCI_SUBCLASS_MASS_STORAGE_MISC 0x80 227 | 228 | /* 0x02 network subclasses */ 229 | #define PCI_SUBCLASS_NETWORK_ETHERNET 0x00 230 | #define PCI_SUBCLASS_NETWORK_TOKENRING 0x01 231 | #define PCI_SUBCLASS_NETWORK_FDDI 0x02 232 | #define PCI_SUBCLASS_NETWORK_ATM 0x03 233 | #define PCI_SUBCLASS_NETWORK_ISDN 0x04 234 | #define PCI_SUBCLASS_NETWORK_WORLDFIP 0x05 235 | #define PCI_SUBCLASS_NETWORK_PCIMGMULTICOMP 0x06 236 | #define PCI_SUBCLASS_NETWORK_MISC 0x80 237 | 238 | /* 0x03 display subclasses */ 239 | #define PCI_SUBCLASS_DISPLAY_VGA 0x00 240 | #define PCI_SUBCLASS_DISPLAY_XGA 0x01 241 | #define PCI_SUBCLASS_DISPLAY_3D 0x02 242 | #define PCI_SUBCLASS_DISPLAY_MISC 0x80 243 | 244 | /* 0x04 multimedia subclasses */ 245 | #define PCI_SUBCLASS_MULTIMEDIA_VIDEO 0x00 246 | #define PCI_SUBCLASS_MULTIMEDIA_AUDIO 0x01 247 | #define PCI_SUBCLASS_MULTIMEDIA_TELEPHONY 0x02 248 | #define PCI_SUBCLASS_MULTIMEDIA_MISC 0x80 249 | 250 | /* 0x05 memory subclasses */ 251 | #define PCI_SUBCLASS_MEMORY_RAM 0x00 252 | #define PCI_SUBCLASS_MEMORY_FLASH 0x01 253 | #define PCI_SUBCLASS_MEMORY_MISC 0x80 254 | 255 | /* 0x06 bridge subclasses */ 256 | #define PCI_SUBCLASS_BRIDGE_HOST 0x00 257 | #define PCI_SUBCLASS_BRIDGE_ISA 0x01 258 | #define PCI_SUBCLASS_BRIDGE_EISA 0x02 259 | #define PCI_SUBCLASS_BRIDGE_MC 0x03 /* XXX _MCA? */ 260 | #define PCI_SUBCLASS_BRIDGE_PCI 0x04 261 | #define PCI_SUBCLASS_BRIDGE_PCMCIA 0x05 262 | #define PCI_SUBCLASS_BRIDGE_NUBUS 0x06 263 | #define PCI_SUBCLASS_BRIDGE_CARDBUS 0x07 264 | #define PCI_SUBCLASS_BRIDGE_RACEWAY 0x08 265 | #define PCI_SUBCLASS_BRIDGE_STPCI 0x09 266 | #define PCI_SUBCLASS_BRIDGE_INFINIBAND 0x0a 267 | #define PCI_SUBCLASS_BRIDGE_MISC 0x80 268 | 269 | /* 0x07 communications subclasses */ 270 | #define PCI_SUBCLASS_COMMUNICATIONS_SERIAL 0x00 271 | #define PCI_SUBCLASS_COMMUNICATIONS_PARALLEL 0x01 272 | #define PCI_SUBCLASS_COMMUNICATIONS_MPSERIAL 0x02 273 | #define PCI_SUBCLASS_COMMUNICATIONS_MODEM 0x03 274 | #define PCI_SUBCLASS_COMMUNICATIONS_GPIB 0x04 275 | #define PCI_SUBCLASS_COMMUNICATIONS_SMARTCARD 0x05 276 | #define PCI_SUBCLASS_COMMUNICATIONS_MISC 0x80 277 | 278 | /* 0x08 system subclasses */ 279 | #define PCI_SUBCLASS_SYSTEM_PIC 0x00 280 | #define PCI_SUBCLASS_SYSTEM_DMA 0x01 281 | #define PCI_SUBCLASS_SYSTEM_TIMER 0x02 282 | #define PCI_SUBCLASS_SYSTEM_RTC 0x03 283 | #define PCI_SUBCLASS_SYSTEM_PCIHOTPLUG 0x04 284 | #define PCI_SUBCLASS_SYSTEM_MISC 0x80 285 | 286 | /* 0x09 input subclasses */ 287 | #define PCI_SUBCLASS_INPUT_KEYBOARD 0x00 288 | #define PCI_SUBCLASS_INPUT_DIGITIZER 0x01 289 | #define PCI_SUBCLASS_INPUT_MOUSE 0x02 290 | #define PCI_SUBCLASS_INPUT_SCANNER 0x03 291 | #define PCI_SUBCLASS_INPUT_GAMEPORT 0x04 292 | #define PCI_SUBCLASS_INPUT_MISC 0x80 293 | 294 | /* 0x0a dock subclasses */ 295 | #define PCI_SUBCLASS_DOCK_GENERIC 0x00 296 | #define PCI_SUBCLASS_DOCK_MISC 0x80 297 | 298 | /* 0x0b processor subclasses */ 299 | #define PCI_SUBCLASS_PROCESSOR_386 0x00 300 | #define PCI_SUBCLASS_PROCESSOR_486 0x01 301 | #define PCI_SUBCLASS_PROCESSOR_PENTIUM 0x02 302 | #define PCI_SUBCLASS_PROCESSOR_ALPHA 0x10 303 | #define PCI_SUBCLASS_PROCESSOR_POWERPC 0x20 304 | #define PCI_SUBCLASS_PROCESSOR_MIPS 0x30 305 | #define PCI_SUBCLASS_PROCESSOR_COPROC 0x40 306 | 307 | /* 0x0c serial bus subclasses */ 308 | #define PCI_SUBCLASS_SERIALBUS_FIREWIRE 0x00 309 | #define PCI_SUBCLASS_SERIALBUS_ACCESS 0x01 310 | #define PCI_SUBCLASS_SERIALBUS_SSA 0x02 311 | #define PCI_SUBCLASS_SERIALBUS_USB 0x03 312 | #define PCI_SUBCLASS_SERIALBUS_FIBER 0x04 /* XXX _FIBRECHANNEL */ 313 | #define PCI_SUBCLASS_SERIALBUS_SMBUS 0x05 314 | #define PCI_SUBCLASS_SERIALBUS_INFINIBAND 0x06 315 | #define PCI_SUBCLASS_SERIALBUS_IPMI 0x07 316 | #define PCI_SUBCLASS_SERIALBUS_SERCOS 0x08 317 | #define PCI_SUBCLASS_SERIALBUS_CANBUS 0x09 318 | 319 | /* 0x0d wireless subclasses */ 320 | #define PCI_SUBCLASS_WIRELESS_IRDA 0x00 321 | #define PCI_SUBCLASS_WIRELESS_CONSUMERIR 0x01 322 | #define PCI_SUBCLASS_WIRELESS_RF 0x10 323 | #define PCI_SUBCLASS_WIRELESS_BLUETOOTH 0x11 324 | #define PCI_SUBCLASS_WIRELESS_BROADBAND 0x12 325 | #define PCI_SUBCLASS_WIRELESS_802_11A 0x20 326 | #define PCI_SUBCLASS_WIRELESS_802_11B 0x21 327 | #define PCI_SUBCLASS_WIRELESS_MISC 0x80 328 | 329 | /* 0x0e I2O (Intelligent I/O) subclasses */ 330 | #define PCI_SUBCLASS_I2O_STANDARD 0x00 331 | 332 | /* 0x0f satellite communication subclasses */ 333 | /* PCI_SUBCLASS_SATCOM_??? 0x00 / * XXX ??? */ 334 | #define PCI_SUBCLASS_SATCOM_TV 0x01 335 | #define PCI_SUBCLASS_SATCOM_AUDIO 0x02 336 | #define PCI_SUBCLASS_SATCOM_VOICE 0x03 337 | #define PCI_SUBCLASS_SATCOM_DATA 0x04 338 | 339 | /* 0x10 encryption/decryption subclasses */ 340 | #define PCI_SUBCLASS_CRYPTO_NETCOMP 0x00 341 | #define PCI_SUBCLASS_CRYPTO_ENTERTAINMENT 0x10 342 | #define PCI_SUBCLASS_CRYPTO_MISC 0x80 343 | 344 | /* 0x11 data acquisition and signal processing subclasses */ 345 | #define PCI_SUBCLASS_DASP_DPIO 0x00 346 | #define PCI_SUBCLASS_DASP_TIMEFREQ 0x01 347 | #define PCI_SUBCLASS_DASP_SYNC 0x10 348 | #define PCI_SUBCLASS_DASP_MGMT 0x20 349 | #define PCI_SUBCLASS_DASP_MISC 0x80 350 | 351 | /* 352 | * PCI BIST/Header Type/Latency Timer/Cache Line Size Register. 353 | */ 354 | #define PCI_BHLC_REG 0x0c 355 | 356 | #define PCI_BIST_SHIFT 24 357 | #define PCI_BIST_MASK 0xff 358 | #define PCI_BIST(bhlcr) \ 359 | (((bhlcr) >> PCI_BIST_SHIFT) & PCI_BIST_MASK) 360 | 361 | #define PCI_HDRTYPE_SHIFT 16 362 | #define PCI_HDRTYPE_MASK 0xff 363 | #define PCI_HDRTYPE(bhlcr) \ 364 | (((bhlcr) >> PCI_HDRTYPE_SHIFT) & PCI_HDRTYPE_MASK) 365 | 366 | #define PCI_HDRTYPE_TYPE(bhlcr) \ 367 | (PCI_HDRTYPE(bhlcr) & 0x7f) 368 | #define PCI_HDRTYPE_MULTIFN(bhlcr) \ 369 | ((PCI_HDRTYPE(bhlcr) & 0x80) != 0) 370 | 371 | #define PCI_LATTIMER_SHIFT 8 372 | #define PCI_LATTIMER_MASK 0xff 373 | #define PCI_LATTIMER(bhlcr) \ 374 | (((bhlcr) >> PCI_LATTIMER_SHIFT) & PCI_LATTIMER_MASK) 375 | 376 | #define PCI_CACHELINE_SHIFT 0 377 | #define PCI_CACHELINE_MASK 0xff 378 | #define PCI_CACHELINE(bhlcr) \ 379 | (((bhlcr) >> PCI_CACHELINE_SHIFT) & PCI_CACHELINE_MASK) 380 | 381 | #define PCI_BHLC_CODE(bist,type,multi,latency,cacheline) \ 382 | ((((bist) & PCI_BIST_MASK) << PCI_BIST_SHIFT) | \ 383 | (((type) & PCI_HDRTYPE_MASK) << PCI_HDRTYPE_SHIFT) | \ 384 | (((multi)?0x80:0) << PCI_HDRTYPE_SHIFT) | \ 385 | (((latency) & PCI_LATTIMER_MASK) << PCI_LATTIMER_SHIFT) | \ 386 | (((cacheline) & PCI_CACHELINE_MASK) << PCI_CACHELINE_SHIFT)) 387 | 388 | /* 389 | * PCI header type 390 | */ 391 | #define PCI_HDRTYPE_DEVICE 0 392 | #define PCI_HDRTYPE_PPB 1 393 | #define PCI_HDRTYPE_PCB 2 394 | 395 | /* 396 | * Mapping registers 397 | */ 398 | #define PCI_MAPREG_START 0x10 399 | #define PCI_MAPREG_END 0x28 400 | #define PCI_MAPREG_ROM 0x30 401 | #define PCI_MAPREG_PPB_END 0x18 402 | #define PCI_MAPREG_PCB_END 0x14 403 | 404 | #define PCI_MAPREG_TYPE(mr) \ 405 | ((mr) & PCI_MAPREG_TYPE_MASK) 406 | #define PCI_MAPREG_TYPE_MASK 0x00000001 407 | 408 | #define PCI_MAPREG_TYPE_MEM 0x00000000 409 | #define PCI_MAPREG_TYPE_IO 0x00000001 410 | #define PCI_MAPREG_ROM_ENABLE 0x00000001 411 | 412 | #define PCI_MAPREG_MEM_TYPE(mr) \ 413 | ((mr) & PCI_MAPREG_MEM_TYPE_MASK) 414 | #define PCI_MAPREG_MEM_TYPE_MASK 0x00000006 415 | 416 | #define PCI_MAPREG_MEM_TYPE_32BIT 0x00000000 417 | #define PCI_MAPREG_MEM_TYPE_32BIT_1M 0x00000002 418 | #define PCI_MAPREG_MEM_TYPE_64BIT 0x00000004 419 | 420 | #define PCI_MAPREG_MEM_PREFETCHABLE(mr) \ 421 | (((mr) & PCI_MAPREG_MEM_PREFETCHABLE_MASK) != 0) 422 | #define PCI_MAPREG_MEM_PREFETCHABLE_MASK 0x00000008 423 | 424 | #define PCI_MAPREG_MEM_ADDR(mr) \ 425 | ((mr) & PCI_MAPREG_MEM_ADDR_MASK) 426 | #define PCI_MAPREG_MEM_SIZE(mr) \ 427 | (PCI_MAPREG_MEM_ADDR(mr) & -PCI_MAPREG_MEM_ADDR(mr)) 428 | #define PCI_MAPREG_MEM_ADDR_MASK 0xfffffff0 429 | 430 | #define PCI_MAPREG_MEM64_ADDR(mr) \ 431 | ((mr) & PCI_MAPREG_MEM64_ADDR_MASK) 432 | #define PCI_MAPREG_MEM64_SIZE(mr) \ 433 | (PCI_MAPREG_MEM64_ADDR(mr) & -PCI_MAPREG_MEM64_ADDR(mr)) 434 | #define PCI_MAPREG_MEM64_ADDR_MASK 0xfffffffffffffff0ULL 435 | 436 | #define PCI_MAPREG_IO_ADDR(mr) \ 437 | ((mr) & PCI_MAPREG_IO_ADDR_MASK) 438 | #define PCI_MAPREG_IO_SIZE(mr) \ 439 | (PCI_MAPREG_IO_ADDR(mr) & -PCI_MAPREG_IO_ADDR(mr)) 440 | #define PCI_MAPREG_IO_ADDR_MASK 0xfffffffc 441 | 442 | #define PCI_MAPREG_SIZE_TO_MASK(size) \ 443 | (-(size)) 444 | 445 | #define PCI_MAPREG_NUM(offset) \ 446 | (((unsigned)(offset)-PCI_MAPREG_START)/4) 447 | 448 | 449 | /* 450 | * Cardbus CIS pointer (PCI rev. 2.1) 451 | */ 452 | #define PCI_CARDBUS_CIS_REG 0x28 453 | 454 | /* 455 | * Subsystem identification register; contains a vendor ID and a device ID. 456 | * Types/macros for PCI_ID_REG apply. 457 | * (PCI rev. 2.1) 458 | */ 459 | #define PCI_SUBSYS_ID_REG 0x2c 460 | 461 | /* 462 | * Capabilities link list (PCI rev. 2.2) 463 | */ 464 | #define PCI_CAPLISTPTR_REG 0x34 /* header type 0 */ 465 | #define PCI_CARDBUS_CAPLISTPTR_REG 0x14 /* header type 2 */ 466 | #define PCI_CAPLIST_PTR(cpr) ((cpr) & 0xff) 467 | #define PCI_CAPLIST_NEXT(cr) (((cr) >> 8) & 0xff) 468 | #define PCI_CAPLIST_CAP(cr) ((cr) & 0xff) 469 | 470 | #define PCI_CAP_RESERVED0 0x00 471 | #define PCI_CAP_PWRMGMT 0x01 472 | #define PCI_CAP_AGP 0x02 473 | #define PCI_CAP_AGP_MAJOR(cr) (((cr) >> 20) & 0xf) 474 | #define PCI_CAP_AGP_MINOR(cr) (((cr) >> 16) & 0xf) 475 | #define PCI_CAP_VPD 0x03 476 | #define PCI_CAP_SLOTID 0x04 477 | #define PCI_CAP_MSI 0x05 478 | #define PCI_CAP_CPCI_HOTSWAP 0x06 479 | #define PCI_CAP_PCIX 0x07 480 | #define PCI_CAP_LDT 0x08 481 | #define PCI_CAP_VENDSPEC 0x09 482 | #define PCI_CAP_DEBUGPORT 0x0a 483 | #define PCI_CAP_CPCI_RSRCCTL 0x0b 484 | #define PCI_CAP_HOTPLUG 0x0c 485 | #define PCI_CAP_AGP8 0x0e 486 | #define PCI_CAP_SECURE 0x0f 487 | #define PCI_CAP_PCIEXPRESS 0x10 488 | #define PCI_CAP_MSIX 0x11 489 | 490 | /* 491 | * Vital Product Data; access via capability pointer (PCI rev 2.2). 492 | */ 493 | #define PCI_VPD_ADDRESS_MASK 0x7fff 494 | #define PCI_VPD_ADDRESS_SHIFT 16 495 | #define PCI_VPD_ADDRESS(ofs) \ 496 | (((ofs) & PCI_VPD_ADDRESS_MASK) << PCI_VPD_ADDRESS_SHIFT) 497 | #define PCI_VPD_DATAREG(ofs) ((ofs) + 4) 498 | #define PCI_VPD_OPFLAG 0x80000000 499 | 500 | /* 501 | * Power Management Capability; access via capability pointer. 502 | */ 503 | 504 | /* Power Management Capability Register */ 505 | #define PCI_PMCR 0x02 506 | #define PCI_PMCR_D1SUPP 0x0200 507 | #define PCI_PMCR_D2SUPP 0x0400 508 | /* Power Management Control Status Register */ 509 | #define PCI_PMCSR 0x04 510 | #define PCI_PMCSR_STATE_MASK 0x03 511 | #define PCI_PMCSR_STATE_D0 0x00 512 | #define PCI_PMCSR_STATE_D1 0x01 513 | #define PCI_PMCSR_STATE_D2 0x02 514 | #define PCI_PMCSR_STATE_D3 0x03 515 | 516 | /* 517 | * PCI-X capability. 518 | */ 519 | 520 | /* 521 | * Command. 16 bits at offset 2 (e.g. upper 16 bits of the first 32-bit 522 | * word at the capability; the lower 16 bits are the capability ID and 523 | * next capability pointer). 524 | * 525 | * Since we always read PCI config space in 32-bit words, we define these 526 | * as 32-bit values, offset and shifted appropriately. Make sure you perform 527 | * the appropriate R/M/W cycles! 528 | */ 529 | #define PCI_PCIX_CMD 0x00 530 | #define PCI_PCIX_CMD_PERR_RECOVER 0x00010000 531 | #define PCI_PCIX_CMD_RELAXED_ORDER 0x00020000 532 | #define PCI_PCIX_CMD_BYTECNT_MASK 0x000c0000 533 | #define PCI_PCIX_CMD_BYTECNT_SHIFT 18 534 | #define PCI_PCIX_CMD_BCNT_512 0x00000000 535 | #define PCI_PCIX_CMD_BCNT_1024 0x00040000 536 | #define PCI_PCIX_CMD_BCNT_2048 0x00080000 537 | #define PCI_PCIX_CMD_BCNT_4096 0x000c0000 538 | #define PCI_PCIX_CMD_SPLTRANS_MASK 0x00700000 539 | #define PCI_PCIX_CMD_SPLTRANS_1 0x00000000 540 | #define PCI_PCIX_CMD_SPLTRANS_2 0x00100000 541 | #define PCI_PCIX_CMD_SPLTRANS_3 0x00200000 542 | #define PCI_PCIX_CMD_SPLTRANS_4 0x00300000 543 | #define PCI_PCIX_CMD_SPLTRANS_8 0x00400000 544 | #define PCI_PCIX_CMD_SPLTRANS_12 0x00500000 545 | #define PCI_PCIX_CMD_SPLTRANS_16 0x00600000 546 | #define PCI_PCIX_CMD_SPLTRANS_32 0x00700000 547 | 548 | /* 549 | * Status. 32 bits at offset 4. 550 | */ 551 | #define PCI_PCIX_STATUS 0x04 552 | #define PCI_PCIX_STATUS_FN_MASK 0x00000007 553 | #define PCI_PCIX_STATUS_DEV_MASK 0x000000f8 554 | #define PCI_PCIX_STATUS_BUS_MASK 0x0000ff00 555 | #define PCI_PCIX_STATUS_64BIT 0x00010000 556 | #define PCI_PCIX_STATUS_133 0x00020000 557 | #define PCI_PCIX_STATUS_SPLDISC 0x00040000 558 | #define PCI_PCIX_STATUS_SPLUNEX 0x00080000 559 | #define PCI_PCIX_STATUS_DEVCPLX 0x00100000 560 | #define PCI_PCIX_STATUS_MAXB_MASK 0x00600000 561 | #define PCI_PCIX_STATUS_MAXB_SHIFT 21 562 | #define PCI_PCIX_STATUS_MAXB_512 0x00000000 563 | #define PCI_PCIX_STATUS_MAXB_1024 0x00200000 564 | #define PCI_PCIX_STATUS_MAXB_2048 0x00400000 565 | #define PCI_PCIX_STATUS_MAXB_4096 0x00600000 566 | #define PCI_PCIX_STATUS_MAXST_MASK 0x03800000 567 | #define PCI_PCIX_STATUS_MAXST_1 0x00000000 568 | #define PCI_PCIX_STATUS_MAXST_2 0x00800000 569 | #define PCI_PCIX_STATUS_MAXST_3 0x01000000 570 | #define PCI_PCIX_STATUS_MAXST_4 0x01800000 571 | #define PCI_PCIX_STATUS_MAXST_8 0x02000000 572 | #define PCI_PCIX_STATUS_MAXST_12 0x02800000 573 | #define PCI_PCIX_STATUS_MAXST_16 0x03000000 574 | #define PCI_PCIX_STATUS_MAXST_32 0x03800000 575 | #define PCI_PCIX_STATUS_MAXRS_MASK 0x1c000000 576 | #define PCI_PCIX_STATUS_MAXRS_1K 0x00000000 577 | #define PCI_PCIX_STATUS_MAXRS_2K 0x04000000 578 | #define PCI_PCIX_STATUS_MAXRS_4K 0x08000000 579 | #define PCI_PCIX_STATUS_MAXRS_8K 0x0c000000 580 | #define PCI_PCIX_STATUS_MAXRS_16K 0x10000000 581 | #define PCI_PCIX_STATUS_MAXRS_32K 0x14000000 582 | #define PCI_PCIX_STATUS_MAXRS_64K 0x18000000 583 | #define PCI_PCIX_STATUS_MAXRS_128K 0x1c000000 584 | #define PCI_PCIX_STATUS_SCERR 0x20000000 585 | 586 | 587 | /* 588 | * Interrupt Configuration Register; contains interrupt pin and line. 589 | */ 590 | #define PCI_INTERRUPT_REG 0x3c 591 | 592 | typedef uint8_t pci_intr_latency_t; 593 | typedef uint8_t pci_intr_grant_t; 594 | typedef uint8_t pci_intr_pin_t; 595 | typedef uint8_t pci_intr_line_t; 596 | 597 | #define PCI_MAX_LAT_SHIFT 24 598 | #define PCI_MAX_LAT_MASK 0xff 599 | #define PCI_MAX_LAT(icr) \ 600 | (((icr) >> PCI_MAX_LAT_SHIFT) & PCI_MAX_LAT_MASK) 601 | 602 | #define PCI_MIN_GNT_SHIFT 16 603 | #define PCI_MIN_GNT_MASK 0xff 604 | #define PCI_MIN_GNT(icr) \ 605 | (((icr) >> PCI_MIN_GNT_SHIFT) & PCI_MIN_GNT_MASK) 606 | 607 | #define PCI_INTERRUPT_GRANT_SHIFT 24 608 | #define PCI_INTERRUPT_GRANT_MASK 0xff 609 | #define PCI_INTERRUPT_GRANT(icr) \ 610 | (((icr) >> PCI_INTERRUPT_GRANT_SHIFT) & PCI_INTERRUPT_GRANT_MASK) 611 | 612 | #define PCI_INTERRUPT_LATENCY_SHIFT 16 613 | #define PCI_INTERRUPT_LATENCY_MASK 0xff 614 | #define PCI_INTERRUPT_LATENCY(icr) \ 615 | (((icr) >> PCI_INTERRUPT_LATENCY_SHIFT) & PCI_INTERRUPT_LATENCY_MASK) 616 | 617 | #define PCI_INTERRUPT_PIN_SHIFT 8 618 | #define PCI_INTERRUPT_PIN_MASK 0xff 619 | #define PCI_INTERRUPT_PIN(icr) \ 620 | (((icr) >> PCI_INTERRUPT_PIN_SHIFT) & PCI_INTERRUPT_PIN_MASK) 621 | 622 | #define PCI_INTERRUPT_LINE_SHIFT 0 623 | #define PCI_INTERRUPT_LINE_MASK 0xff 624 | #define PCI_INTERRUPT_LINE(icr) \ 625 | (((icr) >> PCI_INTERRUPT_LINE_SHIFT) & PCI_INTERRUPT_LINE_MASK) 626 | 627 | #define PCI_INTERRUPT_CODE(lat,gnt,pin,line) \ 628 | ((((lat)&PCI_INTERRUPT_LATENCY_MASK)<> 3) & 0xf) 713 | 714 | #define PCI_VPDRES_LARGE_NAME(x) ((x) & 0x7f) 715 | 716 | #define PCI_VPDRES_TYPE_COMPATIBLE_DEVICE_ID 0x3 /* small */ 717 | #define PCI_VPDRES_TYPE_VENDOR_DEFINED 0xe /* small */ 718 | #define PCI_VPDRES_TYPE_END_TAG 0xf /* small */ 719 | 720 | #define PCI_VPDRES_TYPE_IDENTIFIER_STRING 0x02 /* large */ 721 | #define PCI_VPDRES_TYPE_VPD 0x10 /* large */ 722 | 723 | struct pci_vpd { 724 | uint8_t vpd_key0; 725 | uint8_t vpd_key1; 726 | uint8_t vpd_len; /* length of data only */ 727 | /* Actual data. */ 728 | } __attribute__((__packed__)); 729 | 730 | /* 731 | * Recommended VPD fields: 732 | * 733 | * PN Part number of assembly 734 | * FN FRU part number 735 | * EC EC level of assembly 736 | * MN Manufacture ID 737 | * SN Serial Number 738 | * 739 | * Conditionally recommended VPD fields: 740 | * 741 | * LI Load ID 742 | * RL ROM Level 743 | * RM Alterable ROM Level 744 | * NA Network Address 745 | * DD Device Driver Level 746 | * DG Diagnostic Level 747 | * LL Loadable Microcode Level 748 | * VI Vendor ID/Device ID 749 | * FU Function Number 750 | * SI Subsystem Vendor ID/Subsystem ID 751 | * 752 | * Additional VPD fields: 753 | * 754 | * Z0-ZZ User/Product Specific 755 | */ 756 | 757 | /* 758 | * Threshold below which 32bit PCI DMA needs bouncing. 759 | */ 760 | #define PCI32_DMA_BOUNCE_THRESHOLD 0x100000000ULL 761 | 762 | #endif /* _DEV_PCI_PCIREG_H_ */ 763 | ``` 764 |
765 | 766 |
767 | pci.c 768 | 769 | ```c 770 | #include "types.h" 771 | #include "defs.h" 772 | #include "x86.h" 773 | #include "pci.h" 774 | #include "pcireg.h" 775 | 776 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 777 | 778 | // Flag to do "lspci" at bootup 779 | static int pci_show_devs = 1; 780 | static int pci_show_addrs = 0; 781 | 782 | // PCI "configuration mechanism one" 783 | static uint32_t pci_conf1_addr_ioport = 0x0cf8; 784 | static uint32_t pci_conf1_data_ioport = 0x0cfc; 785 | 786 | // Forward declarations 787 | static int pci_bridge_attach(struct pci_func *pcif); 788 | 789 | // PCI driver table 790 | struct pci_driver { 791 | uint32_t key1, key2; 792 | int (*attachfn) (struct pci_func *pcif); 793 | }; 794 | 795 | // pci_attach_class matches the class and subclass of a PCI device 796 | struct pci_driver pci_attach_class[] = { 797 | { PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI, &pci_bridge_attach }, 798 | { 0, 0, 0 }, 799 | }; 800 | 801 | // pci_attach_vendor matches the vendor ID and device ID of a PCI device. key1 802 | // and key2 should be the vendor ID and device ID respectively 803 | struct pci_driver pci_attach_vendor[] = { 804 | { 0, 0, 0 }, 805 | }; 806 | 807 | static void 808 | pci_conf1_set_addr(uint32_t bus, 809 | uint32_t dev, 810 | uint32_t func, 811 | uint32_t offset) 812 | { 813 | assert(bus < 256); 814 | assert(dev < 32); 815 | assert(func < 8); 816 | assert(offset < 256); 817 | assert((offset & 0x3) == 0); 818 | 819 | uint32_t v = (1 << 31) | // config-space 820 | (bus << 16) | (dev << 11) | (func << 8) | (offset); 821 | outl(pci_conf1_addr_ioport, v); 822 | } 823 | 824 | static uint32_t 825 | pci_conf_read(struct pci_func *f, uint32_t off) 826 | { 827 | pci_conf1_set_addr(f->bus->busno, f->dev, f->func, off); 828 | return inl(pci_conf1_data_ioport); 829 | } 830 | 831 | static void 832 | pci_conf_write(struct pci_func *f, uint32_t off, uint32_t v) 833 | { 834 | pci_conf1_set_addr(f->bus->busno, f->dev, f->func, off); 835 | outl(pci_conf1_data_ioport, v); 836 | } 837 | 838 | static int __attribute__((warn_unused_result)) 839 | pci_attach_match(uint32_t key1, uint32_t key2, 840 | struct pci_driver *list, struct pci_func *pcif) 841 | { 842 | uint32_t i; 843 | 844 | for (i = 0; list[i].attachfn; i++) { 845 | if (list[i].key1 == key1 && list[i].key2 == key2) { 846 | int r = list[i].attachfn(pcif); 847 | if (r > 0) 848 | return r; 849 | if (r < 0) 850 | cprintf("pci_attach_match: attaching " 851 | "%x.%x (%p): e\n", 852 | key1, key2, list[i].attachfn, r); 853 | } 854 | } 855 | return 0; 856 | } 857 | 858 | static int 859 | pci_attach(struct pci_func *f) 860 | { 861 | return 862 | pci_attach_match(PCI_CLASS(f->dev_class), 863 | PCI_SUBCLASS(f->dev_class), 864 | &pci_attach_class[0], f) || 865 | pci_attach_match(PCI_VENDOR(f->dev_id), 866 | PCI_PRODUCT(f->dev_id), 867 | &pci_attach_vendor[0], f); 868 | } 869 | 870 | static const char *pci_class[] = 871 | { 872 | [0x0] = "Unknown", 873 | [0x1] = "Storage controller", 874 | [0x2] = "Network controller", 875 | [0x3] = "Display controller", 876 | [0x4] = "Multimedia device", 877 | [0x5] = "Memory controller", 878 | [0x6] = "Bridge device", 879 | }; 880 | 881 | static void 882 | pci_print_func(struct pci_func *f) 883 | { 884 | const char *class = pci_class[0]; 885 | if (PCI_CLASS(f->dev_class) < ARRAY_SIZE(pci_class)) 886 | class = pci_class[PCI_CLASS(f->dev_class)]; 887 | 888 | cprintf("PCI: %02x:%02x.%d: %04x:%04x: class: %x.%x (%s) irq: %d\n", 889 | f->bus->busno, f->dev, f->func, 890 | PCI_VENDOR(f->dev_id), PCI_PRODUCT(f->dev_id), 891 | PCI_CLASS(f->dev_class), PCI_SUBCLASS(f->dev_class), class, 892 | f->irq_line); 893 | } 894 | 895 | static int 896 | pci_scan_bus(struct pci_bus *bus) 897 | { 898 | int totaldev = 0; 899 | struct pci_func df; 900 | memset(&df, 0, sizeof(df)); 901 | df.bus = bus; 902 | 903 | for (df.dev = 0; df.dev < 32; df.dev++) { 904 | uint32_t bhlc = pci_conf_read(&df, PCI_BHLC_REG); 905 | if (PCI_HDRTYPE_TYPE(bhlc) > 1) // Unsupported or no device 906 | continue; 907 | 908 | totaldev++; 909 | 910 | struct pci_func f = df; 911 | for (f.func = 0; f.func < (PCI_HDRTYPE_MULTIFN(bhlc) ? 8 : 1); 912 | f.func++) { 913 | struct pci_func af = f; 914 | 915 | af.dev_id = pci_conf_read(&f, PCI_ID_REG); 916 | if (PCI_VENDOR(af.dev_id) == 0xffff) 917 | continue; 918 | 919 | uint32_t intr = pci_conf_read(&af, PCI_INTERRUPT_REG); 920 | af.irq_line = PCI_INTERRUPT_LINE(intr); 921 | 922 | af.dev_class = pci_conf_read(&af, PCI_CLASS_REG); 923 | if (pci_show_devs) 924 | pci_print_func(&af); 925 | pci_attach(&af); 926 | } 927 | } 928 | 929 | return totaldev; 930 | } 931 | 932 | static int 933 | pci_bridge_attach(struct pci_func *pcif) 934 | { 935 | uint32_t ioreg = pci_conf_read(pcif, PCI_BRIDGE_STATIO_REG); 936 | uint32_t busreg = pci_conf_read(pcif, PCI_BRIDGE_BUS_REG); 937 | 938 | if (PCI_BRIDGE_IO_32BITS(ioreg)) { 939 | cprintf("PCI: %02x:%02x.%d: 32-bit bridge IO not supported.\n", 940 | pcif->bus->busno, pcif->dev, pcif->func); 941 | return 0; 942 | } 943 | 944 | struct pci_bus nbus; 945 | memset(&nbus, 0, sizeof(nbus)); 946 | nbus.parent_bridge = pcif; 947 | nbus.busno = (busreg >> PCI_BRIDGE_BUS_SECONDARY_SHIFT) & 0xff; 948 | 949 | if (pci_show_devs) 950 | cprintf("PCI: %02x:%02x.%d: bridge to PCI bus %d--%d\n", 951 | pcif->bus->busno, pcif->dev, pcif->func, 952 | nbus.busno, 953 | (busreg >> PCI_BRIDGE_BUS_SUBORDINATE_SHIFT) & 0xff); 954 | 955 | pci_scan_bus(&nbus); 956 | return 1; 957 | } 958 | 959 | // External PCI subsystem interface 960 | 961 | void 962 | pci_func_enable(struct pci_func *f) 963 | { 964 | pci_conf_write(f, PCI_COMMAND_STATUS_REG, 965 | PCI_COMMAND_IO_ENABLE | 966 | PCI_COMMAND_MEM_ENABLE | 967 | PCI_COMMAND_MASTER_ENABLE); 968 | 969 | uint32_t bar_width; 970 | uint32_t bar; 971 | for (bar = PCI_MAPREG_START; bar < PCI_MAPREG_END; 972 | bar += bar_width) 973 | { 974 | uint32_t oldv = pci_conf_read(f, bar); 975 | 976 | bar_width = 4; 977 | pci_conf_write(f, bar, 0xffffffff); 978 | uint32_t rv = pci_conf_read(f, bar); 979 | 980 | if (rv == 0) 981 | continue; 982 | 983 | int regnum = PCI_MAPREG_NUM(bar); 984 | uint32_t base, size; 985 | if (PCI_MAPREG_TYPE(rv) == PCI_MAPREG_TYPE_MEM) { 986 | if (PCI_MAPREG_MEM_TYPE(rv) == PCI_MAPREG_MEM_TYPE_64BIT) 987 | bar_width = 8; 988 | 989 | size = PCI_MAPREG_MEM_SIZE(rv); 990 | base = PCI_MAPREG_MEM_ADDR(oldv); 991 | if (pci_show_addrs) 992 | cprintf(" mem region %d: %d bytes at 0x%x\n", 993 | regnum, size, base); 994 | } else { 995 | size = PCI_MAPREG_IO_SIZE(rv); 996 | base = PCI_MAPREG_IO_ADDR(oldv); 997 | if (pci_show_addrs) 998 | cprintf(" io region %d: %d bytes at 0x%x\n", 999 | regnum, size, base); 1000 | } 1001 | 1002 | pci_conf_write(f, bar, oldv); 1003 | f->reg_base[regnum] = base; 1004 | f->reg_size[regnum] = size; 1005 | 1006 | if (size && !base) 1007 | cprintf("PCI device %02x:%02x.%d (%04x:%04x) " 1008 | "may be misconfigured: " 1009 | "region %d: base 0x%x, size %d\n", 1010 | f->bus->busno, f->dev, f->func, 1011 | PCI_VENDOR(f->dev_id), PCI_PRODUCT(f->dev_id), 1012 | regnum, base, size); 1013 | } 1014 | 1015 | cprintf("PCI function %02x:%02x.%d (%04x:%04x) enabled\n", 1016 | f->bus->busno, f->dev, f->func, 1017 | PCI_VENDOR(f->dev_id), PCI_PRODUCT(f->dev_id)); 1018 | } 1019 | 1020 | static int 1021 | pci_init(void) 1022 | { 1023 | static struct pci_bus root_bus; 1024 | memset(&root_bus, 0, sizeof(root_bus)); 1025 | 1026 | return pci_scan_bus(&root_bus); 1027 | } 1028 | 1029 | void 1030 | pciinit(void) 1031 | { 1032 | (void)pci_init(); 1033 | } 1034 | ``` 1035 |
1036 | 1037 | PCIバスを走査してデバイスを検出すると、デバイスとドライバの対応表からドライバの初期化関数のアドレスを取得し、それを呼び出すようになっています。 1038 | 1039 | #### ✅ I/O空間アクセス関数の追加 1040 | 1041 | I/Oポートを通じて32bitのデータを読み書きするインライン関数を追加します。 1042 | 1043 |
1044 | x86.h 1045 | 1046 | ```diff 1047 | // Routines to let C code use special x86 instructions. 1048 | 1049 | static inline uchar 1050 | inb(ushort port) 1051 | { 1052 | uchar data; 1053 | 1054 | asm volatile("in %1,%0" : "=a" (data) : "d" (port)); 1055 | return data; 1056 | } 1057 | 1058 | +static inline uint 1059 | +inl(int port) 1060 | +{ 1061 | + uint data; 1062 | + 1063 | + asm volatile("inl %w1,%0" : "=a" (data) : "d" (port)); 1064 | + return data; 1065 | +} 1066 | 1067 | ... 1068 | 1069 | static inline void 1070 | outw(ushort port, ushort data) 1071 | { 1072 | asm volatile("out %0,%1" : : "a" (data), "d" (port)); 1073 | } 1074 | + 1075 | +static inline void 1076 | +outl(int port, uint data) 1077 | +{ 1078 | + asm volatile("outl %0,%w1" : : "a" (data), "d" (port)); 1079 | +} 1080 | 1081 | ... 1082 | ``` 1083 |
1084 | 1085 | #### ✅ プロトタイプ宣言の追加 1086 | 1087 | PCI関連のコードで外部へ公開する関数のプロトタイプ宣言を`defs.h`に追加します。 1088 | 1089 |
1090 | defs.h 1091 | 1092 | ```diff 1093 | struct buf; 1094 | struct context; 1095 | struct file; 1096 | struct inode; 1097 | +struct pci_func; 1098 | struct pipe; 1099 | struct proc; 1100 | struct rtcdate; 1101 | struct spinlock; 1102 | struct sleeplock; 1103 | struct stat; 1104 | struct superblock; 1105 | struct timeval; 1106 | 1107 | ... 1108 | 1109 | // mp.c 1110 | extern int ismp; 1111 | void mpinit(void); 1112 | 1113 | +// pci.c 1114 | +void pci_func_enable(struct pci_func *f); 1115 | +void pciinit(void); 1116 | + 1117 | // picirq.c 1118 | void picenable(int); 1119 | void picinit(void); 1120 | 1121 | ... 1122 | 1123 | // number of elements in fixed-size array 1124 | #define NELEM(x) (sizeof(x)/sizeof((x)[0])) 1125 | 1126 | // variable length arguments 1127 | #define va_start(ap, last) __builtin_va_start(ap, last) 1128 | #define va_arg(ap, type) __builtin_va_arg(ap, type) 1129 | #define va_end(ap) __builtin_va_end(ap) 1130 | 1131 | +// assert 1132 | +#define _str(x) #x 1133 | +#define _tostr(x) _str(x) 1134 | +#define _assert_occurs " [" __FILE__ ":" _tostr(__LINE__) "] " 1135 | +#define assert(x) \ 1136 | + do { if (!(x)) panic("assertion failed" _assert_occurs #x); } while (0) 1137 | ``` 1138 |
1139 | 1140 | #### ✅ PCI処理化処理の呼び出し 1141 | 1142 | xv6の`main()`から`pciinit()`を呼び出すコードを追加します。 1143 | 1144 |
1145 | main.c 1146 | 1147 | ```diff 1148 | int 1149 | main(void) 1150 | { 1151 | kinit1(end, P2V(4*1024*1024)); // phys page allocator 1152 | kvmalloc(); // kernel page table 1153 | mpinit(); // detect other processors 1154 | lapicinit(); // interrupt controller 1155 | seginit(); // segment descriptors 1156 | picinit(); // disable pic 1157 | ioapicinit(); // another interrupt controller 1158 | consoleinit(); // console hardware 1159 | uartinit(); // serial port 1160 | pinit(); // process table 1161 | tvinit(); // trap vectors 1162 | binit(); // buffer cache 1163 | fileinit(); // file table 1164 | ideinit(); // disk 1165 | netinit(); // network stack 1166 | + pciinit(); // pci devices 1167 | startothers(); // start other processors 1168 | kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers() 1169 | userinit(); // first user process 1170 | mpmain(); // finish this processor's setup 1171 | } 1172 | ``` 1173 |
1174 | 1175 | #### ✅ Makefileの修正 1176 | 1177 | ソースファイルを追加したので`Makefile`を修正します。 1178 | 1179 |
1180 | Makefile 1181 | 1182 | ```diff 1183 | OBJS = \ 1184 | ... 1185 | mp.o\ 1186 | + pci.o\ 1187 | picirq.o\ 1188 | ... 1189 | ``` 1190 |
1191 | 1192 | #### ✅ 動作確認 1193 | 1194 | 再ビルドした後、`make qemu-nox`を実行してxv6を起動します。 1195 | 1196 |
1197 | ログメッセージ 1198 | 1199 | ``` 1200 | SeaBIOS (version 1.15.0-1) 1201 | 1202 | 1203 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 1204 | 1205 | 1206 | 1207 | Booting from Hard Disk..xv6... 1208 | 02:39:27.000 [D] netinit: Hello, SecCamp2024! (net/net.c:10) 1209 | +------+-------------------------------------------------+------------------+ 1210 | | 0000 | 48 65 6c 6c 6f 2c 20 53 65 63 43 61 6d 70 32 30 | Hello, SecCamp20 | 1211 | | 0010 | 32 34 21 00 | 24!. | 1212 | +------+-------------------------------------------------+------------------+ 1213 | PCI: 00:00.0: 8086:1237: class: 6.0 (Bridge device) irq: 0 1214 | PCI: 00:01.0: 8086:7000: class: 6.1 (Bridge device) irq: 0 1215 | PCI: 00:01.1: 8086:7010: class: 1.1 (Storage controller) irq: 0 1216 | PCI: 00:01.3: 8086:7113: class: 6.80 (Bridge device) irq: 9 1217 | PCI: 00:02.0: 1234:1111: class: 3.0 (Display controller) irq: 0 1218 | PCI: 00:03.0: 8086:100e: class: 2.0 (Network controller) irq: 11 1219 | cpu0: starting 0 1220 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 1221 | init: starting sh 1222 | $ 1223 | ``` 1224 |
1225 | 1226 | 起動の途中でPCIバスの走査が行われ、複数のデバイスを検出していることが確認できます。 1227 | 1228 | ## 3.2. Intel e1000ドライバ 1229 | 1230 | QEMUが提供するネットワークデバイスを検出できるようになりました。 1231 | 1232 | ``` 1233 | PCI: 00:03.0: 8086:100e: class: 2.0 (Network controller) irq: 11 1234 | ``` 1235 | 1236 | これはIntelの82545EMというネットワークデバイス(通称:e1000)をエミュレートしたものです。xv6から見ると本物のネットワークデバイスとして見えてるので、このネットワークデバイスのドライバを追加します。 1237 | 1238 | 参考資料 1239 | 1240 | + https://pdos.csail.mit.edu/6.828/2020/readings/8254x_GBe_SDM.pdf 1241 | + http://yuma.ohgami.jp/x86_64-Jisaku-OS-4/ 1242 | 1243 | #### ✅ ディレクトリの作成 1244 | 1245 | プラットフォーム固有のドライバを配置するためのディレクトリを作成します。 1246 | 1247 | ``` 1248 | $ mkdir net/platform/xv6/driver 1249 | ``` 1250 | 1251 | ソースコードを配置するディレクトリを追加したので`Makefile`を修正します。 1252 | 1253 |
1254 | Makefile 1255 | 1256 | ```diff 1257 | ... 1258 | 1259 | clean: 1260 | rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ 1261 | net/*.o net/*.d net/platform/xv6/*.o net/platform/xv6/*.d \ 1262 | + net/platform/xv6/driver/*.o net/platform/xv6/driver/*.d \ 1263 | *.o *.d *.asm *.sym vectors.S bootblock entryother \ 1264 | initcode initcode.out kernel xv6.img fs.img kernelmemfs \ 1265 | xv6memfs.img mkfs .gdbinit \ 1266 | $(UPROGS) 1267 | 1268 | ... 1269 | ``` 1270 |
1271 | 1272 | #### ✅ ドライバのコード 1273 | 1274 | e1000デバイスのドライバのコードを追加します。 1275 | 1276 |
1277 | net/platform/xv6/driver/e1000_dev.h 1278 | 1279 | ```c 1280 | // 1281 | // E1000 hardware definitions: registers and DMA ring format. 1282 | // from the Intel 82540EP/EM &c manual. 1283 | // 1284 | 1285 | /* Registers */ 1286 | #define E1000_CTL (0x0000) /* Device Control Register - RW */ 1287 | #define E1000_EERD (0x0014) /* EEPROM Read - RW */ 1288 | #define E1000_ICR (0x00C0) /* Interrupt Cause Read - R */ 1289 | #define E1000_IMS (0x00D0) /* Interrupt Mask Set - RW */ 1290 | #define E1000_IMC (0x00D8) /* Interrupt Mask Clear - RW */ 1291 | #define E1000_RCTL (0x0100) /* RX Control - RW */ 1292 | #define E1000_TCTL (0x0400) /* TX Control - RW */ 1293 | #define E1000_TIPG (0x0410) /* TX Inter-packet gap -RW */ 1294 | #define E1000_RDBAL (0x2800) /* RX Descriptor Base Address Low - RW */ 1295 | #define E1000_RDBAH (0x2804) /* RX Descriptor Base Address High - RW */ 1296 | #define E1000_RDTR (0x2820) /* RX Delay Timer */ 1297 | #define E1000_RADV (0x282C) /* RX Interrupt Absolute Delay Timer */ 1298 | #define E1000_RDH (0x2810) /* RX Descriptor Head - RW */ 1299 | #define E1000_RDT (0x2818) /* RX Descriptor Tail - RW */ 1300 | #define E1000_RDLEN (0x2808) /* RX Descriptor Length - RW */ 1301 | #define E1000_RSRPD (0x2C00) /* RX Small Packet Detect Interrupt */ 1302 | #define E1000_TDBAL (0x3800) /* TX Descriptor Base Address Low - RW */ 1303 | #define E1000_TDBAH (0x3804) /* TX Descriptor Base Address Hi - RW */ 1304 | #define E1000_TDLEN (0x3808) /* TX Descriptor Length - RW */ 1305 | #define E1000_TDH (0x3810) /* TX Descriptor Head - RW */ 1306 | #define E1000_TDT (0x3818) /* TX Descripotr Tail - RW */ 1307 | #define E1000_MTA (0x5200) /* Multicast Table Array - RW Array */ 1308 | #define E1000_RA (0x5400) /* Receive Address - RW Array */ 1309 | 1310 | /* Device Control */ 1311 | #define E1000_CTL_SLU 0x00000040 /* set link up */ 1312 | #define E1000_CTL_FRCSPD 0x00000800 /* force speed */ 1313 | #define E1000_CTL_FRCDPLX 0x00001000 /* force duplex */ 1314 | #define E1000_CTL_RST 0x00400000 /* full reset */ 1315 | 1316 | /* EEPROM */ 1317 | #define E1000_EERD_ADDR 8 /* num of bit shifts to get to addr section */ 1318 | #define E1000_EERD_DATA 16 /* num of bit shifts to get to data section */ 1319 | #define E1000_EERD_READ (1 << 0) /* 0th bit */ 1320 | #define E1000_EERD_DONE (1 << 4) /* 4th bit */ 1321 | 1322 | /* Interrupt */ 1323 | #define E1000_IMS_RXT0 0x00000080 /* rx timer intr */ 1324 | #define E1000_ICR_RXT0 E1000_IMS_RXT0 1325 | 1326 | /* Transmit Control */ 1327 | #define E1000_TCTL_RST 0x00000001 /* software reset */ 1328 | #define E1000_TCTL_EN 0x00000002 /* enable tx */ 1329 | #define E1000_TCTL_BCE 0x00000004 /* busy check enable */ 1330 | #define E1000_TCTL_PSP 0x00000008 /* pad short packets */ 1331 | #define E1000_TCTL_CT 0x00000ff0 /* collision threshold */ 1332 | #define E1000_TCTL_CT_SHIFT 4 1333 | #define E1000_TCTL_COLD 0x003ff000 /* collision distance */ 1334 | #define E1000_TCTL_COLD_SHIFT 12 1335 | #define E1000_TCTL_SWXOFF 0x00400000 /* SW Xoff transmission */ 1336 | #define E1000_TCTL_PBE 0x00800000 /* Packet Burst Enable */ 1337 | #define E1000_TCTL_RTLC 0x01000000 /* Re-transmit on late collision */ 1338 | #define E1000_TCTL_NRTU 0x02000000 /* No Re-transmit on underrun */ 1339 | #define E1000_TCTL_MULR 0x10000000 /* Multiple request support */ 1340 | 1341 | /* Receive Control */ 1342 | #define E1000_RCTL_RST 0x00000001 /* Software reset */ 1343 | #define E1000_RCTL_EN 0x00000002 /* enable */ 1344 | #define E1000_RCTL_SBP 0x00000004 /* store bad packet */ 1345 | #define E1000_RCTL_UPE 0x00000008 /* unicast promiscuous enable */ 1346 | #define E1000_RCTL_MPE 0x00000010 /* multicast promiscuous enab */ 1347 | #define E1000_RCTL_LPE 0x00000020 /* long packet enable */ 1348 | #define E1000_RCTL_LBM_NO 0x00000000 /* no loopback mode */ 1349 | #define E1000_RCTL_LBM_MAC 0x00000040 /* MAC loopback mode */ 1350 | #define E1000_RCTL_LBM_SLP 0x00000080 /* serial link loopback mode */ 1351 | #define E1000_RCTL_LBM_TCVR 0x000000C0 /* tcvr loopback mode */ 1352 | #define E1000_RCTL_DTYP_MASK 0x00000C00 /* Descriptor type mask */ 1353 | #define E1000_RCTL_DTYP_PS 0x00000400 /* Packet Split descriptor */ 1354 | #define E1000_RCTL_RDMTS_HALF 0x00000000 /* rx desc min threshold size */ 1355 | #define E1000_RCTL_RDMTS_QUAT 0x00000100 /* rx desc min threshold size */ 1356 | #define E1000_RCTL_RDMTS_EIGTH 0x00000200 /* rx desc min threshold size */ 1357 | #define E1000_RCTL_MO_SHIFT 12 /* multicast offset shift */ 1358 | #define E1000_RCTL_MO_0 0x00000000 /* multicast offset 11:0 */ 1359 | #define E1000_RCTL_MO_1 0x00001000 /* multicast offset 12:1 */ 1360 | #define E1000_RCTL_MO_2 0x00002000 /* multicast offset 13:2 */ 1361 | #define E1000_RCTL_MO_3 0x00003000 /* multicast offset 15:4 */ 1362 | #define E1000_RCTL_MDR 0x00004000 /* multicast desc ring 0 */ 1363 | #define E1000_RCTL_BAM 0x00008000 /* broadcast enable */ 1364 | /* these buffer sizes are valid if E1000_RCTL_BSEX is 0 */ 1365 | #define E1000_RCTL_SZ_2048 0x00000000 /* rx buffer size 2048 */ 1366 | #define E1000_RCTL_SZ_1024 0x00010000 /* rx buffer size 1024 */ 1367 | #define E1000_RCTL_SZ_512 0x00020000 /* rx buffer size 512 */ 1368 | #define E1000_RCTL_SZ_256 0x00030000 /* rx buffer size 256 */ 1369 | /* these buffer sizes are valid if E1000_RCTL_BSEX is 1 */ 1370 | #define E1000_RCTL_SZ_16384 0x00010000 /* rx buffer size 16384 */ 1371 | #define E1000_RCTL_SZ_8192 0x00020000 /* rx buffer size 8192 */ 1372 | #define E1000_RCTL_SZ_4096 0x00030000 /* rx buffer size 4096 */ 1373 | #define E1000_RCTL_VFE 0x00040000 /* vlan filter enable */ 1374 | #define E1000_RCTL_CFIEN 0x00080000 /* canonical form enable */ 1375 | #define E1000_RCTL_CFI 0x00100000 /* canonical form indicator */ 1376 | #define E1000_RCTL_DPF 0x00400000 /* discard pause frames */ 1377 | #define E1000_RCTL_PMCF 0x00800000 /* pass MAC control frames */ 1378 | #define E1000_RCTL_BSEX 0x02000000 /* Buffer size extension */ 1379 | #define E1000_RCTL_SECRC 0x04000000 /* Strip Ethernet CRC */ 1380 | #define E1000_RCTL_FLXBUF_MASK 0x78000000 /* Flexible buffer size */ 1381 | #define E1000_RCTL_FLXBUF_SHIFT 27 /* Flexible buffer shift */ 1382 | 1383 | #define DATA_MAX 1518 1384 | 1385 | /* Transmit Descriptor command definitions [E1000 3.3.3.1] */ 1386 | #define E1000_TXD_CMD_EOP 0x01 /* End of Packet */ 1387 | #define E1000_TXD_CMD_IFCS 0x02 /* Insert FCS (Ethernet CRC) */ 1388 | #define E1000_TXD_CMD_RS 0x08 /* Report Status */ 1389 | #define E1000_TXD_CMD_DEXT 0x20 /* Descriptor extension (0 = legacy) */ 1390 | 1391 | /* Transmit Descriptor status definitions [E1000 3.3.3.2] */ 1392 | #define E1000_TXD_STAT_DD 0x00000001 /* Descriptor Done */ 1393 | 1394 | // [E1000 3.3.3] 1395 | struct tx_desc 1396 | { 1397 | uint64_t addr; 1398 | uint16_t length; 1399 | uint8_t cso; 1400 | uint8_t cmd; 1401 | uint8_t status; 1402 | uint8_t css; 1403 | uint16_t special; 1404 | }; 1405 | 1406 | /* Receive Descriptor bit definitions [E1000 3.2.3.1] */ 1407 | #define E1000_RXD_STAT_DD 0x01 /* Descriptor Done */ 1408 | #define E1000_RXD_STAT_EOP 0x02 /* End of Packet */ 1409 | 1410 | // [E1000 3.2.3] 1411 | struct rx_desc 1412 | { 1413 | uint64_t addr; /* Address of the descriptor's data buffer */ 1414 | uint16_t length; /* Length of data DMAed into data buffer */ 1415 | uint16_t csum; /* Packet checksum */ 1416 | uint8_t status; /* Descriptor status */ 1417 | uint8_t errors; /* Descriptor Errors */ 1418 | uint16_t special; 1419 | }; 1420 | ``` 1421 |
1422 | 1423 |
1424 | net/platform/xv6/driver/e1000.c 1425 | 1426 | ```c 1427 | #include "platform.h" 1428 | 1429 | #include "memlayout.h" 1430 | #include "pci.h" 1431 | 1432 | #include "util.h" 1433 | 1434 | #include "e1000_dev.h" 1435 | 1436 | #define RX_RING_SIZE 16 1437 | #define TX_RING_SIZE 16 1438 | 1439 | struct e1000 { 1440 | struct e1000 *next; 1441 | uint32_t mmio_base; 1442 | struct rx_desc rx_ring[RX_RING_SIZE] __attribute__((aligned(16)));; 1443 | struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16)));; 1444 | uint8_t irq; 1445 | }; 1446 | 1447 | static struct e1000 *devices; 1448 | 1449 | unsigned int 1450 | e1000_reg_read(struct e1000 *e1000, uint16_t reg) 1451 | { 1452 | return *(volatile uint32_t *)(e1000->mmio_base + reg); 1453 | } 1454 | 1455 | void 1456 | e1000_reg_write(struct e1000 *e1000, uint16_t reg, uint32_t val) 1457 | { 1458 | *(volatile uint32_t *)(e1000->mmio_base + reg) = val; 1459 | } 1460 | 1461 | static uint16_t 1462 | e1000_eeprom_read(struct e1000 *e1000, uint8_t addr) 1463 | { 1464 | uint32_t eerd; 1465 | 1466 | e1000_reg_write(e1000, E1000_EERD, E1000_EERD_READ | addr << E1000_EERD_ADDR); 1467 | while (!((eerd = e1000_reg_read(e1000, E1000_EERD)) & E1000_EERD_DONE)) 1468 | microdelay(1); 1469 | return (uint16_t)(eerd >> E1000_EERD_DATA); 1470 | } 1471 | 1472 | static void 1473 | e1000_read_addr_from_eeprom(struct e1000 *e1000, uint8_t *dst) 1474 | { 1475 | uint16_t data; 1476 | 1477 | for (int n = 0; n < 3; n++) { 1478 | data = e1000_eeprom_read(e1000, n); 1479 | dst[n*2+0] = (data & 0xff); 1480 | dst[n*2+1] = (data >> 8) & 0xff; 1481 | } 1482 | } 1483 | 1484 | static uint32_t 1485 | e1000_resolve_mmio_base(struct pci_func *pcif) 1486 | { 1487 | uint32_t mmio_base = 0; 1488 | 1489 | for (int n = 0; n < 6; n++) { 1490 | if (pcif->reg_base[n] > 0xffff) { 1491 | assert(pcif->reg_size[n] == (1<<17)); 1492 | mmio_base = pcif->reg_base[n]; 1493 | break; 1494 | } 1495 | } 1496 | return mmio_base; 1497 | } 1498 | 1499 | static void 1500 | e1000_rx_init(struct e1000 *e1000) 1501 | { 1502 | uint64_t base; 1503 | 1504 | // initialize rx descriptors 1505 | for(int n = 0; n < RX_RING_SIZE; n++) { 1506 | memset(&e1000->rx_ring[n], 0, sizeof(struct rx_desc)); 1507 | // alloc DMA buffer 1508 | e1000->rx_ring[n].addr = (uint64_t)V2P(kalloc()); 1509 | } 1510 | // setup rx descriptors 1511 | base = (uint64_t)(V2P(e1000->rx_ring)); 1512 | e1000_reg_write(e1000, E1000_RDBAL, (uint32_t)(base & 0xffffffff)); 1513 | e1000_reg_write(e1000, E1000_RDBAH, (uint32_t)(base >> 32)); 1514 | // rx descriptor lengh 1515 | e1000_reg_write(e1000, E1000_RDLEN, (uint32_t)(RX_RING_SIZE * sizeof(struct rx_desc))); 1516 | // setup head/tail 1517 | e1000_reg_write(e1000, E1000_RDH, 0); 1518 | e1000_reg_write(e1000, E1000_RDT, RX_RING_SIZE-1); 1519 | // set tx control register 1520 | e1000_reg_write(e1000, E1000_RCTL, ( 1521 | E1000_RCTL_SBP | /* store bad packet */ 1522 | E1000_RCTL_UPE | /* unicast promiscuous enable */ 1523 | E1000_RCTL_MPE | /* multicast promiscuous enab */ 1524 | E1000_RCTL_RDMTS_HALF | /* rx desc min threshold size */ 1525 | E1000_RCTL_SECRC | /* Strip Ethernet CRC */ 1526 | E1000_RCTL_LPE | /* long packet enable */ 1527 | E1000_RCTL_BAM | /* broadcast enable */ 1528 | E1000_RCTL_SZ_2048 | /* rx buffer size 2048 */ 1529 | 0) 1530 | ); 1531 | } 1532 | 1533 | static void 1534 | e1000_tx_init(struct e1000 *e1000) 1535 | { 1536 | uint64_t base; 1537 | 1538 | // initialize tx descriptors 1539 | for (int n = 0; n < TX_RING_SIZE; n++) { 1540 | memset(&e1000->tx_ring[n], 0, sizeof(struct tx_desc)); 1541 | } 1542 | // setup tx descriptors 1543 | base = (uint64_t)(V2P(e1000->tx_ring)); 1544 | e1000_reg_write(e1000, E1000_TDBAL, (uint32_t)(base & 0xffffffff)); 1545 | e1000_reg_write(e1000, E1000_TDBAH, (uint32_t)(base >> 32) ); 1546 | // tx descriptor length 1547 | e1000_reg_write(e1000, E1000_TDLEN, (uint32_t)(TX_RING_SIZE * sizeof(struct tx_desc))); 1548 | // setup head/tail 1549 | e1000_reg_write(e1000, E1000_TDH, 0); 1550 | e1000_reg_write(e1000, E1000_TDT, 0); 1551 | // set tx control register 1552 | e1000_reg_write(e1000, E1000_TCTL, ( 1553 | E1000_TCTL_PSP | /* pad short packets */ 1554 | 0) 1555 | ); 1556 | } 1557 | 1558 | static int 1559 | e1000_open(struct e1000 *e1000) 1560 | { 1561 | // enable interrupts 1562 | e1000_reg_write(e1000, E1000_IMS, E1000_IMS_RXT0); 1563 | // clear existing pending interrupts 1564 | e1000_reg_read(e1000, E1000_ICR); 1565 | // enable RX/TX 1566 | e1000_reg_write(e1000, E1000_RCTL, e1000_reg_read(e1000, E1000_RCTL) | E1000_RCTL_EN); 1567 | e1000_reg_write(e1000, E1000_TCTL, e1000_reg_read(e1000, E1000_TCTL) | E1000_TCTL_EN); 1568 | // link up 1569 | e1000_reg_write(e1000, E1000_CTL, e1000_reg_read(e1000, E1000_CTL) | E1000_CTL_SLU); 1570 | return 0; 1571 | } 1572 | 1573 | int 1574 | e1000init(struct pci_func *pcif) 1575 | { 1576 | struct e1000 *e1000; 1577 | uint8_t addr[6]; 1578 | 1579 | e1000 = (struct e1000 *)memory_alloc(sizeof(*e1000)); 1580 | if (!e1000) { 1581 | errorf("memory_alloc() failure"); 1582 | return -1; 1583 | } 1584 | pci_func_enable(pcif); 1585 | e1000->mmio_base = e1000_resolve_mmio_base(pcif); 1586 | e1000_read_addr_from_eeprom(e1000, addr); 1587 | debugf("addr: %02x:%02x:%02x:%02x:%02x:%02x", 1588 | addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); 1589 | for (int n = 0; n < 128; n++) 1590 | e1000_reg_write(e1000, E1000_MTA + (n << 2), 0); 1591 | e1000_rx_init(e1000); 1592 | e1000_tx_init(e1000); 1593 | e1000->irq = pcif->irq_line; 1594 | e1000->next = devices; 1595 | devices = e1000; 1596 | e1000_open(e1000); 1597 | infof("initialized, irq=%u", e1000->irq); 1598 | return 0; 1599 | } 1600 | ``` 1601 |
1602 | 1603 | 上記のコードは最低限の初期化処理のみを行う内容となっています。 1604 | 1605 | #### ✅ プロトタイプ宣言の追加 1606 | 1607 | xv6からドライバの初期化関数を呼び出せるようにプロトタイプ宣言を追加します。 1608 | 1609 |
1610 | defs.h 1611 | 1612 | ```diff 1613 | ... 1614 | 1615 | // net/net.c 1616 | void netinit(void); 1617 | 1618 | +// net/platform/xv6/driver/e1000.c 1619 | +int e1000init(struct pci_func*); 1620 | + 1621 | // number of elements in fixed-size array 1622 | #define NELEM(x) (sizeof(x)/sizeof((x)[0])) 1623 | 1624 | ... 1625 | ``` 1626 |
1627 | 1628 | #### ✅ Makefileの修正 1629 | 1630 | ソースファイルを追加したので`Makefile`を修正します。 1631 | 1632 |
1633 | Makefile 1634 | 1635 | ```diff 1636 | OBJS = \ 1637 | ... 1638 | net/platform/xv6/std.o\ 1639 | + net/platform/xv6/driver/e1000.o\ 1640 | net/util.o\ 1641 | net/net.o\ 1642 | ... 1643 | ``` 1644 |
1645 | 1646 | #### ✅ ドライバの対応付け 1647 | 1648 | PCIデバイスを検出した際に、適切なドライバの初期化関数を呼び出せるようにデバイスとドライバの対応表に追加します。 1649 | 1650 |
1651 | pci.c 1652 | 1653 | ```diff 1654 | ... 1655 | 1656 | // pci_attach_class matches the class and subclass of a PCI device 1657 | struct pci_driver pci_attach_class[] = { 1658 | { PCI_CLASS_BRIDGE, PCI_SUBCLASS_BRIDGE_PCI, &pci_bridge_attach }, 1659 | { 0, 0, 0 }, 1660 | }; 1661 | 1662 | // pci_attach_vendor matches the vendor ID and device ID of a PCI device. key1 1663 | // and key2 should be the vendor ID and device ID respectively 1664 | struct pci_driver pci_attach_vendor[] = { 1665 | + { 0x8086, 0x100e, &e1000init }, 1666 | { 0, 0, 0 }, 1667 | }; 1668 | 1669 | ... 1670 | ``` 1671 |
1672 | 1673 | IntelのベンダIDは`0x8086`、e1000デバイスのデバイスIDは`0x100e`です。このベンダIDとデバイスIDを持つデバイスを検出したら`e1000init()`が呼び出されるようにしています。 1674 | 1675 | #### ✅ 動作確認 1676 | 1677 | 再ビルドした後、`make qemu-nox`を実行してxv6を起動します。 1678 | 1679 |
1680 | ログメッセージ 1681 | 1682 | ``` 1683 | SeaBIOS (version 1.15.0-1) 1684 | 1685 | 1686 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 1687 | 1688 | 1689 | 1690 | Booting from Hard Disk..xv6... 1691 | 05:00:47.000 [D] netinit: Hello, SecCamp2024! (net/net.c:10) 1692 | +------+-------------------------------------------------+------------------+ 1693 | | 0000 | 48 65 6c 6c 6f 2c 20 53 65 63 43 61 6d 70 32 30 | Hello, SecCamp20 | 1694 | | 0010 | 32 34 21 00 | 24!. | 1695 | +------+-------------------------------------------------+------------------+ 1696 | PCI: 00:00.0: 8086:1237: class: 6.0 (Bridge device) irq: 0 1697 | PCI: 00:01.0: 8086:7000: class: 6.1 (Bridge device) irq: 0 1698 | PCI: 00:01.1: 8086:7010: class: 1.1 (Storage controller) irq: 0 1699 | PCI: 00:01.3: 8086:7113: class: 6.80 (Bridge device) irq: 9 1700 | PCI: 00:02.0: 1234:1111: class: 3.0 (Display controller) irq: 0 1701 | PCI: 00:03.0: 8086:100e: class: 2.0 (Network controller) irq: 11 1702 | PCI function 00:03.0 (8086:100e) enabled 1703 | 05:00:47.000 [D] e1000init: addr: 52:54:00:12:34:56 (net/platform/xv6/driver/e1000.c:206) 1704 | 05:00:47.000 [I] e1000init: initialized, irq=11 (net/platform/xv6/driver/e1000.c:217) 1705 | cpu0: starting 0 1706 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 1707 | init: starting sh 1708 | $ 1709 | ``` 1710 |
1711 | 1712 | PCIバスの走査でネットワークデバイスを検出した後にe1000ドライバの初期化関数が呼び出されていることが分かります。 1713 | 1714 | ## 3.3. 割り込み処理 1715 | 1716 | ### 3.3.1. ハードウェア割り込み 1717 | 1718 | #### ✅ 割り込みの捕捉 1719 | 1720 | e1000デバイスの割り込み番号は`11`であることが確認できました。これを`IRQ_E1000`として`traps.h`に追加します。 1721 | 1722 |
1723 | traps.h 1724 | 1725 | ```diff 1726 | ... 1727 | 1728 | #define T_IRQ0 32 // IRQ 0 corresponds to int T_IRQ 1729 | 1730 | #define IRQ_TIMER 0 1731 | #define IRQ_KBD 1 1732 | #define IRQ_COM1 4 1733 | +#define IRQ_E1000 11 1734 | #define IRQ_IDE 14 1735 | #define IRQ_ERROR 19 1736 | #define IRQ_SPURIOUS 31 1737 | ``` 1738 |
1739 | 1740 |
1741 | trap.c 1742 | 1743 | ```diff 1744 | ... 1745 | void 1746 | trap(struct trapframe *tf) 1747 | { 1748 | ... 1749 | switch(tf->trapno){ 1750 | case T_IRQ0 + IRQ_TIMER: 1751 | if(cpuid() == 0){ 1752 | acquire(&tickslock); 1753 | ticks++; 1754 | wakeup(&ticks); 1755 | release(&tickslock); 1756 | } 1757 | lapiceoi(); 1758 | break; 1759 | case T_IRQ0 + IRQ_IDE: 1760 | ideintr(); 1761 | lapiceoi(); 1762 | break; 1763 | case T_IRQ0 + IRQ_IDE+1: 1764 | // Bochs generates spurious IDE1 interrupts. 1765 | break; 1766 | case T_IRQ0 + IRQ_KBD: 1767 | kbdintr(); 1768 | lapiceoi(); 1769 | break; 1770 | case T_IRQ0 + IRQ_COM1: 1771 | uartintr(); 1772 | lapiceoi(); 1773 | break; 1774 | + case T_IRQ0 + IRQ_E1000: 1775 | + cprintf("!!! IRQ_E1000 !!!\n"); 1776 | + lapiceoi(); 1777 | + break; 1778 | case T_IRQ0 + 7: 1779 | case T_IRQ0 + IRQ_SPURIOUS: 1780 | cprintf("cpu%d: spurious interrupt at %x:%x\n", 1781 | cpuid(), tf->cs, tf->eip); 1782 | lapiceoi(); 1783 | break; 1784 | ... 1785 | } 1786 | ``` 1787 |
1788 | 1789 | 割り込みを捕捉したら`trap.c`にある`trap()`が呼び出されます。xv6の割り込みディスクリプタテーブル(IDT)では全ての割り込みに対して最終的にこの関数が呼び出されるように設定されています。従って、`case`を追加するだけで特定の割り込み発生時の処理を簡単に追加できます。なお、`case`を追加せずにスルーすると「不明な割り込みの発生」でクラッシュします。 1790 | 1791 | #### ✅ 割り込みの有効化 1792 | 1793 | e1000ドライバに割り込みの発生を有効化する処理を追加します。 1794 | 1795 |
1796 | net/platform/xv6/driver/e1000.c 1797 | 1798 | ```diff 1799 | ... 1800 | 1801 | int 1802 | e1000init(struct pci_func *pcif) 1803 | { 1804 | struct e1000 *e1000; 1805 | uint8_t addr[6]; 1806 | 1807 | e1000 = (struct e1000 *)memory_alloc(sizeof(*e1000)); 1808 | if (!e1000) { 1809 | errorf("memory_alloc() failure"); 1810 | return -1; 1811 | } 1812 | pci_func_enable(pcif); 1813 | e1000->mmio_base = e1000_resolve_mmio_base(pcif); 1814 | e1000_read_addr_from_eeprom(e1000, addr); 1815 | debugf("addr: %02x:%02x:%02x:%02x:%02x:%02x", 1816 | addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); 1817 | for (int n = 0; n < 128; n++) 1818 | e1000_reg_write(e1000, E1000_MTA + (n << 2), 0); 1819 | e1000_rx_init(e1000); 1820 | e1000_tx_init(e1000); 1821 | e1000->irq = pcif->irq_line; 1822 | e1000->next = devices; 1823 | devices = e1000; 1824 | + ioapicenable(e1000->irq, ncpu - 1); 1825 | e1000_open(e1000); 1826 | infof("initialized, irq=%d", e1000->irq); 1827 | return 0; 1828 | } 1829 | ``` 1830 |
1831 | 1832 | #### ✅ QEMUのネットワーク設定 1833 | 1834 | xv6からはネットワークデバイスが見えていますが、QEMUの外につながっておらず通信できないためTapデバイスと紐付けてホスト環境と通信できるようにします。 1835 | 1836 |
1837 | Makefile 1838 | 1839 | ```diff 1840 | ... 1841 | 1842 | # try to generate a unique GDB port 1843 | GDBPORT = $(shell expr `id -u` % 5000 + 25000) 1844 | # QEMU's gdb stub command line changed in 0.11 1845 | QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \ 1846 | then echo "-gdb tcp::$(GDBPORT)"; \ 1847 | else echo "-s -p $(GDBPORT)"; fi) 1848 | ifndef CPUS 1849 | CPUS := 2 1850 | endif 1851 | +QEMUNET = -netdev tap,id=n1,ifname=tap0 -device e1000,netdev=n1 1852 | -QEMUOPTS = -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp $(CPUS) -m 512 $(QEMUEXTRA) 1853 | +QEMUOPTS = -drive file=fs.img,index=1,media=disk,format=raw -drive file=xv6.img,index=0,media=disk,format=raw -smp $(CPUS) -m 512 $(QEMUEXTRA) $(QEMUNET) 1854 | 1855 | ... 1856 | ``` 1857 |
1858 | 1859 | #### ✅ 動作確認 1860 | 1861 | QEMUが使用するためのTapデバイスを作成してIPアドレスを設定します。IPアドレスを設定すると`192.0.2.0/24`宛のパケットをTapデバイスにルーティングする経路情報も自動的に登録されるはずです。 1862 | 1863 | ``` 1864 | $ sudo ip tuntap add mode tap user $USER name tap0 1865 | $ sudo ip addr add 192.0.2.1/24 dev tap0 1866 | $ sudo ip link set tap0 up 1867 | ``` 1868 | 1869 | 再ビルドした後、`make qemu-nox`でxv6を起動します。 1870 | 1871 |
1872 | 起動ログ 1873 | 1874 | ``` 1875 | SeaBIOS (version 1.15.0-1) 1876 | 1877 | 1878 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 1879 | 1880 | 1881 | 1882 | Booting from Hard Disk..xv6... 1883 | 05:00:47.000 [D] netinit: Hello, SecCamp2024! (net/net.c:10) 1884 | +------+-------------------------------------------------+------------------+ 1885 | | 0000 | 48 65 6c 6c 6f 2c 20 53 65 63 43 61 6d 70 32 30 | Hello, SecCamp20 | 1886 | | 0010 | 32 34 21 00 | 24!. | 1887 | +------+-------------------------------------------------+------------------+ 1888 | PCI: 00:00.0: 8086:1237: class: 6.0 (Bridge device) irq: 0 1889 | PCI: 00:01.0: 8086:7000: class: 6.1 (Bridge device) irq: 0 1890 | PCI: 00:01.1: 8086:7010: class: 1.1 (Storage controller) irq: 0 1891 | PCI: 00:01.3: 8086:7113: class: 6.80 (Bridge device) irq: 9 1892 | PCI: 00:02.0: 1234:1111: class: 3.0 (Display controller) irq: 0 1893 | PCI: 00:03.0: 8086:100e: class: 2.0 (Network controller) irq: 11 1894 | PCI function 00:03.0 (8086:100e) enabled 1895 | 05:00:47.000 [D] e1000init: addr: 52:54:00:12:34:56 (net/platform/xv6/driver/e1000.c:206) 1896 | 05:00:47.000 [I] e1000init: initialized, irq=11 (net/platform/xv6/driver/e1000.c:217) 1897 | cpu0: starting 0 1898 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 1899 | init: starting sh 1900 | $ 1901 | ``` 1902 |
1903 | 1904 | 開発環境で別のシェルを開き、`192.0.2.2`を宛先にして`ping`コマンドを実行します。 1905 | 1906 | ``` 1907 | $ ping 192.0.2.2 1908 | ``` 1909 | 1910 | これで`192.0.2.2`宛のパケットがTapデバイスにルーティングされ、xv6から見えているネットワークデバイスに到達します。xv6のコンソールには、割り込み捕捉時のメッセージが出力されているはずです。 1911 | 1912 |
1913 | 動作ログ 1914 | 1915 | ``` 1916 | !!! IRQ_E1000 !!! 1917 | ``` 1918 |
1919 | 1920 | ### 3.3.2. 割り込みハンドラ 1921 | 1922 | ネットワークデバイスが発生させたハードウェア割り込みを補足できるようになったので、割り込み発生時に処理を実行する割り込みハンドラを追加します。 1923 | 1924 | #### ✅ 割り込みハンドラの追加 1925 | 1926 | e1000ドライバに割り込みハンドラのコードを追加します。 1927 | 1928 |
1929 | net/platform/xv6/driver/e1000.c 1930 | 1931 | ```diff 1932 | ... 1933 | 1934 | static int 1935 | e1000_open(struct e1000 *e1000) 1936 | { 1937 | ... 1938 | } 1939 | 1940 | +void 1941 | +e1000intr(void) 1942 | +{ 1943 | + struct e1000 *e1000; 1944 | + int icr; 1945 | + uint32_t tail; 1946 | + struct rx_desc *desc; 1947 | + 1948 | + debugf(">>>"); 1949 | + for (e1000 = devices; e1000; e1000 = e1000->next) { 1950 | + icr = e1000_reg_read(e1000, E1000_ICR); 1951 | + if (icr & E1000_ICR_RXT0) { 1952 | + while (1) { 1953 | + tail = (e1000_reg_read(e1000, E1000_RDT)+1) % RX_RING_SIZE; 1954 | + desc = &e1000->rx_ring[tail]; 1955 | + if (!(desc->status & E1000_RXD_STAT_DD)) { 1956 | + /* EMPTY */ 1957 | + break; 1958 | + } 1959 | + do { 1960 | + if (!(desc->status & E1000_RXD_STAT_EOP)) { 1961 | + errorf("not EOP! this driver does not support packet that do not fit in one buffer"); 1962 | + break; 1963 | + } 1964 | + if (desc->errors) { 1965 | + errorf("rx errors (0x%x)", desc->errors); 1966 | + break; 1967 | + } 1968 | + debugf("%d bytes data receive", desc->length); 1969 | + debugdump(P2V((uint32_t)desc->addr), desc->length); 1970 | + } while(0); 1971 | + desc->status = (uint16_t)(0); 1972 | + e1000_reg_write(e1000, E1000_RDT, tail); 1973 | + } 1974 | + // clear pending interrupts 1975 | + e1000_reg_read(e1000, E1000_ICR); 1976 | + } 1977 | + } 1978 | + debugf("<<<"); 1979 | +} 1980 | 1981 | int 1982 | e1000init(struct pci_func *pcif) 1983 | { 1984 | ... 1985 | } 1986 | ``` 1987 |
1988 | 1989 | 同じデバイスが複数接続されていて割り込み番号を共有している場合、どのデバイスで割り込みが発生したのかわからないため、全てのe1000デバイスの状態を確認します。 1990 | 1991 | + `E1000_ICR` ... 割り込みの原因を表すレジスタ 1992 | + このレジスタの値から割り込みが発生した原因の見つける 1993 | + このレジスタを読み取ると割り込みフラグがクリアされる 1994 | + `E1000_ICR_RXT0` ... 受信タイマのタイムアウトを示すビット 1995 | + これがセットされている場合に受信バッファからパケットを読み込む 1996 | 1997 | 複数のパケットが到着している可能性があるため、1度の割り込みで格納されているパケットを全て読み出します。 1998 | 1999 | + `E1000_RDT` ... はリングバッファの`tail`の位置を示すレジスタ 2000 | + 現時点でデバイスはこの位置までパケットを格納できる 2001 | + リングバッファなので`RDT`+1 の位置に受信した最も古いパケットが格納されている 2002 | + `E1000_RXD_STAT_DD` ... そのディスクリプタにデータの格納が完了していることを示すビット 2003 | + このビットがセットされていなければそのディスクリプタは空である 2004 | + `E1000_RXD_STAT_EOP` ... そのディスクリプタにパケットの終端が含まれていることを示すビット 2005 | + パケットの終端が含まれていないということは複数のディスクリプタにまたがって格納されている 2006 | 2007 | `desc->addr`にパケットデータの先頭のアドレスが、`desc->length`にパケット長が設定されています。ただし、アドレスは物理アドレスで示されているので`P2V()`マクロで仮想アドレスに変換してからアクセスします。 2008 | 2009 | ディスクリプタのステータスをリセットし、現在位置を`RDT`に設定します。これは当初の`RDT+1`であり、リングバッファの位置が1つ先に進んでパケットを格納できる領域が増加します。 2010 | 2011 | `while`ループを抜けた先で`ICR`を読み捨てているのは保留中の割り込みをクリアすることが目的です。 2012 | 2013 | #### ✅ プロトタイプ宣言の追加 2014 | 2015 | 割り込みハンドラのプロトタイプ宣言を`defs.h`に追加します。 2016 | 2017 |
2018 | defs.h 2019 | 2020 | ```diff 2021 | ... 2022 | 2023 | // net/platform/xv6/driver/e1000.c 2024 | int e1000init(struct pci_func*); 2025 | +void e1000intr(void); 2026 | 2027 | ... 2028 | ``` 2029 |
2030 | 2031 | #### ✅ 割り込みハンドラの呼び出し 2032 | 2033 | `IRQ_E1000`の割り込みを捕捉した際に`e1000intr()`を呼び出すようにします。 2034 | 2035 |
2036 | trap.c 2037 | 2038 | ```diff 2039 | ... 2040 | case T_IRQ0 + IRQ_E1000: 2041 | - cprintf("!!! IRQ_E1000 !!!\n"); 2042 | + e1000intr(); 2043 | lapiceoi(); 2044 | break; 2045 | ... 2046 | ``` 2047 |
2048 | 2049 | #### ✅ 動作確認 2050 | 2051 | 再ビルドした後、`make qemu-nox`を実行してxv6を起動します。 2052 | 2053 |
2054 | 起動ログ 2055 | 2056 | ``` 2057 | SeaBIOS (version 1.15.0-1) 2058 | 2059 | 2060 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00 2061 | 2062 | 2063 | 2064 | Booting from Hard Disk..xv6... 2065 | 05:00:47.000 [D] netinit: Hello, SecCamp2024! (net/net.c:10) 2066 | +------+-------------------------------------------------+------------------+ 2067 | | 0000 | 48 65 6c 6c 6f 2c 20 53 65 63 43 61 6d 70 32 30 | Hello, SecCamp20 | 2068 | | 0010 | 32 34 21 00 | 24!. | 2069 | +------+-------------------------------------------------+------------------+ 2070 | PCI: 00:00.0: 8086:1237: class: 6.0 (Bridge device) irq: 0 2071 | PCI: 00:01.0: 8086:7000: class: 6.1 (Bridge device) irq: 0 2072 | PCI: 00:01.1: 8086:7010: class: 1.1 (Storage controller) irq: 0 2073 | PCI: 00:01.3: 8086:7113: class: 6.80 (Bridge device) irq: 9 2074 | PCI: 00:02.0: 1234:1111: class: 3.0 (Display controller) irq: 0 2075 | PCI: 00:03.0: 8086:100e: class: 2.0 (Network controller) irq: 11 2076 | PCI function 00:03.0 (8086:100e) enabled 2077 | 05:00:47.000 [D] e1000init: addr: 52:54:00:12:34:56 (net/platform/xv6/driver/e1000.c:206) 2078 | 05:00:47.000 [I] e1000init: initialized, irq=11 (net/platform/xv6/driver/e1000.c:217) 2079 | cpu0: starting 0 2080 | sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58 2081 | init: starting sh 2082 | $ 2083 | ``` 2084 |
2085 | 2086 | 開発環境で別のシェルを開き、`192.0.2.2`に向けて`ping`を実行します。 2087 | 2088 | ``` 2089 | $ ping 192.0.2.2 2090 | ``` 2091 | 2092 | `ping`への応答はありませんが問題ありません。xv6のログメッセージを確認するとe1000ドライバの割り込みハンドラが呼び出され、パケットのデータが取得できているはずです。 2093 | 2094 |
2095 | 動作ログ 2096 | 2097 | ``` 2098 | 05:00:49.000 [D] e1000intr: >>> (net/platform/xv6/driver/e1000.c:159) 2099 | 05:00:49.000 [D] e1000intr: 60 bytes data receive (net/platform/xv6/driver/e1000.c:179) 2100 | +------+-------------------------------------------------+------------------+ 2101 | | 0000 | ff ff ff ff ff ff 16 2a af 19 b5 91 08 06 00 01 | .......*........ | 2102 | | 0010 | 08 00 06 04 00 01 16 2a af 19 b5 91 c0 00 02 01 | .......*........ | 2103 | | 0020 | 00 00 00 00 00 00 c0 00 02 02 00 00 00 00 00 00 | ................ | 2104 | | 0030 | 00 00 00 00 00 00 00 00 00 00 00 00 | ............ | 2105 | +------+-------------------------------------------------+------------------+ 2106 | 05:00:49.000 [D] e1000intr: <<< (net/platform/xv6/driver/e1000.c:189) 2107 | 05:00:50.000 [D] e1000intr: >>> (net/platform/xv6/driver/e1000.c:159) 2108 | 05:00:50.000 [D] e1000intr: 60 bytes data receive (net/platform/xv6/driver/e1000.c:179) 2109 | +------+-------------------------------------------------+------------------+ 2110 | | 0000 | ff ff ff ff ff ff 16 2a af 19 b5 91 08 06 00 01 | .......*........ | 2111 | | 0010 | 08 00 06 04 00 01 16 2a af 19 b5 91 c0 00 02 01 | .......*........ | 2112 | | 0020 | 00 00 00 00 00 00 c0 00 02 02 00 00 00 00 00 00 | ................ | 2113 | | 0030 | 00 00 00 00 00 00 00 00 00 00 00 00 | ............ | 2114 | +------+-------------------------------------------------+------------------+ 2115 | 05:00:50.000 [D] e1000intr: <<< (net/platform/xv6/driver/e1000.c:189) 2116 | 05:00:51.000 [D] e1000intr: >>> (net/platform/xv6/driver/e1000.c:159) 2117 | 05:00:51.000 [D] e1000intr: 60 bytes data receive (net/platform/xv6/driver/e1000.c:179) 2118 | +------+-------------------------------------------------+------------------+ 2119 | | 0000 | ff ff ff ff ff ff 16 2a af 19 b5 91 08 06 00 01 | .......*........ | 2120 | | 0010 | 08 00 06 04 00 01 16 2a af 19 b5 91 c0 00 02 01 | .......*........ | 2121 | | 0020 | 00 00 00 00 00 00 c0 00 02 02 00 00 00 00 00 00 | ................ | 2122 | | 0030 | 00 00 00 00 00 00 00 00 00 00 00 00 | ............ | 2123 | +------+-------------------------------------------------+------------------+ 2124 | 05:00:51.000 [D] e1000intr: <<< (net/platform/xv6/driver/e1000.c:189) 2125 | .... 2126 | ``` 2127 |
-------------------------------------------------------------------------------- /05.md: -------------------------------------------------------------------------------- 1 | # 5. ソケット 2 | 3 | ## 5.1. システムコールの追加 4 | 5 | 既存のシステムコールを参考にしてxv6にソケット用のシステムコールを追加していきます。 6 | 7 | #### ✅ システムコール番号の追加 8 | 9 | 新しく追加するシステムコールに番号を割り当てます。 10 | 11 |
12 | syscall.h 13 | 14 | ```diff 15 | // System call numbers 16 | #define SYS_fork 1 17 | #define SYS_exit 2 18 | #define SYS_wait 3 19 | #define SYS_pipe 4 20 | #define SYS_read 5 21 | #define SYS_kill 6 22 | #define SYS_exec 7 23 | #define SYS_fstat 8 24 | #define SYS_chdir 9 25 | #define SYS_dup 10 26 | #define SYS_getpid 11 27 | #define SYS_sbrk 12 28 | #define SYS_sleep 13 29 | #define SYS_uptime 14 30 | #define SYS_open 15 31 | #define SYS_write 16 32 | #define SYS_mknod 17 33 | #define SYS_unlink 18 34 | #define SYS_link 19 35 | #define SYS_mkdir 20 36 | #define SYS_close 21 37 | +#define SYS_socket 22 38 | +#define SYS_bind 23 39 | +#define SYS_recvfrom 24 40 | +#define SYS_sendto 25 41 | ``` 42 |
43 | 44 | #### ✅ システムコール番号とカーネル関数の対応付け 45 | 46 | ユーザ空間のプログラムがシステムコールを発行した際にカーネル内で実行される関数の対応付けをします。 47 | 48 |
49 | syscall.c 50 | 51 | ```diff 52 | ... 53 | 54 | extern int sys_chdir(void); 55 | extern int sys_close(void); 56 | extern int sys_dup(void); 57 | extern int sys_exec(void); 58 | extern int sys_exit(void); 59 | extern int sys_fork(void); 60 | extern int sys_fstat(void); 61 | extern int sys_getpid(void); 62 | extern int sys_kill(void); 63 | extern int sys_link(void); 64 | extern int sys_mkdir(void); 65 | extern int sys_mknod(void); 66 | extern int sys_open(void); 67 | extern int sys_pipe(void); 68 | extern int sys_read(void); 69 | extern int sys_sbrk(void); 70 | extern int sys_sleep(void); 71 | extern int sys_unlink(void); 72 | extern int sys_wait(void); 73 | extern int sys_write(void); 74 | extern int sys_uptime(void); 75 | +extern int sys_socket(void); 76 | +extern int sys_bind(void); 77 | +extern int sys_recvfrom(void); 78 | +extern int sys_sendto(void); 79 | 80 | static int (*syscalls[])(void) = { 81 | [SYS_fork] sys_fork, 82 | [SYS_exit] sys_exit, 83 | [SYS_wait] sys_wait, 84 | [SYS_pipe] sys_pipe, 85 | [SYS_read] sys_read, 86 | [SYS_kill] sys_kill, 87 | [SYS_exec] sys_exec, 88 | [SYS_fstat] sys_fstat, 89 | [SYS_chdir] sys_chdir, 90 | [SYS_dup] sys_dup, 91 | [SYS_getpid] sys_getpid, 92 | [SYS_sbrk] sys_sbrk, 93 | [SYS_sleep] sys_sleep, 94 | [SYS_uptime] sys_uptime, 95 | [SYS_open] sys_open, 96 | [SYS_write] sys_write, 97 | [SYS_mknod] sys_mknod, 98 | [SYS_unlink] sys_unlink, 99 | [SYS_link] sys_link, 100 | [SYS_mkdir] sys_mkdir, 101 | [SYS_close] sys_close, 102 | +[SYS_socket] sys_socket, 103 | +[SYS_bind] sys_bind, 104 | +[SYS_recvfrom] sys_recvfrom, 105 | +[SYS_sendto] sys_sendto, 106 | }; 107 | 108 | ... 109 | ``` 110 |
111 | 112 | 関数ポインタの配列(`syscalls[]`)に、システムコール番号に対応するカーネル内の関数のポインタを設定します。 113 | 114 | #### ✅ カーネル関数の実装 115 | 116 | システムコールが発行された際にカーネル内で呼び出される関数を追加します。 117 | 118 |
119 | syssocket.c 120 | 121 | ```c 122 | #include "types.h" 123 | #include "defs.h" 124 | #include "param.h" 125 | #include "fs.h" 126 | #include "spinlock.h" 127 | #include "sleeplock.h" 128 | #include "file.h" 129 | 130 | int 131 | sys_socket(void) 132 | { 133 | int fd, domain, type, protocol; 134 | struct file *f; 135 | 136 | if (argint(0, &domain) < 0 || argint(1, &type) < 0 || argint(2, &protocol) < 0) 137 | return -1; 138 | if ((f = socketalloc(domain, type, protocol)) == 0 || (fd = fdalloc(f)) < 0){ 139 | if (f) 140 | fileclose(f); 141 | return -1; 142 | } 143 | return fd; 144 | } 145 | 146 | int 147 | sys_bind(void) 148 | { 149 | struct file *f; 150 | int addrlen; 151 | struct sockaddr *addr; 152 | 153 | if (argfd(0, 0, &f) < 0 || argint(2, &addrlen) < 0 || argptr(1, (void*)&addr, addrlen) < 0) 154 | return -1; 155 | if (f->type != FD_SOCKET) 156 | return -1; 157 | return socketbind(f->socket, addr, addrlen); 158 | } 159 | 160 | int 161 | sys_recvfrom(void) 162 | { 163 | struct file *f; 164 | int n; 165 | char *p; 166 | int *addrlen; 167 | struct sockaddr *addr = NULL; 168 | 169 | if (argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argptr(1, &p, n) < 0 || argptr(4, (void*)&addrlen, sizeof(*addrlen)) < 0) 170 | return -1; 171 | if (addrlen && argptr(3, (void*)&addr, *addrlen) < 0) 172 | return -1; 173 | if (f->type != FD_SOCKET) 174 | return -1; 175 | return socketrecvfrom(f->socket, p, n, addr, addrlen); 176 | } 177 | 178 | int 179 | sys_sendto(void) 180 | { 181 | struct file *f; 182 | int n; 183 | char *p; 184 | int addrlen; 185 | struct sockaddr *addr; 186 | 187 | if (argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argptr(1, &p, n) < 0 || argint(4, &addrlen) < 0 || argptr(3, (void*)&addr, addrlen) < 0) 188 | return -1; 189 | if (f->type != FD_SOCKET) 190 | return -1; 191 | return socketsendto(f->socket, p, n, addr, addrlen); 192 | } 193 | ``` 194 |
195 | 196 | 全ての関数の引数が`void`であることからもわかるように、通常の関数とは引数の受け取り方が異なります。 197 | 198 | システムコールのカーネル関数では、次のような関数を使用して引数を受け取ります。 199 | 200 | + `argint()` ... 整数 201 | + `argptr()` ... ポインタ 202 | + `argfd()` ... ファイルディスクリプタ 203 | + `argstr()` ... 文字列 204 | 205 | これらの関数は、いずれも最初の引数には「0からはじまる引数の番号」を指定します。なお、`argptr()`ではポンタの指すデータがそのプロセスのアドレス空間に存在するかどうかをチェックするためにデータの長さが必要です。したがって、`void *data`, `int len` のような形でポインタに続けて長さを受け取る場合には、先に長さの引数を受け取り、その後にポインタの引数を受け取ります。 206 | 207 | ソケットの具体的な処理は、この後に追加する`net/socket.c`の中で行います。`syssocket.c`の関数はいずれもシステムコールの引数を受け取ったら、それらの関数を呼び出してその結果を返します。 208 | 209 | #### ✅ `sysfile.c`内の関数の利用 210 | 211 | `syssocket.c`の関数は`sysfile.c`に実装されている関数を呼び出していますが、これらはもともと`sysfile.c`の中だけで使用されている関数で`static`修飾子が付いています。このままだと他のファイルから呼び出せないので`static`修飾子を削除し、`defs.h`にプロトタイプ宣言を追加します。 212 | 213 |
214 | sysfile.c 215 | 216 | ```diff 217 | ... 218 | 219 | -static int 220 | +int 221 | argfd(int n, int *pfd, struct file **pf) 222 | { 223 | ... 224 | } 225 | 226 | -static int 227 | +int 228 | fdalloc(struct file *f) 229 | { 230 | ... 231 | } 232 | 233 | ... 234 | ``` 235 |
236 | 237 |
238 | defs.h 239 | 240 | ```diff 241 | ... 242 | 243 | // syscall.c 244 | int argint(int, int*); 245 | int argptr(int, char**, int); 246 | int argstr(int, char**); 247 | int fetchint(uint, int*); 248 | int fetchstr(uint, char**); 249 | void syscall(void); 250 | 251 | +// sysfile.c 252 | +int argfd(int, int*, struct file**); 253 | +int fdalloc(struct file*); 254 | 255 | // time.c 256 | long rtcdate2unixtime(struct rtcdate *); 257 | struct rtcdate* unixtime2rtcdate(long, struct rtcdate*); 258 | time_t time(time_t*); 259 | int gettimeofday(struct timeval*, void*); 260 | 261 | ... 262 | ``` 263 |
264 | 265 | #### ✅ ユーザ空間へのシステムコールの提供 266 | 267 | ユーザ空間のプログラムが呼び出すシステムコールのアセンブリコードを追加します。アセンブリコードを追加すると言っても難しいことはなく、あらかじめ用意されているマクロを利用することで簡単にシステムコールを追加できます。 268 | 269 |
270 | usys.S 271 | 272 | ```diff 273 | #include "syscall.h" 274 | #include "traps.h" 275 | #define SYSCALL(name) \ 276 | .globl name; \ 277 | name: \ 278 | movl $SYS_ ## name, %eax; \ 279 | int $T_SYSCALL; \ 280 | ret 281 | SYSCALL(fork) 282 | SYSCALL(exit) 283 | SYSCALL(wait) 284 | SYSCALL(pipe) 285 | SYSCALL(read) 286 | SYSCALL(write) 287 | SYSCALL(close) 288 | SYSCALL(kill) 289 | SYSCALL(exec) 290 | SYSCALL(open) 291 | SYSCALL(mknod) 292 | SYSCALL(unlink) 293 | SYSCALL(fstat) 294 | SYSCALL(link) 295 | SYSCALL(mkdir) 296 | SYSCALL(chdir) 297 | SYSCALL(dup) 298 | SYSCALL(getpid) 299 | SYSCALL(sbrk) 300 | SYSCALL(sleep) 301 | SYSCALL(uptime) 302 | SYSCALL(ioctl) 303 | +SYSCALL(socket) 304 | +SYSCALL(bind) 305 | +SYSCALL(recvfrom) 306 | +SYSCALL(sendto) 307 | ``` 308 |
309 | 310 | 追加したシステムコールのプロトタイプ宣言を追加します。カーネル内の関数は`defs.h`に記述していましたが、ユーザ空間の関数は`user.h`に記述します。 311 | 312 |
313 | user.h 314 | 315 | ```diff 316 | struct stat; 317 | struct rtcdate; 318 | +struct sockaddr; 319 | 320 | // system calls 321 | int fork(void); 322 | int exit(void) __attribute__((noreturn)); 323 | int wait(void); 324 | int pipe(int*); 325 | int write(int, const void*, int); 326 | int read(int, void*, int); 327 | int close(int); 328 | int kill(int); 329 | int exec(char*, char**); 330 | int open(const char*, int); 331 | int mknod(const char*, short, short); 332 | int unlink(const char*); 333 | int fstat(int fd, struct stat*); 334 | int link(const char*, const char*); 335 | int mkdir(const char*); 336 | int chdir(const char*); 337 | int dup(int); 338 | int getpid(void); 339 | char* sbrk(int); 340 | int sleep(int); 341 | int uptime(void); 342 | +int socket(int, int, int); 343 | +int bind(int, struct sockaddr*, int); 344 | +int recvfrom(int, char*, int, struct sockaddr*, int*); 345 | +int sendto(int, char*, int, struct sockaddr*, int); 346 | 347 | ... 348 | ``` 349 |
350 | 351 | #### ✅ Makefileの修正 352 | 353 | ソースファイルを追加したので`Makefile`を修正します。 354 | 355 |
356 | Makefile 357 | 358 | ```diff 359 | OBJS = \ 360 | ... 361 | syscall.o\ 362 | sysfile.o\ 363 | + syssocket.o\ 364 | sysproc.o\ 365 | time.o\ 366 | ... 367 | ``` 368 |
369 | 370 | ## 5.2. ファイルディスクリプタとの互換性 371 | 372 | Linuxを含むUNIX系のOSでは、ソケットのディスクリプタはファイルディスクリプタと互換性があり、ファイルと同じように扱うことができます。例えば、ストリーム通信用のソケットは`send()`/`recv()`の代わりに`write()`/`read()`のシステムコールを使用してデータを送受信することができます。また、ソケットを閉じる際にもファイルと同様に`close()`システムコールを使用します。 373 | 374 | カーネル内でファイルを扱う構造体(`struct file`)は、ファイルに加えてパイプにも対応しています。これを拡張してソケットにも対応できるようにします。 375 | 376 |
377 | file.h 378 | 379 | ```diff 380 | struct file { 381 | - enum { FD_NONE, FD_PIPE, FD_INODE } type; 382 | + enum { FD_NONE, FD_PIPE, FD_INODE, FD_SOCKET } type; 383 | int ref; // reference count 384 | char readable; 385 | char writable; 386 | struct pipe *pipe; 387 | struct inode *ip; 388 | + struct socket *socket; 389 | uint off; 390 | }; 391 | ``` 392 |
393 | 394 |
395 | file.c 396 | 397 | ```diff 398 | ... 399 | 400 | // Close file f. (Decrement ref count, close when reaches 0.) 401 | void 402 | fileclose(struct file *f) 403 | { 404 | struct file ff; 405 | 406 | acquire(&ftable.lock); 407 | if(f->ref < 1) 408 | panic("fileclose"); 409 | if(--f->ref > 0){ 410 | release(&ftable.lock); 411 | return; 412 | } 413 | ff = *f; 414 | f->ref = 0; 415 | f->type = FD_NONE; 416 | release(&ftable.lock); 417 | 418 | if(ff.type == FD_PIPE) 419 | pipeclose(ff.pipe, ff.writable); 420 | else if(ff.type == FD_INODE){ 421 | begin_op(); 422 | iput(ff.ip); 423 | end_op(); 424 | } 425 | + else if(ff.type == FD_SOCKET) 426 | + socketclose(ff.socket); 427 | } 428 | 429 | ... 430 | ``` 431 |
432 | 433 | `fileclose()`は`close()`システムコールのカーネル関数(`sys_close()`)から呼び出される関数ですが、ソケットだった場合には`socketclose()`を呼び出してソケットのためのクローズ処理を実行するようにします。 434 | 435 | ## 5.3. ソケットの内部実装 436 | 437 | システムコールのカーネル関数から呼び出されて具体的な処理を行うソケット関数のコードを追加します。 438 | 439 | #### ✅ ソケット用の定数とアドレス構造体 440 | 441 | ソケットで使用する定数とアドレス構造体を追加します。このファイルはユーザ空間とカーネル内どちらからも使用します。 442 | 443 |
444 | socket.h 445 | 446 | ```c 447 | #define PF_INET 1 448 | 449 | #define AF_INET PF_INET 450 | 451 | #define SOCK_DGRAM 1 452 | #define SOCK_STREAM 2 453 | 454 | #define IPPROTO_UDP 0 455 | #define IPPROTO_TCP 0 456 | 457 | #define INADDR_ANY ((uint32_t)0) 458 | 459 | struct in_addr { 460 | uint32_t s_addr; 461 | }; 462 | 463 | struct sockaddr { 464 | unsigned short sa_family; 465 | char sa_data[14]; 466 | }; 467 | 468 | struct sockaddr_in { 469 | unsigned short sin_family; 470 | uint16_t sin_port; 471 | struct in_addr sin_addr; 472 | }; 473 | ``` 474 |
475 | 476 | `struct sockaddr`は汎用的なアドレス構造体で、`struct sockaddr_in`はIPv4用のアドレス構造体です。ユーザ空間のプログラムは送信元や宛先のアドレスをこれらの構造体で扱います。ソケット関連のシステムコールにもソケットアドレス構造体が渡されます。 477 | 478 | #### ✅ ソケットのコード 479 | 480 | ソケットのコードを追加します。なお、ここで示すのはUDPで使用するデータグラムソケットだけを扱えるコードとなっており、TCPのためのストリームソケットは応用課題としています。 481 | 482 |
483 | net/socket.c 484 | 485 | ```c 486 | #include "platform.h" 487 | 488 | #include "fs.h" 489 | #include "sleeplock.h" 490 | #include "file.h" 491 | #include "socket.h" 492 | 493 | #include "tcp.h" 494 | #include "udp.h" 495 | 496 | struct socket { 497 | int type; 498 | int desc; 499 | }; 500 | 501 | struct file* 502 | socketalloc(int domain, int type, int protocol) 503 | { 504 | struct file *f; 505 | struct socket *s; 506 | 507 | if (domain != AF_INET || type != SOCK_DGRAM || protocol != 0) { 508 | return NULL; 509 | } 510 | f = filealloc(); 511 | if (!f) { 512 | return NULL; 513 | } 514 | s = (struct socket *)kalloc(); 515 | if (!s) { 516 | fileclose(f); 517 | return NULL; 518 | } 519 | s->type = type; 520 | s->desc = udp_open(); 521 | f->type = FD_SOCKET; 522 | f->readable = 1; 523 | f->writable = 1; 524 | f->socket = s; 525 | return f; 526 | } 527 | 528 | int 529 | socketclose(struct socket *s) 530 | { 531 | if (s->type == SOCK_DGRAM) 532 | return udp_close(s->desc); 533 | return -1; 534 | } 535 | 536 | int 537 | socketbind(struct socket *s, struct sockaddr *addr, int addrlen) 538 | { 539 | struct ip_endpoint local; 540 | 541 | local.addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; 542 | local.port = ((struct sockaddr_in *)addr)->sin_port; 543 | if (s->type == SOCK_DGRAM) 544 | return udp_bind(s->desc, &local); 545 | return -1; 546 | } 547 | 548 | int 549 | socketrecvfrom(struct socket *s, char *buf, int n, struct sockaddr *addr, int *addrlen) 550 | { 551 | struct ip_endpoint foreign; 552 | int ret; 553 | 554 | if (s->type != SOCK_DGRAM) 555 | return -1; 556 | ret = udp_recvfrom(s->desc, (uint8_t *)buf, n, &foreign); 557 | if (addr) { 558 | ((struct sockaddr_in *)addr)->sin_family = AF_INET; 559 | ((struct sockaddr_in *)addr)->sin_addr.s_addr = foreign.addr; 560 | ((struct sockaddr_in *)addr)->sin_port = foreign.port; 561 | } 562 | return ret; 563 | } 564 | 565 | int 566 | socketsendto(struct socket *s, char *buf, int n, struct sockaddr *addr, int addrlen) 567 | { 568 | struct ip_endpoint foreign; 569 | 570 | if (s->type != SOCK_DGRAM) 571 | return -1; 572 | foreign.addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; 573 | foreign.port = ((struct sockaddr_in *)addr)->sin_port; 574 | return udp_sendto(s->desc, (uint8_t *)buf, n, &foreign); 575 | } 576 | ``` 577 |
578 | 579 | 自作プロトコルスタックでは、UDPやTCPのモジュールにアプリケーションに提供するためのユーザコマンドが既に実装されています。そのため、ソケットの各関数はソケットの種別に応じてUDPまたはTCPのユーザコマンドを呼び出すことで対応できます。ただし、自作プロトコルスタック内では送信元や宛先を表現するためにソケットアドレスとは異なるエンドポイント構造体を使用しているため、構造体間で値の詰替えが必要な点に注意してください。 580 | 581 | #### ✅ プロトタイプ宣言の追加 582 | 583 | ソケットの関数のプロトタイプ宣言を`defs.h`に追加します。 584 | 585 |
586 | defs.h 587 | 588 | ```diff 589 | ... 590 | struct buf; 591 | struct context; 592 | struct file; 593 | struct inode; 594 | struct pci_func; 595 | struct pipe; 596 | struct proc; 597 | struct rtcdate; 598 | struct spinlock; 599 | struct sleeplock; 600 | struct stat; 601 | struct superblock; 602 | struct timeval; 603 | +struct socket; 604 | +struct sockaddr; 605 | 606 | ... 607 | 608 | // net/platform/xv6/driver/e1000.c 609 | int e1000init(struct pci_func*); 610 | void e1000intr(void); 611 | 612 | +// net/socket.c 613 | +struct file * socketalloc(int, int, int); 614 | +int socketclose(struct socket*); 615 | +int socketbind(struct socket*, struct sockaddr*, int); 616 | +int socketrecvfrom(struct socket*, char*, int, struct sockaddr*, int*); 617 | +int socketsendto(struct socket*, char*, int, struct sockaddr*, int); 618 | 619 | // number of elements in fixed-size array 620 | #define NELEM(x) (sizeof(x)/sizeof((x)[0])) 621 | 622 | ... 623 | ``` 624 |
625 | 626 | #### ✅ Makefileの修正 627 | 628 | ソースファイルを追加したので`Makefile`を修正します。 629 | 630 |
631 | Makefile 632 | 633 | ```diff 634 | OBJS = \ 635 | ... 636 | net/platform/xv6/std.o\ 637 | net/platform/xv6/driver/e1000.o\ 638 | net/util.o\ 639 | net/net.o\ 640 | net/ether.o\ 641 | net/arp.o\ 642 | net/ip.o\ 643 | net/icmp.o\ 644 | net/udp.o\ 645 | net/tcp.o\ 646 | + net/socket.o\ 647 | 648 | ``` 649 |
650 | 651 | ## 5.4. 通信アプリケーション 652 | 653 | 最後に、実際にソケットを使用する通信アプリケーションを作成します。 654 | 655 | #### ✅ UDPエコーサーバのコード 656 | 657 | UDPエコーサーバのコードを追加します。 658 | 659 |
660 | udpecho.c 661 | 662 | ```c 663 | #include "types.h" 664 | #include "user.h" 665 | #include "socket.h" 666 | 667 | int 668 | main (int argc, char *argv[]) 669 | { 670 | int soc, peerlen, ret; 671 | struct sockaddr_in self, peer; 672 | unsigned char *addr; 673 | char buf[2048]; 674 | 675 | printf(1, "Starting UDP Echo Server\n"); 676 | soc = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); 677 | if (soc == 1) { 678 | printf(1, "socket: failure\n"); 679 | exit(); 680 | } 681 | printf(1, "socket: success, soc=%d\n", soc); 682 | self.sin_family = AF_INET; 683 | self.sin_addr.s_addr = INADDR_ANY; 684 | self.sin_port = htons(7); 685 | if (bind(soc, (struct sockaddr *)&self, sizeof(self)) == -1) { 686 | printf(1, "bind: failure\n"); 687 | close(soc); 688 | exit(); 689 | } 690 | addr = (unsigned char *)&self.sin_addr.s_addr; 691 | printf(1, "bind: success, self=%d.%d.%d.%d:%d\n", 692 | addr[0], addr[1], addr[2], addr[3], ntohs(self.sin_port)); 693 | printf(1, "waiting for message...\n"); 694 | while (1) { 695 | peerlen = sizeof(peer); 696 | ret = recvfrom(soc, buf, sizeof(buf), (struct sockaddr *)&peer, &peerlen); 697 | if (ret <= 0) { 698 | printf(1, "EOF\n"); 699 | break; 700 | } 701 | if (ret == 2 && buf[0] == '.' && buf[1] == '\n') { 702 | printf(1, "quit\n"); 703 | break; 704 | } 705 | addr = (unsigned char *)&peer.sin_addr.s_addr; 706 | printf(1, "recvfrom: %d bytes data received, peer=%d.%d.%d.%d:%d\n", 707 | ret, addr[0], addr[1], addr[2], addr[3], ntohs(peer.sin_port)); 708 | sendto(soc, buf, ret, (struct sockaddr *)&peer, peerlen); 709 | } 710 | close(soc); 711 | exit(); 712 | } 713 | ``` 714 |
715 | 716 | このくらいの単純なものであれば、Linux向けのコードとほとんど差はありません。 717 | 718 | #### ✅ ユーザライブラリへ関数の追加 719 | 720 | 不足してる関数をユーザライブラリへ追加します。バイトオーダー変換の関数郡は`util.c`からのコピペです。 721 | 722 |
723 | ulib.c 724 | 725 | ```diff 726 | ... 727 | 728 | +#ifndef __BIG_ENDIAN 729 | +#define __BIG_ENDIAN 4321 730 | +#endif 731 | +#ifndef __LITTLE_ENDIAN 732 | +#define __LITTLE_ENDIAN 1234 733 | +#endif 734 | + 735 | +static int endian; 736 | + 737 | +static int 738 | +byteorder(void) { 739 | + uint32_t x = 0x00000001; 740 | + 741 | + return *(uint8_t *)&x ? __LITTLE_ENDIAN : __BIG_ENDIAN; 742 | +} 743 | + 744 | +static uint16_t 745 | +byteswap16(uint16_t v) 746 | +{ 747 | + return (v & 0x00ff) << 8 | (v & 0xff00 ) >> 8; 748 | +} 749 | + 750 | +static uint32_t 751 | +byteswap32(uint32_t v) 752 | +{ 753 | + return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; 754 | +} 755 | + 756 | +uint16_t 757 | +htons(uint16_t h) 758 | +{ 759 | + if (!endian) { 760 | + endian = byteorder(); 761 | + } 762 | + return endian == __LITTLE_ENDIAN ? byteswap16(h) : h; 763 | +} 764 | + 765 | +uint16_t 766 | +ntohs(uint16_t n) 767 | +{ 768 | + if (!endian) { 769 | + endian = byteorder(); 770 | + } 771 | + return endian == __LITTLE_ENDIAN ? byteswap16(n) : n; 772 | +} 773 | + 774 | +uint32_t 775 | +htonl(uint32_t h) 776 | +{ 777 | + if (!endian) { 778 | + endian = byteorder(); 779 | + } 780 | + return endian == __LITTLE_ENDIAN ? byteswap32(h) : h; 781 | +} 782 | + 783 | +uint32_t 784 | +ntohl(uint32_t n) 785 | +{ 786 | + if (!endian) { 787 | + endian = byteorder(); 788 | + } 789 | + return endian == __LITTLE_ENDIAN ? byteswap32(n) : n; 790 | +} 791 | ``` 792 |
793 | 794 | `ulib.c`に関数を追加したら`user.h`にプロトタイプ宣言を追加します。 795 | 796 |
797 | user.h 798 | 799 | ```diff 800 | ... 801 | 802 | // ulib.c 803 | int stat(const char*, struct stat*); 804 | char* strcpy(char*, const char*); 805 | void *memmove(void*, const void*, int); 806 | char* strchr(const char*, char c); 807 | int strcmp(const char*, const char*); 808 | void printf(int, const char*, ...); 809 | char* gets(char*, int max); 810 | uint strlen(const char*); 811 | void* memset(void*, int, uint); 812 | void* malloc(uint); 813 | void free(void*); 814 | int atoi(const char*); 815 | +uint16_t htons(uint16_t); 816 | +uint16_t ntohs(uint16_t); 817 | +uint32_t htonl(uint32_t); 818 | +uint32_t ntohl(uint32_t); 819 | +int inet_pton(int, const char*, void*); 820 | ``` 821 |
822 | 823 | #### ✅ Makefileの修正 824 | 825 | ユーザプログラムを追加したので`Makefile`を修正します。 826 | 827 |
828 | Makefile 829 | 830 | ```diff 831 | ... 832 | 833 | UPROGS=\ 834 | _cat\ 835 | _echo\ 836 | _forktest\ 837 | _grep\ 838 | _init\ 839 | _kill\ 840 | _ln\ 841 | _ls\ 842 | _mkdir\ 843 | _rm\ 844 | _sh\ 845 | _stressfs\ 846 | + _udpecho\ 847 | _usertests\ 848 | _wc\ 849 | _zombie\ 850 | 851 | ... 852 | ``` 853 |
854 | 855 | ユーザプログラムは`UPROGS`に列挙します。この際、頭に`_`を付け、末尾の拡張子は取り除きます。 856 | 857 | #### ✅ 動作確認 858 | 859 | ユーザプログラムは`make qemu-nox`でxv6を立ち上げるのタイミングでコンパイルされます。単純に`make`を実行しただけではユーザプログラムはコンパイルされないので注意してください。 860 | 861 | xv6が立ち上がったらUDPエコーサーバのプログラム(`udpecho`)を実行します。 862 | 863 | ``` 864 | $ udpecho 865 | Starting UDP Echo Server 866 | socket: success, soc=3 867 | 22:41:16.000 [D] udp_bind: bound, id=0, local=0.0.0.0:7 (net/udp.c:348) 868 | bind: success, self=0.0.0.0:7 869 | waiting for message... 870 | ``` 871 | 872 | `0.0.0.0`の`7`番ポートでデータが送られてくるのを待っているので、別のシェルで`nc`コマンドを立ち上げてメッセージを送信します。 873 | 874 | ``` 875 | $ nc -u 192.0.2.2 7 876 | hoge 877 | hoge 878 | fuga 879 | fuga 880 | . 881 | ``` 882 | 883 | 正常に動作していれば、送信したメッセージがそのまま送り返されてくるはずです。なお、エコーサーバのプログラムを終了させるには`.`だけのメッセージを送信します。 884 | 885 | エコーサーバのログメッセージも確認してみましょう。 886 | 887 |
888 | ログメッセージ 889 | 890 | ``` 891 | $ udpecho 892 | Starting UDP Echo Server 893 | socket: success, soc=3 894 | 13:10:16.000 [D] udp_bind: bound, id=0, local=0.0.0.0:7 (net/udp.c:348) 895 | bind: success, self=0.0.0.0:7 896 | waiting for message... 897 | 13:10:19.000 [D] e1000intr: >>> (net/platform/xv6/driver/e1000.c:231) 898 | 13:10:19.000 [D] ether_input_helper: dev=net0, type=0x0806, len=60 (net/ether.c:123) 899 | src: 16:2a:af:19:b5:91 900 | dst: ff:ff:ff:ff:ff:ff 901 | type: 0x0806 902 | 13:10:19.000 [D] net_input_handler: queue pushed (num:1), dev=net0, type=0x0806, len=46 (net/net.c:238) 903 | 13:10:19.000 [D] net_softirq_handler: queue popped (num:0), dev=net0, type=0x0806, len=46 (net/net.c:260) 904 | 13:10:19.000 [D] arp_input: dev=net0, len=46 (net/arp.c:236) 905 | hrd: 0x0001 906 | pro: 0x0800 907 | hln: 6 908 | pln: 4 909 | op: 1 (Request) 910 | sha: 16:2a:af:19:b5:91 911 | spa: 192.0.2.1 912 | tha: 00:00:00:00:00:00 913 | tpa: 192.0.2.2 914 | 13:10:19.000 [D] arp_cache_insert: INSERT: pa=192.0.2.1, ha=16:2a:af:19:b5:91 (net/arp.c:173) 915 | 13:10:19.000 [D] arp_reply: dev=net0, len=28 (net/arp.c:210) 916 | hrd: 0x0001 917 | pro: 0x0800 918 | hln: 6 919 | pln: 4 920 | op: 2 (Reply) 921 | sha: 52:54:00:12:34:56 922 | spa: 192.0.2.2 923 | tha: 16:2a:af:19:b5:91 924 | tpa: 192.0.2.1 925 | 13:10:19.000 [D] net_device_output: dev=net0, type=0x0806, len=28 (net/net.c:146) 926 | 13:10:19.000 [D] ether_transmit_helper: dev=net0, type=0x0806, len=60 (net/ether.c:88) 927 | src: 52:54:00:12:34:56 928 | dst: 16:2a:af:19:b5:91 929 | type: 0x0806 930 | 13:10:19.000 [D] e1000_write: dev=net0, 60 bytes data transmit (net/platform/xv6/driver/e1000.c:187) 931 | 13:10:19.000 [D] ether_input_helper: dev=net0, type=0x0800, len=60 (net/ether.c:123) 932 | src: 16:2a:af:19:b5:91 933 | dst: 52:54:00:12:34:56 934 | type: 0x0800 935 | 13:10:19.000 [D] net_input_handler: queue pushed (num:1), dev=net0, type=0x0800, len=46 (net/net.c:238) 936 | 13:10:19.000 [D] net_softirq_handler: queue popped (num:0), dev=net0, type=0x0800, len=46 (net/net.c:260) 937 | 13:10:19.000 [D] ip_input: dev=net0, iface=192.0.2.2, protocol=17, total=33 (net/ip.c:357) 938 | vhl: 0x45 [v: 4, hl: 5 (20)] 939 | tos: 0x00 940 | total: 33 (payload: 13) 941 | id: 674 942 | offset: 0x4000 [flags=2, offset=0] 943 | ttl: 64 944 | protocol: 17 945 | sum: 0xb426 946 | src: 192.0.2.1 947 | dst: 192.0.2.2 948 | 13:10:19.000 [D] udp_input: 192.0.2.1:38538 => 192.0.2.2:7, len=13 (payload=5) (net/udp.c:183) 949 | src: 38538 950 | dst: 7 951 | len: 13 952 | sum: 0x0b6a 953 | 13:10:19.000 [D] udp_input: queue pushed: id=0, num=1 (net/udp.c:210) 954 | 13:10:19.000 [D] e1000intr: <<< (net/platform/xv6/driver/e1000.c:250) 955 | 13:10:19.000 [D] e1000intr: >>> (net/platform/xv6/driver/e1000.c:231) 956 | 13:10:19.000 [D] e1000intr: <<< (net/platform/xv6/driver/e1000.c:250) 957 | recvfrom: 5 bytes data received, peer=192.0.2.1:38538 958 | 13:10:19.000 [D] udp_sendto: select local address, addr=192.0.2.2 (net/udp.c:379) 959 | 13:10:19.000 [D] udp_output: 192.0.2.2:7 => 192.0.2.1:38538, len=13 (payload=5) (net/udp.c:249) 960 | src: 7 961 | dst: 38538 962 | len: 13 963 | sum: 0x0b6a 964 | 13:10:19.000 [D] ip_output_core: dev=net0, dst=192.0.2.1, protocol=17, len=33 (net/ip.c:416) 965 | vhl: 0x45 [v: 4, hl: 5 (20)] 966 | tos: 0x00 967 | total: 33 (payload: 13) 968 | id: 128 969 | offset: 0x0000 [flags=0, offset=0] 970 | ttl: 255 971 | protocol: 17 972 | sum: 0x3748 973 | src: 192.0.2.2 974 | dst: 192.0.2.1 975 | 13:10:19.000 [D] arp_resolve: resolved, pa=192.0.2.1, ha=16:2a:af:19:b5:91 (net/arp.c:298) 976 | 13:10:19.000 [D] net_device_output: dev=net0, type=0x0800, len=33 (net/net.c:146) 977 | 13:10:19.000 [D] ether_transmit_helper: dev=net0, type=0x0800, len=60 (net/ether.c:88) 978 | src: 52:54:00:12:34:56 979 | dst: 16:2a:af:19:b5:91 980 | type: 0x0800 981 | 13:10:19.000 [D] e1000_write: dev=net0, 60 bytes data transmit (net/platform/xv6/driver/e1000.c:187) 982 | ``` 983 |
984 | 985 | ここまでに実装したUDP用のソケットが、問題なく動作していることが確認できました。最低限ではありますが「xv6にネットワーク機能を追加」することができました。ぜひ応用課題でTCP用のソケットにもチャレンジしてみてください。 -------------------------------------------------------------------------------- /06.md: -------------------------------------------------------------------------------- 1 | # 6. 応用課題(選択式) 2 | 3 | ## A: ソケット(TCP対応) 4 | 5 | ソケットをTCPにも対応させ、それを利用したTCPエコーサーバを作成してください。なお、現状のTCPモジュールが提供してるユーザコマンドはソケットとは異なる仕様であるため、ソケット互換のユーザコマンドを追加するための差分コードを提示します。 6 | 7 |
8 | net/tcp.c 9 | 10 | ```diff 11 | @@ -16,6 +16,9 @@ 12 | 13 | #define TCP_PCB_SIZE 16 14 | 15 | +#define TCP_PCB_MODE_RFC793 0 16 | +#define TCP_PCB_MODE_SOCKET 1 17 | + 18 | #define TCP_PCB_STATE_FREE 0 19 | #define TCP_PCB_STATE_CLOSED 1 20 | #define TCP_PCB_STATE_LISTEN 2 21 | @@ -32,6 +35,9 @@ 22 | #define TCP_DEFAULT_RTO 200000 /* micro seconds */ 23 | #define TCP_RETRANSMIT_DEADLINE 12 /* seconds */ 24 | 25 | +#define TCP_SOURCE_PORT_MIN 49152 26 | +#define TCP_SOURCE_PORT_MAX 65535 27 | + 28 | struct pseudo_hdr { 29 | uint32_t src; 30 | uint32_t dst; 31 | @@ -62,6 +68,7 @@ 32 | 33 | struct tcp_pcb { 34 | int state; 35 | + int mode; /* user command mode */ 36 | struct ip_endpoint local; 37 | struct ip_endpoint foreign; 38 | struct { 39 | @@ -84,6 +91,8 @@ 40 | uint8_t buf[65535]; /* receive buffer */ 41 | struct sched_ctx ctx; 42 | struct queue_head queue; /* retransmit queue */ 43 | + struct tcp_pcb *parent; 44 | + struct queue_head backlog; 45 | }; 46 | 47 | struct tcp_queue_entry { 48 | @@ -114,7 +123,7 @@ 49 | return str; 50 | } 51 | 52 | -static void 53 | +void 54 | tcp_dump(const uint8_t *data, size_t len) 55 | { 56 | struct tcp_hdr *hdr; 57 | @@ -160,6 +169,8 @@ 58 | static void 59 | tcp_pcb_release(struct tcp_pcb *pcb) 60 | { 61 | + struct queue_entry *entry; 62 | + struct tcp_pcb *est; 63 | char ep1[IP_ENDPOINT_STR_LEN]; 64 | char ep2[IP_ENDPOINT_STR_LEN]; 65 | 66 | @@ -167,6 +178,12 @@ 67 | sched_wakeup(&pcb->ctx); 68 | return; 69 | } 70 | + while ((entry = queue_pop(&pcb->queue)) != NULL) { 71 | + memory_free(entry); 72 | + } 73 | + while ((est = queue_pop(&pcb->backlog)) != NULL) { 74 | + tcp_pcb_release(est); 75 | + } 76 | debugf("released, local=%s, foreign=%s", 77 | ip_endpoint_ntop(&pcb->local, ep1, sizeof(ep1)), 78 | ip_endpoint_ntop(&pcb->foreign, ep2, sizeof(ep2))); 79 | @@ -363,7 +380,7 @@ 80 | tcp_segment_arrives(struct tcp_segment_info *seg, uint8_t flags, uint8_t *data, size_t len, struct ip_endpoint *local, struct ip_endpoint *foreign) 81 | { 82 | int acceptable = 0; 83 | - struct tcp_pcb *pcb; 84 | + struct tcp_pcb *pcb, *new_pcb; 85 | 86 | pcb = tcp_pcb_select(local, foreign); 87 | if (!pcb || pcb->state == TCP_PCB_STATE_CLOSED) { 88 | @@ -398,6 +415,16 @@ 89 | if (TCP_FLG_ISSET(flags, TCP_FLG_SYN)) { 90 | /* ignore: security/compartment check */ 91 | /* ignore: precedence check */ 92 | + if (pcb->mode == TCP_PCB_MODE_SOCKET) { 93 | + new_pcb = tcp_pcb_alloc(); 94 | + if (!new_pcb) { 95 | + errorf("tcp_pcb_alloc() failure"); 96 | + return; 97 | + } 98 | + new_pcb->mode = TCP_PCB_MODE_SOCKET; 99 | + new_pcb->parent = pcb; 100 | + pcb = new_pcb; 101 | + } 102 | pcb->local = *local; 103 | pcb->foreign = *foreign; 104 | pcb->rcv.wnd = sizeof(pcb->buf); 105 | @@ -548,6 +575,10 @@ 106 | if (pcb->snd.una <= seg->ack && seg->ack <= pcb->snd.nxt) { 107 | pcb->state = TCP_PCB_STATE_ESTABLISHED; 108 | sched_wakeup(&pcb->ctx); 109 | + if (pcb->parent) { 110 | + queue_push(&pcb->parent->backlog, pcb); 111 | + sched_wakeup(&pcb->parent->ctx); 112 | + } 113 | } else { 114 | tcp_output_segment(seg->ack, 0, TCP_FLG_RST, 0, NULL, 0, local, foreign); 115 | return; 116 | @@ -853,6 +884,9 @@ 117 | return -1; 118 | } 119 | switch (pcb->state) { 120 | + case TCP_PCB_STATE_LISTEN: 121 | + pcb->state =TCP_PCB_STATE_CLOSED; 122 | + break; 123 | case TCP_PCB_STATE_ESTABLISHED: 124 | tcp_output(pcb, TCP_FLG_ACK | TCP_FLG_FIN, NULL, 0); 125 | pcb->snd.nxt++; 126 | @@ -878,6 +912,220 @@ 127 | 128 | } 129 | 130 | +/* 131 | + * TCP User Command (Socket) 132 | + */ 133 | + 134 | +int 135 | +tcp_open(void) 136 | +{ 137 | + struct tcp_pcb *pcb; 138 | + int id; 139 | + 140 | + mutex_lock(&mutex); 141 | + pcb = tcp_pcb_alloc(); 142 | + if (!pcb) { 143 | + errorf("tcp_pcb_alloc() failure"); 144 | + mutex_unlock(&mutex); 145 | + return -1; 146 | + } 147 | + pcb->mode = TCP_PCB_MODE_SOCKET; 148 | + id = tcp_pcb_id(pcb); 149 | + mutex_unlock(&mutex); 150 | + return id; 151 | +} 152 | + 153 | +int 154 | +tcp_connect(int id, struct ip_endpoint *foreign) 155 | +{ 156 | + struct tcp_pcb *pcb; 157 | + struct ip_endpoint local; 158 | + struct ip_iface *iface; 159 | + char addr[IP_ADDR_STR_LEN]; 160 | + int p; 161 | + int state; 162 | + 163 | + mutex_lock(&mutex); 164 | + pcb = tcp_pcb_get(id); 165 | + if (!pcb) { 166 | + errorf("pcb not found"); 167 | + mutex_unlock(&mutex); 168 | + return -1; 169 | + } 170 | + if (pcb->mode != TCP_PCB_MODE_SOCKET) { 171 | + errorf("not opened in socket mode"); 172 | + mutex_unlock(&mutex); 173 | + return -1; 174 | + } 175 | + local.addr = pcb->local.addr; 176 | + local.port = pcb->local.port; 177 | + if (local.addr == IP_ADDR_ANY) { 178 | + iface = ip_route_get_iface(foreign->addr); 179 | + if (!iface) { 180 | + errorf("ip_route_get_iface() failure"); 181 | + mutex_unlock(&mutex); 182 | + return -1; 183 | + } 184 | + debugf("select source address: %s", ip_addr_ntop(iface->unicast, addr, sizeof(addr))); 185 | + local.addr = iface->unicast; 186 | + } 187 | + if (!local.port) { 188 | + for (p = TCP_SOURCE_PORT_MIN; p <= TCP_SOURCE_PORT_MAX; p++) { 189 | + local.port = p; 190 | + if (!tcp_pcb_select(&local, foreign)) { 191 | + debugf("dynamic assign source port: %d", ntoh16(local.port)); 192 | + pcb->local.port = local.port; 193 | + break; 194 | + } 195 | + } 196 | + if (!local.port) { 197 | + debugf("failed to dynamic assign source port"); 198 | + mutex_unlock(&mutex); 199 | + return -1; 200 | + } 201 | + } 202 | + pcb->local.addr = local.addr; 203 | + pcb->local.port = local.port; 204 | + pcb->foreign.addr = foreign->addr; 205 | + pcb->foreign.port = foreign->port; 206 | + pcb->rcv.wnd = sizeof(pcb->buf); 207 | + pcb->iss = random(); 208 | + if (tcp_output(pcb, TCP_FLG_SYN, NULL, 0) == -1) { 209 | + errorf("tcp_output() failure"); 210 | + pcb->state = TCP_PCB_STATE_CLOSED; 211 | + tcp_pcb_release(pcb); 212 | + mutex_unlock(&mutex); 213 | + return -1; 214 | + } 215 | + pcb->snd.una = pcb->iss; 216 | + pcb->snd.nxt = pcb->iss + 1; 217 | + pcb->state = TCP_PCB_STATE_SYN_SENT; 218 | +AGAIN: 219 | + state = pcb->state; 220 | + // waiting for state changed 221 | + while (pcb->state == state) { 222 | + if (sched_sleep(&pcb->ctx, &mutex, NULL) == -1) { 223 | + debugf("interrupted"); 224 | + pcb->state = TCP_PCB_STATE_CLOSED; 225 | + tcp_pcb_release(pcb); 226 | + mutex_unlock(&mutex); 227 | + errno = EINTR; 228 | + return -1; 229 | + } 230 | + } 231 | + if (pcb->state != TCP_PCB_STATE_ESTABLISHED) { 232 | + if (pcb->state == TCP_PCB_STATE_SYN_RECEIVED) { 233 | + goto AGAIN; 234 | + } 235 | + errorf("open error: %d", pcb->state); 236 | + pcb->state = TCP_PCB_STATE_CLOSED; 237 | + tcp_pcb_release(pcb); 238 | + mutex_unlock(&mutex); 239 | + return -1; 240 | + } 241 | + id = tcp_pcb_id(pcb); 242 | + mutex_unlock(&mutex); 243 | + return id; 244 | +} 245 | + 246 | +int 247 | +tcp_bind(int id, struct ip_endpoint *local) 248 | +{ 249 | + struct tcp_pcb *pcb, *exist; 250 | + char ep[IP_ENDPOINT_STR_LEN]; 251 | + 252 | + mutex_lock(&mutex); 253 | + pcb = tcp_pcb_get(id); 254 | + if (!pcb) { 255 | + errorf("pcb not found"); 256 | + mutex_unlock(&mutex); 257 | + return -1; 258 | + } 259 | + if (pcb->mode != TCP_PCB_MODE_SOCKET) { 260 | + errorf("not opened in socket mode"); 261 | + mutex_unlock(&mutex); 262 | + return -1; 263 | + } 264 | + exist = tcp_pcb_select(local, NULL); 265 | + if (exist) { 266 | + errorf("already bound, exist=%s", ip_endpoint_ntop(&exist->local, ep, sizeof(ep))); 267 | + mutex_unlock(&mutex); 268 | + return -1; 269 | + } 270 | + pcb->local = *local; 271 | + debugf("success: local=%s", ip_endpoint_ntop(&pcb->local, ep, sizeof(ep))); 272 | + mutex_unlock(&mutex); 273 | + return 0; 274 | +} 275 | + 276 | +int 277 | +tcp_listen(int id, int backlog) 278 | +{ 279 | + struct tcp_pcb *pcb; 280 | + 281 | + mutex_lock(&mutex); 282 | + pcb = tcp_pcb_get(id); 283 | + if (!pcb) { 284 | + errorf("pcb not found"); 285 | + mutex_unlock(&mutex); 286 | + return -1; 287 | + } 288 | + if (pcb->mode != TCP_PCB_MODE_SOCKET) { 289 | + errorf("not opened in socket mode"); 290 | + mutex_unlock(&mutex); 291 | + return -1; 292 | + } 293 | + pcb->state = TCP_PCB_STATE_LISTEN; 294 | + (void)backlog; // TODO: set backlog 295 | + mutex_unlock(&mutex); 296 | + return 0; 297 | +} 298 | + 299 | +int 300 | +tcp_accept(int id, struct ip_endpoint *foreign) 301 | +{ 302 | + struct tcp_pcb *pcb, *new_pcb; 303 | + int new_id; 304 | + 305 | + mutex_lock(&mutex); 306 | + pcb = tcp_pcb_get(id); 307 | + if (!pcb) { 308 | + errorf("pcb not found"); 309 | + mutex_unlock(&mutex); 310 | + return -1; 311 | + } 312 | + if (pcb->mode != TCP_PCB_MODE_SOCKET) { 313 | + errorf("not opened in socket mode"); 314 | + mutex_unlock(&mutex); 315 | + return -1; 316 | + } 317 | + if (pcb->state != TCP_PCB_STATE_LISTEN) { 318 | + errorf("not in LISTEN state"); 319 | + mutex_unlock(&mutex); 320 | + return -1; 321 | + } 322 | + while (!(new_pcb = queue_pop(&pcb->backlog))) { 323 | + if (sched_sleep(&pcb->ctx, &mutex, NULL) == -1) { 324 | + debugf("interrupted"); 325 | + mutex_unlock(&mutex); 326 | + errno = EINTR; 327 | + return -1; 328 | + } 329 | + if (pcb->state == TCP_PCB_STATE_CLOSED) { 330 | + debugf("closed"); 331 | + tcp_pcb_release(pcb); 332 | + mutex_unlock(&mutex); 333 | + return -1; 334 | + } 335 | + } 336 | + if (foreign) { 337 | + *foreign = new_pcb->foreign; 338 | + } 339 | + new_id = tcp_pcb_id(new_pcb); 340 | + mutex_unlock(&mutex); 341 | + return new_id; 342 | +} 343 | + 344 | ssize_t 345 | tcp_send(int id, uint8_t *data, size_t len) 346 | { 347 | ``` 348 |
349 | 350 | ## B: インタフェース制御 351 | 352 | ユーザ空間からインタフェースの情報を取得したりアドレスを設定するコマンドを作成してください。`ifconfig`コマンドが良い例ですが、必ずしもおなじ仕様である必要はありません。必要に応じてシステムコールの追加や、プロトコルスタックに機能を追加してください。 353 | 354 | ## C: タイマ機能の有効化と時刻精度の向上 355 | 356 | ARPとTCPのモジュールはプロトコルスタックに対してタイマを登録していますが、いまの状態では機能していません。タイマ機能を動作させるとともに、`gettimeofday()`の精度を向上させてください。なお、xv6ではLAPICのタイマ割り込みが有効になっており、周期的に`IRQ_TIMER`の割り込みが発生しているのでこれが利用できそうです。 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 YAMAMOTO Masaya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # N6: TCP/IPプロトコルスタック自作入門 2 | 3 | ## 1. 概要 4 | 5 | この講義では、インターネットをはじめとする現在のコンピュータネットワークを支えている基盤技術「TCP/IP」のプロトコルスタックをスクラッチで実装し、実際のOSに搭載してネットワーク機能を持たせる演習を行います。 6 | 7 | 講師が開発している教育用のプロトコルスタック「microps」を教材に、Ethernetフレームを組み立てて送受信するところから ARP、IP、ICMP、UDP、TCP などのプロトコルを処理するプログラムを、全て自分の手で作り上げてもらいます。 8 | 9 | + microps: https://github.com/pandax381/microps 10 | 11 | なお、講義時間が限られているためプロトコルスタックの基本的な実装は事前学習の期間で済ませてもらいます。 12 | 各自で事前学習を進めてもらうにあたり、解説資料を配布するとともに、ミーティングの機会を設けてしっかりフォローアップしますので安心してください。 13 | 14 | 講義時間では、あらかじめ開発を進めておいてもらったプロトコルスタックを、教育用OSの「xv6」に搭載して実際にTCP/IPでの通信を実現することを目指します。 15 | 16 | + xv6: https://github.com/mit-pdos/xv6-public 17 | 18 | 具体的には、次のような作業を行うことになります。 19 | 20 | - デバイスドライバの実装(Intel e1000) 21 | - プロトコルスタックの移植(プラットフォーム依存の処理の実装) 22 | - ソケット関連システムコールとユーザライブラリの実装 23 | 24 | この講義を通じてTCP/IPへの理解を深めるとともに、パケットやプロトコル処理の楽しさを知ってもらえたら嬉しいです。 25 | 26 | ## 2. 開発環境 27 | 28 | 各自で開発用のLinux環境を準備してください。 29 | 30 | - Ubuntu 22.04 を推奨 31 | - 実機 or 仮想環境どちらでもOK(WindowsはWSL2で動作確認済) 32 | - Dockerで構築する場合には --privileged または --cap-add=NET_ADMIN が必要 33 | - 必要パッケージ(Ubuntuの場合) 34 | - build-essential 35 | - git 36 | - iproute2 37 | - iputils-ping 38 | - netcat-openbsd 39 | - gcc-multilib 40 | - qemu-system-x86 41 | - 開発環境からインターネットへ接続できること 42 | 43 | ## 3. 事前学習 44 | 45 | 事前学習のための資料を用意してあります。 46 | 47 | + 事前学習資料: https://drive.google.com/drive/folders/1k2vymbC3vUk5CTJbay4LLEdZ9HemIpZe 48 | 49 | 講義の時間が限られているため、当日はプロトコルスタック本体についての細かな説明はしません。上記の資料に従ってLinuxのユーザ空間で動作するプロトコルスタックを実装し、全体の構成やパケット処理のフローを把握しておいてください。 50 | 51 | ## 4. 講義内容 52 | 53 | 1. [導入](01.md) 54 | - xv6について 55 | - コードの取得 56 | - ビルド 57 | - xv6の起動 58 | - QEMUモニタ 59 | - xv6の終了 60 | - 再ビルド 61 | 2. [下準備](02.md) 62 | - 型定義の追加 63 | - コンソール出力の改良 64 | - 現在時刻の取得 65 | - 便利ライブラリの移植 66 | 3. [ネットワークデバイス](03.md) 67 | - PCIデバイスの検出 68 | - Intel e1000ドライバ 69 | 4. [プロトコルスタックの移植](04.md) 70 | - メインモジュール 71 | - Ethernetモジュール 72 | - IPモジュール 73 | - ARPモジュール 74 | - ICMPモジュール 75 | - UDPモジュール 76 | - TCPモジュール 77 | 5. [ソケット](05.md) 78 | - システムコールの追加 79 | - ファイルディスクリプタとの互換性 80 | - ソケットの内部実装 81 | - 通信アプリケーション 82 | 6. [応用課題(選択式)](06.md) 83 | - A:ソケット(TCP対応) 84 | - B:インタフェース制御 85 | - C:タイマ機能の有効化と時刻精度の向上 86 | --------------------------------------------------------------------------------