├── 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 |
--------------------------------------------------------------------------------