From 69419b8e311c762def9d24bc77c6d89dba3d6f1f Mon Sep 17 00:00:00 2001 From: yanjuguang Date: Thu, 27 Apr 2023 17:35:47 +0800 Subject: [PATCH 01/13] complete some libc function --- ulib/c_libax/include/ctype.h | 20 ++ ulib/c_libax/include/limits.h | 34 +++ ulib/c_libax/include/locale.h | 56 +++++ ulib/c_libax/include/stdbool.h | 4 +- ulib/c_libax/include/stddef.h | 3 + ulib/c_libax/include/stdio.h | 55 +++-- ulib/c_libax/include/stdlib.h | 14 ++ ulib/c_libax/include/sys/time.h | 2 + ulib/c_libax/include/time.h | 9 + ulib/c_libax/include/unistd.h | 2 +- ulib/c_libax/src/assert.c | 2 +- ulib/c_libax/src/ctype.c | 67 ++++++ ulib/c_libax/src/mmap.c | 1 - ulib/c_libax/src/stdio.c | 252 +++++++++++++++++++++- ulib/c_libax/src/stdlib.c | 322 ++++++++++++++++++++++++++++- ulib/c_libax/src/time.c | 155 +++++++++++++- ulib/c_libax/src/unistd.c | 31 ++- ulib/libax/build.rs | 16 +- ulib/libax/ctypes.h | 1 + ulib/libax/src/cbindings/mod.rs | 9 + ulib/libax/src/cbindings/thread.rs | 26 +++ ulib/libax/src/cbindings/time.rs | 61 ++++++ 22 files changed, 1097 insertions(+), 45 deletions(-) create mode 100644 ulib/c_libax/include/ctype.h create mode 100644 ulib/c_libax/include/limits.h create mode 100644 ulib/c_libax/include/locale.h create mode 100644 ulib/c_libax/src/ctype.c create mode 100644 ulib/libax/src/cbindings/thread.rs create mode 100644 ulib/libax/src/cbindings/time.rs diff --git a/ulib/c_libax/include/ctype.h b/ulib/c_libax/include/ctype.h new file mode 100644 index 0000000000..485d105085 --- /dev/null +++ b/ulib/c_libax/include/ctype.h @@ -0,0 +1,20 @@ +#ifndef _CTYPE_H +#define _CTYPE_H + +int tolower(int __c); +int toupper(int __c); + +int isprint(int); +int isalpha(int c); +int isalnum(int); +int isupper(int c); +int islower(int c); + +int isxdigit(int); +int isgraph(int); +int iscntrl(int); +int ispunct(int); + +int isascii(int); + +#endif diff --git a/ulib/c_libax/include/limits.h b/ulib/c_libax/include/limits.h new file mode 100644 index 0000000000..873e61724f --- /dev/null +++ b/ulib/c_libax/include/limits.h @@ -0,0 +1,34 @@ +#ifndef __LIMITS__ +#define __LIMITS__ + +#define __LONG_MAX 0x7fffffffffffffffL +#define CHAR_BIT 8 +#define SCHAR_MIN (-128) +#define SCHAR_MAX 127 +#define UCHAR_MAX 255 +#define SHRT_MIN (-1 - 0x7fff) +#define SHRT_MAX 0x7fff +#define USHRT_MAX 0xffff +#define INT_MIN (-1 - 0x7fffffff) +#define INT_MAX 0x7fffffff +#define UINT_MAX 0xffffffffU +#define LONG_MIN (-LONG_MAX - 1) +#define LONG_MAX __LONG_MAX +#define ULONG_MAX (2UL * LONG_MAX + 1) +#define LLONG_MIN (-LLONG_MAX - 1) +#define LLONG_MAX 0x7fffffffffffffffLL +#define ULLONG_MAX (2ULL * LLONG_MAX + 1) + +#define PTHREAD_STACK_MIN 2048 + +#define LOGIN_NAME_MAX 256 +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif +#define TZNAME_MAX 6 + +#define PATH_MAX 4096 +#define SSIZE_MAX LONG_MAX +#define CHAR_MAX 127 + +#endif diff --git a/ulib/c_libax/include/locale.h b/ulib/c_libax/include/locale.h new file mode 100644 index 0000000000..2e352630de --- /dev/null +++ b/ulib/c_libax/include/locale.h @@ -0,0 +1,56 @@ +#ifndef _LOCALE_H +#define _LOCALE_H + +#define LC_CTYPE 0 +#define LC_NUMERIC 1 +#define LC_TIME 2 +#define LC_COLLATE 3 +#define LC_MONETARY 4 +#define LC_MESSAGES 5 +#define LC_ALL 6 +#define LOCALE_NAME_MAX 23 + +#include + +struct lconv { + char *decimal_point; + char *thousands_sep; + char *grouping; + + char *int_curr_symbol; + char *currency_symbol; + char *mon_decimal_point; + char *mon_thousands_sep; + char *mon_grouping; + char *positive_sign; + char *negative_sign; + char int_frac_digits; + char frac_digits; + char p_cs_precedes; + char p_sep_by_space; + char n_cs_precedes; + char n_sep_by_space; + char p_sign_posn; + char n_sign_posn; + char int_p_cs_precedes; + char int_p_sep_by_space; + char int_n_cs_precedes; + char int_n_sep_by_space; + char int_p_sign_posn; + char int_n_sign_posn; +}; + +struct __locale_map { + const void *map; + size_t map_size; + char name[LOCALE_NAME_MAX + 1]; + const struct __locale_map *next; +}; + +struct __locale_struct { + const struct __locale_map *cat[6]; +}; + +typedef struct __locale_struct *locale_t; + +#endif diff --git a/ulib/c_libax/include/stdbool.h b/ulib/c_libax/include/stdbool.h index af90479064..e3325f24c7 100644 --- a/ulib/c_libax/include/stdbool.h +++ b/ulib/c_libax/include/stdbool.h @@ -3,9 +3,9 @@ /* Represents true-or-false values */ #ifndef __cplusplus -#define true 1 +#define true 1 #define false 0 -#define bool _Bool +#define bool _Bool #endif #endif // __STDBOOL_H__ diff --git a/ulib/c_libax/include/stddef.h b/ulib/c_libax/include/stddef.h index 56d353626c..3060e3c889 100644 --- a/ulib/c_libax/include/stddef.h +++ b/ulib/c_libax/include/stddef.h @@ -2,12 +2,15 @@ #define __STDDEF_H__ #include +#include /* size_t is used for memory object sizes */ typedef uintptr_t size_t; typedef intptr_t ssize_t; typedef ssize_t ptrdiff_t; +typedef int clockid_t; + #ifdef __cplusplus #define NULL 0L #else diff --git a/ulib/c_libax/include/stdio.h b/ulib/c_libax/include/stdio.h index ce3dfc019d..92584809ec 100644 --- a/ulib/c_libax/include/stdio.h +++ b/ulib/c_libax/include/stdio.h @@ -1,34 +1,59 @@ #ifndef __STDIO_H__ #define __STDIO_H__ -#define stdin 0 -#define stdout 1 -#define stderr 2 +#include +#include -int getchar(); -int putchar(int); -int puts(const char *s); -void fprintf(int f, const char *fmt, ...); -int fflush(int); +// TODO: complete this struct +struct IO_FILE { + int fd; +}; + +typedef struct IO_FILE FILE; + +extern FILE *const stdin; +extern FILE *const stdout; +extern FILE *const stderr; + +#define stdin (stdin) +#define stdout (stdout) +#define stderr (stderr) #define EOF (-1) -#define printf(...) fprintf(stdout, __VA_ARGS__) +#define printf(...) fprintf(stdout->fd, __VA_ARGS__) #define unimplemented(fmt, ...) \ printf("\x1b[33m%s%s:\x1b[0m " fmt "\n", "WARN: no ax_call implementation for ", __func__, \ ##__VA_ARGS__) -struct _IO_FILE { - int fd; -}; - -typedef struct _IO_FILE FILE; - #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 +#define F_EOF 16 +#define F_ERR 32 +#define F_SVB 64 +#define F_NORD 4 +#define UNGET 8 + #define FILENAME_MAX 4096 +int getchar(); +int putchar(int); +int puts(const char *s); +void fprintf(int f, const char *fmt, ...); + +#if defined(AX_CONFIG_ALLOC) && defined(AX_CONFIG_FS) +FILE *fopen(const char *filename, const char *mode); +#endif + +int fflush(FILE *); + +int vsnprintf(char *__restrict, size_t, const char *__restrict, va_list); +int snprintf(char *__restrict, size_t, const char *__restrict, ...); +int vsprintf(char *__restrict, const char *__restrict, va_list); +int vfprintf(FILE *__restrict, const char *__restrict, va_list); +int sprintf(char *__restrict, const char *__restrict, ...); + #endif // __STDIO_H__ diff --git a/ulib/c_libax/include/stdlib.h b/ulib/c_libax/include/stdlib.h index 9216169b91..11c9c0d4d4 100644 --- a/ulib/c_libax/include/stdlib.h +++ b/ulib/c_libax/include/stdlib.h @@ -9,9 +9,23 @@ void srand(unsigned); #ifdef AX_CONFIG_ALLOC void *malloc(size_t size); void free(void *addr); +void *calloc(size_t nmemb, size_t size); +void *realloc(void *memblock, size_t size); #endif _Noreturn void abort(void); char *getenv(const char *name); +long strtol(const char *__restrict, char **__restrict, int); +unsigned long strtoul(const char *nptr, char **endptr, int base); +long long strtoll(const char *nptr, char **endptr, int base); +unsigned long long strtoull(const char *nptr, char **endptr, int base); + +long long atoll(const char *nptr); + +void exit(int); + +long long llabs(long long x); +int abs(int x); + #endif //__STDLIB_H__ diff --git a/ulib/c_libax/include/sys/time.h b/ulib/c_libax/include/sys/time.h index cf07d2faa0..420bdb82fa 100644 --- a/ulib/c_libax/include/sys/time.h +++ b/ulib/c_libax/include/sys/time.h @@ -15,6 +15,8 @@ struct timespec { long tv_nsec; /* nanoseconds */ }; +typedef struct timespec timespec; + struct timezone { int tz_minuteswest; /* (minutes west of Greenwich) */ int tz_dsttime; /* (type of DST correction) */ diff --git a/ulib/c_libax/include/time.h b/ulib/c_libax/include/time.h index 210cc0526a..4757618d21 100644 --- a/ulib/c_libax/include/time.h +++ b/ulib/c_libax/include/time.h @@ -2,9 +2,16 @@ #define __TIME_H__ #include +#include typedef long time_t; +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 +#define CLOCKS_PER_SEC 1000000L + +#define ax_time_usec_to_nsec(usec) ((usec)*1000UL) + struct tm { int tm_sec; /* seconds of minute */ int tm_min; /* minutes of hour */ @@ -26,5 +33,7 @@ struct tm *gmtime(const time_t *timer); struct tm *localtime(const time_t *timep); time_t time(time_t *t); +int clock_gettime(clockid_t _clk, struct timespec *ts); +int nanosleep(const struct timespec *requested_time, struct timespec *remaining); #endif diff --git a/ulib/c_libax/include/unistd.h b/ulib/c_libax/include/unistd.h index 25e4fb7c65..2cd3def824 100644 --- a/ulib/c_libax/include/unistd.h +++ b/ulib/c_libax/include/unistd.h @@ -3,7 +3,6 @@ #include #include -#include #define _SC_PAGESIZE 30 @@ -27,6 +26,7 @@ char *getcwd(char *buf, size_t size); #endif unsigned sleep(unsigned seconds); +int usleep(unsigned int useconds); uid_t geteuid(void); pid_t getpid(void); diff --git a/ulib/c_libax/src/assert.c b/ulib/c_libax/src/assert.c index ee071677d5..8c094b54fc 100644 --- a/ulib/c_libax/src/assert.c +++ b/ulib/c_libax/src/assert.c @@ -3,6 +3,6 @@ _Noreturn void __assert_fail(const char *expr, const char *file, int line, const char *func) { - fprintf(stderr, "Assertion failed: %s (%s: %s: %d)\n", expr, file, func, line); + fprintf(stderr->fd, "Assertion failed: %s (%s: %s: %d)\n", expr, file, func, line); abort(); } diff --git a/ulib/c_libax/src/ctype.c b/ulib/c_libax/src/ctype.c new file mode 100644 index 0000000000..1d05c16132 --- /dev/null +++ b/ulib/c_libax/src/ctype.c @@ -0,0 +1,67 @@ +#include +#include +#include + +int isupper(int c) +{ + return (unsigned)c - 'A' < 26; +} + +int tolower(int c) +{ + if (isupper(c)) + return c | 32; + return c; +} + +int islower(int c) +{ + return (unsigned)c - 'a' < 26; +} + +int toupper(int c) +{ + if (islower(c)) + return c & 0x5f; + return c; +} + +int isgraph(int c) +{ + return (unsigned)c - 0x21 < 0x5e; +} + +int isalpha(int c) +{ + return ((unsigned)c | 32) - 'a' < 26; +} + +int isprint(int c) +{ + return (unsigned)c - 0x20 < 0x5f; +} + +int isalnum(int c) +{ + return isalpha(c) || isdigit(c); +} + +int iscntrl(int c) +{ + return (unsigned)c < 0x20 || c == 0x7f; +} + +int ispunct(int c) +{ + return isgraph(c) && !isalnum(c); +} + +int isxdigit(int c) +{ + return isdigit(c) || ((unsigned)c | 32) - 'a' < 6; +} + +int isascii(int c) +{ + return !(c & ~0x7f); +} \ No newline at end of file diff --git a/ulib/c_libax/src/mmap.c b/ulib/c_libax/src/mmap.c index fd97138d07..8ba0894080 100644 --- a/ulib/c_libax/src/mmap.c +++ b/ulib/c_libax/src/mmap.c @@ -1,6 +1,5 @@ #include #include -#include // TODO: void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) diff --git a/ulib/c_libax/src/stdio.c b/ulib/c_libax/src/stdio.c index 9fba722461..3aefec95aa 100644 --- a/ulib/c_libax/src/stdio.c +++ b/ulib/c_libax/src/stdio.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -12,6 +14,22 @@ #define __LINE_WIDTH 256 +FILE __stdin_FILE = { + .fd = 0, +}; + +FILE __stdout_FILE = { + .fd = 1, +}; + +FILE __stderr_FILE = { + .fd = 2, +}; + +FILE *const stdin = &__stdin_FILE; +FILE *const stdout = &__stdout_FILE; +FILE *const stderr = &__stderr_FILE; + static char buffer[__LINE_WIDTH]; static int buffer_len; @@ -81,7 +99,7 @@ static void printint(long long value, int base, int sign) if (sign) buf[--i] = '-'; assert(i >= 0); - out(stdout, buf + i, buf_size - i); + out(stdout->fd, buf + i, buf_size - i); } static void printptr(uint64_t value) @@ -93,7 +111,7 @@ static void printptr(uint64_t value) for (j = 0; j < (sizeof(uint64_t) * 2); j++, value <<= 4) buf[i++] = digits[value >> (sizeof(uint64_t) * 8 - 4)]; buf[i] = 0; - out(stdout, buf, i); + out(stdout->fd, buf, i); } // int getchar() @@ -106,9 +124,9 @@ static void printptr(uint64_t value) // } // } -int fflush(int fd) +int fflush(FILE *f) { - if (fd == stdout || fd == stderr) + if (f->fd == 1 || f->fd == 2) return __fflush(); return 0; } @@ -116,13 +134,13 @@ int fflush(int fd) int putchar(int c) { char byte = c; - return out(stdout, &byte, 1); + return out(stdout->fd, &byte, 1); } int puts(const char *s) { int r; - r = -(out(stdout, s, strlen(s)) < 0 || putchar('\n') < 0); + r = -(out(stdout->fd, s, strlen(s)) < 0 || putchar('\n') < 0); return r; } @@ -196,3 +214,225 @@ void fprintf(int f, const char *restrict fmt, ...) } va_end(ap); } + +int __fmodeflags(const char *mode) +{ + int flags; + if (strchr(mode, '+')) + flags = O_RDWR; + else if (*mode == 'r') + flags = O_RDONLY; + else + flags = O_WRONLY; + if (strchr(mode, 'x')) + flags |= O_EXCL; + if (strchr(mode, 'e')) + flags |= O_CLOEXEC; + if (*mode != 'r') + flags |= O_CREAT; + if (*mode == 'w') + flags |= O_TRUNC; + if (*mode == 'a') + flags |= O_APPEND; + return flags; +} + +#if defined(AX_CONFIG_ALLOC) && defined(AX_CONFIG_FS) + +FILE *fopen(const char *filename, const char *mode) +{ + FILE *f; + int flags; + + // TODO: Should set errno + if (!strchr("rwa", *mode)) { + return 0; + } + + f = (FILE *)malloc(sizeof(FILE)); + + flags = __fmodeflags(mode); + int fd = ax_open(filename, flags, mode); + f->fd = fd; + + return f; +} + +#endif + +// Helper function for vsnprintf() +static void parsenint(char *dst, int *n, size_t m, long long value, int base, int sign) +{ + const int buf_size = 20; + char buf[buf_size + 1]; + int i; + uint64_t x; + + if (sign && (sign = (value < 0))) + x = -value; + else + x = value; + + i = buf_size; + + do { + buf[i--] = digits[x % base]; + } while ((x /= base) != 0); + + if (sign) + buf[i--] = '-'; + assert(i >= 0); + for (int j = i + 1; j <= buf_size && *n < m; j++) { + dst[(*n)++] = buf[j]; + } +} + +// Helper function for vsnprintf() +static void parsenptr(char *dst, int *n, size_t m, uint64_t value) +{ + int i = 0, j; + char buf[32 + 1]; + buf[i++] = '0'; + buf[i++] = 'x'; + for (j = 0; j < (sizeof(uint64_t) * 2); j++, value <<= 4) + buf[i++] = digits[value >> (sizeof(uint64_t) * 8 - 4)]; + for (int k = 0; k < i && *n < m; k++) { + dst[(*n)++] = buf[k]; + } +} + +int vsnprintf(char *restrict buf, size_t size, const char *restrict fmt, va_list ap) +{ + int l = 0; + char *a, *z, *s = (char *)fmt; + int cnt = 0; + int n = size - 1; + + for (; cnt < n;) { + if (!*s) + break; + while (*s && *s != '%') { + buf[cnt++] = *s; + s++; + if (cnt == n) + break; + } + if (cnt == n || !*s) + break; + for (z = s; s[0] == '%' && s[1] == '%'; z++, s += 2) { + buf[cnt++] = '%'; + if (cnt == n) + break; + } + if (cnt == n || !*s) + break; + if (*s != '%') + continue; + if (s[1] == 0) + break; + switch (s[1]) { + case 'u': + parsenint(buf, &cnt, n, va_arg(ap, int), 10, 0); + break; + case 'c': + buf[cnt++] = (char)va_arg(ap, int); + break; + case 'd': + parsenint(buf, &cnt, n, va_arg(ap, int), 10, 1); + break; + case 'x': + parsenint(buf, &cnt, n, va_arg(ap, int), 16, 1); + break; + case 'p': + parsenptr(buf, &cnt, n, va_arg(ap, uint64_t)); + break; + case 's': + if ((a = va_arg(ap, char *)) == 0) + a = "(null)"; + l = strnlen(a, 200); + for (int i = 0; i < l && cnt < n; i++) { + buf[cnt++] = a[i]; + } + break; + case 'l': + if (s[2] == 'u') { + parsenint(buf, &cnt, n, va_arg(ap, long), 10, 0); + } else if (s[2] == 'd') { + parsenint(buf, &cnt, n, va_arg(ap, int), 10, 1); + } else if (s[2] == 'x') { + parsenint(buf, &cnt, n, va_arg(ap, int), 16, 1); + } else if (s[2] == 'l' && s[3] == 'u') { + parsenint(buf, &cnt, n, va_arg(ap, long long), 10, 0); + s += 1; + } else { + buf[cnt++] = '%'; + if (cnt == n) + break; + buf[cnt++] = s[1]; + if (cnt == n) + break; + if (s[2]) { + buf[cnt++] = s[2]; + if (cnt == n) + break; + } else { + s -= 1; + } + } + s += 1; + break; + default: + buf[cnt++] = '%'; + if (cnt == n) + break; + buf[cnt++] = s[1]; + if (cnt == n) + break; + break; + } + s += 2; + } + buf[cnt] = '\0'; + return cnt; +} + +int snprintf(char *restrict s, size_t n, const char *restrict fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + int ret = vsnprintf(s, n, fmt, ap); + va_end(ap); + return ret; +} + +int vsprintf(char *restrict s, const char *restrict fmt, va_list ap) +{ + return vsnprintf(s, INT_MAX, fmt, ap); +} + +/// Currently print formatted text to stdout or stderr. +/// TODO: print to file +int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap) +{ + int ret; + char buf[1024]; + + ret = vsnprintf(buf, sizeof(buf), fmt, ap); + if (ret < 0) + return ret; + + if (f->fd == stdout->fd || f->fd == stderr->fd) + out(f->fd, buf, sizeof(buf)); + + return ret; +} + +int sprintf(char *restrict s, const char *restrict fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vsprintf(s, fmt, ap); + va_end(ap); + return ret; +} diff --git a/ulib/c_libax/src/stdlib.c b/ulib/c_libax/src/stdlib.c index d9f5157d1f..13c68be539 100644 --- a/ulib/c_libax/src/stdlib.c +++ b/ulib/c_libax/src/stdlib.c @@ -1,8 +1,12 @@ +#include +#include +#include #include #include #include +#include -#include +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) void srand(unsigned s) { @@ -21,6 +25,13 @@ void *malloc(size_t size) return ax_malloc(size); } +void *calloc(size_t m, size_t n) +{ + void *mem = ax_malloc(m * n); + + return memset(mem, 0, n * m); +} + void *realloc(void *memblock, size_t size) { size_t o_size = *(size_t *)(memblock - 8); @@ -59,3 +70,312 @@ int __clzdi2(int a) { return 0; } + +// TODO: set errno when overflow +long strtol(const char *restrict nptr, char **restrict endptr, int base) +{ + const char *s; + unsigned long acc; + unsigned char c; + unsigned long qbase, cutoff; + int neg, any, cutlim; + + s = nptr; + if (base < 0 || base == 1 || base > 36) { + // errno = -EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + qbase = (unsigned int)base; + cutoff = neg ? (unsigned long)LONG_MAX - (unsigned long)(LONG_MIN + LONG_MAX) : LONG_MAX; + cutlim = cutoff % qbase; + cutoff /= qbase; + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= qbase; + acc += c; + } + } + + if (any < 0) { + acc = neg ? LONG_MIN : LONG_MAX; + // errno = ERANGE; + } else if (neg) + acc = -acc; + +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +// TODO: set errno +unsigned long strtoul(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long acc; + unsigned char c; + unsigned long cutoff; + int neg = 0, any, cutlim; + + if (base < 0 || base == 1 || base > 36) { + // errno = -EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else if (c == '+') + c = *s++; + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + cutoff = (unsigned long)ULONG_MAX / (unsigned long)base; + cutlim = (unsigned long)ULONG_MAX % (unsigned long)base; + + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= base; + acc += c; + } + } + if (any < 0) { + acc = ULONG_MAX; + // errno = ERANGE; + } else if (neg) + acc = -acc; +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +// TODO: set errno +long long strtoll(const char *nptr, char **endptr, int base) +{ + const char *s; + unsigned long long acc; + unsigned char c; + unsigned long long qbase, cutoff; + int neg, any, cutlim; + + s = nptr; + if (base < 0 || base == 1 || base > 36) { + // errno = -EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + qbase = (unsigned int)base; + cutoff = neg ? (unsigned long long)LLONG_MAX - (unsigned long long)(LLONG_MIN + LLONG_MAX) + : LLONG_MAX; + cutlim = cutoff % qbase; + cutoff /= qbase; + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= qbase; + acc += c; + } + } + + if (any < 0) { + // errno = ERANGE; + acc = neg ? LLONG_MIN : LLONG_MAX; + } else if (neg) + acc = -acc; + +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +// TODO: set errno +unsigned long long strtoull(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long long acc; + unsigned char c; + unsigned long long qbase, cutoff; + int neg, any, cutlim; + + if (base < 0 || base == 1 || base > 36) { + // errno = -EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + qbase = (unsigned int)base; + cutoff = (unsigned long long)ULLONG_MAX / qbase; + cutlim = (unsigned long long)ULLONG_MAX % qbase; + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= qbase; + acc += c; + } + } + if (any < 0) { + // errno = ERANGE; + acc = ULLONG_MAX; + } else if (neg) + acc = -acc; + +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +void exit(int status) +{ + ax_exit(status); +} + +long long llabs(long long a) +{ + return a > 0 ? a : -a; +} + +int abs(int a) +{ + return a > 0 ? a : -a; +} + +long long atoll(const char *s) +{ + long long n = 0; + int neg = 0; + while (isspace(*s)) s++; + switch (*s) { + case '-': + neg = 1; + case '+': + s++; + } + /* Compute n as a negative number to avoid overflow on LLONG_MIN */ + while (isdigit(*s)) n = 10 * n - (*s++ - '0'); + return neg ? n : -n; +} diff --git a/ulib/c_libax/src/time.c b/ulib/c_libax/src/time.c index 3d58dddf5d..c8fed8dd9f 100644 --- a/ulib/c_libax/src/time.c +++ b/ulib/c_libax/src/time.c @@ -1,40 +1,162 @@ +#include #include #include #include -#include #include +#include + +const int SEC_PER_MIN = 60; +const int SEC_PER_HOUR = 3600; +const int MIN_PER_HOUR = 60; +const int HOUR_PER_DAY = 24; + +/* 2000-03-01 (mod 400 year, immediately after feb29 */ +#define LEAPOCH (946684800LL + 86400 * (31 + 29)) +#define DAYS_PER_400Y (365 * 400 + 97) +#define DAYS_PER_100Y (365 * 100 + 24) +#define DAYS_PER_4Y (365 * 4 + 1) + // TODO: size_t strftime(char *__restrict__ _Buf, size_t _SizeInBytes, const char *__restrict__ _Format, const struct tm *__restrict__ _Tm) { + unimplemented(); return 0; } -// TODO: +int __secs_to_tm(long long t, struct tm *tm) +{ + long long days, secs, years; + int remdays, remsecs, remyears; + int qc_cycles, c_cycles, q_cycles; + int months; + int wday, yday, leap; + static const char days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29}; + + /* Reject time_t values whose year would overflow int */ + if (t < INT_MIN * 31622400LL || t > INT_MAX * 31622400LL) + return -1; + + secs = t - LEAPOCH; + days = secs / 86400; + remsecs = secs % 86400; + if (remsecs < 0) { + remsecs += 86400; + days--; + } + + wday = (3 + days) % 7; + if (wday < 0) + wday += 7; + + qc_cycles = days / DAYS_PER_400Y; + remdays = days % DAYS_PER_400Y; + if (remdays < 0) { + remdays += DAYS_PER_400Y; + qc_cycles--; + } + + c_cycles = remdays / DAYS_PER_100Y; + if (c_cycles == 4) + c_cycles--; + remdays -= c_cycles * DAYS_PER_100Y; + + q_cycles = remdays / DAYS_PER_4Y; + if (q_cycles == 25) + q_cycles--; + remdays -= q_cycles * DAYS_PER_4Y; + + remyears = remdays / 365; + if (remyears == 4) + remyears--; + remdays -= remyears * 365; + + leap = !remyears && (q_cycles || !c_cycles); + yday = remdays + 31 + 28 + leap; + if (yday >= 365 + leap) + yday -= 365 + leap; + + years = remyears + 4 * q_cycles + 100 * c_cycles + 400LL * qc_cycles; + + for (months = 0; days_in_month[months] <= remdays; months++) remdays -= days_in_month[months]; + + if (months >= 10) { + months -= 12; + years++; + } + + if (years + 100 > INT_MAX || years + 100 < INT_MIN) + return -1; + + tm->tm_year = years + 100; + tm->tm_mon = months + 2; + tm->tm_mday = remdays + 1; + tm->tm_wday = wday; + tm->tm_yday = yday; + + tm->tm_hour = remsecs / 3600; + tm->tm_min = remsecs / 60 % 60; + tm->tm_sec = remsecs % 60; + + return 0; +} + +struct tm *__gmtime_r(const time_t *restrict t, struct tm *restrict tm) +{ + if (__secs_to_tm(*t, tm) < 0) { + // TODO: set errno + // errno = EOVERFLOW; + return 0; + } + tm->tm_isdst = 0; + tm->__tm_gmtoff = 0; + // TODO: set timezone + // tm->__tm_zone = __utc; + return tm; +} + struct tm *gmtime(const time_t *timer) { - return NULL; + static struct tm tm; + return __gmtime_r(timer, &tm); +} + +// TODO: more field should be added +struct tm *localtime_r(const time_t *restrict t, struct tm *restrict tm) +{ + time_t sec = *t; + tm->tm_sec = sec % SEC_PER_MIN; + tm->tm_min = (sec / SEC_PER_MIN) % MIN_PER_HOUR; + tm->tm_hour = (sec / SEC_PER_HOUR) % HOUR_PER_DAY; + + return tm; } -// TODO: struct tm *localtime(const time_t *timep) { - unimplemented(); - return 0; + static struct tm tm; + return localtime_r(timep, &tm); } -// TODO: time_t time(time_t *t) { - unimplemented(); - return 0; + struct timespec ts; + ax_clock_gettime(&ts); + time_t ret = ts.tv_sec; + if (t) + *t = ret; + return ret; } -// TODO: int gettimeofday(struct timeval *tv, struct timezone *tz) { - unimplemented(); + struct timespec ts; + if (!tv) + return 0; + clock_gettime(CLOCK_REALTIME, &ts); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = (int)ts.tv_nsec / 1000; return 0; } @@ -44,3 +166,14 @@ int utimes(const char *filename, const struct timeval times[2]) unimplemented(); return 0; } + +// TODO: Should match _clk, +int clock_gettime(clockid_t _clk, struct timespec *ts) +{ + return ax_clock_gettime(ts); +} + +int nanosleep(const struct timespec *req, struct timespec *rem) +{ + return ax_nanosleep(req, rem); +} diff --git a/ulib/c_libax/src/unistd.c b/ulib/c_libax/src/unistd.c index 03ce84a6a7..07037706c7 100644 --- a/ulib/c_libax/src/unistd.c +++ b/ulib/c_libax/src/unistd.c @@ -1,6 +1,7 @@ #include #include #include +#include #include // TODO: @@ -10,17 +11,37 @@ uid_t geteuid(void) return 0; } -// TODO: pid_t getpid(void) { - unimplemented(); - return -1; +#ifdef AX_CONFIG_MULTITASK + return ax_getpid(); +#else + // return 'main' task Id + return 2; +#endif } -// TODO: unsigned int sleep(unsigned int seconds) { - unimplemented(); + struct timespec ts; + + ts.tv_sec = seconds; + ts.tv_nsec = 0; + if (nanosleep(&ts, &ts)) + return ts.tv_sec; + + return 0; +} + +int usleep(unsigned int usec) +{ + struct timespec ts; + + ts.tv_sec = (long int)(usec / 1000000); + ts.tv_nsec = (long int)ax_time_usec_to_nsec(usec % 1000000); + if (nanosleep(&ts, &ts)) + return -1; + return 0; } diff --git a/ulib/libax/build.rs b/ulib/libax/build.rs index 0f3420d9a0..44534a2dd0 100644 --- a/ulib/libax/build.rs +++ b/ulib/libax/build.rs @@ -8,7 +8,12 @@ fn main() { cpp_compat: true, no_includes: false, usize_is_size_t: true, - sys_includes: vec!["sys/types.h".into(), "sys/stat.h".into()], + sys_includes: vec![ + "sys/types.h".into(), + "sys/stat.h".into(), + "stdio.h".into(), + "time.h".into(), + ], header: Some("/* Generated by cbindgen and build.rs, DO NOT edit! */".into()), ..Default::default() }; @@ -17,6 +22,11 @@ fn main() { .rename .insert("stat".into(), "struct stat".into()); + config + .export + .rename + .insert("timespec".into(), "struct timespec".into()); + cbindgen::generate_with_config(crate_dir, config) .expect("Unable to generate rust->c bindings") .write_to_file(out_file); @@ -26,7 +36,9 @@ fn main() { println!("cargo:rerun-if-changed={in_file}"); let include_dir = crate_dir.join("../c_libax/include"); - let allow_types = ["stat", "size_t", "ssize_t", "off_t", "mode_t", "O_*"]; + let allow_types = [ + "stat", "size_t", "ssize_t", "off_t", "mode_t", "O_*", "FILE", + ]; let allow_vars = ["O_.*"]; let mut builder = bindgen::Builder::default() diff --git a/ulib/libax/ctypes.h b/ulib/libax/ctypes.h index 335c8b82d8..4857ec6a6c 100644 --- a/ulib/libax/ctypes.h +++ b/ulib/libax/ctypes.h @@ -1,3 +1,4 @@ #include #include #include +#include diff --git a/ulib/libax/src/cbindings/mod.rs b/ulib/libax/src/cbindings/mod.rs index d00f5e3088..f083161ec4 100644 --- a/ulib/libax/src/cbindings/mod.rs +++ b/ulib/libax/src/cbindings/mod.rs @@ -10,6 +10,9 @@ mod fs; #[cfg(feature = "alloc")] mod malloc; +mod thread; +mod time; + /// cbindgen:ignore #[rustfmt::skip] #[path = "./ctypes_gen.rs"] @@ -54,3 +57,9 @@ pub use self::malloc::{ax_free, ax_malloc}; pub use self::fs::{ ax_close, ax_fstat, ax_getcwd, ax_lseek, ax_lstat, ax_open, ax_read, ax_stat, ax_write, }; + +#[cfg(feature = "multitask")] +pub use self::thread::ax_getpid; + +pub use self::thread::ax_exit; +pub use self::time::{ax_clock_gettime, ax_nanosleep}; diff --git a/ulib/libax/src/cbindings/thread.rs b/ulib/libax/src/cbindings/thread.rs new file mode 100644 index 0000000000..cb46d144b7 --- /dev/null +++ b/ulib/libax/src/cbindings/thread.rs @@ -0,0 +1,26 @@ +//! This crate includes syscalls that need `multitask` feature + +use crate::debug; +use crate::task::exit; +use axerrno::{LinuxError, LinuxResult}; +use core::ffi::c_int; + +#[cfg(feature = "multitask")] +use crate::task::current; + +/// Exit current task +#[no_mangle] +pub unsafe extern "C" fn ax_exit(exit_code: c_int) -> ! { + exit(exit_code) +} + +/// Get current thread ID +#[cfg(feature = "multitask")] +#[no_mangle] +pub unsafe extern "C" fn ax_getpid() -> c_int { + ax_call_body!(ax_getpid, { + let pid = current().id().as_u64() as c_int; + debug!("getpid return {}", pid); + return Ok(pid); + }) +} diff --git a/ulib/libax/src/cbindings/time.rs b/ulib/libax/src/cbindings/time.rs new file mode 100644 index 0000000000..05829ac423 --- /dev/null +++ b/ulib/libax/src/cbindings/time.rs @@ -0,0 +1,61 @@ +use crate::{debug, task::sleep}; +use axerrno::{LinuxError, LinuxResult}; +use axhal::time::{current_time, NANOS_PER_SEC}; +use core::{ + ffi::{c_int, c_long}, + time::Duration, +}; + +use super::ctypes; + +/// Get clock time since booting +#[no_mangle] +pub unsafe extern "C" fn ax_clock_gettime(ts: *mut ctypes::timespec) -> c_int { + ax_call_body!(ax_clock_gettime, { + if ts.is_null() { + return Err(LinuxError::EFAULT); + } + let now = current_time(); + let ret = ctypes::timespec { + tv_sec: now.as_secs() as c_long, + tv_nsec: now.subsec_nanos() as c_long, + }; + unsafe { + *ts = ret; + } + debug!("ax_clock_gettime: {}s, {}ns", now.as_secs(), now.as_nanos()); + return Ok(0); + }) +} + +/// Sleep some nanoseconds +/// +/// TODO: should be woken by signals, and set errno +#[no_mangle] +pub unsafe extern "C" fn ax_nanosleep( + req: *const ctypes::timespec, + rem: *mut ctypes::timespec, +) -> c_int { + ax_call_body!(ax_nanosleep, { + if req.is_null() || (*req).tv_nsec < 0 || (*req).tv_nsec > 999999999 { + return Err(LinuxError::EINVAL); + } + + let total_nano = (*req).tv_sec as u64 * NANOS_PER_SEC + (*req).tv_nsec as u64; + let before = current_time().as_nanos() as u64; + + sleep(Duration::from_nanos(total_nano)); + + let after = current_time().as_nanos() as u64; + let diff = after - before; + + if diff < total_nano { + if !rem.is_null() { + (*rem).tv_sec = ((total_nano - diff) / NANOS_PER_SEC) as i64; + (*rem).tv_nsec = ((total_nano - diff) % NANOS_PER_SEC) as i64; + } + return Err(LinuxError::EINTR); + } + return Ok(0); + }) +} From a8fe8f1b84f4fe65e47c003977da3fbce2c803f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B6=E6=98=8A=E6=98=9F?= Date: Sat, 29 Apr 2023 15:54:12 +0800 Subject: [PATCH 02/13] apply cfs.patch --- Cargo.lock | 7 + Cargo.toml | 1 + apps/task/parallel/Cargo.toml | 3 +- apps/task/parallel/test_cmd | 2 +- apps/task/priority/Cargo.toml | 16 ++ apps/task/priority/expect_info_smp1_cfs.out | 38 ++++ apps/task/priority/expect_info_smp1_fifo.out | 37 ++++ apps/task/priority/expect_info_smp1_rr.out | 38 ++++ apps/task/priority/expect_info_smp4_cfs.out | 44 +++++ apps/task/priority/src/main.rs | 126 +++++++++++++ apps/task/priority/test_cmd | 4 + crates/scheduler/src/cfs.rs | 188 +++++++++++++++++++ crates/scheduler/src/fifo.rs | 8 + crates/scheduler/src/lib.rs | 5 + crates/scheduler/src/round_robin.rs | 8 + crates/scheduler/src/tests.rs | 3 + doc/apps_priority.md | 41 ++++ modules/axtask/Cargo.toml | 1 + modules/axtask/src/api.rs | 15 +- modules/axtask/src/run_queue.rs | 5 + modules/axtask/src/tests.rs | 1 + scripts/test/app_test.sh | 1 + ulib/libax/Cargo.toml | 1 + ulib/libax/src/task.rs | 2 +- ulib/libax/src/time.rs | 1 + 25 files changed, 588 insertions(+), 8 deletions(-) create mode 100644 apps/task/priority/Cargo.toml create mode 100644 apps/task/priority/expect_info_smp1_cfs.out create mode 100644 apps/task/priority/expect_info_smp1_fifo.out create mode 100644 apps/task/priority/expect_info_smp1_rr.out create mode 100644 apps/task/priority/expect_info_smp4_cfs.out create mode 100644 apps/task/priority/src/main.rs create mode 100644 apps/task/priority/test_cmd create mode 100644 crates/scheduler/src/cfs.rs create mode 100644 doc/apps_priority.md diff --git a/Cargo.lock b/Cargo.lock index 10a89d3c85..a4abd98458 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,13 @@ dependencies = [ "libax", ] +[[package]] +name = "arceos-priority" +version = "0.1.0" +dependencies = [ + "libax", +] + [[package]] name = "arceos-shell" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 839971bf2b..cdf46fd2d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "apps/task/parallel", "apps/task/sleep", "apps/task/yield", + "apps/task/priority", "crates/allocator", "crates/arm_gic", diff --git a/apps/task/parallel/Cargo.toml b/apps/task/parallel/Cargo.toml index 87e69d9468..7b2835b975 100644 --- a/apps/task/parallel/Cargo.toml +++ b/apps/task/parallel/Cargo.toml @@ -7,8 +7,9 @@ authors = ["Yuekai Jia "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -preempt = ["libax/sched_rr"] +sched_rr = ["libax/sched_rr"] default = ["libax/default"] +sched_cfs = ["libax/sched_cfs"] [dependencies] libax = { path = "../../../ulib/libax", default-features = false, features = ["alloc", "paging", "multitask", "irq"] } diff --git a/apps/task/parallel/test_cmd b/apps/task/parallel/test_cmd index 5f48dbd667..1bf2e97ef9 100644 --- a/apps/task/parallel/test_cmd +++ b/apps/task/parallel/test_cmd @@ -1,2 +1,2 @@ test_one "LOG=info" "expect_info_smp1_fifo.out" -test_one "SMP=4 LOG=info APP_FEATURES=preempt" "expect_info_smp4_rr.out" +test_one "SMP=4 LOG=info APP_FEATURES=sched_rr" "expect_info_smp4_rr.out" diff --git a/apps/task/priority/Cargo.toml b/apps/task/priority/Cargo.toml new file mode 100644 index 0000000000..de8d948ad8 --- /dev/null +++ b/apps/task/priority/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "arceos-priority" +version = "0.1.0" +edition = "2021" +authors = ["Haoxing Ye "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["libax/default"] +sched_cfs = ["libax/sched_cfs"] +sched_rr = ["libax/sched_rr"] +sched_fifo = ["libax/sched_fifo"] + +[dependencies] +libax = { path = "../../../ulib/libax", default-features = false, features = ["alloc", "paging", "multitask"] } diff --git a/apps/task/priority/expect_info_smp1_cfs.out b/apps/task/priority/expect_info_smp1_cfs.out new file mode 100644 index 0000000000..1a51880aa2 --- /dev/null +++ b/apps/task/priority/expect_info_smp1_cfs.out @@ -0,0 +1,38 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize scheduling... + use Completely Fair scheduler. +Initialize interrupt handlers... +Primary CPU 0 init OK. +part 0: TaskId(4) \[0, 40) +part 1: TaskId(5) \[0, 40) +part 2: TaskId(6) \[0, 40) +part 3: TaskId(7) \[0, 40) +part 4: TaskId(8) \[0, 4) +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/expect_info_smp1_fifo.out b/apps/task/priority/expect_info_smp1_fifo.out new file mode 100644 index 0000000000..74c8cd62bd --- /dev/null +++ b/apps/task/priority/expect_info_smp1_fifo.out @@ -0,0 +1,37 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize scheduling... + use FIFO scheduler. +Primary CPU 0 init OK. +part 0: TaskId(4) \[0, 40) +part 0: TaskId(4) finished +part 1: TaskId(5) \[0, 40) +part 1: TaskId(5) finished +part 2: TaskId(6) \[0, 40) +part 2: TaskId(6) finished +part 3: TaskId(7) \[0, 40) +part 3: TaskId(7) finished +part 4: TaskId(8) \[0, 4) +part 4: TaskId(8) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/expect_info_smp1_rr.out b/apps/task/priority/expect_info_smp1_rr.out new file mode 100644 index 0000000000..b3a9270cf8 --- /dev/null +++ b/apps/task/priority/expect_info_smp1_rr.out @@ -0,0 +1,38 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize scheduling... + use Round-robin scheduler. +Initialize interrupt handlers... +Primary CPU 0 init OK. +part 0: TaskId(4) \[0, 40) +part 1: TaskId(5) \[0, 40) +part 2: TaskId(6) \[0, 40) +part 3: TaskId(7) \[0, 40) +part 4: TaskId(8) \[0, 4) +part 0: TaskId(4) finished +part 1: TaskId(5) finished +part 2: TaskId(6) finished +part 3: TaskId(7) finished +part 4: TaskId(8) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/expect_info_smp4_cfs.out b/apps/task/priority/expect_info_smp4_cfs.out new file mode 100644 index 0000000000..ef7428adcf --- /dev/null +++ b/apps/task/priority/expect_info_smp4_cfs.out @@ -0,0 +1,44 @@ +smp = 4 +build_mode = release +log_level = info + +Primary CPU [0-9]\+ started, +Secondary CPU [0-9]\+ started. +Secondary CPU [0-9]\+ started. +Secondary CPU [0-9]\+ started. +Secondary CPU [0-9]\+ init OK. +Secondary CPU [0-9]\+ init OK. +Secondary CPU [0-9]\+ init OK. +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize scheduling... + use Completely Fair scheduler. +Initialize interrupt handlers... +Primary CPU 0 init OK. +part 0: TaskId(7) \[0, 40) +part 1: TaskId(8) \[0, 40) +part 2: TaskId(9) \[0, 40) +part 3: TaskId(10) \[0, 40) +part 4: TaskId(11) \[0, 4) +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +part [0-9]\+: TaskId([0-9]\+) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/src/main.rs b/apps/task/priority/src/main.rs new file mode 100644 index 0000000000..ae4aea7068 --- /dev/null +++ b/apps/task/priority/src/main.rs @@ -0,0 +1,126 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate libax; +extern crate alloc; + +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::sync::atomic::{AtomicUsize, Ordering}; +use libax::sync::{Mutex, WaitQueue}; +use libax::task; + +struct TaskParam { + data_len: usize, + value: u64, + nice: isize, +} + +const TASK_PARAMS: &[TaskParam] = &[ + // four short tasks + TaskParam { + data_len: 40, + value: 1000000, + nice: 19, + }, + TaskParam { + data_len: 40, + value: 1000000, + nice: 10, + }, + TaskParam { + data_len: 40, + value: 1000000, + nice: 0, + }, + TaskParam { + data_len: 40, + value: 1000000, + nice: -10, + }, + // one long task + TaskParam { + data_len: 4, + value: 10000000, + nice: 0, + }, +]; + +const PAYLOAD_KIND: usize = 5; + +static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + +static MAIN_WQ: WaitQueue = WaitQueue::new(); +static RESULTS: Mutex<[u64; PAYLOAD_KIND]> = Mutex::new([0; PAYLOAD_KIND]); // TODO: task join +static LEAVE_TIME: Mutex<[u64; PAYLOAD_KIND]> = Mutex::new([0; PAYLOAD_KIND]); + +fn load(n: &u64) -> u64 { + // time consuming is linear with *n + let mut sum: u64 = *n; + for i in 0..*n { + sum += ((i ^ (i * 3)) ^ (i + *n)) / (i + 1); + } + sum +} + +#[no_mangle] +fn main() { + task::set_priority(-20); + let data = (0..PAYLOAD_KIND) + .map(|i| Arc::new(vec![TASK_PARAMS[i].value; TASK_PARAMS[i].data_len])) + .collect::>(); + let mut expect: u64 = 0; + for i in 0..PAYLOAD_KIND { + expect += data[i].iter().map(load).sum::(); + } + let start_time = libax::time::Instant::now(); + for i in 0..PAYLOAD_KIND { + let vec = data[i].clone(); + let data_len = TASK_PARAMS[i].data_len; + let nice = TASK_PARAMS[i].nice; + task::spawn(move || { + let left = 0; + let right = data_len; + task::set_priority(nice); + println!( + "part {}: {:?} [{}, {})", + i, + task::current().id(), + left, + right + ); + + RESULTS.lock()[i] = vec[left..right].iter().map(load).sum(); + LEAVE_TIME.lock()[i] = start_time.elapsed().as_millis() as u64; + + println!("part {}: {:?} finished", i, task::current().id()); + let n = FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + if n == PAYLOAD_KIND - 1 { + MAIN_WQ.notify_one(true); + } + }); + } + + MAIN_WQ.wait(); + + let actual = RESULTS.lock().iter().sum(); + println!("sum = {}", actual); + let level_times = LEAVE_TIME.lock(); + println!("leave time:"); + for i in 0..PAYLOAD_KIND { + println!("task {} = {}ms", i, level_times[i]); + } + + if cfg!(feature = "sched_cfs") && option_env!("SMP") == Some("1") { + assert!( + level_times[0] > level_times[1] + && level_times[1] > level_times[2] + && level_times[2] > level_times[3] + ); + } + + assert_eq!(expect, actual); + + println!("Priority tests run OK!"); +} diff --git a/apps/task/priority/test_cmd b/apps/task/priority/test_cmd new file mode 100644 index 0000000000..2202189771 --- /dev/null +++ b/apps/task/priority/test_cmd @@ -0,0 +1,4 @@ +test_one "SMP=1 LOG=info APP_FEATURES=sched_cfs" "expect_info_smp1_cfs.out" +test_one "SMP=1 LOG=info APP_FEATURES=sched_fifo" "expect_info_smp1_fifo.out" +test_one "SMP=1 LOG=info APP_FEATURES=sched_rr" "expect_info_smp1_rr.out" +test_one "SMP=4 LOG=info APP_FEATURES=sched_cfs" "expect_info_smp4_cfs.out" \ No newline at end of file diff --git a/crates/scheduler/src/cfs.rs b/crates/scheduler/src/cfs.rs new file mode 100644 index 0000000000..a257d2ce73 --- /dev/null +++ b/crates/scheduler/src/cfs.rs @@ -0,0 +1,188 @@ +use alloc::{collections::BTreeMap, sync::Arc}; +use core::ops::Deref; +use core::sync::atomic::{AtomicIsize, Ordering}; + +use crate::BaseScheduler; + +/// task for CFS +pub struct CFSTask { + inner: T, + init_vruntime: AtomicIsize, + delta: AtomicIsize, + nice: AtomicIsize, + id: AtomicIsize, +} + +// https://elixir.bootlin.com/linux/latest/source/include/linux/sched/prio.h + +const NICE_RANGE_POS: usize = 19; // MAX_NICE in Linux +const NICE_RANGE_NEG: usize = 20; // -MIN_NICE in Linux, the range of nice is [MIN_NICE, MAX_NICE] + +// https://elixir.bootlin.com/linux/latest/source/kernel/sched/core.c + +const NICE2WEIGHT_POS: [isize; NICE_RANGE_POS + 1] = [ + 1024, 820, 655, 526, 423, 335, 272, 215, 172, 137, 110, 87, 70, 56, 45, 36, 29, 23, 18, 15, +]; +const NICE2WEIGHT_NEG: [isize; NICE_RANGE_NEG + 1] = [ + 1024, 1277, 1586, 1991, 2501, 3121, 3906, 4904, 6100, 7620, 9548, 11916, 14949, 18705, 23254, + 29154, 36291, 46273, 56483, 71755, 88761, +]; + +impl CFSTask { + /// new with default values + pub const fn new(inner: T) -> Self { + Self { + inner, + init_vruntime: AtomicIsize::new(0_isize), + delta: AtomicIsize::new(0_isize), + nice: AtomicIsize::new(0_isize), + id: AtomicIsize::new(0_isize), + } + } + + fn get_weight(&self) -> isize { + let nice = self.nice.load(Ordering::Acquire); + if nice >= 0 { + NICE2WEIGHT_POS[nice as usize] + } else { + NICE2WEIGHT_NEG[(-nice) as usize] + } + } + + fn get_id(&self) -> isize { + self.id.load(Ordering::Acquire) + } + + fn get_vruntime(&self) -> isize { + if self.nice.load(Ordering::Acquire) == 0 { + self.init_vruntime.load(Ordering::Acquire) + self.delta.load(Ordering::Acquire) + } else { + self.init_vruntime.load(Ordering::Acquire) + + self.delta.load(Ordering::Acquire) * 1024 / self.get_weight() + } + } + + fn set_vruntime(&self, v: isize) { + self.init_vruntime.store(v, Ordering::Release); + } + + // Simple Implementation: no change in vruntime. + // Only modifying priority of current process is supported currently. + fn set_priority(&self, nice: isize) { + let current_init_vruntime = self.get_vruntime(); + self.init_vruntime + .store(current_init_vruntime, Ordering::Release); + self.delta.store(0, Ordering::Release); + self.nice.store(nice, Ordering::Release); + } + + fn set_id(&self, id: isize) { + self.id.store(id, Ordering::Release); + } + + fn task_tick(&self) { + self.delta.fetch_add(1, Ordering::Release); + } + + /// Returns a reference to the inner task struct. + pub const fn inner(&self) -> &T { + &self.inner + } +} + +impl Deref for CFSTask { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// scheduler for CFS +pub struct CFScheduler { + ready_queue: BTreeMap<(isize, isize), Arc>>, // (vruntime, taskid) + min_vruntime: Option, + id_pool: AtomicIsize, +} + +impl CFScheduler { + /// Creates a new empty [`CFScheduler`]. + pub const fn new() -> Self { + Self { + ready_queue: BTreeMap::new(), + min_vruntime: None, + id_pool: AtomicIsize::new(0_isize), + } + } + /// get the name of scheduler + pub fn scheduler_name() -> &'static str { + "Completely Fair" + } +} + +impl BaseScheduler for CFScheduler { + type SchedItem = Arc>; + + fn init(&mut self) {} + + fn add_task(&mut self, task: Self::SchedItem) { + if self.min_vruntime.is_none() { + self.min_vruntime = Some(AtomicIsize::new(0_isize)); + } + let vruntime = self.min_vruntime.as_mut().unwrap().load(Ordering::Acquire); + let taskid = self.id_pool.fetch_add(1, Ordering::Release); + task.set_vruntime(vruntime); + task.set_id(taskid); + self.ready_queue.insert((vruntime, taskid), task); + if let Some(((min_vruntime, _), _)) = self.ready_queue.first_key_value() { + self.min_vruntime = Some(AtomicIsize::new(*min_vruntime)); + } else { + self.min_vruntime = None; + } + } + + fn remove_task(&mut self, task: &Self::SchedItem) -> Option { + if let Some((_, tmp)) = self + .ready_queue + .remove_entry(&(task.clone().get_vruntime(), task.clone().get_id())) + { + if let Some(((min_vruntime, _), _)) = self.ready_queue.first_key_value() { + self.min_vruntime = Some(AtomicIsize::new(*min_vruntime)); + } else { + self.min_vruntime = None; + } + Some(tmp) + } else { + None + } + } + + fn pick_next_task(&mut self) -> Option { + if let Some((_, v)) = self.ready_queue.pop_first() { + Some(v) + } else { + None + } + } + + fn put_prev_task(&mut self, prev: Self::SchedItem, _preempt: bool) { + let taskid = self.id_pool.fetch_add(1, Ordering::Release); + prev.set_id(taskid); + self.ready_queue + .insert((prev.clone().get_vruntime(), taskid), prev); + } + + fn task_tick(&mut self, current: &Self::SchedItem) -> bool { + current.task_tick(); + self.min_vruntime.is_none() + || current.get_vruntime() > self.min_vruntime.as_mut().unwrap().load(Ordering::Acquire) + } + + fn set_priority(&mut self, task: &Self::SchedItem, prio: isize) -> bool { + if (-20..=19).contains(&prio) { + task.set_priority(prio); + true + } else { + false + } + } +} diff --git a/crates/scheduler/src/fifo.rs b/crates/scheduler/src/fifo.rs index 7d56acaaf5..f2469e5c56 100644 --- a/crates/scheduler/src/fifo.rs +++ b/crates/scheduler/src/fifo.rs @@ -65,6 +65,10 @@ impl FifoScheduler { ready_queue: List::new(), } } + /// get the name of scheduler + pub fn scheduler_name() -> &'static str { + "FIFO" + } } impl BaseScheduler for FifoScheduler { @@ -91,4 +95,8 @@ impl BaseScheduler for FifoScheduler { fn task_tick(&mut self, _current: &Self::SchedItem) -> bool { false // no reschedule } + + fn set_priority(&mut self, _task: &Self::SchedItem, _prio: isize) -> bool { + false + } } diff --git a/crates/scheduler/src/lib.rs b/crates/scheduler/src/lib.rs index 8040fd9575..13269959f0 100644 --- a/crates/scheduler/src/lib.rs +++ b/crates/scheduler/src/lib.rs @@ -8,6 +8,7 @@ #![cfg_attr(not(test), no_std)] #![feature(const_mut_refs)] +mod cfs; mod fifo; mod round_robin; @@ -16,6 +17,7 @@ mod tests; extern crate alloc; +pub use cfs::{CFSTask, CFScheduler}; pub use fifo::{FifoScheduler, FifoTask}; pub use round_robin::{RRScheduler, RRTask}; @@ -60,4 +62,7 @@ pub trait BaseScheduler { /// /// `current` is the current running task. fn task_tick(&mut self, current: &Self::SchedItem) -> bool; + + /// set priority for a task + fn set_priority(&mut self, task: &Self::SchedItem, prio: isize) -> bool; } diff --git a/crates/scheduler/src/round_robin.rs b/crates/scheduler/src/round_robin.rs index c35167f1ca..ad823bfd63 100644 --- a/crates/scheduler/src/round_robin.rs +++ b/crates/scheduler/src/round_robin.rs @@ -65,6 +65,10 @@ impl RRScheduler { ready_queue: VecDeque::new(), } } + /// get the name of scheduler + pub fn scheduler_name() -> &'static str { + "Round-robin" + } } impl BaseScheduler for RRScheduler { @@ -101,4 +105,8 @@ impl BaseScheduler for RRScheduler { let old_slice = current.time_slice.fetch_sub(1, Ordering::Release); old_slice <= 1 } + + fn set_priority(&mut self, _task: &Self::SchedItem, _prio: isize) -> bool { + false + } } diff --git a/crates/scheduler/src/tests.rs b/crates/scheduler/src/tests.rs index 595bdfd4af..472820cae9 100644 --- a/crates/scheduler/src/tests.rs +++ b/crates/scheduler/src/tests.rs @@ -16,6 +16,8 @@ macro_rules! def_test_sched { for i in 0..NUM_TASKS * 10 - 1 { let next = scheduler.pick_next_task().unwrap(); assert_eq!(*next.inner(), i % NUM_TASKS); + // pass a tick to ensure the order of tasks + scheduler.task_tick(&next); scheduler.put_prev_task(next, false); } @@ -79,3 +81,4 @@ macro_rules! def_test_sched { def_test_sched!(fifo, FifoScheduler::, FifoTask::); def_test_sched!(rr, RRScheduler::, RRTask::); +def_test_sched!(cfs, CFScheduler::, CFSTask::); diff --git a/doc/apps_priority.md b/doc/apps_priority.md new file mode 100644 index 0000000000..0b9238e637 --- /dev/null +++ b/doc/apps_priority.md @@ -0,0 +1,41 @@ +# INTRODUCTION + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [priority](../apps/task/priority/) | axalloc, axtask | alloc, paging, multitask, sched_fifo, sched_rr, sched_cfs | test priority according to cfs::nice| + +# RUN +```shell +make A=apps/task/priority ARCH=riscv64 SMP=1 APP_FEATURES=sched_cfs run LOG=info +``` +Other choises of APP_FEATURES: sched_fifo, sched_rr +## Using multicore +```shell +make A=apps/task/sched-realtime ARCH=riscv64 SMP=4 APP_FEATURES=sched_cfs run LOG=info +``` +Other choises of APP_FEATURES: sched_fifo, sched_rr + +# RESULT +``` +make A=apps/task/priority ARCH=riscv64 SMP=1 APP_FEATURES=sched_cfs run LOG=info +... +part 0: TaskId(4) [0, 40) +part 1: TaskId(5) [0, 40) +part 2: TaskId(6) [0, 40) +part 3: TaskId(7) [0, 40) +part 4: TaskId(8) [0, 4) +part 3: TaskId(7) finished +part 4: TaskId(8) finished +part 2: TaskId(6) finished +part 1: TaskId(5) finished +part 0: TaskId(4) finished +sum = 3318102132 +leave time: +task 0 = 614ms +task 1 = 479ms +task 2 = 374ms +task 3 = 166ms +task 4 = 371ms +Priority tests run OK! +[ 1.274073 0:2 axhal::platform::qemu_virt_riscv::misc:3] Shutting down... +``` \ No newline at end of file diff --git a/modules/axtask/Cargo.toml b/modules/axtask/Cargo.toml index 6e1cdbb349..e7348050d6 100644 --- a/modules/axtask/Cargo.toml +++ b/modules/axtask/Cargo.toml @@ -20,6 +20,7 @@ preempt = ["irq", "percpu?/preempt", "kernel_guard/preempt"] sched_fifo = ["multitask"] sched_rr = ["multitask", "preempt"] +sched_cfs = ["multitask", "preempt"] default = ["sched_fifo"] [dependencies] diff --git a/modules/axtask/src/api.rs b/modules/axtask/src/api.rs index 5be01ab320..5e04ce7d3b 100644 --- a/modules/axtask/src/api.rs +++ b/modules/axtask/src/api.rs @@ -18,6 +18,9 @@ cfg_if::cfg_if! { const MAX_TIME_SLICE: usize = 5; pub(crate) type AxTask = scheduler::RRTask; pub(crate) type Scheduler = scheduler::RRScheduler; + } else if #[cfg(feature = "sched_cfs")] { + pub(crate) type AxTask = scheduler::CFSTask; + pub(crate) type Scheduler = scheduler::CFScheduler; } } @@ -63,11 +66,7 @@ pub fn init_scheduler() { #[cfg(feature = "irq")] crate::timers::init(); - if cfg!(feature = "sched_fifo") { - info!(" use FIFO scheduler."); - } else if cfg!(feature = "sched_rr") { - info!(" use Round-robin scheduler."); - } + info!(" use {} scheduler.", Scheduler::scheduler_name()); } /// Initializes the task scheduler for secondary CPUs. @@ -97,6 +96,12 @@ where RUN_QUEUE.lock().add_task(task); } +/// set priority for current task. +/// In CFS, priority is the nice value, ranging from -20 to 19. +pub fn set_priority(prio: isize) -> bool { + RUN_QUEUE.lock().set_priority(prio) +} + /// Current task gives up the CPU time voluntarily, and switches to another /// ready task. pub fn yield_now() { diff --git a/modules/axtask/src/run_queue.rs b/modules/axtask/src/run_queue.rs index efe24931cf..b6371d795f 100644 --- a/modules/axtask/src/run_queue.rs +++ b/modules/axtask/src/run_queue.rs @@ -52,6 +52,11 @@ impl AxRunQueue { self.resched_inner(false); } + pub fn set_priority(&mut self, prio: isize) -> bool { + self.scheduler + .set_priority(crate::current().as_task_ref(), prio) + } + #[cfg(feature = "preempt")] pub fn resched(&mut self) { let curr = crate::current(); diff --git a/modules/axtask/src/tests.rs b/modules/axtask/src/tests.rs index 6ee660c23e..236183335a 100644 --- a/modules/axtask/src/tests.rs +++ b/modules/axtask/src/tests.rs @@ -22,6 +22,7 @@ fn test_sched_fifo() { assert_eq!(order, i); // FIFO scheduler }); } + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { axtask::yield_now(); } diff --git a/scripts/test/app_test.sh b/scripts/test/app_test.sh index ad47295b80..f97075a518 100755 --- a/scripts/test/app_test.sh +++ b/scripts/test/app_test.sh @@ -112,6 +112,7 @@ if [ -z "$1" ]; then "apps/task/yield" "apps/task/parallel" "apps/task/sleep" + "apps/task/priority" "apps/net/httpclient" "apps/c/helloworld" "apps/c/memtest" diff --git a/ulib/libax/Cargo.toml b/ulib/libax/Cargo.toml index b28cf863a3..655842458a 100644 --- a/ulib/libax/Cargo.toml +++ b/ulib/libax/Cargo.toml @@ -35,6 +35,7 @@ irq = ["axruntime/irq"] multitask = ["axtask", "axruntime/multitask", "axsync/multitask"] sched_fifo = ["axtask/sched_fifo"] sched_rr = ["axtask/sched_rr", "irq"] +sched_cfs = ["axtask/sched_cfs", "irq"] # File system fs = ["alloc", "axruntime/fs", "dep:axfs"] diff --git a/ulib/libax/src/task.rs b/ulib/libax/src/task.rs index c7b014ccd2..bcc25eb03b 100644 --- a/ulib/libax/src/task.rs +++ b/ulib/libax/src/task.rs @@ -1,7 +1,7 @@ //! Native threads. #[cfg(feature = "multitask")] -pub use axtask::{current, spawn, TaskId}; +pub use axtask::{current, set_priority, spawn, TaskId}; /// Current task gives up the CPU time voluntarily, and switches to another /// ready task. diff --git a/ulib/libax/src/time.rs b/ulib/libax/src/time.rs index 5a96c5a318..585052c83f 100644 --- a/ulib/libax/src/time.rs +++ b/ulib/libax/src/time.rs @@ -4,6 +4,7 @@ pub use core::time::Duration; /// A measurement of a monotonically nondecreasing clock. /// Opaque and useful only with [`Duration`]. +#[derive(Clone, Copy)] pub struct Instant(Duration); impl Instant { From 6a3fe3c1e3706d6696154758f9e812a0988b0386 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Sat, 29 Apr 2023 16:31:39 +0800 Subject: [PATCH 03/13] Fix CI --- apps/task/priority/expect_info_smp4_cfs.out | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/task/priority/expect_info_smp4_cfs.out b/apps/task/priority/expect_info_smp4_cfs.out index ef7428adcf..38c7f1e710 100644 --- a/apps/task/priority/expect_info_smp4_cfs.out +++ b/apps/task/priority/expect_info_smp4_cfs.out @@ -22,7 +22,7 @@ Initialize kernel page table... Initialize scheduling... use Completely Fair scheduler. Initialize interrupt handlers... -Primary CPU 0 init OK. +Primary CPU [0-9]\+ init OK. part 0: TaskId(7) \[0, 40) part 1: TaskId(8) \[0, 40) part 2: TaskId(9) \[0, 40) From cba30118c3dfc58cca3c163d2e139bde3afcc089 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Mon, 24 Apr 2023 22:03:05 +0800 Subject: [PATCH 04/13] x86: start to port to x86_64 --- .gitignore | 1 + Cargo.lock | 1 + Makefile | 24 +++- apps/c/sqlite3/axbuild.mk | 4 + apps/task/yield/expect_info_smp1_fifo.out | 32 +++++ apps/task/yield/test_cmd | 1 + modules/axconfig/Cargo.toml | 1 + modules/axconfig/build.rs | 26 ++-- modules/axconfig/src/lib.rs | 19 ++- modules/axconfig/src/platform/pc-x86.toml | 24 ++++ modules/axhal/Cargo.toml | 2 + modules/axhal/build.rs | 4 +- modules/axhal/src/arch/riscv/mod.rs | 2 +- modules/axhal/src/arch/x86_64/context.rs | 2 +- modules/axhal/src/arch/x86_64/mod.rs | 19 +-- modules/axhal/src/lib.rs | 2 + modules/axhal/src/platform/mod.rs | 6 + modules/axhal/src/platform/pc_x86/boot.rs | 50 ++++++++ modules/axhal/src/platform/pc_x86/irq.rs | 20 ++++ modules/axhal/src/platform/pc_x86/mem.rs | 30 +++++ modules/axhal/src/platform/pc_x86/misc.rs | 14 +++ modules/axhal/src/platform/pc_x86/mod.rs | 46 +++++++ modules/axhal/src/platform/pc_x86/mp.rs | 4 + modules/axhal/src/platform/pc_x86/multiboot.S | 113 ++++++++++++++++++ modules/axhal/src/platform/pc_x86/time.rs | 19 +++ .../axhal/src/platform/pc_x86/uart16550.rs | 105 ++++++++++++++++ rust-toolchain.toml | 2 +- scripts/make/cargo.mk | 2 +- scripts/make/qemu.mk | 24 +++- ulib/c_libax/build.mk | 12 +- ulib/libax/Cargo.toml | 1 + ulib/libax/src/lib.rs | 1 + 32 files changed, 568 insertions(+), 45 deletions(-) create mode 100644 apps/task/yield/expect_info_smp1_fifo.out create mode 100644 modules/axconfig/src/platform/pc-x86.toml create mode 100644 modules/axhal/src/platform/pc_x86/boot.rs create mode 100644 modules/axhal/src/platform/pc_x86/irq.rs create mode 100644 modules/axhal/src/platform/pc_x86/mem.rs create mode 100644 modules/axhal/src/platform/pc_x86/misc.rs create mode 100644 modules/axhal/src/platform/pc_x86/mod.rs create mode 100644 modules/axhal/src/platform/pc_x86/mp.rs create mode 100644 modules/axhal/src/platform/pc_x86/multiboot.S create mode 100644 modules/axhal/src/platform/pc_x86/time.rs create mode 100644 modules/axhal/src/platform/pc_x86/uart16550.rs diff --git a/.gitignore b/.gitignore index e51cbf7052..1ae218476f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.asm *.img actual.out +qemu.log diff --git a/Cargo.lock b/Cargo.lock index a4abd98458..ede0b2aafc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,7 @@ dependencies = [ "static_assertions", "tock-registers", "x86", + "x86_64", ] [[package]] diff --git a/Makefile b/Makefile index 074673229d..8d821d33c5 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # Arguments -ARCH ?= riscv64 +ARCH ?= x86_64 SMP ?= 1 MODE ?= release LOG ?= warn @@ -13,6 +13,8 @@ FS ?= n NET ?= n GRAPHIC ?= n +QEMU_LOG ?= n + ifeq ($(wildcard $(APP)),) $(error Application path "$(APP)" is not valid) endif @@ -24,14 +26,20 @@ else endif # Platform -ifeq ($(ARCH), riscv64) +ifeq ($(ARCH), x86_64) + ACCEL ?= y + PLATFORM ?= pc-x86 + TARGET := x86_64-unknown-none +else ifeq ($(ARCH), riscv64) + ACCEL ?= n PLATFORM ?= qemu-virt-riscv TARGET := riscv64gc-unknown-none-elf else ifeq ($(ARCH), aarch64) + ACCEL ?= n PLATFORM ?= qemu-virt-aarch64 TARGET := aarch64-unknown-none-softfloat else - $(error "ARCH" must be "riscv64" or "aarch64") + $(error "ARCH" must be one of "x86_64", "riscv64", or "aarch64") endif export ARCH @@ -44,9 +52,9 @@ export LOG ifeq ($(APP_LANG), c) CROSS_COMPILE ?= $(ARCH)-linux-musl- CC := $(CROSS_COMPILE)gcc - LD := $(CROSS_COMPILE)ld AR := $(CROSS_COMPILE)ar RANLIB := $(CROSS_COMPILE)ranlib + LD := rust-lld -flavor gnu endif OBJDUMP ?= rust-objdump -d --print-imm-hex --x86-asm-syntax=intel @@ -80,9 +88,13 @@ justrun: $(call run_qemu) debug: build - $(call run_qemu,-s -S) & + $(call run_qemu_debug) & sleep 1 - $(GDB) $(OUT_ELF) -ex 'target remote localhost:1234' + $(GDB) $(OUT_ELF) \ + -ex 'target remote localhost:1234' \ + -ex 'b rust_entry' \ + -ex 'continue' \ + -ex 'disp /16i $$pc' clippy: $(call cargo_clippy) diff --git a/apps/c/sqlite3/axbuild.mk b/apps/c/sqlite3/axbuild.mk index 0c2ba6d6e4..c48527de64 100644 --- a/apps/c/sqlite3/axbuild.mk +++ b/apps/c/sqlite3/axbuild.mk @@ -1,5 +1,9 @@ SQLITE3_CFLAGS := -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_FLOATING_POINT -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEBUG +ifeq ($(ARCH), riscv64) + LDFLAGS += --no-relax +endif + app-objs := main.o sqlite3.o $(APP)/main.o: $(APP)/sqlite3.c diff --git a/apps/task/yield/expect_info_smp1_fifo.out b/apps/task/yield/expect_info_smp1_fifo.out new file mode 100644 index 0000000000..8930c2a663 --- /dev/null +++ b/apps/task/yield/expect_info_smp1_fifo.out @@ -0,0 +1,32 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Primary CPU 0 init OK. +Hello, main task! +Hello, task 0! id = TaskId(4) +Hello, task 1! id = TaskId(5) +Hello, task 2! id = TaskId(6) +Hello, task 3! id = TaskId(7) +Hello, task 4! id = TaskId(8) +Hello, task 5! id = TaskId(9) +Hello, task 6! id = TaskId(10) +Hello, task 7! id = TaskId(11) +Hello, task 8! id = TaskId(12) +Hello, task 9! id = TaskId(13) +Task yielding tests run OK! +Shutting down... diff --git a/apps/task/yield/test_cmd b/apps/task/yield/test_cmd index bcc2072d7f..fdc4bae008 100644 --- a/apps/task/yield/test_cmd +++ b/apps/task/yield/test_cmd @@ -1,2 +1,3 @@ +test_one "LOG=info" "expect_info_smp1_fifo.out" test_one "SMP=4 LOG=info" "expect_info_smp4_fifo.out" test_one "SMP=4 LOG=info APP_FEATURES=preempt" "expect_info_smp4_rr.out" diff --git a/modules/axconfig/Cargo.toml b/modules/axconfig/Cargo.toml index db5f4c8410..24c82addf3 100644 --- a/modules/axconfig/Cargo.toml +++ b/modules/axconfig/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://github.com/rcore-os/arceos/tree/main/modules/axconfig" documentation = "https://rcore-os.github.io/arceos/axconfig/index.html" [features] +platform-pc-x86 = [] platform-qemu-virt-riscv = [] platform-qemu-virt-aarch64 = [] default = [] diff --git a/modules/axconfig/build.rs b/modules/axconfig/build.rs index 6468e430f6..1b211400eb 100644 --- a/modules/axconfig/build.rs +++ b/modules/axconfig/build.rs @@ -3,18 +3,16 @@ use std::{convert::AsRef, fs, path::Path}; use toml_edit::{Decor, Document, Item, Table, Value}; fn main() { - let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - let platform = if cfg!(feature = "platform-qemu-virt-riscv") { - "qemu-virt-riscv" - } else if cfg!(feature = "platform-qemu-virt-aarch64") { - "qemu-virt-aarch64" - } else { - println!("Unsupported platform, use dummy config!"); - "dummy" - }; - - gen_config_rs(&arch, platform).unwrap(); - + // generate config_*.rs for all platforms + for fname in fs::read_dir("src/platform").unwrap() { + let fname = fname.unwrap().path(); + if fname.extension().unwrap() == "toml" { + let platform = fname.file_stem().unwrap().to_str().unwrap(); + gen_config_rs(platform).unwrap(); + println!("cargo:rerun-if-changed={}", fname.display()); + } + } + println!("cargo:rerun-if-changed=src/defconfig.toml"); println!("cargo:rerun-if-env-changed=SMP"); } @@ -58,7 +56,7 @@ fn is_num(s: &str) -> bool { } } -fn gen_config_rs(arch: &str, platform: &str) -> Result<()> { +fn gen_config_rs(platform: &str) -> Result<()> { // Load TOML config file let mut config = Table::new(); parse_config_toml(&mut config, "src/defconfig.toml").unwrap(); @@ -77,7 +75,7 @@ fn gen_config_rs(arch: &str, platform: &str) -> Result<()> { let mut output = Vec::new(); writeln!( output, - "//! Platform constants and parameters for {arch} {platform}." + "//! Platform constants and parameters for {platform}." )?; writeln!(output, "//! Generated by build.rs, DO NOT edit!\n")?; diff --git a/modules/axconfig/src/lib.rs b/modules/axconfig/src/lib.rs index 7a380a58f4..e0a5f241de 100644 --- a/modules/axconfig/src/lib.rs +++ b/modules/axconfig/src/lib.rs @@ -2,6 +2,7 @@ //! //! Currently supported platforms (specify by cargo features): //! +//! - `platform-pc-x86`: Standard PC with x86_64 ISA. //! - `platform-qemu-virt-riscv`: QEMU virt machine with RISC-V ISA. //! - `platform-qemu-virt-aarch64`: QEMU virt machine with AArch64 ISA. //! - `dummy`: If none of the above platform is selected, the dummy platform @@ -14,11 +15,25 @@ #![no_std] cfg_if::cfg_if! { - if #[cfg(feature = "platform-qemu-virt-riscv")] { + // add `not(target_os = "none")` check to use in `build.rs` + if #[cfg(all( + any(target_arch = "x86_64", not(target_os = "none")), + feature = "platform-pc-x86" + ))] { + #[rustfmt::skip] + #[path = "config_pc_x86.rs"] + mod config; + } else if #[cfg(all( + any(target_arch = "riscv32", target_arch = "riscv64", not(target_os = "none")), + feature = "platform-qemu-virt-riscv" + ))] { #[rustfmt::skip] #[path = "config_qemu_virt_riscv.rs"] mod config; - } else if #[cfg(feature = "platform-qemu-virt-aarch64")] { + } else if #[cfg(all( + any(target_arch = "aarch64", not(target_os = "none")), + feature = "platform-qemu-virt-aarch64" + ))] { #[rustfmt::skip] #[path = "config_qemu_virt_aarch64.rs"] mod config; diff --git a/modules/axconfig/src/platform/pc-x86.toml b/modules/axconfig/src/platform/pc-x86.toml new file mode 100644 index 0000000000..9fb698f0ef --- /dev/null +++ b/modules/axconfig/src/platform/pc-x86.toml @@ -0,0 +1,24 @@ +# Architecture identifier. +arch = "x86_64" +# Platform identifier. +platform = "pc-x86" + +# Base address of the whole physical memory. +phys-memory-base = "0" +# Size of the whole physical memory. +phys-memory-size = "0x800_0000" # 128M +# Base physical address of the kernel image. +kernel-base-paddr = "0x20_0000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_ff80_0020_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_ff80_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0xfec0_0000", "0x1000"], # IO APIC + ["0xfed0_0000", "0x1000"], # HPET + ["0xfee0_0000", "0x1000"], # Local APIC +] +# VirtIO MMIO regions with format (`base_paddr`, `size`). +virtio-mmio-regions = [] diff --git a/modules/axhal/Cargo.toml b/modules/axhal/Cargo.toml index 7cb8f0128e..547628af71 100644 --- a/modules/axhal/Cargo.toml +++ b/modules/axhal/Cargo.toml @@ -14,6 +14,7 @@ smp = [] fp_simd = [] paging = ["axalloc", "page_table"] irq = [] +platform-pc-x86 = ["axconfig/platform-pc-x86"] platform-qemu-virt-riscv = ["axconfig/platform-qemu-virt-riscv"] platform-qemu-virt-aarch64 = [ "axconfig/platform-qemu-virt-aarch64", @@ -41,6 +42,7 @@ crate_interface = { path = "../../crates/crate_interface" } [target.'cfg(target_arch = "x86_64")'.dependencies] x86 = "0.52" +x86_64 = "0.14" raw-cpuid = "10.7" [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] diff --git a/modules/axhal/build.rs b/modules/axhal/build.rs index ec38bbf3e5..4992beb6c6 100644 --- a/modules/axhal/build.rs +++ b/modules/axhal/build.rs @@ -7,7 +7,9 @@ fn main() { fn gen_linker_script(arch: &str) -> Result<()> { let fname = format!("linker_{}.lds", arch); - let output_arch = if arch.contains("riscv") { + let output_arch = if arch == "x86_64" { + "i386:x86-64" + } else if arch.contains("riscv") { "riscv" // OUTPUT_ARCH of both riscv32/riscv64 is "riscv" } else { arch diff --git a/modules/axhal/src/arch/riscv/mod.rs b/modules/axhal/src/arch/riscv/mod.rs index 34efae92db..b312bbdfce 100644 --- a/modules/axhal/src/arch/riscv/mod.rs +++ b/modules/axhal/src/arch/riscv/mod.rs @@ -8,7 +8,7 @@ use memory_addr::{PhysAddr, VirtAddr}; use riscv::asm; use riscv::register::{satp, sstatus, stvec}; -pub use context::{GeneralRegisters, TaskContext, TrapFrame}; +pub use self::context::{GeneralRegisters, TaskContext, TrapFrame}; /// Allows the current CPU to respond to interrupts. #[inline] diff --git a/modules/axhal/src/arch/x86_64/context.rs b/modules/axhal/src/arch/x86_64/context.rs index 9eb18d4670..e5baa22020 100644 --- a/modules/axhal/src/arch/x86_64/context.rs +++ b/modules/axhal/src/arch/x86_64/context.rs @@ -173,7 +173,7 @@ impl TaskContext { /// It first saves the current task's context from CPU to this place, and then /// restores the next task's context from `next_ctx` to CPU. pub fn switch_to(&mut self, next_ctx: &Self) { - #[cfg(all(feature = "fp_simd", target_feature = "sse"))] + #[cfg(feature = "fp_simd")] { self.ext_state.save(); next_ctx.ext_state.restore(); diff --git a/modules/axhal/src/arch/x86_64/mod.rs b/modules/axhal/src/arch/x86_64/mod.rs index 7d3f2bcd72..a25a04351c 100644 --- a/modules/axhal/src/arch/x86_64/mod.rs +++ b/modules/axhal/src/arch/x86_64/mod.rs @@ -3,36 +3,29 @@ mod context; use core::arch::asm; use memory_addr::{PhysAddr, VirtAddr}; -use x86::{bits64::rflags, bits64::rflags::RFlags, controlregs, tlb}; +use x86::{controlregs, tlb}; +use x86_64::instructions::interrupts; -pub use context::{ExtendedState, FxsaveArea, TaskContext, TrapFrame}; +pub use self::context::{ExtendedState, FxsaveArea, TaskContext, TrapFrame}; /// Allows the current CPU to respond to interrupts. #[inline] pub fn enable_irqs() { #[cfg(target_os = "none")] - unsafe { - asm!("sti") - } + interrupts::enable() } /// Makes the current CPU to ignore interrupts. #[inline] pub fn disable_irqs() { #[cfg(target_os = "none")] - unsafe { - asm!("cli") - } + interrupts::disable() } /// Returns whether the current CPU is allowed to respond to interrupts. #[inline] pub fn irqs_enabled() -> bool { - if cfg!(target_os = "none") { - !rflags::read().contains(RFlags::FLAGS_IF) - } else { - false - } + interrupts::are_enabled() } /// Relaxes the current CPU and waits for interrupts. diff --git a/modules/axhal/src/lib.rs b/modules/axhal/src/lib.rs index 6f8a7edad7..f8201362fd 100644 --- a/modules/axhal/src/lib.rs +++ b/modules/axhal/src/lib.rs @@ -6,6 +6,7 @@ //! //! Currently supported platforms (specify by cargo features): //! +//! - `platform-pc-x86`: Standard PC with x86_64 ISA. //! - `platform-qemu-virt-riscv`: QEMU virt machine with RISC-V ISA. //! - `platform-qemu-virt-aarch64`: QEMU virt machine with AArch64 ISA. //! - `dummy`: If none of the above platform is selected, the dummy platform @@ -18,6 +19,7 @@ //! - `fp_simd`: Enable floating-point and SIMD support. //! - `paging`: Enable page table manipulation. //! - `irq`: Enable interrupt handling support. +//! - `platform-pc-x86`: Specify for use on the corresponding platform. //! - `platform-qemu-virt-riscv`: Specify for use on the corresponding platform. //! - `platform-qemu-virt-aarch64`: Specify for use on the corresponding platform. //! diff --git a/modules/axhal/src/platform/mod.rs b/modules/axhal/src/platform/mod.rs index f8fad2219f..20b2ecffae 100644 --- a/modules/axhal/src/platform/mod.rs +++ b/modules/axhal/src/platform/mod.rs @@ -2,6 +2,12 @@ cfg_if::cfg_if! { if #[cfg(all( + target_arch = "x86_64", + feature = "platform-pc-x86" + ))] { + mod pc_x86; + pub use self::pc_x86::*; + } else if #[cfg(all( any(target_arch = "riscv32", target_arch = "riscv64"), feature = "platform-qemu-virt-riscv" ))] { diff --git a/modules/axhal/src/platform/pc_x86/boot.rs b/modules/axhal/src/platform/pc_x86/boot.rs new file mode 100644 index 0000000000..487257cad7 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/boot.rs @@ -0,0 +1,50 @@ +use core::arch::global_asm; + +use x86_64::registers::control::{Cr0Flags, Cr4Flags}; +use x86_64::registers::model_specific::EferFlags; + +use axconfig::{PHYS_VIRT_OFFSET, TASK_STACK_SIZE}; + +/// Flags set in the ’flags’ member of the multiboot header. +/// +/// (bits 1, 16: memory information, address fields in header) +const MULTIBOOT_HEADER_FLAGS: usize = 0x0001_0002; + +/// The magic field should contain this. +const MULTIBOOT_HEADER_MAGIC: usize = 0x1BADB002; + +/// This should be in EAX. +pub(super) const MULTIBOOT_BOOTLOADER_MAGIC: usize = 0x2BADB002; + +const CR0: u64 = Cr0Flags::PROTECTED_MODE_ENABLE.bits() + | Cr0Flags::MONITOR_COPROCESSOR.bits() + | Cr0Flags::NUMERIC_ERROR.bits() + | Cr0Flags::WRITE_PROTECT.bits() + | Cr0Flags::PAGING.bits(); +const CR4: u64 = Cr4Flags::PHYSICAL_ADDRESS_EXTENSION.bits() + | Cr4Flags::PAGE_GLOBAL.bits() + | if cfg!(feature = "fp_simd") { + Cr4Flags::OSFXSR.bits() | Cr4Flags::OSXMMEXCPT_ENABLE.bits() + } else { + 0 + }; +const EFER: u64 = EferFlags::LONG_MODE_ENABLE.bits() | EferFlags::NO_EXECUTE_ENABLE.bits(); + +#[link_section = ".bss.stack"] +static mut BOOT_STACK: [u8; TASK_STACK_SIZE] = [0; TASK_STACK_SIZE]; + +global_asm!( + include_str!("multiboot.S"), + mb_hdr_magic = const MULTIBOOT_HEADER_MAGIC, + mb_hdr_flags = const MULTIBOOT_HEADER_FLAGS, + entry = sym super::rust_entry, + + offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const TASK_STACK_SIZE, + boot_stack = sym BOOT_STACK, + + cr0 = const CR0, + cr4 = const CR4, + efer_msr = const x86::msr::IA32_EFER, + efer = const EFER, +); diff --git a/modules/axhal/src/platform/pc_x86/irq.rs b/modules/axhal/src/platform/pc_x86/irq.rs new file mode 100644 index 0000000000..7ce3c58100 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/irq.rs @@ -0,0 +1,20 @@ +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 256; + +/// The timer IRQ number. +pub const TIMER_IRQ_NUM: usize = 0; + +/// Enables or disables the given IRQ. +pub fn set_enable(irq_num: usize, enabled: bool) {} + +/// Registers an IRQ handler for the given IRQ. +pub fn register_handler(irq_num: usize, handler: crate::irq::IrqHandler) -> bool { + false +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +pub fn dispatch_irq(irq_num: usize) {} diff --git a/modules/axhal/src/platform/pc_x86/mem.rs b/modules/axhal/src/platform/pc_x86/mem.rs new file mode 100644 index 0000000000..1e43fa1050 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/mem.rs @@ -0,0 +1,30 @@ +use crate::mem::*; + +/// Number of physical memory regions. +pub(crate) fn memory_regions_num() -> usize { + common_memory_regions_num() + 1 +} + +/// Returns the physical memory region at the given index, or [`None`] if the +/// index is out of bounds. +pub(crate) fn memory_region_at(idx: usize) -> Option { + use core::cmp::Ordering; + match idx.cmp(&common_memory_regions_num()) { + Ordering::Less => common_memory_region_at(idx), + Ordering::Equal => { + // free memory + extern "C" { + fn ekernel(); + } + let start = virt_to_phys((ekernel as usize).into()).align_up_4k(); + let end = PhysAddr::from(axconfig::PHYS_MEMORY_END).align_down_4k(); + Some(MemRegion { + paddr: start, + size: end.as_usize() - start.as_usize(), + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "free memory", + }) + } + Ordering::Greater => None, + } +} diff --git a/modules/axhal/src/platform/pc_x86/misc.rs b/modules/axhal/src/platform/pc_x86/misc.rs new file mode 100644 index 0000000000..7af6231a12 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/misc.rs @@ -0,0 +1,14 @@ +use x86_64::instructions::port::PortWriteOnly; + +/// Shutdown the whole system (in QEMU), including all CPUs. +/// +/// See for more information. +pub fn terminate() -> ! { + info!("Shutting down..."); + unsafe { PortWriteOnly::new(0x604).write(0x2000u16) }; + crate::arch::halt(); + warn!("It should shutdown!"); + loop { + crate::arch::halt(); + } +} diff --git a/modules/axhal/src/platform/pc_x86/mod.rs b/modules/axhal/src/platform/pc_x86/mod.rs new file mode 100644 index 0000000000..030941e74c --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/mod.rs @@ -0,0 +1,46 @@ +#![allow(dead_code)] +#![allow(unused_variables)] + +mod boot; +mod uart16550; + +pub mod mem; +pub mod misc; +pub mod time; + +#[cfg(feature = "irq")] +pub mod irq; + +#[cfg(feature = "smp")] +pub mod mp; + +pub mod console { + pub use super::uart16550::*; +} + +extern "C" { + fn rust_main(cpu_id: usize, dtb: usize) -> !; +} + +fn current_cpu_id() -> usize { + match raw_cpuid::CpuId::new().get_feature_info() { + Some(finfo) => finfo.initial_local_apic_id() as usize, + None => 0, + } +} + +unsafe extern "C" fn rust_entry(magic: usize, mbi: usize) { + if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + crate::mem::clear_bss(); + crate::cpu::init_primary(current_cpu_id()); + self::uart16550::init(); + rust_main(current_cpu_id(), 0); + } +} + +/// Initializes the platform devices for the primary CPU. +pub fn platform_init() {} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() {} diff --git a/modules/axhal/src/platform/pc_x86/mp.rs b/modules/axhal/src/platform/pc_x86/mp.rs new file mode 100644 index 0000000000..0866ef6295 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/mp.rs @@ -0,0 +1,4 @@ +use crate::mem::PhysAddr; + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(cpu_id: usize, stack_top: PhysAddr) {} diff --git a/modules/axhal/src/platform/pc_x86/multiboot.S b/modules/axhal/src/platform/pc_x86/multiboot.S new file mode 100644 index 0000000000..d90feb7b27 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/multiboot.S @@ -0,0 +1,113 @@ +# Bootstrapping from 32-bit with the Multiboot specification. +# See https://www.gnu.org/software/grub/manual/multiboot/multiboot.html + +.section .text.boot +.code32 +.global _start +_start: + mov edi, eax # arg1: magic: 0x2BADB002 + mov esi, ebx # arg2: multiboot info + jmp entry32 + +.balign 4 +.type multiboot_header, STT_OBJECT +multiboot_header: + .int {mb_hdr_magic} # magic: 0x1BADB002 + .int {mb_hdr_flags} # flags + .int -({mb_hdr_magic} + {mb_hdr_flags}) # checksum + .int multiboot_header - {offset} # header_addr + .int skernel - {offset} # load_addr + .int edata - {offset} # load_end + .int ebss - {offset} # bss_end_addr + .int _start - {offset} # entry_addr + +.code32 +entry32: + lgdt [.Ltmp_gdt_desc - {offset}] # load the temporary GDT + + # set data segment selectors + mov ax, 0x18 + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + # set PAE, PGE bit in CR4 + mov eax, {cr4} + mov cr4, eax + + # load the temporary page table + lea eax, [.Ltmp_pml4 - {offset}] + mov cr3, eax + + # set LME, NXE bit in IA32_EFER + mov ecx, {efer_msr} + mov edx, 0 + mov eax, {efer} + wrmsr + + # set protected mode, write protect, paging bit in CR0 + mov eax, {cr0} + mov cr0, eax + + ljmp 0x10, offset entry64 - {offset} # 0x10 is code64 segment + +.code64 +entry64: + # clear segment selectors + xor ax, ax + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + # set RSP to boot stack + movabs rsp, offset {boot_stack} + add rsp, {boot_stack_size} + + # call rust_entry(magic, mbi) + movabs rax, offset {entry} + call rax + +.Lhlt: + hlt + jmp .Lhlt + +.section .rodata +.balign 8 +.Ltmp_gdt_desc: + .short .Ltmp_gdt_end - .Ltmp_gdt - 1 # limit + .long .Ltmp_gdt - {offset} # base + +.section .data +.balign 16 +.Ltmp_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) + .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) +.Ltmp_gdt_end: + +.balign 4096 +.Ltmp_pml4: + # 0x0000_0000 ~ 0xffff_ffff + .quad .Ltmp_pdpt_low - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + .zero 8 * 510 + # 0xffff_ff80_0000_0000 ~ 0xffff_ff80_ffff_ffff + .quad .Ltmp_pdpt_high - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + +.Ltmp_pdpt_low: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 + +.Ltmp_pdpt_high: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 diff --git a/modules/axhal/src/platform/pc_x86/time.rs b/modules/axhal/src/platform/pc_x86/time.rs new file mode 100644 index 0000000000..31fac66d9b --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/time.rs @@ -0,0 +1,19 @@ +/// Returns the current clock time in hardware ticks. +pub fn current_ticks() -> u64 { + 0 +} + +/// Converts hardware ticks to nanoseconds. +pub fn ticks_to_nanos(ticks: u64) -> u64 { + ticks +} + +/// Converts nanoseconds to hardware ticks. +pub fn nanos_to_ticks(nanos: u64) -> u64 { + nanos +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the given deadline (in nanoseconds). +pub fn set_oneshot_timer(deadline_ns: u64) {} diff --git a/modules/axhal/src/platform/pc_x86/uart16550.rs b/modules/axhal/src/platform/pc_x86/uart16550.rs new file mode 100644 index 0000000000..7627c080bd --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/uart16550.rs @@ -0,0 +1,105 @@ +//! Uart 16550. + +use spinlock::SpinNoIrq; +use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; + +const UART_CLOCK_FACTOR: usize = 16; +const OSC_FREQ: usize = 1_843_200; + +static COM1: SpinNoIrq = SpinNoIrq::new(Uart16550::new(0x3f8)); + +bitflags::bitflags! { + /// Line status flags + struct LineStsFlags: u8 { + const INPUT_FULL = 1; + // 1 to 4 unknown + const OUTPUT_EMPTY = 1 << 5; + // 6 and 7 unknown + } +} + +struct Uart16550 { + data: Port, + int_en: PortWriteOnly, + fifo_ctrl: PortWriteOnly, + line_ctrl: PortWriteOnly, + modem_ctrl: PortWriteOnly, + line_sts: PortReadOnly, +} + +impl Uart16550 { + const fn new(port: u16) -> Self { + Self { + data: Port::new(port), + int_en: PortWriteOnly::new(port + 1), + fifo_ctrl: PortWriteOnly::new(port + 2), + line_ctrl: PortWriteOnly::new(port + 3), + modem_ctrl: PortWriteOnly::new(port + 4), + line_sts: PortReadOnly::new(port + 5), + } + } + + fn init(&mut self, baud_rate: usize) { + unsafe { + // Disable interrupts + self.int_en.write(0x00); + + // Enable DLAB + self.line_ctrl.write(0x80); + + // Set maximum speed according the input baud rate by configuring DLL and DLM + let divisor = OSC_FREQ / (baud_rate * UART_CLOCK_FACTOR); + self.data.write((divisor & 0xff) as u8); + self.int_en.write((divisor >> 8) as u8); + + // Disable DLAB and set data word length to 8 bits + self.line_ctrl.write(0x03); + + // Enable FIFO, clear TX/RX queues and + // set interrupt watermark at 14 bytes + self.fifo_ctrl.write(0xC7); + + // Mark data terminal ready, signal request to send + // and enable auxilliary output #2 (used as interrupt line for CPU) + self.modem_ctrl.write(0x0B); + } + } + + fn line_sts(&mut self) -> LineStsFlags { + unsafe { LineStsFlags::from_bits_truncate(self.line_sts.read()) } + } + + fn putchar(&mut self, c: u8) { + while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {} + unsafe { self.data.write(c) }; + } + + fn getchar(&mut self) -> Option { + if self.line_sts().contains(LineStsFlags::INPUT_FULL) { + unsafe { Some(self.data.read()) } + } else { + None + } + } +} + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = COM1.lock(); + match c { + b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + COM1.lock().getchar() +} + +pub(super) fn init() { + COM1.lock().init(115200); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 057cada013..9cda0c1fe8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,4 +2,4 @@ profile = "minimal" channel = "nightly" components = ["rust-src", "llvm-tools-preview", "rustfmt", "clippy"] -targets = ["riscv64gc-unknown-none-elf", "aarch64-unknown-none-softfloat"] +targets = ["x86_64-unknown-none", "riscv64gc-unknown-none-elf", "aarch64-unknown-none-softfloat"] diff --git a/scripts/make/cargo.mk b/scripts/make/cargo.mk index d4ff060da5..66bddacb26 100644 --- a/scripts/make/cargo.mk +++ b/scripts/make/cargo.mk @@ -48,7 +48,7 @@ ifeq ($(default_features),n) build_args += --no-default-features endif -rustc_flags := -Clink-arg=-T$(LD_SCRIPT) +rustc_flags := -Clink-args="-T$(LD_SCRIPT) -no-pie" define cargo_build cargo rustc $(build_args) $(1) -- $(rustc_flags) diff --git a/scripts/make/qemu.mk b/scripts/make/qemu.mk index 29eaf59a5e..d67e904862 100644 --- a/scripts/make/qemu.mk +++ b/scripts/make/qemu.mk @@ -2,6 +2,10 @@ QEMU := qemu-system-$(ARCH) +qemu_args-x86_64 := \ + -machine q35 \ + -kernel $(OUT_ELF) + qemu_args-riscv64 := \ -machine virt \ -bios default \ @@ -30,7 +34,25 @@ ifeq ($(GRAPHIC), n) qemu_args-y += -nographic endif +ifeq ($(QEMU_LOG), y) + qemu_args-y += -D qemu.log -d in_asm,int,mmu,pcall,cpu_reset,guest_errors +endif + +qemu_args-debug := $(qemu_args-y) -s -S + +# Do not use KVM for debugging +ifeq ($(shell uname), Darwin) + qemu_args-$(ACCEL) += -cpu host -accel hvf +else + qemu_args-$(ACCEL) += -cpu host -accel kvm +endif + define run_qemu @printf " $(CYAN_C)Running$(END_C) $(QEMU) $(qemu_args-y) $(1)\n" - @$(QEMU) $(qemu_args-y) $(1) + @$(QEMU) $(qemu_args-y) +endef + +define run_qemu_debug + @printf " $(CYAN_C)Running$(END_C) $(QEMU) $(qemu_args-debug) $(1)\n" + @$(QEMU) $(qemu_args-debug) endef diff --git a/ulib/c_libax/build.mk b/ulib/c_libax/build.mk index 53a2a166a7..2f69ec201e 100644 --- a/ulib/c_libax/build.mk +++ b/ulib/c_libax/build.mk @@ -13,9 +13,9 @@ out_feat := $(obj_dir)/.features.txt ulib_src := $(wildcard $(src_dir)/*.c) ulib_obj := $(patsubst $(src_dir)/%.c,$(obj_dir)/%.o,$(ulib_src)) -CFLAGS += -static -no-pie -fno-builtin -ffreestanding -nostdinc -Wall +CFLAGS += -nostdinc -static -no-pie -fno-builtin -ffreestanding -Wall CFLAGS += -I$(inc_dir) -I$(ulib_dir)/../libax -LDFLAGS += -nostdlib -T$(LD_SCRIPT) +LDFLAGS += -nostdlib -static -no-pie --gc-sections -T$(LD_SCRIPT) ifeq ($(MODE), release) CFLAGS += -O3 @@ -27,8 +27,12 @@ endif ifeq ($(ARCH), riscv64) CFLAGS += -march=rv64gc -mabi=lp64d -mcmodel=medany -else ifeq ($(ARCH), aarch64) - ifeq ($(fp_simd),) +endif + +ifeq ($(fp_simd),) + ifeq ($(ARCH), x86_64) + CFLAGS += -mno-sse + else ifeq ($(ARCH), aarch64) CFLAGS += -mgeneral-regs-only endif endif diff --git a/ulib/libax/Cargo.toml b/ulib/libax/Cargo.toml index 655842458a..dfcbe39df9 100644 --- a/ulib/libax/Cargo.toml +++ b/ulib/libax/Cargo.toml @@ -55,6 +55,7 @@ log-level-debug = ["axlog/log-level-debug"] log-level-trace = ["axlog/log-level-trace"] # Platform +platform-pc-x86 = ["axhal/platform-pc-x86", "axdriver?/bus-pci"] platform-qemu-virt-riscv = ["axhal/platform-qemu-virt-riscv", "axdriver?/bus-mmio"] platform-qemu-virt-aarch64 = ["axhal/platform-qemu-virt-aarch64", "axdriver?/bus-mmio"] diff --git a/ulib/libax/src/lib.rs b/ulib/libax/src/lib.rs index eeab4af0b3..c6edae7c4b 100644 --- a/ulib/libax/src/lib.rs +++ b/ulib/libax/src/lib.rs @@ -27,6 +27,7 @@ //! - `log-level-error`, `log-level-warn`, `log-level-info`, `log-level-debug`, //! `log-level-trace`: Keep logging only at the specified level or higher. //! - Platform +//! - `platform-pc-x86`: Specify for use on the corresponding platform. //! - `platform-qemu-virt-riscv`: Specify for use on the corresponding platform. //! - `platform-qemu-virt-aarch64`: Specify for use on the corresponding platform. //! - Other From b286d4fe6b364df503278f688dc67c925ff9ceef Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Tue, 25 Apr 2023 00:17:47 +0800 Subject: [PATCH 05/13] x86: add exception handling --- .github/workflows/build.yml | 4 +- apps/exception/expect_debug_aarch64.out | 2 +- apps/exception/expect_debug_riscv64.out | 2 +- apps/exception/expect_debug_x86_64.out | 20 +++++ modules/axhal/src/arch/x86_64/context.rs | 13 ++- modules/axhal/src/arch/x86_64/gdt.rs | 88 ++++++++++++++++++++ modules/axhal/src/arch/x86_64/idt.rs | 67 +++++++++++++++ modules/axhal/src/arch/x86_64/mod.rs | 8 ++ modules/axhal/src/arch/x86_64/trap.S | 84 +++++++++++++++++++ modules/axhal/src/arch/x86_64/trap.rs | 46 ++++++++++ modules/axhal/src/platform/pc_x86/dtables.rs | 36 ++++++++ modules/axhal/src/platform/pc_x86/mod.rs | 2 + 12 files changed, 365 insertions(+), 7 deletions(-) create mode 100644 apps/exception/expect_debug_x86_64.out create mode 100644 modules/axhal/src/arch/x86_64/gdt.rs create mode 100644 modules/axhal/src/arch/x86_64/idt.rs create mode 100644 modules/axhal/src/arch/x86_64/trap.S create mode 100644 modules/axhal/src/arch/x86_64/trap.rs create mode 100644 modules/axhal/src/platform/pc_x86/dtables.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bd473f46d..6a907a5e8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,8 @@ jobs: components: rust-src, clippy, rustfmt - name: Clippy for the default target run: cargo clippy --all-features + - name: Clippy for x86_64 + run: make clippy ARCH=x86_64 - name: Clippy for riscv64 run: make clippy ARCH=riscv64 - name: Clippy for aarch64 @@ -29,7 +31,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - arch: [riscv64, aarch64] + arch: [x86_64, riscv64, aarch64] steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 diff --git a/apps/exception/expect_debug_aarch64.out b/apps/exception/expect_debug_aarch64.out index c097855b08..2291ea0da9 100644 --- a/apps/exception/expect_debug_aarch64.out +++ b/apps/exception/expect_debug_aarch64.out @@ -16,6 +16,6 @@ Initialize kernel page table... Initialize platform devices... Primary CPU 0 init OK. Running exception tests... -BRK #0x0 @ +BRK #0x0 @ 0x[0-9a-f]\{16\} Exception tests run OK! Shutting down... diff --git a/apps/exception/expect_debug_riscv64.out b/apps/exception/expect_debug_riscv64.out index 272622c846..3e61b79bcc 100644 --- a/apps/exception/expect_debug_riscv64.out +++ b/apps/exception/expect_debug_riscv64.out @@ -16,6 +16,6 @@ Initialize kernel page table... Initialize platform devices... Primary CPU 0 init OK. Running exception tests... -Exception(Breakpoint) @ +Exception(Breakpoint) @ 0x[0-9a-f]\{16\} Exception tests run OK! Shutting down... diff --git a/apps/exception/expect_debug_x86_64.out b/apps/exception/expect_debug_x86_64.out new file mode 100644 index 0000000000..0560551fba --- /dev/null +++ b/apps/exception/expect_debug_x86_64.out @@ -0,0 +1,20 @@ +smp = 1 +build_mode = release +log_level = debug + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Primary CPU 0 init OK. +Running exception tests... +#BP @ 0x[0-9a-f]\{16\} +Exception tests run OK! +Shutting down... diff --git a/modules/axhal/src/arch/x86_64/context.rs b/modules/axhal/src/arch/x86_64/context.rs index e5baa22020..a3ee388b31 100644 --- a/modules/axhal/src/arch/x86_64/context.rs +++ b/modules/axhal/src/arch/x86_64/context.rs @@ -22,7 +22,7 @@ pub struct TrapFrame { pub r14: u64, pub r15: u64, - // Pushed by 'vector.S' + // Pushed by `trap.S` pub vector: u64, pub error_code: u64, @@ -30,10 +30,15 @@ pub struct TrapFrame { pub rip: u64, pub cs: u64, pub rflags: u64, + pub rsp: u64, + pub ss: u64, +} - // Pushed by CPU when trap from ring-3 - pub user_rsp: u64, - pub user_ss: u64, +impl TrapFrame { + /// Whether the trap is from userspace. + pub const fn is_user(&self) -> bool { + self.cs & 0b11 == 3 + } } #[repr(C)] diff --git a/modules/axhal/src/arch/x86_64/gdt.rs b/modules/axhal/src/arch/x86_64/gdt.rs new file mode 100644 index 0000000000..e0ab99ac6f --- /dev/null +++ b/modules/axhal/src/arch/x86_64/gdt.rs @@ -0,0 +1,88 @@ +use core::fmt; + +use x86_64::instructions::tables::{lgdt, load_tss}; +use x86_64::registers::segmentation::{Segment, SegmentSelector, CS}; +use x86_64::structures::gdt::{Descriptor, DescriptorFlags}; +use x86_64::structures::{tss::TaskStateSegment, DescriptorTablePointer}; +use x86_64::{addr::VirtAddr, PrivilegeLevel}; + +/// A wrapper of the Global Descriptor Table (GDT) with maximum 16 entries. +#[repr(align(16))] +pub struct GdtStruct { + table: [u64; 16], +} + +impl GdtStruct { + /// Kernel code segment for 32-bit mode. + pub const KCODE32_SELECTOR: SegmentSelector = SegmentSelector::new(1, PrivilegeLevel::Ring0); + /// Kernel code segment for 64-bit mode. + pub const KCODE64_SELECTOR: SegmentSelector = SegmentSelector::new(2, PrivilegeLevel::Ring0); + /// Kernel data segment. + pub const KDATA_SELECTOR: SegmentSelector = SegmentSelector::new(3, PrivilegeLevel::Ring0); + /// User code segment for 32-bit mode. + pub const UCODE32_SELECTOR: SegmentSelector = SegmentSelector::new(4, PrivilegeLevel::Ring3); + /// User data segment. + pub const UDATA_SELECTOR: SegmentSelector = SegmentSelector::new(5, PrivilegeLevel::Ring3); + /// User code segment for 64-bit mode. + pub const UCODE64_SELECTOR: SegmentSelector = SegmentSelector::new(6, PrivilegeLevel::Ring3); + /// TSS segment. + pub const TSS_SELECTOR: SegmentSelector = SegmentSelector::new(7, PrivilegeLevel::Ring0); + + /// Constructs a new GDT struct that filled with the default segment + /// descriptors, including the given TSS segment. + pub fn new(tss: &'static TaskStateSegment) -> Self { + let mut table = [0; 16]; + // first 3 entries are the same as in multiboot.S + table[1] = DescriptorFlags::KERNEL_CODE32.bits(); // 0x00cf9b000000ffff + table[2] = DescriptorFlags::KERNEL_CODE64.bits(); // 0x00af9b000000ffff + table[3] = DescriptorFlags::KERNEL_DATA.bits(); // 0x00cf93000000ffff + table[4] = DescriptorFlags::USER_CODE32.bits(); // 0x00cffb000000ffff + table[5] = DescriptorFlags::USER_DATA.bits(); // 0x00cff3000000ffff + table[6] = DescriptorFlags::USER_CODE64.bits(); // 0x00affb000000ffff + if let Descriptor::SystemSegment(low, high) = Descriptor::tss_segment(tss) { + table[7] = low; + table[8] = high; + } + Self { table } + } + + /// Returns the GDT pointer (base and limit) that can be used in `lgdt` + /// instruction. + pub fn pointer(&self) -> DescriptorTablePointer { + DescriptorTablePointer { + base: VirtAddr::new(self.table.as_ptr() as u64), + limit: (core::mem::size_of_val(&self.table) - 1) as u16, + } + } + + /// Loads the GDT into the CPU (executes the `lgdt` instruction), and + /// updates the code segment register (`CS`). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load(&'static self) { + lgdt(&self.pointer()); + CS::set_reg(Self::KCODE64_SELECTOR); + } + + /// Loads the TSS into the CPU (executes the `ltr` instruction). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load_tss(&'static self) { + load_tss(Self::TSS_SELECTOR); + } +} + +impl fmt::Debug for GdtStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("GdtStruct") + .field("pointer", &self.pointer()) + .field("table", &self.table) + .finish() + } +} diff --git a/modules/axhal/src/arch/x86_64/idt.rs b/modules/axhal/src/arch/x86_64/idt.rs new file mode 100644 index 0000000000..ffccff7d4c --- /dev/null +++ b/modules/axhal/src/arch/x86_64/idt.rs @@ -0,0 +1,67 @@ +use core::fmt; + +use x86_64::addr::VirtAddr; +use x86_64::structures::idt::{Entry, HandlerFunc, InterruptDescriptorTable}; +use x86_64::structures::DescriptorTablePointer; + +const NUM_INT: usize = 256; + +/// A wrapper of the Interrupt Descriptor Table (IDT). +#[repr(transparent)] +pub struct IdtStruct { + table: InterruptDescriptorTable, +} + +impl IdtStruct { + /// Constructs a new IDT struct that filled with entries from + /// `trap_handler_table`. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + extern "C" { + #[link_name = "trap_handler_table"] + static ENTRIES: [extern "C" fn(); NUM_INT]; + } + let mut idt = Self { + table: InterruptDescriptorTable::new(), + }; + + let entries = unsafe { + core::slice::from_raw_parts_mut( + &mut idt.table as *mut _ as *mut Entry, + NUM_INT, + ) + }; + for i in 0..NUM_INT { + entries[i].set_handler_fn(unsafe { core::mem::transmute(ENTRIES[i]) }); + } + idt + } + + /// Returns the IDT pointer (base and limit) that can be used in the `lidt` + /// instruction. + pub fn pointer(&self) -> DescriptorTablePointer { + DescriptorTablePointer { + base: VirtAddr::new(&self.table as *const _ as u64), + limit: (core::mem::size_of::() - 1) as u16, + } + } + + /// Loads the IDT into the CPU (executes the `lidt` instruction). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load(&'static self) { + self.table.load(); + } +} + +impl fmt::Debug for IdtStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("IdtStruct") + .field("pointer", &self.pointer()) + .field("table", &self.table) + .finish() + } +} diff --git a/modules/axhal/src/arch/x86_64/mod.rs b/modules/axhal/src/arch/x86_64/mod.rs index a25a04351c..5a51ac620b 100644 --- a/modules/axhal/src/arch/x86_64/mod.rs +++ b/modules/axhal/src/arch/x86_64/mod.rs @@ -1,4 +1,9 @@ mod context; +mod gdt; +mod idt; + +#[cfg(target_os = "none")] +mod trap; use core::arch::asm; @@ -7,6 +12,9 @@ use x86::{controlregs, tlb}; use x86_64::instructions::interrupts; pub use self::context::{ExtendedState, FxsaveArea, TaskContext, TrapFrame}; +pub use self::gdt::GdtStruct; +pub use self::idt::IdtStruct; +pub use x86_64::structures::tss::TaskStateSegment; /// Allows the current CPU to respond to interrupts. #[inline] diff --git a/modules/axhal/src/arch/x86_64/trap.S b/modules/axhal/src/arch/x86_64/trap.S new file mode 100644 index 0000000000..4bd0d941ec --- /dev/null +++ b/modules/axhal/src/arch/x86_64/trap.S @@ -0,0 +1,84 @@ +.equ NUM_INT, 256 + +.altmacro +.macro DEF_HANDLER, i +.Ltrap_handler_\i: +.if \i == 8 || (\i >= 10 && \i <= 14) || \i == 17 + # error code pushed by CPU + push \i # interrupt vector + jmp .Ltrap_common +.else + push 0 # fill in error code in TrapFrame + push \i # interrupt vector + jmp .Ltrap_common +.endif +.endm + +.macro DEF_TABLE_ENTRY, i + .quad .Ltrap_handler_\i +.endm + +.section .text +.code64 +_trap_handlers: +.set i, 0 +.rept NUM_INT + DEF_HANDLER %i + .set i, i + 1 +.endr + +.Ltrap_common: + test byte ptr [rsp + 3 * 8], 3 # swap GS if it comes from user space + jz 1f + swapgs +1: + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + push rbx + push rdx + push rcx + push rax + + mov rdi, rsp + call x86_trap_handler + + pop rax + pop rcx + pop rdx + pop rbx + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + + test byte ptr [rsp + 3 * 8], 3 # swap GS back if return to user space + jz 2f + swapgs +2: + add rsp, 16 # pop vector, error_code + iretq + +.section .rodata +.global trap_handler_table +trap_handler_table: +.set i, 0 +.rept NUM_INT + DEF_TABLE_ENTRY %i + .set i, i + 1 +.endr diff --git a/modules/axhal/src/arch/x86_64/trap.rs b/modules/axhal/src/arch/x86_64/trap.rs new file mode 100644 index 0000000000..121a6864f9 --- /dev/null +++ b/modules/axhal/src/arch/x86_64/trap.rs @@ -0,0 +1,46 @@ +use x86::{controlregs::cr2, irq::*}; + +use super::context::TrapFrame; + +core::arch::global_asm!(include_str!("trap.S")); + +const IRQ_VECTOR_START: u8 = 0x20; +const IRQ_VECTOR_END: u8 = 0xff; + +#[no_mangle] +fn x86_trap_handler(tf: &mut TrapFrame) { + match tf.vector as u8 { + PAGE_FAULT_VECTOR => { + if tf.is_user() { + warn!( + "User #PF @ {:#x}, fault_vaddr={:#x}, error_code={:#x}", + tf.rip, + unsafe { cr2() }, + tf.error_code, + ); + } else { + panic!( + "Kernel #PF @ {:#x}, fault_vaddr={:#x}, error_code={:#x}:\n{:#x?}", + tf.rip, + unsafe { cr2() }, + tf.error_code, + tf, + ); + } + } + BREAKPOINT_VECTOR => debug!("#BP @ {:#x} ", tf.rip), + GENERAL_PROTECTION_FAULT_VECTOR => { + panic!( + "#GP @ {:#x}, error_code={:#x}:\n{:#x?}", + tf.rip, tf.error_code, tf + ); + } + IRQ_VECTOR_START..=IRQ_VECTOR_END => crate::trap::handle_irq_extern(tf.vector as _), + _ => { + panic!( + "Unhandled exception {} (error_code = {:#x}) @ {:#x}:\n{:#x?}", + tf.vector, tf.error_code, tf.rip, tf + ); + } + } +} diff --git a/modules/axhal/src/platform/pc_x86/dtables.rs b/modules/axhal/src/platform/pc_x86/dtables.rs new file mode 100644 index 0000000000..b6cc529fdc --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/dtables.rs @@ -0,0 +1,36 @@ +//! Description tables (per-CPU GDT, per-CPU ISS, IDT) + +use crate::arch::{GdtStruct, IdtStruct, TaskStateSegment}; +use lazy_init::LazyInit; + +static IDT: LazyInit = LazyInit::new(); + +#[percpu::def_percpu] +static TSS: LazyInit = LazyInit::new(); + +#[percpu::def_percpu] +static GDT: LazyInit = LazyInit::new(); + +fn init_percpu() { + unsafe { + IDT.load(); + let tss = TSS.current_ref_mut_raw(); + let gdt = GDT.current_ref_mut_raw(); + tss.init_by(TaskStateSegment::new()); + gdt.init_by(GdtStruct::new(tss)); + gdt.load(); + gdt.load_tss(); + } +} + +/// Initializes IDT, GDT on the primary CPU. +pub(super) fn init_primary() { + IDT.init_by(IdtStruct::new()); + init_percpu(); +} + +/// Initializes IDT, GDT on secondary CPUs. +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + init_percpu(); +} diff --git a/modules/axhal/src/platform/pc_x86/mod.rs b/modules/axhal/src/platform/pc_x86/mod.rs index 030941e74c..5e6a1353ff 100644 --- a/modules/axhal/src/platform/pc_x86/mod.rs +++ b/modules/axhal/src/platform/pc_x86/mod.rs @@ -2,6 +2,7 @@ #![allow(unused_variables)] mod boot; +mod dtables; mod uart16550; pub mod mem; @@ -34,6 +35,7 @@ unsafe extern "C" fn rust_entry(magic: usize, mbi: usize) { crate::mem::clear_bss(); crate::cpu::init_primary(current_cpu_id()); self::uart16550::init(); + self::dtables::init_primary(); rust_main(current_cpu_id(), 0); } } From f0522645ea6d29c9e9b691641b6d66c90a726020 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Wed, 26 Apr 2023 00:11:29 +0800 Subject: [PATCH 06/13] x86: enable LAPIC timer interrupts --- Cargo.lock | 27 ++++ modules/axconfig/src/platform/dummy.toml | 2 +- modules/axconfig/src/platform/pc-x86.toml | 3 + .../src/platform/qemu-virt-riscv.toml | 2 +- modules/axhal/Cargo.toml | 4 +- modules/axhal/src/platform/pc_x86/apic.rs | 116 ++++++++++++++++++ modules/axhal/src/platform/pc_x86/dtables.rs | 1 + modules/axhal/src/platform/pc_x86/irq.rs | 20 --- modules/axhal/src/platform/pc_x86/mod.rs | 26 ++-- modules/axhal/src/platform/pc_x86/mp.rs | 2 +- modules/axhal/src/platform/pc_x86/multiboot.S | 1 + modules/axhal/src/platform/pc_x86/time.rs | 71 ++++++++++- 12 files changed, 238 insertions(+), 37 deletions(-) create mode 100644 modules/axhal/src/platform/pc_x86/apic.rs delete mode 100644 modules/axhal/src/platform/pc_x86/irq.rs diff --git a/Cargo.lock b/Cargo.lock index ede0b2aafc..34eb4eca48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -269,6 +269,7 @@ dependencies = [ "arm_gic", "axalloc", "axconfig", + "axlog", "bitflags 2.1.0", "cfg-if", "crate_interface", @@ -287,6 +288,7 @@ dependencies = [ "spinlock", "static_assertions", "tock-registers", + "x2apic", "x86", "x86_64", ] @@ -402,6 +404,12 @@ dependencies = [ "which", ] +[[package]] +name = "bit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b645c5c09a7d4035949cfce1a915785aaad6f17800c35fda8a8c311c491f284" + [[package]] name = "bit_field" version = "0.10.2" @@ -1115,6 +1123,12 @@ dependencies = [ "x86_64", ] +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1870,6 +1884,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "x2apic" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "547152b57dd1ae0ce7a4ef1c6470f6039aa7ed22e2179d5bc4f3eda1304e0db3" +dependencies = [ + "bit", + "bitflags 1.3.2", + "paste", + "raw-cpuid", + "x86_64", +] + [[package]] name = "x86" version = "0.52.0" diff --git a/modules/axconfig/src/platform/dummy.toml b/modules/axconfig/src/platform/dummy.toml index a6b0a0b8e2..88fc1ca97d 100644 --- a/modules/axconfig/src/platform/dummy.toml +++ b/modules/axconfig/src/platform/dummy.toml @@ -19,5 +19,5 @@ mmio-regions = [] # VirtIO MMIO regions with format (`base_paddr`, `size`). virtio-mmio-regions = [] -# Timer interrupt frequency. +# Timer interrupt frequency in Hz. timer_frequency = "0" diff --git a/modules/axconfig/src/platform/pc-x86.toml b/modules/axconfig/src/platform/pc-x86.toml index 9fb698f0ef..0c5c5f300f 100644 --- a/modules/axconfig/src/platform/pc-x86.toml +++ b/modules/axconfig/src/platform/pc-x86.toml @@ -22,3 +22,6 @@ mmio-regions = [ ] # VirtIO MMIO regions with format (`base_paddr`, `size`). virtio-mmio-regions = [] + +# Timer interrupt frequencyin Hz. +timer_frequency = "4_000_000_000" # 4.0GHz diff --git a/modules/axconfig/src/platform/qemu-virt-riscv.toml b/modules/axconfig/src/platform/qemu-virt-riscv.toml index bb464c7abc..eb30c12347 100644 --- a/modules/axconfig/src/platform/qemu-virt-riscv.toml +++ b/modules/axconfig/src/platform/qemu-virt-riscv.toml @@ -32,5 +32,5 @@ virtio-mmio-regions = [ ["0x1000_8000", "0x1000"], ] -# Timer interrupt frequency. +# Timer interrupt frequency in Hz. timer_frequency = "10_000_000" # 10MHz diff --git a/modules/axhal/Cargo.toml b/modules/axhal/Cargo.toml index 547628af71..2d101fa2b0 100644 --- a/modules/axhal/Cargo.toml +++ b/modules/axhal/Cargo.toml @@ -14,7 +14,7 @@ smp = [] fp_simd = [] paging = ["axalloc", "page_table"] irq = [] -platform-pc-x86 = ["axconfig/platform-pc-x86"] +platform-pc-x86 = ["axconfig/platform-pc-x86", "dep:ratio"] platform-qemu-virt-riscv = ["axconfig/platform-qemu-virt-riscv"] platform-qemu-virt-aarch64 = [ "axconfig/platform-qemu-virt-aarch64", @@ -27,6 +27,7 @@ log = "0.4" cfg-if = "1.0" bitflags = "2.0" static_assertions = "1.1.0" +axlog = { path = "../axlog" } axconfig = { path = "../axconfig" } axalloc = { path = "../axalloc", optional = true } kernel_guard = { path = "../../crates/kernel_guard" } @@ -43,6 +44,7 @@ crate_interface = { path = "../../crates/crate_interface" } [target.'cfg(target_arch = "x86_64")'.dependencies] x86 = "0.52" x86_64 = "0.14" +x2apic = "0.4" raw-cpuid = "10.7" [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] diff --git a/modules/axhal/src/platform/pc_x86/apic.rs b/modules/axhal/src/platform/pc_x86/apic.rs new file mode 100644 index 0000000000..2065e930b9 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/apic.rs @@ -0,0 +1,116 @@ +#![allow(dead_code)] + +use lazy_init::LazyInit; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; +use x2apic::ioapic::IoApic; +use x2apic::lapic::{xapic_base, LocalApic, LocalApicBuilder}; +use x86_64::instructions::port::Port; + +use self::vectors::*; +use crate::mem::phys_to_virt; + +pub(super) mod vectors { + pub const APIC_TIMER_VECTOR: u8 = 0xf0; + pub const APIC_SPURIOUS_VECTOR: u8 = 0xf1; + pub const APIC_ERROR_VECTOR: u8 = 0xf2; +} + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 256; + +/// The timer IRQ number. +pub const TIMER_IRQ_NUM: usize = APIC_TIMER_VECTOR as usize; + +const IO_APIC_BASE: PhysAddr = PhysAddr::from(0xFEC0_0000); + +static mut LOCAL_APIC: Option = None; +static mut IS_X2APIC: bool = false; +static IO_APIC: LazyInit> = LazyInit::new(); + +/// Enables or disables the given IRQ. +#[cfg(feature = "irq")] +pub fn set_enable(vector: usize, enabled: bool) { + // should not affect LAPIC interrupts + if vector < APIC_TIMER_VECTOR as _ { + unsafe { + if enabled { + IO_APIC.lock().enable_irq(vector as u8); + } else { + IO_APIC.lock().disable_irq(vector as u8); + } + } + } +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +#[cfg(feature = "irq")] +pub fn register_handler(vector: usize, handler: crate::irq::IrqHandler) -> bool { + crate::irq::register_handler_common(vector, handler) +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +#[cfg(feature = "irq")] +pub fn dispatch_irq(vector: usize) { + crate::irq::dispatch_irq_common(vector); + unsafe { local_apic().end_of_interrupt() }; +} + +pub(super) fn local_apic<'a>() -> &'a mut LocalApic { + // It's safe as LAPIC is per-cpu. + unsafe { LOCAL_APIC.as_mut().unwrap() } +} + +fn cpu_has_x2apic() -> bool { + match raw_cpuid::CpuId::new().get_feature_info() { + Some(finfo) => finfo.has_x2apic(), + None => false, + } +} + +pub(super) fn init_primary() { + info!("Initialize Local APIC..."); + + unsafe { + // Disable 8259A interrupt controllers + Port::::new(0x21).write(0xff); + Port::::new(0xA1).write(0xff); + } + + let mut builder = LocalApicBuilder::new(); + builder + .timer_vector(APIC_TIMER_VECTOR as _) + .error_vector(APIC_ERROR_VECTOR as _) + .spurious_vector(APIC_SPURIOUS_VECTOR as _); + + if cpu_has_x2apic() { + info!("Using x2APIC."); + unsafe { IS_X2APIC = true }; + } else { + info!("Using xAPIC."); + let base_vaddr = phys_to_virt(PhysAddr::from(unsafe { xapic_base() } as usize)); + builder.set_xapic_base(base_vaddr.as_usize() as u64); + } + + let mut lapic = builder.build().unwrap(); + unsafe { + lapic.enable(); + LOCAL_APIC = Some(lapic); + } + + info!("Initialize IO APIC..."); + let io_apic = unsafe { IoApic::new(phys_to_virt(IO_APIC_BASE).as_usize() as u64) }; + IO_APIC.init_by(SpinNoIrq::new(io_apic)); +} + +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + unsafe { local_apic().enable() }; +} diff --git a/modules/axhal/src/platform/pc_x86/dtables.rs b/modules/axhal/src/platform/pc_x86/dtables.rs index b6cc529fdc..f40edb2e3d 100644 --- a/modules/axhal/src/platform/pc_x86/dtables.rs +++ b/modules/axhal/src/platform/pc_x86/dtables.rs @@ -25,6 +25,7 @@ fn init_percpu() { /// Initializes IDT, GDT on the primary CPU. pub(super) fn init_primary() { + axlog::ax_println!("\nInitialize IDT & GDT..."); IDT.init_by(IdtStruct::new()); init_percpu(); } diff --git a/modules/axhal/src/platform/pc_x86/irq.rs b/modules/axhal/src/platform/pc_x86/irq.rs deleted file mode 100644 index 7ce3c58100..0000000000 --- a/modules/axhal/src/platform/pc_x86/irq.rs +++ /dev/null @@ -1,20 +0,0 @@ -/// The maximum number of IRQs. -pub const MAX_IRQ_COUNT: usize = 256; - -/// The timer IRQ number. -pub const TIMER_IRQ_NUM: usize = 0; - -/// Enables or disables the given IRQ. -pub fn set_enable(irq_num: usize, enabled: bool) {} - -/// Registers an IRQ handler for the given IRQ. -pub fn register_handler(irq_num: usize, handler: crate::irq::IrqHandler) -> bool { - false -} - -/// Dispatches the IRQ. -/// -/// This function is called by the common interrupt handler. It looks -/// up in the IRQ handler table and calls the corresponding handler. If -/// necessary, it also acknowledges the interrupt controller after handling. -pub fn dispatch_irq(irq_num: usize) {} diff --git a/modules/axhal/src/platform/pc_x86/mod.rs b/modules/axhal/src/platform/pc_x86/mod.rs index 5e6a1353ff..36677b211e 100644 --- a/modules/axhal/src/platform/pc_x86/mod.rs +++ b/modules/axhal/src/platform/pc_x86/mod.rs @@ -1,6 +1,4 @@ -#![allow(dead_code)] -#![allow(unused_variables)] - +mod apic; mod boot; mod dtables; mod uart16550; @@ -9,12 +7,14 @@ pub mod mem; pub mod misc; pub mod time; -#[cfg(feature = "irq")] -pub mod irq; - #[cfg(feature = "smp")] pub mod mp; +#[cfg(feature = "irq")] +pub mod irq { + pub use super::apic::*; +} + pub mod console { pub use super::uart16550::*; } @@ -30,19 +30,27 @@ fn current_cpu_id() -> usize { } } -unsafe extern "C" fn rust_entry(magic: usize, mbi: usize) { +unsafe extern "C" fn rust_entry(magic: usize, _mbi: usize) { + // TODO: handle multiboot info if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { crate::mem::clear_bss(); crate::cpu::init_primary(current_cpu_id()); self::uart16550::init(); self::dtables::init_primary(); + self::time::init_early(); rust_main(current_cpu_id(), 0); } } /// Initializes the platform devices for the primary CPU. -pub fn platform_init() {} +pub fn platform_init() { + self::apic::init_primary(); + self::time::init_primary(); +} /// Initializes the platform devices for secondary CPUs. #[cfg(feature = "smp")] -pub fn platform_init_secondary() {} +pub fn platform_init_secondary() { + self::apic::init_secondary(); + self::time::init_secondary(); +} diff --git a/modules/axhal/src/platform/pc_x86/mp.rs b/modules/axhal/src/platform/pc_x86/mp.rs index 0866ef6295..69440f36de 100644 --- a/modules/axhal/src/platform/pc_x86/mp.rs +++ b/modules/axhal/src/platform/pc_x86/mp.rs @@ -1,4 +1,4 @@ use crate::mem::PhysAddr; /// Starts the given secondary CPU with its boot stack. -pub fn start_secondary_cpu(cpu_id: usize, stack_top: PhysAddr) {} +pub fn start_secondary_cpu(_cpu_id: usize, _stack_top: PhysAddr) {} diff --git a/modules/axhal/src/platform/pc_x86/multiboot.S b/modules/axhal/src/platform/pc_x86/multiboot.S index d90feb7b27..2c93ddba31 100644 --- a/modules/axhal/src/platform/pc_x86/multiboot.S +++ b/modules/axhal/src/platform/pc_x86/multiboot.S @@ -98,6 +98,7 @@ entry64: # 0xffff_ff80_0000_0000 ~ 0xffff_ff80_ffff_ffff .quad .Ltmp_pdpt_high - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) +# FIXME: may not work on macOS using hvf as the CPU does not support 1GB page (pdpe1gb) .Ltmp_pdpt_low: .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) diff --git a/modules/axhal/src/platform/pc_x86/time.rs b/modules/axhal/src/platform/pc_x86/time.rs index 31fac66d9b..596db94bef 100644 --- a/modules/axhal/src/platform/pc_x86/time.rs +++ b/modules/axhal/src/platform/pc_x86/time.rs @@ -1,19 +1,82 @@ +use raw_cpuid::CpuId; + +#[cfg(feature = "irq")] +const LAPIC_TICKS_PER_SEC: u64 = 1_000_000_000; // TODO: need to calibrate + +#[cfg(feature = "irq")] +static mut NANOS_TO_LAPIC_TICKS_RATIO: ratio::Ratio = ratio::Ratio::zero(); + +static mut INIT_TICK: u64 = 0; +static mut CPU_FREQ_MHZ: u64 = axconfig::TIMER_FREQUENCY as u64 / 1_000_000; + /// Returns the current clock time in hardware ticks. pub fn current_ticks() -> u64 { - 0 + unsafe { core::arch::x86_64::_rdtsc() - INIT_TICK } } /// Converts hardware ticks to nanoseconds. pub fn ticks_to_nanos(ticks: u64) -> u64 { - ticks + ticks * 1_000 / unsafe { CPU_FREQ_MHZ } } /// Converts nanoseconds to hardware ticks. pub fn nanos_to_ticks(nanos: u64) -> u64 { - nanos + nanos * unsafe { CPU_FREQ_MHZ } / 1_000 } /// Set a one-shot timer. /// /// A timer interrupt will be triggered at the given deadline (in nanoseconds). -pub fn set_oneshot_timer(deadline_ns: u64) {} +#[cfg(feature = "irq")] +pub fn set_oneshot_timer(deadline_ns: u64) { + let lapic = super::apic::local_apic(); + let now_ns = crate::time::current_time_nanos(); + unsafe { + if now_ns < deadline_ns { + let apic_ticks = NANOS_TO_LAPIC_TICKS_RATIO.mul_trunc(deadline_ns - now_ns); + assert!(apic_ticks <= u32::MAX as u64); + lapic.set_timer_initial(apic_ticks.max(1) as u32); + } else { + lapic.set_timer_initial(1); + } + } +} + +pub(super) fn init_early() { + if let Some(freq) = CpuId::new() + .get_processor_frequency_info() + .map(|info| info.processor_base_frequency()) + { + if freq > 0 { + axlog::ax_println!("Got TSC frequency by CPUID: {} MHz", freq); + unsafe { CPU_FREQ_MHZ = freq as u64 } + } + } + + unsafe { INIT_TICK = core::arch::x86_64::_rdtsc() }; +} + +pub(super) fn init_primary() { + #[cfg(feature = "irq")] + unsafe { + use x2apic::lapic::{TimerDivide, TimerMode}; + let lapic = super::apic::local_apic(); + lapic.set_timer_mode(TimerMode::OneShot); + lapic.set_timer_divide(TimerDivide::Div256); // indeed it is Div1, the name is confusing. + lapic.enable_timer(); + + // TODO: calibrate with HPET + NANOS_TO_LAPIC_TICKS_RATIO = ratio::Ratio::new( + LAPIC_TICKS_PER_SEC as u32, + crate::time::NANOS_PER_SEC as u32, + ); + } +} + +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + #[cfg(feature = "irq")] + unsafe { + super::apic::local_apic().enable_timer(); + } +} From 6cf28d868e280ce320299e130af85935c36320e8 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Wed, 26 Apr 2023 23:16:49 +0800 Subject: [PATCH 07/13] x86: add support of multicore --- .github/workflows/test.yml | 2 +- apps/c/sqlite3/test_cmd | 8 ++- apps/net/httpclient/test_cmd | 6 +- modules/axhal/src/platform/pc_x86/ap_start.S | 69 +++++++++++++++++++ modules/axhal/src/platform/pc_x86/apic.rs | 8 +++ modules/axhal/src/platform/pc_x86/boot.rs | 2 + modules/axhal/src/platform/pc_x86/mem.rs | 46 ++++++++----- modules/axhal/src/platform/pc_x86/mod.rs | 12 ++++ modules/axhal/src/platform/pc_x86/mp.rs | 45 +++++++++++- modules/axhal/src/platform/pc_x86/multiboot.S | 47 ++++++++++--- modules/axruntime/src/mp.rs | 10 ++- scripts/test/app_test.sh | 6 +- 12 files changed, 222 insertions(+), 39 deletions(-) create mode 100644 modules/axhal/src/platform/pc_x86/ap_start.S diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38b6b7af80..bb105db559 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - arch: [riscv64, aarch64] + arch: [x86_64, riscv64, aarch64] steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 diff --git a/apps/c/sqlite3/test_cmd b/apps/c/sqlite3/test_cmd index bc7066bd79..47b0521a74 100644 --- a/apps/c/sqlite3/test_cmd +++ b/apps/c/sqlite3/test_cmd @@ -1,3 +1,5 @@ -test_one "LOG=info FS=y" "expect_info.out" -test_one "LOG=info FS=y" "expect_info_again.out" -rm -f $APP/*.o +if [ "$ARCH" != "x86_64" ]; then + test_one "LOG=info FS=y" "expect_info.out" + test_one "LOG=info FS=y" "expect_info_again.out" + rm -f $APP/*.o +fi diff --git a/apps/net/httpclient/test_cmd b/apps/net/httpclient/test_cmd index f56d6d43d4..b66783b985 100644 --- a/apps/net/httpclient/test_cmd +++ b/apps/net/httpclient/test_cmd @@ -1,2 +1,4 @@ -test_one "LOG=info NET=y" "expect_info.out" -test_one "LOG=info NET=y APP_FEATURES=dns" "expect_info_dns.out" +if [ "$ARCH" != "x86_64" ]; then + test_one "LOG=info NET=y" "expect_info.out" + test_one "LOG=info NET=y APP_FEATURES=dns" "expect_info_dns.out" +fi diff --git a/modules/axhal/src/platform/pc_x86/ap_start.S b/modules/axhal/src/platform/pc_x86/ap_start.S new file mode 100644 index 0000000000..9784f76724 --- /dev/null +++ b/modules/axhal/src/platform/pc_x86/ap_start.S @@ -0,0 +1,69 @@ +# Boot application processors into the protected mode. + +# Each non-boot CPU ("AP") is started up in response to a STARTUP +# IPI from the boot CPU. Section B.4.2 of the Multi-Processor +# Specification says that the AP will start in real mode with CS:IP +# set to XY00:0000, where XY is an 8-bit value sent with the +# STARTUP. Thus this code must start at a 4096-byte boundary. +# +# Because this code sets DS to zero, it must sit +# at an address in the low 2^16 bytes. + +.equ pa_ap_start32, ap_start32 - ap_start + {start_page_paddr} +.equ pa_ap_gdt, .Lap_tmp_gdt - ap_start + {start_page_paddr} +.equ pa_ap_gdt_desc, .Lap_tmp_gdt_desc - ap_start + {start_page_paddr} + +.equ stack_ptr, {start_page_paddr} + 0xff0 +.equ entry_ptr, {start_page_paddr} + 0xff8 + +# 0x6000 +.section .text +.code16 +.p2align 12 +.global ap_start +ap_start: + cli + wbinvd + + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # load the 64-bit GDT + lgdt [pa_ap_gdt_desc] + + # switch to protected-mode + mov eax, cr0 + or eax, (1 << 0) + mov cr0, eax + + # far jump to 32-bit code. 0x8 is code32 segment selector + ljmp 0x8, offset pa_ap_start32 + +.code32 +ap_start32: + mov esp, [stack_ptr] + mov eax, [entry_ptr] + jmp eax + +.balign 8 +# .type multiboot_header, STT_OBJECT +.Lap_tmp_gdt_desc: + .short .Lap_tmp_gdt_end - .Lap_tmp_gdt - 1 # limit + .long pa_ap_gdt # base + +.balign 16 +.Lap_tmp_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) + .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) +.Lap_tmp_gdt_end: + +# 0x7000 +.p2align 12 +.global ap_end +ap_end: diff --git a/modules/axhal/src/platform/pc_x86/apic.rs b/modules/axhal/src/platform/pc_x86/apic.rs index 2065e930b9..eb4e3e5930 100644 --- a/modules/axhal/src/platform/pc_x86/apic.rs +++ b/modules/axhal/src/platform/pc_x86/apic.rs @@ -68,6 +68,14 @@ pub(super) fn local_apic<'a>() -> &'a mut LocalApic { unsafe { LOCAL_APIC.as_mut().unwrap() } } +pub(super) fn raw_apic_id(id_u8: u8) -> u32 { + if unsafe { IS_X2APIC } { + id_u8 as u32 + } else { + (id_u8 as u32) << 24 + } +} + fn cpu_has_x2apic() -> bool { match raw_cpuid::CpuId::new().get_feature_info() { Some(finfo) => finfo.has_x2apic(), diff --git a/modules/axhal/src/platform/pc_x86/boot.rs b/modules/axhal/src/platform/pc_x86/boot.rs index 487257cad7..f21f66982c 100644 --- a/modules/axhal/src/platform/pc_x86/boot.rs +++ b/modules/axhal/src/platform/pc_x86/boot.rs @@ -35,9 +35,11 @@ static mut BOOT_STACK: [u8; TASK_STACK_SIZE] = [0; TASK_STACK_SIZE]; global_asm!( include_str!("multiboot.S"), + mb_magic = const MULTIBOOT_BOOTLOADER_MAGIC, mb_hdr_magic = const MULTIBOOT_HEADER_MAGIC, mb_hdr_flags = const MULTIBOOT_HEADER_FLAGS, entry = sym super::rust_entry, + entry_secondary = sym super::rust_entry_secondary, offset = const PHYS_VIRT_OFFSET, boot_stack_size = const TASK_STACK_SIZE, diff --git a/modules/axhal/src/platform/pc_x86/mem.rs b/modules/axhal/src/platform/pc_x86/mem.rs index 1e43fa1050..efed58cf70 100644 --- a/modules/axhal/src/platform/pc_x86/mem.rs +++ b/modules/axhal/src/platform/pc_x86/mem.rs @@ -1,30 +1,40 @@ +// TODO: get memory regions from multiboot info. + use crate::mem::*; /// Number of physical memory regions. pub(crate) fn memory_regions_num() -> usize { - common_memory_regions_num() + 1 + common_memory_regions_num() + 2 } /// Returns the physical memory region at the given index, or [`None`] if the /// index is out of bounds. pub(crate) fn memory_region_at(idx: usize) -> Option { - use core::cmp::Ordering; - match idx.cmp(&common_memory_regions_num()) { - Ordering::Less => common_memory_region_at(idx), - Ordering::Equal => { - // free memory - extern "C" { - fn ekernel(); - } - let start = virt_to_phys((ekernel as usize).into()).align_up_4k(); - let end = PhysAddr::from(axconfig::PHYS_MEMORY_END).align_down_4k(); - Some(MemRegion { - paddr: start, - size: end.as_usize() - start.as_usize(), - flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, - name: "free memory", - }) + let num = common_memory_regions_num(); + if idx == 0 { + // low physical memory + Some(MemRegion { + paddr: PhysAddr::from(0), + size: 0x9f000, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "low memory", + }) + } else if idx <= num { + common_memory_region_at(idx - 1) + } else if idx == num + 1 { + // free memory + extern "C" { + fn ekernel(); } - Ordering::Greater => None, + let start = virt_to_phys((ekernel as usize).into()).align_up_4k(); + let end = PhysAddr::from(axconfig::PHYS_MEMORY_END).align_down_4k(); + Some(MemRegion { + paddr: start, + size: end.as_usize() - start.as_usize(), + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "free memory", + }) + } else { + None } } diff --git a/modules/axhal/src/platform/pc_x86/mod.rs b/modules/axhal/src/platform/pc_x86/mod.rs index 36677b211e..ba3ce43630 100644 --- a/modules/axhal/src/platform/pc_x86/mod.rs +++ b/modules/axhal/src/platform/pc_x86/mod.rs @@ -21,6 +21,8 @@ pub mod console { extern "C" { fn rust_main(cpu_id: usize, dtb: usize) -> !; + #[cfg(feature = "smp")] + fn rust_main_secondary(cpu_id: usize) -> !; } fn current_cpu_id() -> usize { @@ -42,6 +44,16 @@ unsafe extern "C" fn rust_entry(magic: usize, _mbi: usize) { } } +#[allow(unused_variables)] +unsafe extern "C" fn rust_entry_secondary(magic: usize) { + #[cfg(feature = "smp")] + if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + crate::cpu::init_secondary(current_cpu_id()); + self::dtables::init_secondary(); + rust_main_secondary(current_cpu_id()); + } +} + /// Initializes the platform devices for the primary CPU. pub fn platform_init() { self::apic::init_primary(); diff --git a/modules/axhal/src/platform/pc_x86/mp.rs b/modules/axhal/src/platform/pc_x86/mp.rs index 69440f36de..dd3cdd1f5b 100644 --- a/modules/axhal/src/platform/pc_x86/mp.rs +++ b/modules/axhal/src/platform/pc_x86/mp.rs @@ -1,4 +1,45 @@ -use crate::mem::PhysAddr; +use crate::mem::{phys_to_virt, PhysAddr, PAGE_SIZE_4K}; +use crate::time::{busy_wait, Duration}; + +const START_PAGE_IDX: u8 = 6; +const START_PAGE_PADDR: PhysAddr = PhysAddr::from(START_PAGE_IDX as usize * PAGE_SIZE_4K); + +core::arch::global_asm!( + include_str!("ap_start.S"), + start_page_paddr = const START_PAGE_PADDR.as_usize(), +); + +unsafe fn setup_startup_page(stack_top: PhysAddr) { + extern "C" { + fn ap_entry32(); + fn ap_start(); + fn ap_end(); + } + const U64_PER_PAGE: usize = PAGE_SIZE_4K / 8; + + let start_page_ptr = phys_to_virt(START_PAGE_PADDR).as_mut_ptr() as *mut u64; + let start_page = core::slice::from_raw_parts_mut(start_page_ptr, U64_PER_PAGE); + core::ptr::copy_nonoverlapping( + ap_start as *const u64, + start_page_ptr, + (ap_end as usize - ap_start as usize) / 8, + ); + start_page[U64_PER_PAGE - 2] = stack_top.as_usize() as u64; // stack_top + start_page[U64_PER_PAGE - 1] = ap_entry32 as usize as _; // entry +} /// Starts the given secondary CPU with its boot stack. -pub fn start_secondary_cpu(_cpu_id: usize, _stack_top: PhysAddr) {} +pub fn start_secondary_cpu(apic_id: usize, stack_top: PhysAddr) { + unsafe { setup_startup_page(stack_top) }; + + let apic_id = super::apic::raw_apic_id(apic_id as u8); + let lapic = super::apic::local_apic(); + + // INIT-SIPI-SIPI Sequence + // Ref: Intel SDM Vol 3C, Section 8.4.4, MP Initialization Example + unsafe { lapic.send_init_ipi(apic_id) }; + busy_wait(Duration::from_millis(10)); // 10ms + unsafe { lapic.send_sipi(START_PAGE_IDX, apic_id) }; + busy_wait(Duration::from_micros(200)); // 200us + unsafe { lapic.send_sipi(START_PAGE_IDX, apic_id) }; +} diff --git a/modules/axhal/src/platform/pc_x86/multiboot.S b/modules/axhal/src/platform/pc_x86/multiboot.S index 2c93ddba31..1e938ffe1a 100644 --- a/modules/axhal/src/platform/pc_x86/multiboot.S +++ b/modules/axhal/src/platform/pc_x86/multiboot.S @@ -7,7 +7,7 @@ _start: mov edi, eax # arg1: magic: 0x2BADB002 mov esi, ebx # arg2: multiboot info - jmp entry32 + jmp bsp_entry32 .balign 4 .type multiboot_header, STT_OBJECT @@ -21,10 +21,8 @@ multiboot_header: .int ebss - {offset} # bss_end_addr .int _start - {offset} # entry_addr -.code32 -entry32: - lgdt [.Ltmp_gdt_desc - {offset}] # load the temporary GDT - +# Common code in 32-bit, prepare states to enter 64-bit. +.macro ENTRY32_COMMON # set data segment selectors mov ax, 0x18 mov ss, ax @@ -50,11 +48,10 @@ entry32: # set protected mode, write protect, paging bit in CR0 mov eax, {cr0} mov cr0, eax +.endm - ljmp 0x10, offset entry64 - {offset} # 0x10 is code64 segment - -.code64 -entry64: +# Common code in 64-bit +.macro ENTRY64_COMMON # clear segment selectors xor ax, ax mov ss, ax @@ -62,6 +59,23 @@ entry64: mov es, ax mov fs, ax mov gs, ax +.endm + +.code32 +bsp_entry32: + lgdt [.Ltmp_gdt_desc - {offset}] # load the temporary GDT + ENTRY32_COMMON + ljmp 0x10, offset bsp_entry64 - {offset} # 0x10 is code64 segment + +.code32 +.global ap_entry32 +ap_entry32: + ENTRY32_COMMON + ljmp 0x10, offset ap_entry64 - {offset} # 0x10 is code64 segment + +.code64 +bsp_entry64: + ENTRY64_COMMON # set RSP to boot stack movabs rsp, offset {boot_stack} @@ -70,6 +84,21 @@ entry64: # call rust_entry(magic, mbi) movabs rax, offset {entry} call rax + jmp .Lhlt + +.code64 +ap_entry64: + ENTRY64_COMMON + + # set RSP to high address (already set in ap_start.S) + mov rax, {offset} + add rsp, rax + + # call rust_entry_secondary(magic) + mov rdi, {mb_magic} + movabs rax, offset {entry_secondary} + call rax + jmp .Lhlt .Lhlt: hlt diff --git a/modules/axruntime/src/mp.rs b/modules/axruntime/src/mp.rs index 7297d28678..4cde9b617d 100644 --- a/modules/axruntime/src/mp.rs +++ b/modules/axruntime/src/mp.rs @@ -1,9 +1,12 @@ use axconfig::{SMP, TASK_STACK_SIZE}; use axhal::mem::{virt_to_phys, VirtAddr}; +use core::sync::atomic::{AtomicUsize, Ordering}; #[link_section = ".bss.stack"] static mut SECONDARY_BOOT_STACK: [[u8; TASK_STACK_SIZE]; SMP - 1] = [[0; TASK_STACK_SIZE]; SMP - 1]; +static ENTERED_CPUS: AtomicUsize = AtomicUsize::new(1); + pub fn start_secondary_cpus(primary_cpu_id: usize) { let mut logic_cpu_id = 0; for i in 0..SMP { @@ -15,6 +18,10 @@ pub fn start_secondary_cpus(primary_cpu_id: usize) { debug!("starting CPU {}...", i); axhal::mp::start_secondary_cpu(i, stack_top); logic_cpu_id += 1; + + while ENTERED_CPUS.load(Ordering::Acquire) <= logic_cpu_id { + core::hint::spin_loop(); + } } } } @@ -24,6 +31,7 @@ pub fn start_secondary_cpus(primary_cpu_id: usize) { /// It is called from the bootstrapping code in [axhal]. #[no_mangle] pub extern "C" fn rust_main_secondary(cpu_id: usize) -> ! { + ENTERED_CPUS.fetch_add(1, Ordering::Relaxed); info!("Secondary CPU {} started.", cpu_id); #[cfg(feature = "paging")] @@ -35,7 +43,7 @@ pub extern "C" fn rust_main_secondary(cpu_id: usize) -> ! { axtask::init_scheduler_secondary(); info!("Secondary CPU {} init OK.", cpu_id); - super::INITED_CPUS.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + super::INITED_CPUS.fetch_add(1, Ordering::Relaxed); while !super::is_init_ok() { core::hint::spin_loop(); diff --git a/scripts/test/app_test.sh b/scripts/test/app_test.sh index f97075a518..07a7fd0201 100755 --- a/scripts/test/app_test.sh +++ b/scripts/test/app_test.sh @@ -18,9 +18,9 @@ BLOD_C="\x1b[1m" END_C="\x1b[0m" if [ -z "$ARCH" ]; then - ARCH=riscv64 + ARCH=x86_64 fi -if [ "$ARCH" != "riscv64" ] && [ "$ARCH" != "aarch64" ]; then +if [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "riscv64" ] && [ "$ARCH" != "aarch64" ]; then echo "Unknown architecture: $ARCH" exit $S_FAILED fi @@ -77,7 +77,7 @@ function test_one() { local args=$1 local expect="$APP/$2" local actual="$APP/actual.out" - args="$args ARCH=$ARCH" + args="$args ARCH=$ARCH ACCEL=n" rm -f "$actual" MSG= From 9eb0a00dfe1cb34c0aed4a7a8712176b4fe216cc Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Mon, 1 May 2023 22:12:47 +0800 Subject: [PATCH 08/13] Update README --- .github/workflows/docs.yml | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 22f19f4873..765346349a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,4 +1,4 @@ -name: Deploy docs +name: Build & Deploy docs on: [push, pull_request] diff --git a/README.md b/README.md index 74cea71be9..f9644b88cd 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ ArceOS was inspired a lot by [Unikraft](https://github.com/unikraft/unikraft). ## Features & TODOs -* [x] Architecture: riscv64, aarch64 -* [x] Platform: QEMU virt riscv64/aarch64 +* [x] Architecture: x86_64, riscv64, aarch64 +* [x] Platform: QEMU pc-q35 (x86_64), virt (riscv64/aarch64) * [x] Multi-thread -* [x] Cooperative/preemptive scheduler +* [x] FIFO/RR/CFS scheduler * [x] VirtIO net/blk/gpu drivers * [x] TCP/UDP net stack using [smoltcp](https://github.com/smoltcp-rs/smoltcp) * [x] Synchronization/Mutex From 737c349810ab3d31156b6a56026ad160776f1e52 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Wed, 3 May 2023 15:12:22 +0800 Subject: [PATCH 09/13] Publish crate_interface to crates.io --- Cargo.lock | 2 +- README.md | 6 +++++ crates/crate_interface/Cargo.toml | 6 +++-- crates/crate_interface/README.md | 38 +++++++++++++++++++++++++++++++ crates/crate_interface/src/lib.rs | 36 +---------------------------- 5 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 crates/crate_interface/README.md diff --git a/Cargo.lock b/Cargo.lock index 34eb4eca48..f2f9c2604c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,7 +570,7 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "crate_interface" -version = "0.1.0" +version = "0.1.1" dependencies = [ "proc-macro2", "quote", diff --git a/README.md b/README.md index f9644b88cd..0eccb2c4e2 100644 --- a/README.md +++ b/README.md @@ -142,3 +142,9 @@ make A=apps/net/httpserver ARCH=aarch64 LOG=info NET=y SMP=4 run ## Design ![](doc/figures/ArceOS.svg) + +## Crates available in [crates.io](https://crates.io) + +| Crate | Badge | Description | +|-|-|-| +| [crate_interface](crates/crate_interface/) | [![Crates.io](https://img.shields.io/crates/v/crate_interface)](https://crates.io/crates/crate_interface) | Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate. | diff --git a/crates/crate_interface/Cargo.toml b/crates/crate_interface/Cargo.toml index 541ea235d2..7608daea3a 100644 --- a/crates/crate_interface/Cargo.toml +++ b/crates/crate_interface/Cargo.toml @@ -1,13 +1,15 @@ [package] name = "crate_interface" -version = "0.1.0" +version = "0.1.1" edition = "2021" authors = ["Yuekai Jia "] -description = "Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate" +description = "Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate." license = "GPL-3.0-or-later OR Apache-2.0" homepage = "https://github.com/rcore-os/arceos" repository = "https://github.com/rcore-os/arceos/tree/main/crates/crate_interface" documentation = "https://rcore-os.github.io/arceos/crate_interface/index.html" +keywords = ["arceos", "api", "macro"] +categories = ["development-tools::procedural-macro-helpers", "no-std"] [dependencies] proc-macro2 = "1.0" diff --git a/crates/crate_interface/README.md b/crates/crate_interface/README.md new file mode 100644 index 0000000000..ca910ca7ce --- /dev/null +++ b/crates/crate_interface/README.md @@ -0,0 +1,38 @@ +# crate_interface + +[![Crates.io](https://img.shields.io/crates/v/crate_interface)](https://crates.io/crates/crate_interface) + +Provides a way to **define** an interface (trait) in a crate, but can +**implement** or **use** it in any crate. It 's usually used to solve +the problem of *circular dependencies* between crates. + +## Example + +```rust +// Define the interface +#[crate_interface::def_interface] +pub trait HelloIf { + fn hello(&self, name: &str, id: usize) -> String; +} + +// Implement the interface in any crate +struct HelloIfImpl; + +#[crate_interface::impl_interface] +impl HelloIf for HelloIfImpl { + fn hello(&self, name: &str, id: usize) -> String { + format!("Hello, {} {}!", name, id) + } +} + +// Call `HelloIfImpl::hello` in any crate +use crate_interface::call_interface; +assert_eq!( + call_interface!(HelloIf::hello("world", 123)), + "Hello, world 123!" +); +assert_eq!( + call_interface!(HelloIf::hello, "rust", 456), // another calling style + "Hello, rust 456!" +); +``` diff --git a/crates/crate_interface/src/lib.rs b/crates/crate_interface/src/lib.rs index 9f65b62984..adc2621de1 100644 --- a/crates/crate_interface/src/lib.rs +++ b/crates/crate_interface/src/lib.rs @@ -1,38 +1,4 @@ -//! Provides a way to **define** an interface (trait) in a crate, but can -//! **implement** or **use** it in any crate. It 's usually used to solve -//! the problem of *circular dependencies* between crates. -//! -//! # Example -//! -//! ``` -//! // Define the interface -//! #[crate_interface::def_interface] -//! pub trait HelloIf { -//! fn hello(&self, name: &str, id: usize) -> String; -//! } -//! -//! // Implement the interface in any crate -//! struct HelloIfImpl; -//! -//! #[crate_interface::impl_interface] -//! impl HelloIf for HelloIfImpl { -//! fn hello(&self, name: &str, id: usize) -> String { -//! format!("Hello, {} {}!", name, id) -//! } -//! } -//! -//! // Call `HelloIfImpl::hello` in any crate -//! use crate_interface::call_interface; -//! assert_eq!( -//! call_interface!(HelloIf::hello("world", 123)), -//! "Hello, world 123!" -//! ); -//! assert_eq!( -//! call_interface!(HelloIf::hello, "rust", 456), // another calling style -//! "Hello, rust 456!" -//! ); -//! ``` - +#![doc = include_str!("../README.md")] #![feature(iter_next_chunk)] use proc_macro::TokenStream; From 771615d8e9914e09e6c95766e9e56d15343130af Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Wed, 3 May 2023 15:53:14 +0800 Subject: [PATCH 10/13] Rename: doc/arceos-arch.md -> doc/README.md --- README.md | 6 ------ apps/net/udpserver/Cargo.toml | 2 +- doc/{arceos-arch.md => README.md} | 29 +++++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 9 deletions(-) rename doc/{arceos-arch.md => README.md} (71%) diff --git a/README.md b/README.md index 0eccb2c4e2..f9644b88cd 100644 --- a/README.md +++ b/README.md @@ -142,9 +142,3 @@ make A=apps/net/httpserver ARCH=aarch64 LOG=info NET=y SMP=4 run ## Design ![](doc/figures/ArceOS.svg) - -## Crates available in [crates.io](https://crates.io) - -| Crate | Badge | Description | -|-|-|-| -| [crate_interface](crates/crate_interface/) | [![Crates.io](https://img.shields.io/crates/v/crate_interface)](https://crates.io/crates/crate_interface) | Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate. | diff --git a/apps/net/udpserver/Cargo.toml b/apps/net/udpserver/Cargo.toml index ba27ec413e..e5987b91ce 100644 --- a/apps/net/udpserver/Cargo.toml +++ b/apps/net/udpserver/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" authors = ["Dashuai Wu "] [dependencies] -libax = { path = "../../../ulib/libax", features = ["paging", "multitask", "net"] } +libax = { path = "../../../ulib/libax", features = ["paging", "net"] } diff --git a/doc/arceos-arch.md b/doc/README.md similarity index 71% rename from doc/arceos-arch.md rename to doc/README.md index 306e965630..8eb6d7c9ea 100644 --- a/doc/arceos-arch.md +++ b/doc/README.md @@ -1,4 +1,4 @@ -# ArceOS Architecture +# ArceOS Architecture Overview ## ArceOS Modules @@ -23,7 +23,7 @@ * [axfs_vfs](../crates/axfs_vfs): Virtual filesystem interfaces used by ArceOS. * [axio](../crates/axio): `std::io`-like I/O traits for `no_std` environment. * [capability](../crates/capability): Provide basic capability-based security. -* [crate_interface](../crates/crate_interface): Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate. +* [crate_interface](../crates/crate_interface): Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate. [![Crates.io](https://img.shields.io/crates/v/crate_interface)](https://crates.io/crates/crate_interface) * [driver_block](../crates/driver_block): Common traits and types for block storage drivers. * [driver_common](../crates/driver_common): Device driver interfaces used by ArceOS. * [driver_display](../crates/driver_display): Common traits and types for graphics device drivers. @@ -45,6 +45,31 @@ * [timer_list](../crates/timer_list): A list of timed events that will be triggered sequentially when the timer expires. * [tuple_for_each](../crates/tuple_for_each): Provides macros and methods to iterate over the fields of a tuple struct. +## Applications (Rust) + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [helloworld](../apps/helloworld/) | | | A minimal app that just prints a string | +| [exception](../apps/exception/) | | paging | Exception handling test | +| [memtest](../apps/memtest/) | axalloc | alloc, paging | Dynamic memory allocation test | +| [display](../apps/display/) | axalloc, axdisplay | alloc, paging, display | Graphic/GUI test | +| [yield](../apps/task/yield/) | axalloc, axtask | alloc, paging, multitask, sched_fifo | Multi-threaded yielding test | +| [parallel](../apps/task/parallel/) | axalloc, axtask | alloc, paging, multitask, sched_fifo, irq | Parallel computing test (to test synchronization & mutex) | +| [sleep](../apps/task/sleep/) | axalloc, axtask | alloc, paging, multitask, sched_fifo, irq | Thread sleeping test | +| [priority](../apps/task/priority/) | axalloc, axtask | alloc, paging, multitask, sched_cfs | Thread priority test | +| [shell](../apps/fs/shell/) | axalloc, axdriver, axfs | alloc, paging, fs | A simple shell that responds to filesystem operations | +| [httpclient](../apps/net/httpclient/) | axalloc, axdriver, axnet | alloc, paging, net | A simple client that sends an HTTP request and then prints the response | +| [echoserver](../apps/net/echoserver/) | axalloc, axdriver, axnet, axtask | alloc, paging, net, multitask | A multi-threaded TCP server that reverses messages sent by the client | +| [httpserver](../apps/net/httpserver/) | axalloc, axdriver, axnet, axtask | alloc, paging, net, multitask | A multi-threaded HTTP server that serves a static web page | +| [udpserver](../apps/net/udpserver/) | axalloc, axdriver, axnet | alloc, paging, net | A simple echo server using UDP protocol | + +## Applications (C) +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [helloworld](../apps/c/helloworld/) | | | A minimal C app that just prints a string | +| [memtest](../apps/c/memtest/) | axalloc | alloc, paging | Dynamic memory allocation test in C | +| [sqlite3](../apps/c/sqlite3/) | axalloc, axdriver, axfs | alloc, paging, fp_simd, fs | Porting of [SQLite3](https://sqlite.org/index.html) | + ## Dependencies ```mermaid From fcd6eab9582525b174fc23bb04835b9d540ce62b Mon Sep 17 00:00:00 2001 From: chyyuu Date: Wed, 3 May 2023 18:23:18 +0800 Subject: [PATCH 11/13] update build&run C apps info in README.md --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f9644b88cd..e28d707464 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,27 @@ Install [cargo-binutils](https://github.com/rust-embedded/cargo-binutils) to use cargo install cargo-binutils ``` +#### for build&run C apps +Install `libclang-dev`: + +```bash +sudo apt install libclang-dev +``` + +Download&Install `cross-musl-based toolchains`: +``` +# download +wget https://musl.cc/aarch64-linux-musl-cross.tgz +wget https://musl.cc/riscv64-linux-musl-cross.tgz +wget https://musl.cc/x86_64-linux-musl-cross.tgz +# install +tar zxf aarch64-linux-musl-cross.tgz +tar zxf riscv64-linux-musl-cross.tgz +tar zxf x86_64-linux-musl-cross.tgz +# exec below command in bash OR add below info in ~/.bashrc +export PATH=`pwd`/x86_64-linux-musl-cross/bin:`pwd`/aarch64-linux-musl-cross/bin:`pwd`/riscv64-linux-musl-cross/bin:$PATH +``` + ### Example apps ```bash @@ -67,7 +88,7 @@ cargo install cargo-binutils make A=path/to/app ARCH= LOG= NET=[y|n] FS=[y|n] ``` -Where `` should be one of `riscv64`, `aarch64`. +Where `` should be one of `riscv64`, `aarch64`,`x86_64`. `` should be one of `off`, `error`, `warn`, `info`, `debug`, `trace`. From 47376801b224cb43e51cdee808e476afdeba2bf4 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Wed, 3 May 2023 23:38:22 +0800 Subject: [PATCH 12/13] CI: increase app test timeout to 60s --- scripts/test/app_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test/app_test.sh b/scripts/test/app_test.sh index 07a7fd0201..1c3dde80cb 100755 --- a/scripts/test/app_test.sh +++ b/scripts/test/app_test.sh @@ -2,7 +2,7 @@ APP= ROOT=$(realpath $(dirname $0))/../../ -TIMEOUT=30s +TIMEOUT=60s EXIT_STATUS=0 S_PASS=0 From bb5f055b70a6feee1a3481115df32c7a0f457a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=8F=E5=B7=A8=E5=B9=BF?= Date: Fri, 5 May 2023 00:29:09 +0800 Subject: [PATCH 13/13] merge main and optimize code --- ulib/c_libax/include/ctype.h | 24 +++++++------- ulib/c_libax/include/stdbool.h | 4 +-- ulib/c_libax/include/string.h | 2 -- ulib/c_libax/include/time.h | 2 -- ulib/c_libax/src/ctype.c | 50 ------------------------------ ulib/c_libax/src/stdio.c | 9 ++++-- ulib/c_libax/src/string.c | 11 +------ ulib/c_libax/src/unistd.c | 2 +- ulib/libax/src/cbindings/thread.rs | 8 ++--- ulib/libax/src/cbindings/time.rs | 3 +- 10 files changed, 27 insertions(+), 88 deletions(-) diff --git a/ulib/c_libax/include/ctype.h b/ulib/c_libax/include/ctype.h index 485d105085..a3ba95b031 100644 --- a/ulib/c_libax/include/ctype.h +++ b/ulib/c_libax/include/ctype.h @@ -4,17 +4,17 @@ int tolower(int __c); int toupper(int __c); -int isprint(int); -int isalpha(int c); -int isalnum(int); -int isupper(int c); -int islower(int c); - -int isxdigit(int); -int isgraph(int); -int iscntrl(int); -int ispunct(int); - -int isascii(int); +#define isalpha(a) ((((unsigned)(a) | 32) - 'a') < 26) +#define isdigit(a) (((unsigned)(a) - '0') < 10) +#define islower(a) (((unsigned)(a) - 'a') < 26) +#define isupper(a) (((unsigned)(a) - 'A') < 26) +#define isprint(a) (((unsigned)(a)-0x20) < 0x5f) +#define isgraph(a) (((unsigned)(a)-0x21) < 0x5e) +#define isalnum(a) ((isalpha(a) || isdigit(a))) +#define iscntrl(a) (((unsigned)a < 0x20 || a == 0x7f)) +#define ispunct(a) ((isgraph(a) && !isalnum(a))) +#define isxdigit(a) ((isdigit(a) || ((unsigned)a | 32) - 'a' < 6)) +#define isascii(a) ((!(a & ~0x7f))) +#define isspace(a) ((a == ' ' || (unsigned)a - '\t' < 5)) #endif diff --git a/ulib/c_libax/include/stdbool.h b/ulib/c_libax/include/stdbool.h index e3325f24c7..af90479064 100644 --- a/ulib/c_libax/include/stdbool.h +++ b/ulib/c_libax/include/stdbool.h @@ -3,9 +3,9 @@ /* Represents true-or-false values */ #ifndef __cplusplus -#define true 1 +#define true 1 #define false 0 -#define bool _Bool +#define bool _Bool #endif #endif // __STDBOOL_H__ diff --git a/ulib/c_libax/include/string.h b/ulib/c_libax/include/string.h index fa5f01a0fa..478bf9b07c 100644 --- a/ulib/c_libax/include/string.h +++ b/ulib/c_libax/include/string.h @@ -3,8 +3,6 @@ #include -int isspace(int c); -int isdigit(int c); int atoi(const char *s); void *memset(void *dest, int c, size_t n); diff --git a/ulib/c_libax/include/time.h b/ulib/c_libax/include/time.h index 4757618d21..e810462d4b 100644 --- a/ulib/c_libax/include/time.h +++ b/ulib/c_libax/include/time.h @@ -10,8 +10,6 @@ typedef long time_t; #define CLOCK_MONOTONIC 1 #define CLOCKS_PER_SEC 1000000L -#define ax_time_usec_to_nsec(usec) ((usec)*1000UL) - struct tm { int tm_sec; /* seconds of minute */ int tm_min; /* minutes of hour */ diff --git a/ulib/c_libax/src/ctype.c b/ulib/c_libax/src/ctype.c index 1d05c16132..08e8e5e07f 100644 --- a/ulib/c_libax/src/ctype.c +++ b/ulib/c_libax/src/ctype.c @@ -2,11 +2,6 @@ #include #include -int isupper(int c) -{ - return (unsigned)c - 'A' < 26; -} - int tolower(int c) { if (isupper(c)) @@ -14,54 +9,9 @@ int tolower(int c) return c; } -int islower(int c) -{ - return (unsigned)c - 'a' < 26; -} - int toupper(int c) { if (islower(c)) return c & 0x5f; return c; } - -int isgraph(int c) -{ - return (unsigned)c - 0x21 < 0x5e; -} - -int isalpha(int c) -{ - return ((unsigned)c | 32) - 'a' < 26; -} - -int isprint(int c) -{ - return (unsigned)c - 0x20 < 0x5f; -} - -int isalnum(int c) -{ - return isalpha(c) || isdigit(c); -} - -int iscntrl(int c) -{ - return (unsigned)c < 0x20 || c == 0x7f; -} - -int ispunct(int c) -{ - return isgraph(c) && !isalnum(c); -} - -int isxdigit(int c) -{ - return isdigit(c) || ((unsigned)c | 32) - 'a' < 6; -} - -int isascii(int c) -{ - return !(c & ~0x7f); -} \ No newline at end of file diff --git a/ulib/c_libax/src/stdio.c b/ulib/c_libax/src/stdio.c index 3aefec95aa..7850dd4e1c 100644 --- a/ulib/c_libax/src/stdio.c +++ b/ulib/c_libax/src/stdio.c @@ -215,6 +215,8 @@ void fprintf(int f, const char *restrict fmt, ...) va_end(ap); } +#if defined(AX_CONFIG_ALLOC) && defined(AX_CONFIG_FS) + int __fmodeflags(const char *mode) { int flags; @@ -237,8 +239,6 @@ int __fmodeflags(const char *mode) return flags; } -#if defined(AX_CONFIG_ALLOC) && defined(AX_CONFIG_FS) - FILE *fopen(const char *filename, const char *mode) { FILE *f; @@ -252,7 +252,10 @@ FILE *fopen(const char *filename, const char *mode) f = (FILE *)malloc(sizeof(FILE)); flags = __fmodeflags(mode); - int fd = ax_open(filename, flags, mode); + // TODO: currently mode is unused in ax_open + int fd = ax_open(filename, flags, 0666); + if (fd < 0) + return NULL; f->fd = fd; return f; diff --git a/ulib/c_libax/src/string.c b/ulib/c_libax/src/string.c index cafd811aca..626e2ee208 100644 --- a/ulib/c_libax/src/string.c +++ b/ulib/c_libax/src/string.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -16,16 +17,6 @@ size_t strnlen(const char *s, size_t n) return p ? p - s : n; } -int isspace(int c) -{ - return c == ' ' || (unsigned)c - '\t' < 5; -} - -int isdigit(int c) -{ - return (unsigned)c - '0' < 10; -} - int atoi(const char *s) { int n = 0, neg = 0; diff --git a/ulib/c_libax/src/unistd.c b/ulib/c_libax/src/unistd.c index 07037706c7..5ae400813b 100644 --- a/ulib/c_libax/src/unistd.c +++ b/ulib/c_libax/src/unistd.c @@ -38,7 +38,7 @@ int usleep(unsigned int usec) struct timespec ts; ts.tv_sec = (long int)(usec / 1000000); - ts.tv_nsec = (long int)ax_time_usec_to_nsec(usec % 1000000); + ts.tv_nsec = (long int)usec % 1000; if (nanosleep(&ts, &ts)) return -1; diff --git a/ulib/libax/src/cbindings/thread.rs b/ulib/libax/src/cbindings/thread.rs index cb46d144b7..600394e61a 100644 --- a/ulib/libax/src/cbindings/thread.rs +++ b/ulib/libax/src/cbindings/thread.rs @@ -1,13 +1,12 @@ -//! This crate includes syscalls that need `multitask` feature - -use crate::debug; use crate::task::exit; -use axerrno::{LinuxError, LinuxResult}; use core::ffi::c_int; #[cfg(feature = "multitask")] use crate::task::current; +#[cfg(feature = "multitask")] +use axerrno::LinuxResult; + /// Exit current task #[no_mangle] pub unsafe extern "C" fn ax_exit(exit_code: c_int) -> ! { @@ -20,7 +19,6 @@ pub unsafe extern "C" fn ax_exit(exit_code: c_int) -> ! { pub unsafe extern "C" fn ax_getpid() -> c_int { ax_call_body!(ax_getpid, { let pid = current().id().as_u64() as c_int; - debug!("getpid return {}", pid); return Ok(pid); }) } diff --git a/ulib/libax/src/cbindings/time.rs b/ulib/libax/src/cbindings/time.rs index 05829ac423..da23fd595c 100644 --- a/ulib/libax/src/cbindings/time.rs +++ b/ulib/libax/src/cbindings/time.rs @@ -23,7 +23,7 @@ pub unsafe extern "C" fn ax_clock_gettime(ts: *mut ctypes::timespec) -> c_int { unsafe { *ts = ret; } - debug!("ax_clock_gettime: {}s, {}ns", now.as_secs(), now.as_nanos()); + debug!("ax_clock_gettime: {}.{:09}s", ret.tv_sec, ret.tv_nsec); return Ok(0); }) } @@ -41,6 +41,7 @@ pub unsafe extern "C" fn ax_nanosleep( return Err(LinuxError::EINVAL); } + debug!("ax_nanosleep <= {}.{:09}s", (*req).tv_sec, (*req).tv_nsec); let total_nano = (*req).tv_sec as u64 * NANOS_PER_SEC + (*req).tv_nsec as u64; let before = current_time().as_nanos() as u64;