Merge master into 0.2.0 - 688 commits merged, restore 0.2.0 local dev recipes
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
diff --git a/bootstrap/Cargo.toml b/bootstrap/Cargo.toml
|
||||
index 82120c21..50faead5 100644
|
||||
--- a/bootstrap/Cargo.toml
|
||||
+++ b/bootstrap/Cargo.toml
|
||||
@@ -7,5 +7,3 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
-[workspace]
|
||||
@@ -8 +7,0 @@ license = "MIT"
|
||||
-
|
||||
[dependencies]
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
diff --git a/Cargo.toml b/Cargo.toml
|
||||
index 9e776232..fdaeae69 100644
|
||||
--- a/Cargo.toml
|
||||
+++ b/Cargo.toml
|
||||
@@ -117 +117,2 @@ precedence = "deny"
|
||||
-#redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||
+redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||
+redox-rt = { path = "../../relibc/source/redox-rt" }
|
||||
@@ -0,0 +1,127 @@
|
||||
diff --git a/src/header/sys_select/cbindgen.toml b/src/header/sys_select/cbindgen.toml
|
||||
index 36bfa1fc..bb88cf19 100644
|
||||
--- a/src/header/sys_select/cbindgen.toml
|
||||
+++ b/src/header/sys_select/cbindgen.toml
|
||||
@@ -2,0 +3,2 @@ after_includes = """
|
||||
+#include <bits/timespec.h> // for timespec (needed by pselect)
|
||||
+
|
||||
@@ -33,0 +36,3 @@ exclude = ["FD_SETSIZE", "fd_set"]
|
||||
+
|
||||
+[export.rename]
|
||||
+"timespec" = "struct timespec"
|
||||
diff --git a/src/header/sys_select/mod.rs b/src/header/sys_select/mod.rs
|
||||
index 45e7a238..d4c34fb1 100644
|
||||
--- a/src/header/sys_select/mod.rs
|
||||
+++ b/src/header/sys_select/mod.rs
|
||||
@@ -5 +5 @@
|
||||
-use core::mem;
|
||||
+use core::{mem, ptr};
|
||||
@@ -11,0 +12,2 @@ use crate::{
|
||||
+ bits_sigset_t::sigset_t,
|
||||
+ bits_timespec::timespec,
|
||||
@@ -15 +17 @@ use crate::{
|
||||
- epoll_data, epoll_event, epoll_wait,
|
||||
+ epoll_data, epoll_event, epoll_pwait,
|
||||
@@ -48,0 +51,5 @@ pub struct fd_set {
|
||||
+/// Inner implementation shared by `select` and `pselect`.
|
||||
+///
|
||||
+/// Uses epoll internally, mirroring the `poll_epoll` pattern from `poll/mod.rs`.
|
||||
+/// The `timeout_ms` parameter is in milliseconds (-1 = block forever, 0 = non-blocking).
|
||||
+/// The `sigmask` parameter is passed through to `epoll_pwait` for atomic signal mask changes.
|
||||
@@ -55 +62,2 @@ pub fn select_epoll(
|
||||
- timeout: Option<&mut timeval>,
|
||||
+ timeout_ms: c_int,
|
||||
+ sigmask: *const sigset_t,
|
||||
@@ -136,11 +144 @@ pub fn select_epoll(
|
||||
- match timeout {
|
||||
- Some(timeout) => {
|
||||
- let sec_ms = (timeout.tv_sec as c_int).checked_mul(1000);
|
||||
- let usec_ms = (timeout.tv_usec as c_int) / 1000;
|
||||
- match sec_ms.and_then(|s| s.checked_add(usec_ms)) {
|
||||
- Some(s) => s as c_int,
|
||||
- None => c_int::MAX,
|
||||
- }
|
||||
- }
|
||||
- None => -1,
|
||||
- }
|
||||
+ timeout_ms
|
||||
@@ -149 +147 @@ pub fn select_epoll(
|
||||
- epoll_wait(
|
||||
+ epoll_pwait(
|
||||
@@ -153,0 +152 @@ pub fn select_epoll(
|
||||
+ sigmask,
|
||||
@@ -188 +187 @@ pub fn select_epoll(
|
||||
-/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pselect.html>.
|
||||
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/select.html>.
|
||||
@@ -196,0 +196,11 @@ pub unsafe extern "C" fn select(
|
||||
+ let timeout_ms = if timeout.is_null() {
|
||||
+ -1
|
||||
+ } else {
|
||||
+ let tv = unsafe { &*timeout };
|
||||
+ let sec_ms = (tv.tv_sec as c_int).checked_mul(1000);
|
||||
+ let usec_ms = (tv.tv_usec as c_int) / 1000;
|
||||
+ match sec_ms.and_then(|s| s.checked_add(usec_ms)) {
|
||||
+ Some(s) => s as c_int,
|
||||
+ None => c_int::MAX,
|
||||
+ }
|
||||
+ };
|
||||
@@ -215,5 +225,2 @@ pub unsafe extern "C" fn select(
|
||||
- if timeout.is_null() {
|
||||
- None
|
||||
- } else {
|
||||
- Some(unsafe { &mut *timeout })
|
||||
- }
|
||||
+ timeout_ms,
|
||||
+ ptr::null()
|
||||
@@ -228,0 +236,51 @@ pub unsafe extern "C" fn select(
|
||||
+
|
||||
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/pselect.html>.
|
||||
+#[unsafe(no_mangle)]
|
||||
+pub unsafe extern "C" fn pselect(
|
||||
+ nfds: c_int,
|
||||
+ readfds: *mut fd_set,
|
||||
+ writefds: *mut fd_set,
|
||||
+ exceptfds: *mut fd_set,
|
||||
+ timeout: *const timespec,
|
||||
+ sigmask: *const sigset_t,
|
||||
+) -> c_int {
|
||||
+ let timeout_ms = if timeout.is_null() {
|
||||
+ -1
|
||||
+ } else {
|
||||
+ let ts = unsafe { &*timeout };
|
||||
+ if ts.tv_sec > (c_int::MAX / 1000) as _ {
|
||||
+ c_int::MAX
|
||||
+ } else {
|
||||
+ ((ts.tv_sec as c_int) * 1000) + ((ts.tv_nsec as c_int) / 1000000)
|
||||
+ }
|
||||
+ };
|
||||
+ trace_expr!(
|
||||
+ select_epoll(
|
||||
+ nfds,
|
||||
+ if readfds.is_null() {
|
||||
+ None
|
||||
+ } else {
|
||||
+ Some(unsafe { &mut *readfds })
|
||||
+ },
|
||||
+ if writefds.is_null() {
|
||||
+ None
|
||||
+ } else {
|
||||
+ Some(unsafe { &mut *writefds })
|
||||
+ },
|
||||
+ if exceptfds.is_null() {
|
||||
+ None
|
||||
+ } else {
|
||||
+ Some(unsafe { &mut *exceptfds })
|
||||
+ },
|
||||
+ timeout_ms,
|
||||
+ sigmask
|
||||
+ ),
|
||||
+ "pselect({}, {:p}, {:p}, {:p}, {:p}, {:p})",
|
||||
+ nfds,
|
||||
+ readfds,
|
||||
+ writefds,
|
||||
+ exceptfds,
|
||||
+ timeout,
|
||||
+ sigmask
|
||||
+ )
|
||||
+}
|
||||
@@ -7,13 +7,102 @@ template = "custom"
|
||||
script = """
|
||||
DYNAMIC_INIT
|
||||
export ac_cv_func___fseterr=yes
|
||||
export ac_cv_type_sigset_t=yes
|
||||
|
||||
# Gnulib cross-compilation: relibc has standard POSIX headers and types
|
||||
# but gnulib's configure can't run test programs during cross-compilation.
|
||||
# Without these, gnulib generates broken #define fallbacks and replacement headers.
|
||||
|
||||
# Standard headers (gnulib can't detect these when cross-compiling)
|
||||
export ac_cv_header_stdio_h=yes
|
||||
export ac_cv_header_stdlib_h=yes
|
||||
export ac_cv_header_string_h=yes
|
||||
export ac_cv_header_strings_h=yes
|
||||
export ac_cv_header_inttypes_h=yes
|
||||
export ac_cv_header_stdint_h=yes
|
||||
export ac_cv_header_unistd_h=yes
|
||||
export ac_cv_header_sys_types_h=yes
|
||||
export ac_cv_header_sys_stat_h=yes
|
||||
export ac_cv_header_time_h=yes
|
||||
export ac_cv_header_sys_time_h=yes
|
||||
export ac_cv_header_sys_select_h=yes
|
||||
export ac_cv_header_wchar_h=yes
|
||||
export ac_cv_header_wctype_h=yes
|
||||
export ac_cv_header_signal_h=yes
|
||||
export ac_cv_header_dirent_h=yes
|
||||
export ac_cv_header_fcntl_h=yes
|
||||
export ac_cv_header_locale_h=yes
|
||||
export ac_cv_header_errno_h=yes
|
||||
export ac_cv_header_ctype_h=yes
|
||||
export ac_cv_header_limits_h=yes
|
||||
export ac_cv_header_stdarg_h=yes
|
||||
export ac_cv_header_stddef_h=yes
|
||||
export ac_cv_header_math_h=yes
|
||||
export ac_cv_header_spawn_h=yes
|
||||
export gl_cv_header_inttypes_h=yes
|
||||
export gl_cv_header_stdint_h=yes
|
||||
export gl_cv_header_inttypes_h_with_uintmax=yes
|
||||
export ac_cv_have_inttypes_h_with_uintmax=yes
|
||||
|
||||
# Standard types (gnulib generates broken fallbacks without these)
|
||||
export ac_cv_type_intmax_t=yes
|
||||
export ac_cv_type_uintmax_t=yes
|
||||
export ac_cv_type_gid_t=yes
|
||||
export ac_cv_type_uid_t=yes
|
||||
export ac_cv_type_pid_t=yes
|
||||
export ac_cv_type_mode_t=yes
|
||||
export ac_cv_type_off_t=yes
|
||||
export ac_cv_type_size_t=yes
|
||||
export ac_cv_type_ssize_t=yes
|
||||
export ac_cv_type_ptrdiff_t=yes
|
||||
export ac_cv_type_nlink_t=yes
|
||||
export ac_cv_type_mbstate_t=yes
|
||||
export gl_cv_type_intmax_t=yes
|
||||
export gl_cv_type_ptrdiff_t_signed=yes
|
||||
export gl_cv_header_inttypes_h_with_uintmax=yes
|
||||
export ac_cv_have_inttypes_h_with_uintmax=yes
|
||||
|
||||
# Spawn functions (relibc provides these via the P3-spawn patch)
|
||||
export gl_cv_func_spawn_posix_spawn=yes
|
||||
export gl_cv_func_spawn_posix_spawnp=yes
|
||||
export ac_cv_func_posix_spawn=yes
|
||||
export ac_cv_func_posix_spawnp=yes
|
||||
export ac_cv_func_posix_spawn_file_actions_init=yes
|
||||
export ac_cv_func_posix_spawn_file_actions_destroy=yes
|
||||
export ac_cv_func_posix_spawn_file_actions_addopen=yes
|
||||
export ac_cv_func_posix_spawn_file_actions_addclose=yes
|
||||
export ac_cv_func_posix_spawn_file_actions_adddup2=yes
|
||||
export ac_cv_func_posix_spawnattr_init=yes
|
||||
export ac_cv_func_posix_spawnattr_destroy=yes
|
||||
export ac_cv_func_posix_spawnattr_setflags=yes
|
||||
export ac_cv_func_posix_spawnattr_getflags=yes
|
||||
export ac_cv_func_posix_spawnattr_setsigmask=yes
|
||||
export ac_cv_func_posix_spawnattr_getsigmask=yes
|
||||
|
||||
# Other functions
|
||||
export ac_cv_func_getpagesize=yes
|
||||
export ac_cv_func_memcmp_working=yes
|
||||
export ac_cv_func_mmap_fixed_mapped=yes
|
||||
|
||||
# Spawn types
|
||||
export ac_cv_type_posix_spawnattr_t=yes
|
||||
export ac_cv_type_posix_spawn_file_actions_t=yes
|
||||
COOKBOOK_CONFIGURE_FLAGS+=(
|
||||
--disable-nls
|
||||
)
|
||||
cookbook_configure
|
||||
|
||||
# Cross-compilation fix: run configure manually, then patch the
|
||||
# generated Makefile to use host bison instead of the cross-compiled
|
||||
# wrapper. The generated Makefile hardcodes
|
||||
# BISON = $(top_builddir)/tests/bison
|
||||
# which wraps the x86_64-unknown-redox binary — unrunnable on the host.
|
||||
"${COOKBOOK_CONFIGURE}" "${COOKBOOK_CONFIGURE_FLAGS[@]}"
|
||||
sed -i 's|^BISON = .*|BISON = /usr/bin/bison|' "${COOKBOOK_BUILD}/Makefile"
|
||||
|
||||
# Fix gnulib cross-compilation misdetections in config.h
|
||||
"${COOKBOOK_ROOT}/local/scripts/gnulib-cross-fix.sh" "${COOKBOOK_BUILD}/lib/config.h"
|
||||
|
||||
"${COOKBOOK_MAKE}" -j "${COOKBOOK_MAKE_JOBS}"
|
||||
"${COOKBOOK_MAKE}" install DESTDIR="${COOKBOOK_STAGE}"
|
||||
"""
|
||||
|
||||
[package]
|
||||
|
||||
+7623
-6338
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
@set UPDATED 12 September 2021
|
||||
@set UPDATED-MONTH September 2021
|
||||
@set UPDATED 15 May 2026
|
||||
@set UPDATED-MONTH May 2026
|
||||
@set EDITION 3.8.2
|
||||
@set VERSION 3.8.2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@set UPDATED 12 September 2021
|
||||
@set UPDATED-MONTH September 2021
|
||||
@set UPDATED 15 May 2026
|
||||
@set UPDATED-MONTH May 2026
|
||||
@set EDITION 3.8.2
|
||||
@set VERSION 3.8.2
|
||||
|
||||
@@ -585,10 +585,10 @@
|
||||
/* Define to 1 if you have the <bp-sym.h> header file. */
|
||||
#undef HAVE_BP_SYM_H
|
||||
|
||||
/* Define to 1 if you have the `canonicalize_file_name' function. */
|
||||
/* Define to 1 if you have the 'canonicalize_file_name' function. */
|
||||
#undef HAVE_CANONICALIZE_FILE_NAME
|
||||
|
||||
/* Define to 1 if you have the `catgets' function. */
|
||||
/* Define to 1 if you have the 'catgets' function. */
|
||||
#undef HAVE_CATGETS
|
||||
|
||||
/* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the
|
||||
@@ -599,16 +599,16 @@
|
||||
the CoreFoundation framework. */
|
||||
#undef HAVE_CFPREFERENCESCOPYAPPVALUE
|
||||
|
||||
/* Define to 1 if you have the `clock_gettime' function. */
|
||||
/* Define to 1 if you have the 'clock_gettime' function. */
|
||||
#undef HAVE_CLOCK_GETTIME
|
||||
|
||||
/* Define to 1 if you have the `clock_settime' function. */
|
||||
/* Define to 1 if you have the 'clock_settime' function. */
|
||||
#undef HAVE_CLOCK_SETTIME
|
||||
|
||||
/* Define to 1 if you have the `closedir' function. */
|
||||
/* Define to 1 if you have the 'closedir' function. */
|
||||
#undef HAVE_CLOSEDIR
|
||||
|
||||
/* Define to 1 if you have the `confstr' function. */
|
||||
/* Define to 1 if you have the 'confstr' function. */
|
||||
#undef HAVE_CONFSTR
|
||||
|
||||
/* Define if the copysignf function is declared in <math.h> and available in
|
||||
@@ -623,7 +623,7 @@
|
||||
libc. */
|
||||
#undef HAVE_COPYSIGN_IN_LIBC
|
||||
|
||||
/* Define to 1 if you have the `copy_file_range' function. */
|
||||
/* Define to 1 if you have the 'copy_file_range' function. */
|
||||
#undef HAVE_COPY_FILE_RANGE
|
||||
|
||||
/* Define to 1 if you have the <crtdefs.h> header file. */
|
||||
@@ -633,127 +633,127 @@
|
||||
*/
|
||||
#undef HAVE_DCGETTEXT
|
||||
|
||||
/* Define to 1 if you have the declaration of `alarm', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'alarm', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_ALARM
|
||||
|
||||
/* Define to 1 if you have the declaration of `clearerr_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'clearerr_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_CLEARERR_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `copysign', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'copysign', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_COPYSIGN
|
||||
|
||||
/* Define to 1 if you have the declaration of `copysignf', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'copysignf', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_COPYSIGNF
|
||||
|
||||
/* Define to 1 if you have the declaration of `copysignl', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'copysignl', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_COPYSIGNL
|
||||
|
||||
/* Define to 1 if you have the declaration of `dirfd', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'dirfd', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_DIRFD
|
||||
|
||||
/* Define to 1 if you have the declaration of `ecvt', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'ecvt', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_ECVT
|
||||
|
||||
/* Define to 1 if you have the declaration of `execvpe', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'execvpe', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_EXECVPE
|
||||
|
||||
/* Define to 1 if you have the declaration of `fchdir', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'fchdir', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_FCHDIR
|
||||
|
||||
/* Define to 1 if you have the declaration of `fcloseall', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'fcloseall', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_FCLOSEALL
|
||||
|
||||
/* Define to 1 if you have the declaration of `fcvt', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'fcvt', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_FCVT
|
||||
|
||||
/* Define to 1 if you have the declaration of `fdopendir', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'fdopendir', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_FDOPENDIR
|
||||
|
||||
/* Define to 1 if you have the declaration of `feof_unlocked', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'feof_unlocked', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_FEOF_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `ferror_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'ferror_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_FERROR_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `fflush_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'fflush_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_FFLUSH_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `fgets_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'fgets_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_FGETS_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `fputc_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'fputc_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_FPUTC_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `fputs_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'fputs_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_FPUTS_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `fread_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'fread_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_FREAD_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `fwrite_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'fwrite_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_FWRITE_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `gcvt', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'gcvt', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_GCVT
|
||||
|
||||
/* Define to 1 if you have the declaration of `getchar_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'getchar_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_GETCHAR_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `getcwd', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'getcwd', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_GETCWD
|
||||
|
||||
/* Define to 1 if you have the declaration of `getc_unlocked', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'getc_unlocked', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_GETC_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `getdelim', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'getdelim', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_GETDELIM
|
||||
|
||||
/* Define to 1 if you have the declaration of `getdtablesize', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'getdtablesize', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_GETDTABLESIZE
|
||||
|
||||
/* Define to 1 if you have the declaration of `gethrtime', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'gethrtime', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_GETHRTIME
|
||||
|
||||
/* Define to 1 if you have the declaration of `getline', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'getline', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_GETLINE
|
||||
|
||||
/* Define to 1 if you have the declaration of `iswblank', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'iswblank', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_ISWBLANK
|
||||
|
||||
/* Define to 1 if you have the declaration of `mbrtowc', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'mbrtowc', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_MBRTOWC
|
||||
|
||||
/* Define to 1 if you have the declaration of `mbsinit', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'mbsinit', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_MBSINIT
|
||||
|
||||
@@ -761,122 +761,122 @@
|
||||
otherwise. */
|
||||
#undef HAVE_DECL_MBSWIDTH_IN_WCHAR_H
|
||||
|
||||
/* Define to 1 if you have the declaration of `memrchr', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'memrchr', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_MEMRCHR
|
||||
|
||||
/* Define to 1 if you have the declaration of `obstack_printf', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'obstack_printf', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_OBSTACK_PRINTF
|
||||
|
||||
/* Define to 1 if you have the declaration of `posix_spawn', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'posix_spawn', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_POSIX_SPAWN
|
||||
|
||||
/* Define to 1 if you have the declaration of `program_invocation_name', and
|
||||
/* Define to 1 if you have the declaration of 'program_invocation_name', and
|
||||
to 0 if you don't. */
|
||||
#undef HAVE_DECL_PROGRAM_INVOCATION_NAME
|
||||
|
||||
/* Define to 1 if you have the declaration of `program_invocation_short_name',
|
||||
/* Define to 1 if you have the declaration of 'program_invocation_short_name',
|
||||
and to 0 if you don't. */
|
||||
#undef HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME
|
||||
|
||||
/* Define to 1 if you have the declaration of `putchar_unlocked', and to 0 if
|
||||
/* Define to 1 if you have the declaration of 'putchar_unlocked', and to 0 if
|
||||
you don't. */
|
||||
#undef HAVE_DECL_PUTCHAR_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `putc_unlocked', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'putc_unlocked', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_PUTC_UNLOCKED
|
||||
|
||||
/* Define to 1 if you have the declaration of `setenv', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'setenv', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_SETENV
|
||||
|
||||
/* Define to 1 if you have the declaration of `snprintf', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'snprintf', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_SNPRINTF
|
||||
|
||||
/* Define to 1 if you have the declaration of `stpncpy', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'stpncpy', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_STPNCPY
|
||||
|
||||
/* Define to 1 if you have the declaration of `strdup', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'strdup', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_STRDUP
|
||||
|
||||
/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'strerror_r', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_STRERROR_R
|
||||
|
||||
/* Define to 1 if you have the declaration of `strndup', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'strndup', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_STRNDUP
|
||||
|
||||
/* Define to 1 if you have the declaration of `strnlen', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'strnlen', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_STRNLEN
|
||||
|
||||
/* Define to 1 if you have the declaration of `towlower', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'towlower', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_TOWLOWER
|
||||
|
||||
/* Define to 1 if you have the declaration of `unsetenv', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'unsetenv', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_UNSETENV
|
||||
|
||||
/* Define to 1 if you have the declaration of `vsnprintf', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'vsnprintf', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_VSNPRINTF
|
||||
|
||||
/* Define to 1 if you have the declaration of `wcsdup', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of 'wcsdup', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL_WCSDUP
|
||||
|
||||
/* Define to 1 if you have the declaration of `wcwidth', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of 'wcwidth', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL_WCWIDTH
|
||||
|
||||
/* Define to 1 if you have the declaration of `_snprintf', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of '_snprintf', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL__SNPRINTF
|
||||
|
||||
/* Define to 1 if you have the declaration of `__argv', and to 0 if you don't.
|
||||
/* Define to 1 if you have the declaration of '__argv', and to 0 if you don't.
|
||||
*/
|
||||
#undef HAVE_DECL___ARGV
|
||||
|
||||
/* Define to 1 if you have the declaration of `__fpending', and to 0 if you
|
||||
/* Define to 1 if you have the declaration of '__fpending', and to 0 if you
|
||||
don't. */
|
||||
#undef HAVE_DECL___FPENDING
|
||||
|
||||
/* Define to 1 if you have the <dirent.h> header file. */
|
||||
#undef HAVE_DIRENT_H
|
||||
|
||||
/* Define to 1 if you have the `dirfd' function. */
|
||||
/* Define to 1 if you have the 'dirfd' function. */
|
||||
#undef HAVE_DIRFD
|
||||
|
||||
/* Define if you have the declaration of environ. */
|
||||
#undef HAVE_ENVIRON_DECL
|
||||
|
||||
/* Define to 1 if you have the `faccessat' function. */
|
||||
/* Define to 1 if you have the 'faccessat' function. */
|
||||
#undef HAVE_FACCESSAT
|
||||
|
||||
/* Define to 1 if you have the `fchdir' function. */
|
||||
/* Define to 1 if you have the 'fchdir' function. */
|
||||
#undef HAVE_FCHDIR
|
||||
|
||||
/* Define to 1 if you have the `fcntl' function. */
|
||||
/* Define to 1 if you have the 'fcntl' function. */
|
||||
#undef HAVE_FCNTL
|
||||
|
||||
/* Define to 1 if you have the `fdopendir' function. */
|
||||
/* Define to 1 if you have the 'fdopendir' function. */
|
||||
#undef HAVE_FDOPENDIR
|
||||
|
||||
/* Define to 1 if you have the <features.h> header file. */
|
||||
#undef HAVE_FEATURES_H
|
||||
|
||||
/* Define to 1 if you have the `ffsl' function. */
|
||||
/* Define to 1 if you have the 'ffsl' function. */
|
||||
#undef HAVE_FFSL
|
||||
|
||||
/* Define to 1 if you have the `flockfile' function. */
|
||||
/* Define to 1 if you have the 'flockfile' function. */
|
||||
#undef HAVE_FLOCKFILE
|
||||
|
||||
/* Define if the 'free' function is guaranteed to preserve errno. */
|
||||
@@ -888,50 +888,50 @@
|
||||
/* Define if the frexp function is available in libc. */
|
||||
#undef HAVE_FREXP_IN_LIBC
|
||||
|
||||
/* Define to 1 if you have the `fstatat' function. */
|
||||
/* Define to 1 if you have the 'fstatat' function. */
|
||||
#undef HAVE_FSTATAT
|
||||
|
||||
/* Define to 1 if you have the `fsync' function. */
|
||||
/* Define to 1 if you have the 'fsync' function. */
|
||||
#undef HAVE_FSYNC
|
||||
|
||||
/* Define to 1 if you have the `funlockfile' function. */
|
||||
/* Define to 1 if you have the 'funlockfile' function. */
|
||||
#undef HAVE_FUNLOCKFILE
|
||||
|
||||
/* Define to 1 if you have the `getcwd' function. */
|
||||
/* Define to 1 if you have the 'getcwd' function. */
|
||||
#undef HAVE_GETCWD
|
||||
|
||||
/* Define to 1 if getcwd works, but with shorter paths than is generally
|
||||
tested with the replacement. */
|
||||
#undef HAVE_GETCWD_SHORTER
|
||||
|
||||
/* Define to 1 if you have the `getdelim' function. */
|
||||
/* Define to 1 if you have the 'getdelim' function. */
|
||||
#undef HAVE_GETDELIM
|
||||
|
||||
/* Define to 1 if you have the `getdtablesize' function. */
|
||||
/* Define to 1 if you have the 'getdtablesize' function. */
|
||||
#undef HAVE_GETDTABLESIZE
|
||||
|
||||
/* Define to 1 if you have the `getexecname' function. */
|
||||
/* Define to 1 if you have the 'getexecname' function. */
|
||||
#undef HAVE_GETEXECNAME
|
||||
|
||||
/* Define to 1 if you have the <getopt.h> header file. */
|
||||
#undef HAVE_GETOPT_H
|
||||
|
||||
/* Define to 1 if you have the `getopt_long_only' function. */
|
||||
/* Define to 1 if you have the 'getopt_long_only' function. */
|
||||
#undef HAVE_GETOPT_LONG_ONLY
|
||||
|
||||
/* Define to 1 if the system has the 'getpagesize' function. */
|
||||
#undef HAVE_GETPAGESIZE
|
||||
|
||||
/* Define to 1 if you have the `getprogname' function. */
|
||||
/* Define to 1 if you have the 'getprogname' function. */
|
||||
#undef HAVE_GETPROGNAME
|
||||
|
||||
/* Define to 1 if you have the `getrusage' function. */
|
||||
/* Define to 1 if you have the 'getrusage' function. */
|
||||
#undef HAVE_GETRUSAGE
|
||||
|
||||
/* Define if the GNU gettext() function is already present or preinstalled. */
|
||||
#undef HAVE_GETTEXT
|
||||
|
||||
/* Define to 1 if you have the `gettimeofday' function. */
|
||||
/* Define to 1 if you have the 'gettimeofday' function. */
|
||||
#undef HAVE_GETTIMEOFDAY
|
||||
|
||||
/* Define if you have the iconv() function and it works. */
|
||||
@@ -955,7 +955,7 @@
|
||||
declares uintmax_t. */
|
||||
#undef HAVE_INTTYPES_H_WITH_UINTMAX
|
||||
|
||||
/* Define to 1 if you have the `isascii' function. */
|
||||
/* Define to 1 if you have the 'isascii' function. */
|
||||
#undef HAVE_ISASCII
|
||||
|
||||
/* Define if the isnan(double) function is available in libc. */
|
||||
@@ -967,10 +967,10 @@
|
||||
/* Define if the isnan(long double) function is available in libc. */
|
||||
#undef HAVE_ISNANL_IN_LIBC
|
||||
|
||||
/* Define to 1 if you have the `iswblank' function. */
|
||||
/* Define to 1 if you have the 'iswblank' function. */
|
||||
#undef HAVE_ISWBLANK
|
||||
|
||||
/* Define to 1 if you have the `iswcntrl' function. */
|
||||
/* Define to 1 if you have the 'iswcntrl' function. */
|
||||
#undef HAVE_ISWCNTRL
|
||||
|
||||
/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
|
||||
@@ -991,7 +991,7 @@
|
||||
/* Define to 1 if you have the <limits.h> header file. */
|
||||
#undef HAVE_LIMITS_H
|
||||
|
||||
/* Define to 1 if you have the `link' function. */
|
||||
/* Define to 1 if you have the 'link' function. */
|
||||
#undef HAVE_LINK
|
||||
|
||||
/* Define to 1 if you have the <locale.h> header file. */
|
||||
@@ -1000,7 +1000,7 @@
|
||||
/* Define to 1 if the system has the type 'long long int'. */
|
||||
#undef HAVE_LONG_LONG_INT
|
||||
|
||||
/* Define to 1 if you have the `lstat' function. */
|
||||
/* Define to 1 if you have the 'lstat' function. */
|
||||
#undef HAVE_LSTAT
|
||||
|
||||
/* Define to 1 if you have the <mach-o/dyld.h> header file. */
|
||||
@@ -1016,22 +1016,22 @@
|
||||
/* Define to 1 if you have the <math.h> header file. */
|
||||
#undef HAVE_MATH_H
|
||||
|
||||
/* Define to 1 if you have the `mbrtowc' function. */
|
||||
/* Define to 1 if you have the 'mbrtowc' function. */
|
||||
#undef HAVE_MBRTOWC
|
||||
|
||||
/* Define to 1 if you have the `mbsinit' function. */
|
||||
/* Define to 1 if you have the 'mbsinit' function. */
|
||||
#undef HAVE_MBSINIT
|
||||
|
||||
/* Define to 1 if <wchar.h> declares mbstate_t. */
|
||||
#undef HAVE_MBSTATE_T
|
||||
|
||||
/* Define to 1 if you have the `mempcpy' function. */
|
||||
/* Define to 1 if you have the 'mempcpy' function. */
|
||||
#undef HAVE_MEMPCPY
|
||||
|
||||
/* Define to 1 if you have the `memrchr' function. */
|
||||
/* Define to 1 if you have the 'memrchr' function. */
|
||||
#undef HAVE_MEMRCHR
|
||||
|
||||
/* Define to 1 if you have the `microuptime' function. */
|
||||
/* Define to 1 if you have the 'microuptime' function. */
|
||||
#undef HAVE_MICROUPTIME
|
||||
|
||||
/* Define to 1 if getcwd minimally works, that is, its result can be trusted
|
||||
@@ -1047,29 +1047,29 @@
|
||||
/* Define to 1 if <sys/param.h> defines the MIN and MAX macros. */
|
||||
#undef HAVE_MINMAX_IN_SYS_PARAM_H
|
||||
|
||||
/* Define to 1 if you have the `mprotect' function. */
|
||||
/* Define to 1 if you have the 'mprotect' function. */
|
||||
#undef HAVE_MPROTECT
|
||||
|
||||
/* Define to 1 on MSVC platforms that have the "invalid parameter handler"
|
||||
concept. */
|
||||
#undef HAVE_MSVC_INVALID_PARAMETER_HANDLER
|
||||
|
||||
/* Define to 1 if you have the `nanouptime' function. */
|
||||
/* Define to 1 if you have the 'nanouptime' function. */
|
||||
#undef HAVE_NANOUPTIME
|
||||
|
||||
/* Define to 1 if you have the `nl_langinfo' function. */
|
||||
/* Define to 1 if you have the 'nl_langinfo' function. */
|
||||
#undef HAVE_NL_LANGINFO
|
||||
|
||||
/* Define to 1 if the system has obstacks that work with any size object. */
|
||||
#undef HAVE_OBSTACK
|
||||
|
||||
/* Define to 1 if you have the `obstack_printf' function. */
|
||||
/* Define to 1 if you have the 'obstack_printf' function. */
|
||||
#undef HAVE_OBSTACK_PRINTF
|
||||
|
||||
/* Define to 1 if you have the `openat' function. */
|
||||
/* Define to 1 if you have the 'openat' function. */
|
||||
#undef HAVE_OPENAT
|
||||
|
||||
/* Define to 1 if you have the `opendir' function. */
|
||||
/* Define to 1 if you have the 'opendir' function. */
|
||||
#undef HAVE_OPENDIR
|
||||
|
||||
/* Define to 1 if getcwd works, except it sometimes fails when it shouldn't,
|
||||
@@ -1079,27 +1079,27 @@
|
||||
/* Define to 1 if you have the <paths.h> header file. */
|
||||
#undef HAVE_PATHS_H
|
||||
|
||||
/* Define to 1 if you have the `pipe' function. */
|
||||
/* Define to 1 if you have the 'pipe' function. */
|
||||
#undef HAVE_PIPE
|
||||
|
||||
/* Define to 1 if you have the `pipe2' function. */
|
||||
/* Define to 1 if you have the 'pipe2' function. */
|
||||
#undef HAVE_PIPE2
|
||||
|
||||
/* Define to 1 if you have the `posix_spawn' function. */
|
||||
/* Define to 1 if you have the 'posix_spawn' function. */
|
||||
#undef HAVE_POSIX_SPAWN
|
||||
|
||||
/* Define to 1 if the system has the type `posix_spawnattr_t'. */
|
||||
/* Define to 1 if the system has the type 'posix_spawnattr_t'. */
|
||||
#undef HAVE_POSIX_SPAWNATTR_T
|
||||
|
||||
/* Define to 1 if you have the `posix_spawn_file_actions_addchdir' function.
|
||||
/* Define to 1 if you have the 'posix_spawn_file_actions_addchdir' function.
|
||||
*/
|
||||
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR
|
||||
|
||||
/* Define to 1 if you have the `posix_spawn_file_actions_addchdir_np'
|
||||
/* Define to 1 if you have the 'posix_spawn_file_actions_addchdir_np'
|
||||
function. */
|
||||
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
|
||||
|
||||
/* Define to 1 if the system has the type `posix_spawn_file_actions_t'. */
|
||||
/* Define to 1 if the system has the type 'posix_spawn_file_actions_t'. */
|
||||
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_T
|
||||
|
||||
/* Define if you have the <pthread.h> header and the POSIX threads API. */
|
||||
@@ -1115,13 +1115,13 @@
|
||||
reader. */
|
||||
#undef HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER
|
||||
|
||||
/* Define to 1 if you have the `raise' function. */
|
||||
/* Define to 1 if you have the 'raise' function. */
|
||||
#undef HAVE_RAISE
|
||||
|
||||
/* Define to 1 if you have the `rawmemchr' function. */
|
||||
/* Define to 1 if you have the 'rawmemchr' function. */
|
||||
#undef HAVE_RAWMEMCHR
|
||||
|
||||
/* Define to 1 if you have the `readdir' function. */
|
||||
/* Define to 1 if you have the 'readdir' function. */
|
||||
#undef HAVE_READDIR
|
||||
|
||||
/* Define if you have the readline library. */
|
||||
@@ -1133,19 +1133,19 @@
|
||||
/* Define to 1 if you have the <readline/readline.h> header file. */
|
||||
#undef HAVE_READLINE_READLINE_H
|
||||
|
||||
/* Define to 1 if you have the `readlink' function. */
|
||||
/* Define to 1 if you have the 'readlink' function. */
|
||||
#undef HAVE_READLINK
|
||||
|
||||
/* Define to 1 if you have the `readlinkat' function. */
|
||||
/* Define to 1 if you have the 'readlinkat' function. */
|
||||
#undef HAVE_READLINKAT
|
||||
|
||||
/* Define to 1 if you have the `reallocarray' function. */
|
||||
/* Define to 1 if you have the 'reallocarray' function. */
|
||||
#undef HAVE_REALLOCARRAY
|
||||
|
||||
/* Define to 1 if you have the `realpath' function. */
|
||||
/* Define to 1 if you have the 'realpath' function. */
|
||||
#undef HAVE_REALPATH
|
||||
|
||||
/* Define to 1 if you have the `rewinddir' function. */
|
||||
/* Define to 1 if you have the 'rewinddir' function. */
|
||||
#undef HAVE_REWINDDIR
|
||||
|
||||
/* Define to 1 if 'long double' and 'double' have the same representation. */
|
||||
@@ -1154,10 +1154,10 @@
|
||||
/* Define to 1 if you have the <sched.h> header file. */
|
||||
#undef HAVE_SCHED_H
|
||||
|
||||
/* Define to 1 if you have the `sched_setparam' function. */
|
||||
/* Define to 1 if you have the 'sched_setparam' function. */
|
||||
#undef HAVE_SCHED_SETPARAM
|
||||
|
||||
/* Define to 1 if you have the `sched_setscheduler' function. */
|
||||
/* Define to 1 if you have the 'sched_setscheduler' function. */
|
||||
#undef HAVE_SCHED_SETSCHEDULER
|
||||
|
||||
/* Define to 1 if you have the <sdkddkver.h> header file. */
|
||||
@@ -1166,31 +1166,31 @@
|
||||
/* Define to 1 if you have the <search.h> header file. */
|
||||
#undef HAVE_SEARCH_H
|
||||
|
||||
/* Define to 1 if you have the `setdtablesize' function. */
|
||||
/* Define to 1 if you have the 'setdtablesize' function. */
|
||||
#undef HAVE_SETDTABLESIZE
|
||||
|
||||
/* Define to 1 if you have the `setegid' function. */
|
||||
/* Define to 1 if you have the 'setegid' function. */
|
||||
#undef HAVE_SETEGID
|
||||
|
||||
/* Define to 1 if you have the `setenv' function. */
|
||||
/* Define to 1 if you have the 'setenv' function. */
|
||||
#undef HAVE_SETENV
|
||||
|
||||
/* Define to 1 if you have the `seteuid' function. */
|
||||
/* Define to 1 if you have the 'seteuid' function. */
|
||||
#undef HAVE_SETEUID
|
||||
|
||||
/* Define to 1 if you have the `setlocale' function. */
|
||||
/* Define to 1 if you have the 'setlocale' function. */
|
||||
#undef HAVE_SETLOCALE
|
||||
|
||||
/* Define to 1 if you have the `sigaction' function. */
|
||||
/* Define to 1 if you have the 'sigaction' function. */
|
||||
#undef HAVE_SIGACTION
|
||||
|
||||
/* Define to 1 if you have the `sigaltstack' function. */
|
||||
/* Define to 1 if you have the 'sigaltstack' function. */
|
||||
#undef HAVE_SIGALTSTACK
|
||||
|
||||
/* Define to 1 if the system has the type `siginfo_t'. */
|
||||
/* Define to 1 if the system has the type 'siginfo_t'. */
|
||||
#undef HAVE_SIGINFO_T
|
||||
|
||||
/* Define to 1 if you have the `siginterrupt' function. */
|
||||
/* Define to 1 if you have the 'siginterrupt' function. */
|
||||
#undef HAVE_SIGINTERRUPT
|
||||
|
||||
/* Define to 1 if 'sig_atomic_t' is a signed integer type. */
|
||||
@@ -1202,13 +1202,13 @@
|
||||
/* Define to 1 if 'wint_t' is a signed integer type. */
|
||||
#undef HAVE_SIGNED_WINT_T
|
||||
|
||||
/* Define to 1 if the system has the type `sigset_t'. */
|
||||
/* Define to 1 if the system has the type 'sigset_t'. */
|
||||
#undef HAVE_SIGSET_T
|
||||
|
||||
/* Define to 1 if the system has the type `sig_atomic_t'. */
|
||||
/* Define to 1 if the system has the type 'sig_atomic_t'. */
|
||||
#undef HAVE_SIG_ATOMIC_T
|
||||
|
||||
/* Define to 1 if you have the `snprintf' function. */
|
||||
/* Define to 1 if you have the 'snprintf' function. */
|
||||
#undef HAVE_SNPRINTF
|
||||
|
||||
/* Define if the return value of the snprintf function is the number of of
|
||||
@@ -1239,16 +1239,16 @@
|
||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
||||
#undef HAVE_STDLIB_H
|
||||
|
||||
/* Define to 1 if you have the `stpcpy' function. */
|
||||
/* Define to 1 if you have the 'stpcpy' function. */
|
||||
#undef HAVE_STPCPY
|
||||
|
||||
/* Define if you have the stpncpy() function and it works. */
|
||||
#undef HAVE_STPNCPY
|
||||
|
||||
/* Define to 1 if you have the `strchrnul' function. */
|
||||
/* Define to 1 if you have the 'strchrnul' function. */
|
||||
#undef HAVE_STRCHRNUL
|
||||
|
||||
/* Define to 1 if you have the `strerror_r' function. */
|
||||
/* Define to 1 if you have the 'strerror_r' function. */
|
||||
#undef HAVE_STRERROR_R
|
||||
|
||||
/* Define to 1 if you have the <strings.h> header file. */
|
||||
@@ -1257,43 +1257,43 @@
|
||||
/* Define to 1 if you have the <string.h> header file. */
|
||||
#undef HAVE_STRING_H
|
||||
|
||||
/* Define to 1 if you have the `strndup' function. */
|
||||
/* Define to 1 if you have the 'strndup' function. */
|
||||
#undef HAVE_STRNDUP
|
||||
|
||||
/* Define to 1 if you have the `strnlen' function. */
|
||||
/* Define to 1 if you have the 'strnlen' function. */
|
||||
#undef HAVE_STRNLEN
|
||||
|
||||
/* Define to 1 if `sa_sigaction' is a member of `struct sigaction'. */
|
||||
/* Define to 1 if 'sa_sigaction' is a member of 'struct sigaction'. */
|
||||
#undef HAVE_STRUCT_SIGACTION_SA_SIGACTION
|
||||
|
||||
/* Define to 1 if `st_atimensec' is a member of `struct stat'. */
|
||||
/* Define to 1 if 'st_atimensec' is a member of 'struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_ATIMENSEC
|
||||
|
||||
/* Define to 1 if `st_atimespec.tv_nsec' is a member of `struct stat'. */
|
||||
/* Define to 1 if 'st_atimespec.tv_nsec' is a member of 'struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
|
||||
|
||||
/* Define to 1 if `st_atim.st__tim.tv_nsec' is a member of `struct stat'. */
|
||||
/* Define to 1 if 'st_atim.st__tim.tv_nsec' is a member of 'struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC
|
||||
|
||||
/* Define to 1 if `st_atim.tv_nsec' is a member of `struct stat'. */
|
||||
/* Define to 1 if 'st_atim.tv_nsec' is a member of 'struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
|
||||
|
||||
/* Define to 1 if `st_birthtimensec' is a member of `struct stat'. */
|
||||
/* Define to 1 if 'st_birthtimensec' is a member of 'struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC
|
||||
|
||||
/* Define to 1 if `st_birthtimespec.tv_nsec' is a member of `struct stat'. */
|
||||
/* Define to 1 if 'st_birthtimespec.tv_nsec' is a member of 'struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC
|
||||
|
||||
/* Define to 1 if `st_birthtim.tv_nsec' is a member of `struct stat'. */
|
||||
/* Define to 1 if 'st_birthtim.tv_nsec' is a member of 'struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC
|
||||
|
||||
/* Define to 1 if the system has the type `struct tms'. */
|
||||
/* Define to 1 if the system has the type 'struct tms'. */
|
||||
#undef HAVE_STRUCT_TMS
|
||||
|
||||
/* Define to 1 if you have the `strverscmp' function. */
|
||||
/* Define to 1 if you have the 'strverscmp' function. */
|
||||
#undef HAVE_STRVERSCMP
|
||||
|
||||
/* Define to 1 if you have the `symlink' function. */
|
||||
/* Define to 1 if you have the 'symlink' function. */
|
||||
#undef HAVE_SYMLINK
|
||||
|
||||
/* Define to 1 if you have the <sys/bitypes.h> header file. */
|
||||
@@ -1338,28 +1338,28 @@
|
||||
/* Define to 1 if you have the <sys/wait.h> header file. */
|
||||
#undef HAVE_SYS_WAIT_H
|
||||
|
||||
/* Define to 1 if you have the `tcdrain' function. */
|
||||
/* Define to 1 if you have the 'tcdrain' function. */
|
||||
#undef HAVE_TCDRAIN
|
||||
|
||||
/* Define to 1 if you have the <termios.h> header file. */
|
||||
#undef HAVE_TERMIOS_H
|
||||
|
||||
/* Define to 1 if you have the `thrd_create' function. */
|
||||
/* Define to 1 if you have the 'thrd_create' function. */
|
||||
#undef HAVE_THRD_CREATE
|
||||
|
||||
/* Define to 1 if you have the <threads.h> header file. */
|
||||
#undef HAVE_THREADS_H
|
||||
|
||||
/* Define to 1 if you have the `towlower' function. */
|
||||
/* Define to 1 if you have the 'towlower' function. */
|
||||
#undef HAVE_TOWLOWER
|
||||
|
||||
/* Define to 1 if you have the `tsearch' function. */
|
||||
/* Define to 1 if you have the 'tsearch' function. */
|
||||
#undef HAVE_TSEARCH
|
||||
|
||||
/* Define to 1 if you have the <unistd.h> header file. */
|
||||
#undef HAVE_UNISTD_H
|
||||
|
||||
/* Define to 1 if you have the `unsetenv' function. */
|
||||
/* Define to 1 if you have the 'unsetenv' function. */
|
||||
#undef HAVE_UNSETENV
|
||||
|
||||
/* Define to 1 if the system has the type 'unsigned long long int'. */
|
||||
@@ -1368,23 +1368,23 @@
|
||||
/* Define if you have a global __progname variable */
|
||||
#undef HAVE_VAR___PROGNAME
|
||||
|
||||
/* Define to 1 if you have the `vasnprintf' function. */
|
||||
/* Define to 1 if you have the 'vasnprintf' function. */
|
||||
#undef HAVE_VASNPRINTF
|
||||
|
||||
/* Define to 1 if you have the `vasprintf' function. */
|
||||
/* Define to 1 if you have the 'vasprintf' function. */
|
||||
#undef HAVE_VASPRINTF
|
||||
|
||||
/* Define to 1 if you have the `vfork' function. */
|
||||
/* Define to 1 if you have the 'vfork' function. */
|
||||
#undef HAVE_VFORK
|
||||
|
||||
/* Define to 1 or 0, depending whether the compiler supports simple visibility
|
||||
declarations. */
|
||||
#undef HAVE_VISIBILITY
|
||||
|
||||
/* Define to 1 if you have the `vsnprintf' function. */
|
||||
/* Define to 1 if you have the 'vsnprintf' function. */
|
||||
#undef HAVE_VSNPRINTF
|
||||
|
||||
/* Define to 1 if you have the `waitid' function. */
|
||||
/* Define to 1 if you have the 'waitid' function. */
|
||||
#undef HAVE_WAITID
|
||||
|
||||
/* Define to 1 if you have the <wchar.h> header file. */
|
||||
@@ -1393,19 +1393,19 @@
|
||||
/* Define if you have the 'wchar_t' type. */
|
||||
#undef HAVE_WCHAR_T
|
||||
|
||||
/* Define to 1 if you have the `wcrtomb' function. */
|
||||
/* Define to 1 if you have the 'wcrtomb' function. */
|
||||
#undef HAVE_WCRTOMB
|
||||
|
||||
/* Define to 1 if you have the `wcslen' function. */
|
||||
/* Define to 1 if you have the 'wcslen' function. */
|
||||
#undef HAVE_WCSLEN
|
||||
|
||||
/* Define to 1 if you have the `wcsnlen' function. */
|
||||
/* Define to 1 if you have the 'wcsnlen' function. */
|
||||
#undef HAVE_WCSNLEN
|
||||
|
||||
/* Define to 1 if you have the <wctype.h> header file. */
|
||||
#undef HAVE_WCTYPE_H
|
||||
|
||||
/* Define to 1 if you have the `wcwidth' function. */
|
||||
/* Define to 1 if you have the 'wcwidth' function. */
|
||||
#undef HAVE_WCWIDTH
|
||||
|
||||
/* Define to 1 if the compiler and linker support weak declarations of
|
||||
@@ -1431,13 +1431,13 @@
|
||||
/* Define to 1 if you have the <xlocale.h> header file. */
|
||||
#undef HAVE_XLOCALE_H
|
||||
|
||||
/* Define to 1 if the system has the type `_Bool'. */
|
||||
/* Define to 1 if the system has the type '_Bool'. */
|
||||
#undef HAVE__BOOL
|
||||
|
||||
/* Define to 1 if you have the `_NSGetExecutablePath' function. */
|
||||
/* Define to 1 if you have the '_NSGetExecutablePath' function. */
|
||||
#undef HAVE__NSGETEXECUTABLEPATH
|
||||
|
||||
/* Define to 1 if you have the `_set_invalid_parameter_handler' function. */
|
||||
/* Define to 1 if you have the '_set_invalid_parameter_handler' function. */
|
||||
#undef HAVE__SET_INVALID_PARAMETER_HANDLER
|
||||
|
||||
/* Define to 1 if the compiler supports __builtin_expect,
|
||||
@@ -1450,13 +1450,13 @@
|
||||
#endif
|
||||
|
||||
|
||||
/* Define to 1 if you have the `__fseterr' function. */
|
||||
/* Define to 1 if you have the '__fseterr' function. */
|
||||
#undef HAVE___FSETERR
|
||||
|
||||
/* Define to 1 if the compiler supports the keyword '__inline'. */
|
||||
#undef HAVE___INLINE
|
||||
|
||||
/* Define to 1 if you have the `__xpg_strerror_r' function. */
|
||||
/* Define to 1 if you have the '__xpg_strerror_r' function. */
|
||||
#undef HAVE___XPG_STRERROR_R
|
||||
|
||||
/* Define as const if the declaration of iconv() needs const. */
|
||||
@@ -1720,10 +1720,10 @@
|
||||
STACK_DIRECTION = 0 => direction of growth unknown */
|
||||
#undef STACK_DIRECTION
|
||||
|
||||
/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
|
||||
/* Define to 1 if the 'S_IS*' macros in <sys/stat.h> do not work properly. */
|
||||
#undef STAT_MACROS_BROKEN
|
||||
|
||||
/* Define to 1 if all of the C90 standard headers exist (not just the ones
|
||||
/* Define to 1 if all of the C89 standard headers exist (not just the ones
|
||||
required in a freestanding environment). This macro is provided for
|
||||
backward compatibility; new code need not use it. */
|
||||
#undef STDC_HEADERS
|
||||
@@ -1760,10 +1760,14 @@
|
||||
weak. */
|
||||
#undef USE_POSIX_THREADS_WEAK
|
||||
|
||||
/* Enable extensions on AIX 3, Interix. */
|
||||
/* Enable extensions on AIX, Interix, z/OS. */
|
||||
#ifndef _ALL_SOURCE
|
||||
# undef _ALL_SOURCE
|
||||
#endif
|
||||
/* Enable extensions on Cosmopolitan Libc. */
|
||||
#ifndef _COSMO_SOURCE
|
||||
# undef _COSMO_SOURCE
|
||||
#endif
|
||||
/* Enable general extensions on macOS. */
|
||||
#ifndef _DARWIN_C_SOURCE
|
||||
# undef _DARWIN_C_SOURCE
|
||||
@@ -1821,11 +1825,15 @@
|
||||
#ifndef __STDC_WANT_IEC_60559_DFP_EXT__
|
||||
# undef __STDC_WANT_IEC_60559_DFP_EXT__
|
||||
#endif
|
||||
/* Enable extensions specified by C23 Annex F. */
|
||||
#ifndef __STDC_WANT_IEC_60559_EXT__
|
||||
# undef __STDC_WANT_IEC_60559_EXT__
|
||||
#endif
|
||||
/* Enable extensions specified by ISO/IEC TS 18661-4:2015. */
|
||||
#ifndef __STDC_WANT_IEC_60559_FUNCS_EXT__
|
||||
# undef __STDC_WANT_IEC_60559_FUNCS_EXT__
|
||||
#endif
|
||||
/* Enable extensions specified by ISO/IEC TS 18661-3:2015. */
|
||||
/* Enable extensions specified by C23 Annex H and ISO/IEC TS 18661-3:2015. */
|
||||
#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__
|
||||
# undef __STDC_WANT_IEC_60559_TYPES_EXT__
|
||||
#endif
|
||||
@@ -2455,10 +2463,10 @@
|
||||
# define _GL_INLINE_HEADER_END
|
||||
#endif
|
||||
|
||||
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||
/* Define as 'int' if <sys/types.h> doesn't define. */
|
||||
#undef gid_t
|
||||
|
||||
/* Define to `__inline__' or `__inline' if that's what the C compiler
|
||||
/* Define to '__inline__' or '__inline' if that's what the C compiler
|
||||
calls it, or to nothing if 'inline' is not supported under any name. */
|
||||
#ifndef __cplusplus
|
||||
#undef inline
|
||||
@@ -2496,7 +2504,7 @@
|
||||
#define _GL_CMP(n1, n2) (((n1) > (n2)) - ((n1) < (n2)))
|
||||
|
||||
|
||||
/* Define to `int' if <sys/types.h> does not define. */
|
||||
/* Define to 'int' if <sys/types.h> does not define. */
|
||||
#undef mode_t
|
||||
|
||||
/* Define to the type of st_nlink in struct stat, or a supertype. */
|
||||
@@ -2528,13 +2536,13 @@
|
||||
accessed atomically even in the presence of asynchronous signals. */
|
||||
#undef sig_atomic_t
|
||||
|
||||
/* Define to `unsigned int' if <sys/types.h> does not define. */
|
||||
/* Define as 'unsigned int' if <stddef.h> doesn't define. */
|
||||
#undef size_t
|
||||
|
||||
/* Define as a signed type of the same size as size_t. */
|
||||
#undef ssize_t
|
||||
|
||||
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||
/* Define as 'int' if <sys/types.h> doesn't define. */
|
||||
#undef uid_t
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
This is flex.info, produced by makeinfo version 6.1 from flex.texi.
|
||||
This is flex.info, produced by makeinfo version 7.3 from flex.texi.
|
||||
|
||||
The flex manual is placed under the same licensing conditions as the
|
||||
rest of flex:
|
||||
|
||||
Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2012 The Flex
|
||||
Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2012 The Flex
|
||||
Project.
|
||||
|
||||
Copyright (C) 1990, 1997 The Regents of the University of California.
|
||||
Copyright © 1990, 1997 The Regents of the University of California.
|
||||
All rights reserved.
|
||||
|
||||
This code is derived from software contributed to Berkeley by Vern
|
||||
@@ -42,240 +42,245 @@ END-INFO-DIR-ENTRY
|
||||
|
||||
|
||||
Indirect:
|
||||
flex.info-1: 1622
|
||||
flex.info-2: 318745
|
||||
flex.info-1: 1620
|
||||
flex.info-2: 324917
|
||||
|
||||
Tag Table:
|
||||
(Indirect)
|
||||
Node: Top1622
|
||||
Node: Copyright9414
|
||||
Node: Reporting Bugs10933
|
||||
Node: Introduction11189
|
||||
Node: Simple Examples12018
|
||||
Node: Format15304
|
||||
Node: Definitions Section15759
|
||||
Ref: Definitions Section-Footnote-118017
|
||||
Node: Rules Section18085
|
||||
Node: User Code Section19243
|
||||
Node: Comments in the Input19681
|
||||
Node: Patterns21051
|
||||
Ref: case and character ranges27883
|
||||
Node: Matching31886
|
||||
Node: Actions35171
|
||||
Node: Generated Scanner44133
|
||||
Node: Start Conditions49136
|
||||
Node: Multiple Input Buffers59678
|
||||
Ref: Scanning Strings66221
|
||||
Node: EOF67850
|
||||
Node: Misc Macros69436
|
||||
Node: User Values72290
|
||||
Node: Yacc74615
|
||||
Node: Scanner Options75510
|
||||
Node: Options for Specifying Filenames78299
|
||||
Ref: option-header78525
|
||||
Ref: option-outfile79239
|
||||
Ref: option-stdout79564
|
||||
Node: Options Affecting Scanner Behavior80547
|
||||
Ref: option-case-insensitive80788
|
||||
Ref: option-lex-compat81221
|
||||
Ref: option-batch81753
|
||||
Ref: option-interactive82272
|
||||
Ref: option-7bit83626
|
||||
Ref: option-8bit84930
|
||||
Ref: option-default85342
|
||||
Ref: option-always-interactive85406
|
||||
Ref: option-posix86010
|
||||
Ref: option-stack87157
|
||||
Ref: option-stdinit87265
|
||||
Ref: option-yylineno87744
|
||||
Ref: option-yywrap88187
|
||||
Node: Code-Level And API Options88454
|
||||
Ref: option-ansi-definitions88681
|
||||
Ref: option-ansi-prototypes88756
|
||||
Ref: option-bison-bridge88829
|
||||
Ref: option-bison-locations89170
|
||||
Ref: option-noline89430
|
||||
Ref: option-reentrant89944
|
||||
Ref: option-c++90556
|
||||
Ref: option-array90682
|
||||
Ref: option-pointer90780
|
||||
Ref: option-prefix90907
|
||||
Ref: option-main92435
|
||||
Ref: option-nounistd92619
|
||||
Ref: option-yyclass93130
|
||||
Node: Options for Scanner Speed and Size93614
|
||||
Ref: option-align94164
|
||||
Ref: option-ecs94666
|
||||
Ref: option-meta-ecs95705
|
||||
Ref: option-read96193
|
||||
Ref: option-full98076
|
||||
Ref: option-fast98271
|
||||
Node: Debugging Options99195
|
||||
Ref: option-backup99382
|
||||
Ref: option-debug99927
|
||||
Ref: option-perf-report100649
|
||||
Ref: option-nodefault101275
|
||||
Ref: option-trace101593
|
||||
Ref: option-nowarn101884
|
||||
Ref: option-verbose101952
|
||||
Ref: option-warn102381
|
||||
Node: Miscellaneous Options102600
|
||||
Node: Performance103056
|
||||
Node: Cxx113303
|
||||
Node: Reentrant121395
|
||||
Node: Reentrant Uses122129
|
||||
Node: Reentrant Overview123691
|
||||
Node: Reentrant Example124491
|
||||
Node: Reentrant Detail125264
|
||||
Node: Specify Reentrant125768
|
||||
Node: Extra Reentrant Argument126418
|
||||
Node: Global Replacement127670
|
||||
Node: Init and Destroy Functions128905
|
||||
Node: Accessor Methods131426
|
||||
Node: Extra Data132773
|
||||
Node: About yyscan_t135040
|
||||
Node: Reentrant Functions135437
|
||||
Ref: bison-functions136921
|
||||
Node: Lex and Posix137660
|
||||
Node: Memory Management145007
|
||||
Ref: memory-management145153
|
||||
Node: The Default Memory Management145387
|
||||
Ref: The Default Memory Management-Footnote-1149207
|
||||
Node: Overriding The Default Memory Management149360
|
||||
Ref: Overriding The Default Memory Management-Footnote-1151774
|
||||
Node: A Note About yytext And Memory151938
|
||||
Node: Serialized Tables153178
|
||||
Ref: serialization153322
|
||||
Node: Creating Serialized Tables154102
|
||||
Node: Loading and Unloading Serialized Tables155717
|
||||
Node: Tables File Format157490
|
||||
Node: Diagnostics164515
|
||||
Node: Limitations167924
|
||||
Node: Bibliography169872
|
||||
Node: FAQ170542
|
||||
Node: When was flex born?175705
|
||||
Node: How do I expand backslash-escape sequences in C-style quoted strings?176082
|
||||
Node: Why do flex scanners call fileno if it is not ANSI compatible?177385
|
||||
Node: Does flex support recursive pattern definitions?178182
|
||||
Node: How do I skip huge chunks of input (tens of megabytes) while using flex?179029
|
||||
Node: Flex is not matching my patterns in the same order that I defined them.179496
|
||||
Node: My actions are executing out of order or sometimes not at all.181242
|
||||
Node: How can I have multiple input sources feed into the same scanner at the same time?182015
|
||||
Node: Can I build nested parsers that work with the same input file?184000
|
||||
Node: How can I match text only at the end of a file?185007
|
||||
Node: How can I make REJECT cascade across start condition boundaries?185811
|
||||
Node: Why cant I use fast or full tables with interactive mode?186825
|
||||
Node: How much faster is -F or -f than -C?188082
|
||||
Node: If I have a simple grammar cant I just parse it with flex?188394
|
||||
Node: Why doesn't yyrestart() set the start state back to INITIAL?188876
|
||||
Node: How can I match C-style comments?189503
|
||||
Node: The period isn't working the way I expected.190313
|
||||
Node: Can I get the flex manual in another format?191558
|
||||
Node: Does there exist a "faster" NDFA->DFA algorithm?192048
|
||||
Node: How does flex compile the DFA so quickly?192558
|
||||
Node: How can I use more than 8192 rules?193524
|
||||
Node: How do I abandon a file in the middle of a scan and switch to a new file?194934
|
||||
Node: How do I execute code only during initialization (only before the first scan)?195488
|
||||
Node: How do I execute code at termination?196265
|
||||
Node: Where else can I find help?196591
|
||||
Node: Can I include comments in the "rules" section of the file?196965
|
||||
Node: I get an error about undefined yywrap().197345
|
||||
Node: How can I change the matching pattern at run time?197821
|
||||
Node: How can I expand macros in the input?198183
|
||||
Node: How can I build a two-pass scanner?199215
|
||||
Node: How do I match any string not matched in the preceding rules?200133
|
||||
Node: I am trying to port code from AT&T lex that uses yysptr and yysbuf.201042
|
||||
Node: Is there a way to make flex treat NULL like a regular character?201837
|
||||
Node: Whenever flex can not match the input it says "flex scanner jammed".202357
|
||||
Node: Why doesn't flex have non-greedy operators like perl does?203000
|
||||
Node: Memory leak - 16386 bytes allocated by malloc.204353
|
||||
Ref: faq-memory-leak204651
|
||||
Node: How do I track the byte offset for lseek()?205622
|
||||
Node: How do I use my own I/O classes in a C++ scanner?207131
|
||||
Node: How do I skip as many chars as possible?207974
|
||||
Node: deleteme00209049
|
||||
Node: Are certain equivalent patterns faster than others?209489
|
||||
Node: Is backing up a big deal?212907
|
||||
Node: Can I fake multi-byte character support?214813
|
||||
Node: deleteme01216254
|
||||
Node: Can you discuss some flex internals?217363
|
||||
Node: unput() messes up yy_at_bol219607
|
||||
Node: The | operator is not doing what I want220709
|
||||
Node: Why can't flex understand this variable trailing context pattern?222255
|
||||
Node: The ^ operator isn't working223504
|
||||
Node: Trailing context is getting confused with trailing optional patterns224739
|
||||
Node: Is flex GNU or not?225982
|
||||
Node: ERASEME53227655
|
||||
Node: I need to scan if-then-else blocks and while loops228425
|
||||
Node: ERASEME55229624
|
||||
Node: ERASEME56230722
|
||||
Node: ERASEME57232080
|
||||
Node: Is there a repository for flex scanners?233078
|
||||
Node: How can I conditionally compile or preprocess my flex input file?233394
|
||||
Node: Where can I find grammars for lex and yacc?233867
|
||||
Node: I get an end-of-buffer message for each character scanned.234214
|
||||
Node: unnamed-faq-62234809
|
||||
Node: unnamed-faq-63235827
|
||||
Node: unnamed-faq-64237124
|
||||
Node: unnamed-faq-65238090
|
||||
Node: unnamed-faq-66238876
|
||||
Node: unnamed-faq-67239991
|
||||
Node: unnamed-faq-68240978
|
||||
Node: unnamed-faq-69242120
|
||||
Node: unnamed-faq-70242833
|
||||
Node: unnamed-faq-71243594
|
||||
Node: unnamed-faq-72244803
|
||||
Node: unnamed-faq-73245846
|
||||
Node: unnamed-faq-74246770
|
||||
Node: unnamed-faq-75247715
|
||||
Node: unnamed-faq-76248847
|
||||
Node: unnamed-faq-77249553
|
||||
Node: unnamed-faq-78250446
|
||||
Node: unnamed-faq-79251444
|
||||
Node: unnamed-faq-80253144
|
||||
Node: unnamed-faq-81254462
|
||||
Node: unnamed-faq-82257262
|
||||
Node: unnamed-faq-83258219
|
||||
Node: unnamed-faq-84259999
|
||||
Node: unnamed-faq-85261102
|
||||
Node: unnamed-faq-86262109
|
||||
Node: unnamed-faq-87263047
|
||||
Node: unnamed-faq-88263693
|
||||
Node: unnamed-faq-90264524
|
||||
Node: unnamed-faq-91265787
|
||||
Node: unnamed-faq-92268215
|
||||
Node: unnamed-faq-93268714
|
||||
Node: unnamed-faq-94269641
|
||||
Node: unnamed-faq-95271053
|
||||
Node: unnamed-faq-96272571
|
||||
Node: unnamed-faq-97273330
|
||||
Node: unnamed-faq-98273997
|
||||
Node: unnamed-faq-99274662
|
||||
Node: unnamed-faq-100275591
|
||||
Node: unnamed-faq-101276301
|
||||
Node: What is the difference between YYLEX_PARAM and YY_DECL?277114
|
||||
Node: Why do I get "conflicting types for yylex" error?277638
|
||||
Node: How do I access the values set in a Flex action from within a Bison action?278168
|
||||
Node: Appendices278597
|
||||
Node: Makefiles and Flex278862
|
||||
Ref: Makefiles and Flex-Footnote-1282064
|
||||
Ref: Makefiles and Flex-Footnote-2282181
|
||||
Ref: Makefiles and Flex-Footnote-3282368
|
||||
Node: Bison Bridge282419
|
||||
Ref: Bison Bridge-Footnote-1285086
|
||||
Node: M4 Dependency285278
|
||||
Ref: M4 Dependency-Footnote-1286692
|
||||
Node: Common Patterns286828
|
||||
Node: Numbers287151
|
||||
Node: Identifiers288127
|
||||
Node: Quoted Constructs288954
|
||||
Node: Addresses290008
|
||||
Node: Indices291320
|
||||
Node: Concept Index291612
|
||||
Node: Index of Functions and Macros318745
|
||||
Node: Index of Variables323714
|
||||
Node: Index of Data Types325380
|
||||
Node: Index of Hooks326268
|
||||
Node: Index of Scanner Options326836
|
||||
Node: Top1620
|
||||
Node: Copyright7694
|
||||
Node: Reporting Bugs9211
|
||||
Node: Introduction9471
|
||||
Node: Simple Examples10328
|
||||
Node: Format13771
|
||||
Node: Definitions Section14192
|
||||
Ref: Definitions Section-Footnote-116550
|
||||
Node: Rules Section16626
|
||||
Node: User Code Section17800
|
||||
Node: Comments in the Input18246
|
||||
Node: Patterns19651
|
||||
Ref: case and character ranges26973
|
||||
Node: Matching31202
|
||||
Node: Actions34639
|
||||
Node: Generated Scanner43962
|
||||
Node: Start Conditions49185
|
||||
Node: Multiple Input Buffers60101
|
||||
Ref: Scanning Strings66886
|
||||
Node: EOF68579
|
||||
Node: Misc Macros70215
|
||||
Node: User Values73173
|
||||
Node: Yacc75614
|
||||
Node: Scanner Options76575
|
||||
Node: Options for Specifying Filenames79407
|
||||
Ref: option-header79633
|
||||
Ref: option-outfile80379
|
||||
Ref: option-stdout80744
|
||||
Node: Options Affecting Scanner Behavior81763
|
||||
Ref: option-case-insensitive82004
|
||||
Ref: option-lex-compat82461
|
||||
Ref: option-batch83033
|
||||
Ref: option-interactive83588
|
||||
Ref: option-7bit84990
|
||||
Ref: option-8bit86358
|
||||
Ref: option-default86798
|
||||
Ref: option-always-interactive86870
|
||||
Ref: option-posix87498
|
||||
Ref: option-stack88711
|
||||
Ref: option-stdinit88827
|
||||
Ref: option-yylineno89350
|
||||
Ref: option-yywrap89829
|
||||
Node: Code-Level And API Options90120
|
||||
Ref: option-ansi-definitions90347
|
||||
Ref: option-ansi-prototypes90430
|
||||
Ref: option-bison-bridge90511
|
||||
Ref: option-bison-locations90876
|
||||
Ref: option-noline91164
|
||||
Ref: option-reentrant91714
|
||||
Ref: option-c++92346
|
||||
Ref: option-array92480
|
||||
Ref: option-pointer92586
|
||||
Ref: option-prefix92733
|
||||
Ref: option-main94325
|
||||
Ref: option-nounistd94529
|
||||
Ref: option-yyclass95068
|
||||
Node: Options for Scanner Speed and Size95596
|
||||
Ref: option-align96158
|
||||
Ref: option-ecs96668
|
||||
Ref: option-meta-ecs97751
|
||||
Ref: option-read98259
|
||||
Ref: option-full100222
|
||||
Ref: option-fast100437
|
||||
Node: Debugging Options101389
|
||||
Ref: option-backup101576
|
||||
Ref: option-debug102145
|
||||
Ref: option-perf-report102887
|
||||
Ref: option-nodefault103545
|
||||
Ref: option-trace103875
|
||||
Ref: option-nowarn104190
|
||||
Ref: option-verbose104266
|
||||
Ref: option-warn104723
|
||||
Node: Miscellaneous Options104950
|
||||
Node: Performance105434
|
||||
Node: Cxx115909
|
||||
Node: Reentrant124503
|
||||
Node: Reentrant Uses125197
|
||||
Node: Reentrant Overview126812
|
||||
Node: Reentrant Example127650
|
||||
Node: Reentrant Detail128458
|
||||
Node: Specify Reentrant128895
|
||||
Node: Extra Reentrant Argument129557
|
||||
Node: Global Replacement130869
|
||||
Node: Init and Destroy Functions132160
|
||||
Node: Accessor Methods134796
|
||||
Node: Extra Data136183
|
||||
Node: About yyscan_t138510
|
||||
Node: Reentrant Functions138919
|
||||
Ref: bison-functions140420
|
||||
Node: Lex and Posix141191
|
||||
Node: Memory Management149030
|
||||
Ref: memory-management149176
|
||||
Node: The Default Memory Management149404
|
||||
Ref: The Default Memory Management-Footnote-1153240
|
||||
Node: Overriding The Default Memory Management153393
|
||||
Ref: Overriding The Default Memory Management-Footnote-1155904
|
||||
Node: A Note About yytext And Memory156080
|
||||
Node: Serialized Tables157324
|
||||
Ref: serialization157468
|
||||
Node: Creating Serialized Tables158238
|
||||
Node: Loading and Unloading Serialized Tables159885
|
||||
Node: Tables File Format161694
|
||||
Node: Diagnostics169019
|
||||
Node: Limitations172584
|
||||
Node: Bibliography174608
|
||||
Node: FAQ175290
|
||||
Node: When was flex born?179534
|
||||
Node: How do I expand backslash-escape sequences in C-style quoted strings?179915
|
||||
Node: Why do flex scanners call fileno if it is not ANSI compatible?181230
|
||||
Node: Does flex support recursive pattern definitions?182071
|
||||
Node: How do I skip huge chunks of input (tens of megabytes) while using flex?182922
|
||||
Node: Flex is not matching my patterns in the same order that I defined them.183401
|
||||
Node: My actions are executing out of order or sometimes not at all.185187
|
||||
Node: How can I have multiple input sources feed into the same scanner at the same time?185982
|
||||
Node: Can I build nested parsers that work with the same input file?188033
|
||||
Node: How can I match text only at the end of a file?189060
|
||||
Node: How can I make REJECT cascade across start condition boundaries?189876
|
||||
Node: Why cant I use fast or full tables with interactive mode?190902
|
||||
Node: How much faster is -F or -f than -C?192159
|
||||
Node: If I have a simple grammar cant I just parse it with flex?192471
|
||||
Node: Why doesn't yyrestart() set the start state back to INITIAL?192953
|
||||
Node: How can I match C-style comments?193588
|
||||
Node: The period isn't working the way I expected.194398
|
||||
Node: Can I get the flex manual in another format?195735
|
||||
Node: Does there exist a "faster" NDFA->DFA algorithm?196233
|
||||
Node: How does flex compile the DFA so quickly?196743
|
||||
Node: How can I use more than 8192 rules?197713
|
||||
Node: How do I abandon a file in the middle of a scan and switch to a new file?199135
|
||||
Node: How do I execute code only during initialization (only before the first scan)?199701
|
||||
Node: How do I execute code at termination?200491
|
||||
Node: Where else can I find help?200821
|
||||
Node: Can I include comments in the "rules" section of the file?201195
|
||||
Node: I get an error about undefined yywrap().201575
|
||||
Node: How can I change the matching pattern at run time?202063
|
||||
Node: How can I expand macros in the input?202425
|
||||
Node: How can I build a two-pass scanner?203462
|
||||
Node: How do I match any string not matched in the preceding rules?204380
|
||||
Node: I am trying to port code from AT&T lex that uses yysptr and yysbuf.205301
|
||||
Node: Is there a way to make flex treat NULL like a regular character?206120
|
||||
Node: Whenever flex can not match the input it says "flex scanner jammed".206652
|
||||
Node: Why doesn't flex have non-greedy operators like perl does?207304
|
||||
Node: Memory leak - 16386 bytes allocated by malloc.208669
|
||||
Ref: faq-memory-leak208967
|
||||
Node: How do I track the byte offset for lseek()?209966
|
||||
Node: How do I use my own I/O classes in a C++ scanner?211523
|
||||
Node: How do I skip as many chars as possible?212386
|
||||
Node: deleteme00213461
|
||||
Node: Are certain equivalent patterns faster than others?213906
|
||||
Node: Is backing up a big deal?217394
|
||||
Node: Can I fake multi-byte character support?219365
|
||||
Node: deleteme01220841
|
||||
Node: Can you discuss some flex internals?221965
|
||||
Node: unput() messes up yy_at_bol224254
|
||||
Node: The | operator is not doing what I want225391
|
||||
Node: Why can't flex understand this variable trailing context pattern?226982
|
||||
Node: The ^ operator isn't working228246
|
||||
Node: Trailing context is getting confused with trailing optional patterns229516
|
||||
Node: Is flex GNU or not?230784
|
||||
Node: ERASEME53232497
|
||||
Node: I need to scan if-then-else blocks and while loops233292
|
||||
Node: ERASEME55234511
|
||||
Node: ERASEME56235624
|
||||
Node: ERASEME57237017
|
||||
Node: Is there a repository for flex scanners?238050
|
||||
Node: How can I conditionally compile or preprocess my flex input file?238366
|
||||
Node: Where can I find grammars for lex and yacc?238839
|
||||
Node: I get an end-of-buffer message for each character scanned.239186
|
||||
Node: unnamed-faq-62239781
|
||||
Node: unnamed-faq-63240829
|
||||
Node: unnamed-faq-64242141
|
||||
Node: unnamed-faq-65243142
|
||||
Node: unnamed-faq-66243943
|
||||
Node: unnamed-faq-67245073
|
||||
Node: unnamed-faq-68246075
|
||||
Node: unnamed-faq-69247232
|
||||
Node: unnamed-faq-70247965
|
||||
Node: unnamed-faq-71248741
|
||||
Node: unnamed-faq-72249970
|
||||
Node: unnamed-faq-73251038
|
||||
Node: unnamed-faq-74251982
|
||||
Node: unnamed-faq-75252952
|
||||
Node: unnamed-faq-76254124
|
||||
Node: unnamed-faq-77254845
|
||||
Node: unnamed-faq-78255753
|
||||
Node: unnamed-faq-79256766
|
||||
Node: unnamed-faq-80258501
|
||||
Node: unnamed-faq-81259844
|
||||
Node: unnamed-faq-82262684
|
||||
Node: unnamed-faq-83263666
|
||||
Node: unnamed-faq-84265471
|
||||
Node: unnamed-faq-85266589
|
||||
Node: unnamed-faq-86267636
|
||||
Node: unnamed-faq-87268609
|
||||
Node: unnamed-faq-88269270
|
||||
Node: unnamed-faq-90270126
|
||||
Node: unnamed-faq-91271424
|
||||
Node: unnamed-faq-92273907
|
||||
Node: unnamed-faq-93274421
|
||||
Node: unnamed-faq-94275363
|
||||
Node: unnamed-faq-95276805
|
||||
Node: unnamed-faq-96278338
|
||||
Node: unnamed-faq-97279122
|
||||
Node: unnamed-faq-98279804
|
||||
Node: unnamed-faq-99280494
|
||||
Node: unnamed-faq-100281453
|
||||
Node: unnamed-faq-101282178
|
||||
Node: What is the difference between YYLEX_PARAM and YY_DECL?283011
|
||||
Node: Why do I get "conflicting types for yylex" error?283535
|
||||
Node: How do I access the values set in a Flex action from within a Bison action?284065
|
||||
Node: Appendices284494
|
||||
Node: Makefiles and Flex284703
|
||||
Ref: Makefiles and Flex-Footnote-1288049
|
||||
Ref: Makefiles and Flex-Footnote-2288174
|
||||
Ref: Makefiles and Flex-Footnote-3288365
|
||||
Node: Bison Bridge288416
|
||||
Ref: Bison Bridge-Footnote-1291217
|
||||
Node: M4 Dependency291409
|
||||
Ref: M4 Dependency-Footnote-1292903
|
||||
Node: Common Patterns293039
|
||||
Node: Numbers293330
|
||||
Node: Identifiers294323
|
||||
Node: Quoted Constructs295154
|
||||
Node: Addresses296228
|
||||
Node: Indices297548
|
||||
Node: Concept Index297786
|
||||
Node: Index of Functions and Macros324917
|
||||
Node: Index of Variables329886
|
||||
Node: Index of Data Types331552
|
||||
Node: Index of Hooks332440
|
||||
Node: Index of Scanner Options333008
|
||||
|
||||
End Tag Table
|
||||
|
||||
|
||||
Local Variables:
|
||||
coding: utf-8
|
||||
End:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
@set UPDATED 6 May 2017
|
||||
@set UPDATED-MONTH May 2017
|
||||
@set UPDATED 15 May 2026
|
||||
@set UPDATED-MONTH May 2026
|
||||
@set EDITION 2.6.4
|
||||
@set VERSION 2.6.4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@set UPDATED 6 May 2017
|
||||
@set UPDATED-MONTH May 2017
|
||||
@set UPDATED 15 May 2026
|
||||
@set UPDATED-MONTH May 2026
|
||||
@set EDITION 2.6.4
|
||||
@set VERSION 2.6.4
|
||||
|
||||
@@ -1,30 +1,18 @@
|
||||
/* src/config.h.in. Generated from configure.ac by autoheader. */
|
||||
|
||||
/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
|
||||
systems. This function is required for `alloca.c' support on those systems.
|
||||
*/
|
||||
#undef CRAY_STACKSEG_END
|
||||
|
||||
/* Define to 1 if using `alloca.c'. */
|
||||
/* Define to 1 if using 'alloca.c'. */
|
||||
#undef C_ALLOCA
|
||||
|
||||
/* Define to 1 if translation of program messages to the user's native
|
||||
language is requested. */
|
||||
#undef ENABLE_NLS
|
||||
|
||||
/* Define to 1 if you have `alloca', as a function or macro. */
|
||||
/* Define to 1 if you have 'alloca', as a function or macro. */
|
||||
#undef HAVE_ALLOCA
|
||||
|
||||
/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
|
||||
*/
|
||||
/* Define to 1 if <alloca.h> works. */
|
||||
#undef HAVE_ALLOCA_H
|
||||
|
||||
/* Define to 1 if you have the `available.' function. */
|
||||
#undef HAVE_AVAILABLE_
|
||||
|
||||
/* Define to 1 if you have the `by' function. */
|
||||
#undef HAVE_BY
|
||||
|
||||
/* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the
|
||||
CoreFoundation framework. */
|
||||
#undef HAVE_CFLOCALECOPYCURRENT
|
||||
@@ -40,43 +28,25 @@
|
||||
/* Define to 1 if you have the <dlfcn.h> header file. */
|
||||
#undef HAVE_DLFCN_H
|
||||
|
||||
/* Define to 1 if you have the `dnl' function. */
|
||||
#undef HAVE_DNL
|
||||
|
||||
/* Define to 1 if you have the `dup2' function. */
|
||||
/* Define to 1 if you have the 'dup2' function. */
|
||||
#undef HAVE_DUP2
|
||||
|
||||
/* Define to 1 if you have the `enabled' function. */
|
||||
#undef HAVE_ENABLED
|
||||
|
||||
/* Define to 1 if you have the `fork' function. */
|
||||
/* Define to 1 if you have the 'fork' function. */
|
||||
#undef HAVE_FORK
|
||||
|
||||
/* Define to 1 if you have the `function.' function. */
|
||||
#undef HAVE_FUNCTION_
|
||||
|
||||
/* Define if the GNU gettext() function is already present or preinstalled. */
|
||||
#undef HAVE_GETTEXT
|
||||
|
||||
/* Define to 1 if you have the `have' function. */
|
||||
#undef HAVE_HAVE
|
||||
|
||||
/* Define if you have the iconv() function and it works. */
|
||||
#undef HAVE_ICONV
|
||||
|
||||
/* Define to 1 if you have the `if' function. */
|
||||
#undef HAVE_IF
|
||||
|
||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
||||
#undef HAVE_INTTYPES_H
|
||||
|
||||
/* Define to 1 if you have the `is' function. */
|
||||
#undef HAVE_IS
|
||||
|
||||
/* Define to 1 if you have the <libintl.h> header file. */
|
||||
#undef HAVE_LIBINTL_H
|
||||
|
||||
/* Define to 1 if you have the `m' library (-lm). */
|
||||
/* Define to 1 if you have the 'm' library (-lm). */
|
||||
#undef HAVE_LIBM
|
||||
|
||||
/* Define to 1 if you have the <limits.h> header file. */
|
||||
@@ -85,60 +55,39 @@
|
||||
/* Define to 1 if you have the <locale.h> header file. */
|
||||
#undef HAVE_LOCALE_H
|
||||
|
||||
/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
|
||||
/* Define to 1 if your system has a GNU libc compatible 'malloc' function, and
|
||||
to 0 otherwise. */
|
||||
#undef HAVE_MALLOC
|
||||
|
||||
/* Define to 1 if you have the <malloc.h> header file. */
|
||||
#undef HAVE_MALLOC_H
|
||||
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
#undef HAVE_MEMORY_H
|
||||
|
||||
/* Define to 1 if you have the `memset' function. */
|
||||
/* Define to 1 if you have the 'memset' function. */
|
||||
#undef HAVE_MEMSET
|
||||
|
||||
/* Define to 1 if you have the `Needed' function. */
|
||||
#undef HAVE_NEEDED
|
||||
|
||||
/* Define to 1 if you have the <netinet/in.h> header file. */
|
||||
#undef HAVE_NETINET_IN_H
|
||||
|
||||
/* Define to 1 if you have the `NLS' function. */
|
||||
#undef HAVE_NLS
|
||||
|
||||
/* Define to 1 if you have the `not' function. */
|
||||
#undef HAVE_NOT
|
||||
|
||||
/* Define to 1 if you have the `only' function. */
|
||||
#undef HAVE_ONLY
|
||||
|
||||
/* Define to 1 if you have the `OpenBSD' function. */
|
||||
#undef HAVE_OPENBSD
|
||||
|
||||
/* Define to 1 if you have the `pow' function. */
|
||||
/* Define to 1 if you have the 'pow' function. */
|
||||
#undef HAVE_POW
|
||||
|
||||
/* Define to 1 if you have the <pthread.h> header file. */
|
||||
#undef HAVE_PTHREAD_H
|
||||
|
||||
/* Define to 1 if your system has a GNU libc compatible `realloc' function,
|
||||
/* Define to 1 if your system has a GNU libc compatible 'realloc' function,
|
||||
and to 0 otherwise. */
|
||||
#undef HAVE_REALLOC
|
||||
|
||||
/* Define to 1 if you have the `reallocarray' function. */
|
||||
/* Define to 1 if you have the 'reallocarray' function. */
|
||||
#undef HAVE_REALLOCARRAY
|
||||
|
||||
/* Define to 1 if you have the `regcomp' function. */
|
||||
/* Define to 1 if you have the 'regcomp' function. */
|
||||
#undef HAVE_REGCOMP
|
||||
|
||||
/* Define to 1 if you have the <regex.h> header file. */
|
||||
#undef HAVE_REGEX_H
|
||||
|
||||
/* Define to 1 if you have the `replacement' function. */
|
||||
#undef HAVE_REPLACEMENT
|
||||
|
||||
/* Define to 1 if you have the `setlocale' function. */
|
||||
/* Define to 1 if you have the 'setlocale' function. */
|
||||
#undef HAVE_SETLOCALE
|
||||
|
||||
/* Define to 1 if stdbool.h conforms to C99. */
|
||||
@@ -147,16 +96,19 @@
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
#undef HAVE_STDINT_H
|
||||
|
||||
/* Define to 1 if you have the <stdio.h> header file. */
|
||||
#undef HAVE_STDIO_H
|
||||
|
||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
||||
#undef HAVE_STDLIB_H
|
||||
|
||||
/* Define to 1 if you have the `strcasecmp' function. */
|
||||
/* Define to 1 if you have the 'strcasecmp' function. */
|
||||
#undef HAVE_STRCASECMP
|
||||
|
||||
/* Define to 1 if you have the `strchr' function. */
|
||||
/* Define to 1 if you have the 'strchr' function. */
|
||||
#undef HAVE_STRCHR
|
||||
|
||||
/* Define to 1 if you have the `strdup' function. */
|
||||
/* Define to 1 if you have the 'strdup' function. */
|
||||
#undef HAVE_STRDUP
|
||||
|
||||
/* Define to 1 if you have the <strings.h> header file. */
|
||||
@@ -165,7 +117,7 @@
|
||||
/* Define to 1 if you have the <string.h> header file. */
|
||||
#undef HAVE_STRING_H
|
||||
|
||||
/* Define to 1 if you have the `strtol' function. */
|
||||
/* Define to 1 if you have the 'strtol' function. */
|
||||
#undef HAVE_STRTOL
|
||||
|
||||
/* Define to 1 if you have the <sys/stat.h> header file. */
|
||||
@@ -180,25 +132,19 @@
|
||||
/* Define to 1 if you have the <unistd.h> header file. */
|
||||
#undef HAVE_UNISTD_H
|
||||
|
||||
/* Define to 1 if you have the `Used' function. */
|
||||
#undef HAVE_USED
|
||||
|
||||
/* Define to 1 if you have the `vfork' function. */
|
||||
/* Define to 1 if you have the 'vfork' function. */
|
||||
#undef HAVE_VFORK
|
||||
|
||||
/* Define to 1 if you have the <vfork.h> header file. */
|
||||
#undef HAVE_VFORK_H
|
||||
|
||||
/* Define to 1 if you have the `We' function. */
|
||||
#undef HAVE_WE
|
||||
|
||||
/* Define to 1 if `fork' works. */
|
||||
/* Define to 1 if 'fork' works. */
|
||||
#undef HAVE_WORKING_FORK
|
||||
|
||||
/* Define to 1 if `vfork' works. */
|
||||
/* Define to 1 if 'vfork' works. */
|
||||
#undef HAVE_WORKING_VFORK
|
||||
|
||||
/* Define to 1 if the system has the type `_Bool'. */
|
||||
/* Define to 1 if the system has the type '_Bool'. */
|
||||
#undef HAVE__BOOL
|
||||
|
||||
/* Define to the sub-directory where libtool stores uninstalled libraries. */
|
||||
@@ -236,30 +182,32 @@
|
||||
STACK_DIRECTION = 0 => direction of growth unknown */
|
||||
#undef STACK_DIRECTION
|
||||
|
||||
/* Define to 1 if you have the ANSI C header files. */
|
||||
/* Define to 1 if all of the C89 standard headers exist (not just the ones
|
||||
required in a freestanding environment). This macro is provided for
|
||||
backward compatibility; new code need not use it. */
|
||||
#undef STDC_HEADERS
|
||||
|
||||
/* Version number of package */
|
||||
#undef VERSION
|
||||
|
||||
/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a
|
||||
`char[]'. */
|
||||
/* Define to 1 if 'lex' declares 'yytext' as a 'char *' by default, not a
|
||||
'char[]'. */
|
||||
#undef YYTEXT_POINTER
|
||||
|
||||
/* Define to empty if `const' does not conform to ANSI C. */
|
||||
/* Define to empty if 'const' does not conform to ANSI C. */
|
||||
#undef const
|
||||
|
||||
/* Define to rpl_malloc if the replacement function should be used. */
|
||||
#undef malloc
|
||||
|
||||
/* Define to `int' if <sys/types.h> does not define. */
|
||||
/* Define as a signed integer type capable of holding a process identifier. */
|
||||
#undef pid_t
|
||||
|
||||
/* Define to rpl_realloc if the replacement function should be used. */
|
||||
#undef realloc
|
||||
|
||||
/* Define to `unsigned int' if <sys/types.h> does not define. */
|
||||
/* Define as 'unsigned int' if <stddef.h> doesn't define. */
|
||||
#undef size_t
|
||||
|
||||
/* Define as `fork' if `vfork' does not work. */
|
||||
/* Define as 'fork' if 'vfork' does not work. */
|
||||
#undef vfork
|
||||
|
||||
@@ -6,16 +6,121 @@ patches = ["redox.patch"]
|
||||
template = "custom"
|
||||
script = """
|
||||
DYNAMIC_INIT
|
||||
|
||||
# Add relibc system headers to the include path.
|
||||
# The cookbook sets CPPFLAGS="-I${COOKBOOK_SYSROOT}/include" but the recipe
|
||||
# sysroot is empty for packages with no header-providing deps. The relibc
|
||||
# headers are at prefix/${TARGET}/relibc-install/${TARGET}/include/ — not in
|
||||
# the compiler's default search path. Without this, gnulib's #include_next
|
||||
# can't find the system headers and every wrapper fails.
|
||||
RELIBC_INCLUDE="${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/${TARGET}/include"
|
||||
export CPPFLAGS="${CPPFLAGS} -isystem ${RELIBC_INCLUDE}"
|
||||
|
||||
# relibc's float.h is missing LDBL_DIG (and possibly other LDBL_* macros).
|
||||
# For x86_64 80-bit extended precision: LDBL_DIG = floor(63 * log10(2)) = 18
|
||||
export CPPFLAGS="${CPPFLAGS} -DLDBL_DIG=18"
|
||||
|
||||
# The redoxer toolchain has a stale libc without __fseterr/__freadahead.
|
||||
# Add the relibc library path so the linker finds the updated library.
|
||||
RELIBC_LIB="${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/${TARGET}/lib"
|
||||
export LDFLAGS="${LDFLAGS} -L${RELIBC_LIB}"
|
||||
|
||||
# Gnulib cross-compilation: relibc provides standard POSIX headers and types
|
||||
# but gnulib's configure can't run test programs during cross-compilation.
|
||||
export ac_cv_header_stdio_h=yes
|
||||
export ac_cv_header_stdlib_h=yes
|
||||
export ac_cv_header_string_h=yes
|
||||
export ac_cv_header_strings_h=yes
|
||||
export ac_cv_header_inttypes_h=yes
|
||||
export ac_cv_header_stdint_h=yes
|
||||
export ac_cv_header_unistd_h=yes
|
||||
export ac_cv_header_sys_types_h=yes
|
||||
export ac_cv_header_sys_stat_h=yes
|
||||
export ac_cv_header_time_h=yes
|
||||
export ac_cv_header_sys_time_h=yes
|
||||
export ac_cv_header_sys_select_h=yes
|
||||
export ac_cv_header_wchar_h=yes
|
||||
export ac_cv_header_wctype_h=yes
|
||||
export ac_cv_header_signal_h=yes
|
||||
export ac_cv_header_dirent_h=yes
|
||||
export ac_cv_header_fcntl_h=yes
|
||||
export ac_cv_header_locale_h=yes
|
||||
export ac_cv_header_errno_h=yes
|
||||
export ac_cv_header_ctype_h=yes
|
||||
export ac_cv_header_limits_h=yes
|
||||
export ac_cv_header_stdarg_h=yes
|
||||
export ac_cv_header_stddef_h=yes
|
||||
export ac_cv_header_spawn_h=yes
|
||||
|
||||
# Standard types
|
||||
export ac_cv_type_intmax_t=yes
|
||||
export ac_cv_type_uintmax_t=yes
|
||||
export ac_cv_type_gid_t=yes
|
||||
export ac_cv_type_uid_t=yes
|
||||
export ac_cv_type_pid_t=yes
|
||||
export ac_cv_type_mode_t=yes
|
||||
export ac_cv_type_off_t=yes
|
||||
export ac_cv_type_size_t=yes
|
||||
export ac_cv_type_ssize_t=yes
|
||||
export ac_cv_type_ptrdiff_t=yes
|
||||
export ac_cv_type_nlink_t=yes
|
||||
export ac_cv_type_mbstate_t=yes
|
||||
export ac_cv_type_sigset_t=yes
|
||||
export ac_cv_type_posix_spawnattr_t=yes
|
||||
export ac_cv_type_posix_spawn_file_actions_t=yes
|
||||
export gl_cv_type_intmax_t=yes
|
||||
export gl_cv_type_ptrdiff_t_signed=yes
|
||||
export gl_cv_header_inttypes_h=yes
|
||||
export gl_cv_header_stdint_h=yes
|
||||
export gl_cv_header_inttypes_h_with_uintmax=yes
|
||||
export ac_cv_have_inttypes_h_with_uintmax=yes
|
||||
|
||||
# m4-specific gnulib function checks
|
||||
export ac_cv_func___freadahead=yes
|
||||
export ac_cv_have_decl___freadahead=yes
|
||||
export gl_cv_header_wchar_h_correct_inline=yes
|
||||
export gl_cv_func_btowc_nul=yes
|
||||
export gl_cv_func_btowc_consistent=yes
|
||||
export gl_cv_onwards_func___freadahead=yes
|
||||
export gl_cv_socklen_t_equiv=socklen_t
|
||||
export ac_cv_func_getpagesize=yes
|
||||
export ac_cv_func_memcmp_working=yes
|
||||
|
||||
# Tell gnulib these wide-char functions exist and work
|
||||
export ac_cv_func_btowc=yes
|
||||
export ac_cv_func_mbrtowc=yes
|
||||
export ac_cv_func_mbsinit=yes
|
||||
export ac_cv_func_wcrtomb=yes
|
||||
export ac_cv_func_wctob=yes
|
||||
export ac_cv_func_mbsrtowcs=yes
|
||||
export ac_cv_func_wcswidth=yes
|
||||
export ac_cv_func_wcwidth=yes
|
||||
export gl_cv_func_btowc=yes
|
||||
export gl_cv_func_mbrtowc=yes
|
||||
export gl_cv_func_mbsinit=yes
|
||||
export gl_cv_func_wcrtomb=yes
|
||||
export gl_cv_func_wctob=yes
|
||||
export gl_cv_func_wcwidth=yes
|
||||
export gl_cv_func_wcswidth=yes
|
||||
|
||||
# Functions that relibc provides but gnulib can't detect during cross-compilation
|
||||
export ac_cv_func___fseterr=yes
|
||||
export ac_cv_func_getlocalename_l=yes
|
||||
|
||||
COOKBOOK_CONFIGURE_FLAGS+=(
|
||||
--disable-nls
|
||||
ac_cv_func___freadahead=yes
|
||||
ac_cv_have_decl___freadahead=yes
|
||||
gl_cv_header_wchar_h_correct_inline=yes
|
||||
gl_cv_func_btowc_nul=yes
|
||||
gl_cv_func_btowc_consistent=yes
|
||||
gl_cv_onwards_func___freadahead=yes
|
||||
)
|
||||
cookbook_configure
|
||||
|
||||
"${COOKBOOK_CONFIGURE}" "${COOKBOOK_CONFIGURE_FLAGS[@]}"
|
||||
|
||||
# Fix gnulib cross-compilation misdetections in config.h
|
||||
"${COOKBOOK_ROOT}/local/scripts/gnulib-cross-fix.sh" "${COOKBOOK_BUILD}/lib/config.h"
|
||||
|
||||
# Prevent man page regeneration (help2man not available in cross-env)
|
||||
touch "${COOKBOOK_SOURCE}/doc/m4.1"
|
||||
|
||||
"${COOKBOOK_MAKE}" -j "${COOKBOOK_MAKE_JOBS}" HELP2MAN=true
|
||||
"${COOKBOOK_MAKE}" install DESTDIR="${COOKBOOK_STAGE}" HELP2MAN=true
|
||||
"""
|
||||
|
||||
[package]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
};
|
||||
const char *name = ((struct __locale_t *) locale)->mb_cur_max == 4 ? "C.UTF-8" : "C";
|
||||
return (struct string_with_storage) { name, STORAGE_INDEFINITE };
|
||||
+#elif defined __redox__ && HAVE_GETLOCALENAME_L
|
||||
+#elif defined __RELIBC__ && HAVE_GETLOCALENAME_L
|
||||
+ const char *name = getlocalename_l (category, locale);
|
||||
+ return (struct string_with_storage) { name != NULL ? name : "", STORAGE_OBJECT };
|
||||
#else
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@
|
||||
m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
|
||||
m4_ifndef([AC_AUTOCONF_VERSION],
|
||||
[m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
|
||||
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.72.90],,
|
||||
[m4_warning([this file was generated for autoconf 2.72.90.
|
||||
m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.73],,
|
||||
[m4_warning([this file was generated for autoconf 2.73.
|
||||
You have another version of autoconf. It may work, but is not guaranteed to.
|
||||
If you have problems, you may need to regenerate the build system entirely.
|
||||
To do so, use the procedure documented by the package, typically 'autoreconf'.])])
|
||||
|
||||
+29
-29
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.72.90 for GNU M4 1.4.21.
|
||||
# Generated by GNU Autoconf 2.73 for GNU M4 1.4.21.
|
||||
#
|
||||
# Report bugs to <bug-m4@gnu.org>.
|
||||
#
|
||||
@@ -634,7 +634,7 @@ gl_getopt_required=POSIX
|
||||
gl_trunc_required=plain
|
||||
gl_truncl_required=plain
|
||||
gt_needs=
|
||||
enable_year2038=no
|
||||
: ${enable_year2038:=no}
|
||||
ac_subst_vars='M4tests_libm4_LIBOBJDEPS
|
||||
M4tests_libm4_LTLIBOBJS
|
||||
M4tests_libm4_LIBOBJS
|
||||
@@ -3969,7 +3969,7 @@ test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
GNU M4 configure 1.4.21
|
||||
generated by GNU Autoconf 2.72.90
|
||||
generated by GNU Autoconf 2.73
|
||||
|
||||
Copyright (C) 2026 Free Software Foundation, Inc.
|
||||
This configure script is free software; the Free Software Foundation
|
||||
@@ -4692,7 +4692,7 @@ This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by GNU M4 $as_me 1.4.21, which was
|
||||
generated by GNU Autoconf 2.72.90. Invocation command line was
|
||||
generated by GNU Autoconf 2.73. Invocation command line was
|
||||
|
||||
$ $0$ac_configure_args_raw
|
||||
|
||||
@@ -61929,14 +61929,14 @@ int
|
||||
main (void)
|
||||
{
|
||||
#if defined _AIX && !defined _AIX51
|
||||
#error "AIX pre 5.1 is buggy"
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
#include <android/api-level.h>
|
||||
#if __ANDROID_API__ < 22
|
||||
#error "Android API < 22 is buggy"
|
||||
#endif
|
||||
#endif
|
||||
#error "AIX pre 5.1 is buggy"
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
#include <android/api-level.h>
|
||||
#if __ANDROID_API__ < 22
|
||||
#error "Android API < 22 is buggy"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
;
|
||||
return 0;
|
||||
@@ -61955,27 +61955,27 @@ else case e in #(
|
||||
/* end confdefs.h. */
|
||||
$ac_includes_default
|
||||
/* Use pstrnlen to test; 'volatile' prevents the compiler
|
||||
from optimizing the strnlen calls away. */
|
||||
size_t (*volatile pstrnlen) (char const *, size_t) = strnlen;
|
||||
char const s[] = "foobar";
|
||||
int s_len = sizeof s - 1;
|
||||
from optimizing the strnlen calls away. */
|
||||
size_t (*volatile pstrnlen) (char const *, size_t) = strnlen;
|
||||
char const s[] = "foobar";
|
||||
int s_len = sizeof s - 1;
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
|
||||
/* AIX 4.3 is buggy: strnlen (S, 1) == 3. */
|
||||
int i;
|
||||
for (i = 0; i < s_len + 1; ++i)
|
||||
{
|
||||
int expected = i <= s_len ? i : s_len;
|
||||
if (pstrnlen (s, i) != expected)
|
||||
return 1;
|
||||
}
|
||||
/* AIX 4.3 is buggy: strnlen (S, 1) == 3. */
|
||||
int i;
|
||||
for (i = 0; i < s_len + 1; ++i)
|
||||
{
|
||||
int expected = i <= s_len ? i : s_len;
|
||||
if (pstrnlen (s, i) != expected)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Android 5.0 (API 21) strnlen ("", SIZE_MAX) incorrectly crashes. */
|
||||
if (pstrnlen ("", -1) != 0)
|
||||
return 1;
|
||||
/* Android 5.0 (API 21) strnlen ("", SIZE_MAX) incorrectly crashes. */
|
||||
if (pstrnlen ("", -1) != 0)
|
||||
return 1;
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
@@ -75126,7 +75126,7 @@ cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by GNU M4 $as_me 1.4.21, which was
|
||||
generated by GNU Autoconf 2.72.90. Invocation command line was
|
||||
generated by GNU Autoconf 2.73. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
CONFIG_HEADERS = $CONFIG_HEADERS
|
||||
@@ -75200,7 +75200,7 @@ cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config='$ac_cs_config_escaped'
|
||||
ac_cs_version="\\
|
||||
GNU M4 config.status 1.4.21
|
||||
configured by $0, generated by GNU Autoconf 2.72.90,
|
||||
configured by $0, generated by GNU Autoconf 2.73,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
Copyright (C) 2026 Free Software Foundation, Inc.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
This is m4.info, produced by makeinfo version 7.2 from m4.texi.
|
||||
This is m4.info, produced by makeinfo version 7.3 from m4.texi.
|
||||
|
||||
This manual (6 February 2026) is for GNU M4 (version 1.4.21), a package
|
||||
This manual (15 May 2026) is for GNU M4 (version 1.4.21), a package
|
||||
containing an implementation of the m4 macro language.
|
||||
|
||||
Copyright © 1989-1994, 2004-2014, 2016-2017, 2020-2026 Free Software
|
||||
@@ -19,117 +19,117 @@ END-INFO-DIR-ENTRY
|
||||
|
||||
|
||||
Indirect:
|
||||
m4.info-1: 832
|
||||
m4.info-2: 317307
|
||||
m4.info-1: 828
|
||||
m4.info-2: 317295
|
||||
|
||||
Tag Table:
|
||||
(Indirect)
|
||||
Node: Top832
|
||||
Node: Preliminaries9778
|
||||
Node: Intro10484
|
||||
Node: History12175
|
||||
Node: Bugs16228
|
||||
Node: Manual17491
|
||||
Node: Invoking m420994
|
||||
Node: Operation modes23198
|
||||
Node: Preprocessor features26297
|
||||
Node: Limits control29467
|
||||
Node: Frozen state33478
|
||||
Node: Debugging options34317
|
||||
Node: Command line files36369
|
||||
Node: Syntax38020
|
||||
Node: Names39175
|
||||
Node: Quoted strings39657
|
||||
Node: Comments40324
|
||||
Node: Other tokens41227
|
||||
Node: Input processing41821
|
||||
Ref: Input processing-Footnote-150258
|
||||
Node: Macros50455
|
||||
Node: Invocation50949
|
||||
Node: Inhibiting Invocation51750
|
||||
Node: Macro Arguments55992
|
||||
Node: Quoting Arguments59112
|
||||
Node: Macro expansion61248
|
||||
Node: Definitions61966
|
||||
Node: Define62751
|
||||
Node: Arguments65269
|
||||
Node: Pseudo Arguments69035
|
||||
Node: Undefine72664
|
||||
Node: Defn73823
|
||||
Node: Pushdef78886
|
||||
Node: Indir81626
|
||||
Node: Builtin83793
|
||||
Node: Conditionals86068
|
||||
Node: Ifdef87014
|
||||
Node: Ifelse87896
|
||||
Node: Shift91282
|
||||
Node: Forloop102102
|
||||
Node: Foreach104783
|
||||
Node: Stacks110405
|
||||
Node: Composition113540
|
||||
Node: Debugging121217
|
||||
Node: Dumpdef121810
|
||||
Node: Trace123228
|
||||
Node: Debug Levels126880
|
||||
Node: Debug Output131750
|
||||
Node: Input Control133063
|
||||
Node: Dnl133604
|
||||
Node: Changequote135546
|
||||
Node: Changecom145278
|
||||
Node: Changeword149164
|
||||
Node: M4wrap154769
|
||||
Node: File Inclusion158854
|
||||
Node: Include159175
|
||||
Node: Search Path161992
|
||||
Node: Diversions162941
|
||||
Node: Divert164648
|
||||
Node: Undivert167214
|
||||
Node: Divnum170599
|
||||
Node: Cleardivert171072
|
||||
Node: Text handling172293
|
||||
Node: Len173020
|
||||
Node: Index macro173414
|
||||
Node: Regexp174307
|
||||
Node: Substr177468
|
||||
Node: Translit178526
|
||||
Node: Patsubst181317
|
||||
Node: Format185958
|
||||
Node: Arithmetic189366
|
||||
Node: Incr189819
|
||||
Node: Eval191494
|
||||
Node: Shell commands200238
|
||||
Node: Platform macros201176
|
||||
Node: Syscmd203378
|
||||
Node: Esyscmd205753
|
||||
Node: Sysval207336
|
||||
Node: Mkstemp209303
|
||||
Node: Miscellaneous213360
|
||||
Node: Errprint213797
|
||||
Node: Location215049
|
||||
Node: M4exit217926
|
||||
Node: Frozen files220052
|
||||
Node: Using frozen files220850
|
||||
Node: Frozen file format224231
|
||||
Node: Compatibility227381
|
||||
Node: Extensions228463
|
||||
Node: Incompatibilities232517
|
||||
Node: Other Incompatibilities241821
|
||||
Node: Answers244551
|
||||
Node: Improved exch245365
|
||||
Node: Improved forloop245918
|
||||
Node: Improved foreach251374
|
||||
Node: Improved copy264752
|
||||
Node: Improved m4wrap268809
|
||||
Node: Improved cleardivert271305
|
||||
Node: Improved capitalize272303
|
||||
Node: Improved fatal_error277339
|
||||
Node: Copying This Package278436
|
||||
Node: GNU General Public License278915
|
||||
Node: Copying This Manual317307
|
||||
Node: GNU Free Documentation License317831
|
||||
Node: Indices342955
|
||||
Node: Macro index343239
|
||||
Node: Concept index349849
|
||||
Node: Top828
|
||||
Node: Preliminaries9770
|
||||
Node: Intro10476
|
||||
Node: History12167
|
||||
Node: Bugs16220
|
||||
Node: Manual17483
|
||||
Node: Invoking m420986
|
||||
Node: Operation modes23190
|
||||
Node: Preprocessor features26289
|
||||
Node: Limits control29459
|
||||
Node: Frozen state33470
|
||||
Node: Debugging options34309
|
||||
Node: Command line files36361
|
||||
Node: Syntax38012
|
||||
Node: Names39167
|
||||
Node: Quoted strings39649
|
||||
Node: Comments40316
|
||||
Node: Other tokens41219
|
||||
Node: Input processing41813
|
||||
Ref: Input processing-Footnote-150250
|
||||
Node: Macros50447
|
||||
Node: Invocation50941
|
||||
Node: Inhibiting Invocation51742
|
||||
Node: Macro Arguments55984
|
||||
Node: Quoting Arguments59104
|
||||
Node: Macro expansion61240
|
||||
Node: Definitions61958
|
||||
Node: Define62743
|
||||
Node: Arguments65261
|
||||
Node: Pseudo Arguments69027
|
||||
Node: Undefine72656
|
||||
Node: Defn73815
|
||||
Node: Pushdef78878
|
||||
Node: Indir81618
|
||||
Node: Builtin83785
|
||||
Node: Conditionals86060
|
||||
Node: Ifdef87006
|
||||
Node: Ifelse87888
|
||||
Node: Shift91274
|
||||
Node: Forloop102094
|
||||
Node: Foreach104775
|
||||
Node: Stacks110397
|
||||
Node: Composition113532
|
||||
Node: Debugging121209
|
||||
Node: Dumpdef121802
|
||||
Node: Trace123220
|
||||
Node: Debug Levels126872
|
||||
Node: Debug Output131742
|
||||
Node: Input Control133055
|
||||
Node: Dnl133596
|
||||
Node: Changequote135538
|
||||
Node: Changecom145270
|
||||
Node: Changeword149156
|
||||
Node: M4wrap154761
|
||||
Node: File Inclusion158846
|
||||
Node: Include159167
|
||||
Node: Search Path161984
|
||||
Node: Diversions162933
|
||||
Node: Divert164640
|
||||
Node: Undivert167206
|
||||
Node: Divnum170591
|
||||
Node: Cleardivert171064
|
||||
Node: Text handling172285
|
||||
Node: Len173012
|
||||
Node: Index macro173406
|
||||
Node: Regexp174299
|
||||
Node: Substr177460
|
||||
Node: Translit178518
|
||||
Node: Patsubst181309
|
||||
Node: Format185950
|
||||
Node: Arithmetic189358
|
||||
Node: Incr189811
|
||||
Node: Eval191486
|
||||
Node: Shell commands200230
|
||||
Node: Platform macros201168
|
||||
Node: Syscmd203370
|
||||
Node: Esyscmd205745
|
||||
Node: Sysval207328
|
||||
Node: Mkstemp209295
|
||||
Node: Miscellaneous213352
|
||||
Node: Errprint213789
|
||||
Node: Location215041
|
||||
Node: M4exit217918
|
||||
Node: Frozen files220044
|
||||
Node: Using frozen files220842
|
||||
Node: Frozen file format224223
|
||||
Node: Compatibility227373
|
||||
Node: Extensions228455
|
||||
Node: Incompatibilities232509
|
||||
Node: Other Incompatibilities241813
|
||||
Node: Answers244543
|
||||
Node: Improved exch245357
|
||||
Node: Improved forloop245910
|
||||
Node: Improved foreach251366
|
||||
Node: Improved copy264744
|
||||
Node: Improved m4wrap268801
|
||||
Node: Improved cleardivert271297
|
||||
Node: Improved capitalize272295
|
||||
Node: Improved fatal_error277331
|
||||
Node: Copying This Package278428
|
||||
Node: GNU General Public License278907
|
||||
Node: Copying This Manual317295
|
||||
Node: GNU Free Documentation License317819
|
||||
Node: Indices342943
|
||||
Node: Macro index343227
|
||||
Node: Concept index349837
|
||||
|
||||
End Tag Table
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
This is m4.info, produced by makeinfo version 7.2 from m4.texi.
|
||||
This is m4.info, produced by makeinfo version 7.3 from m4.texi.
|
||||
|
||||
This manual (6 February 2026) is for GNU M4 (version 1.4.21), a package
|
||||
This manual (15 May 2026) is for GNU M4 (version 1.4.21), a package
|
||||
containing an implementation of the m4 macro language.
|
||||
|
||||
Copyright © 1989-1994, 2004-2014, 2016-2017, 2020-2026 Free Software
|
||||
@@ -23,7 +23,7 @@ File: m4.info, Node: Top, Next: Preliminaries, Up: (dir)
|
||||
GNU M4
|
||||
******
|
||||
|
||||
This manual (6 February 2026) is for GNU M4 (version 1.4.21), a package
|
||||
This manual (15 May 2026) is for GNU M4 (version 1.4.21), a package
|
||||
containing an implementation of the m4 macro language.
|
||||
|
||||
Copyright © 1989-1994, 2004-2014, 2016-2017, 2020-2026 Free Software
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
This is m4.info, produced by makeinfo version 7.2 from m4.texi.
|
||||
This is m4.info, produced by makeinfo version 7.3 from m4.texi.
|
||||
|
||||
This manual (6 February 2026) is for GNU M4 (version 1.4.21), a package
|
||||
This manual (15 May 2026) is for GNU M4 (version 1.4.21), a package
|
||||
containing an implementation of the m4 macro language.
|
||||
|
||||
Copyright © 1989-1994, 2004-2014, 2016-2017, 2020-2026 Free Software
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@set UPDATED 6 February 2026
|
||||
@set UPDATED-MONTH February 2026
|
||||
@set UPDATED 15 May 2026
|
||||
@set UPDATED-MONTH May 2026
|
||||
@set EDITION 1.4.21
|
||||
@set VERSION 1.4.21
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@set UPDATED 6 February 2026
|
||||
@set UPDATED-MONTH February 2026
|
||||
@set UPDATED 15 May 2026
|
||||
@set UPDATED-MONTH May 2026
|
||||
@set EDITION 1.4.21
|
||||
@set VERSION 1.4.21
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
[source]
|
||||
git = "https://github.com/ninja-build/ninja"
|
||||
rev = "v1.13.1"
|
||||
patches = ["redox.patch"]
|
||||
|
||||
[build]
|
||||
template = "cmake"
|
||||
template = "custom"
|
||||
script = """
|
||||
DYNAMIC_INIT
|
||||
|
||||
# Add relibc include/lib paths (same as m4/flex gnulib packages)
|
||||
RELIBC_INCLUDE="${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/${TARGET}/include"
|
||||
RELIBC_LIB="${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/${TARGET}/lib"
|
||||
export CPPFLAGS="${CPPFLAGS} -isystem ${RELIBC_INCLUDE}"
|
||||
export LDFLAGS="${LDFLAGS} -L${RELIBC_LIB}"
|
||||
|
||||
# Copy updated relibc headers into sysroot so they take precedence over
|
||||
# the stale toolchain headers at ~/.redoxer/. The C++ <cstdlib> wrapper
|
||||
# explicitly includes the toolchain's stdlib.h which lacks newer functions
|
||||
# like getloadavg. The -I path (sysroot/include) takes priority over all
|
||||
# other include paths, so this ensures our headers win.
|
||||
mkdir -p "${COOKBOOK_SYSROOT}/include"
|
||||
cp -a "${RELIBC_INCLUDE}/"* "${COOKBOOK_SYSROOT}/include/"
|
||||
|
||||
# Disable tests — build_test.cc includes host headers that conflict with
|
||||
# relibc's headers during cross-compilation.
|
||||
cookbook_cmake -DBUILD_TESTING=OFF
|
||||
"""
|
||||
|
||||
[package]
|
||||
description = "Ninja build system"
|
||||
|
||||
@@ -1,67 +1,14 @@
|
||||
--- a/src/subprocess-posix.cc
|
||||
+++ b/src/subprocess-posix.cc
|
||||
@@ -72,6 +72,45 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
|
||||
SetCloseOnExec(fd_);
|
||||
}
|
||||
|
||||
+#if defined(__redox__)
|
||||
+ pid_ = fork();
|
||||
+ if (pid_ < 0)
|
||||
+ Fatal("fork: %s", strerror(errno));
|
||||
+ if (pid_ == 0) {
|
||||
+ if (sigprocmask(SIG_SETMASK, &set->old_mask_, 0) < 0) {
|
||||
+ perror("ninja: sigprocmask");
|
||||
+ _exit(1);
|
||||
+ }
|
||||
+ if (!use_console_) {
|
||||
+ if (setpgid(0, 0) < 0) {
|
||||
+ perror("ninja: setpgid");
|
||||
+ _exit(1);
|
||||
+ }
|
||||
+ int devnull = open("/dev/null", O_RDONLY);
|
||||
+ if (devnull < 0) {
|
||||
+ perror("ninja: open /dev/null");
|
||||
+ _exit(1);
|
||||
+ }
|
||||
+ if (dup2(devnull, 0) < 0 || dup2(subproc_stdout_fd, 1) < 0 ||
|
||||
+ dup2(subproc_stdout_fd, 2) < 0) {
|
||||
+ perror("ninja: dup2");
|
||||
+ _exit(1);
|
||||
+ }
|
||||
+ close(devnull);
|
||||
+ close(fd_);
|
||||
+ close(subproc_stdout_fd);
|
||||
+ }
|
||||
+
|
||||
+ const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
|
||||
+ execve("/bin/sh", const_cast<char**>(spawned_args), environ);
|
||||
+ perror("ninja: execve /bin/sh");
|
||||
+ _exit(127);
|
||||
+ }
|
||||
+
|
||||
+ if (!use_console_)
|
||||
+ close(subproc_stdout_fd);
|
||||
+ return true;
|
||||
+#else
|
||||
posix_spawn_file_actions_t action;
|
||||
int err = posix_spawn_file_actions_init(&action);
|
||||
if (err != 0)
|
||||
@@ -145,6 +184,7 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) {
|
||||
if (!use_console_)
|
||||
close(subproc_stdout_fd);
|
||||
return true;
|
||||
+#endif
|
||||
}
|
||||
|
||||
void Subprocess::OnPipeReady() {
|
||||
--- a/src/util.cc
|
||||
+++ b/src/util.cc
|
||||
@@ -973,7 +973,7 @@ double GetLoadAverage() {
|
||||
return -0.0f;
|
||||
return 1.0 / (1 << SI_LOAD_SHIFT) * si.loads[0];
|
||||
}
|
||||
-#elif defined(__HAIKU__)
|
||||
+#elif defined(__HAIKU__) || defined(__redox__)
|
||||
double GetLoadAverage() {
|
||||
return -0.0f;
|
||||
}
|
||||
@@ -30,6 +30,11 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
+// Redox: the C++ <cstdlib> wrapper pulls in a stale toolchain stdlib.h that
|
||||
+// lacks getloadavg. Re-declare it here since relibc provides the implementation.
|
||||
+#if defined(__redox__)
|
||||
+extern "C" int getloadavg(double loadavg[], int nelem);
|
||||
+#endif
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "audiod"
|
||||
description = "Sound daemon"
|
||||
version = "0.1.0"
|
||||
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
daemon = { path = "../daemon" }
|
||||
redox_syscall = { workspace = true, features = ["std"] }
|
||||
libc.workspace = true
|
||||
libredox = { workspace = true, features = ["mkns"] }
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../scheme-utils" }
|
||||
anyhow.workspace = true
|
||||
ioslice = "0.6.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,94 +0,0 @@
|
||||
//! The audio daemon for RedoxOS.
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::addr_of_mut;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{mem, process, slice, thread};
|
||||
|
||||
use anyhow::Context;
|
||||
use ioslice::IoSlice;
|
||||
use libredox::flag;
|
||||
use libredox::{error::Result, Fd};
|
||||
|
||||
use redox_scheme::Socket;
|
||||
use scheme_utils::ReadinessBased;
|
||||
|
||||
use daemon::SchemeDaemon;
|
||||
|
||||
use self::scheme::AudioScheme;
|
||||
|
||||
mod scheme;
|
||||
|
||||
extern "C" fn sigusr_handler(_sig: usize) {}
|
||||
|
||||
fn thread(scheme: Arc<Mutex<AudioScheme>>, pid: usize, hw_file: Fd) -> Result<()> {
|
||||
loop {
|
||||
let buffer = scheme.lock().unwrap().buffer();
|
||||
let buffer_u8 = unsafe {
|
||||
slice::from_raw_parts(buffer.as_ptr() as *const u8, mem::size_of_val(&buffer))
|
||||
};
|
||||
|
||||
// Wake up the scheme thread
|
||||
libredox::call::kill(pid, libredox::flag::SIGUSR1 as u32)?;
|
||||
|
||||
hw_file.write(&buffer_u8)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
|
||||
// Handle signals from the hw thread
|
||||
|
||||
let new_sigaction = unsafe {
|
||||
let mut sigaction = MaybeUninit::<libc::sigaction>::uninit();
|
||||
addr_of_mut!((*sigaction.as_mut_ptr()).sa_flags).write(0);
|
||||
libc::sigemptyset(addr_of_mut!((*sigaction.as_mut_ptr()).sa_mask));
|
||||
addr_of_mut!((*sigaction.as_mut_ptr()).sa_sigaction).write(sigusr_handler as usize);
|
||||
sigaction.assume_init()
|
||||
};
|
||||
libredox::call::sigaction(flag::SIGUSR1, Some(&new_sigaction), None)?;
|
||||
|
||||
let pid = libredox::call::getpid()?;
|
||||
|
||||
let hw_file = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?;
|
||||
|
||||
let socket = Socket::create().context("failed to create scheme")?;
|
||||
|
||||
let scheme = Arc::new(Mutex::new(AudioScheme::new()));
|
||||
|
||||
let _ = daemon.ready_sync_scheme(&socket, &mut *scheme.lock().unwrap());
|
||||
|
||||
// Enter a constrained namespace
|
||||
let ns = libredox::call::mkns(&[
|
||||
IoSlice::new(b"memory"),
|
||||
IoSlice::new(b"rand"), // for HashMap
|
||||
])
|
||||
.context("failed to make namespace")?;
|
||||
libredox::call::setns(ns).context("failed to set namespace")?;
|
||||
|
||||
// Spawn a thread to mix and send audio data
|
||||
let scheme_thread = scheme.clone();
|
||||
let _thread = thread::spawn(move || thread(scheme_thread, pid, hw_file));
|
||||
|
||||
let mut readiness = ReadinessBased::new(&socket, 16);
|
||||
|
||||
loop {
|
||||
readiness.read_and_process_requests(&mut *scheme.lock().unwrap())?;
|
||||
readiness.poll_all_requests(&mut *scheme.lock().unwrap())?;
|
||||
readiness.write_responses()?;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
SchemeDaemon::new(inner);
|
||||
}
|
||||
|
||||
fn inner(x: SchemeDaemon) -> ! {
|
||||
match daemon(x) {
|
||||
Ok(()) => {
|
||||
process::exit(0);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("audiod: {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
use scheme_utils::HandleMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::str;
|
||||
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT, EWOULDBLOCK};
|
||||
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
|
||||
// The strict buffer size of the audiohw: driver
|
||||
const HW_BUFFER_SIZE: usize = 512;
|
||||
// The desired buffer size of each handle
|
||||
const HANDLE_BUFFER_SIZE: usize = 4096;
|
||||
|
||||
enum Handle {
|
||||
Audio { buffer: VecDeque<(i16, i16)> },
|
||||
// TODO: move volume to audiohw:?
|
||||
// TODO: Use SYS_CALL to handle this better?
|
||||
Volume,
|
||||
SchemeRoot,
|
||||
}
|
||||
|
||||
pub struct AudioScheme {
|
||||
handles: HandleMap<Handle>,
|
||||
volume: i32,
|
||||
}
|
||||
|
||||
impl AudioScheme {
|
||||
pub fn new() -> Self {
|
||||
AudioScheme {
|
||||
handles: HandleMap::new(),
|
||||
volume: 50,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer(&mut self) -> [(i16, i16); HW_BUFFER_SIZE] {
|
||||
let mut mix_buffer = [(0i16, 0i16); HW_BUFFER_SIZE];
|
||||
|
||||
// Multiply each sample by the cube of volume divided by 100
|
||||
// This mimics natural perception of loudness
|
||||
let volume_factor = ((self.volume as f32) / 100.0).powi(3);
|
||||
for (_id, handle) in self.handles.iter_mut() {
|
||||
match handle {
|
||||
Handle::Audio { ref mut buffer } => {
|
||||
let mut i = 0;
|
||||
while i < mix_buffer.len() {
|
||||
if let Some(sample) = buffer.pop_front() {
|
||||
let left = (sample.0 as f32 * volume_factor) as i16;
|
||||
let right = (sample.1 as f32 * volume_factor) as i16;
|
||||
mix_buffer[i].0 = mix_buffer[i].0.saturating_add(left);
|
||||
mix_buffer[i].1 = mix_buffer[i].1.saturating_add(right);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
mix_buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for AudioScheme {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.insert(Handle::SchemeRoot))
|
||||
}
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
let (handle, flags) = match path.trim_matches('/') {
|
||||
"" => (
|
||||
Handle::Audio {
|
||||
buffer: VecDeque::new(),
|
||||
},
|
||||
NewFdFlags::empty(),
|
||||
),
|
||||
"volume" => (Handle::Volume, NewFdFlags::POSITIONED),
|
||||
_ => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
|
||||
let id = self.handles.insert(handle);
|
||||
|
||||
Ok(OpenResult::ThisScheme { number: id, flags })
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
off: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
//TODO: check flags for readable
|
||||
match self.handles.get_mut(id)? {
|
||||
Handle::Audio { buffer: _ } => {
|
||||
//TODO: audio input?
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
Handle::Volume => {
|
||||
let Ok(off) = usize::try_from(off) else {
|
||||
return Ok(0);
|
||||
};
|
||||
//TODO: should we allocate every time?
|
||||
let bytes = format!("{}", self.volume).into_bytes();
|
||||
let src = bytes.get(off..).unwrap_or(&[]);
|
||||
let len = src.len().min(buf.len());
|
||||
buf[..len].copy_from_slice(&src[..len]);
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
offset: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
//TODO: check flags for writable
|
||||
match self.handles.get_mut(id)? {
|
||||
Handle::Audio { ref mut buffer } => {
|
||||
if buffer.len() >= HANDLE_BUFFER_SIZE {
|
||||
Err(Error::new(EWOULDBLOCK))
|
||||
} else {
|
||||
let mut i = 0;
|
||||
while i + 4 <= buf.len() {
|
||||
buffer.push_back((
|
||||
(buf[i] as i16) | ((buf[i + 1] as i16) << 8),
|
||||
(buf[i + 2] as i16) | ((buf[i + 3] as i16) << 8),
|
||||
));
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
Ok(i)
|
||||
}
|
||||
}
|
||||
Handle::Volume => {
|
||||
//TODO: support other offsets?
|
||||
if offset == 0 {
|
||||
let value = str::from_utf8(buf)
|
||||
.map_err(|_| Error::new(EINVAL))?
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.map_err(|_| Error::new(EINVAL))?;
|
||||
if value >= 0 && value <= 100 {
|
||||
self.volume = value;
|
||||
Ok(buf.len())
|
||||
} else {
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
} else {
|
||||
// EOF
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
[unstable]
|
||||
build-std = ["core", "alloc", "compiler_builtins"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
@@ -1,35 +0,0 @@
|
||||
[package]
|
||||
name = "bootstrap"
|
||||
description = "Userspace bootstrapper"
|
||||
version = "0.0.0"
|
||||
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
hashbrown = { version = "0.15", default-features = false, features = [
|
||||
"inline-more",
|
||||
"default-hasher",
|
||||
] }
|
||||
linked_list_allocator = "0.10"
|
||||
libredox = { version = "0.1.16", default-features = false, features = ["protocol"] }
|
||||
log = { version = "0.4", default-features = false }
|
||||
plain = "0.2"
|
||||
redox-initfs = { path = "../initfs", default-features = false }
|
||||
redox_syscall = "0.7.4"
|
||||
redox-scheme = { version = "0.11.0", default-features = false }
|
||||
redox-path = "0.3.1"
|
||||
slab = { version = "0.4.9", default-features = false }
|
||||
arrayvec = { version = "0.7.6", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc.git", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "fat"
|
||||
opt-level = "s"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
@@ -1,14 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
if arch == "x86" {
|
||||
arch = "i586".to_owned();
|
||||
}
|
||||
|
||||
println!("cargo::rustc-link-arg=-z");
|
||||
println!("cargo::rustc-link-arg=max-page-size=4096");
|
||||
println!("cargo::rustc-link-arg=-T");
|
||||
println!("cargo::rustc-link-arg={manifest_dir}/src/{arch}.ld");
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
ENTRY(_start)
|
||||
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
|
||||
|
||||
SECTIONS {
|
||||
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
|
||||
__initfs_header = . - 4096;
|
||||
. += SIZEOF_HEADERS;
|
||||
. = ALIGN(4096);
|
||||
|
||||
.text : {
|
||||
__text_start = .;
|
||||
*(.text*)
|
||||
. = ALIGN(4096);
|
||||
__text_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
__rodata_start = .;
|
||||
*(.rodata*)
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got)
|
||||
}
|
||||
.got.plt : {
|
||||
*(.got.plt)
|
||||
. = ALIGN(4096);
|
||||
__rodata_end = .;
|
||||
}
|
||||
.data : {
|
||||
__data_start = .;
|
||||
*(.data*)
|
||||
. = ALIGN(4096);
|
||||
__data_end = .;
|
||||
|
||||
*(.tbss*)
|
||||
. = ALIGN(4096);
|
||||
*(.tdata*)
|
||||
. = ALIGN(4096);
|
||||
|
||||
__bss_start = .;
|
||||
*(.bss*)
|
||||
. = ALIGN(4096);
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.rel.eh_frame*)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
use core::mem;
|
||||
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
|
||||
|
||||
pub const USERMODE_END: usize = 0x0000_8000_0000_0000;
|
||||
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
|
||||
|
||||
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
|
||||
static MAP: Map = Map {
|
||||
offset: 0,
|
||||
size: STACK_SIZE,
|
||||
flags: MapFlags::PROT_READ
|
||||
.union(MapFlags::PROT_WRITE)
|
||||
.union(MapFlags::MAP_PRIVATE)
|
||||
.union(MapFlags::MAP_FIXED_NOREPLACE),
|
||||
address: STACK_START, // highest possible user address
|
||||
};
|
||||
|
||||
core::arch::global_asm!(
|
||||
"
|
||||
.globl _start
|
||||
_start:
|
||||
// Setup a stack.
|
||||
ldr x8, ={number}
|
||||
ldr x0, ={fd}
|
||||
ldr x1, ={map} // pointer to Map struct
|
||||
ldr x2, ={map_size} // size of Map struct
|
||||
svc 0
|
||||
|
||||
// Failure if return value is zero
|
||||
cbz x0, 1f
|
||||
|
||||
// Failure if return value is negative
|
||||
tbnz x0, 63, 1f
|
||||
|
||||
// Set up stack frame
|
||||
mov sp, x0
|
||||
add sp, sp, #{stack_size}
|
||||
mov fp, sp
|
||||
|
||||
// Stack has the same alignment as `size`.
|
||||
bl start
|
||||
// `start` must never return.
|
||||
|
||||
// failure, emit undefined instruction
|
||||
1:
|
||||
udf #0
|
||||
",
|
||||
fd = const usize::MAX, // dummy fd indicates anonymous map
|
||||
map = sym MAP,
|
||||
map_size = const mem::size_of::<Map>(),
|
||||
number = const SYS_FMAP,
|
||||
stack_size = const STACK_SIZE,
|
||||
);
|
||||
@@ -1,354 +0,0 @@
|
||||
use alloc::string::ToString;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::CStr;
|
||||
use core::str::FromStr;
|
||||
use hashbrown::HashMap;
|
||||
use redox_scheme::Socket;
|
||||
|
||||
use syscall::CallFlags;
|
||||
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
|
||||
use syscall::flag::{O_CLOEXEC, O_RDONLY, O_STAT};
|
||||
use syscall::{EINTR, Error};
|
||||
|
||||
use redox_rt::proc::*;
|
||||
|
||||
use crate::KernelSchemeMap;
|
||||
|
||||
struct Logger;
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||
metadata.level() <= log::max_level()
|
||||
}
|
||||
fn log(&self, record: &log::Record) {
|
||||
let file = record.file().unwrap_or("");
|
||||
let line = record.line().unwrap_or(0);
|
||||
let level = record.level();
|
||||
let msg = record.args();
|
||||
let _ = syscall::write(
|
||||
1,
|
||||
alloc::format!("[{file}:{line} {level}] {msg}\n").as_bytes(),
|
||||
);
|
||||
}
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
const KERNEL_METADATA_BASE: usize = crate::arch::USERMODE_END - syscall::KERNEL_METADATA_SIZE;
|
||||
|
||||
pub fn main() -> ! {
|
||||
let mut cursor = KERNEL_METADATA_BASE;
|
||||
let kernel_scheme_infos = unsafe {
|
||||
let base_ptr = cursor as *const u8;
|
||||
let infos_len = *(base_ptr as *const usize);
|
||||
let infos_ptr = base_ptr.add(core::mem::size_of::<usize>()) as *const KernelSchemeInfo;
|
||||
let slice = core::slice::from_raw_parts(infos_ptr, infos_len);
|
||||
cursor += core::mem::size_of::<usize>() // kernel scheme number size
|
||||
+ infos_len // kernel scheme number
|
||||
* core::mem::size_of::<KernelSchemeInfo>();
|
||||
slice
|
||||
};
|
||||
let scheme_creation_cap = unsafe {
|
||||
let base_ptr = cursor as *const u8;
|
||||
FdGuard::new(*(base_ptr as *const usize))
|
||||
};
|
||||
|
||||
let mut kernel_schemes = KernelSchemeMap::new(kernel_scheme_infos);
|
||||
|
||||
let auth = kernel_schemes
|
||||
.0
|
||||
.remove(&GlobalSchemes::Proc)
|
||||
.expect("failed to get proc fd");
|
||||
|
||||
let this_thr_fd = auth
|
||||
.dup(b"cur-context")
|
||||
.expect("failed to open open_via_dup")
|
||||
.to_upper()
|
||||
.unwrap();
|
||||
let this_thr_fd = unsafe { redox_rt::initialize_freestanding(this_thr_fd) };
|
||||
|
||||
let mut env_bytes = [0_u8; 4096];
|
||||
let mut envs = {
|
||||
let fd = FdGuard::new(
|
||||
syscall::openat(
|
||||
kernel_schemes
|
||||
.get(GlobalSchemes::Sys)
|
||||
.expect("failed to get sys fd")
|
||||
.as_raw_fd(),
|
||||
"env",
|
||||
O_RDONLY | O_CLOEXEC,
|
||||
0,
|
||||
)
|
||||
.expect("bootstrap: failed to open env"),
|
||||
);
|
||||
let bytes_read = fd
|
||||
.read(&mut env_bytes)
|
||||
.expect("bootstrap: failed to read env");
|
||||
|
||||
if bytes_read >= env_bytes.len() {
|
||||
// TODO: Handle this, we can allocate as much as we want in theory.
|
||||
panic!("env is too large");
|
||||
}
|
||||
let env_bytes = &mut env_bytes[..bytes_read];
|
||||
|
||||
env_bytes
|
||||
.split(|&c| c == b'\n')
|
||||
.filter(|var| !var.is_empty())
|
||||
.filter(|var| !var.starts_with(b"INITFS_"))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
envs.push(b"RUST_BACKTRACE=1");
|
||||
//envs.push(b"LD_DEBUG=all");
|
||||
envs.push(b"LD_LIBRARY_PATH=/scheme/initfs/lib");
|
||||
|
||||
log::set_max_level(log::LevelFilter::Warn);
|
||||
|
||||
if let Some(log_env) = envs
|
||||
.iter()
|
||||
.find_map(|var| var.strip_prefix(b"BOOTSTRAP_LOG_LEVEL="))
|
||||
{
|
||||
if let Ok(Ok(log_level)) = str::from_utf8(&log_env).map(|s| log::LevelFilter::from_str(s)) {
|
||||
log::set_max_level(log_level);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = log::set_logger(&Logger);
|
||||
|
||||
unsafe extern "C" {
|
||||
// The linker script will define this as the location of the initfs header.
|
||||
static __initfs_header: u8;
|
||||
|
||||
// The linker script will define this as the end of the executable (excluding initfs).
|
||||
static __bss_end: u8;
|
||||
}
|
||||
|
||||
let initfs_start = core::ptr::addr_of!(__initfs_header);
|
||||
let initfs_length = unsafe {
|
||||
(*(core::ptr::addr_of!(__initfs_header) as *const redox_initfs::types::Header))
|
||||
.initfs_size
|
||||
.get() as usize
|
||||
};
|
||||
|
||||
let (scheme_creation_cap, auth, kernel_schemes, initfs_fd) = spawn(
|
||||
"initfs daemon",
|
||||
auth,
|
||||
&this_thr_fd,
|
||||
scheme_creation_cap,
|
||||
kernel_schemes,
|
||||
false,
|
||||
|write_fd, socket, _, _| unsafe {
|
||||
crate::initfs::run(
|
||||
core::slice::from_raw_parts(initfs_start, initfs_length),
|
||||
write_fd,
|
||||
socket,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Unmap initfs data as only the initfs scheme implementation needs it.
|
||||
unsafe {
|
||||
let executable_end = core::ptr::addr_of!(__bss_end)
|
||||
.add(core::ptr::addr_of!(__bss_end).align_offset(syscall::PAGE_SIZE));
|
||||
syscall::funmap(
|
||||
executable_end as usize,
|
||||
initfs_length.next_multiple_of(syscall::PAGE_SIZE)
|
||||
- (executable_end.offset_from(initfs_start) as usize),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let (scheme_creation_cap, auth, kernel_schemes, proc_fd) = spawn(
|
||||
"process manager",
|
||||
auth,
|
||||
&this_thr_fd,
|
||||
scheme_creation_cap,
|
||||
kernel_schemes,
|
||||
true,
|
||||
|write_fd, socket, auth, mut kernel_schemes| {
|
||||
let event = kernel_schemes
|
||||
.0
|
||||
.remove(&GlobalSchemes::Event)
|
||||
.expect("failed to get event fd");
|
||||
drop(kernel_schemes);
|
||||
crate::procmgr::run(write_fd, socket, auth, event)
|
||||
},
|
||||
);
|
||||
|
||||
let scheme_creation_cap_dup = scheme_creation_cap
|
||||
.dup(b"")
|
||||
.expect("failed to dup scheme creation cap");
|
||||
let (_, _, _, initns_fd) = spawn(
|
||||
"init namespace manager",
|
||||
auth,
|
||||
&this_thr_fd,
|
||||
scheme_creation_cap,
|
||||
kernel_schemes,
|
||||
false,
|
||||
|write_fd, socket, _, kernel_schemes| {
|
||||
let mut schemes = HashMap::default();
|
||||
for (scheme, fd) in kernel_schemes.0.into_iter() {
|
||||
schemes.insert(scheme.as_str().to_string(), Arc::new(fd));
|
||||
}
|
||||
schemes.insert(
|
||||
"proc".to_string(),
|
||||
// A bit dirty, but necessary as the parent process still needs access to it. Rust
|
||||
// doesn't know that the fd got cloned by fork.
|
||||
Arc::new(FdGuard::new(proc_fd.as_raw_fd())),
|
||||
);
|
||||
schemes.insert("initfs".to_string(), Arc::new(initfs_fd));
|
||||
|
||||
crate::initnsmgr::run(write_fd, socket, schemes, scheme_creation_cap_dup)
|
||||
},
|
||||
);
|
||||
|
||||
let (init_proc_fd, init_thr_fd) = unsafe { make_init(proc_fd.take()) };
|
||||
// from this point, this_thr_fd is no longer valid
|
||||
|
||||
const CWD: &[u8] = b"/scheme/initfs";
|
||||
let cwd_fd = FdGuard::new(
|
||||
syscall::openat(initns_fd.as_raw_fd(), "/scheme/initfs", O_STAT, 0)
|
||||
.expect("failed to open cwd fd"),
|
||||
)
|
||||
.to_upper()
|
||||
.unwrap();
|
||||
let extrainfo = ExtraInfo {
|
||||
cwd: Some(CWD),
|
||||
sigprocmask: 0,
|
||||
sigignmask: 0,
|
||||
umask: redox_rt::sys::get_umask(),
|
||||
thr_fd: init_thr_fd.as_raw_fd(),
|
||||
proc_fd: init_proc_fd.as_raw_fd(),
|
||||
ns_fd: Some(initns_fd.take()),
|
||||
cwd_fd: Some(cwd_fd.as_raw_fd()),
|
||||
};
|
||||
|
||||
let exe_path = "/scheme/initfs/bin/init";
|
||||
|
||||
let image_file = FdGuard::new(
|
||||
syscall::openat(extrainfo.ns_fd.unwrap(), exe_path, O_RDONLY | O_CLOEXEC, 0)
|
||||
.expect("failed to open init"),
|
||||
)
|
||||
.to_upper()
|
||||
.unwrap();
|
||||
|
||||
let FexecResult::Interp {
|
||||
path: interp_path,
|
||||
interp_override,
|
||||
} = fexec_impl(
|
||||
image_file,
|
||||
init_thr_fd,
|
||||
init_proc_fd,
|
||||
exe_path.as_bytes(),
|
||||
&[exe_path.as_bytes()],
|
||||
&envs,
|
||||
&extrainfo,
|
||||
None,
|
||||
)
|
||||
.expect("failed to execute init");
|
||||
|
||||
// According to elf(5), PT_INTERP requires that the interpreter path be
|
||||
// null-terminated. Violating this should therefore give the "format error" ENOEXEC.
|
||||
let interp_cstr = CStr::from_bytes_with_nul(&interp_path).expect("interpreter not valid C str");
|
||||
let interp_file = FdGuard::new(
|
||||
syscall::openat(
|
||||
extrainfo.ns_fd.unwrap(), // initns, not initfs!
|
||||
interp_cstr.to_str().expect("interpreter not UTF-8"),
|
||||
O_RDONLY | O_CLOEXEC,
|
||||
0,
|
||||
)
|
||||
.expect("failed to open dynamic linker"),
|
||||
)
|
||||
.to_upper()
|
||||
.unwrap();
|
||||
|
||||
fexec_impl(
|
||||
interp_file,
|
||||
init_thr_fd,
|
||||
init_proc_fd,
|
||||
exe_path.as_bytes(),
|
||||
&[exe_path.as_bytes()],
|
||||
&envs,
|
||||
&extrainfo,
|
||||
Some(interp_override),
|
||||
)
|
||||
.expect("failed to execute init");
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub(crate) fn spawn(
|
||||
name: &str,
|
||||
auth: FdGuard,
|
||||
this_thr_fd: &FdGuardUpper,
|
||||
scheme_creation_cap: FdGuard,
|
||||
kernel_schemes: KernelSchemeMap,
|
||||
nonblock: bool,
|
||||
inner: impl FnOnce(FdGuard, Socket, FdGuard, KernelSchemeMap) -> !,
|
||||
) -> (FdGuard, FdGuard, KernelSchemeMap, FdGuard) {
|
||||
let read = FdGuard::new(
|
||||
syscall::openat(
|
||||
kernel_schemes
|
||||
.get(GlobalSchemes::Pipe)
|
||||
.expect("failed to get pipe fd")
|
||||
.as_raw_fd(),
|
||||
"",
|
||||
O_CLOEXEC,
|
||||
0,
|
||||
)
|
||||
.expect("failed to open sync read pipe"),
|
||||
);
|
||||
|
||||
// The write pipe will not inherit O_CLOEXEC, but is closed by the daemon later.
|
||||
let write = FdGuard::new(
|
||||
syscall::dup(read.as_raw_fd(), b"write").expect("failed to open sync write pipe"),
|
||||
);
|
||||
|
||||
match fork_impl(&ForkArgs::Init {
|
||||
this_thr_fd,
|
||||
auth: &auth,
|
||||
}) {
|
||||
Err(err) => {
|
||||
panic!("Failed to fork in order to start {name}: {err}");
|
||||
}
|
||||
// Continue serving the scheme as the child.
|
||||
Ok(0) => {
|
||||
drop(read);
|
||||
|
||||
let socket = Socket::create_inner(scheme_creation_cap.as_raw_fd(), nonblock)
|
||||
.expect("failed to open proc scheme socket");
|
||||
drop(scheme_creation_cap);
|
||||
|
||||
inner(write, socket, auth, kernel_schemes)
|
||||
}
|
||||
// Return in order to execute init, as the parent.
|
||||
Ok(_) => {
|
||||
drop(write);
|
||||
|
||||
let mut new_fd = usize::MAX;
|
||||
let fd_bytes = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
core::slice::from_mut(&mut new_fd).as_mut_ptr() as *mut u8,
|
||||
core::mem::size_of::<usize>(),
|
||||
)
|
||||
};
|
||||
loop {
|
||||
match syscall::call_ro(
|
||||
read.as_raw_fd(),
|
||||
fd_bytes,
|
||||
CallFlags::FD | CallFlags::FD_UPPER,
|
||||
&[],
|
||||
) {
|
||||
Err(Error { errno: EINTR }) => continue,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
scheme_creation_cap,
|
||||
auth,
|
||||
kernel_schemes,
|
||||
FdGuard::new(new_fd),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
ENTRY(_start)
|
||||
OUTPUT_FORMAT(elf32-i386)
|
||||
|
||||
SECTIONS {
|
||||
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
|
||||
__initfs_header = . - 4096;
|
||||
. += SIZEOF_HEADERS;
|
||||
. = ALIGN(4096);
|
||||
|
||||
.text : {
|
||||
__text_start = .;
|
||||
*(.text*)
|
||||
. = ALIGN(4096);
|
||||
__text_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
__rodata_start = .;
|
||||
*(.rodata*)
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got)
|
||||
}
|
||||
.got.plt : {
|
||||
*(.got.plt)
|
||||
. = ALIGN(4096);
|
||||
__rodata_end = .;
|
||||
}
|
||||
.data : {
|
||||
__data_start = .;
|
||||
*(.data*)
|
||||
. = ALIGN(4096);
|
||||
__data_end = .;
|
||||
|
||||
*(.tbss*)
|
||||
. = ALIGN(4096);
|
||||
*(.tdata*)
|
||||
. = ALIGN(4096);
|
||||
|
||||
__bss_start = .;
|
||||
*(.bss*)
|
||||
. = ALIGN(4096);
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.rel.eh_frame*)
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
ENTRY(_start)
|
||||
OUTPUT_FORMAT(elf32-i386)
|
||||
|
||||
SECTIONS {
|
||||
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
|
||||
__initfs_header = . - 4096;
|
||||
. += SIZEOF_HEADERS;
|
||||
. = ALIGN(4096);
|
||||
|
||||
.text : {
|
||||
__text_start = .;
|
||||
*(.text*)
|
||||
. = ALIGN(4096);
|
||||
__text_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
__rodata_start = .;
|
||||
*(.rodata*)
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got)
|
||||
}
|
||||
.got.plt : {
|
||||
*(.got.plt)
|
||||
. = ALIGN(4096);
|
||||
__rodata_end = .;
|
||||
}
|
||||
.data : {
|
||||
__data_start = .;
|
||||
*(.data*)
|
||||
. = ALIGN(4096);
|
||||
__data_end = .;
|
||||
|
||||
*(.tbss*)
|
||||
. = ALIGN(4096);
|
||||
*(.tdata*)
|
||||
. = ALIGN(4096);
|
||||
|
||||
__bss_start = .;
|
||||
*(.bss*)
|
||||
. = ALIGN(4096);
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.rel.eh_frame*)
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use core::mem;
|
||||
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
|
||||
|
||||
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
|
||||
pub const USERMODE_END: usize = 0x8000_0000;
|
||||
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
|
||||
|
||||
static MAP: Map = Map {
|
||||
offset: 0,
|
||||
size: STACK_SIZE,
|
||||
flags: MapFlags::PROT_READ
|
||||
.union(MapFlags::PROT_WRITE)
|
||||
.union(MapFlags::MAP_PRIVATE)
|
||||
.union(MapFlags::MAP_FIXED_NOREPLACE),
|
||||
address: STACK_START, // highest possible user address
|
||||
};
|
||||
|
||||
core::arch::global_asm!(
|
||||
"
|
||||
.globl _start
|
||||
_start:
|
||||
# Setup a stack.
|
||||
mov eax, {number}
|
||||
mov ebx, {fd}
|
||||
mov ecx, offset {map} # pointer to Map struct
|
||||
mov edx, {map_size} # size of Map struct
|
||||
int 0x80
|
||||
|
||||
# Test for success (nonzero value).
|
||||
cmp eax, 0
|
||||
jg 1f
|
||||
# (failure)
|
||||
ud2
|
||||
1:
|
||||
# Subtract 16 since all instructions seem to hate non-canonical ESP values :)
|
||||
lea esp, [eax+{stack_size}-16]
|
||||
mov ebp, esp
|
||||
|
||||
# Stack has the same alignment as `size`.
|
||||
call start
|
||||
# `start` must never return.
|
||||
ud2
|
||||
",
|
||||
fd = const usize::MAX, // dummy fd indicates anonymous map
|
||||
map = sym MAP,
|
||||
map_size = const mem::size_of::<Map>(),
|
||||
number = const SYS_FMAP,
|
||||
stack_size = const STACK_SIZE,
|
||||
);
|
||||
@@ -1,485 +0,0 @@
|
||||
use core::convert::TryFrom;
|
||||
#[allow(deprecated)]
|
||||
use core::hash::{BuildHasherDefault, SipHasher};
|
||||
use core::str;
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use redox_initfs::{InitFs, Inode, InodeDir, InodeKind, InodeStruct};
|
||||
|
||||
use redox_rt::proc::FdGuard;
|
||||
use redox_scheme::{
|
||||
CallerCtx, OpenResult, RequestKind,
|
||||
scheme::{SchemeState, SchemeSync},
|
||||
};
|
||||
|
||||
use redox_scheme::{SignalBehavior, Socket};
|
||||
use syscall::PAGE_SIZE;
|
||||
use syscall::data::Stat;
|
||||
use syscall::dirent::DirEntry;
|
||||
use syscall::dirent::DirentBuf;
|
||||
use syscall::dirent::DirentKind;
|
||||
use syscall::error::*;
|
||||
use syscall::flag::*;
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
|
||||
enum Handle {
|
||||
Node(Node),
|
||||
SchemeRoot,
|
||||
}
|
||||
impl Handle {
|
||||
fn as_node(&self) -> Result<&Node> {
|
||||
match self {
|
||||
Handle::Node(n) => Ok(n),
|
||||
_ => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
fn as_node_mut(&mut self) -> Result<&mut Node> {
|
||||
match self {
|
||||
Handle::Node(n) => Ok(n),
|
||||
_ => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Node {
|
||||
inode: Inode,
|
||||
// TODO: Any better way to implement fpath? Or maybe work around it, e.g. by giving paths such
|
||||
// as `initfs:__inodes__/<inode>`?
|
||||
filename: String,
|
||||
}
|
||||
pub struct InitFsScheme {
|
||||
#[allow(deprecated)]
|
||||
handles: HashMap<usize, Handle, BuildHasherDefault<SipHasher>>,
|
||||
next_id: usize,
|
||||
fs: InitFs<'static>,
|
||||
}
|
||||
impl InitFsScheme {
|
||||
pub fn new(bytes: &'static [u8]) -> Self {
|
||||
Self {
|
||||
handles: HashMap::default(),
|
||||
next_id: 0,
|
||||
fs: InitFs::new(bytes, Some(PAGE_SIZE.try_into().unwrap()))
|
||||
.expect("failed to parse initfs"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inode(fs: &InitFs<'static>, inode: Inode) -> Result<InodeStruct<'static>> {
|
||||
fs.get_inode(inode).ok_or_else(|| Error::new(EIO))
|
||||
}
|
||||
fn next_id(&mut self) -> usize {
|
||||
assert_ne!(self.next_id, usize::MAX, "usize overflow in initfs scheme");
|
||||
self.next_id += 1;
|
||||
self.next_id
|
||||
}
|
||||
}
|
||||
|
||||
struct Iter {
|
||||
dir: InodeDir<'static>,
|
||||
idx: u32,
|
||||
}
|
||||
impl Iterator for Iter {
|
||||
type Item = Result<redox_initfs::Entry<'static>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let entry = self.dir.get_entry(self.idx).map_err(|_| Error::new(EIO));
|
||||
self.idx += 1;
|
||||
entry.transpose()
|
||||
}
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self.dir.entry_count().ok() {
|
||||
Some(size) => {
|
||||
let size =
|
||||
usize::try_from(size).expect("expected u32 to be convertible into usize");
|
||||
(size, Some(size))
|
||||
}
|
||||
None => (0, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inode_len(inode: InodeStruct<'static>) -> Result<usize> {
|
||||
Ok(match inode.kind() {
|
||||
InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?.len(),
|
||||
InodeKind::Dir(dir) => (Iter { dir, idx: 0 }).fold(0, |len, entry| {
|
||||
len + entry
|
||||
.and_then(|entry| entry.name().map_err(|_| Error::new(EIO)))
|
||||
.map_or(0, |name| name.len() + 1)
|
||||
}),
|
||||
InodeKind::Link(link) => link.data().map_err(|_| Error::new(EIO))?.len(),
|
||||
InodeKind::Unknown => return Err(Error::new(EIO)),
|
||||
})
|
||||
}
|
||||
|
||||
impl SchemeSync for InitFsScheme {
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if !matches!(
|
||||
self.handles.get(&dirfd).ok_or(Error::new(EBADF))?,
|
||||
Handle::SchemeRoot
|
||||
) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
let mut components = path
|
||||
// trim leading and trailing slash
|
||||
.trim_matches('/')
|
||||
// divide into components
|
||||
.split('/')
|
||||
// filter out double slashes (e.g. /usr//bin/...)
|
||||
.filter(|c| !c.is_empty());
|
||||
|
||||
let mut current_inode = self.fs.root_inode();
|
||||
|
||||
while let Some(component) = components.next() {
|
||||
match component {
|
||||
"." => continue,
|
||||
".." => {
|
||||
let _ = components.next_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let current_inode_struct = Self::get_inode(&self.fs, current_inode)?;
|
||||
|
||||
let dir = match current_inode_struct.kind() {
|
||||
InodeKind::Dir(dir) => dir,
|
||||
|
||||
// TODO: Support symlinks in other position than xopen target
|
||||
InodeKind::Link(_) => {
|
||||
return Err(Error::new(EOPNOTSUPP));
|
||||
}
|
||||
|
||||
// If we still have more components in the path, and the file tree for that
|
||||
// particular branch is not all directories except the last, then that file cannot
|
||||
// exist.
|
||||
InodeKind::File(_) | InodeKind::Unknown => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
|
||||
let mut entries = Iter { dir, idx: 0 };
|
||||
|
||||
current_inode = loop {
|
||||
let entry_res = match entries.next() {
|
||||
Some(e) => e,
|
||||
None => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
let entry = entry_res?;
|
||||
let name = entry.name().map_err(|_| Error::new(EIO))?;
|
||||
if name == component.as_bytes() {
|
||||
break entry.inode();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// xopen target is link -- return EXDEV so that the file is opened as a link.
|
||||
// TODO: Maybe follow initfs-local symlinks here? Would be faster
|
||||
let is_link = matches!(
|
||||
Self::get_inode(&self.fs, current_inode)?.kind(),
|
||||
InodeKind::Link(_)
|
||||
);
|
||||
let o_stat_nofollow = flags & O_STAT != 0 && flags & O_NOFOLLOW != 0;
|
||||
let o_symlink = flags & O_SYMLINK != 0;
|
||||
if is_link && !o_stat_nofollow && !o_symlink {
|
||||
return Err(Error::new(EXDEV));
|
||||
}
|
||||
|
||||
let id = self.next_id();
|
||||
let old = self.handles.insert(
|
||||
id,
|
||||
Handle::Node(Node {
|
||||
inode: current_inode,
|
||||
filename: path.into(),
|
||||
}),
|
||||
);
|
||||
assert!(old.is_none());
|
||||
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: id,
|
||||
flags: NewFdFlags::POSITIONED,
|
||||
})
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buffer: &mut [u8],
|
||||
offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let Ok(offset) = usize::try_from(offset) else {
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
let handle = self
|
||||
.handles
|
||||
.get_mut(&id)
|
||||
.ok_or(Error::new(EBADF))?
|
||||
.as_node_mut()?;
|
||||
|
||||
match Self::get_inode(&self.fs, handle.inode)?.kind() {
|
||||
InodeKind::File(file) => {
|
||||
let data = file.data().map_err(|_| Error::new(EIO))?;
|
||||
let src_buf = &data[core::cmp::min(offset, data.len())..];
|
||||
|
||||
let to_copy = core::cmp::min(src_buf.len(), buffer.len());
|
||||
buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]);
|
||||
|
||||
Ok(to_copy)
|
||||
}
|
||||
InodeKind::Dir(_) => Err(Error::new(EISDIR)),
|
||||
InodeKind::Link(link) => {
|
||||
let link_data = link.data().map_err(|_| Error::new(EIO))?;
|
||||
let src_buf = &link_data[core::cmp::min(offset, link_data.len())..];
|
||||
|
||||
let to_copy = core::cmp::min(src_buf.len(), buffer.len());
|
||||
buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]);
|
||||
|
||||
Ok(to_copy)
|
||||
}
|
||||
InodeKind::Unknown => Err(Error::new(EIO)),
|
||||
}
|
||||
}
|
||||
fn getdents<'buf>(
|
||||
&mut self,
|
||||
id: usize,
|
||||
mut buf: DirentBuf<&'buf mut [u8]>,
|
||||
opaque_offset: u64,
|
||||
) -> Result<DirentBuf<&'buf mut [u8]>> {
|
||||
let Ok(offset) = u32::try_from(opaque_offset) else {
|
||||
return Ok(buf);
|
||||
};
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
|
||||
let InodeKind::Dir(dir) = Self::get_inode(&self.fs, handle.inode)?.kind() else {
|
||||
return Err(Error::new(ENOTDIR));
|
||||
};
|
||||
let iter = Iter { dir, idx: offset };
|
||||
for (index, entry) in iter.enumerate() {
|
||||
let entry = entry?;
|
||||
buf.entry(DirEntry {
|
||||
// TODO: Add getter
|
||||
//inode: entry.inode(),
|
||||
inode: 0,
|
||||
|
||||
name: entry
|
||||
.name()
|
||||
.ok()
|
||||
.and_then(|utf8| core::str::from_utf8(utf8).ok())
|
||||
.ok_or(Error::new(EIO))?,
|
||||
next_opaque_id: index as u64 + 1,
|
||||
kind: DirentKind::Unspecified,
|
||||
})?;
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result<u64> {
|
||||
let handle = self
|
||||
.handles
|
||||
.get_mut(&id)
|
||||
.ok_or(Error::new(EBADF))?
|
||||
.as_node_mut()?;
|
||||
|
||||
Ok(inode_len(Self::get_inode(&self.fs, handle.inode)?)? as u64)
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
|
||||
let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
|
||||
|
||||
// TODO: Copy scheme part in kernel
|
||||
let scheme_path = b"/scheme/initfs";
|
||||
let scheme_bytes = core::cmp::min(scheme_path.len(), buf.len());
|
||||
buf[..scheme_bytes].copy_from_slice(&scheme_path[..scheme_bytes]);
|
||||
|
||||
let source = handle.filename.as_bytes();
|
||||
let path_bytes = core::cmp::min(buf.len() - scheme_bytes, source.len());
|
||||
buf[scheme_bytes..scheme_bytes + path_bytes].copy_from_slice(&source[..path_bytes]);
|
||||
|
||||
Ok(scheme_bytes + path_bytes)
|
||||
}
|
||||
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
|
||||
|
||||
let inode = Self::get_inode(&self.fs, handle.inode)?;
|
||||
|
||||
stat.st_ino = inode.id();
|
||||
stat.st_mode = inode.mode()
|
||||
| match inode.kind() {
|
||||
InodeKind::Dir(_) => MODE_DIR,
|
||||
InodeKind::File(_) => MODE_FILE,
|
||||
InodeKind::Link(_) => MODE_SYMLINK,
|
||||
_ => 0,
|
||||
};
|
||||
stat.st_uid = 0;
|
||||
stat.st_gid = 0;
|
||||
stat.st_size = u64::try_from(inode_len(inode)?).unwrap_or(u64::MAX);
|
||||
|
||||
stat.st_ctime = 0;
|
||||
stat.st_ctime_nsec = 0;
|
||||
stat.st_mtime = 0;
|
||||
stat.st_mtime_nsec = 0;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
|
||||
if !self.handles.contains_key(&id) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mmap_prep(
|
||||
&mut self,
|
||||
id: usize,
|
||||
offset: u64,
|
||||
size: usize,
|
||||
flags: MapFlags,
|
||||
_ctx: &CallerCtx,
|
||||
) -> syscall::Result<usize> {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
let Handle::Node(node) = handle else {
|
||||
return Err(Error::new(EBADF));
|
||||
};
|
||||
let data = match Self::get_inode(&self.fs, node.inode)?.kind() {
|
||||
InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?,
|
||||
InodeKind::Dir(_) => return Err(Error::new(EISDIR)),
|
||||
InodeKind::Link(_) => return Err(Error::new(ELOOP)),
|
||||
InodeKind::Unknown => return Err(Error::new(EIO)),
|
||||
};
|
||||
|
||||
if flags.contains(MapFlags::PROT_WRITE) {
|
||||
return Err(Error::new(EPERM));
|
||||
}
|
||||
|
||||
let Some(last_addr) = offset.checked_add(size as u64) else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
|
||||
if last_addr > data.len().next_multiple_of(PAGE_SIZE) as u64 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
Ok(data.as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(bytes: &'static [u8], sync_pipe: FdGuard, socket: Socket) -> ! {
|
||||
log::info!("bootstrap: starting initfs scheme");
|
||||
let mut state = SchemeState::new();
|
||||
let mut scheme = InitFsScheme::new(bytes);
|
||||
|
||||
// send open-capability to bootstrap
|
||||
let new_id = scheme.next_id();
|
||||
scheme.handles.insert(new_id, Handle::SchemeRoot);
|
||||
let cap_fd = socket
|
||||
.create_this_scheme_fd(0, new_id, 0, 0)
|
||||
.expect("failed to issue initfs root fd");
|
||||
let _ = syscall::call_rw(
|
||||
sync_pipe.as_raw_fd(),
|
||||
&mut cap_fd.to_ne_bytes(),
|
||||
CallFlags::FD,
|
||||
&[],
|
||||
);
|
||||
drop(sync_pipe);
|
||||
|
||||
loop {
|
||||
let Some(req) = socket
|
||||
.next_request(SignalBehavior::Restart)
|
||||
.expect("bootstrap: failed to read scheme request from kernel")
|
||||
else {
|
||||
break;
|
||||
};
|
||||
match req.kind() {
|
||||
RequestKind::Call(req) => {
|
||||
let resp = req.handle_sync(&mut scheme, &mut state);
|
||||
|
||||
if !socket
|
||||
.write_response(resp, SignalBehavior::Restart)
|
||||
.expect("bootstrap: failed to write scheme response to kernel")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
RequestKind::OnClose { id } => {
|
||||
scheme.handles.remove(&id);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
// TODO: Restructure bootstrap so it calls into relibc, or a split-off derivative without the C
|
||||
// parts, such as "redox-rt".
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn redox_read_v1(fd: usize, ptr: *mut u8, len: usize) -> isize {
|
||||
Error::mux(syscall::read(fd, unsafe {
|
||||
core::slice::from_raw_parts_mut(ptr, len)
|
||||
})) as isize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn redox_write_v1(fd: usize, ptr: *const u8, len: usize) -> isize {
|
||||
Error::mux(syscall::write(fd, unsafe {
|
||||
core::slice::from_raw_parts(ptr, len)
|
||||
})) as isize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe fn redox_dup_v1(fd: usize, buf: *const u8, len: usize) -> isize {
|
||||
Error::mux(syscall::dup(fd, unsafe {
|
||||
core::slice::from_raw_parts(buf, len)
|
||||
})) as isize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn redox_close_v1(fd: usize) -> isize {
|
||||
Error::mux(syscall::close(fd)) as isize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn redox_sys_call_v0(
|
||||
fd: usize,
|
||||
payload: *mut u8,
|
||||
payload_len: usize,
|
||||
flags: usize,
|
||||
metadata: *const u64,
|
||||
metadata_len: usize,
|
||||
) -> isize {
|
||||
let flags = CallFlags::from_bits_retain(flags);
|
||||
|
||||
let metadata = unsafe { core::slice::from_raw_parts(metadata, metadata_len) };
|
||||
|
||||
let result = if flags.contains(CallFlags::READ) {
|
||||
let payload = unsafe { core::slice::from_raw_parts_mut(payload, payload_len) };
|
||||
if flags.contains(CallFlags::WRITE) {
|
||||
syscall::call_rw(fd, payload, flags, metadata)
|
||||
} else {
|
||||
syscall::call_ro(fd, payload, flags, metadata)
|
||||
}
|
||||
} else {
|
||||
let payload = unsafe { core::slice::from_raw_parts(payload, payload_len) };
|
||||
syscall::call_wo(fd, payload, flags, metadata)
|
||||
};
|
||||
|
||||
Error::mux(result) as isize
|
||||
}
|
||||
@@ -1,560 +0,0 @@
|
||||
use alloc::rc::Rc;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use core::cell::RefCell;
|
||||
use core::fmt::Debug;
|
||||
use core::mem;
|
||||
use hashbrown::HashMap;
|
||||
use libredox::protocol::{NsDup, NsPermissions};
|
||||
use log::{error, warn};
|
||||
use redox_path::RedoxPath;
|
||||
use redox_path::RedoxScheme;
|
||||
use redox_rt::proc::FdGuard;
|
||||
use redox_scheme::{
|
||||
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
|
||||
scheme::{SchemeState, SchemeSync},
|
||||
};
|
||||
use syscall::Stat;
|
||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
||||
use syscall::{CallFlags, FobtainFdFlags, error::*, schemev2::NewFdFlags};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Namespace {
|
||||
schemes: HashMap<String, Arc<FdGuard>>,
|
||||
}
|
||||
|
||||
impl Namespace {
|
||||
fn fork(&self, buf: &[u8]) -> Result<Self> {
|
||||
let mut schemes = HashMap::new();
|
||||
let mut cursor = 0;
|
||||
while cursor < buf.len() {
|
||||
let len = read_num::<usize>(&buf[cursor..])?;
|
||||
cursor += mem::size_of::<usize>();
|
||||
let name = String::from_utf8(Vec::from(&buf[cursor..cursor + len]))
|
||||
.map_err(|_| Error::new(EINVAL))?;
|
||||
cursor += len;
|
||||
if name.ends_with('*') {
|
||||
let prefix = &name[..name.len() - 1];
|
||||
for (registered_name, fd) in &self.schemes {
|
||||
if registered_name.starts_with(prefix) {
|
||||
schemes.insert(registered_name.clone(), fd.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let Some(fd) = self.schemes.get(&name) else {
|
||||
warn!("Scheme {} not found in namespace", name);
|
||||
continue;
|
||||
};
|
||||
schemes.insert(name, fd.clone());
|
||||
}
|
||||
}
|
||||
Ok(Self { schemes })
|
||||
}
|
||||
fn get_scheme_fd(&self, scheme: &str) -> Option<&Arc<FdGuard>> {
|
||||
self.schemes.get(scheme)
|
||||
}
|
||||
fn remove_scheme(&mut self, scheme: &str) -> Option<()> {
|
||||
self.schemes.remove(scheme).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct NamespaceAccess {
|
||||
namespace: Rc<RefCell<Namespace>>,
|
||||
permission: NsPermissions,
|
||||
}
|
||||
|
||||
impl NamespaceAccess {
|
||||
fn has_permission(&self, permission: NsPermissions) -> bool {
|
||||
self.permission.contains(permission)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SchemeRegister {
|
||||
target_namespace: Rc<RefCell<Namespace>>,
|
||||
scheme_name: String,
|
||||
}
|
||||
|
||||
impl SchemeRegister {
|
||||
fn register(&self, fd: FdGuard) -> Result<()> {
|
||||
let mut ns = self.target_namespace.borrow_mut();
|
||||
if ns.schemes.contains_key(&self.scheme_name) {
|
||||
return Err(Error::new(EEXIST));
|
||||
}
|
||||
ns.schemes.insert(self.scheme_name.clone(), Arc::new(fd));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Handle {
|
||||
Access(NamespaceAccess),
|
||||
Register(SchemeRegister),
|
||||
List(NamespaceAccess),
|
||||
}
|
||||
|
||||
pub struct NamespaceScheme<'sock> {
|
||||
socket: &'sock Socket,
|
||||
handles: HashMap<usize, Handle>,
|
||||
root_namespace: Namespace,
|
||||
next_id: usize,
|
||||
scheme_creation_cap: FdGuard,
|
||||
}
|
||||
|
||||
const HIGH_PERMISSIONS: NsPermissions = NsPermissions::SCHEME_CREATE;
|
||||
|
||||
impl<'sock> NamespaceScheme<'sock> {
|
||||
pub fn new(
|
||||
socket: &'sock Socket,
|
||||
schemes: HashMap<String, Arc<FdGuard>>,
|
||||
scheme_creation_cap: FdGuard,
|
||||
) -> Self {
|
||||
Self {
|
||||
socket,
|
||||
handles: HashMap::new(),
|
||||
root_namespace: Namespace { schemes },
|
||||
next_id: 0,
|
||||
scheme_creation_cap,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_namespace(&mut self, id: usize, schemes: Namespace, permission: NsPermissions) {
|
||||
let handle = Handle::Access(NamespaceAccess {
|
||||
namespace: Rc::new(RefCell::new(schemes)),
|
||||
permission,
|
||||
});
|
||||
self.handles.insert(id, handle);
|
||||
}
|
||||
|
||||
fn get_ns_access(&self, id: usize) -> Option<&NamespaceAccess> {
|
||||
let handle = self.handles.get(&id);
|
||||
match handle {
|
||||
Some(Handle::Access(access)) => Some(access),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn open_namespace_resource(
|
||||
&self,
|
||||
ns_access: &NamespaceAccess,
|
||||
reference: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
match reference {
|
||||
"scheme-creation-cap" => {
|
||||
if !ns_access.has_permission(NsPermissions::SCHEME_CREATE) {
|
||||
error!("Permission denied to get scheme creation capability");
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
Ok(syscall::dup(self.scheme_creation_cap.as_raw_fd(), &[])?)
|
||||
}
|
||||
_ => {
|
||||
error!("Unknown special reference: {}", reference);
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_scheme_resource(
|
||||
&self,
|
||||
ns: &Namespace,
|
||||
scheme: &str,
|
||||
reference: &str,
|
||||
flags: usize,
|
||||
fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let Some(cap_fd) = ns.get_scheme_fd(scheme) else {
|
||||
log::info!("Scheme {:?} not found in namespace", scheme);
|
||||
return Err(Error::new(ENODEV));
|
||||
};
|
||||
|
||||
let scheme_fd = syscall::openat_with_filter(
|
||||
cap_fd.as_raw_fd(),
|
||||
reference,
|
||||
flags,
|
||||
fcntl_flags as usize,
|
||||
ctx.uid,
|
||||
ctx.gid,
|
||||
)?;
|
||||
|
||||
Ok(scheme_fd)
|
||||
}
|
||||
|
||||
fn fork_namespace(&mut self, namespace: Rc<RefCell<Namespace>>, names: &[u8]) -> Result<usize> {
|
||||
let new_id = self.next_id;
|
||||
let new_namespace = namespace.borrow().fork(names).map_err(|e| {
|
||||
error!("Failed to fork namespace {}: {}", new_id, e);
|
||||
e
|
||||
})?;
|
||||
self.add_namespace(
|
||||
new_id,
|
||||
new_namespace,
|
||||
NsPermissions::all().difference(HIGH_PERMISSIONS),
|
||||
);
|
||||
self.next_id += 1;
|
||||
Ok(new_id)
|
||||
}
|
||||
|
||||
fn shrink_permissions(
|
||||
&mut self,
|
||||
mut ns: NamespaceAccess,
|
||||
permission: NsPermissions,
|
||||
) -> Result<usize> {
|
||||
ns.permission = ns.permission.intersection(permission);
|
||||
let next_id = self.next_id;
|
||||
self.handles.insert(next_id, Handle::Access(ns));
|
||||
self.next_id += 1;
|
||||
Ok(next_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'sock> SchemeSync for NamespaceScheme<'sock> {
|
||||
fn openat(
|
||||
&mut self,
|
||||
fd: usize,
|
||||
path: &str,
|
||||
flags: usize,
|
||||
fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
let ns_access = {
|
||||
let handle = self.handles.get(&fd);
|
||||
match handle {
|
||||
Some(Handle::Access(access)) => Some(access),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
error!("Namespace with ID {} not found", fd);
|
||||
Error::new(ENOENT)
|
||||
})?;
|
||||
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
|
||||
let (scheme, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
|
||||
|
||||
let res_fd = match scheme.as_ref() {
|
||||
"namespace" => self.open_namespace_resource(
|
||||
ns_access,
|
||||
reference.as_ref(),
|
||||
flags,
|
||||
fcntl_flags,
|
||||
ctx,
|
||||
)?,
|
||||
"" => {
|
||||
if !ns_access.has_permission(NsPermissions::LIST) {
|
||||
error!("Permission denied to list schemes in namespace {}", fd);
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
let new_id = self.next_id;
|
||||
self.next_id += 1;
|
||||
|
||||
self.handles.insert(new_id, Handle::List(ns_access.clone()));
|
||||
|
||||
return Ok(OpenResult::ThisScheme {
|
||||
number: new_id,
|
||||
flags: NewFdFlags::empty(),
|
||||
});
|
||||
}
|
||||
_ => self.open_scheme_resource(
|
||||
&ns_access.namespace.borrow(),
|
||||
scheme.as_ref(),
|
||||
reference.as_ref(),
|
||||
flags,
|
||||
fcntl_flags,
|
||||
ctx,
|
||||
)?,
|
||||
};
|
||||
|
||||
Ok(OpenResult::OtherScheme { fd: res_fd })
|
||||
}
|
||||
|
||||
fn dup(&mut self, id: usize, buf: &[u8], _ctx: &CallerCtx) -> Result<OpenResult> {
|
||||
let ns_access = self.get_ns_access(id).ok_or_else(|| {
|
||||
error!("Namespace with ID {} not found", id);
|
||||
Error::new(ENOENT)
|
||||
})?;
|
||||
|
||||
let raw_kind = read_num::<usize>(buf)?;
|
||||
let Some(kind) = NsDup::try_from_raw(raw_kind) else {
|
||||
error!("Unknown dup kind: {}", raw_kind);
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
let payload = &buf[mem::size_of::<NsDup>()..];
|
||||
let new_id = match kind {
|
||||
NsDup::ForkNs => {
|
||||
let ns = ns_access.namespace.clone();
|
||||
let _ = ns_access;
|
||||
self.fork_namespace(ns, payload)?
|
||||
}
|
||||
NsDup::ShrinkPermissions => self.shrink_permissions(
|
||||
ns_access.clone(),
|
||||
NsPermissions::from_bits_truncate(read_num::<usize>(payload)?),
|
||||
)?,
|
||||
NsDup::IssueRegister => {
|
||||
let name = core::str::from_utf8(payload).map_err(|_| Error::new(EINVAL))?;
|
||||
let scheme_name = RedoxScheme::new(name).ok_or_else(|| {
|
||||
error!("Invalid scheme name: {}", name);
|
||||
Error::new(EINVAL)
|
||||
})?;
|
||||
|
||||
if !ns_access.has_permission(NsPermissions::INSERT) {
|
||||
error!(
|
||||
"Permission denied to issue register capability for namespace {}",
|
||||
id
|
||||
);
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
let new_id = self.next_id;
|
||||
let register_cap = Handle::Register(SchemeRegister {
|
||||
target_namespace: ns_access.namespace.clone(),
|
||||
scheme_name: scheme_name.as_ref().to_string(),
|
||||
});
|
||||
self.handles.insert(new_id, register_cap);
|
||||
self.next_id += 1;
|
||||
new_id
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: new_id,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
fn unlinkat(&mut self, fd: usize, path: &str, flags: usize, ctx: &CallerCtx) -> Result<()> {
|
||||
let ns_access = self.get_ns_access(fd).ok_or_else(|| {
|
||||
error!("Namespace with ID {} not found", fd);
|
||||
Error::new(ENOENT)
|
||||
})?;
|
||||
let mut ns = ns_access.namespace.borrow_mut();
|
||||
|
||||
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
|
||||
let (scheme, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
|
||||
if reference.as_ref().is_empty() {
|
||||
if !ns_access.has_permission(NsPermissions::DELETE) {
|
||||
error!("Permission denied to remove scheme for namespace {}", fd);
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
match ns.remove_scheme(scheme.as_ref()) {
|
||||
Some(_) => return Ok(()),
|
||||
None => {
|
||||
error!("Scheme {} not found in namespace", scheme);
|
||||
return Err(Error::new(ENODEV));
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(cap_fd) = ns.get_scheme_fd(scheme.as_ref()) else {
|
||||
error!("Scheme {} not found in namespace", scheme);
|
||||
return Err(Error::new(ENODEV));
|
||||
};
|
||||
|
||||
syscall::unlinkat_with_filter(cap_fd.as_raw_fd(), reference, flags, ctx.uid, ctx.gid)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.remove(&id);
|
||||
}
|
||||
|
||||
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
|
||||
let namespace_id = sendfd_request.id();
|
||||
let num_fds = sendfd_request.num_fds();
|
||||
|
||||
let handle = self.handles.get(&namespace_id).ok_or_else(|| {
|
||||
error!("Namespace with ID {} not found", namespace_id);
|
||||
Error::new(ENOENT)
|
||||
})?;
|
||||
let Handle::Register(register_cap) = handle else {
|
||||
error!(
|
||||
"Handle with ID {} is not a register capability",
|
||||
namespace_id
|
||||
);
|
||||
return Err(Error::new(EACCES));
|
||||
};
|
||||
|
||||
if num_fds == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
if num_fds > 1 {
|
||||
error!("Can only send one fd at a time");
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let mut new_fd = usize::MAX;
|
||||
if let Err(e) = sendfd_request.obtain_fd(
|
||||
&self.socket,
|
||||
FobtainFdFlags::UPPER_TBL,
|
||||
core::slice::from_mut(&mut new_fd),
|
||||
) {
|
||||
error!("on_sendfd: obtain_fd failed with error: {:?}", e);
|
||||
return Err(e);
|
||||
}
|
||||
register_cap.register(FdGuard::new(new_fd))?;
|
||||
|
||||
Ok(num_fds)
|
||||
}
|
||||
|
||||
fn getdents<'buf>(
|
||||
&mut self,
|
||||
id: usize,
|
||||
mut buf: DirentBuf<&'buf mut [u8]>,
|
||||
opaque_offset: u64,
|
||||
) -> Result<DirentBuf<&'buf mut [u8]>> {
|
||||
let Handle::List(ns_access) = self.handles.get(&id).ok_or(Error::new(EBADF))? else {
|
||||
return Err(Error::new(ENOTDIR));
|
||||
};
|
||||
|
||||
if !ns_access.has_permission(NsPermissions::LIST) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
let ns = ns_access.namespace.borrow();
|
||||
|
||||
let opaque_offset = opaque_offset as usize;
|
||||
for (i, (name, _)) in ns.schemes.iter().enumerate().skip(opaque_offset) {
|
||||
if name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Err(err) = buf.entry(DirEntry {
|
||||
kind: DirentKind::Unspecified,
|
||||
name: &name.clone(),
|
||||
inode: 0,
|
||||
next_opaque_id: i as u64 + 1,
|
||||
}) {
|
||||
if err.errno == EINVAL && i > opaque_offset {
|
||||
// POSIX allows partial result of getdents
|
||||
break;
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
||||
let resource_stat = match self.handles.get(&id).ok_or(Error::new(EBADF))? {
|
||||
Handle::List(_) => Stat {
|
||||
st_mode: 0o444 | syscall::MODE_DIR,
|
||||
st_uid: 0,
|
||||
st_gid: 0,
|
||||
st_size: 0,
|
||||
..Default::default()
|
||||
},
|
||||
Handle::Access(_) | Handle::Register(_) => Stat {
|
||||
st_mode: 0o666 | syscall::MODE_FILE,
|
||||
st_uid: 0,
|
||||
st_gid: 0,
|
||||
st_size: 0,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
*stat = resource_stat;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
trait NumFromBytes: Sized + Debug {
|
||||
fn from_le_bytes_slice(buffer: &[u8]) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
macro_rules! num_from_bytes_impl {
|
||||
($($t:ty),*) => {
|
||||
$(
|
||||
impl NumFromBytes for $t {
|
||||
fn from_le_bytes_slice(buffer: &[u8]) -> Result<Self, Error> {
|
||||
let size = mem::size_of::<Self>();
|
||||
let buffer_slice = buffer.get(..size).and_then(|s| s.try_into().ok());
|
||||
|
||||
if let Some(slice) = buffer_slice {
|
||||
Ok(Self::from_le_bytes(slice))
|
||||
} else {
|
||||
error!(
|
||||
"read_num: buffer is too short to read num of size {} (buffer len: {})",
|
||||
size, buffer.len()
|
||||
);
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
num_from_bytes_impl!(usize);
|
||||
|
||||
fn read_num<T>(buffer: &[u8]) -> Result<T, Error>
|
||||
where
|
||||
T: NumFromBytes,
|
||||
{
|
||||
T::from_le_bytes_slice(buffer)
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
sync_pipe: FdGuard,
|
||||
socket: Socket,
|
||||
schemes: HashMap<String, Arc<FdGuard>>,
|
||||
scheme_creation_cap: FdGuard,
|
||||
) -> ! {
|
||||
let mut state = SchemeState::new();
|
||||
let mut scheme = NamespaceScheme::new(&socket, schemes, scheme_creation_cap);
|
||||
|
||||
// send namespace fd to bootstrap
|
||||
let new_id = scheme.next_id;
|
||||
scheme.add_namespace(new_id, scheme.root_namespace.clone(), NsPermissions::all());
|
||||
scheme.next_id += 1;
|
||||
let cap_fd = scheme
|
||||
.socket
|
||||
.create_this_scheme_fd(0, new_id, 0, 0)
|
||||
.expect("nsmgr: failed to create namespace fd");
|
||||
let _ = syscall::call_wo(
|
||||
sync_pipe.as_raw_fd(),
|
||||
&cap_fd.to_ne_bytes(),
|
||||
CallFlags::FD,
|
||||
&[],
|
||||
);
|
||||
drop(sync_pipe);
|
||||
|
||||
log::info!("bootstrap: namespace scheme start!");
|
||||
loop {
|
||||
let Some(req) = socket
|
||||
.next_request(SignalBehavior::Restart)
|
||||
.expect("bootstrap: failed to read scheme request from kernel")
|
||||
else {
|
||||
break;
|
||||
};
|
||||
match req.kind() {
|
||||
RequestKind::Call(req) => {
|
||||
let resp = req.handle_sync(&mut scheme, &mut state);
|
||||
|
||||
if !socket
|
||||
.write_response(resp, SignalBehavior::Restart)
|
||||
.expect("bootstrap: failed to write scheme response to kernel")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
RequestKind::OnClose { id } => scheme.on_close(id),
|
||||
RequestKind::SendFd(sendfd_request) => {
|
||||
let result = scheme.on_sendfd(&sendfd_request);
|
||||
let resp = Response::new(result, sendfd_request);
|
||||
if !socket
|
||||
.write_response(resp, SignalBehavior::Restart)
|
||||
.expect("bootstrap: failed to write scheme response to kernel")
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![allow(internal_features)]
|
||||
#![feature(core_intrinsics, str_from_raw_parts, never_type)]
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[path = "aarch64.rs"]
|
||||
pub mod arch;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[path = "i686.rs"]
|
||||
pub mod arch;
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[path = "x86_64.rs"]
|
||||
pub mod arch;
|
||||
|
||||
#[cfg(target_arch = "riscv64")]
|
||||
#[path = "riscv64.rs"]
|
||||
pub mod arch;
|
||||
|
||||
pub mod exec;
|
||||
pub mod initfs;
|
||||
pub mod initnsmgr;
|
||||
pub mod procmgr;
|
||||
pub mod start;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use redox_rt::proc::FdGuard;
|
||||
use syscall::data::Map;
|
||||
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
|
||||
use syscall::flag::MapFlags;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
|
||||
use core::fmt::Write;
|
||||
|
||||
struct Writer;
|
||||
|
||||
impl Write for Writer {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
syscall::write(1, s.as_bytes())
|
||||
.map_err(|_| core::fmt::Error)
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
let _ = writeln!(&mut Writer, "{}", info);
|
||||
core::intrinsics::abort();
|
||||
}
|
||||
|
||||
const HEAP_OFF: usize = arch::USERMODE_END / 2;
|
||||
|
||||
struct Allocator;
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: Allocator = Allocator;
|
||||
|
||||
struct AllocStateInner {
|
||||
heap: Option<linked_list_allocator::Heap>,
|
||||
heap_top: usize,
|
||||
}
|
||||
struct AllocState(UnsafeCell<AllocStateInner>);
|
||||
unsafe impl Send for AllocState {}
|
||||
unsafe impl Sync for AllocState {}
|
||||
static ALLOC_STATE: AllocState = AllocState(UnsafeCell::new(AllocStateInner {
|
||||
heap: None,
|
||||
heap_top: HEAP_OFF + SIZE,
|
||||
}));
|
||||
|
||||
const SIZE: usize = 1024 * 1024;
|
||||
const HEAP_INCREASE_BY: usize = SIZE;
|
||||
|
||||
unsafe impl alloc::alloc::GlobalAlloc for Allocator {
|
||||
unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
|
||||
let state = unsafe { &mut (*ALLOC_STATE.0.get()) };
|
||||
let heap = state.heap.get_or_insert_with(|| {
|
||||
state.heap_top = HEAP_OFF + SIZE;
|
||||
let _ = unsafe {
|
||||
syscall::fmap(
|
||||
!0,
|
||||
&Map {
|
||||
offset: 0,
|
||||
size: SIZE,
|
||||
address: HEAP_OFF,
|
||||
flags: MapFlags::PROT_WRITE
|
||||
| MapFlags::PROT_READ
|
||||
| MapFlags::MAP_PRIVATE
|
||||
| MapFlags::MAP_FIXED_NOREPLACE,
|
||||
},
|
||||
)
|
||||
}
|
||||
.expect("failed to map initial heap");
|
||||
unsafe { linked_list_allocator::Heap::new(HEAP_OFF as *mut u8, SIZE) }
|
||||
});
|
||||
|
||||
match heap.allocate_first_fit(layout) {
|
||||
Ok(p) => p.as_ptr(),
|
||||
Err(_) => {
|
||||
if layout.size() > HEAP_INCREASE_BY || layout.align() > 4096 {
|
||||
return core::ptr::null_mut();
|
||||
}
|
||||
|
||||
let _ = unsafe {
|
||||
syscall::fmap(
|
||||
!0,
|
||||
&Map {
|
||||
offset: 0,
|
||||
size: HEAP_INCREASE_BY,
|
||||
address: state.heap_top,
|
||||
flags: MapFlags::PROT_WRITE
|
||||
| MapFlags::PROT_READ
|
||||
| MapFlags::MAP_PRIVATE
|
||||
| MapFlags::MAP_FIXED_NOREPLACE,
|
||||
},
|
||||
)
|
||||
}
|
||||
.expect("failed to extend heap");
|
||||
unsafe { heap.extend(HEAP_INCREASE_BY) };
|
||||
state.heap_top += HEAP_INCREASE_BY;
|
||||
|
||||
return unsafe { self.alloc(layout) };
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
|
||||
unsafe {
|
||||
(&mut *ALLOC_STATE.0.get())
|
||||
.heap
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.deallocate(core::ptr::NonNull::new(ptr).unwrap(), layout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KernelSchemeMap(BTreeMap<GlobalSchemes, FdGuard>);
|
||||
impl KernelSchemeMap {
|
||||
fn new(kernel_scheme_infos: &[KernelSchemeInfo]) -> Self {
|
||||
let mut map = BTreeMap::new();
|
||||
for info in kernel_scheme_infos {
|
||||
if let Some(scheme_id) = GlobalSchemes::try_from_raw(info.scheme_id) {
|
||||
map.insert(scheme_id, FdGuard::new(info.fd));
|
||||
}
|
||||
}
|
||||
Self(map)
|
||||
}
|
||||
fn get(&self, scheme: GlobalSchemes) -> Option<&FdGuard> {
|
||||
self.0.get(&scheme)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
||||
ENTRY(_start)
|
||||
OUTPUT_FORMAT(elf64-littleriscv)
|
||||
|
||||
SECTIONS {
|
||||
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
|
||||
__initfs_header = . - 4096;
|
||||
. += SIZEOF_HEADERS;
|
||||
. = ALIGN(4096);
|
||||
|
||||
.text : {
|
||||
__text_start = .;
|
||||
*(.text*)
|
||||
. = ALIGN(4096);
|
||||
__text_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
__rodata_start = .;
|
||||
*(.rodata*)
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got)
|
||||
}
|
||||
.got.plt : {
|
||||
*(.got.plt)
|
||||
. = ALIGN(4096);
|
||||
__rodata_end = .;
|
||||
}
|
||||
.data : {
|
||||
__data_start = .;
|
||||
*(.data*)
|
||||
*(.sdata*)
|
||||
. = ALIGN(4096);
|
||||
__data_end = .;
|
||||
|
||||
__bss_start = .;
|
||||
*(.bss*)
|
||||
*(.sbss*)
|
||||
. = ALIGN(4096);
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.rel.eh_frame*)
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use core::mem;
|
||||
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
|
||||
|
||||
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
|
||||
pub const USERMODE_END: usize = 1 << 38; // Assuming Sv39
|
||||
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
|
||||
|
||||
static MAP: Map = Map {
|
||||
offset: 0,
|
||||
size: STACK_SIZE,
|
||||
flags: MapFlags::PROT_READ
|
||||
.union(MapFlags::PROT_WRITE)
|
||||
.union(MapFlags::MAP_PRIVATE)
|
||||
.union(MapFlags::MAP_FIXED_NOREPLACE),
|
||||
address: STACK_START, // highest possible user address
|
||||
};
|
||||
|
||||
core::arch::global_asm!(
|
||||
"
|
||||
.globl _start
|
||||
_start:
|
||||
# Setup a stack.
|
||||
li a7, {number}
|
||||
li a0, {fd}
|
||||
la a1, {map} # pointer to Map struct
|
||||
li a2, {map_size} # size of Map struct
|
||||
ecall
|
||||
|
||||
# Test for success (nonzero value).
|
||||
bne a0, x0, 2f
|
||||
# (failure)
|
||||
unimp
|
||||
2:
|
||||
li sp, {stack_size}
|
||||
add sp, sp, a0
|
||||
mv fp, x0
|
||||
|
||||
jal start
|
||||
# `start` must never return.
|
||||
unimp
|
||||
",
|
||||
fd = const usize::MAX, // dummy fd indicates anonymous map
|
||||
map = sym MAP,
|
||||
map_size = const mem::size_of::<Map>(),
|
||||
number = const SYS_FMAP,
|
||||
stack_size = const STACK_SIZE,
|
||||
);
|
||||
@@ -1,86 +0,0 @@
|
||||
use syscall::flag::MapFlags;
|
||||
|
||||
mod offsets {
|
||||
unsafe extern "C" {
|
||||
// text (R-X)
|
||||
static __text_start: u8;
|
||||
static __text_end: u8;
|
||||
// rodata (R--)
|
||||
static __rodata_start: u8;
|
||||
static __rodata_end: u8;
|
||||
// data+bss (RW-)
|
||||
static __data_start: u8;
|
||||
static __bss_end: u8;
|
||||
}
|
||||
pub fn text() -> (usize, usize) {
|
||||
unsafe {
|
||||
(
|
||||
&__text_start as *const u8 as usize,
|
||||
&__text_end as *const u8 as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
pub fn rodata() -> (usize, usize) {
|
||||
unsafe {
|
||||
(
|
||||
&__rodata_start as *const u8 as usize,
|
||||
&__rodata_end as *const u8 as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
pub fn data_and_bss() -> (usize, usize) {
|
||||
unsafe {
|
||||
(
|
||||
&__data_start as *const u8 as usize,
|
||||
&__bss_end as *const u8 as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn start() -> ! {
|
||||
// Remap self, from the previous RWX
|
||||
|
||||
let (text_start, text_end) = offsets::text();
|
||||
let (rodata_start, rodata_end) = offsets::rodata();
|
||||
let (data_start, data_end) = offsets::data_and_bss();
|
||||
|
||||
// NOTE: Assuming the debug scheme root fd is always placed at this position
|
||||
let debug_fd = syscall::UPPER_FDTBL_TAG + syscall::data::GlobalSchemes::Debug as usize;
|
||||
let _ = syscall::openat(debug_fd, "", syscall::O_RDONLY, 0); // stdin
|
||||
let _ = syscall::openat(debug_fd, "", syscall::O_WRONLY, 0); // stdout
|
||||
let _ = syscall::openat(debug_fd, "", syscall::O_WRONLY, 0); // stderr
|
||||
|
||||
unsafe {
|
||||
let _ = syscall::mprotect(4096, 4096, MapFlags::PROT_READ | MapFlags::MAP_PRIVATE)
|
||||
.expect("mprotect failed for initfs header page");
|
||||
|
||||
let _ = syscall::mprotect(
|
||||
text_start,
|
||||
text_end - text_start,
|
||||
MapFlags::PROT_READ | MapFlags::PROT_EXEC | MapFlags::MAP_PRIVATE,
|
||||
)
|
||||
.expect("mprotect failed for .text");
|
||||
let _ = syscall::mprotect(
|
||||
rodata_start,
|
||||
rodata_end - rodata_start,
|
||||
MapFlags::PROT_READ | MapFlags::MAP_PRIVATE,
|
||||
)
|
||||
.expect("mprotect failed for .rodata");
|
||||
let _ = syscall::mprotect(
|
||||
data_start,
|
||||
data_end - data_start,
|
||||
MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE,
|
||||
)
|
||||
.expect("mprotect failed for .data/.bss");
|
||||
let _ = syscall::mprotect(
|
||||
data_end,
|
||||
crate::arch::STACK_START - data_end,
|
||||
MapFlags::PROT_READ | MapFlags::MAP_PRIVATE,
|
||||
)
|
||||
.expect("mprotect failed for rest of memory");
|
||||
}
|
||||
|
||||
crate::exec::main();
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
ENTRY(_start)
|
||||
OUTPUT_FORMAT(elf64-x86-64)
|
||||
|
||||
SECTIONS {
|
||||
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
|
||||
__initfs_header = . - 4096;
|
||||
. += SIZEOF_HEADERS;
|
||||
. = ALIGN(4096);
|
||||
|
||||
.text : {
|
||||
__text_start = .;
|
||||
*(.text*)
|
||||
. = ALIGN(4096);
|
||||
__text_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
__rodata_start = .;
|
||||
*(.rodata*)
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got)
|
||||
}
|
||||
.got.plt : {
|
||||
*(.got.plt)
|
||||
. = ALIGN(4096);
|
||||
__rodata_end = .;
|
||||
}
|
||||
.data : {
|
||||
__data_start = .;
|
||||
*(.data*)
|
||||
. = ALIGN(4096);
|
||||
__data_end = .;
|
||||
|
||||
*(.tbss*)
|
||||
. = ALIGN(4096);
|
||||
*(.tdata*)
|
||||
. = ALIGN(4096);
|
||||
|
||||
__bss_start = .;
|
||||
*(.bss*)
|
||||
. = ALIGN(4096);
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
*(.gcc_except_table*)
|
||||
*(.note*)
|
||||
*(.rel.eh_frame*)
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use core::mem;
|
||||
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
|
||||
|
||||
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
|
||||
pub const USERMODE_END: usize = 0x0000_8000_0000_0000;
|
||||
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
|
||||
|
||||
static MAP: Map = Map {
|
||||
offset: 0,
|
||||
size: STACK_SIZE,
|
||||
flags: MapFlags::PROT_READ
|
||||
.union(MapFlags::PROT_WRITE)
|
||||
.union(MapFlags::MAP_PRIVATE)
|
||||
.union(MapFlags::MAP_FIXED_NOREPLACE),
|
||||
address: STACK_START, // highest possible user address
|
||||
};
|
||||
|
||||
core::arch::global_asm!(
|
||||
"
|
||||
.globl _start
|
||||
_start:
|
||||
# Setup a stack.
|
||||
mov rax, {number}
|
||||
mov rdi, {fd}
|
||||
mov rsi, offset {map} # pointer to Map struct
|
||||
mov rdx, {map_size} # size of Map struct
|
||||
syscall
|
||||
|
||||
# Test for success (nonzero value).
|
||||
cmp rax, 0
|
||||
jg 1f
|
||||
# (failure)
|
||||
ud2
|
||||
1:
|
||||
# Subtract 16 since all instructions seem to hate non-canonical RSP values :)
|
||||
lea rsp, [rax+{stack_size}-16]
|
||||
mov rbp, rsp
|
||||
|
||||
# Stack has the same alignment as `size`.
|
||||
call start
|
||||
# `start` must never return.
|
||||
ud2
|
||||
",
|
||||
fd = const usize::MAX, // dummy fd indicates anonymous map
|
||||
map = sym MAP,
|
||||
map_size = const mem::size_of::<Map>(),
|
||||
number = const SYS_FMAP,
|
||||
stack_size = const STACK_SIZE,
|
||||
);
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "config"
|
||||
description = "Configuration override library"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,40 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
pub fn config(name: &str) -> Result<Vec<PathBuf>, io::Error> {
|
||||
config_for_dirs(&[
|
||||
&Path::new("/usr/lib").join(format!("{name}.d")),
|
||||
&Path::new("/etc").join(format!("{name}.d")),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn config_for_initfs(name: &str) -> Result<Vec<PathBuf>, io::Error> {
|
||||
config_for_dirs(&[
|
||||
&Path::new("/scheme/initfs/lib").join(format!("{name}.d")),
|
||||
&Path::new("/scheme/initfs/etc").join(format!("{name}.d")),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn config_for_dirs(dirs: &[impl AsRef<Path>]) -> Result<Vec<PathBuf>, io::Error> {
|
||||
// This must be a BTreeMap to iterate in sorted order.
|
||||
let mut entries = BTreeMap::new();
|
||||
|
||||
for dir in dirs {
|
||||
let dir = dir.as_ref();
|
||||
if !dir.exists() {
|
||||
// Skip non-existent dirs
|
||||
continue;
|
||||
}
|
||||
|
||||
for entry_res in fs::read_dir(&dir)? {
|
||||
// This intentionally overwrites older entries with
|
||||
// the same filename to allow overriding entries in
|
||||
// one search dir with those in a later search dir.
|
||||
let entry = entry_res?;
|
||||
entries.insert(entry.file_name(), entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entries.into_values().collect())
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "daemon"
|
||||
description = "Redox daemon library"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
libc.workspace = true
|
||||
libredox.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,153 +0,0 @@
|
||||
//! A library for creating and managing daemons for RedoxOS.
|
||||
#![feature(never_type)]
|
||||
|
||||
use std::io::{self, PipeWriter, Read, Write};
|
||||
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
use libredox::Fd;
|
||||
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
|
||||
use redox_scheme::Socket;
|
||||
|
||||
unsafe fn get_fd(var: &str) -> Option<RawFd> {
|
||||
let value = match std::env::var(var) {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
let exe = std::env::args()
|
||||
.next()
|
||||
.unwrap_or_else(|| "daemon".to_string());
|
||||
eprintln!("daemon: {var} not set for {exe}; readiness notification disabled");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let fd: RawFd = match value.parse() {
|
||||
Ok(fd) => fd,
|
||||
Err(err) => {
|
||||
let exe = std::env::args()
|
||||
.next()
|
||||
.unwrap_or_else(|| "daemon".to_string());
|
||||
eprintln!("daemon: invalid {var} value {value:?} for {exe}: {err}; readiness notification disabled");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
|
||||
eprintln!(
|
||||
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Some(fd)
|
||||
}
|
||||
|
||||
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||
cmd.env(env, format!("{}", fd.as_raw_fd()));
|
||||
unsafe {
|
||||
cmd.pre_exec(move || {
|
||||
// Pass notify pipe to child
|
||||
if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A long running background process that handles requests.
|
||||
#[must_use = "Daemon::ready must be called"]
|
||||
pub struct Daemon {
|
||||
write_pipe: Option<PipeWriter>,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
/// Create a new daemon.
|
||||
pub fn new(f: impl FnOnce(Daemon) -> !) -> ! {
|
||||
let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
|
||||
|
||||
f(Daemon { write_pipe })
|
||||
}
|
||||
|
||||
/// Notify the process that the daemon is ready to accept requests.
|
||||
pub fn ready(mut self) {
|
||||
if let Some(write_pipe) = self.write_pipe.as_mut() {
|
||||
write_pipe.write_all(&[0]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes `Command` as a child process.
|
||||
// FIXME remove once the service spawning of hwd and pcid-spawner is moved to init
|
||||
#[deprecated]
|
||||
pub fn spawn(mut cmd: Command) {
|
||||
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
|
||||
|
||||
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) };
|
||||
|
||||
if let Err(err) = cmd.spawn() {
|
||||
eprintln!("daemon: failed to execute {cmd:?}: {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut data = [0];
|
||||
match read_pipe.read_exact(&mut data) {
|
||||
Ok(()) => {}
|
||||
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
||||
eprintln!("daemon: {cmd:?} exited without notifying readiness");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("daemon: failed to wait for {cmd:?}: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A long running background process that handles requests using schemes.
|
||||
#[must_use = "SchemeDaemon::ready must be called"]
|
||||
pub struct SchemeDaemon {
|
||||
write_pipe: Option<PipeWriter>,
|
||||
}
|
||||
|
||||
impl SchemeDaemon {
|
||||
/// Create a new daemon for use with schemes.
|
||||
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
|
||||
let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
|
||||
|
||||
f(SchemeDaemon { write_pipe })
|
||||
}
|
||||
|
||||
/// Notify the process that the scheme daemon is ready to accept requests.
|
||||
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
|
||||
if let Some(write_pipe) = self.write_pipe {
|
||||
syscall::call_wo(
|
||||
write_pipe.as_raw_fd() as usize,
|
||||
&cap_fd.into_raw().to_ne_bytes(),
|
||||
syscall::CallFlags::FD,
|
||||
&[],
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Notify the process that the synchronous scheme daemon is ready to accept requests.
|
||||
pub fn ready_sync_scheme<S: SchemeSync>(
|
||||
self,
|
||||
socket: &Socket,
|
||||
scheme: &mut S,
|
||||
) -> syscall::Result<()> {
|
||||
let cap_id = scheme.scheme_root()?;
|
||||
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
|
||||
self.ready_with_fd(Fd::new(cap_fd))
|
||||
}
|
||||
|
||||
/// Notify the process that the asynchronous scheme daemon is ready to accept requests.
|
||||
pub fn ready_async_scheme<S: SchemeAsync>(
|
||||
self,
|
||||
socket: &Socket,
|
||||
scheme: &mut S,
|
||||
) -> syscall::Result<()> {
|
||||
let cap_id = scheme.scheme_root()?;
|
||||
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
|
||||
self.ready_with_fd(Fd::new(cap_fd))
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#[repr(C, packed)]
|
||||
pub struct Dhcp {
|
||||
pub op: u8,
|
||||
pub htype: u8,
|
||||
pub hlen: u8,
|
||||
pub hops: u8,
|
||||
pub tid: u32,
|
||||
pub secs: u16,
|
||||
pub flags: u16,
|
||||
pub ciaddr: [u8; 4],
|
||||
pub yiaddr: [u8; 4],
|
||||
pub siaddr: [u8; 4],
|
||||
pub giaddr: [u8; 4],
|
||||
pub chaddr: [u8; 16],
|
||||
pub sname: [u8; 64],
|
||||
pub file: [u8; 128],
|
||||
pub magic: u32,
|
||||
pub options: [u8; 308],
|
||||
}
|
||||
@@ -1,497 +0,0 @@
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::time::Duration;
|
||||
use std::{env, process, time};
|
||||
|
||||
use dhcp::Dhcp;
|
||||
|
||||
mod dhcp;
|
||||
|
||||
macro_rules! try_fmt {
|
||||
($e:expr, $m:expr) => {
|
||||
match $e {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => return Err(format!("{}: {}", $m, err)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn get_cfg_value(path: &str) -> Result<String, String> {
|
||||
let path = format!("/scheme/netcfg/{path}");
|
||||
let mut file = File::open(&path).map_err(|_| format!("Can't open {path}"))?;
|
||||
let mut result = String::new();
|
||||
file.read_to_string(&mut result)
|
||||
.map_err(|_| format!("Can't read {path}"))?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_iface_cfg_value(iface: &str, cfg: &str) -> Result<String, String> {
|
||||
let path = format!("ifaces/{iface}/{cfg}");
|
||||
get_cfg_value(&path)
|
||||
}
|
||||
|
||||
fn set_cfg_value(path: &str, value: &str) -> Result<(), String> {
|
||||
let path = format!("/scheme/netcfg/{path}");
|
||||
let mut file = OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.open(&path)
|
||||
.map_err(|_| format!("Can't open {path}"))?;
|
||||
file.write(value.as_bytes())
|
||||
.map(|_| ())
|
||||
.map_err(|_| format!("Can't write {value} to {path}"))?;
|
||||
file.sync_data()
|
||||
.map_err(|_| format!("Can't commit {value} to {path}"))
|
||||
}
|
||||
|
||||
fn set_iface_cfg_value(iface: &str, cfg: &str, value: &str) -> Result<(), String> {
|
||||
let path = format!("ifaces/{iface}/{cfg}");
|
||||
set_cfg_value(&path, value)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
struct MacAddr {
|
||||
bytes: [u8; 6],
|
||||
}
|
||||
|
||||
impl MacAddr {
|
||||
fn from_str(string: &str) -> Self {
|
||||
MacAddr::try_parse_with_delimeter(string, ':')
|
||||
.or_else(|| MacAddr::try_parse_with_delimeter(string, '-'))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn try_parse_with_delimeter(string: &str, delimeter: char) -> Option<MacAddr> {
|
||||
let mut addr = MacAddr::default();
|
||||
let mut segments = 0;
|
||||
|
||||
for part in string.split(delimeter) {
|
||||
if segments >= addr.bytes.len() {
|
||||
return None;
|
||||
}
|
||||
addr.bytes[segments] = match u8::from_str_radix(part, 16) {
|
||||
Ok(b) => b,
|
||||
_ => return None,
|
||||
};
|
||||
segments += 1;
|
||||
}
|
||||
|
||||
if segments == addr.bytes.len() {
|
||||
Some(addr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"{:>02X}-{:>02X}-{:>02X}-{:>02X}-{:>02X}-{:>02X}",
|
||||
self.bytes[0],
|
||||
self.bytes[1],
|
||||
self.bytes[2],
|
||||
self.bytes[3],
|
||||
self.bytes[4],
|
||||
self.bytes[5]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
||||
let current_mac = MacAddr::from_str(get_iface_cfg_value(iface, "mac")?.trim());
|
||||
|
||||
let current_ip = get_iface_cfg_value(iface, "addr/list")?
|
||||
.lines()
|
||||
.next()
|
||||
.map(|l| l.to_owned())
|
||||
.unwrap_or("0.0.0.0".to_string());
|
||||
|
||||
if verbose {
|
||||
println!(
|
||||
"DHCP: MAC: {} Current IP: {}",
|
||||
current_mac.to_string(),
|
||||
current_ip.trim()
|
||||
);
|
||||
}
|
||||
|
||||
let tid = try_fmt!(
|
||||
time::SystemTime::now().duration_since(time::UNIX_EPOCH),
|
||||
"failed to get time"
|
||||
)
|
||||
.subsec_nanos();
|
||||
|
||||
let socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind udp");
|
||||
try_fmt!(
|
||||
socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))),
|
||||
"failed to connect udp"
|
||||
);
|
||||
try_fmt!(
|
||||
socket.set_read_timeout(Some(Duration::new(30, 0))),
|
||||
"failed to set read timeout"
|
||||
);
|
||||
try_fmt!(
|
||||
socket.set_write_timeout(Some(Duration::new(30, 0))),
|
||||
"failed to set write timeout"
|
||||
);
|
||||
|
||||
{
|
||||
let mut discover = Dhcp {
|
||||
op: 1,
|
||||
htype: 1,
|
||||
hlen: 6,
|
||||
hops: 0,
|
||||
tid,
|
||||
secs: 0,
|
||||
flags: 0x8000u16.to_be(),
|
||||
ciaddr: [0, 0, 0, 0],
|
||||
yiaddr: [0, 0, 0, 0],
|
||||
siaddr: [0, 0, 0, 0],
|
||||
giaddr: [0, 0, 0, 0],
|
||||
chaddr: [
|
||||
current_mac.bytes[0],
|
||||
current_mac.bytes[1],
|
||||
current_mac.bytes[2],
|
||||
current_mac.bytes[3],
|
||||
current_mac.bytes[4],
|
||||
current_mac.bytes[5],
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
sname: [0; 64],
|
||||
file: [0; 128],
|
||||
magic: 0x63825363u32.to_be(),
|
||||
options: [0; 308],
|
||||
};
|
||||
|
||||
for (s, d) in [
|
||||
// DHCP Message Type (Discover)
|
||||
53, 1, 1, // End
|
||||
255,
|
||||
]
|
||||
.iter()
|
||||
.zip(discover.options.iter_mut())
|
||||
{
|
||||
*d = *s;
|
||||
}
|
||||
|
||||
let discover_data = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(&discover as *const Dhcp) as *const u8,
|
||||
std::mem::size_of::<Dhcp>(),
|
||||
)
|
||||
};
|
||||
|
||||
let _sent = try_fmt!(socket.send(discover_data), "failed to send discover");
|
||||
|
||||
if verbose {
|
||||
println!("DHCP: Sent Discover");
|
||||
}
|
||||
}
|
||||
|
||||
let mut offer_data = [0; 65536];
|
||||
try_fmt!(socket.recv(&mut offer_data), "failed to receive offer");
|
||||
let offer = unsafe { &*(offer_data.as_ptr() as *const Dhcp) };
|
||||
if verbose {
|
||||
println!(
|
||||
"DHCP: Offer IP: {:?}, Server IP: {:?}",
|
||||
offer.yiaddr, offer.siaddr
|
||||
);
|
||||
}
|
||||
|
||||
let mut subnet_option = None;
|
||||
let mut router_option = None;
|
||||
let mut dns_option = None;
|
||||
let mut server_id_option = None;
|
||||
{
|
||||
let mut options = offer.options.iter();
|
||||
while let Some(option) = options.next() {
|
||||
match *option {
|
||||
0 => (),
|
||||
255 => break,
|
||||
_ => {
|
||||
if let Some(len) = options.next() {
|
||||
if *len as usize <= options.as_slice().len() {
|
||||
let data = &options.as_slice()[..*len as usize];
|
||||
for _data_i in 0..*len {
|
||||
options.next();
|
||||
}
|
||||
match *option {
|
||||
1 => {
|
||||
if verbose {
|
||||
println!("DHCP: Subnet Mask: {data:?}");
|
||||
}
|
||||
if data.len() == 4 && subnet_option.is_none() {
|
||||
subnet_option = Some(Vec::from(data));
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
if verbose {
|
||||
println!("DHCP: Router: {data:?}");
|
||||
}
|
||||
if data.len() == 4 && router_option.is_none() {
|
||||
router_option = Some(Vec::from(data));
|
||||
}
|
||||
}
|
||||
6 => {
|
||||
if verbose {
|
||||
println!("DHCP: Domain Name Server: {data:?}");
|
||||
}
|
||||
if data.len() == 4 && dns_option.is_none() {
|
||||
dns_option = Some(Vec::from(data));
|
||||
}
|
||||
}
|
||||
51 => {
|
||||
if verbose {
|
||||
println!("DHCP: Lease Time: {data:?}");
|
||||
}
|
||||
}
|
||||
53 => {
|
||||
if verbose {
|
||||
println!("DHCP: Message Type: {data:?}");
|
||||
}
|
||||
}
|
||||
54 => {
|
||||
if verbose {
|
||||
println!("DHCP: Server ID: {data:?}");
|
||||
}
|
||||
if data.len() == 4 {
|
||||
// Store the server ID
|
||||
server_id_option =
|
||||
Some([data[0], data[1], data[2], data[3]]);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if verbose {
|
||||
println!("DHCP: {option}: {data:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mask_len = if let Some(subnet) = subnet_option {
|
||||
let mut subnet: u32 = (subnet[0] as u32) << 24
|
||||
| (subnet[1] as u32) << 16
|
||||
| (subnet[2] as u32) << 8
|
||||
| subnet[3] as u32;
|
||||
subnet = !subnet;
|
||||
subnet.leading_zeros()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let new_ips = format!(
|
||||
"{}.{}.{}.{}/{}\n",
|
||||
offer.yiaddr[0], offer.yiaddr[1], offer.yiaddr[2], offer.yiaddr[3], mask_len
|
||||
);
|
||||
try_fmt!(
|
||||
set_iface_cfg_value(iface, "addr/set", &new_ips),
|
||||
"failed to set ip"
|
||||
);
|
||||
|
||||
if verbose {
|
||||
let new_ip = try_fmt!(get_iface_cfg_value(iface, "addr/list"), "failed to get ip");
|
||||
println!("DHCP: New IP: {}", new_ip.trim());
|
||||
}
|
||||
|
||||
if let Some(router) = router_option {
|
||||
let default_route = format!(
|
||||
"default via {}.{}.{}.{}",
|
||||
router[0], router[1], router[2], router[3]
|
||||
);
|
||||
|
||||
try_fmt!(
|
||||
set_cfg_value("route/add", &default_route),
|
||||
"failed to set default route"
|
||||
);
|
||||
|
||||
if verbose {
|
||||
let new_router = try_fmt!(get_cfg_value("route/list"), "failed to get ip router");
|
||||
println!("DHCP: New Router: {}", new_router.trim());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut dns) = dns_option {
|
||||
if dns[0] == 127 {
|
||||
let quad9 = [9, 9, 9, 9].to_vec();
|
||||
if verbose {
|
||||
println!(
|
||||
"DHCP: Received sarcastic DNS suggestion {}.{}.{}.{}, using {}.{}.{}.{} instead",
|
||||
dns[0], dns[1], dns[2], dns[3], quad9[0], quad9[1], quad9[2], quad9[3]
|
||||
);
|
||||
}
|
||||
dns = quad9;
|
||||
}
|
||||
|
||||
let nameserver = format!("{}.{}.{}.{}", dns[0], dns[1], dns[2], dns[3]);
|
||||
|
||||
try_fmt!(
|
||||
set_cfg_value("resolv/nameserver", &nameserver),
|
||||
"failed to set name server"
|
||||
);
|
||||
|
||||
if verbose {
|
||||
let new_dns = try_fmt!(get_cfg_value("resolv/nameserver"), "failed to get dns");
|
||||
println!("DHCP: New DNS: {}", new_dns.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut request = Dhcp {
|
||||
op: 1,
|
||||
htype: 1,
|
||||
hlen: 6,
|
||||
hops: 0,
|
||||
tid,
|
||||
secs: 0,
|
||||
flags: 0,
|
||||
ciaddr: [0; 4],
|
||||
yiaddr: [0; 4],
|
||||
siaddr: [0; 4],
|
||||
giaddr: [0; 4],
|
||||
chaddr: [
|
||||
current_mac.bytes[0],
|
||||
current_mac.bytes[1],
|
||||
current_mac.bytes[2],
|
||||
current_mac.bytes[3],
|
||||
current_mac.bytes[4],
|
||||
current_mac.bytes[5],
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
sname: [0; 64],
|
||||
file: [0; 128],
|
||||
magic: 0x63825363u32.to_be(),
|
||||
options: [0; 308],
|
||||
};
|
||||
|
||||
// If the server_id_option was None, use "0.0.0.0"
|
||||
let server_id = server_id_option.unwrap_or([0, 0, 0, 0]);
|
||||
|
||||
for (s, d) in [
|
||||
// DHCP Message Type (Request)
|
||||
53,
|
||||
1,
|
||||
3,
|
||||
// Requested IP Address
|
||||
50,
|
||||
4,
|
||||
offer.yiaddr[0],
|
||||
offer.yiaddr[1],
|
||||
offer.yiaddr[2],
|
||||
offer.yiaddr[3],
|
||||
// Server Identifier - use Option 54 from the Offer
|
||||
54,
|
||||
4,
|
||||
server_id[0],
|
||||
server_id[1],
|
||||
server_id[2],
|
||||
server_id[3],
|
||||
// End
|
||||
255,
|
||||
]
|
||||
.iter()
|
||||
.zip(request.options.iter_mut())
|
||||
{
|
||||
*d = *s;
|
||||
}
|
||||
|
||||
let request_data = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(&request as *const Dhcp) as *const u8,
|
||||
std::mem::size_of::<Dhcp>(),
|
||||
)
|
||||
};
|
||||
|
||||
let _sent = try_fmt!(socket.send(request_data), "failed to send request");
|
||||
|
||||
if verbose {
|
||||
println!("DHCP: Sent Request");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut ack_data = [0; 65536];
|
||||
try_fmt!(socket.recv(&mut ack_data), "failed to receive ack");
|
||||
let ack = unsafe { &*(ack_data.as_ptr() as *const Dhcp) };
|
||||
if verbose {
|
||||
println!(
|
||||
"DHCP: Ack IP: {:?}, Server IP: {:?}",
|
||||
ack.yiaddr, ack.siaddr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut verbose = false;
|
||||
let iface = "eth0";
|
||||
|
||||
//TODO: parse iface from the args
|
||||
for arg in env::args().skip(1) {
|
||||
match arg.as_ref() {
|
||||
"-v" => verbose = true,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = dhcp(iface, verbose) {
|
||||
eprintln!("dhcpd: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::MacAddr;
|
||||
|
||||
#[test]
|
||||
fn from_str_test() {
|
||||
let mac = MacAddr {
|
||||
bytes: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
|
||||
};
|
||||
let empty_mac = MacAddr::default();
|
||||
|
||||
assert_eq!(mac, MacAddr::from_str("01:23:45:67:89:ab"));
|
||||
assert_eq!(mac, MacAddr::from_str("1:23:45:67:89:ab"));
|
||||
assert_eq!(mac, MacAddr::from_str("01:23:45:67:89:AB"));
|
||||
assert_eq!(mac, MacAddr::from_str("01-23-45-67-89-ab"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str(""));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("01:23:45:67:89"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("01:23:45:67:89:ab:cd"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("x1:23:45:67:89:ab"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("01:23-45-67-89-ab"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("01-23-45-67-89-ag"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("01.23.45.67.89.ab"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("01234-23-45-67-89-ab"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("01--23-45-67-89-ab"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("12"));
|
||||
assert_eq!(empty_mac, MacAddr::from_str("0:0:0:0:0:0"));
|
||||
|
||||
assert_eq!(mac, MacAddr::from_str(&mac.to_string()));
|
||||
assert_eq!(empty_mac, MacAddr::from_str(&empty_mac.to_string()));
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
# Community Hardware
|
||||
|
||||
This document tracks the devices from developers or community that need a driver.
|
||||
|
||||
This document was created because unfortunately we can't know the most sold device models of the world to measure our device porting priority, thus we will use our community data to measure our device priorities, if you find a "device model users" survey (similar to [Debian Popularity Contest](https://popcon.debian.org/) and [Steam Hardware/Software Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam)), please comment.
|
||||
|
||||
If you want to contribute to this table, install [pciutils](https://mj.ucw.cz/sw/pciutils/) on your Linux or Unix-like distribution (it may have a package on your distribution), run the `lspci -v` command to see your hardware devices, their kernel drivers and give the results of these items on each device:
|
||||
|
||||
- The first field (each device has an unique name for this item)
|
||||
- Kernel driver
|
||||
- Kernel module
|
||||
|
||||
If you are unsure of what to do, you can talk with us on the [chat](https://doc.redox-os.org/book/chat.html).
|
||||
|
||||
## Template
|
||||
|
||||
You will use this template to insert your devices on the table.
|
||||
|
||||
```
|
||||
| | | | No |
|
||||
```
|
||||
|
||||
- Remove the `#` characters in the port numbers to avoid GitLab issues to be wrongly mentioned
|
||||
|
||||
## Devices
|
||||
|
||||
| **Device model** | **Kernel driver?** | **Kernel module?** | **There's a Redox driver?** |
|
||||
|------------------|--------------------|--------------------|-----------------------------|
|
||||
| Realtek RTL8821CE 802.11ac (Wi-Fi) | rtw_8821ce | rtw88_8821ce | No |
|
||||
| Intel Ice Lake-LP SPI Controller | intel-spi | spi_intel_pci | No |
|
||||
| Intel Ice Lake-LP SMBus Controller | i801_smbus | i2c_i801 | No |
|
||||
| Intel Ice Lake-LP Smart Sound Technology Audio Controller | snd_hda_intel | snd_hda_intel, snd_sof_pci_intel_icl | No |
|
||||
| Intel Ice Lake-LP Serial IO SPI Controller | intel-lpss | No | No |
|
||||
| Intel Ice Lake-LP Serial IO UART Controller | intel-lpss | No | No |
|
||||
| Intel Ice Lake-LP Serial IO I2C Controller | intel-lpss | No | No |
|
||||
| Ice Lake-LP USB 3.1 xHCI Host Controller | xhci_hcd | No | No |
|
||||
| Intel Processor Power and Thermal Controller | proc_thermal | processor_thermal_device_pci_legacy | No |
|
||||
| Intel Device 8a02 | icl_uncore | No | No |
|
||||
| Iris Plus Graphics G1 (Ice Lake) | i915 | i915 | No |
|
||||
| Intel Corporation Raptor Lake-P 6p+8e cores Host Bridge/DRAM Controller | No | No | No |
|
||||
| Intel Corporation Raptor Lake PCI Express 5.0 Graphics Port (PEG010) (prog-if 00 [Normal decode]) | pcieport | No | No |
|
||||
| Intel Corporation Raptor Lake-P [UHD Graphics] (rev 04) (prog-if 00 [VGA controller]) | i915 | i915 | No |
|
||||
| Intel Corporation Raptor Lake Dynamic Platform and Thermal Framework Processor Participant | proc_thermal_pci | processor_thermal_device_pci | No |
|
||||
| Intel Corporation Raptor Lake PCIe 4.0 Graphics Port (prog-if 00 [Normal decode]) | pcieport | No | No |
|
||||
| Intel Corporation Raptor Lake-P Thunderbolt 4 PCI Express Root Port 0 (prog-if 00 [Normal decode]) | pcieport | No | No |
|
||||
| Intel Corporation GNA Scoring Accelerator module | No | No | No |
|
||||
| Intel Corporation Raptor Lake-P Thunderbolt 4 USB Controller (prog-if 30 [XHCI]) | xhci_hcd | xhci_pci | No |
|
||||
| Intel Corporation Raptor Lake-P Thunderbolt 4 NHI 0 (prog-if 40 [USB4 Host Interface]) | thunderbolt | thunderbolt | No |
|
||||
| Intel Corporation Raptor Lake-P Thunderbolt 4 NHI 1 (prog-if 40 [USB4 Host Interface]) | thunderbolt | thunderbolt | No |
|
||||
| Intel Corporation Alder Lake PCH USB 3.2 xHCI Host Controller (rev 01) (prog-if 30 [XHCI]) | xhci_hcd | xhci_pci | No |
|
||||
| Intel Corporation Alder Lake PCH Shared SRAM (rev 01) | No | No | No |
|
||||
| Intel Corporation Raptor Lake PCH CNVi WiFi (rev 01) | iwlwifi | iwlwifi | No |
|
||||
| Intel Corporation Alder Lake PCH Serial IO I2C Controller #0 (rev 01) | intel-lpss | intel_lpss_pci | No |
|
||||
| Intel Corporation Alder Lake PCH HECI Controller (rev 01) | mei_me | mei_me | No |
|
||||
| Intel Corporation Device 51b8 (rev 01) (prog-if 00 [Normal decode]) | pcieport | No | No |
|
||||
| Intel Corporation Alder Lake-P PCH PCIe Root Port 6 (rev 01) (prog-if 00 [Normal decode]) | pcieport | No | No |
|
||||
| Intel Corporation Raptor Lake LPC/eSPI Controller (rev 01) | No | No | No |
|
||||
| Intel Corporation Raptor Lake-P/U/H cAVS (rev 01) (prog-if 80) | sof-audio-pci-intel-tgl | snd_hda_intel, snd_sof_pci_intel_tgl | No |
|
||||
| Intel Corporation Alder Lake PCH-P SMBus Host Controller | i801_smbus | i2c_i801 | No |
|
||||
| Intel Corporation Alder Lake-P PCH SPI Controller (rev 01) | intel-spi | spi_intel_pci | No |
|
||||
| NVIDIA Corporation GA107GLM [RTX A1000 6GB Laptop GPU] (rev a1) | nvidia | nouveau, nvidia_drm, nvidia | No |
|
||||
| SK hynix Platinum P41/PC801 NVMe Solid State Drive (prog-if 02 [NVM Express]) | nvme | nvme | No |
|
||||
| Realtek Semiconductor Co., Ltd. RTS5261 PCI Express Card Reader (rev 01) | rtsx_pci | rtsx_pci | No |
|
||||
@@ -1,160 +0,0 @@
|
||||
# Drivers
|
||||
|
||||
- [Libraries](#libraries)
|
||||
- [Services](#services)
|
||||
- [Hardware Interfaces](#hardware-interfaces)
|
||||
- [Devices](#devices)
|
||||
- [CPU](#cpu)
|
||||
- [Controllers](#controllers)
|
||||
- [Storage](#storage)
|
||||
- [Graphics](#graphics)
|
||||
- [Input](#input)
|
||||
- [Sound](#sound)
|
||||
- [Networking](#networking)
|
||||
- [Virtualization](#virtualization)
|
||||
- [System Interfaces](#system-interfaces)
|
||||
- [System Calls](#system-calls)
|
||||
- [Schemes](#schemes)
|
||||
- [Contribution Details](#contribution-details)
|
||||
|
||||
## Libraries
|
||||
|
||||
- amlserde - Library to provide serialization/deserialization of the AML symbol table from ACPI
|
||||
- common - Library with shared driver code
|
||||
- executor - Library to run Rust futures and integrate the executor in an interrupt+queue model without a separated reactor thread
|
||||
- [graphics/console-draw](graphics/console-draw/) - Library with shared terminal drawing code
|
||||
- [graphics/driver-graphics](graphics/driver-graphics/) - Library with shared graphics code
|
||||
- [graphics/graphics-ipc](graphics/graphics-ipc/) - Library with graphics IPC shared code
|
||||
- [net/driver-network](net/driver-network/) - Library with shared networking code
|
||||
- [storage/partitionlib](storage/partitionlib/) - Library with MBR and GPT code
|
||||
- [storage/driver-block](storage/driver-block/) - Library with shared storage code
|
||||
- virtio-core - VirtIO driver library
|
||||
|
||||
## Services
|
||||
|
||||
- [graphics/fbbootlogd](graphics/fbbootlogd/) - Daemon for boot log drawing
|
||||
- [graphics/fbcond](graphics/fbcond/) - Terminal daemon
|
||||
- hwd - Daemon that handle the ACPI and DeviceTree booting
|
||||
- inputd - Multiplexes input from multiple input drivers and provides that to Orbital
|
||||
- pcid-spawner - Daemon for PCI-based device driver spawn
|
||||
- [storage/lived](storage/lived/) - Daemon for live disk
|
||||
- redoxerd - Daemon that send/receive terminal text between the host system and QEMU
|
||||
|
||||
## Hardware Interfaces
|
||||
|
||||
- acpid - ACPI interface driver
|
||||
- pcid - PCI and PCI Express driver
|
||||
|
||||
## Devices
|
||||
|
||||
### CPU
|
||||
|
||||
- rtcd - x86 Real Time Clock driver
|
||||
|
||||
### Controllers
|
||||
|
||||
- [usb/xhcid](usb/xhcid/) - xHCI USB controller driver
|
||||
|
||||
### Storage
|
||||
|
||||
- [storage/ahcid](storage/ahcid/) - AHCI (SATA) driver
|
||||
- [storage/bcm2835-sdhcid](storage/bcm2835-sdhcid/) - BCM2835 storage driver
|
||||
- [storage/ided](storage/ided/) - PATA (IDE) driver
|
||||
- [storage/nvmed](storage/nvmed/) - NVMe driver
|
||||
- [storage/virtio-blkd](storage/virtio-blkd/) - VirtIO block device driver
|
||||
- [storage/usbscsid](storage/usbscsid/) - USB SCSI driver
|
||||
|
||||
### Graphics
|
||||
|
||||
- [graphics/ihdgd](graphics/ihdgd/) - Intel graphics driver
|
||||
- [graphics/vesad](graphics/vesad/) - VESA video driver
|
||||
- [graphics/virtio-gpud](graphics/virtio-gpud/) - VirtIO-GPU device driver
|
||||
|
||||
### Input
|
||||
|
||||
- [input/ps2d](input/ps2d/) - PS/2 interface driver
|
||||
- [input/usbhidd](input/usbhidd/) - USB HID driver
|
||||
- [usb/usbhubd](usb/usbhubd/) - USB Hub driver
|
||||
- [usb/usbctl](usb/usbctl/) - TODO
|
||||
|
||||
### Sound
|
||||
|
||||
- [audio/ac97d](audio/ac97d/) - AC'97 codec driver
|
||||
- [audio/ihdad](audio/ihdad/) - Intel HD Audio chipset driver
|
||||
- [audio/sb16d](audio/sb16d/) - Sound Blaster sound card driver
|
||||
|
||||
### Networking
|
||||
|
||||
- [net/e1000d](net/e1000d/) - Intel Gigabit ethernet driver
|
||||
- [net/ixgbed](net/ixgbed/) - Intel 10 Gigabit ethernet driver
|
||||
- [net/rtl8139d](net/rtl8139d/), [net/rtl8168d](net/rtl8168d/) - Realtek ethernet drivers
|
||||
- [net/virtio-netd](net/virtio-netd/) - VirtIO network device driver
|
||||
|
||||
### Virtualization
|
||||
|
||||
- vboxd - VirtualBox driver
|
||||
|
||||
Some drivers are work-in-progress and incomplete, read [this](https://gitlab.redox-os.org/redox-os/base/-/issues/56) tracking issue to verify.
|
||||
|
||||
## System Interfaces
|
||||
|
||||
This section explain the system interfaces used by drivers.
|
||||
|
||||
### System Calls
|
||||
|
||||
- `iopl` : system call that sets the I/O privilege level. x86 has four privilege rings (0/1/2/3), of which the kernel runs in ring 0 and userspace in ring 3. IOPL can only be changed by the kernel, for obvious security reasons, and therefore the Redox kernel needs root to set it. It is unique for each process. Processes with IOPL=3 can access I/O ports, and the kernel can access them as well.
|
||||
|
||||
### Schemes
|
||||
|
||||
- `/scheme/memory/physical` : Allows mapping physical memory frames to driver-accessible virtual memory pages, with various available memory types:
|
||||
- `/scheme/memory/physical` : Default memory type (currently writeback)
|
||||
- `/scheme/memory/physical@wb` Writeback cached memory
|
||||
- `/scheme/memory/physical@uc` : Uncacheable memory
|
||||
- `/scheme/memory/physical@wc` : Write-combining memory
|
||||
- `/scheme/irq` : Allows getting events from interrupts. It is used primarily by listening for its file descriptors using the `/scheme/event` scheme.
|
||||
|
||||
## Contribution Details
|
||||
|
||||
### Driver Design
|
||||
|
||||
A device driver on Redox is an user-space daemon that use system calls and schemes to work, while operating systems with monolithic kernels drivers use internal kernel APIs instead of common program APIs.
|
||||
|
||||
If you want to port a driver from a monolithic operating system to Redox you will need to rewrite the driver with reverse enginnering of the code logic, because the logic is adapted to internal kernel APIs (it's a hard task if the device is complex, datasheets are much more easy).
|
||||
|
||||
### Write a Driver
|
||||
|
||||
Datasheets are preferable (much more easy depending on device complexity), when they are freely available. Be aware that datasheets are often provided under a [Non-Disclosure Agreement](https://en.wikipedia.org/wiki/Non-disclosure_agreement) from hardware vendors, which can affect the ability to create an MIT-licensed driver.
|
||||
|
||||
If datasheets aren't available you need to do reverse-engineering of BSD or Linux drivers (if you want use a Linux driver as reference for your Redox driver please ask in the [Chat](https://doc.redox-os.org/book/chat.html) before the implementation to know/satisfy the license requirements and not waste your time, also if you use a BSD driver not licensed as BSD as reference).
|
||||
|
||||
### Libraries
|
||||
|
||||
You should use the [redox-scheme](https://crates.io/crates/redox-scheme) and [redox_event](https://crates.io/crates/redox_event) libraries to create your drivers, you can also read the [example driver](https://gitlab.redox-os.org/redox-os/exampled) or read the code of other drivers with the same type of your device.
|
||||
|
||||
Before testing your changes be aware of [this](https://doc.redox-os.org/book/coding-and-building.html#how-to-update-initfs).
|
||||
|
||||
### References
|
||||
|
||||
If you want to reverse enginner the existing drivers, you can access the BSD code using these links:
|
||||
|
||||
- [FreeBSD drivers](https://github.com/freebsd/freebsd-src/tree/main/sys/dev)
|
||||
- [NetBSD drivers](https://github.com/NetBSD/src/tree/trunk/sys/dev)
|
||||
- [OpenBSD drivers](https://github.com/openbsd/src/tree/master/sys/dev)
|
||||
|
||||
## How To Contribute
|
||||
|
||||
To learn how to contribute to this system component you need to read the following document:
|
||||
|
||||
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## Development
|
||||
|
||||
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
|
||||
|
||||
### How To Build
|
||||
|
||||
To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page.
|
||||
|
||||
This is necessary because they only work with cross-compilation to a Redox virtual machine or real hardware, but you can do some testing from Linux.
|
||||
|
||||
[Back to top](#drivers)
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "acpi-resource"
|
||||
description = "Shared ACPI resource template decoder"
|
||||
version = "0.0.1"
|
||||
authors = ["Red Bear OS"]
|
||||
repository = "https://gitlab.redox-os.org/redox-os/drivers"
|
||||
categories = ["hardware-support"]
|
||||
license = "MIT/Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
@@ -1,688 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
const SMALL_IRQ: u8 = 0x20;
|
||||
const SMALL_END_TAG: u8 = 0x78;
|
||||
|
||||
const LARGE_MEMORY32: u8 = 0x85;
|
||||
const LARGE_FIXED_MEMORY32: u8 = 0x86;
|
||||
const LARGE_ADDRESS32: u8 = 0x87;
|
||||
const LARGE_EXTENDED_IRQ: u8 = 0x89;
|
||||
const LARGE_ADDRESS64: u8 = 0x8A;
|
||||
const LARGE_GPIO: u8 = 0x8C;
|
||||
const LARGE_SERIAL_BUS: u8 = 0x8E;
|
||||
|
||||
const SERIAL_BUS_I2C: u8 = 1;
|
||||
const I2C_TYPE_DATA_LEN: usize = 6;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum InterruptTrigger {
|
||||
Edge,
|
||||
Level,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum InterruptPolarity {
|
||||
ActiveHigh,
|
||||
ActiveLow,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AddressResourceType {
|
||||
MemoryRange,
|
||||
IoRange,
|
||||
BusNumberRange,
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ResourceSource {
|
||||
pub index: u8,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IrqDescriptor {
|
||||
pub interrupts: Vec<u8>,
|
||||
pub triggering: InterruptTrigger,
|
||||
pub polarity: InterruptPolarity,
|
||||
pub shareable: bool,
|
||||
pub wake_capable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExtendedIrqDescriptor {
|
||||
pub producer_consumer: bool,
|
||||
pub interrupts: Vec<u32>,
|
||||
pub triggering: InterruptTrigger,
|
||||
pub polarity: InterruptPolarity,
|
||||
pub shareable: bool,
|
||||
pub wake_capable: bool,
|
||||
pub resource_source: Option<ResourceSource>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GpioDescriptor {
|
||||
pub revision_id: u8,
|
||||
pub producer_consumer: bool,
|
||||
pub pin_config: u8,
|
||||
pub shareable: bool,
|
||||
pub wake_capable: bool,
|
||||
pub io_restriction: u8,
|
||||
pub triggering: InterruptTrigger,
|
||||
pub polarity: InterruptPolarity,
|
||||
pub drive_strength: u16,
|
||||
pub debounce_timeout: u16,
|
||||
pub pins: Vec<u16>,
|
||||
pub resource_source: Option<ResourceSource>,
|
||||
pub vendor_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct I2cSerialBusDescriptor {
|
||||
pub revision_id: u8,
|
||||
pub producer_consumer: bool,
|
||||
pub slave_mode: bool,
|
||||
pub connection_sharing: bool,
|
||||
pub type_revision_id: u8,
|
||||
pub access_mode_10bit: bool,
|
||||
pub slave_address: u16,
|
||||
pub connection_speed: u32,
|
||||
pub resource_source: Option<ResourceSource>,
|
||||
pub vendor_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Memory32RangeDescriptor {
|
||||
pub write_protect: bool,
|
||||
pub minimum: u32,
|
||||
pub maximum: u32,
|
||||
pub alignment: u32,
|
||||
pub address_length: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct FixedMemory32Descriptor {
|
||||
pub write_protect: bool,
|
||||
pub address: u32,
|
||||
pub address_length: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Address32Descriptor {
|
||||
pub resource_type: AddressResourceType,
|
||||
pub producer_consumer: bool,
|
||||
pub decode: bool,
|
||||
pub min_address_fixed: bool,
|
||||
pub max_address_fixed: bool,
|
||||
pub specific_flags: u8,
|
||||
pub granularity: u32,
|
||||
pub minimum: u32,
|
||||
pub maximum: u32,
|
||||
pub translation_offset: u32,
|
||||
pub address_length: u32,
|
||||
pub resource_source: Option<ResourceSource>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Address64Descriptor {
|
||||
pub resource_type: AddressResourceType,
|
||||
pub producer_consumer: bool,
|
||||
pub decode: bool,
|
||||
pub min_address_fixed: bool,
|
||||
pub max_address_fixed: bool,
|
||||
pub specific_flags: u8,
|
||||
pub granularity: u64,
|
||||
pub minimum: u64,
|
||||
pub maximum: u64,
|
||||
pub translation_offset: u64,
|
||||
pub address_length: u64,
|
||||
pub resource_source: Option<ResourceSource>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ResourceDescriptor {
|
||||
Irq(IrqDescriptor),
|
||||
ExtendedIrq(ExtendedIrqDescriptor),
|
||||
GpioInt(GpioDescriptor),
|
||||
GpioIo(GpioDescriptor),
|
||||
I2cSerialBus(I2cSerialBusDescriptor),
|
||||
Memory32Range(Memory32RangeDescriptor),
|
||||
FixedMemory32(FixedMemory32Descriptor),
|
||||
Address32(Address32Descriptor),
|
||||
Address64(Address64Descriptor),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum ResourceDecodeError {
|
||||
#[error("descriptor at offset {offset} overruns the resource template")]
|
||||
TruncatedDescriptor { offset: usize },
|
||||
|
||||
#[error("unsupported small descriptor length {length} for tag {tag:#04x} at offset {offset}")]
|
||||
InvalidSmallLength {
|
||||
offset: usize,
|
||||
tag: u8,
|
||||
length: usize,
|
||||
},
|
||||
|
||||
#[error("descriptor {descriptor} at offset {offset} is shorter than {minimum} bytes")]
|
||||
InvalidLargeLength {
|
||||
offset: usize,
|
||||
descriptor: &'static str,
|
||||
minimum: usize,
|
||||
},
|
||||
|
||||
#[error("descriptor {descriptor} at offset {offset} has an invalid internal offset")]
|
||||
InvalidInternalOffset {
|
||||
offset: usize,
|
||||
descriptor: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn decode_resource_template(
|
||||
bytes: &[u8],
|
||||
) -> Result<Vec<ResourceDescriptor>, ResourceDecodeError> {
|
||||
let mut resources = Vec::new();
|
||||
let mut offset = 0usize;
|
||||
|
||||
while offset < bytes.len() {
|
||||
let descriptor = *bytes
|
||||
.get(offset)
|
||||
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
|
||||
|
||||
if descriptor & 0x80 == 0 {
|
||||
let length = usize::from(descriptor & 0x07);
|
||||
let end = offset + 1 + length;
|
||||
let desc = bytes
|
||||
.get(offset..end)
|
||||
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
|
||||
let body = &desc[1..];
|
||||
|
||||
match descriptor & 0x78 {
|
||||
SMALL_IRQ => resources.push(ResourceDescriptor::Irq(parse_irq(body, offset)?)),
|
||||
SMALL_END_TAG => break,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
offset = end;
|
||||
continue;
|
||||
}
|
||||
|
||||
let length = usize::from(read_u16(bytes, offset + 1)?);
|
||||
let end = offset + 3 + length;
|
||||
let desc = bytes
|
||||
.get(offset..end)
|
||||
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
|
||||
let body = &desc[3..];
|
||||
|
||||
match descriptor {
|
||||
LARGE_MEMORY32 => resources.push(ResourceDescriptor::Memory32Range(parse_memory32(
|
||||
body, offset,
|
||||
)?)),
|
||||
LARGE_FIXED_MEMORY32 => resources.push(ResourceDescriptor::FixedMemory32(
|
||||
parse_fixed_memory32(body, offset)?,
|
||||
)),
|
||||
LARGE_ADDRESS32 => {
|
||||
resources.push(ResourceDescriptor::Address32(parse_address32(
|
||||
desc, body, offset,
|
||||
)?));
|
||||
}
|
||||
LARGE_ADDRESS64 => {
|
||||
resources.push(ResourceDescriptor::Address64(parse_address64(
|
||||
desc, body, offset,
|
||||
)?));
|
||||
}
|
||||
LARGE_EXTENDED_IRQ => resources.push(ResourceDescriptor::ExtendedIrq(
|
||||
parse_extended_irq(desc, body, offset)?,
|
||||
)),
|
||||
LARGE_GPIO => {
|
||||
let (is_interrupt, descriptor) = parse_gpio(desc, body, offset)?;
|
||||
resources.push(if is_interrupt {
|
||||
ResourceDescriptor::GpioInt(descriptor)
|
||||
} else {
|
||||
ResourceDescriptor::GpioIo(descriptor)
|
||||
});
|
||||
}
|
||||
LARGE_SERIAL_BUS => {
|
||||
if let Some(descriptor) = parse_i2c_serial_bus(desc, body, offset)? {
|
||||
resources.push(ResourceDescriptor::I2cSerialBus(descriptor));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
offset = end;
|
||||
}
|
||||
|
||||
Ok(resources)
|
||||
}
|
||||
|
||||
fn parse_irq(body: &[u8], offset: usize) -> Result<IrqDescriptor, ResourceDecodeError> {
|
||||
if body.len() != 2 && body.len() != 3 {
|
||||
return Err(ResourceDecodeError::InvalidSmallLength {
|
||||
offset,
|
||||
tag: SMALL_IRQ,
|
||||
length: body.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mask = u16::from_le_bytes([body[0], body[1]]);
|
||||
let flags = body.get(2).copied().unwrap_or(0);
|
||||
let interrupts = (0..16)
|
||||
.filter(|irq| mask & (1 << irq) != 0)
|
||||
.map(|irq| irq as u8)
|
||||
.collect();
|
||||
|
||||
Ok(IrqDescriptor {
|
||||
interrupts,
|
||||
triggering: if flags & 0x01 != 0 {
|
||||
InterruptTrigger::Level
|
||||
} else {
|
||||
InterruptTrigger::Edge
|
||||
},
|
||||
polarity: if flags & 0x08 != 0 {
|
||||
InterruptPolarity::ActiveLow
|
||||
} else {
|
||||
InterruptPolarity::ActiveHigh
|
||||
},
|
||||
shareable: flags & 0x10 != 0,
|
||||
wake_capable: flags & 0x20 != 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_extended_irq(
|
||||
desc: &[u8],
|
||||
body: &[u8],
|
||||
offset: usize,
|
||||
) -> Result<ExtendedIrqDescriptor, ResourceDecodeError> {
|
||||
ensure_length(body, 2, offset, "ExtendedIrq")?;
|
||||
|
||||
let flags = body[0];
|
||||
let count = usize::from(body[1]);
|
||||
let ints_len = count * 4;
|
||||
ensure_length(body, 2 + ints_len, offset, "ExtendedIrq")?;
|
||||
|
||||
let interrupts = (0..count)
|
||||
.map(|index| read_u32(body, 2 + index * 4))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let resource_source = if body.len() > 2 + ints_len {
|
||||
Some(parse_source_inline(&body[2 + ints_len..]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let _ = desc;
|
||||
|
||||
Ok(ExtendedIrqDescriptor {
|
||||
producer_consumer: flags & 0x01 != 0,
|
||||
triggering: if flags & 0x02 != 0 {
|
||||
InterruptTrigger::Level
|
||||
} else {
|
||||
InterruptTrigger::Edge
|
||||
},
|
||||
polarity: if flags & 0x04 != 0 {
|
||||
InterruptPolarity::ActiveLow
|
||||
} else {
|
||||
InterruptPolarity::ActiveHigh
|
||||
},
|
||||
shareable: flags & 0x08 != 0,
|
||||
wake_capable: flags & 0x10 != 0,
|
||||
interrupts,
|
||||
resource_source,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_gpio(
|
||||
desc: &[u8],
|
||||
body: &[u8],
|
||||
offset: usize,
|
||||
) -> Result<(bool, GpioDescriptor), ResourceDecodeError> {
|
||||
ensure_length(body, 20, offset, "Gpio")?;
|
||||
|
||||
let connection_type = body[1];
|
||||
let flags = read_u16(body, 2)?;
|
||||
let int_flags = read_u16(body, 4)?;
|
||||
let pin_table_offset = usize::from(read_u16(body, 11)?);
|
||||
let resource_source_index = body[13];
|
||||
let resource_source_offset = usize::from(read_u16(body, 14)?);
|
||||
let vendor_offset = usize::from(read_u16(body, 16)?);
|
||||
let vendor_length = usize::from(read_u16(body, 18)?);
|
||||
|
||||
let pins_end = min_nonzero([resource_source_offset, vendor_offset, desc.len()]);
|
||||
let pins = parse_u16_list(desc, pin_table_offset, pins_end, offset, "Gpio")?;
|
||||
let resource_source = parse_source_absolute(
|
||||
desc,
|
||||
resource_source_offset,
|
||||
min_nonzero([vendor_offset, desc.len()]),
|
||||
resource_source_index,
|
||||
offset,
|
||||
"Gpio",
|
||||
)?;
|
||||
let vendor_data = parse_blob_absolute(desc, vendor_offset, vendor_length, offset, "Gpio")?;
|
||||
|
||||
Ok((
|
||||
connection_type == 0,
|
||||
GpioDescriptor {
|
||||
revision_id: body[0],
|
||||
producer_consumer: flags & 0x0001 != 0,
|
||||
pin_config: body[6],
|
||||
shareable: int_flags & 0x0008 != 0,
|
||||
wake_capable: int_flags & 0x0010 != 0,
|
||||
io_restriction: (int_flags & 0x0003) as u8,
|
||||
triggering: if int_flags & 0x0001 != 0 {
|
||||
InterruptTrigger::Level
|
||||
} else {
|
||||
InterruptTrigger::Edge
|
||||
},
|
||||
polarity: if int_flags & 0x0002 != 0 {
|
||||
InterruptPolarity::ActiveLow
|
||||
} else {
|
||||
InterruptPolarity::ActiveHigh
|
||||
},
|
||||
drive_strength: read_u16(body, 7)?,
|
||||
debounce_timeout: read_u16(body, 9)?,
|
||||
pins,
|
||||
resource_source,
|
||||
vendor_data,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_i2c_serial_bus(
|
||||
desc: &[u8],
|
||||
body: &[u8],
|
||||
offset: usize,
|
||||
) -> Result<Option<I2cSerialBusDescriptor>, ResourceDecodeError> {
|
||||
ensure_length(body, 15, offset, "SerialBus")?;
|
||||
if body[2] != SERIAL_BUS_I2C {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let type_data_length = usize::from(read_u16(body, 7)?);
|
||||
if type_data_length < I2C_TYPE_DATA_LEN {
|
||||
return Err(ResourceDecodeError::InvalidLargeLength {
|
||||
offset,
|
||||
descriptor: "I2cSerialBus",
|
||||
minimum: 15,
|
||||
});
|
||||
}
|
||||
|
||||
let vendor_length = type_data_length - I2C_TYPE_DATA_LEN;
|
||||
let vendor_data = parse_blob_absolute(desc, 18, vendor_length, offset, "I2cSerialBus")?;
|
||||
let resource_source = parse_source_absolute(
|
||||
desc,
|
||||
12 + type_data_length,
|
||||
desc.len(),
|
||||
body[1],
|
||||
offset,
|
||||
"I2cSerialBus",
|
||||
)?;
|
||||
|
||||
Ok(Some(I2cSerialBusDescriptor {
|
||||
revision_id: body[0],
|
||||
producer_consumer: body[3] & 0x02 != 0,
|
||||
slave_mode: body[3] & 0x01 != 0,
|
||||
connection_sharing: body[3] & 0x04 != 0,
|
||||
type_revision_id: body[6],
|
||||
access_mode_10bit: read_u16(body, 4)? & 0x0001 != 0,
|
||||
connection_speed: read_u32(body, 9)?,
|
||||
slave_address: read_u16(body, 13)?,
|
||||
resource_source,
|
||||
vendor_data,
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_memory32(
|
||||
body: &[u8],
|
||||
offset: usize,
|
||||
) -> Result<Memory32RangeDescriptor, ResourceDecodeError> {
|
||||
ensure_length(body, 17, offset, "Memory32Range")?;
|
||||
Ok(Memory32RangeDescriptor {
|
||||
write_protect: body[0] & 0x01 != 0,
|
||||
minimum: read_u32(body, 1)?,
|
||||
maximum: read_u32(body, 5)?,
|
||||
alignment: read_u32(body, 9)?,
|
||||
address_length: read_u32(body, 13)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_fixed_memory32(
|
||||
body: &[u8],
|
||||
offset: usize,
|
||||
) -> Result<FixedMemory32Descriptor, ResourceDecodeError> {
|
||||
ensure_length(body, 9, offset, "FixedMemory32")?;
|
||||
Ok(FixedMemory32Descriptor {
|
||||
write_protect: body[0] & 0x01 != 0,
|
||||
address: read_u32(body, 1)?,
|
||||
address_length: read_u32(body, 5)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_address32(
|
||||
desc: &[u8],
|
||||
body: &[u8],
|
||||
offset: usize,
|
||||
) -> Result<Address32Descriptor, ResourceDecodeError> {
|
||||
ensure_length(body, 23, offset, "Address32")?;
|
||||
Ok(Address32Descriptor {
|
||||
resource_type: parse_address_type(body[0]),
|
||||
producer_consumer: body[1] & 0x01 != 0,
|
||||
decode: body[1] & 0x02 != 0,
|
||||
min_address_fixed: body[1] & 0x04 != 0,
|
||||
max_address_fixed: body[1] & 0x08 != 0,
|
||||
specific_flags: body[2],
|
||||
granularity: read_u32(body, 3)?,
|
||||
minimum: read_u32(body, 7)?,
|
||||
maximum: read_u32(body, 11)?,
|
||||
translation_offset: read_u32(body, 15)?,
|
||||
address_length: read_u32(body, 19)?,
|
||||
resource_source: if desc.len() > 26 {
|
||||
parse_source_absolute(desc, 26, desc.len(), desc[26], offset, "Address32")?
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_address64(
|
||||
desc: &[u8],
|
||||
body: &[u8],
|
||||
offset: usize,
|
||||
) -> Result<Address64Descriptor, ResourceDecodeError> {
|
||||
ensure_length(body, 43, offset, "Address64")?;
|
||||
Ok(Address64Descriptor {
|
||||
resource_type: parse_address_type(body[0]),
|
||||
producer_consumer: body[1] & 0x01 != 0,
|
||||
decode: body[1] & 0x02 != 0,
|
||||
min_address_fixed: body[1] & 0x04 != 0,
|
||||
max_address_fixed: body[1] & 0x08 != 0,
|
||||
specific_flags: body[2],
|
||||
granularity: read_u64(body, 3)?,
|
||||
minimum: read_u64(body, 11)?,
|
||||
maximum: read_u64(body, 19)?,
|
||||
translation_offset: read_u64(body, 27)?,
|
||||
address_length: read_u64(body, 35)?,
|
||||
resource_source: if desc.len() > 46 {
|
||||
parse_source_absolute(desc, 46, desc.len(), desc[46], offset, "Address64")?
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_length(
|
||||
body: &[u8],
|
||||
minimum: usize,
|
||||
offset: usize,
|
||||
descriptor: &'static str,
|
||||
) -> Result<(), ResourceDecodeError> {
|
||||
if body.len() < minimum {
|
||||
return Err(ResourceDecodeError::InvalidLargeLength {
|
||||
offset,
|
||||
descriptor,
|
||||
minimum,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_source_inline(bytes: &[u8]) -> ResourceSource {
|
||||
let index = bytes.first().copied().unwrap_or(0);
|
||||
let source = bytes.get(1..).map(parse_nul_string).unwrap_or_default();
|
||||
ResourceSource { index, source }
|
||||
}
|
||||
|
||||
fn parse_source_absolute(
|
||||
desc: &[u8],
|
||||
start: usize,
|
||||
end: usize,
|
||||
index: u8,
|
||||
offset: usize,
|
||||
descriptor: &'static str,
|
||||
) -> Result<Option<ResourceSource>, ResourceDecodeError> {
|
||||
if start == 0 || start >= end || start > desc.len() {
|
||||
return Ok(None);
|
||||
}
|
||||
let slice = desc
|
||||
.get(start..end)
|
||||
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
|
||||
Ok(Some(ResourceSource {
|
||||
index,
|
||||
source: parse_nul_string(slice),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_blob_absolute(
|
||||
desc: &[u8],
|
||||
start: usize,
|
||||
length: usize,
|
||||
offset: usize,
|
||||
descriptor: &'static str,
|
||||
) -> Result<Vec<u8>, ResourceDecodeError> {
|
||||
if start == 0 || length == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let end = start + length;
|
||||
Ok(desc
|
||||
.get(start..end)
|
||||
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?
|
||||
.to_vec())
|
||||
}
|
||||
|
||||
fn parse_u16_list(
|
||||
desc: &[u8],
|
||||
start: usize,
|
||||
end: usize,
|
||||
offset: usize,
|
||||
descriptor: &'static str,
|
||||
) -> Result<Vec<u16>, ResourceDecodeError> {
|
||||
if start == 0 || start >= end || start > desc.len() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let slice = desc
|
||||
.get(start..end)
|
||||
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
|
||||
if slice.len() % 2 != 0 {
|
||||
return Err(ResourceDecodeError::InvalidInternalOffset { offset, descriptor });
|
||||
}
|
||||
slice
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| Ok(u16::from_le_bytes([chunk[0], chunk[1]])))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_nul_string(bytes: &[u8]) -> String {
|
||||
let end = bytes
|
||||
.iter()
|
||||
.position(|byte| *byte == 0)
|
||||
.unwrap_or(bytes.len());
|
||||
String::from_utf8_lossy(&bytes[..end]).to_string()
|
||||
}
|
||||
|
||||
fn parse_address_type(value: u8) -> AddressResourceType {
|
||||
match value {
|
||||
0 => AddressResourceType::MemoryRange,
|
||||
1 => AddressResourceType::IoRange,
|
||||
2 => AddressResourceType::BusNumberRange,
|
||||
other => AddressResourceType::Unknown(other),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, ResourceDecodeError> {
|
||||
let slice = bytes
|
||||
.get(offset..offset + 2)
|
||||
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
|
||||
Ok(u16::from_le_bytes([slice[0], slice[1]]))
|
||||
}
|
||||
|
||||
fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, ResourceDecodeError> {
|
||||
let slice = bytes
|
||||
.get(offset..offset + 4)
|
||||
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
|
||||
Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
|
||||
}
|
||||
|
||||
fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, ResourceDecodeError> {
|
||||
let slice = bytes
|
||||
.get(offset..offset + 8)
|
||||
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
|
||||
Ok(u64::from_le_bytes([
|
||||
slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn min_nonzero<const N: usize>(values: [usize; N]) -> usize {
|
||||
values
|
||||
.into_iter()
|
||||
.filter(|value| *value != 0)
|
||||
.min()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{decode_resource_template, ResourceDescriptor};
|
||||
|
||||
#[test]
|
||||
fn decodes_small_irq_descriptor() {
|
||||
let resources = decode_resource_template(&[0x23, 0x0A, 0x00, 0x19, 0x79, 0x00]).unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
&resources[0],
|
||||
ResourceDescriptor::Irq(descriptor)
|
||||
if descriptor.interrupts == vec![1, 3]
|
||||
&& descriptor.shareable
|
||||
&& descriptor.wake_capable == false
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_i2c_serial_bus_descriptor() {
|
||||
let template = [
|
||||
0x8E, 0x14, 0x00, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0x80, 0x1A,
|
||||
0x06, 0x00, 0x15, 0x00, b'I', b'2', b'C', b'0', 0x00, 0x79, 0x00,
|
||||
];
|
||||
let resources = decode_resource_template(&template).unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
&resources[0],
|
||||
ResourceDescriptor::I2cSerialBus(descriptor)
|
||||
if descriptor.connection_speed == 400_000
|
||||
&& descriptor.slave_address == 0x15
|
||||
&& descriptor.resource_source.as_ref().map(|source| source.source.as_str())
|
||||
== Some("I2C0")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decodes_gpio_interrupt_descriptor() {
|
||||
let template = [
|
||||
0x8C, 0x1B, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17,
|
||||
0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, b'\\', b'_', b'S', b'B',
|
||||
0x00, 0x79, 0x00,
|
||||
];
|
||||
let resources = decode_resource_template(&template).unwrap();
|
||||
|
||||
assert!(matches!(&resources[0], ResourceDescriptor::GpioInt(_)));
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "acpid"
|
||||
description = "ACPI daemon"
|
||||
version = "0.1.0"
|
||||
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
acpi.workspace = true
|
||||
arrayvec = "0.7.6"
|
||||
log.workspace = true
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
parking_lot.workspace = true
|
||||
plain.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
redox_event.workspace = true
|
||||
rustc-hash = "1.1.0"
|
||||
thiserror.workspace = true
|
||||
ron.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
amlserde = { path = "../amlserde" }
|
||||
common = { path = "../common" }
|
||||
daemon = { path = "../../daemon" }
|
||||
libredox.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../scheme-utils" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,874 +0,0 @@
|
||||
use acpi::aml::object::{Object, WrappedObject};
|
||||
use acpi::aml::op_region::{RegionHandler, RegionSpace};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::error::Error;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{fmt, mem};
|
||||
use syscall::PAGE_SIZE;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use common::io::{Io, Pio};
|
||||
|
||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use thiserror::Error;
|
||||
|
||||
use acpi::{
|
||||
aml::{namespace::AmlName, AmlError, Interpreter},
|
||||
platform::AcpiPlatform,
|
||||
AcpiTables,
|
||||
};
|
||||
use amlserde::aml_serde_name::aml_to_symbol;
|
||||
use amlserde::{AmlSerde, AmlSerdeValue};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod dmar;
|
||||
use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler};
|
||||
|
||||
/// The raw SDT header struct, as defined by the ACPI specification.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct SdtHeader {
|
||||
pub signature: [u8; 4],
|
||||
pub length: u32,
|
||||
pub revision: u8,
|
||||
pub checksum: u8,
|
||||
pub oem_id: [u8; 6],
|
||||
pub oem_table_id: [u8; 8],
|
||||
pub oem_revision: u32,
|
||||
pub creator_id: u32,
|
||||
pub creator_revision: u32,
|
||||
}
|
||||
unsafe impl plain::Plain for SdtHeader {}
|
||||
|
||||
impl SdtHeader {
|
||||
pub fn signature(&self) -> SdtSignature {
|
||||
SdtSignature {
|
||||
signature: self.signature,
|
||||
oem_id: self.oem_id,
|
||||
oem_table_id: self.oem_table_id,
|
||||
}
|
||||
}
|
||||
pub fn length(&self) -> usize {
|
||||
self.length
|
||||
.try_into()
|
||||
.expect("expected usize to be at least 32 bits")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct SdtSignature {
|
||||
pub signature: [u8; 4],
|
||||
pub oem_id: [u8; 6],
|
||||
pub oem_table_id: [u8; 8],
|
||||
}
|
||||
|
||||
impl fmt::Display for SdtSignature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}-{}-{}",
|
||||
String::from_utf8_lossy(&self.signature),
|
||||
String::from_utf8_lossy(&self.oem_id),
|
||||
String::from_utf8_lossy(&self.oem_table_id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TablePhysLoadError {
|
||||
// TODO: Make syscall::Error implement std::error::Error, when enabling a Cargo feature.
|
||||
#[error("i/o error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("invalid SDT: {0}")]
|
||||
Validity(#[from] InvalidSdtError),
|
||||
}
|
||||
#[derive(Debug, Error)]
|
||||
pub enum InvalidSdtError {
|
||||
#[error("invalid size")]
|
||||
InvalidSize,
|
||||
|
||||
#[error("invalid checksum")]
|
||||
BadChecksum,
|
||||
}
|
||||
|
||||
struct PhysmapGuard {
|
||||
virt: *const u8,
|
||||
size: usize,
|
||||
}
|
||||
impl PhysmapGuard {
|
||||
fn map(page: usize, page_count: usize) -> std::io::Result<Self> {
|
||||
let size = page_count * PAGE_SIZE;
|
||||
let virt = unsafe {
|
||||
common::physmap(page, size, common::Prot::RO, common::MemoryType::default())
|
||||
.map_err(|error| std::io::Error::from_raw_os_error(error.errno()))?
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
virt: virt as *const u8,
|
||||
size,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Deref for PhysmapGuard {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { std::slice::from_raw_parts(self.virt as *const u8, self.size) }
|
||||
}
|
||||
}
|
||||
impl Drop for PhysmapGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = libredox::call::munmap(self.virt as *mut (), self.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Sdt(Arc<[u8]>);
|
||||
|
||||
impl Sdt {
|
||||
pub fn new(slice: Arc<[u8]>) -> Result<Self, InvalidSdtError> {
|
||||
let header = match plain::from_bytes::<SdtHeader>(&slice) {
|
||||
Ok(header) => header,
|
||||
Err(plain::Error::TooShort) => return Err(InvalidSdtError::InvalidSize),
|
||||
Err(plain::Error::BadAlignment) => {
|
||||
log::error!("acpid: plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)] - internal inconsistency");
|
||||
return Err(InvalidSdtError::InvalidSize);
|
||||
}
|
||||
};
|
||||
|
||||
if header.length() != slice.len() {
|
||||
return Err(InvalidSdtError::InvalidSize);
|
||||
}
|
||||
|
||||
let checksum = slice
|
||||
.iter()
|
||||
.copied()
|
||||
.fold(0_u8, |current_sum, item| current_sum.wrapping_add(item));
|
||||
|
||||
if checksum != 0 {
|
||||
return Err(InvalidSdtError::BadChecksum);
|
||||
}
|
||||
|
||||
Ok(Self(slice))
|
||||
}
|
||||
pub fn load_from_physical(physaddr: usize) -> Result<Self, TablePhysLoadError> {
|
||||
let physaddr_start_page = physaddr / PAGE_SIZE * PAGE_SIZE;
|
||||
let physaddr_page_offset = physaddr % PAGE_SIZE;
|
||||
|
||||
// Begin by reading and validating the header first. The SDT header is always 36 bytes
|
||||
// long, and can thus span either one or two page table frames.
|
||||
let needs_extra_page = (PAGE_SIZE - physaddr_page_offset)
|
||||
.checked_sub(mem::size_of::<SdtHeader>())
|
||||
.is_none();
|
||||
let page_table_count = 1 + if needs_extra_page { 1 } else { 0 };
|
||||
|
||||
let pages = PhysmapGuard::map(physaddr_start_page, page_table_count)?;
|
||||
assert!(pages.len() >= mem::size_of::<SdtHeader>());
|
||||
let sdt_mem = &pages[physaddr_page_offset..];
|
||||
|
||||
let sdt = plain::from_bytes::<SdtHeader>(&sdt_mem[..mem::size_of::<SdtHeader>()])
|
||||
.expect("either alignment is wrong, or the length is too short, both of which are already checked for");
|
||||
|
||||
let total_length = sdt.length();
|
||||
let base_length = std::cmp::min(total_length, sdt_mem.len());
|
||||
let extended_length = total_length - base_length;
|
||||
|
||||
let mut loaded = sdt_mem[..base_length].to_owned();
|
||||
loaded.reserve(extended_length);
|
||||
|
||||
const SIMULTANEOUS_PAGE_COUNT: usize = 4;
|
||||
|
||||
let mut left = extended_length;
|
||||
let mut offset = physaddr_start_page + page_table_count * PAGE_SIZE;
|
||||
|
||||
let length_per_iteration = PAGE_SIZE * SIMULTANEOUS_PAGE_COUNT;
|
||||
|
||||
while left > 0 {
|
||||
let to_copy = std::cmp::min(left, length_per_iteration);
|
||||
let additional_pages = PhysmapGuard::map(offset, to_copy.div_ceil(PAGE_SIZE))?;
|
||||
|
||||
loaded.extend(&additional_pages[..to_copy]);
|
||||
|
||||
left -= to_copy;
|
||||
offset += to_copy;
|
||||
}
|
||||
assert_eq!(left, 0);
|
||||
|
||||
Self::new(loaded.into()).map_err(Into::into)
|
||||
}
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Sdt {
|
||||
type Target = SdtHeader;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes::<SdtHeader>(&self.0)
|
||||
.expect("expected already validated Sdt to be able to get its header")
|
||||
}
|
||||
}
|
||||
|
||||
impl Sdt {
|
||||
pub fn data(&self) -> &[u8] {
|
||||
&self.0[mem::size_of::<SdtHeader>()..]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Sdt {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Sdt")
|
||||
.field("header", &*self as &SdtHeader)
|
||||
.field("extra_len", &self.data().len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dsdt(Sdt);
|
||||
pub struct Ssdt(Sdt);
|
||||
|
||||
// Current AML implementation builds the aml_context.namespace at startup,
|
||||
// but the cache for symbols is lazy-loaded when someone
|
||||
// reads from the acpi:/symbols scheme.
|
||||
// If you dynamically add an SDT, you can add to the namespace, but you
|
||||
// must empty the cache so it is rebuilt.
|
||||
// If you modify an SDT, you must discard the aml_context and rebuild it.
|
||||
pub struct AmlSymbols {
|
||||
aml_context: Option<Interpreter<AmlPhysMemHandler>>,
|
||||
// k = name, v = description
|
||||
symbol_cache: FxHashMap<String, String>,
|
||||
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||
aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
||||
}
|
||||
|
||||
impl AmlSymbols {
|
||||
pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> Self {
|
||||
Self {
|
||||
aml_context: None,
|
||||
symbol_cache: FxHashMap::default(),
|
||||
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
|
||||
aml_region_handlers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, pci_fd: Option<&libredox::Fd>) -> Result<(), Box<dyn Error>> {
|
||||
if self.aml_context.is_some() {
|
||||
return Err("AML interpreter already initialized".into());
|
||||
}
|
||||
let format_err = |err| format!("{:?}", err);
|
||||
let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
|
||||
//TODO: use these parsed tables for the rest of acpid
|
||||
let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
|
||||
let tables =
|
||||
unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? };
|
||||
let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?;
|
||||
let interpreter = Interpreter::new_from_platform(&platform).map_err(format_err)?;
|
||||
for (region, handler) in self.aml_region_handlers.drain(..) {
|
||||
interpreter.install_region_handler(region, handler);
|
||||
}
|
||||
self.aml_context = Some(interpreter);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn aml_context_mut(
|
||||
&mut self,
|
||||
pci_fd: Option<&libredox::Fd>,
|
||||
) -> Result<&mut Interpreter<AmlPhysMemHandler>, AmlEvalError> {
|
||||
if self.aml_context.is_none() {
|
||||
match self.init(pci_fd) {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
log::error!("failed to initialize AML context: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.aml_context
|
||||
.as_mut()
|
||||
.ok_or(AmlEvalError::NotInitialized)
|
||||
}
|
||||
|
||||
pub fn symbols_cache(&self) -> &FxHashMap<String, String> {
|
||||
&self.symbol_cache
|
||||
}
|
||||
|
||||
pub fn lookup(&self, symbol: &str) -> Option<String> {
|
||||
if let Some(description) = self.symbol_cache.get(symbol) {
|
||||
log::trace!("Found symbol in cache, {}, {}", symbol, description);
|
||||
return Some(description.to_owned());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn build_cache(&mut self, pci_fd: Option<&libredox::Fd>) {
|
||||
let Ok(aml_context) = self.aml_context_mut(pci_fd) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut symbol_list: Vec<(AmlName, String)> = Vec::with_capacity(5000);
|
||||
|
||||
if aml_context
|
||||
.namespace
|
||||
.lock()
|
||||
.traverse(|level_aml_name, level| {
|
||||
for (child_seg, handle) in level.values.iter() {
|
||||
if let Ok(aml_name) =
|
||||
AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name)
|
||||
{
|
||||
let name = aml_to_symbol(&aml_name);
|
||||
symbol_list.push((aml_name, name));
|
||||
} else {
|
||||
log::error!(
|
||||
"AmlName resolve failed, {:?}:{:?}",
|
||||
level_aml_name,
|
||||
child_seg
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
log::error!("Namespace traverse failed");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut symbol_cache: FxHashMap<String, String> = FxHashMap::default();
|
||||
|
||||
for (aml_name, name) in &symbol_list {
|
||||
// create an empty entry, in case something goes wrong with serialization
|
||||
symbol_cache.insert(name.to_owned(), "".to_owned());
|
||||
if let Some(ser_value) = AmlSerde::from_aml(aml_context, aml_name) {
|
||||
if let Ok(ser_string) = ron::ser::to_string_pretty(&ser_value, Default::default()) {
|
||||
// replace the empty entry
|
||||
symbol_cache.insert(name.to_owned(), ser_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the new list
|
||||
log::trace!("Updating symbols list");
|
||||
|
||||
self.symbol_cache = symbol_cache;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AmlEvalError {
|
||||
#[error("AML error")]
|
||||
AmlError(AmlError),
|
||||
#[error("Failed to serialize argument")]
|
||||
SerializationError,
|
||||
#[error("Failed to deserialize")]
|
||||
DeserializationError,
|
||||
#[error("AML not initialized")]
|
||||
NotInitialized,
|
||||
}
|
||||
impl From<AmlError> for AmlEvalError {
|
||||
fn from(value: AmlError) -> Self {
|
||||
AmlEvalError::AmlError(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcpiContext {
|
||||
tables: Vec<Sdt>,
|
||||
dsdt: Option<Dsdt>,
|
||||
fadt: Option<Fadt>,
|
||||
|
||||
aml_symbols: RwLock<AmlSymbols>,
|
||||
|
||||
// TODO: The kernel ACPI code seemed to use load_table quite ubiquitously, however ACPI 5.1
|
||||
// states that DDBHandles can only be obtained when loading XSDT-pointed tables. So, we'll
|
||||
// generate an index only for those.
|
||||
sdt_order: RwLock<Vec<Option<SdtSignature>>>,
|
||||
|
||||
pub next_ctx: RwLock<u64>,
|
||||
}
|
||||
|
||||
impl AcpiContext {
|
||||
pub fn aml_eval(
|
||||
&self,
|
||||
symbol: AmlName,
|
||||
args: Vec<AmlSerdeValue>,
|
||||
) -> Result<AmlSerdeValue, AmlEvalError> {
|
||||
let mut symbols = self.aml_symbols.write();
|
||||
let interpreter = symbols.aml_context_mut(None)?;
|
||||
interpreter.acquire_global_lock(16)?;
|
||||
|
||||
let args = args
|
||||
.into_iter()
|
||||
.map(|aml_serde_value| {
|
||||
aml_serde_value
|
||||
.to_aml_object()
|
||||
.map(Object::wrap)
|
||||
.ok_or(AmlEvalError::DeserializationError)
|
||||
})
|
||||
.collect::<Result<Vec<WrappedObject>, AmlEvalError>>()?;
|
||||
|
||||
let result = interpreter.evaluate(symbol, args);
|
||||
interpreter
|
||||
.release_global_lock()
|
||||
.expect("Failed to release GIL!"); //TODO: check if this should panic
|
||||
|
||||
result
|
||||
.map_err(AmlEvalError::from)
|
||||
.map(|object| {
|
||||
AmlSerdeValue::from_aml_value(object.deref())
|
||||
.ok_or(AmlEvalError::SerializationError)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
rxsdt_physaddrs: impl Iterator<Item = u64>,
|
||||
ec: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
||||
) -> Self {
|
||||
let tables = rxsdt_physaddrs
|
||||
.map(|physaddr| {
|
||||
let physaddr: usize = physaddr
|
||||
.try_into()
|
||||
.expect("expected ACPI addresses to be compatible with the current word size");
|
||||
|
||||
log::trace!("TABLE AT {:#>08X}", physaddr);
|
||||
|
||||
Sdt::load_from_physical(physaddr).expect("failed to load physical SDT")
|
||||
})
|
||||
.collect::<Vec<Sdt>>();
|
||||
|
||||
let mut this = Self {
|
||||
tables,
|
||||
dsdt: None,
|
||||
fadt: None,
|
||||
|
||||
// Temporary values
|
||||
aml_symbols: RwLock::new(AmlSymbols::new(ec)),
|
||||
|
||||
next_ctx: RwLock::new(0),
|
||||
|
||||
sdt_order: RwLock::new(Vec::new()),
|
||||
};
|
||||
|
||||
for table in &this.tables {
|
||||
this.new_index(&table.signature());
|
||||
}
|
||||
|
||||
Fadt::init(&mut this);
|
||||
//TODO (hangs on real hardware): Dmar::init(&this);
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn dsdt(&self) -> Option<&Dsdt> {
|
||||
self.dsdt.as_ref()
|
||||
}
|
||||
pub fn ssdts(&self) -> impl Iterator<Item = Ssdt> + '_ {
|
||||
self.find_multiple_sdts(*b"SSDT")
|
||||
.map(|sdt| Ssdt(sdt.clone()))
|
||||
}
|
||||
fn find_single_sdt_pos(&self, signature: [u8; 4]) -> Option<usize> {
|
||||
let count = self
|
||||
.tables
|
||||
.iter()
|
||||
.filter(|sdt| sdt.signature == signature)
|
||||
.count();
|
||||
|
||||
if count > 1 {
|
||||
log::warn!(
|
||||
"Expected only a single SDT of signature `{}` ({:?}), but there were {}",
|
||||
String::from_utf8_lossy(&signature),
|
||||
signature,
|
||||
count
|
||||
);
|
||||
}
|
||||
|
||||
self.tables
|
||||
.iter()
|
||||
.position(|sdt| sdt.signature == signature)
|
||||
}
|
||||
pub fn find_multiple_sdts<'a>(&'a self, signature: [u8; 4]) -> impl Iterator<Item = &'a Sdt> {
|
||||
self.tables
|
||||
.iter()
|
||||
.filter(move |sdt| sdt.signature == signature)
|
||||
}
|
||||
pub fn take_single_sdt(&self, signature: [u8; 4]) -> Option<Sdt> {
|
||||
self.find_single_sdt_pos(signature)
|
||||
.map(|pos| self.tables[pos].clone())
|
||||
}
|
||||
pub fn fadt(&self) -> Option<&Fadt> {
|
||||
self.fadt.as_ref()
|
||||
}
|
||||
pub fn sdt_from_signature(&self, signature: &SdtSignature) -> Option<&Sdt> {
|
||||
self.tables.iter().find(|sdt| {
|
||||
sdt.signature == signature.signature
|
||||
&& sdt.oem_id == signature.oem_id
|
||||
&& sdt.oem_table_id == signature.oem_table_id
|
||||
})
|
||||
}
|
||||
pub fn get_signature_from_index(&self, index: usize) -> Option<SdtSignature> {
|
||||
self.sdt_order.read().get(index).copied().flatten()
|
||||
}
|
||||
pub fn get_index_from_signature(&self, signature: &SdtSignature) -> Option<usize> {
|
||||
self.sdt_order
|
||||
.read()
|
||||
.iter()
|
||||
.rposition(|sig| sig.map_or(false, |sig| &sig == signature))
|
||||
}
|
||||
pub fn tables(&self) -> &[Sdt] {
|
||||
&self.tables
|
||||
}
|
||||
pub fn new_index(&self, signature: &SdtSignature) {
|
||||
self.sdt_order.write().push(Some(*signature));
|
||||
}
|
||||
|
||||
pub fn aml_lookup(&self, symbol: &str) -> Option<String> {
|
||||
if let Ok(aml_symbols) = self.aml_symbols(None) {
|
||||
aml_symbols.lookup(symbol)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aml_symbols(
|
||||
&self,
|
||||
pci_fd: Option<&libredox::Fd>,
|
||||
) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
|
||||
// return the cached value if it exists
|
||||
let symbols = self.aml_symbols.read();
|
||||
if !symbols.symbols_cache().is_empty() {
|
||||
return Ok(symbols);
|
||||
}
|
||||
// free the read lock
|
||||
drop(symbols);
|
||||
|
||||
// List has not been initialized, we have to build it
|
||||
log::trace!("Creating symbols list");
|
||||
|
||||
let mut aml_symbols = self.aml_symbols.write();
|
||||
|
||||
aml_symbols.build_cache(pci_fd);
|
||||
|
||||
// return the cached value
|
||||
Ok(RwLockWriteGuard::downgrade(aml_symbols))
|
||||
}
|
||||
|
||||
/// Discard any cached symbols list. To be called if the AML namespace changes.
|
||||
pub fn aml_symbols_reset(&self) {
|
||||
let mut aml_symbols = self.aml_symbols.write();
|
||||
aml_symbols.symbol_cache = FxHashMap::default();
|
||||
}
|
||||
|
||||
/// Set Power State
|
||||
/// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf
|
||||
/// - search for PM1a
|
||||
/// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details
|
||||
pub fn set_global_s_state(&self, state: u8) {
|
||||
if state != 5 {
|
||||
return;
|
||||
}
|
||||
let fadt = match self.fadt() {
|
||||
Some(fadt) => fadt,
|
||||
None => {
|
||||
log::error!("Cannot set global S-state due to missing FADT.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let port = fadt.pm1a_control_block as u16;
|
||||
let mut val = 1 << 13;
|
||||
|
||||
let aml_symbols = self.aml_symbols.read();
|
||||
|
||||
let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") {
|
||||
Ok(aml_name) => aml_name,
|
||||
Err(error) => {
|
||||
log::error!("Could not build AmlName for \\_S5, {:?}", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let s5 = match &aml_symbols.aml_context {
|
||||
Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) {
|
||||
Ok(s5) => s5,
|
||||
Err(error) => {
|
||||
log::error!("Cannot set S-state, missing \\_S5, {:?}", error);
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::error!("Cannot set S-state, AML context not initialized");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let package = match s5.deref() {
|
||||
acpi::aml::object::Object::Package(package) => package,
|
||||
_ => {
|
||||
log::error!("Cannot set S-state, \\_S5 is not a package");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let slp_typa = match package[0].deref() {
|
||||
acpi::aml::object::Object::Integer(i) => i.to_owned(),
|
||||
_ => {
|
||||
log::error!("typa is not an Integer");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let slp_typb = match package[1].deref() {
|
||||
acpi::aml::object::Object::Integer(i) => i.to_owned(),
|
||||
_ => {
|
||||
log::error!("typb is not an Integer");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb);
|
||||
val |= slp_typa as u16;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
{
|
||||
log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val);
|
||||
Pio::<u16>::new(port).write(val);
|
||||
}
|
||||
|
||||
// TODO: Handle SLP_TYPb
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
{
|
||||
log::error!(
|
||||
"Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture",
|
||||
port,
|
||||
val
|
||||
);
|
||||
}
|
||||
|
||||
loop {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FadtStruct {
|
||||
pub header: SdtHeader,
|
||||
pub firmware_ctrl: u32,
|
||||
pub dsdt: u32,
|
||||
|
||||
// field used in ACPI 1.0; no longer in use, for compatibility only
|
||||
reserved: u8,
|
||||
|
||||
pub preferred_power_managament: u8,
|
||||
pub sci_interrupt: u16,
|
||||
pub smi_command_port: u32,
|
||||
pub acpi_enable: u8,
|
||||
pub acpi_disable: u8,
|
||||
pub s4_bios_req: u8,
|
||||
pub pstate_control: u8,
|
||||
pub pm1a_event_block: u32,
|
||||
pub pm1b_event_block: u32,
|
||||
pub pm1a_control_block: u32,
|
||||
pub pm1b_control_block: u32,
|
||||
pub pm2_control_block: u32,
|
||||
pub pm_timer_block: u32,
|
||||
pub gpe0_block: u32,
|
||||
pub gpe1_block: u32,
|
||||
pub pm1_event_length: u8,
|
||||
pub pm1_control_length: u8,
|
||||
pub pm2_control_length: u8,
|
||||
pub pm_timer_length: u8,
|
||||
pub gpe0_ength: u8,
|
||||
pub gpe1_length: u8,
|
||||
pub gpe1_base: u8,
|
||||
pub c_state_control: u8,
|
||||
pub worst_c2_latency: u16,
|
||||
pub worst_c3_latency: u16,
|
||||
pub flush_size: u16,
|
||||
pub flush_stride: u16,
|
||||
pub duty_offset: u8,
|
||||
pub duty_width: u8,
|
||||
pub day_alarm: u8,
|
||||
pub month_alarm: u8,
|
||||
pub century: u8,
|
||||
|
||||
// reserved in ACPI 1.0; used since ACPI 2.0+
|
||||
pub boot_architecture_flags: u16,
|
||||
|
||||
reserved2: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
unsafe impl plain::Plain for FadtStruct {}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct GenericAddressStructure {
|
||||
address_space: u8,
|
||||
bit_width: u8,
|
||||
bit_offset: u8,
|
||||
access_size: u8,
|
||||
address: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FadtAcpi2Struct {
|
||||
// 12 byte structure; see below for details
|
||||
pub reset_reg: GenericAddressStructure,
|
||||
|
||||
pub reset_value: u8,
|
||||
reserved3: [u8; 3],
|
||||
|
||||
// 64bit pointers - Available on ACPI 2.0+
|
||||
pub x_firmware_control: u64,
|
||||
pub x_dsdt: u64,
|
||||
|
||||
pub x_pm1a_event_block: GenericAddressStructure,
|
||||
pub x_pm1b_event_block: GenericAddressStructure,
|
||||
pub x_pm1a_control_block: GenericAddressStructure,
|
||||
pub x_pm1b_control_block: GenericAddressStructure,
|
||||
pub x_pm2_control_block: GenericAddressStructure,
|
||||
pub x_pm_timer_block: GenericAddressStructure,
|
||||
pub x_gpe0_block: GenericAddressStructure,
|
||||
pub x_gpe1_block: GenericAddressStructure,
|
||||
}
|
||||
unsafe impl plain::Plain for FadtAcpi2Struct {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Fadt(Sdt);
|
||||
|
||||
impl Fadt {
|
||||
pub fn acpi_2_struct(&self) -> Option<&FadtAcpi2Struct> {
|
||||
let bytes = &self.0 .0[mem::size_of::<FadtStruct>()..];
|
||||
|
||||
match plain::from_bytes::<FadtAcpi2Struct>(bytes) {
|
||||
Ok(fadt2) => Some(fadt2),
|
||||
Err(plain::Error::TooShort) => None,
|
||||
Err(plain::Error::BadAlignment) => unreachable!(
|
||||
"plain::from_bytes reported bad alignment, but FadtAcpi2Struct is #[repr(packed)]"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Fadt {
|
||||
type Target = FadtStruct;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes::<FadtStruct>(&self.0 .0)
|
||||
.expect("expected FADT struct to already be validated in Deref impl")
|
||||
}
|
||||
}
|
||||
|
||||
impl Fadt {
|
||||
pub fn new(sdt: Sdt) -> Option<Fadt> {
|
||||
if sdt.signature != *b"FACP" || sdt.length() < mem::size_of::<Fadt>() {
|
||||
return None;
|
||||
}
|
||||
Some(Fadt(sdt))
|
||||
}
|
||||
|
||||
pub fn init(context: &mut AcpiContext) {
|
||||
let fadt_sdt = context
|
||||
.take_single_sdt(*b"FACP")
|
||||
.expect("expected ACPI to always have a FADT");
|
||||
|
||||
let fadt = match Fadt::new(fadt_sdt) {
|
||||
Some(fadt) => fadt,
|
||||
None => {
|
||||
log::error!("Failed to find FADT");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let dsdt_ptr = match fadt.acpi_2_struct() {
|
||||
Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| {
|
||||
usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize")
|
||||
}),
|
||||
None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"),
|
||||
};
|
||||
|
||||
log::debug!("FACP at {:X}", { dsdt_ptr });
|
||||
|
||||
let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) {
|
||||
Ok(dsdt) => dsdt,
|
||||
Err(error) => {
|
||||
log::error!("Failed to load DSDT: {}", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
context.fadt = Some(fadt.clone());
|
||||
context.dsdt = Some(Dsdt(dsdt_sdt.clone()));
|
||||
|
||||
context.tables.push(dsdt_sdt);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PossibleAmlTables {
|
||||
Dsdt(Dsdt),
|
||||
Ssdt(Ssdt),
|
||||
}
|
||||
impl PossibleAmlTables {
|
||||
pub fn try_new(inner: Sdt) -> Option<Self> {
|
||||
match &inner.signature {
|
||||
b"DSDT" => Some(Self::Dsdt(Dsdt(inner))),
|
||||
b"SSDT" => Some(Self::Ssdt(Ssdt(inner))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AmlContainingTable for PossibleAmlTables {
|
||||
fn aml(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Dsdt(dsdt) => dsdt.aml(),
|
||||
Self::Ssdt(ssdt) => ssdt.aml(),
|
||||
}
|
||||
}
|
||||
fn header(&self) -> &SdtHeader {
|
||||
match self {
|
||||
Self::Dsdt(dsdt) => dsdt.header(),
|
||||
Self::Ssdt(ssdt) => ssdt.header(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AmlContainingTable {
|
||||
fn aml(&self) -> &[u8];
|
||||
fn header(&self) -> &SdtHeader;
|
||||
}
|
||||
|
||||
impl<T> AmlContainingTable for &T
|
||||
where
|
||||
T: AmlContainingTable,
|
||||
{
|
||||
fn aml(&self) -> &[u8] {
|
||||
T::aml(*self)
|
||||
}
|
||||
fn header(&self) -> &SdtHeader {
|
||||
T::header(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AmlContainingTable for Dsdt {
|
||||
fn aml(&self) -> &[u8] {
|
||||
self.0.data()
|
||||
}
|
||||
fn header(&self) -> &SdtHeader {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
impl AmlContainingTable for Ssdt {
|
||||
fn aml(&self) -> &[u8] {
|
||||
self.0.data()
|
||||
}
|
||||
fn header(&self) -> &SdtHeader {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use common::io::Mmio;
|
||||
|
||||
// TODO: Only wrap with Mmio where there are hardware-registers. (Some of these structs seem to be
|
||||
// ring buffer entries, which are not to be treated the same way).
|
||||
|
||||
pub struct DrhdPage {
|
||||
virt: *mut Drhd,
|
||||
}
|
||||
impl DrhdPage {
|
||||
pub fn map(base_phys: usize) -> syscall::Result<Self> {
|
||||
assert_eq!(
|
||||
base_phys % crate::acpi::PAGE_SIZE,
|
||||
0,
|
||||
"DRHD registers must be page-aligned"
|
||||
);
|
||||
|
||||
// TODO: Uncachable? Can reads have side-effects?
|
||||
let virt = unsafe {
|
||||
common::physmap(
|
||||
base_phys,
|
||||
crate::acpi::PAGE_SIZE,
|
||||
common::Prot::RO,
|
||||
common::MemoryType::default(),
|
||||
)?
|
||||
} as *mut Drhd;
|
||||
|
||||
Ok(Self { virt })
|
||||
}
|
||||
}
|
||||
impl Deref for DrhdPage {
|
||||
type Target = Drhd;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.virt }
|
||||
}
|
||||
}
|
||||
impl DerefMut for DrhdPage {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { &mut *self.virt }
|
||||
}
|
||||
}
|
||||
impl Drop for DrhdPage {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = libredox::call::munmap(self.virt.cast(), crate::acpi::PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DrhdFault {
|
||||
pub sts: Mmio<u32>,
|
||||
pub ctrl: Mmio<u32>,
|
||||
pub data: Mmio<u32>,
|
||||
pub addr: [Mmio<u32>; 2],
|
||||
_rsv: [Mmio<u64>; 2],
|
||||
pub log: Mmio<u64>,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DrhdProtectedMemory {
|
||||
pub en: Mmio<u32>,
|
||||
pub low_base: Mmio<u32>,
|
||||
pub low_limit: Mmio<u32>,
|
||||
pub high_base: Mmio<u64>,
|
||||
pub high_limit: Mmio<u64>,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DrhdInvalidation {
|
||||
pub queue_head: Mmio<u64>,
|
||||
pub queue_tail: Mmio<u64>,
|
||||
pub queue_addr: Mmio<u64>,
|
||||
_rsv: Mmio<u32>,
|
||||
pub cmpl_sts: Mmio<u32>,
|
||||
pub cmpl_ctrl: Mmio<u32>,
|
||||
pub cmpl_data: Mmio<u32>,
|
||||
pub cmpl_addr: [Mmio<u32>; 2],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DrhdPageRequest {
|
||||
pub queue_head: Mmio<u64>,
|
||||
pub queue_tail: Mmio<u64>,
|
||||
pub queue_addr: Mmio<u64>,
|
||||
_rsv: Mmio<u32>,
|
||||
pub sts: Mmio<u32>,
|
||||
pub ctrl: Mmio<u32>,
|
||||
pub data: Mmio<u32>,
|
||||
pub addr: [Mmio<u32>; 2],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DrhdMtrrVariable {
|
||||
pub base: Mmio<u64>,
|
||||
pub mask: Mmio<u64>,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DrhdMtrr {
|
||||
pub cap: Mmio<u64>,
|
||||
pub def_type: Mmio<u64>,
|
||||
pub fixed: [Mmio<u64>; 11],
|
||||
pub variable: [DrhdMtrrVariable; 10],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct Drhd {
|
||||
pub version: Mmio<u32>,
|
||||
_rsv: Mmio<u32>,
|
||||
pub cap: Mmio<u64>,
|
||||
pub ext_cap: Mmio<u64>,
|
||||
pub gl_cmd: Mmio<u32>,
|
||||
pub gl_sts: Mmio<u32>,
|
||||
pub root_table: Mmio<u64>,
|
||||
pub ctx_cmd: Mmio<u64>,
|
||||
_rsv1: Mmio<u32>,
|
||||
pub fault: DrhdFault,
|
||||
_rsv2: Mmio<u32>,
|
||||
pub pm: DrhdProtectedMemory,
|
||||
pub invl: DrhdInvalidation,
|
||||
_rsv3: Mmio<u64>,
|
||||
pub intr_table: Mmio<u64>,
|
||||
pub page_req: DrhdPageRequest,
|
||||
pub mtrr: DrhdMtrr,
|
||||
}
|
||||
@@ -1,528 +0,0 @@
|
||||
//! DMA Remapping Table -- `DMAR`. This is Intel's implementation of IOMMU functionality, known as
|
||||
//! VT-d.
|
||||
//!
|
||||
//! Too understand what all of these structs mean, refer to the "Intel(R) Virtualization
|
||||
//! Technology for Directed I/O" specification.
|
||||
|
||||
// TODO: Move this code to a separate driver as well?
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::ops::Deref;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use common::io::Io as _;
|
||||
|
||||
use num_derive::FromPrimitive;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use self::drhd::DrhdPage;
|
||||
use crate::acpi::{AcpiContext, Sdt, SdtHeader};
|
||||
|
||||
pub mod drhd;
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DmarStruct {
|
||||
pub sdt_header: SdtHeader,
|
||||
pub host_addr_width: u8,
|
||||
pub flags: u8,
|
||||
pub _rsvd: [u8; 10],
|
||||
// This header is followed by N remapping structures.
|
||||
}
|
||||
unsafe impl plain::Plain for DmarStruct {}
|
||||
|
||||
/// The DMA Remapping Table
|
||||
#[derive(Debug)]
|
||||
pub struct Dmar(Sdt);
|
||||
|
||||
impl Dmar {
|
||||
fn remmapping_structs_area(&self) -> &[u8] {
|
||||
&self.0.as_slice()[mem::size_of::<DmarStruct>()..]
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Dmar {
|
||||
type Target = DmarStruct;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes(self.0.as_slice())
|
||||
.expect("expected Dmar struct to already have checked the length, and alignment issues should be impossible due to #[repr(packed)]")
|
||||
}
|
||||
}
|
||||
|
||||
impl Dmar {
|
||||
// TODO: Again, perhaps put this code into a different driver, and read the table the regular
|
||||
// way via the acpi scheme?
|
||||
pub fn init(acpi_ctx: &AcpiContext) {
|
||||
let dmar_sdt = match acpi_ctx.take_single_sdt(*b"DMAR") {
|
||||
Some(dmar_sdt) => dmar_sdt,
|
||||
None => {
|
||||
log::warn!("Unable to find `DMAR` ACPI table.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dmar = match Dmar::new(dmar_sdt) {
|
||||
Some(dmar) => dmar,
|
||||
None => {
|
||||
log::error!("Failed to parse DMAR table, possibly malformed.");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
log::info!("Found DMAR: {}: {}", dmar.host_addr_width, dmar.flags);
|
||||
log::debug!("DMAR: {:?}", dmar);
|
||||
|
||||
for dmar_entry in dmar.iter() {
|
||||
log::debug!("DMAR entry: {:?}", dmar_entry);
|
||||
match dmar_entry {
|
||||
DmarEntry::Drhd(dmar_drhd) => {
|
||||
let drhd = dmar_drhd.map();
|
||||
|
||||
log::debug!("VER: {:X}", drhd.version.read());
|
||||
log::debug!("CAP: {:X}", drhd.cap.read());
|
||||
log::debug!("EXT_CAP: {:X}", drhd.ext_cap.read());
|
||||
log::debug!("GCMD: {:X}", drhd.gl_cmd.read());
|
||||
log::debug!("GSTS: {:X}", drhd.gl_sts.read());
|
||||
log::debug!("RT: {:X}", drhd.root_table.read());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new(sdt: Sdt) -> Option<Dmar> {
|
||||
assert_eq!(
|
||||
sdt.signature, *b"DMAR",
|
||||
"signature already checked against `DMAR`"
|
||||
);
|
||||
if sdt.length() < mem::size_of::<DmarStruct>() {
|
||||
log::error!(
|
||||
"The DMAR table was too small ({} B < {} B).",
|
||||
sdt.length(),
|
||||
mem::size_of::<Dmar>()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
// No need to check alignment for #[repr(packed)] structs.
|
||||
|
||||
Some(Dmar(sdt))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> DmarIter<'_> {
|
||||
DmarIter(DmarRawIter {
|
||||
bytes: self.remmapping_structs_area(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// DMAR DMA Remapping Hardware Unit Definition
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DmarDrhdHeader {
|
||||
pub kind: u16,
|
||||
pub length: u16,
|
||||
|
||||
pub flags: u8,
|
||||
pub _rsv: u8,
|
||||
pub segment: u16,
|
||||
pub base: u64,
|
||||
}
|
||||
unsafe impl plain::Plain for DmarDrhdHeader {}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DeviceScopeHeader {
|
||||
pub ty: u8,
|
||||
pub len: u8,
|
||||
pub _rsvd: u16,
|
||||
pub enumeration_id: u8,
|
||||
pub start_bus_num: u8,
|
||||
// The variable-sized path comes after.
|
||||
}
|
||||
unsafe impl plain::Plain for DeviceScopeHeader {}
|
||||
|
||||
pub struct DeviceScope(Box<[u8]>);
|
||||
|
||||
impl DeviceScope {
|
||||
pub fn try_new(raw: &[u8]) -> Option<Self> {
|
||||
// TODO: Check ty.
|
||||
|
||||
let header_bytes = match raw.get(..mem::size_of::<DeviceScopeHeader>()) {
|
||||
Some(bytes) => bytes,
|
||||
None => return None,
|
||||
};
|
||||
let header = plain::from_bytes::<DeviceScopeHeader>(header_bytes)
|
||||
.expect("length already checked, and alignment 1 (#[repr(packed)] should suffice");
|
||||
|
||||
let len = usize::from(header.len);
|
||||
|
||||
if len > raw.len() {
|
||||
log::warn!("Device scope smaller than len field.");
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(raw.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DeviceScope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DeviceScope")
|
||||
.field("header", &*self as &DeviceScopeHeader)
|
||||
.field("path", &self.path())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DeviceScope {
|
||||
type Target = DeviceScopeHeader;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes(&self.0)
|
||||
.expect("expected length to be sufficient, and alignment (due to #[repr(packed)]")
|
||||
}
|
||||
}
|
||||
impl DeviceScope {
|
||||
pub fn path(&self) -> &[u8] {
|
||||
&self.0[mem::size_of::<DeviceScopeHeader>()..]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DmarDrhd(Box<[u8]>);
|
||||
|
||||
impl DmarDrhd {
|
||||
pub fn try_new(raw: &[u8]) -> Option<Self> {
|
||||
if raw.len() < mem::size_of::<DmarDrhdHeader>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(raw.into()))
|
||||
}
|
||||
pub fn device_scope_area(&self) -> &[u8] {
|
||||
&self.0[mem::size_of::<DmarDrhdHeader>()..]
|
||||
}
|
||||
pub fn map(&self) -> DrhdPage {
|
||||
let base = usize::try_from(self.base).expect("expected u64 to fit within usize");
|
||||
|
||||
DrhdPage::map(base).expect("failed to map DRHD registers")
|
||||
}
|
||||
}
|
||||
impl Deref for DmarDrhd {
|
||||
type Target = DmarDrhdHeader;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes::<DmarDrhdHeader>(&self.0[..mem::size_of::<DmarDrhdHeader>()])
|
||||
.expect("length is already checked, and alignment 1 (#[repr(packed)] should suffice")
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for DmarDrhd {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DmarDrhd")
|
||||
.field("header", &*self as &DmarDrhd)
|
||||
// TODO: print out device scopes
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// DMAR Reserved Memory Region Reporting
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DmarRmrrHeader {
|
||||
pub kind: u16,
|
||||
pub length: u16,
|
||||
pub _rsv: u16,
|
||||
pub segment: u16,
|
||||
pub base: u64,
|
||||
pub limit: u64,
|
||||
// The device scopes come after.
|
||||
}
|
||||
unsafe impl plain::Plain for DmarRmrrHeader {}
|
||||
|
||||
pub struct DmarRmrr(Box<[u8]>);
|
||||
|
||||
impl DmarRmrr {
|
||||
pub fn try_new(raw: &[u8]) -> Option<Self> {
|
||||
if raw.len() < mem::size_of::<DmarRmrrHeader>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(raw.into()))
|
||||
}
|
||||
}
|
||||
impl Deref for DmarRmrr {
|
||||
type Target = DmarRmrrHeader;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes(&self.0[..mem::size_of::<DmarRmrrHeader>()])
|
||||
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for DmarRmrr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DmarRmrr")
|
||||
.field("header", &*self as &DmarRmrrHeader)
|
||||
// TODO: print out device scopes
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// DMAR Root Port ATS Capability Reporting
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DmarAtsrHeader {
|
||||
kind: u16,
|
||||
length: u16,
|
||||
flags: u8,
|
||||
_rsv: u8,
|
||||
segment: u16,
|
||||
// The device scopes come after.
|
||||
}
|
||||
unsafe impl plain::Plain for DmarAtsrHeader {}
|
||||
|
||||
pub struct DmarAtsr(Box<[u8]>);
|
||||
|
||||
impl DmarAtsr {
|
||||
pub fn try_new(raw: &[u8]) -> Option<Self> {
|
||||
if raw.len() < mem::size_of::<DmarAtsrHeader>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(raw.into()))
|
||||
}
|
||||
}
|
||||
impl Deref for DmarAtsr {
|
||||
type Target = DmarAtsrHeader;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes(&self.0[..mem::size_of::<DmarAtsrHeader>()])
|
||||
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for DmarAtsr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DmarAtsr")
|
||||
.field("header", &*self as &DmarAtsrHeader)
|
||||
// TODO: print out device scopes
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// DMAR Remapping Hardware Static Affinity
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DmarRhsa {
|
||||
pub kind: u16,
|
||||
pub length: u16,
|
||||
|
||||
pub _rsv: u32,
|
||||
pub base: u64,
|
||||
pub domain: u32,
|
||||
}
|
||||
unsafe impl plain::Plain for DmarRhsa {}
|
||||
impl DmarRhsa {
|
||||
pub fn try_new(raw: &[u8]) -> Option<Self> {
|
||||
let bytes = raw.get(..mem::size_of::<DmarRhsa>())?;
|
||||
|
||||
let this = plain::from_bytes(bytes)
|
||||
.expect("length is already checked, and alignment 1 should suffice (#[repr(packed)])");
|
||||
|
||||
Some(*this)
|
||||
}
|
||||
}
|
||||
|
||||
/// DMAR ACPI Name-space Device Declaration
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DmarAnddHeader {
|
||||
pub kind: u16,
|
||||
pub length: u16,
|
||||
|
||||
pub _rsv: [u8; 3],
|
||||
pub acpi_dev: u8,
|
||||
// The device scopes come after.
|
||||
}
|
||||
unsafe impl plain::Plain for DmarAnddHeader {}
|
||||
|
||||
pub struct DmarAndd(Box<[u8]>);
|
||||
|
||||
impl DmarAndd {
|
||||
pub fn try_new(raw: &[u8]) -> Option<Self> {
|
||||
if raw.len() < mem::size_of::<DmarAnddHeader>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(raw.into()))
|
||||
}
|
||||
}
|
||||
impl Deref for DmarAndd {
|
||||
type Target = DmarAnddHeader;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes(&self.0[..mem::size_of::<DmarAnddHeader>()])
|
||||
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for DmarAndd {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DmarAndd")
|
||||
.field("header", &*self as &DmarAnddHeader)
|
||||
// TODO: print out device scopes
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// DMAR ACPI Name-space Device Declaration
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, packed)]
|
||||
pub struct DmarSatcHeader {
|
||||
pub kind: u16,
|
||||
pub length: u16,
|
||||
|
||||
pub flags: u8,
|
||||
pub _rsvd: u8,
|
||||
pub seg_num: u16,
|
||||
// The device scopes come after.
|
||||
}
|
||||
unsafe impl plain::Plain for DmarSatcHeader {}
|
||||
|
||||
pub struct DmarSatc(Box<[u8]>);
|
||||
|
||||
impl DmarSatc {
|
||||
pub fn try_new(raw: &[u8]) -> Option<Self> {
|
||||
if raw.len() < mem::size_of::<DmarSatcHeader>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self(raw.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DmarSatc {
|
||||
type Target = DmarSatcHeader;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
plain::from_bytes(&self.0[..mem::size_of::<DmarSatcHeader>()])
|
||||
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for DmarSatc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DmarSatc")
|
||||
.field("header", &*self as &DmarSatcHeader)
|
||||
// TODO: print out device scopes
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// The list of different "Remapping Structure Types".
|
||||
///
|
||||
/// Refer to section 8.2 in the VTIO spec (as of revision 3.2).
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive)]
|
||||
#[repr(u16)]
|
||||
pub enum EntryType {
|
||||
Drhd = 0,
|
||||
Rmrr = 1,
|
||||
Atsr = 2,
|
||||
Rhsa = 3,
|
||||
Andd = 4,
|
||||
Satc = 5,
|
||||
}
|
||||
|
||||
/// DMAR Entries
|
||||
#[derive(Debug)]
|
||||
pub enum DmarEntry {
|
||||
Drhd(DmarDrhd),
|
||||
Rmrr(DmarRmrr),
|
||||
Atsr(DmarAtsr),
|
||||
Rhsa(DmarRhsa),
|
||||
Andd(DmarAndd),
|
||||
|
||||
// TODO: "SoC Integrated Address Translation Cache Reporting Structure".
|
||||
Satc(DmarSatc),
|
||||
|
||||
TooShort(EntryType),
|
||||
Unknown(u16),
|
||||
}
|
||||
|
||||
struct DmarRawIter<'sdt> {
|
||||
bytes: &'sdt [u8],
|
||||
}
|
||||
|
||||
impl<'sdt> Iterator for DmarRawIter<'sdt> {
|
||||
type Item = (u16, &'sdt [u8]);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let type_bytes = match self.bytes.get(..2) {
|
||||
Some(bytes) => bytes,
|
||||
None => {
|
||||
if !self.bytes.is_empty() {
|
||||
log::warn!("DMAR table ended between two entries.");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let len_bytes = match self.bytes.get(2..4) {
|
||||
Some(bytes) => bytes,
|
||||
None => {
|
||||
log::warn!("DMAR table ended between two entries.");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let remainder = &self.bytes[4..];
|
||||
|
||||
let type_bytes = <[u8; 2]>::try_from(type_bytes)
|
||||
.expect("expected a 2-byte slice to be convertible to [u8; 2]");
|
||||
let len_bytes = <[u8; 2]>::try_from(type_bytes)
|
||||
.expect("expected a 2-byte slice to be convertible to [u8; 2]");
|
||||
|
||||
let ty = u16::from_ne_bytes(type_bytes);
|
||||
let len = u16::from_ne_bytes(len_bytes);
|
||||
|
||||
let len = usize::try_from(len).expect("expected u16 to fit within usize");
|
||||
|
||||
if len > remainder.len() {
|
||||
log::warn!("DMAR remapping structure length was smaller than the remaining length of the table.");
|
||||
return None;
|
||||
}
|
||||
|
||||
let (current, residue) = self.bytes.split_at(len);
|
||||
self.bytes = residue;
|
||||
|
||||
Some((ty, current))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DmarIter<'sdt>(DmarRawIter<'sdt>);
|
||||
|
||||
impl Iterator for DmarIter<'_> {
|
||||
type Item = DmarEntry;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (raw_type, raw) = self.0.next()?;
|
||||
|
||||
// NOTE: If any of these entries look incorrect, we should simply continue the iterator,
|
||||
// and instead print a warning.
|
||||
|
||||
let entry_type = match EntryType::from_u16(raw_type) {
|
||||
Some(ty) => ty,
|
||||
None => {
|
||||
log::warn!(
|
||||
"Encountered invalid entry type {} (length {})",
|
||||
raw_type,
|
||||
raw.len()
|
||||
);
|
||||
return Some(DmarEntry::Unknown(raw_type));
|
||||
}
|
||||
};
|
||||
|
||||
let item_opt = match entry_type {
|
||||
EntryType::Drhd => DmarDrhd::try_new(raw).map(DmarEntry::Drhd),
|
||||
EntryType::Rmrr => DmarRmrr::try_new(raw).map(DmarEntry::Rmrr),
|
||||
EntryType::Atsr => DmarAtsr::try_new(raw).map(DmarEntry::Atsr),
|
||||
EntryType::Rhsa => DmarRhsa::try_new(raw).map(DmarEntry::Rhsa),
|
||||
EntryType::Andd => DmarAndd::try_new(raw).map(DmarEntry::Andd),
|
||||
EntryType::Satc => DmarSatc::try_new(raw).map(DmarEntry::Satc),
|
||||
};
|
||||
let item = item_opt.unwrap_or(DmarEntry::TooShort(entry_type));
|
||||
|
||||
Some(item)
|
||||
}
|
||||
}
|
||||
@@ -1,430 +0,0 @@
|
||||
use acpi::{aml::AmlError, Handle, PciAddress, PhysicalMapping};
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use common::io::{Io, Pio};
|
||||
use num_traits::PrimInt;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::fmt::LowerHex;
|
||||
use std::mem::size_of;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use syscall::PAGE_SIZE;
|
||||
|
||||
const PAGE_MASK: usize = !(PAGE_SIZE - 1);
|
||||
const OFFSET_MASK: usize = PAGE_SIZE - 1;
|
||||
|
||||
struct MappedPage {
|
||||
phys_page: usize,
|
||||
virt_page: usize,
|
||||
}
|
||||
|
||||
impl MappedPage {
|
||||
fn new(phys_page: usize) -> std::io::Result<Self> {
|
||||
let virt_page = unsafe {
|
||||
common::physmap(
|
||||
phys_page,
|
||||
PAGE_SIZE,
|
||||
common::Prot::RW,
|
||||
common::MemoryType::default(),
|
||||
)
|
||||
.map_err(|error| std::io::Error::from_raw_os_error(error.errno()))?
|
||||
} as usize;
|
||||
Ok(Self {
|
||||
phys_page,
|
||||
virt_page,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MappedPage {
|
||||
fn drop(&mut self) {
|
||||
log::trace!("Drop page {:#x}", self.phys_page);
|
||||
if let Err(e) = unsafe { libredox::call::munmap(self.virt_page as *mut (), PAGE_SIZE) } {
|
||||
log::error!("funmap (phys): {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AmlPageCache {
|
||||
page_cache: FxHashMap<usize, MappedPage>,
|
||||
}
|
||||
|
||||
impl AmlPageCache {
|
||||
/// get a virtual address for the given physical page
|
||||
fn get_page(&mut self, phys_target: usize) -> std::io::Result<&MappedPage> {
|
||||
let phys_page = phys_target & PAGE_MASK;
|
||||
if self.page_cache.contains_key(&phys_page) {
|
||||
log::trace!("re-using cached page {:#x}", phys_page);
|
||||
|
||||
Ok(self
|
||||
.page_cache
|
||||
.get(&phys_page)
|
||||
.expect("could not get page after contains=true"))
|
||||
} else {
|
||||
let mapped_page = MappedPage::new(phys_page)?;
|
||||
log::trace!("adding page {:#x} to cache", mapped_page.phys_page);
|
||||
self.page_cache.insert(phys_page, mapped_page);
|
||||
Ok(self
|
||||
.page_cache
|
||||
.get(&phys_page)
|
||||
.expect("can't find page that was just inserted"))
|
||||
}
|
||||
}
|
||||
|
||||
/// The offset into the virtual slice of T that matches the physical target
|
||||
fn sized_index<T>(phys_target: usize) -> usize {
|
||||
assert_eq!(
|
||||
phys_target & !(size_of::<T>() - 1),
|
||||
phys_target,
|
||||
"address {} is not aligned",
|
||||
phys_target
|
||||
);
|
||||
(phys_target & OFFSET_MASK) / size_of::<T>()
|
||||
}
|
||||
/// Read from the given physical address
|
||||
fn read_from_phys<T: PrimInt + LowerHex>(&mut self, phys_target: usize) -> std::io::Result<T> {
|
||||
let mapped_page = self.get_page(phys_target)?;
|
||||
let page_as_slice = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
mapped_page.virt_page as *const T,
|
||||
PAGE_SIZE / size_of::<T>(),
|
||||
)
|
||||
};
|
||||
// for debugging only
|
||||
let _virt_ptr = page_as_slice[Self::sized_index::<T>(phys_target)..].as_ptr() as usize;
|
||||
|
||||
let val = page_as_slice[Self::sized_index::<T>(phys_target)];
|
||||
|
||||
log::trace!(
|
||||
"read {:#x}, virt {:#x}, val {:#x}",
|
||||
phys_target,
|
||||
_virt_ptr,
|
||||
val
|
||||
);
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Write to the given physical address
|
||||
fn write_to_phys<T: PrimInt + LowerHex>(
|
||||
&mut self,
|
||||
phys_target: usize,
|
||||
val: T,
|
||||
) -> std::io::Result<()> {
|
||||
let mapped_page = self.get_page(phys_target)?;
|
||||
let page_as_slice = unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
mapped_page.virt_page as *mut T,
|
||||
PAGE_SIZE / size_of::<T>(),
|
||||
)
|
||||
};
|
||||
// for debugging only
|
||||
let _virt_ptr = page_as_slice[Self::sized_index::<T>(phys_target)..].as_ptr() as usize;
|
||||
|
||||
page_as_slice[Self::sized_index::<T>(phys_target)] = val;
|
||||
|
||||
log::trace!(
|
||||
"write {:#x}, virt {:#x}, val {:#x}",
|
||||
phys_target,
|
||||
_virt_ptr,
|
||||
val
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
log::trace!("Clear page cache");
|
||||
self.page_cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AmlPhysMemHandler {
|
||||
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||
pci_fd: Arc<Option<libredox::Fd>>,
|
||||
}
|
||||
|
||||
/// Read from a physical address.
|
||||
/// Generic parameter must be u8, u16, u32 or u64.
|
||||
impl AmlPhysMemHandler {
|
||||
pub fn new(pci_fd_opt: Option<&libredox::Fd>, page_cache: Arc<Mutex<AmlPageCache>>) -> Self {
|
||||
let pci_fd = if let Some(pci_fd) = pci_fd_opt {
|
||||
Some(libredox::Fd::new(pci_fd.raw()))
|
||||
} else {
|
||||
log::error!("pci_fd is not registered");
|
||||
None
|
||||
};
|
||||
Self {
|
||||
page_cache,
|
||||
pci_fd: Arc::new(pci_fd),
|
||||
}
|
||||
}
|
||||
|
||||
fn pci_call_metadata(kind: u8, addr: PciAddress, off: u16) -> [u64; 2] {
|
||||
// Segment: u16, at 28 bits
|
||||
// Bus: u8, 8 bits, 256 total, at 20 bits
|
||||
// Device: u8, 5 bits, 32 total, at 15 bits
|
||||
// Function: u8, 3 bits, 8 total, at 12 bits
|
||||
// Offset: u16, 12 bits, 4096 total, at 0 bits
|
||||
[
|
||||
kind.into(),
|
||||
(u64::from(addr.segment()) << 28)
|
||||
| (u64::from(addr.bus()) << 20)
|
||||
| (u64::from(addr.device()) << 15)
|
||||
| (u64::from(addr.function()) << 12)
|
||||
| u64::from(off),
|
||||
]
|
||||
}
|
||||
|
||||
fn read_pci(&self, addr: PciAddress, off: u16, value: &mut [u8]) {
|
||||
let metadata = Self::pci_call_metadata(1, addr, off);
|
||||
match &*self.pci_fd {
|
||||
Some(pci_fd) => match pci_fd.call_ro(value, syscall::CallFlags::empty(), &metadata) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
log::error!("read pci {addr}@{off:04X}:{:02X}: {}", value.len(), err);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::error!(
|
||||
"read pci {addr}@{off:04X}:{:02X}: pci access not available",
|
||||
value.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pci(&self, addr: PciAddress, off: u16, value: &[u8]) {
|
||||
let metadata = Self::pci_call_metadata(2, addr, off);
|
||||
match &*self.pci_fd {
|
||||
Some(pci_fd) => match pci_fd.call_wo(value, syscall::CallFlags::empty(), &metadata) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
log::error!("write pci {addr}@{off:04X}={value:02X?}: {}", err);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::error!("write pci {addr}@{off:04X}={value:02X?}: pci access not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl acpi::Handler for AmlPhysMemHandler {
|
||||
unsafe fn map_physical_region<T>(&self, phys: usize, size: usize) -> PhysicalMapping<Self, T> {
|
||||
let phys_page = phys & PAGE_MASK;
|
||||
let offset = phys & OFFSET_MASK;
|
||||
let pages = (offset + size + PAGE_SIZE - 1) / PAGE_SIZE;
|
||||
let map_size = pages * PAGE_SIZE;
|
||||
let virt_page = common::physmap(
|
||||
phys_page,
|
||||
map_size,
|
||||
common::Prot::RW,
|
||||
common::MemoryType::default(),
|
||||
)
|
||||
.expect("failed to map physical region") as usize;
|
||||
PhysicalMapping {
|
||||
physical_start: phys,
|
||||
virtual_start: NonNull::new((virt_page + offset) as *mut T).unwrap(),
|
||||
region_length: size,
|
||||
mapped_length: map_size,
|
||||
handler: self.clone(),
|
||||
}
|
||||
}
|
||||
fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>) {
|
||||
let virt_page = region.virtual_start.addr().get() & PAGE_MASK;
|
||||
unsafe {
|
||||
libredox::call::munmap(virt_page as *mut (), region.mapped_length)
|
||||
.expect("failed to unmap physical region")
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u8(&self, address: usize) -> u8 {
|
||||
log::trace!("read u8 {:X}", address);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if let Ok(value) = page_cache.read_from_phys::<u8>(address) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
log::error!("failed to read u8 {:#x}", address);
|
||||
0
|
||||
}
|
||||
fn read_u16(&self, address: usize) -> u16 {
|
||||
log::trace!("read u16 {:X}", address);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if let Ok(value) = page_cache.read_from_phys::<u16>(address) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
log::error!("failed to read u16 {:#x}", address);
|
||||
0
|
||||
}
|
||||
fn read_u32(&self, address: usize) -> u32 {
|
||||
log::trace!("read u32 {:X}", address);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if let Ok(value) = page_cache.read_from_phys::<u32>(address) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
log::error!("failed to read u32 {:#x}", address);
|
||||
0
|
||||
}
|
||||
fn read_u64(&self, address: usize) -> u64 {
|
||||
log::trace!("read u64 {:X}", address);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if let Ok(value) = page_cache.read_from_phys::<u64>(address) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
log::error!("failed to read u64 {:#x}", address);
|
||||
0
|
||||
}
|
||||
|
||||
fn write_u8(&self, address: usize, value: u8) {
|
||||
log::trace!("write u8 {:X} = {:X}", address, value);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if page_cache.write_to_phys::<u8>(address, value).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::error!("failed to write u8 {:#x}", address);
|
||||
}
|
||||
fn write_u16(&self, address: usize, value: u16) {
|
||||
log::trace!("write u16 {:X} = {:X}", address, value);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if page_cache.write_to_phys::<u16>(address, value).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::error!("failed to write u16 {:#x}", address);
|
||||
}
|
||||
fn write_u32(&self, address: usize, value: u32) {
|
||||
log::trace!("write u32 {:X} = {:X}", address, value);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if page_cache.write_to_phys::<u32>(address, value).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::error!("failed to write u32 {:#x}", address);
|
||||
}
|
||||
fn write_u64(&self, address: usize, value: u64) {
|
||||
log::trace!("write u64 {:X} = {:X}", address, value);
|
||||
if let Ok(mut page_cache) = self.page_cache.lock() {
|
||||
if page_cache.write_to_phys::<u64>(address, value).is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
log::error!("failed to write u64 {:#x}", address);
|
||||
}
|
||||
|
||||
// Pio must be enabled via syscall::iopl
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn read_io_u8(&self, port: u16) -> u8 {
|
||||
Pio::<u8>::new(port).read()
|
||||
}
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn read_io_u16(&self, port: u16) -> u16 {
|
||||
Pio::<u16>::new(port).read()
|
||||
}
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn read_io_u32(&self, port: u16) -> u32 {
|
||||
Pio::<u32>::new(port).read()
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn write_io_u8(&self, port: u16, value: u8) {
|
||||
Pio::<u8>::new(port).write(value)
|
||||
}
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn write_io_u16(&self, port: u16, value: u16) {
|
||||
Pio::<u16>::new(port).write(value)
|
||||
}
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn write_io_u32(&self, port: u16, value: u32) {
|
||||
Pio::<u32>::new(port).write(value)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn read_io_u8(&self, port: u16) -> u8 {
|
||||
log::error!("cannot read u8 from port 0x{port:04X}");
|
||||
0
|
||||
}
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn read_io_u16(&self, port: u16) -> u16 {
|
||||
log::error!("cannot read u16 from port 0x{port:04X}");
|
||||
0
|
||||
}
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn read_io_u32(&self, port: u16) -> u32 {
|
||||
log::error!("cannot read u32 from port 0x{port:04X}");
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn write_io_u8(&self, port: u16, value: u8) {
|
||||
log::error!("cannot write 0x{value:02X} to port 0x{port:04X}");
|
||||
}
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn write_io_u16(&self, port: u16, value: u16) {
|
||||
log::error!("cannot write 0x{value:04X} to port 0x{port:04X}");
|
||||
}
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn write_io_u32(&self, port: u16, value: u32) {
|
||||
log::error!("cannot write 0x{value:08X} to port 0x{port:04X}");
|
||||
}
|
||||
|
||||
fn read_pci_u8(&self, addr: PciAddress, off: u16) -> u8 {
|
||||
let mut value = [0u8];
|
||||
self.read_pci(addr, off, &mut value);
|
||||
value[0]
|
||||
}
|
||||
fn read_pci_u16(&self, addr: PciAddress, off: u16) -> u16 {
|
||||
let mut value = [0u8; 2];
|
||||
self.read_pci(addr, off, &mut value);
|
||||
u16::from_le_bytes(value)
|
||||
}
|
||||
fn read_pci_u32(&self, addr: PciAddress, off: u16) -> u32 {
|
||||
let mut value = [0u8; 4];
|
||||
self.read_pci(addr, off, &mut value);
|
||||
u32::from_le_bytes(value)
|
||||
}
|
||||
fn write_pci_u8(&self, addr: PciAddress, off: u16, value: u8) {
|
||||
self.write_pci(addr, off, &[value]);
|
||||
}
|
||||
fn write_pci_u16(&self, addr: PciAddress, off: u16, value: u16) {
|
||||
self.write_pci(addr, off, &value.to_le_bytes());
|
||||
}
|
||||
fn write_pci_u32(&self, addr: PciAddress, off: u16, value: u32) {
|
||||
self.write_pci(addr, off, &value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn nanos_since_boot(&self) -> u64 {
|
||||
let ts = libredox::call::clock_gettime(libredox::flag::CLOCK_MONOTONIC)
|
||||
.expect("failed to get time");
|
||||
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
|
||||
}
|
||||
|
||||
fn stall(&self, microseconds: u64) {
|
||||
let start = std::time::Instant::now();
|
||||
while start.elapsed().as_micros() < microseconds.into() {
|
||||
std::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
fn sleep(&self, milliseconds: u64) {
|
||||
std::thread::sleep(std::time::Duration::from_millis(milliseconds));
|
||||
}
|
||||
|
||||
fn create_mutex(&self) -> Handle {
|
||||
log::debug!("TODO: Handler::create_mutex");
|
||||
Handle(0)
|
||||
}
|
||||
|
||||
fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> {
|
||||
log::debug!("TODO: Handler::acquire");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&self, mutex: Handle) {
|
||||
log::debug!("TODO: Handler::release");
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use acpi::aml::{
|
||||
op_region::{OpRegion, RegionHandler, RegionSpace},
|
||||
AmlError,
|
||||
};
|
||||
use common::{
|
||||
io::{Io, Pio},
|
||||
timeout::Timeout,
|
||||
};
|
||||
use log::*;
|
||||
|
||||
const EC_DATA: u16 = 0x62;
|
||||
const EC_SC: u16 = 0x66;
|
||||
|
||||
const OBF: u8 = 1 << 0; // output full / data ready for host <> empty
|
||||
const IBF: u8 = 1 << 1; // input full / data ready for ec <> empty
|
||||
const CMD: u8 = 1 << 3; // byte in data reg is command <> data
|
||||
const BURST: u8 = 1 << 4; // burst mode <> normal mode
|
||||
const SCI_EVT: u8 = 1 << 5; // sci event pending <> not
|
||||
const SMI_EVT: u8 = 1 << 6; // smi event pending <> not
|
||||
|
||||
const RD_EC: u8 = 0x80;
|
||||
const WR_EC: u8 = 0x81;
|
||||
const BE_EC: u8 = 0x82;
|
||||
const BD_EC: u8 = 0x83;
|
||||
const QR_EC: u8 = 0x84;
|
||||
|
||||
const BURST_ACK: u8 = 0x90;
|
||||
|
||||
pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10);
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct ScBits(u8);
|
||||
#[allow(dead_code)]
|
||||
impl ScBits {
|
||||
const fn obf(&self) -> bool {
|
||||
(self.0 & OBF) != 0
|
||||
}
|
||||
const fn ibf(&self) -> bool {
|
||||
(self.0 & IBF) != 0
|
||||
}
|
||||
const fn cmd(&self) -> bool {
|
||||
(self.0 & CMD) != 0
|
||||
}
|
||||
const fn burst(&self) -> bool {
|
||||
(self.0 & BURST) != 0
|
||||
}
|
||||
const fn sci_evt(&self) -> bool {
|
||||
(self.0 & SCI_EVT) != 0
|
||||
}
|
||||
const fn smi_evt(&self) -> bool {
|
||||
(self.0 & SMI_EVT) != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Ec {
|
||||
sc: u16,
|
||||
data: u16,
|
||||
|
||||
timeout: Duration,
|
||||
}
|
||||
impl Ec {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sc: EC_SC,
|
||||
data: EC_DATA,
|
||||
timeout: DEFAULT_EC_TIMEOUT,
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn with_address(sc: u16, data: u16, timeout: Duration) -> Self {
|
||||
Self { sc, data, timeout }
|
||||
}
|
||||
#[inline]
|
||||
fn read_reg_sc(&self) -> ScBits {
|
||||
ScBits(Pio::<u8>::new(self.sc).read())
|
||||
}
|
||||
#[inline]
|
||||
fn read_reg_data(&self) -> u8 {
|
||||
Pio::<u8>::new(self.data).read()
|
||||
}
|
||||
#[inline]
|
||||
fn write_reg_sc(&self, value: u8) {
|
||||
Pio::<u8>::new(self.sc).write(value);
|
||||
}
|
||||
#[inline]
|
||||
fn write_reg_data(&self, value: u8) {
|
||||
Pio::<u8>::new(self.data).write(value);
|
||||
}
|
||||
#[inline]
|
||||
fn wait_for_write_ready(&self) -> Option<()> {
|
||||
let timeout = Timeout::new(self.timeout);
|
||||
loop {
|
||||
if !self.read_reg_sc().ibf() {
|
||||
return Some(());
|
||||
}
|
||||
timeout.run().ok()?;
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn wait_for_read_ready(&self) -> Option<()> {
|
||||
let timeout = Timeout::new(self.timeout);
|
||||
loop {
|
||||
if self.read_reg_sc().obf() {
|
||||
return Some(());
|
||||
}
|
||||
timeout.run().ok()?;
|
||||
}
|
||||
}
|
||||
|
||||
//https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html
|
||||
pub fn read(&self, address: u8) -> Option<u8> {
|
||||
trace!("ec read addr: {:x}", address);
|
||||
self.wait_for_write_ready()?;
|
||||
|
||||
self.write_reg_sc(RD_EC);
|
||||
|
||||
self.wait_for_write_ready()?;
|
||||
|
||||
self.write_reg_data(address);
|
||||
|
||||
self.wait_for_read_ready()?;
|
||||
|
||||
let val = self.read_reg_data();
|
||||
trace!("got: {:x}", val);
|
||||
Some(val)
|
||||
}
|
||||
pub fn write(&self, address: u8, value: u8) -> Option<()> {
|
||||
trace!("ec write addr: {:x}, with: {:x}", address, value);
|
||||
self.wait_for_write_ready()?;
|
||||
|
||||
self.write_reg_sc(WR_EC);
|
||||
|
||||
self.wait_for_write_ready()?;
|
||||
|
||||
self.write_reg_data(address);
|
||||
|
||||
self.wait_for_write_ready()?;
|
||||
|
||||
self.write_reg_data(value);
|
||||
trace!("done");
|
||||
Some(())
|
||||
}
|
||||
// disabled if not met
|
||||
// First Access - 400 microseconds
|
||||
// Subsequent Accesses - 50 microseconds each
|
||||
// Total Burst Time - 1 millisecond
|
||||
//Accesses should be responded to within 50 microseconds.
|
||||
#[allow(dead_code)]
|
||||
fn enable_burst(&self) -> bool {
|
||||
trace!("ec burst enable");
|
||||
self.wait_for_write_ready();
|
||||
|
||||
self.write_reg_sc(BE_EC);
|
||||
|
||||
self.wait_for_read_ready();
|
||||
|
||||
let res = self.read_reg_data() == BURST_ACK;
|
||||
trace!("success: {}", res);
|
||||
res
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
fn disable_burst(&self) {
|
||||
trace!("ec burst disable");
|
||||
self.wait_for_write_ready();
|
||||
self.write_reg_sc(BD_EC);
|
||||
trace!("done");
|
||||
}
|
||||
//OSPM driver sends this command when the SCI_EVT flag in the EC_SC register is set.
|
||||
#[allow(dead_code)]
|
||||
fn queue_query(&mut self) -> u8 {
|
||||
trace!("ec query");
|
||||
self.wait_for_write_ready();
|
||||
|
||||
self.write_reg_sc(QR_EC);
|
||||
|
||||
self.wait_for_read_ready();
|
||||
|
||||
let val = self.read_reg_data();
|
||||
trace!("got: {}", val);
|
||||
val
|
||||
}
|
||||
}
|
||||
impl RegionHandler for Ec {
|
||||
fn read_u8(
|
||||
&self,
|
||||
region: &acpi::aml::op_region::OpRegion,
|
||||
offset: usize,
|
||||
) -> Result<u8, acpi::aml::AmlError> {
|
||||
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
||||
self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
|
||||
}
|
||||
fn write_u8(
|
||||
&self,
|
||||
region: &OpRegion,
|
||||
offset: usize,
|
||||
value: u8,
|
||||
) -> Result<(), acpi::aml::AmlError> {
|
||||
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
||||
self.write(offset as u8, value)
|
||||
.ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
|
||||
}
|
||||
fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result<u16, acpi::aml::AmlError> {
|
||||
warn!("Got u16 EC read from AML!");
|
||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
||||
RegionSpace::EmbeddedControl,
|
||||
)) // TODO proper error type
|
||||
}
|
||||
fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result<u32, acpi::aml::AmlError> {
|
||||
warn!("Got u32 EC read from AML!");
|
||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
||||
RegionSpace::EmbeddedControl,
|
||||
)) // TODO proper error type
|
||||
}
|
||||
fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result<u64, acpi::aml::AmlError> {
|
||||
warn!("Got u64 EC read from AML!");
|
||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
||||
RegionSpace::EmbeddedControl,
|
||||
)) // TODO proper error type
|
||||
}
|
||||
fn write_u16(
|
||||
&self,
|
||||
_region: &OpRegion,
|
||||
_offset: usize,
|
||||
_value: u16,
|
||||
) -> Result<(), acpi::aml::AmlError> {
|
||||
warn!("Got u16 EC write from AML!");
|
||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
||||
RegionSpace::EmbeddedControl,
|
||||
)) // TODO proper error type
|
||||
}
|
||||
fn write_u32(
|
||||
&self,
|
||||
_region: &OpRegion,
|
||||
_offset: usize,
|
||||
_value: u32,
|
||||
) -> Result<(), acpi::aml::AmlError> {
|
||||
warn!("Got u32 EC write from AML!");
|
||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
||||
RegionSpace::EmbeddedControl,
|
||||
)) // TODO proper error type
|
||||
}
|
||||
fn write_u64(
|
||||
&self,
|
||||
_region: &OpRegion,
|
||||
_offset: usize,
|
||||
_value: u64,
|
||||
) -> Result<(), acpi::aml::AmlError> {
|
||||
warn!("Got u64 EC write from AML!");
|
||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
||||
RegionSpace::EmbeddedControl,
|
||||
)) // TODO proper error type
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::File;
|
||||
use std::mem;
|
||||
use std::ops::ControlFlow;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::acpi::aml::op_region::{RegionHandler, RegionSpace};
|
||||
use event::{EventFlags, RawEventQueue};
|
||||
use redox_scheme::{scheme::register_sync_scheme, Socket};
|
||||
use scheme_utils::Blocking;
|
||||
|
||||
mod acpi;
|
||||
mod aml_physmem;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod ec;
|
||||
|
||||
mod scheme;
|
||||
|
||||
fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
common::setup_logging(
|
||||
"misc",
|
||||
"acpi",
|
||||
"acpid",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
log::info!("acpid start");
|
||||
|
||||
let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") {
|
||||
Ok(data) => data.into(),
|
||||
Err(err) => {
|
||||
log::error!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if rxsdt_raw_data.is_empty() {
|
||||
log::info!("System doesn't use ACPI");
|
||||
daemon.ready();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) {
|
||||
Ok(sdt) => sdt,
|
||||
Err(err) => {
|
||||
log::error!("acpid: failed to parse [RX]SDT: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut thirty_two_bit;
|
||||
let mut sixty_four_bit;
|
||||
|
||||
let physaddrs_iter = match &sdt.signature {
|
||||
b"RSDT" => {
|
||||
thirty_two_bit = sdt
|
||||
.data()
|
||||
.chunks(mem::size_of::<u32>())
|
||||
// TODO: With const generics, the compiler has some way of doing this for static sizes.
|
||||
.map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
|
||||
.map(|chunk| u32::from_le_bytes(chunk))
|
||||
.map(u64::from);
|
||||
|
||||
&mut thirty_two_bit as &mut dyn Iterator<Item = u64>
|
||||
}
|
||||
b"XSDT" => {
|
||||
sixty_four_bit = sdt
|
||||
.data()
|
||||
.chunks(mem::size_of::<u64>())
|
||||
.map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
|
||||
.map(|chunk| u64::from_le_bytes(chunk));
|
||||
|
||||
&mut sixty_four_bit as &mut dyn Iterator<Item = u64>
|
||||
}
|
||||
_ => {
|
||||
log::error!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler + 'static>)> = vec![
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
(RegionSpace::EmbeddedControl, Box::new(ec::Ec::new())),
|
||||
];
|
||||
let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
|
||||
|
||||
// TODO: I/O permission bitmap?
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
if let Err(err) = common::acquire_port_io_rights() {
|
||||
log::error!("acpid: failed to set I/O privilege level to Ring 3: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
log::error!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut event_queue = match RawEventQueue::new() {
|
||||
Ok(q) => q,
|
||||
Err(err) => {
|
||||
log::error!("acpid: failed to create event queue: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let socket = match Socket::nonblock() {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
log::error!("acpid: failed to create scheme socket: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
|
||||
let mut handler = Blocking::new(&socket, 16);
|
||||
|
||||
if let Err(err) = event_queue
|
||||
.subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
|
||||
{
|
||||
log::error!("acpid: failed to register shutdown pipe for event queue: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Err(err) = event_queue
|
||||
.subscribe(socket.inner().raw(), 1, EventFlags::READ)
|
||||
{
|
||||
log::error!("acpid: failed to register scheme socket for event queue: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) {
|
||||
log::error!("acpid: failed to register acpi scheme to namespace: {:?}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
daemon.ready();
|
||||
|
||||
if let Err(err) = libredox::call::setrens(0, 0) {
|
||||
log::error!("acpid: failed to enter null namespace: {}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut mounted = true;
|
||||
while mounted {
|
||||
let event = match event_queue.next().transpose() {
|
||||
Ok(Some(ev)) => ev,
|
||||
Ok(None) => break,
|
||||
Err(err) => {
|
||||
log::error!("acpid: failed to read event file: {:?}", err);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if event.fd == socket.inner().raw() {
|
||||
loop {
|
||||
match handler.process_requests_nonblocking(&mut scheme) {
|
||||
Ok(flow) => match flow {
|
||||
ControlFlow::Continue(()) => {}
|
||||
ControlFlow::Break(()) => break,
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("acpid: failed to process requests: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if event.fd == shutdown_pipe.as_raw_fd() as usize {
|
||||
log::info!("Received shutdown request from kernel.");
|
||||
mounted = false;
|
||||
} else {
|
||||
log::debug!("Received request to unknown fd: {}", event.fd);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
drop(shutdown_pipe);
|
||||
drop(event_queue);
|
||||
|
||||
acpi_context.set_global_s_state(5);
|
||||
|
||||
unreachable!("System should have shut down before this is entered");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
common::init();
|
||||
daemon::Daemon::new(daemon);
|
||||
}
|
||||
@@ -1,485 +0,0 @@
|
||||
use acpi::aml::namespace::AmlName;
|
||||
use amlserde::aml_serde_name::to_aml_format;
|
||||
use amlserde::AmlSerdeValue;
|
||||
use core::str;
|
||||
use libredox::Fd;
|
||||
use parking_lot::RwLockReadGuard;
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket};
|
||||
use ron::de::SpannedError;
|
||||
use scheme_utils::HandleMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::str::FromStr;
|
||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::FobtainFdFlags;
|
||||
|
||||
use syscall::data::Stat;
|
||||
use syscall::error::{Error, Result};
|
||||
use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
|
||||
use syscall::flag::{MODE_DIR, MODE_FILE};
|
||||
use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK};
|
||||
use syscall::{EOVERFLOW, EPERM};
|
||||
|
||||
use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
|
||||
|
||||
pub struct AcpiScheme<'acpi, 'sock> {
|
||||
ctx: &'acpi AcpiContext,
|
||||
handles: HandleMap<Handle<'acpi>>,
|
||||
pci_fd: Option<Fd>,
|
||||
socket: &'sock Socket,
|
||||
}
|
||||
|
||||
struct Handle<'a> {
|
||||
kind: HandleKind<'a>,
|
||||
stat: bool,
|
||||
allowed_to_eval: bool,
|
||||
}
|
||||
enum HandleKind<'a> {
|
||||
TopLevel,
|
||||
Tables,
|
||||
Table(SdtSignature),
|
||||
Symbols(RwLockReadGuard<'a, AmlSymbols>),
|
||||
Symbol { name: String, description: String },
|
||||
SchemeRoot,
|
||||
RegisterPci,
|
||||
}
|
||||
|
||||
impl HandleKind<'_> {
|
||||
fn is_dir(&self) -> bool {
|
||||
match self {
|
||||
Self::TopLevel => true,
|
||||
Self::Tables => true,
|
||||
Self::Table(_) => false,
|
||||
Self::Symbols(_) => true,
|
||||
Self::Symbol { .. } => false,
|
||||
Self::SchemeRoot => false,
|
||||
Self::RegisterPci => false,
|
||||
}
|
||||
}
|
||||
fn len(&self, acpi_ctx: &AcpiContext) -> Result<usize> {
|
||||
Ok(match self {
|
||||
// Files
|
||||
Self::Table(signature) => acpi_ctx
|
||||
.sdt_from_signature(signature)
|
||||
.ok_or(Error::new(EBADFD))?
|
||||
.length(),
|
||||
Self::Symbol { description, .. } => description.len(),
|
||||
// Directories
|
||||
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
|
||||
Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
|
||||
pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
handles: HandleMap::new(),
|
||||
pci_fd: None,
|
||||
socket,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hex_digit(hex: u8) -> Option<u8> {
|
||||
let hex = hex.to_ascii_lowercase();
|
||||
|
||||
if hex >= b'a' && hex <= b'f' {
|
||||
Some(hex - b'a' + 10)
|
||||
} else if hex >= b'0' && hex <= b'9' {
|
||||
Some(hex - b'0')
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hex_2digit(hex: &[u8]) -> Option<u8> {
|
||||
parse_hex_digit(hex[0])
|
||||
.and_then(|most_significant| Some((most_significant << 4) | parse_hex_digit(hex[1])?))
|
||||
}
|
||||
|
||||
fn parse_oem_id(hex: [u8; 12]) -> Option<[u8; 6]> {
|
||||
Some([
|
||||
parse_hex_2digit(&hex[0..2])?,
|
||||
parse_hex_2digit(&hex[2..4])?,
|
||||
parse_hex_2digit(&hex[4..6])?,
|
||||
parse_hex_2digit(&hex[6..8])?,
|
||||
parse_hex_2digit(&hex[8..10])?,
|
||||
parse_hex_2digit(&hex[10..12])?,
|
||||
])
|
||||
}
|
||||
fn parse_oem_table_id(hex: [u8; 16]) -> Option<[u8; 8]> {
|
||||
Some([
|
||||
parse_hex_2digit(&hex[0..2])?,
|
||||
parse_hex_2digit(&hex[2..4])?,
|
||||
parse_hex_2digit(&hex[4..6])?,
|
||||
parse_hex_2digit(&hex[6..8])?,
|
||||
parse_hex_2digit(&hex[8..10])?,
|
||||
parse_hex_2digit(&hex[10..12])?,
|
||||
parse_hex_2digit(&hex[12..14])?,
|
||||
parse_hex_2digit(&hex[14..16])?,
|
||||
])
|
||||
}
|
||||
|
||||
fn parse_table(table: &[u8]) -> Option<SdtSignature> {
|
||||
let signature_part = table.get(..4)?;
|
||||
let first_hyphen = table.get(4)?;
|
||||
let oem_id_part = table.get(5..17)?;
|
||||
let second_hyphen = table.get(17)?;
|
||||
let oem_table_part = table.get(18..34)?;
|
||||
|
||||
if *first_hyphen != b'-' {
|
||||
return None;
|
||||
}
|
||||
if *second_hyphen != b'-' {
|
||||
return None;
|
||||
}
|
||||
|
||||
if table.len() > 34 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SdtSignature {
|
||||
signature: <[u8; 4]>::try_from(signature_part)
|
||||
.expect("expected 4-byte slice to be convertible into [u8; 4]"),
|
||||
oem_id: {
|
||||
let hex = <[u8; 12]>::try_from(oem_id_part)
|
||||
.expect("expected 12-byte slice to be convertible into [u8; 12]");
|
||||
parse_oem_id(hex)?
|
||||
},
|
||||
oem_table_id: {
|
||||
let hex = <[u8; 16]>::try_from(oem_table_part)
|
||||
.expect("expected 16-byte slice to be convertible into [u8; 16]");
|
||||
parse_oem_table_id(hex)?
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.insert(Handle {
|
||||
stat: false,
|
||||
kind: HandleKind::SchemeRoot,
|
||||
allowed_to_eval: false,
|
||||
}))
|
||||
}
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
let handle = self.handles.get(dirfd)?;
|
||||
|
||||
let path = path.trim_start_matches('/');
|
||||
|
||||
let flag_stat = flags & O_STAT == O_STAT;
|
||||
let flag_dir = flags & O_DIRECTORY == O_DIRECTORY;
|
||||
|
||||
let kind = match handle.kind {
|
||||
HandleKind::SchemeRoot => {
|
||||
// TODO: arrayvec
|
||||
let components = {
|
||||
let mut v = arrayvec::ArrayVec::<&str, 3>::new();
|
||||
let it = path.split('/');
|
||||
for component in it.take(3) {
|
||||
v.push(component);
|
||||
}
|
||||
|
||||
v
|
||||
};
|
||||
|
||||
match &*components {
|
||||
[""] => HandleKind::TopLevel,
|
||||
["register_pci"] => HandleKind::RegisterPci,
|
||||
["tables"] => HandleKind::Tables,
|
||||
|
||||
["tables", table] => {
|
||||
let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
|
||||
HandleKind::Table(signature)
|
||||
}
|
||||
|
||||
["symbols"] => {
|
||||
if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
|
||||
HandleKind::Symbols(aml_symbols)
|
||||
} else {
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
}
|
||||
|
||||
["symbols", symbol] => {
|
||||
if let Some(description) = self.ctx.aml_lookup(symbol) {
|
||||
HandleKind::Symbol {
|
||||
name: (*symbol).to_owned(),
|
||||
description,
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
}
|
||||
|
||||
_ => return Err(Error::new(ENOENT)),
|
||||
}
|
||||
}
|
||||
HandleKind::Symbols(ref aml_symbols) => {
|
||||
if let Some(description) = aml_symbols.lookup(path) {
|
||||
HandleKind::Symbol {
|
||||
name: (*path).to_owned(),
|
||||
description,
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::new(EACCES)),
|
||||
};
|
||||
|
||||
if kind.is_dir() && !flag_dir && !flag_stat {
|
||||
return Err(Error::new(EISDIR));
|
||||
} else if !kind.is_dir() && flag_dir && !flag_stat {
|
||||
return Err(Error::new(ENOTDIR));
|
||||
}
|
||||
|
||||
let allowed_to_eval = if flags & O_ACCMODE == O_RDONLY || flag_stat {
|
||||
false
|
||||
} else if ctx.uid == 0 {
|
||||
true
|
||||
} else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
|
||||
if flags & O_SYMLINK == O_SYMLINK && !flag_stat {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let fd = self.handles.insert(Handle {
|
||||
stat: flag_stat,
|
||||
kind,
|
||||
allowed_to_eval,
|
||||
});
|
||||
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: fd,
|
||||
flags: NewFdFlags::POSITIONED,
|
||||
})
|
||||
}
|
||||
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
||||
let handle = self.handles.get(id)?;
|
||||
|
||||
stat.st_size = handle
|
||||
.kind
|
||||
.len(self.ctx)?
|
||||
.try_into()
|
||||
.unwrap_or(u64::max_value());
|
||||
|
||||
if handle.kind.is_dir() {
|
||||
stat.st_mode = MODE_DIR;
|
||||
} else {
|
||||
stat.st_mode = MODE_FILE;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
offset: u64,
|
||||
_fcntl: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?;
|
||||
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
|
||||
if handle.stat {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
|
||||
let src_buf = match &handle.kind {
|
||||
HandleKind::Table(ref signature) => self
|
||||
.ctx
|
||||
.sdt_from_signature(signature)
|
||||
.ok_or(Error::new(EBADFD))?
|
||||
.as_slice(),
|
||||
HandleKind::Symbol { description, .. } => description.as_bytes(),
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
};
|
||||
|
||||
let offset = std::cmp::min(src_buf.len(), offset);
|
||||
let src_buf = &src_buf[offset..];
|
||||
|
||||
let to_copy = std::cmp::min(src_buf.len(), buf.len());
|
||||
|
||||
buf[..to_copy].copy_from_slice(&src_buf[..to_copy]);
|
||||
|
||||
Ok(to_copy)
|
||||
}
|
||||
|
||||
fn getdents<'buf>(
|
||||
&mut self,
|
||||
id: usize,
|
||||
mut buf: DirentBuf<&'buf mut [u8]>,
|
||||
opaque_offset: u64,
|
||||
) -> Result<DirentBuf<&'buf mut [u8]>> {
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
|
||||
match &handle.kind {
|
||||
HandleKind::TopLevel => {
|
||||
const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
|
||||
|
||||
for (idx, name) in TOPLEVEL_ENTRIES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(opaque_offset as usize)
|
||||
{
|
||||
buf.entry(DirEntry {
|
||||
inode: 0,
|
||||
next_opaque_id: idx as u64 + 1,
|
||||
name,
|
||||
kind: DirentKind::Directory,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
HandleKind::Symbols(aml_symbols) => {
|
||||
for (idx, (symbol_name, _value)) in aml_symbols
|
||||
.symbols_cache()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(opaque_offset as usize)
|
||||
{
|
||||
buf.entry(DirEntry {
|
||||
inode: 0,
|
||||
next_opaque_id: idx as u64 + 1,
|
||||
name: symbol_name.as_str(),
|
||||
kind: DirentKind::Regular,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
HandleKind::Tables => {
|
||||
for (idx, table) in self
|
||||
.ctx
|
||||
.tables()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(opaque_offset as usize)
|
||||
{
|
||||
let utf8_or_eio = |bytes| str::from_utf8(bytes).map_err(|_| Error::new(EIO));
|
||||
|
||||
let mut name = String::new();
|
||||
name.push_str(utf8_or_eio(&table.signature[..])?);
|
||||
name.push('-');
|
||||
for byte in table.oem_id.iter() {
|
||||
std::fmt::write(&mut name, format_args!("{:>02X}", byte)).unwrap();
|
||||
}
|
||||
name.push('-');
|
||||
for byte in table.oem_table_id.iter() {
|
||||
std::fmt::write(&mut name, format_args!("{:>02X}", byte)).unwrap();
|
||||
}
|
||||
|
||||
buf.entry(DirEntry {
|
||||
inode: 0,
|
||||
next_opaque_id: idx as u64 + 1,
|
||||
name: &name,
|
||||
kind: DirentKind::Regular,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::new(EIO)),
|
||||
}
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
id: usize,
|
||||
payload: &mut [u8],
|
||||
_metadata: &[u64],
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
if !handle.allowed_to_eval {
|
||||
return Err(Error::new(EPERM));
|
||||
}
|
||||
|
||||
let Ok(args): Result<Vec<AmlSerdeValue>, SpannedError> = ron::de::from_bytes(payload)
|
||||
else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
|
||||
let HandleKind::Symbol { name, .. } = &handle.kind else {
|
||||
return Err(Error::new(EBADF));
|
||||
};
|
||||
|
||||
let Ok(aml_name) = AmlName::from_str(&to_aml_format(name)) else {
|
||||
log::error!("Failed to convert symbol name: \"{name}\" to aml name!");
|
||||
return Err(Error::new(EBADF));
|
||||
};
|
||||
|
||||
let Ok(result) = self.ctx.aml_eval(aml_name, args) else {
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
|
||||
let Ok(serialized_result) = ron::ser::to_string(&result) else {
|
||||
log::error!("Failed to serialize aml result!");
|
||||
return Err(Error::new(EINVAL));
|
||||
};
|
||||
|
||||
let byte_result = serialized_result.as_bytes();
|
||||
let result_len = byte_result.len();
|
||||
|
||||
if result_len > payload.len() {
|
||||
return Err(Error::new(EOVERFLOW));
|
||||
}
|
||||
|
||||
payload[..result_len].copy_from_slice(byte_result);
|
||||
|
||||
Ok(result_len)
|
||||
}
|
||||
|
||||
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
|
||||
let id = sendfd_request.id();
|
||||
let num_fds = sendfd_request.num_fds();
|
||||
|
||||
let handle = self.handles.get(id)?;
|
||||
if !matches!(handle.kind, HandleKind::RegisterPci) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
if num_fds == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if num_fds > 1 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let mut new_fd = usize::MAX;
|
||||
if let Err(e) = sendfd_request.obtain_fd(
|
||||
&self.socket,
|
||||
FobtainFdFlags::UPPER_TBL,
|
||||
std::slice::from_mut(&mut new_fd),
|
||||
) {
|
||||
return Err(e);
|
||||
}
|
||||
let new_fd = libredox::Fd::new(new_fd);
|
||||
|
||||
if self.pci_fd.is_some() {
|
||||
return Err(Error::new(EINVAL));
|
||||
} else {
|
||||
self.pci_fd = Some(new_fd);
|
||||
}
|
||||
|
||||
Ok(num_fds)
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "amlserde"
|
||||
description = "Library for serializing AML symbols"
|
||||
version = "0.0.1"
|
||||
authors = ["Ron Williams"]
|
||||
repository = "https://gitlab.redox-os.org/redox-os/drivers"
|
||||
categories = ["hardware-support"]
|
||||
license = "MIT/Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
acpi.workspace = true
|
||||
serde.workspace = true
|
||||
toml.workspace = true
|
||||
@@ -1,484 +0,0 @@
|
||||
use acpi::{
|
||||
aml::{
|
||||
namespace::AmlName,
|
||||
object::{
|
||||
FieldAccessType, FieldFlags, FieldUnit, FieldUnitKind, FieldUpdateRule, MethodFlags,
|
||||
Object, ReferenceKind, WrappedObject,
|
||||
},
|
||||
op_region::{OpRegion, RegionSpace},
|
||||
Interpreter,
|
||||
},
|
||||
Handle, Handler,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
ops::{Deref, Shl},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AmlSerde {
|
||||
pub name: String,
|
||||
pub value: AmlSerdeValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AmlSerdeValue {
|
||||
Uninitialized,
|
||||
Integer(u64),
|
||||
String(String),
|
||||
OpRegion {
|
||||
region: AmlSerdeRegionSpace,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
parent_device: String,
|
||||
},
|
||||
Field {
|
||||
kind: AmlSerdeFieldKind,
|
||||
flags: AmlSerdeFieldFlags,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
},
|
||||
Device,
|
||||
Event(u64),
|
||||
Method {
|
||||
arg_count: usize,
|
||||
serialize: bool,
|
||||
sync_level: u8,
|
||||
},
|
||||
Buffer(Vec<u8>),
|
||||
BufferField {
|
||||
offset: u64,
|
||||
length: u64,
|
||||
data: Box<AmlSerdeValue>,
|
||||
},
|
||||
Processor {
|
||||
id: u8,
|
||||
pblk_address: u32,
|
||||
pblk_len: u8,
|
||||
},
|
||||
Mutex {
|
||||
mutex: u32,
|
||||
sync_level: u8,
|
||||
},
|
||||
Reference {
|
||||
kind: AmlSerdeReferenceKind,
|
||||
inner: Box<AmlSerdeValue>,
|
||||
},
|
||||
Package {
|
||||
contents: Vec<AmlSerdeValue>,
|
||||
},
|
||||
PowerResource {
|
||||
system_level: u8,
|
||||
resource_order: u16,
|
||||
},
|
||||
RawDataBuffer,
|
||||
ThermalZone,
|
||||
Debug,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AmlSerdeRegionSpace {
|
||||
SystemMemory,
|
||||
SystemIo,
|
||||
PciConfig,
|
||||
EmbeddedControl,
|
||||
SMBus,
|
||||
SystemCmos,
|
||||
PciBarTarget,
|
||||
IPMI,
|
||||
GeneralPurposeIo,
|
||||
GenericSerialBus,
|
||||
Pcc,
|
||||
OemDefined(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AmlSerdeFieldKind {
|
||||
Normal {
|
||||
region: Box<AmlSerdeValue>,
|
||||
},
|
||||
Bank {
|
||||
region: Box<AmlSerdeValue>,
|
||||
bank: Box<AmlSerdeValue>,
|
||||
bank_value: u64,
|
||||
},
|
||||
Index {
|
||||
index: Box<AmlSerdeValue>,
|
||||
data: Box<AmlSerdeValue>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AmlSerdeFieldFlags {
|
||||
pub access_type: AmlSerdeFieldAccessType,
|
||||
pub lock_rule: bool, // bit 4
|
||||
pub update_rule: AmlSerdeFieldUpdateRule,
|
||||
}
|
||||
impl Into<u8> for AmlSerdeFieldFlags {
|
||||
fn into(self) -> u8 {
|
||||
// bits 0..4
|
||||
(self.access_type as u8) +
|
||||
// bit 4
|
||||
(self.lock_rule as u8).shl(4) +
|
||||
// bits 5..7
|
||||
(self.update_rule as u8).shl(5)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum AmlSerdeFieldAccessType {
|
||||
Any = 0,
|
||||
Byte = 1,
|
||||
Word = 2,
|
||||
DWord = 3,
|
||||
QWord = 4,
|
||||
Buffer = 5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum AmlSerdeFieldUpdateRule {
|
||||
Preserve = 0,
|
||||
WriteAsOnes = 1,
|
||||
WriteAsZeros = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AmlSerdeReferenceKind {
|
||||
RefOf,
|
||||
Local,
|
||||
Arg,
|
||||
Index,
|
||||
Named,
|
||||
Unresolved,
|
||||
}
|
||||
|
||||
impl AmlSerde {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
name: "name".to_owned(),
|
||||
value: AmlSerdeValue::String(String::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_aml<H: Handler>(aml_context: &Interpreter<H>, aml_name: &AmlName) -> Option<Self> {
|
||||
//TODO: why does namespace.get not take a reference to aml_name
|
||||
let aml_value = if let Ok(aml_value) = aml_context.namespace.lock().get(aml_name.clone()) {
|
||||
aml_value
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let value = if let Some(value) = AmlSerdeValue::from_aml_value(aml_value.deref()) {
|
||||
value
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(AmlSerde {
|
||||
name: aml_name.to_string(),
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AmlSerdeValue {
|
||||
pub fn default() -> Self {
|
||||
AmlSerdeValue::String("".to_owned())
|
||||
}
|
||||
|
||||
pub fn from_aml_value(aml_value: &Object) -> Option<Self> {
|
||||
Some(match aml_value {
|
||||
Object::Uninitialized => AmlSerdeValue::Uninitialized,
|
||||
Object::Integer(n) => AmlSerdeValue::Integer(n.to_owned()),
|
||||
Object::String(s) => AmlSerdeValue::String(s.to_owned()),
|
||||
Object::OpRegion(region) => AmlSerdeValue::OpRegion {
|
||||
region: match region.space {
|
||||
RegionSpace::SystemMemory => AmlSerdeRegionSpace::SystemMemory,
|
||||
RegionSpace::SystemIO => AmlSerdeRegionSpace::SystemIo,
|
||||
RegionSpace::PciConfig => AmlSerdeRegionSpace::PciConfig,
|
||||
RegionSpace::EmbeddedControl => AmlSerdeRegionSpace::EmbeddedControl,
|
||||
RegionSpace::SmBus => AmlSerdeRegionSpace::SMBus,
|
||||
RegionSpace::SystemCmos => AmlSerdeRegionSpace::SystemCmos,
|
||||
RegionSpace::PciBarTarget => AmlSerdeRegionSpace::PciBarTarget,
|
||||
RegionSpace::Ipmi => AmlSerdeRegionSpace::IPMI,
|
||||
RegionSpace::GeneralPurposeIo => AmlSerdeRegionSpace::GeneralPurposeIo,
|
||||
RegionSpace::GenericSerialBus => AmlSerdeRegionSpace::GenericSerialBus,
|
||||
RegionSpace::Pcc => AmlSerdeRegionSpace::Pcc,
|
||||
RegionSpace::Oem(n) => AmlSerdeRegionSpace::OemDefined(n.to_owned()),
|
||||
},
|
||||
offset: region.base,
|
||||
length: region.length,
|
||||
parent_device: region.parent_device_path.to_string(),
|
||||
},
|
||||
Object::FieldUnit(field) => AmlSerdeValue::Field {
|
||||
kind: match &field.kind {
|
||||
FieldUnitKind::Normal { region } => AmlSerdeFieldKind::Normal {
|
||||
region: AmlSerdeValue::from_aml_value(region.deref()).map(Box::new)?,
|
||||
},
|
||||
FieldUnitKind::Bank {
|
||||
region,
|
||||
bank,
|
||||
bank_value,
|
||||
} => AmlSerdeFieldKind::Bank {
|
||||
region: AmlSerdeValue::from_aml_value(region.deref()).map(Box::new)?,
|
||||
bank: AmlSerdeValue::from_aml_value(bank.deref()).map(Box::new)?,
|
||||
bank_value: bank_value.to_owned(),
|
||||
},
|
||||
FieldUnitKind::Index { index, data } => AmlSerdeFieldKind::Index {
|
||||
index: AmlSerdeValue::from_aml_value(index.deref()).map(Box::new)?,
|
||||
data: AmlSerdeValue::from_aml_value(data.deref()).map(Box::new)?,
|
||||
},
|
||||
},
|
||||
flags: AmlSerdeFieldFlags {
|
||||
access_type: match field.flags.access_type() {
|
||||
Ok(FieldAccessType::Any) => AmlSerdeFieldAccessType::Any,
|
||||
Ok(FieldAccessType::Byte) => AmlSerdeFieldAccessType::Byte,
|
||||
Ok(FieldAccessType::Word) => AmlSerdeFieldAccessType::Word,
|
||||
Ok(FieldAccessType::DWord) => AmlSerdeFieldAccessType::DWord,
|
||||
Ok(FieldAccessType::QWord) => AmlSerdeFieldAccessType::QWord,
|
||||
Ok(FieldAccessType::Buffer) => AmlSerdeFieldAccessType::Buffer,
|
||||
_ => return None,
|
||||
},
|
||||
lock_rule: field.flags.lock_rule(),
|
||||
update_rule: match field.flags.update_rule() {
|
||||
FieldUpdateRule::Preserve => AmlSerdeFieldUpdateRule::Preserve,
|
||||
FieldUpdateRule::WriteAsOnes => AmlSerdeFieldUpdateRule::WriteAsOnes,
|
||||
FieldUpdateRule::WriteAsZeros => AmlSerdeFieldUpdateRule::WriteAsZeros,
|
||||
},
|
||||
},
|
||||
offset: field.bit_index as u64,
|
||||
length: field.bit_length as u64,
|
||||
},
|
||||
Object::Device => AmlSerdeValue::Device,
|
||||
Object::Event(event) => AmlSerdeValue::Event(event.load(Ordering::Relaxed)),
|
||||
Object::Method { flags, code: _ } => AmlSerdeValue::Method {
|
||||
arg_count: flags.arg_count(),
|
||||
serialize: flags.serialize(),
|
||||
sync_level: flags.sync_level(),
|
||||
},
|
||||
//TODO: distinguish from Method?
|
||||
Object::NativeMethod { f: _, flags } => AmlSerdeValue::Method {
|
||||
arg_count: flags.arg_count(),
|
||||
serialize: flags.serialize(),
|
||||
sync_level: flags.sync_level(),
|
||||
},
|
||||
Object::Buffer(buffer_data) => AmlSerdeValue::Buffer(buffer_data.to_owned()),
|
||||
Object::BufferField {
|
||||
buffer,
|
||||
offset,
|
||||
length,
|
||||
} => AmlSerdeValue::BufferField {
|
||||
offset: offset.to_owned() as u64,
|
||||
length: length.to_owned() as u64,
|
||||
data: AmlSerdeValue::from_aml_value(buffer.deref()).map(Box::new)?,
|
||||
},
|
||||
Object::Processor {
|
||||
proc_id,
|
||||
pblk_address,
|
||||
pblk_length,
|
||||
} => AmlSerdeValue::Processor {
|
||||
id: proc_id.to_owned(),
|
||||
pblk_address: pblk_address.to_owned(),
|
||||
pblk_len: pblk_length.to_owned(),
|
||||
},
|
||||
Object::Mutex { mutex, sync_level } => AmlSerdeValue::Mutex {
|
||||
mutex: mutex.0,
|
||||
sync_level: sync_level.to_owned(),
|
||||
},
|
||||
Object::Reference { kind, inner } => AmlSerdeValue::Reference {
|
||||
kind: match kind {
|
||||
ReferenceKind::RefOf => AmlSerdeReferenceKind::RefOf,
|
||||
ReferenceKind::Local => AmlSerdeReferenceKind::Local,
|
||||
ReferenceKind::Arg => AmlSerdeReferenceKind::Arg,
|
||||
ReferenceKind::Index => AmlSerdeReferenceKind::Index,
|
||||
ReferenceKind::Named => AmlSerdeReferenceKind::Named,
|
||||
ReferenceKind::Unresolved => AmlSerdeReferenceKind::Unresolved,
|
||||
},
|
||||
inner: AmlSerdeValue::from_aml_value(inner.deref()).map(Box::new)?,
|
||||
},
|
||||
Object::Package(aml_contents) => AmlSerdeValue::Package {
|
||||
contents: aml_contents
|
||||
.iter()
|
||||
.filter_map(|item| AmlSerdeValue::from_aml_value(item))
|
||||
.collect(),
|
||||
},
|
||||
Object::PowerResource {
|
||||
system_level,
|
||||
resource_order,
|
||||
} => AmlSerdeValue::PowerResource {
|
||||
system_level: system_level.to_owned(),
|
||||
resource_order: resource_order.to_owned(),
|
||||
},
|
||||
Object::RawDataBuffer => AmlSerdeValue::RawDataBuffer,
|
||||
Object::ThermalZone => AmlSerdeValue::ThermalZone,
|
||||
Object::Debug => AmlSerdeValue::Debug,
|
||||
})
|
||||
}
|
||||
pub fn to_aml_object(self) -> Option<Object> {
|
||||
Some(match self {
|
||||
AmlSerdeValue::Uninitialized => Object::Uninitialized,
|
||||
AmlSerdeValue::Integer(n) => Object::Integer(n),
|
||||
AmlSerdeValue::String(s) => Object::String(s),
|
||||
AmlSerdeValue::OpRegion {
|
||||
region,
|
||||
offset,
|
||||
length,
|
||||
parent_device,
|
||||
} => Object::OpRegion(OpRegion {
|
||||
space: match region {
|
||||
AmlSerdeRegionSpace::PciConfig => RegionSpace::PciConfig,
|
||||
AmlSerdeRegionSpace::EmbeddedControl => RegionSpace::EmbeddedControl,
|
||||
AmlSerdeRegionSpace::SMBus => RegionSpace::SmBus,
|
||||
AmlSerdeRegionSpace::SystemCmos => RegionSpace::SystemCmos,
|
||||
AmlSerdeRegionSpace::PciBarTarget => RegionSpace::PciBarTarget,
|
||||
AmlSerdeRegionSpace::IPMI => RegionSpace::Ipmi,
|
||||
AmlSerdeRegionSpace::GeneralPurposeIo => RegionSpace::GeneralPurposeIo,
|
||||
AmlSerdeRegionSpace::GenericSerialBus => RegionSpace::GenericSerialBus,
|
||||
AmlSerdeRegionSpace::SystemMemory => RegionSpace::SystemMemory,
|
||||
AmlSerdeRegionSpace::SystemIo => RegionSpace::SystemIO,
|
||||
AmlSerdeRegionSpace::Pcc => RegionSpace::Pcc,
|
||||
AmlSerdeRegionSpace::OemDefined(n) => RegionSpace::Oem(n),
|
||||
},
|
||||
base: offset,
|
||||
length,
|
||||
//
|
||||
parent_device_path: AmlName::from_str(&parent_device).ok()?, // TODO: Error value hidden
|
||||
}),
|
||||
AmlSerdeValue::Field {
|
||||
kind,
|
||||
flags,
|
||||
offset,
|
||||
length,
|
||||
} => Object::FieldUnit(FieldUnit {
|
||||
kind: match kind {
|
||||
AmlSerdeFieldKind::Normal { region } => FieldUnitKind::Normal {
|
||||
region: region.to_aml_object()?.wrap(),
|
||||
},
|
||||
AmlSerdeFieldKind::Bank {
|
||||
region,
|
||||
bank,
|
||||
bank_value,
|
||||
} => FieldUnitKind::Bank {
|
||||
region: region.to_aml_object()?.wrap(),
|
||||
bank: bank.to_aml_object()?.wrap(),
|
||||
bank_value: bank_value.to_owned(),
|
||||
},
|
||||
AmlSerdeFieldKind::Index { index, data } => FieldUnitKind::Index {
|
||||
index: index.to_aml_object()?.wrap(),
|
||||
data: data.to_aml_object()?.wrap(),
|
||||
},
|
||||
},
|
||||
flags: FieldFlags(flags.into()),
|
||||
bit_index: offset as usize,
|
||||
bit_length: length as usize,
|
||||
}),
|
||||
AmlSerdeValue::Device => Object::Device,
|
||||
AmlSerdeValue::Event(event) => Object::Event(Arc::new(AtomicU64::new(event))),
|
||||
AmlSerdeValue::Method {
|
||||
arg_count,
|
||||
serialize,
|
||||
sync_level,
|
||||
} => Object::Method {
|
||||
code: (return None), //TODO figure out what to do here
|
||||
//TODO check specs to see if all bit patterns are allowed
|
||||
flags: MethodFlags(
|
||||
(arg_count as u8).clamp(0, 7)
|
||||
+ (serialize as u8).shl(3)
|
||||
+ sync_level.clamp(0, 15).shl(4),
|
||||
),
|
||||
},
|
||||
//TODO: handle native method?
|
||||
AmlSerdeValue::Buffer(buffer_data) => Object::Buffer(buffer_data),
|
||||
AmlSerdeValue::BufferField {
|
||||
data,
|
||||
offset,
|
||||
length,
|
||||
} => Object::BufferField {
|
||||
offset: offset as usize,
|
||||
length: length as usize,
|
||||
buffer: data.to_aml_object()?.wrap(),
|
||||
},
|
||||
AmlSerdeValue::Processor {
|
||||
id,
|
||||
pblk_address,
|
||||
pblk_len,
|
||||
} => Object::Processor {
|
||||
proc_id: id,
|
||||
pblk_address,
|
||||
pblk_length: pblk_len,
|
||||
},
|
||||
AmlSerdeValue::Mutex { mutex, sync_level } => Object::Mutex {
|
||||
mutex: Handle(mutex),
|
||||
sync_level: sync_level,
|
||||
},
|
||||
AmlSerdeValue::Reference { kind, inner } => Object::Reference {
|
||||
kind: match kind {
|
||||
AmlSerdeReferenceKind::RefOf => ReferenceKind::RefOf,
|
||||
AmlSerdeReferenceKind::Local => ReferenceKind::Local,
|
||||
AmlSerdeReferenceKind::Arg => ReferenceKind::Arg,
|
||||
AmlSerdeReferenceKind::Index => ReferenceKind::Index,
|
||||
AmlSerdeReferenceKind::Named => ReferenceKind::Named,
|
||||
AmlSerdeReferenceKind::Unresolved => ReferenceKind::Unresolved,
|
||||
},
|
||||
inner: inner.to_aml_object()?.wrap(),
|
||||
},
|
||||
AmlSerdeValue::Package { contents } => Object::Package(
|
||||
contents
|
||||
.into_iter()
|
||||
.map(|item| item.to_aml_object().map(Object::wrap)) // TODO: see if errors should be ignored here
|
||||
.collect::<Option<Vec<WrappedObject>>>()?,
|
||||
),
|
||||
AmlSerdeValue::PowerResource {
|
||||
system_level,
|
||||
resource_order,
|
||||
} => Object::PowerResource {
|
||||
system_level: system_level.to_owned(),
|
||||
resource_order: resource_order.to_owned(),
|
||||
},
|
||||
AmlSerdeValue::RawDataBuffer => Object::RawDataBuffer,
|
||||
AmlSerdeValue::ThermalZone => Object::ThermalZone,
|
||||
AmlSerdeValue::Debug => Object::Debug,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub mod aml_serde_name {
|
||||
use acpi::aml::namespace::AmlName;
|
||||
|
||||
/// Add a leading backslash to make the name a valid
|
||||
/// namespace reference
|
||||
pub fn to_aml_format(pretty_name: &String) -> String {
|
||||
format!("\\{}", pretty_name)
|
||||
}
|
||||
|
||||
/// convert a string from AML namespace style to
|
||||
/// acpi symbol style
|
||||
pub fn to_symbol(aml_style_name: &String) -> String {
|
||||
let mut name = aml_style_name.to_owned();
|
||||
|
||||
// remove leading slash
|
||||
name = name.trim_start_matches("\\").to_owned();
|
||||
// remove unnecessary underscores
|
||||
while let Some(index) = name.find("_.") {
|
||||
name.remove(index);
|
||||
}
|
||||
while name.len() > 0 && &name[name.len() - 1..] == "_" {
|
||||
name.pop();
|
||||
}
|
||||
name.shrink_to_fit();
|
||||
name
|
||||
}
|
||||
|
||||
/// Convert to string and remove
|
||||
/// trailing underscores from each name segment
|
||||
pub fn aml_to_symbol(aml_name: &AmlName) -> String {
|
||||
to_symbol(&aml_name.as_string())
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "ac97d"
|
||||
description = "AC'97 driver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
common = { path = "../../common" }
|
||||
libredox.workspace = true
|
||||
log.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
spin.workspace = true
|
||||
|
||||
daemon = { path = "../../../daemon" }
|
||||
pcid = { path = "../../pcid" }
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,5 +0,0 @@
|
||||
[[drivers]]
|
||||
name = "AC97 Audio"
|
||||
class = 0x04
|
||||
subclass = 0x01
|
||||
command = ["ac97d"]
|
||||
@@ -1,333 +0,0 @@
|
||||
use common::io::Pio;
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::CallerCtx;
|
||||
use redox_scheme::OpenResult;
|
||||
use scheme_utils::{FpathWriter, HandleMap};
|
||||
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::EWOULDBLOCK;
|
||||
|
||||
use common::{
|
||||
dma::Dma,
|
||||
io::{Io, Mmio},
|
||||
};
|
||||
use spin::Mutex;
|
||||
|
||||
const NUM_SUB_BUFFS: usize = 32;
|
||||
const SUB_BUFF_SIZE: usize = 2048;
|
||||
|
||||
enum Handle {
|
||||
Todo,
|
||||
SchemeRoot,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct MixerRegs {
|
||||
/* 0x00 */ reset: Pio<u16>,
|
||||
/* 0x02 */ master_volume: Pio<u16>,
|
||||
/* 0x04 */ aux_out_volume: Pio<u16>,
|
||||
/* 0x06 */ mono_volume: Pio<u16>,
|
||||
/* 0x08 */ master_tone: Pio<u16>,
|
||||
/* 0x0A */ pc_beep_volume: Pio<u16>,
|
||||
/* 0x0C */ phone_volume: Pio<u16>,
|
||||
/* 0x0E */ mic_volume: Pio<u16>,
|
||||
/* 0x10 */ line_in_volume: Pio<u16>,
|
||||
/* 0x12 */ cd_volume: Pio<u16>,
|
||||
/* 0x14 */ video_volume: Pio<u16>,
|
||||
/* 0x16 */ aux_in_volume: Pio<u16>,
|
||||
/* 0x18 */ pcm_out_volume: Pio<u16>,
|
||||
/* 0x1A */ record_select: Pio<u16>,
|
||||
/* 0x1C */ record_gain: Pio<u16>,
|
||||
/* 0x1E */ record_gain_mic: Pio<u16>,
|
||||
/* 0x20 */ general_purpose: Pio<u16>,
|
||||
/* 0x22 */ control_3d: Pio<u16>,
|
||||
/* 0x24 */ audio_int_paging: Pio<u16>,
|
||||
/* 0x26 */ powerdown: Pio<u16>,
|
||||
/* 0x28 */ extended_id: Pio<u16>,
|
||||
/* 0x2A */ extended_ctrl: Pio<u16>,
|
||||
/* 0x2C */ vra_pcm_front: Pio<u16>,
|
||||
}
|
||||
|
||||
impl MixerRegs {
|
||||
fn new(bar0: u16) -> Self {
|
||||
Self {
|
||||
reset: Pio::new(bar0 + 0x00),
|
||||
master_volume: Pio::new(bar0 + 0x02),
|
||||
aux_out_volume: Pio::new(bar0 + 0x04),
|
||||
mono_volume: Pio::new(bar0 + 0x06),
|
||||
master_tone: Pio::new(bar0 + 0x08),
|
||||
pc_beep_volume: Pio::new(bar0 + 0x0A),
|
||||
phone_volume: Pio::new(bar0 + 0x0C),
|
||||
mic_volume: Pio::new(bar0 + 0x0E),
|
||||
line_in_volume: Pio::new(bar0 + 0x10),
|
||||
cd_volume: Pio::new(bar0 + 0x12),
|
||||
video_volume: Pio::new(bar0 + 0x14),
|
||||
aux_in_volume: Pio::new(bar0 + 0x16),
|
||||
pcm_out_volume: Pio::new(bar0 + 0x18),
|
||||
record_select: Pio::new(bar0 + 0x1A),
|
||||
record_gain: Pio::new(bar0 + 0x1C),
|
||||
record_gain_mic: Pio::new(bar0 + 0x1E),
|
||||
general_purpose: Pio::new(bar0 + 0x20),
|
||||
control_3d: Pio::new(bar0 + 0x22),
|
||||
audio_int_paging: Pio::new(bar0 + 0x24),
|
||||
powerdown: Pio::new(bar0 + 0x26),
|
||||
extended_id: Pio::new(bar0 + 0x28),
|
||||
extended_ctrl: Pio::new(bar0 + 0x2A),
|
||||
vra_pcm_front: Pio::new(bar0 + 0x2C),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct BusBoxRegs {
|
||||
/// Buffer descriptor list base address
|
||||
/* 0x00 */
|
||||
bdbar: Pio<u32>,
|
||||
/// Current index value
|
||||
/* 0x04 */
|
||||
civ: Pio<u8>,
|
||||
/// Last valid index
|
||||
/* 0x05 */
|
||||
lvi: Pio<u8>,
|
||||
/// Status
|
||||
/* 0x06 */
|
||||
sr: Pio<u16>,
|
||||
/// Position in current buffer
|
||||
/* 0x08 */
|
||||
picb: Pio<u16>,
|
||||
/// Prefetched index value
|
||||
/* 0x0A */
|
||||
piv: Pio<u8>,
|
||||
/// Control
|
||||
/* 0x0B */
|
||||
cr: Pio<u8>,
|
||||
}
|
||||
|
||||
impl BusBoxRegs {
|
||||
fn new(base: u16) -> Self {
|
||||
Self {
|
||||
bdbar: Pio::new(base + 0x00),
|
||||
civ: Pio::new(base + 0x04),
|
||||
lvi: Pio::new(base + 0x05),
|
||||
sr: Pio::new(base + 0x06),
|
||||
picb: Pio::new(base + 0x08),
|
||||
piv: Pio::new(base + 0x0A),
|
||||
cr: Pio::new(base + 0x0B),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct BusRegs {
|
||||
/// PCM in register box
|
||||
/* 0x00 */
|
||||
pi: BusBoxRegs,
|
||||
/// PCM out register box
|
||||
/* 0x10 */
|
||||
po: BusBoxRegs,
|
||||
/// Microphone register box
|
||||
/* 0x20 */
|
||||
mc: BusBoxRegs,
|
||||
}
|
||||
|
||||
impl BusRegs {
|
||||
fn new(bar1: u16) -> Self {
|
||||
Self {
|
||||
pi: BusBoxRegs::new(bar1 + 0x00),
|
||||
po: BusBoxRegs::new(bar1 + 0x10),
|
||||
mc: BusBoxRegs::new(bar1 + 0x20),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct BufferDescriptor {
|
||||
/* 0x00 */ addr: Mmio<u32>,
|
||||
/* 0x04 */ samples: Mmio<u16>,
|
||||
/* 0x06 */ flags: Mmio<u16>,
|
||||
}
|
||||
|
||||
pub struct Ac97 {
|
||||
mixer: MixerRegs,
|
||||
bus: BusRegs,
|
||||
bdl: Dma<[BufferDescriptor; NUM_SUB_BUFFS]>,
|
||||
buf: Dma<[u8; NUM_SUB_BUFFS * SUB_BUFF_SIZE]>,
|
||||
handles: Mutex<HandleMap<Handle>>,
|
||||
}
|
||||
|
||||
impl Ac97 {
|
||||
pub unsafe fn new(bar0: u16, bar1: u16) -> Result<Self> {
|
||||
let mut module = Ac97 {
|
||||
mixer: MixerRegs::new(bar0),
|
||||
bus: BusRegs::new(bar1),
|
||||
bdl: Dma::zeroed(
|
||||
//TODO: PhysBox::new_in_32bit_space(bdl_size)?
|
||||
)?
|
||||
.assume_init(),
|
||||
buf: Dma::zeroed(
|
||||
//TODO: PhysBox::new_in_32bit_space(buf_size)?
|
||||
)?
|
||||
.assume_init(),
|
||||
handles: Mutex::new(HandleMap::new()),
|
||||
};
|
||||
|
||||
module.init()?;
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fn init(&mut self) -> Result<()> {
|
||||
//TODO: support other sample rates, or just the default of 48000 Hz
|
||||
{
|
||||
// Check if VRA is supported
|
||||
if !self.mixer.extended_id.readf(1 << 0) {
|
||||
println!("ac97d: VRA not supported and is currently required");
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
|
||||
// Enable VRA
|
||||
self.mixer.extended_ctrl.writef(1 << 0, true);
|
||||
|
||||
// Attempt to set sample rate for PCM front to 44100 Hz
|
||||
let desired_sample_rate = 44100;
|
||||
self.mixer.vra_pcm_front.write(desired_sample_rate);
|
||||
|
||||
// Read back real sample rate
|
||||
let real_sample_rate = self.mixer.vra_pcm_front.read();
|
||||
println!("ac97d: set sample rate to {}", real_sample_rate);
|
||||
|
||||
// Error if we cannot set the sample rate as desired
|
||||
if real_sample_rate != desired_sample_rate {
|
||||
println!(
|
||||
"ac97d: sample rate is {} but only {} is supported",
|
||||
real_sample_rate, desired_sample_rate
|
||||
);
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure PCM out is stopped
|
||||
self.bus.po.cr.writef(1, false);
|
||||
|
||||
// Reset PCM out
|
||||
self.bus.po.cr.writef(1 << 1, true);
|
||||
while self.bus.po.cr.readf(1 << 1) {
|
||||
// Spinning on resetting PCM out
|
||||
//TODO: relax
|
||||
}
|
||||
|
||||
// Initialize BDL for PCM out
|
||||
for i in 0..NUM_SUB_BUFFS {
|
||||
self.bdl[i]
|
||||
.addr
|
||||
.write((self.buf.physical() + i * SUB_BUFF_SIZE) as u32);
|
||||
self.bdl[i]
|
||||
.samples
|
||||
.write((SUB_BUFF_SIZE / 2/* Each sample is i16 or 2 bytes */) as u16);
|
||||
self.bdl[i]
|
||||
.flags
|
||||
.write(1 << 15 /* Interrupt on completion */);
|
||||
}
|
||||
self.bus.po.bdbar.write(self.bdl.physical() as u32);
|
||||
|
||||
// Enable interrupt on completion
|
||||
self.bus.po.cr.writef(1 << 4, true);
|
||||
|
||||
// Start bus master
|
||||
self.bus.po.cr.writef(1 << 0, true);
|
||||
|
||||
// Set master volume to 0 db (loudest output, DANGER!)
|
||||
self.mixer.master_volume.write(0);
|
||||
|
||||
// Set PCM output volume to 0 db (medium)
|
||||
self.mixer.pcm_out_volume.write(0x808);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn irq(&mut self) -> bool {
|
||||
let ints = self.bus.po.sr.read() & 0b11100;
|
||||
if ints != 0 {
|
||||
self.bus.po.sr.write(ints);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for Ac97 {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.lock().insert(Handle::SchemeRoot))
|
||||
}
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
_path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
{
|
||||
let handles = self.handles.lock();
|
||||
let handle = handles.get(dirfd)?;
|
||||
if !matches!(handle, Handle::SchemeRoot) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
}
|
||||
if ctx.uid == 0 {
|
||||
let id = self.handles.lock().insert(Handle::Todo);
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: id,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(EACCES))
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
{
|
||||
let mut handles = self.handles.lock();
|
||||
let handle = handles.get_mut(id)?;
|
||||
if !matches!(handle, Handle::Todo) {
|
||||
return Err(Error::new(EBADF));
|
||||
}
|
||||
}
|
||||
|
||||
if buf.len() != SUB_BUFF_SIZE {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let civ = self.bus.po.civ.read() as usize;
|
||||
let mut lvi = self.bus.po.lvi.read() as usize;
|
||||
if lvi == (civ + 3) % NUM_SUB_BUFFS {
|
||||
// Block if we already are 3 buffers ahead
|
||||
Err(Error::new(EWOULDBLOCK))
|
||||
} else {
|
||||
// Fill next buffer
|
||||
lvi = (lvi + 1) % NUM_SUB_BUFFS;
|
||||
for i in 0..SUB_BUFF_SIZE {
|
||||
self.buf[lvi * SUB_BUFF_SIZE + i] = buf[i];
|
||||
}
|
||||
self.bus.po.lvi.write(lvi as u8);
|
||||
|
||||
Ok(SUB_BUFF_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||
FpathWriter::with(buf, "audiohw", |_| Ok(()))
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.lock().remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::usize;
|
||||
|
||||
use event::{user_data, EventQueue};
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
use redox_scheme::scheme::register_sync_scheme;
|
||||
use redox_scheme::Socket;
|
||||
use scheme_utils::ReadinessBased;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub mod device;
|
||||
|
||||
fn main() {
|
||||
pcid_interface::pci_daemon(daemon);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
let mut name = pci_config.func.name();
|
||||
name.push_str("_ac97");
|
||||
|
||||
let bar0 = pci_config.func.bars[0].expect_port();
|
||||
let bar1 = pci_config.func.bars[1].expect_port();
|
||||
|
||||
let irq = pci_config
|
||||
.func
|
||||
.legacy_interrupt_line
|
||||
.expect("ac97d: no legacy interrupts supported");
|
||||
|
||||
println!(" + ac97 {}", pci_config.func.display());
|
||||
|
||||
common::setup_logging(
|
||||
"audio",
|
||||
"pci",
|
||||
&name,
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3");
|
||||
|
||||
let mut irq_file = irq.irq_handle("ac97d");
|
||||
|
||||
let socket = Socket::nonblock().expect("ac97d: failed to create socket");
|
||||
let mut device =
|
||||
unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") };
|
||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
||||
|
||||
user_data! {
|
||||
enum Source {
|
||||
Irq,
|
||||
Scheme,
|
||||
}
|
||||
}
|
||||
|
||||
let event_queue = EventQueue::<Source>::new().expect("ac97d: Could not create event queue.");
|
||||
event_queue
|
||||
.subscribe(
|
||||
irq_file.as_raw_fd() as usize,
|
||||
Source::Irq,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
register_sync_scheme(&socket, "audiohw", &mut device)
|
||||
.expect("ac97d: failed to register audiohw scheme to namespace");
|
||||
daemon.ready();
|
||||
|
||||
libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace");
|
||||
|
||||
let all = [Source::Irq, Source::Scheme];
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data))
|
||||
{
|
||||
match event {
|
||||
Source::Irq => {
|
||||
let mut irq = [0; 8];
|
||||
irq_file.read(&mut irq).unwrap();
|
||||
|
||||
if !device.irq() {
|
||||
continue;
|
||||
}
|
||||
irq_file.write(&mut irq).unwrap();
|
||||
|
||||
readiness_based
|
||||
.poll_all_requests(&mut device)
|
||||
.expect("ac97d: failed to poll requests");
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("ac97d: failed to write to socket");
|
||||
|
||||
/*
|
||||
let next_read = device_irq.next_read();
|
||||
if next_read > 0 {
|
||||
return Ok(Some(next_read));
|
||||
}
|
||||
*/
|
||||
}
|
||||
Source::Scheme => {
|
||||
readiness_based
|
||||
.read_and_process_requests(&mut device)
|
||||
.expect("ac97d: failed to read from socket");
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("ac97d: failed to write to socket");
|
||||
|
||||
/*
|
||||
let next_read = device.borrow().next_read();
|
||||
if next_read > 0 {
|
||||
return Ok(Some(next_read));
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "ihdad"
|
||||
description = "Intel HD Audio chipset driver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
libredox.workspace = true
|
||||
log.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
spin.workspace = true
|
||||
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
pcid = { path = "../../pcid" }
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,5 +0,0 @@
|
||||
[[drivers]]
|
||||
name = "Intel HD Audio"
|
||||
class = 0x04
|
||||
subclass = 0x03
|
||||
command = ["ihdad"]
|
||||
@@ -1,501 +0,0 @@
|
||||
use common::dma::Dma;
|
||||
use common::io::{Io, Mmio};
|
||||
use common::timeout::Timeout;
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use super::common::*;
|
||||
|
||||
// CORBCTL
|
||||
const CMEIE: u8 = 1 << 0; // 1 bit
|
||||
const CORBRUN: u8 = 1 << 1; // 1 bit
|
||||
|
||||
// CORBSIZE
|
||||
const CORBSZCAP: (u8, u8) = (4, 4);
|
||||
const CORBSIZE: (u8, u8) = (0, 2);
|
||||
|
||||
// CORBRP
|
||||
const CORBRPRST: u16 = 1 << 15;
|
||||
|
||||
// RIRBWP
|
||||
const RIRBWPRST: u16 = 1 << 15;
|
||||
|
||||
// RIRBCTL
|
||||
const RINTCTL: u8 = 1 << 0; // 1 bit
|
||||
const RIRBDMAEN: u8 = 1 << 1; // 1 bit
|
||||
|
||||
const CORB_OFFSET: usize = 0x00;
|
||||
const RIRB_OFFSET: usize = 0x10;
|
||||
const ICMD_OFFSET: usize = 0x20;
|
||||
|
||||
// ICS
|
||||
const ICB: u16 = 1 << 0;
|
||||
const IRV: u16 = 1 << 1;
|
||||
|
||||
// CORB and RIRB offset
|
||||
|
||||
const COMMAND_BUFFER_OFFSET: usize = 0x40;
|
||||
const CORB_BUFF_MAX_SIZE: usize = 1024;
|
||||
|
||||
struct CommandBufferRegs {
|
||||
corblbase: Mmio<u32>,
|
||||
corbubase: Mmio<u32>,
|
||||
corbwp: Mmio<u16>,
|
||||
corbrp: Mmio<u16>,
|
||||
corbctl: Mmio<u8>,
|
||||
corbsts: Mmio<u8>,
|
||||
corbsize: Mmio<u8>,
|
||||
rsvd5: Mmio<u8>,
|
||||
|
||||
rirblbase: Mmio<u32>,
|
||||
rirbubase: Mmio<u32>,
|
||||
rirbwp: Mmio<u16>,
|
||||
rintcnt: Mmio<u16>,
|
||||
rirbctl: Mmio<u8>,
|
||||
rirbsts: Mmio<u8>,
|
||||
rirbsize: Mmio<u8>,
|
||||
rsvd6: Mmio<u8>,
|
||||
}
|
||||
|
||||
struct CorbRegs {
|
||||
corblbase: Mmio<u32>,
|
||||
corbubase: Mmio<u32>,
|
||||
corbwp: Mmio<u16>,
|
||||
corbrp: Mmio<u16>,
|
||||
corbctl: Mmio<u8>,
|
||||
corbsts: Mmio<u8>,
|
||||
corbsize: Mmio<u8>,
|
||||
rsvd5: Mmio<u8>,
|
||||
}
|
||||
|
||||
struct Corb {
|
||||
regs: &'static mut CorbRegs,
|
||||
corb_base: *mut u32,
|
||||
corb_base_phys: usize,
|
||||
corb_count: usize,
|
||||
}
|
||||
|
||||
impl Corb {
|
||||
pub fn new(regs_addr: usize, corb_buff_phys: usize, corb_buff_virt: *mut u32) -> Corb {
|
||||
unsafe {
|
||||
Corb {
|
||||
regs: &mut *(regs_addr as *mut CorbRegs),
|
||||
corb_base: corb_buff_virt,
|
||||
corb_base_phys: corb_buff_phys,
|
||||
corb_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Intel 4.4.1.3
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
self.stop()?;
|
||||
//Determine CORB and RIRB size and allocate buffer
|
||||
|
||||
//3.3.24
|
||||
let corbsize_reg = self.regs.corbsize.read();
|
||||
let corbszcap = (corbsize_reg >> 4) & 0xF;
|
||||
|
||||
let mut corbsize_bytes: usize = 0;
|
||||
let mut corbsize: u8 = 0;
|
||||
|
||||
if (corbszcap & 4) == 4 {
|
||||
corbsize = 2;
|
||||
corbsize_bytes = 1024;
|
||||
|
||||
self.corb_count = 256;
|
||||
} else if (corbszcap & 2) == 2 {
|
||||
corbsize = 1;
|
||||
corbsize_bytes = 64;
|
||||
|
||||
self.corb_count = 16;
|
||||
} else if (corbszcap & 1) == 1 {
|
||||
corbsize = 0;
|
||||
corbsize_bytes = 8;
|
||||
|
||||
self.corb_count = 2;
|
||||
}
|
||||
|
||||
assert!(self.corb_count != 0);
|
||||
let addr = self.corb_base_phys;
|
||||
self.set_address(addr);
|
||||
self.regs.corbsize.write((corbsize_reg & 0xFC) | corbsize);
|
||||
|
||||
self.reset_read_pointer()?;
|
||||
let old_wp = self.regs.corbwp.read();
|
||||
self.regs.corbwp.write(old_wp & 0xFF00);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.regs.corbctl.writef(CORBRUN, true);
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn stop(&mut self) -> Result<()> {
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while self.regs.corbctl.readf(CORBRUN) {
|
||||
self.regs.corbctl.writef(CORBRUN, false);
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on clearing CORBRUN");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_address(&mut self, addr: usize) {
|
||||
self.regs.corblbase.write((addr & 0xFFFFFFFF) as u32);
|
||||
self.regs.corbubase.write(((addr as u64) >> 32) as u32);
|
||||
}
|
||||
|
||||
pub fn reset_read_pointer(&mut self) -> Result<()> {
|
||||
// 3.3.21
|
||||
|
||||
self.stop()?;
|
||||
|
||||
// Set CORBRPRST to 1
|
||||
log::trace!("CORBRP {:X}", self.regs.corbrp.read());
|
||||
self.regs.corbrp.writef(CORBRPRST, true);
|
||||
log::trace!("CORBRP {:X}", self.regs.corbrp.read());
|
||||
|
||||
{
|
||||
// Wait for it to become 1
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while !self.regs.corbrp.readf(CORBRPRST) {
|
||||
self.regs.corbrp.writef(CORBRPRST, true);
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on setting CORBRPRST");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the bit again
|
||||
self.regs.corbrp.writef(CORBRPRST, false);
|
||||
|
||||
{
|
||||
// Read back the bit until zero to verify that it is cleared.
|
||||
let timeout = Timeout::from_secs(1);
|
||||
loop {
|
||||
if !self.regs.corbrp.readf(CORBRPRST) {
|
||||
break;
|
||||
}
|
||||
self.regs.corbrp.writef(CORBRPRST, false);
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on clearing CORBRPRST");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_command(&mut self, cmd: u32) -> Result<()> {
|
||||
{
|
||||
// wait for the commands to finish
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while (self.regs.corbwp.read() & 0xff) != (self.regs.corbrp.read() & 0xff) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on CORB command");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
let write_pos: usize = ((self.regs.corbwp.read() as usize & 0xFF) + 1) % self.corb_count;
|
||||
unsafe {
|
||||
*self.corb_base.offset(write_pos as isize) = cmd;
|
||||
}
|
||||
|
||||
self.regs.corbwp.write(write_pos as u16);
|
||||
|
||||
log::trace!("Corb: {:08X}", cmd);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct RirbRegs {
|
||||
rirblbase: Mmio<u32>,
|
||||
rirbubase: Mmio<u32>,
|
||||
rirbwp: Mmio<u16>,
|
||||
rintcnt: Mmio<u16>,
|
||||
rirbctl: Mmio<u8>,
|
||||
rirbsts: Mmio<u8>,
|
||||
rirbsize: Mmio<u8>,
|
||||
rsvd6: Mmio<u8>,
|
||||
}
|
||||
|
||||
struct Rirb {
|
||||
regs: &'static mut RirbRegs,
|
||||
rirb_base: *mut u64,
|
||||
rirb_base_phys: usize,
|
||||
rirb_rp: u16,
|
||||
rirb_count: usize,
|
||||
}
|
||||
|
||||
impl Rirb {
|
||||
pub fn new(regs_addr: usize, rirb_buff_phys: usize, rirb_buff_virt: *mut u64) -> Rirb {
|
||||
unsafe {
|
||||
Rirb {
|
||||
regs: &mut *(regs_addr as *mut RirbRegs),
|
||||
rirb_base: rirb_buff_virt,
|
||||
rirb_rp: 0,
|
||||
rirb_base_phys: rirb_buff_phys,
|
||||
rirb_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
//Intel 4.4.1.3
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
self.stop()?;
|
||||
|
||||
let rirbsize_reg = self.regs.rirbsize.read();
|
||||
let rirbszcap = (rirbsize_reg >> 4) & 0xF;
|
||||
|
||||
let mut rirbsize_bytes: usize = 0;
|
||||
let mut rirbsize: u8 = 0;
|
||||
|
||||
if (rirbszcap & 4) == 4 {
|
||||
rirbsize = 2;
|
||||
rirbsize_bytes = 2048;
|
||||
|
||||
self.rirb_count = 256;
|
||||
} else if (rirbszcap & 2) == 2 {
|
||||
rirbsize = 1;
|
||||
rirbsize_bytes = 128;
|
||||
|
||||
self.rirb_count = 8;
|
||||
} else if (rirbszcap & 1) == 1 {
|
||||
rirbsize = 0;
|
||||
rirbsize_bytes = 16;
|
||||
|
||||
self.rirb_count = 2;
|
||||
}
|
||||
|
||||
assert!(self.rirb_count != 0);
|
||||
|
||||
let addr = self.rirb_base_phys;
|
||||
self.set_address(addr);
|
||||
|
||||
self.reset_write_pointer();
|
||||
self.rirb_rp = 0;
|
||||
|
||||
self.regs.rintcnt.write(1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.regs.rirbctl.writef(RIRBDMAEN | RINTCTL, true);
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) -> Result<()> {
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while self.regs.rirbctl.readf(RIRBDMAEN) {
|
||||
self.regs.rirbctl.writef(RIRBDMAEN, false);
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on clearing RIRBDMAEN");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_address(&mut self, addr: usize) {
|
||||
self.regs.rirblbase.write((addr & 0xFFFFFFFF) as u32);
|
||||
self.regs.rirbubase.write(((addr as u64) >> 32) as u32);
|
||||
}
|
||||
|
||||
pub fn reset_write_pointer(&mut self) {
|
||||
self.regs.rirbwp.writef(RIRBWPRST, true);
|
||||
}
|
||||
|
||||
fn read_response(&mut self) -> Result<u64> {
|
||||
{
|
||||
// wait for response
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while (self.regs.rirbwp.read() & 0xff) == (self.rirb_rp & 0xff) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on RIRB response");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
let read_pos: u16 = (self.rirb_rp + 1) % self.rirb_count as u16;
|
||||
|
||||
let res: u64;
|
||||
unsafe {
|
||||
res = *self.rirb_base.offset(read_pos as isize);
|
||||
}
|
||||
self.rirb_rp = read_pos;
|
||||
log::trace!("Rirb: {:08X}", res);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
struct ImmediateCommandRegs {
|
||||
icoi: Mmio<u32>,
|
||||
irii: Mmio<u32>,
|
||||
ics: Mmio<u16>,
|
||||
rsvd7: [Mmio<u8>; 6],
|
||||
}
|
||||
|
||||
pub struct ImmediateCommand {
|
||||
regs: &'static mut ImmediateCommandRegs,
|
||||
}
|
||||
|
||||
impl ImmediateCommand {
|
||||
pub fn new(regs_addr: usize) -> ImmediateCommand {
|
||||
unsafe {
|
||||
ImmediateCommand {
|
||||
regs: &mut *(regs_addr as *mut ImmediateCommandRegs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmd(&mut self, cmd: u32) -> Result<u64> {
|
||||
{
|
||||
// wait for ready
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while self.regs.ics.readf(ICB) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on immediate command");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// write command
|
||||
self.regs.icoi.write(cmd);
|
||||
|
||||
// set ICB bit to send command
|
||||
self.regs.ics.writef(ICB, true);
|
||||
|
||||
{
|
||||
// wait for IRV bit to be set to indicate a response is latched
|
||||
let timeout = Timeout::from_secs(1);
|
||||
while !self.regs.ics.readf(IRV) {
|
||||
timeout.run().map_err(|()| {
|
||||
log::error!("timeout on immediate response");
|
||||
Error::new(EIO)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// read the result register twice, total of 8 bytes
|
||||
// highest 4 will most likely be zeros (so I've heard)
|
||||
let mut res: u64 = self.regs.irii.read() as u64;
|
||||
res |= (self.regs.irii.read() as u64) << 32;
|
||||
|
||||
// clear the bit so we know when the next response comes
|
||||
self.regs.ics.writef(IRV, false);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommandBuffer {
|
||||
// regs: &'static mut CommandBufferRegs,
|
||||
corb: Corb,
|
||||
rirb: Rirb,
|
||||
icmd: ImmediateCommand,
|
||||
|
||||
use_immediate_cmd: bool,
|
||||
mem: Dma<[u8; 0x1000]>,
|
||||
}
|
||||
|
||||
impl CommandBuffer {
|
||||
pub fn new(regs_addr: usize, mut cmd_buff: Dma<[u8; 0x1000]>) -> CommandBuffer {
|
||||
let corb = Corb::new(
|
||||
regs_addr + CORB_OFFSET,
|
||||
cmd_buff.physical(),
|
||||
cmd_buff.as_mut_ptr().cast(),
|
||||
);
|
||||
let rirb = Rirb::new(
|
||||
regs_addr + RIRB_OFFSET,
|
||||
cmd_buff.physical() + CORB_BUFF_MAX_SIZE,
|
||||
cmd_buff
|
||||
.as_mut_ptr()
|
||||
.cast::<u8>()
|
||||
.wrapping_add(CORB_BUFF_MAX_SIZE)
|
||||
.cast(),
|
||||
);
|
||||
|
||||
let icmd = ImmediateCommand::new(regs_addr + ICMD_OFFSET);
|
||||
|
||||
let cmdbuff = CommandBuffer {
|
||||
corb,
|
||||
rirb,
|
||||
icmd,
|
||||
|
||||
use_immediate_cmd: false,
|
||||
|
||||
mem: cmd_buff,
|
||||
};
|
||||
|
||||
cmdbuff
|
||||
}
|
||||
|
||||
pub fn init(&mut self, use_imm_cmds: bool) -> Result<()> {
|
||||
self.corb.init()?;
|
||||
self.rirb.init()?;
|
||||
self.set_use_imm_cmds(use_imm_cmds)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) -> Result<()> {
|
||||
self.corb.stop()?;
|
||||
self.rirb.stop()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cmd12(&mut self, addr: WidgetAddr, command: u32, data: u8) -> Result<u64> {
|
||||
let mut ncmd: u32 = 0;
|
||||
|
||||
ncmd |= (addr.0 as u32 & 0x00F) << 28;
|
||||
ncmd |= (addr.1 as u32 & 0x0FF) << 20;
|
||||
ncmd |= (command & 0xFFF) << 8;
|
||||
ncmd |= (data as u32 & 0x0FF) << 0;
|
||||
self.cmd(ncmd)
|
||||
}
|
||||
pub fn cmd4(&mut self, addr: WidgetAddr, command: u32, data: u16) -> Result<u64> {
|
||||
let mut ncmd: u32 = 0;
|
||||
|
||||
ncmd |= (addr.0 as u32 & 0x000F) << 28;
|
||||
ncmd |= (addr.1 as u32 & 0x00FF) << 20;
|
||||
ncmd |= (command & 0x000F) << 16;
|
||||
ncmd |= (data as u32 & 0xFFFF) << 0;
|
||||
self.cmd(ncmd)
|
||||
}
|
||||
|
||||
pub fn cmd(&mut self, cmd: u32) -> Result<u64> {
|
||||
if self.use_immediate_cmd {
|
||||
self.cmd_imm(cmd)
|
||||
} else {
|
||||
self.cmd_buff(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmd_imm(&mut self, cmd: u32) -> Result<u64> {
|
||||
self.icmd.cmd(cmd)
|
||||
}
|
||||
|
||||
pub fn cmd_buff(&mut self, cmd: u32) -> Result<u64> {
|
||||
self.corb.send_command(cmd)?;
|
||||
self.rirb.read_response()
|
||||
}
|
||||
|
||||
pub fn set_use_imm_cmds(&mut self, use_imm: bool) -> Result<()> {
|
||||
self.use_immediate_cmd = use_imm;
|
||||
|
||||
if self.use_immediate_cmd {
|
||||
self.corb.stop()?;
|
||||
self.rirb.stop()?;
|
||||
} else {
|
||||
self.corb.start();
|
||||
self.rirb.start();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::mem::transmute;
|
||||
|
||||
pub type HDANodeAddr = u16;
|
||||
pub type HDACodecAddr = u8;
|
||||
|
||||
pub type NodeAddr = u16;
|
||||
pub type CodecAddr = u8;
|
||||
|
||||
pub type WidgetAddr = (CodecAddr, NodeAddr);
|
||||
/*
|
||||
impl fmt::Display for WidgetAddr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:01X}:{:02X}\n", self.0, self.1)
|
||||
}
|
||||
}*/
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum HDAWidgetType {
|
||||
AudioOutput = 0x0,
|
||||
AudioInput = 0x1,
|
||||
AudioMixer = 0x2,
|
||||
AudioSelector = 0x3,
|
||||
PinComplex = 0x4,
|
||||
Power = 0x5,
|
||||
VolumeKnob = 0x6,
|
||||
BeepGenerator = 0x7,
|
||||
|
||||
VendorDefined = 0xf,
|
||||
}
|
||||
|
||||
impl fmt::Display for HDAWidgetType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum DefaultDevice {
|
||||
LineOut = 0x0,
|
||||
Speaker = 0x1,
|
||||
HPOut = 0x2,
|
||||
CD = 0x3,
|
||||
SPDIF = 0x4,
|
||||
DigitalOtherOut = 0x5,
|
||||
ModemLineSide = 0x6,
|
||||
ModemHandsetSide = 0x7,
|
||||
LineIn = 0x8,
|
||||
AUX = 0x9,
|
||||
MicIn = 0xA,
|
||||
Telephony = 0xB,
|
||||
SPDIFIn = 0xC,
|
||||
DigitalOtherIn = 0xD,
|
||||
Reserved = 0xE,
|
||||
Other = 0xF,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum PortConnectivity {
|
||||
ConnectedToJack = 0x0,
|
||||
NoPhysicalConnection = 0x1,
|
||||
FixedFunction = 0x2,
|
||||
JackAndInternal = 0x3,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum GrossLocation {
|
||||
ExternalOnPrimary = 0x0,
|
||||
Internal = 0x1,
|
||||
SeperateChasis = 0x2,
|
||||
Other = 0x3,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum GeometricLocation {
|
||||
NA = 0x0,
|
||||
Rear = 0x1,
|
||||
Front = 0x2,
|
||||
Left = 0x3,
|
||||
Right = 0x4,
|
||||
Top = 0x5,
|
||||
Bottom = 0x6,
|
||||
Special1 = 0x7,
|
||||
Special2 = 0x8,
|
||||
Special3 = 0x9,
|
||||
Resvd1 = 0xA,
|
||||
Resvd2 = 0xB,
|
||||
Resvd3 = 0xC,
|
||||
Resvd4 = 0xD,
|
||||
Resvd5 = 0xE,
|
||||
Resvd6 = 0xF,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Color {
|
||||
Unknown = 0x0,
|
||||
Black = 0x1,
|
||||
Grey = 0x2,
|
||||
Blue = 0x3,
|
||||
Green = 0x4,
|
||||
Red = 0x5,
|
||||
Orange = 0x6,
|
||||
Yellow = 0x7,
|
||||
Purple = 0x8,
|
||||
Pink = 0x9,
|
||||
Resvd1 = 0xA,
|
||||
Resvd2 = 0xB,
|
||||
Resvd3 = 0xC,
|
||||
Resvd4 = 0xD,
|
||||
White = 0xE,
|
||||
Other = 0xF,
|
||||
}
|
||||
|
||||
pub struct ConfigurationDefault {
|
||||
value: u32,
|
||||
}
|
||||
|
||||
impl ConfigurationDefault {
|
||||
pub fn from_u32(value: u32) -> ConfigurationDefault {
|
||||
ConfigurationDefault { value: value }
|
||||
}
|
||||
|
||||
pub fn color(&self) -> Color {
|
||||
unsafe { transmute(((self.value >> 12) & 0xF) as u8) }
|
||||
}
|
||||
|
||||
pub fn default_device(&self) -> DefaultDevice {
|
||||
unsafe { transmute(((self.value >> 20) & 0xF) as u8) }
|
||||
}
|
||||
|
||||
pub fn port_connectivity(&self) -> PortConnectivity {
|
||||
unsafe { transmute(((self.value >> 30) & 0x3) as u8) }
|
||||
}
|
||||
|
||||
pub fn gross_location(&self) -> GrossLocation {
|
||||
unsafe { transmute(((self.value >> 28) & 0x3) as u8) }
|
||||
}
|
||||
|
||||
pub fn geometric_location(&self) -> GeometricLocation {
|
||||
unsafe { transmute(((self.value >> 24) & 0x7) as u8) }
|
||||
}
|
||||
|
||||
pub fn is_output(&self) -> bool {
|
||||
match self.default_device() {
|
||||
DefaultDevice::LineOut
|
||||
| DefaultDevice::Speaker
|
||||
| DefaultDevice::HPOut
|
||||
| DefaultDevice::CD
|
||||
| DefaultDevice::SPDIF
|
||||
| DefaultDevice::DigitalOtherOut
|
||||
| DefaultDevice::ModemLineSide => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_input(&self) -> bool {
|
||||
match self.default_device() {
|
||||
DefaultDevice::ModemHandsetSide
|
||||
| DefaultDevice::LineIn
|
||||
| DefaultDevice::AUX
|
||||
| DefaultDevice::MicIn
|
||||
| DefaultDevice::Telephony
|
||||
| DefaultDevice::SPDIFIn
|
||||
| DefaultDevice::DigitalOtherIn => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sequence(&self) -> u8 {
|
||||
(self.value & 0xF) as u8
|
||||
}
|
||||
|
||||
pub fn default_association(&self) -> u8 {
|
||||
((self.value >> 4) & 0xF) as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfigurationDefault {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:?} {:?} {:?} {:?}",
|
||||
self.default_device(),
|
||||
self.color(),
|
||||
self.gross_location(),
|
||||
self.geometric_location()
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
pub mod cmdbuff;
|
||||
pub mod common;
|
||||
pub mod device;
|
||||
pub mod node;
|
||||
pub mod stream;
|
||||
|
||||
pub use self::node::*;
|
||||
pub use self::stream::*;
|
||||
|
||||
pub use self::cmdbuff::*;
|
||||
pub use self::device::IntelHDA;
|
||||
pub use self::stream::BitsPerSample;
|
||||
pub use self::stream::BufferDescriptorListEntry;
|
||||
pub use self::stream::StreamBuffer;
|
||||
pub use self::stream::StreamDescriptorRegs;
|
||||
@@ -1,108 +0,0 @@
|
||||
use super::common::*;
|
||||
use std::{fmt, mem};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HDANode {
|
||||
pub addr: WidgetAddr,
|
||||
|
||||
// 0x4
|
||||
pub subnode_count: u16,
|
||||
pub subnode_start: u16,
|
||||
|
||||
// 0x5
|
||||
pub function_group_type: u8,
|
||||
|
||||
// 0x9
|
||||
pub capabilities: u32,
|
||||
|
||||
// 0xE
|
||||
pub conn_list_len: u8,
|
||||
|
||||
pub connections: Vec<WidgetAddr>,
|
||||
|
||||
pub connection_default: u8,
|
||||
|
||||
pub is_widget: bool,
|
||||
|
||||
pub config_default: u32,
|
||||
}
|
||||
|
||||
impl HDANode {
|
||||
pub fn new() -> HDANode {
|
||||
HDANode {
|
||||
addr: (0, 0),
|
||||
subnode_count: 0,
|
||||
subnode_start: 0,
|
||||
function_group_type: 0,
|
||||
capabilities: 0,
|
||||
conn_list_len: 0,
|
||||
|
||||
config_default: 0,
|
||||
is_widget: false,
|
||||
connections: Vec::<WidgetAddr>::new(),
|
||||
connection_default: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn widget_type(&self) -> HDAWidgetType {
|
||||
unsafe { mem::transmute(((self.capabilities >> 20) & 0xF) as u8) }
|
||||
}
|
||||
|
||||
pub fn device_default(&self) -> Option<DefaultDevice> {
|
||||
if self.widget_type() != HDAWidgetType::PinComplex {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { mem::transmute(((self.config_default >> 20) & 0xF) as u8) })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configuration_default(&self) -> ConfigurationDefault {
|
||||
ConfigurationDefault::from_u32(self.config_default)
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> WidgetAddr {
|
||||
self.addr
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HDANode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.addr == (0, 0) {
|
||||
write!(
|
||||
f,
|
||||
"Addr: {:02X}:{:02X}, Root Node.",
|
||||
self.addr.0, self.addr.1
|
||||
)
|
||||
} else if self.is_widget {
|
||||
match self.widget_type() {
|
||||
HDAWidgetType::PinComplex => write!(
|
||||
f,
|
||||
"Addr: {:02X}:{:02X}, Type: {:?}: {:?}, Inputs: {}/{}: {:X?}.",
|
||||
self.addr.0,
|
||||
self.addr.1,
|
||||
self.widget_type(),
|
||||
self.device_default().unwrap(),
|
||||
self.connection_default,
|
||||
self.conn_list_len,
|
||||
self.connections
|
||||
),
|
||||
_ => write!(
|
||||
f,
|
||||
"Addr: {:02X}:{:02X}, Type: {:?}, Inputs: {}/{}: {:X?}.",
|
||||
self.addr.0,
|
||||
self.addr.1,
|
||||
self.widget_type(),
|
||||
self.connection_default,
|
||||
self.conn_list_len,
|
||||
self.connections
|
||||
),
|
||||
}
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"Addr: {:02X}:{:02X}, AFG: {}, Widget count {}.",
|
||||
self.addr.0, self.addr.1, self.function_group_type, self.subnode_count
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,387 +0,0 @@
|
||||
use common::dma::Dma;
|
||||
use common::io::{Io, Mmio};
|
||||
use std::cmp::min;
|
||||
use std::ptr::copy_nonoverlapping;
|
||||
use std::result;
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
use syscall::PAGE_SIZE;
|
||||
|
||||
extern crate syscall;
|
||||
|
||||
pub enum BaseRate {
|
||||
BR44_1,
|
||||
BR48,
|
||||
}
|
||||
|
||||
pub struct SampleRate {
|
||||
base: BaseRate,
|
||||
mult: u16,
|
||||
div: u16,
|
||||
}
|
||||
|
||||
use self::BaseRate::{BR44_1, BR48};
|
||||
|
||||
pub const SR_8: SampleRate = SampleRate {
|
||||
base: BR48,
|
||||
mult: 1,
|
||||
div: 6,
|
||||
};
|
||||
pub const SR_11_025: SampleRate = SampleRate {
|
||||
base: BR44_1,
|
||||
mult: 1,
|
||||
div: 4,
|
||||
};
|
||||
pub const SR_16: SampleRate = SampleRate {
|
||||
base: BR48,
|
||||
mult: 1,
|
||||
div: 3,
|
||||
};
|
||||
pub const SR_22_05: SampleRate = SampleRate {
|
||||
base: BR44_1,
|
||||
mult: 1,
|
||||
div: 2,
|
||||
};
|
||||
pub const SR_32: SampleRate = SampleRate {
|
||||
base: BR48,
|
||||
mult: 2,
|
||||
div: 3,
|
||||
};
|
||||
|
||||
pub const SR_44_1: SampleRate = SampleRate {
|
||||
base: BR44_1,
|
||||
mult: 1,
|
||||
div: 1,
|
||||
};
|
||||
pub const SR_48: SampleRate = SampleRate {
|
||||
base: BR48,
|
||||
mult: 1,
|
||||
div: 1,
|
||||
};
|
||||
pub const SR_88_1: SampleRate = SampleRate {
|
||||
base: BR44_1,
|
||||
mult: 2,
|
||||
div: 1,
|
||||
};
|
||||
pub const SR_96: SampleRate = SampleRate {
|
||||
base: BR48,
|
||||
mult: 2,
|
||||
div: 1,
|
||||
};
|
||||
pub const SR_176_4: SampleRate = SampleRate {
|
||||
base: BR44_1,
|
||||
mult: 4,
|
||||
div: 1,
|
||||
};
|
||||
pub const SR_192: SampleRate = SampleRate {
|
||||
base: BR48,
|
||||
mult: 4,
|
||||
div: 1,
|
||||
};
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum BitsPerSample {
|
||||
Bits8 = 0,
|
||||
Bits16 = 1,
|
||||
Bits20 = 2,
|
||||
Bits24 = 3,
|
||||
Bits32 = 4,
|
||||
}
|
||||
|
||||
pub fn format_to_u16(sr: &SampleRate, bps: BitsPerSample, channels: u8) -> u16 {
|
||||
// 3.3.41
|
||||
|
||||
let base: u16 = match sr.base {
|
||||
BaseRate::BR44_1 => 1 << 14,
|
||||
BaseRate::BR48 => 0,
|
||||
};
|
||||
|
||||
let mult = ((sr.mult - 1) & 0x7) << 11;
|
||||
|
||||
let div = ((sr.div - 1) & 0x7) << 8;
|
||||
|
||||
let bits = (bps as u16) << 4;
|
||||
|
||||
let chan = ((channels - 1) & 0xF) as u16;
|
||||
|
||||
let val: u16 = base | mult | div | bits | chan;
|
||||
|
||||
val
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct StreamDescriptorRegs {
|
||||
ctrl_lo: Mmio<u16>,
|
||||
ctrl_hi: Mmio<u8>,
|
||||
status: Mmio<u8>,
|
||||
link_pos: Mmio<u32>,
|
||||
buff_length: Mmio<u32>,
|
||||
last_valid_index: Mmio<u16>,
|
||||
resv1: Mmio<u16>,
|
||||
fifo_size_: Mmio<u16>,
|
||||
format: Mmio<u16>,
|
||||
resv2: Mmio<u32>,
|
||||
buff_desc_list_lo: Mmio<u32>,
|
||||
buff_desc_list_hi: Mmio<u32>,
|
||||
}
|
||||
|
||||
impl StreamDescriptorRegs {
|
||||
pub fn status(&self) -> u8 {
|
||||
self.status.read()
|
||||
}
|
||||
|
||||
pub fn set_status(&mut self, status: u8) {
|
||||
self.status.write(status);
|
||||
}
|
||||
|
||||
pub fn control(&self) -> u32 {
|
||||
let mut ctrl = self.ctrl_lo.read() as u32;
|
||||
ctrl |= (self.ctrl_hi.read() as u32) << 16;
|
||||
ctrl
|
||||
}
|
||||
|
||||
pub fn set_control(&mut self, control: u32) {
|
||||
self.ctrl_lo.write((control & 0xFFFF) as u16);
|
||||
self.ctrl_hi.write(((control >> 16) & 0xFF) as u8);
|
||||
}
|
||||
|
||||
pub fn set_pcm_format(&mut self, sr: &SampleRate, bps: BitsPerSample, channels: u8) {
|
||||
// 3.3.41
|
||||
|
||||
let val = format_to_u16(sr, bps, channels);
|
||||
self.format.write(val);
|
||||
}
|
||||
|
||||
pub fn fifo_size(&self) -> u16 {
|
||||
self.fifo_size_.read()
|
||||
}
|
||||
|
||||
pub fn set_cyclic_buffer_length(&mut self, length: u32) {
|
||||
self.buff_length.write(length);
|
||||
}
|
||||
|
||||
pub fn cyclic_buffer_length(&self) -> u32 {
|
||||
self.buff_length.read()
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let val = self.control() | (1 << 1);
|
||||
self.set_control(val);
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
let val = self.control() & !(1 << 1);
|
||||
self.set_control(val);
|
||||
}
|
||||
|
||||
pub fn stream_number(&self) -> u8 {
|
||||
((self.control() >> 20) & 0xF) as u8
|
||||
}
|
||||
|
||||
pub fn set_stream_number(&mut self, stream_number: u8) {
|
||||
let val = (self.control() & 0x00FFFF) | (((stream_number & 0xF) as u32) << 20);
|
||||
self.set_control(val);
|
||||
}
|
||||
|
||||
pub fn set_address(&mut self, addr: usize) {
|
||||
self.buff_desc_list_lo.write((addr & 0xFFFFFFFF) as u32);
|
||||
self.buff_desc_list_hi
|
||||
.write((((addr as u64) >> 32) & 0xFFFFFFFF) as u32);
|
||||
}
|
||||
|
||||
pub fn set_last_valid_index(&mut self, index: u16) {
|
||||
self.last_valid_index.write(index);
|
||||
}
|
||||
|
||||
pub fn link_position(&self) -> u32 {
|
||||
self.link_pos.read()
|
||||
}
|
||||
|
||||
pub fn set_interrupt_on_completion(&mut self, enable: bool) {
|
||||
let mut ctrl = self.control();
|
||||
if enable {
|
||||
ctrl |= 1 << 2;
|
||||
} else {
|
||||
ctrl &= !(1 << 2);
|
||||
}
|
||||
self.set_control(ctrl);
|
||||
}
|
||||
|
||||
pub fn buffer_complete(&self) -> bool {
|
||||
self.status.readf(1 << 2)
|
||||
}
|
||||
|
||||
pub fn clear_interrupts(&mut self) {
|
||||
self.status.write(0x7 << 2);
|
||||
}
|
||||
|
||||
// get sample size in bytes
|
||||
pub fn sample_size(&self) -> usize {
|
||||
let format = self.format.read();
|
||||
let chan = (format & 0xF) as usize;
|
||||
let bits = ((format >> 4) & 0xF) as usize;
|
||||
match bits {
|
||||
0 => 1 * (chan + 1),
|
||||
1 => 2 * (chan + 1),
|
||||
_ => 4 * (chan + 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutputStream {
|
||||
buff: StreamBuffer,
|
||||
|
||||
desc_regs: &'static mut StreamDescriptorRegs,
|
||||
}
|
||||
|
||||
impl OutputStream {
|
||||
pub fn new(
|
||||
block_count: usize,
|
||||
block_length: usize,
|
||||
regs: &'static mut StreamDescriptorRegs,
|
||||
) -> OutputStream {
|
||||
OutputStream {
|
||||
buff: StreamBuffer::new(block_length, block_count).unwrap(),
|
||||
|
||||
desc_regs: regs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_block(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
self.buff.write_block(buf)
|
||||
}
|
||||
|
||||
pub fn block_size(&self) -> usize {
|
||||
self.buff.block_size()
|
||||
}
|
||||
|
||||
pub fn block_count(&self) -> usize {
|
||||
self.buff.block_count()
|
||||
}
|
||||
|
||||
pub fn current_block(&self) -> usize {
|
||||
self.buff.current_block()
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> usize {
|
||||
self.buff.addr()
|
||||
}
|
||||
|
||||
pub fn phys(&self) -> usize {
|
||||
self.buff.phys()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct BufferDescriptorListEntry {
|
||||
addr_low: Mmio<u32>,
|
||||
addr_high: Mmio<u32>,
|
||||
len: Mmio<u32>,
|
||||
ioc_resv: Mmio<u32>,
|
||||
}
|
||||
|
||||
impl BufferDescriptorListEntry {
|
||||
pub fn address(&self) -> u64 {
|
||||
(self.addr_low.read() as u64) | ((self.addr_high.read() as u64) << 32)
|
||||
}
|
||||
|
||||
pub fn set_address(&mut self, addr: u64) {
|
||||
self.addr_low.write(addr as u32);
|
||||
self.addr_high.write((addr >> 32) as u32);
|
||||
}
|
||||
|
||||
pub fn length(&self) -> u32 {
|
||||
self.len.read()
|
||||
}
|
||||
|
||||
pub fn set_length(&mut self, length: u32) {
|
||||
self.len.write(length)
|
||||
}
|
||||
|
||||
pub fn interrupt_on_completion(&self) -> bool {
|
||||
(self.ioc_resv.read() & 0x1) == 0x1
|
||||
}
|
||||
|
||||
pub fn set_interrupt_on_complete(&mut self, ioc: bool) {
|
||||
self.ioc_resv.writef(1, ioc);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StreamBuffer {
|
||||
mem: Dma<[u8]>,
|
||||
|
||||
block_cnt: usize,
|
||||
block_len: usize,
|
||||
|
||||
cur_pos: usize,
|
||||
}
|
||||
|
||||
impl StreamBuffer {
|
||||
pub fn new(
|
||||
block_length: usize,
|
||||
block_count: usize,
|
||||
) -> result::Result<StreamBuffer, &'static str> {
|
||||
let page_aligned_size = (block_length * block_count).next_multiple_of(PAGE_SIZE);
|
||||
let mem = unsafe {
|
||||
Dma::zeroed_slice(page_aligned_size)
|
||||
.map_err(|_| "Could not allocate physical memory for buffer.")?
|
||||
.assume_init()
|
||||
};
|
||||
|
||||
Ok(StreamBuffer {
|
||||
mem,
|
||||
block_len: block_length,
|
||||
block_cnt: block_count,
|
||||
cur_pos: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn length(&self) -> usize {
|
||||
self.block_len * self.block_cnt
|
||||
}
|
||||
|
||||
pub fn addr(&self) -> usize {
|
||||
self.mem.as_ptr() as usize
|
||||
}
|
||||
|
||||
pub fn phys(&self) -> usize {
|
||||
self.mem.physical()
|
||||
}
|
||||
|
||||
pub fn block_size(&self) -> usize {
|
||||
self.block_len
|
||||
}
|
||||
|
||||
pub fn block_count(&self) -> usize {
|
||||
self.block_cnt
|
||||
}
|
||||
|
||||
pub fn current_block(&self) -> usize {
|
||||
self.cur_pos
|
||||
}
|
||||
|
||||
pub fn write_block(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
if buf.len() != self.block_size() {
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
let len = min(self.block_size(), buf.len());
|
||||
|
||||
//log::trace!("Phys: {:X} Virt: {:X} Offset: {:X} Len: {:X}", self.phys(), self.addr(), self.current_block() * self.block_size(), len);
|
||||
unsafe {
|
||||
copy_nonoverlapping(
|
||||
buf.as_ptr(),
|
||||
(self.addr() + self.current_block() * self.block_size()) as *mut u8,
|
||||
len,
|
||||
);
|
||||
}
|
||||
|
||||
self.cur_pos += 1;
|
||||
self.cur_pos %= self.block_count();
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
impl Drop for StreamBuffer {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("IHDA: Deallocating buffer.");
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
use redox_scheme::scheme::register_sync_scheme;
|
||||
use redox_scheme::Socket;
|
||||
use scheme_utils::ReadinessBased;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::usize;
|
||||
|
||||
use event::{user_data, EventQueue};
|
||||
use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
|
||||
pub mod hda;
|
||||
|
||||
/*
|
||||
VEND:PROD
|
||||
Virtualbox 8086:2668
|
||||
QEMU ICH9 8086:293E
|
||||
82801H ICH8 8086:284B
|
||||
*/
|
||||
|
||||
fn main() {
|
||||
pcid_interface::pci_daemon(daemon);
|
||||
}
|
||||
|
||||
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
let mut name = pci_config.func.name();
|
||||
name.push_str("_ihda");
|
||||
|
||||
common::setup_logging(
|
||||
"audio",
|
||||
"pci",
|
||||
&name,
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
log::info!("IHDA {}", pci_config.func.display());
|
||||
|
||||
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
|
||||
|
||||
let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
|
||||
|
||||
{
|
||||
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
|
||||
| (pci_config.func.full_device_id.device_id as u32);
|
||||
|
||||
user_data! {
|
||||
enum Source {
|
||||
Irq,
|
||||
Scheme,
|
||||
}
|
||||
}
|
||||
|
||||
let event_queue =
|
||||
EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
|
||||
let socket = Socket::nonblock().expect("ihdad: failed to create socket");
|
||||
let mut device = unsafe {
|
||||
hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
|
||||
};
|
||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
||||
|
||||
register_sync_scheme(&socket, "audiohw", &mut device)
|
||||
.expect("ihdad: failed to register audiohw scheme to namespace");
|
||||
daemon.ready();
|
||||
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
irq_file.irq_handle().as_raw_fd() as usize,
|
||||
Source::Irq,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
libredox::call::setrens(0, 0).expect("ihdad: failed to enter null namespace");
|
||||
|
||||
let all = [Source::Irq, Source::Scheme];
|
||||
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("failed to get next event").user_data))
|
||||
{
|
||||
match event {
|
||||
Source::Irq => {
|
||||
let mut irq = [0; 8];
|
||||
irq_file.irq_handle().read(&mut irq).unwrap();
|
||||
|
||||
if !device.irq() {
|
||||
continue;
|
||||
}
|
||||
irq_file.irq_handle().write(&mut irq).unwrap();
|
||||
|
||||
readiness_based
|
||||
.poll_all_requests(&mut device)
|
||||
.expect("ihdad: failed to poll requests");
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("ihdad: failed to write to socket");
|
||||
|
||||
/*
|
||||
let next_read = device_irq.next_read();
|
||||
if next_read > 0 {
|
||||
return Ok(Some(next_read));
|
||||
}
|
||||
*/
|
||||
}
|
||||
Source::Scheme => {
|
||||
readiness_based
|
||||
.read_and_process_requests(&mut device)
|
||||
.expect("ihdad: failed to read from socket");
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("ihdad: failed to write to socket");
|
||||
|
||||
/*
|
||||
let next_read = device.borrow().next_read();
|
||||
if next_read > 0 {
|
||||
return Ok(Some(next_read));
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "sb16d"
|
||||
description = "Sound Blaster sound card driver"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
common = { path = "../../common" }
|
||||
libredox.workspace = true
|
||||
log.workspace = true
|
||||
daemon = { path = "../../../daemon" }
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
spin.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,232 +0,0 @@
|
||||
use std::{thread, time};
|
||||
|
||||
use common::io::{Io, Pio, ReadOnly, WriteOnly};
|
||||
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::CallerCtx;
|
||||
use redox_scheme::OpenResult;
|
||||
use scheme_utils::{FpathWriter, HandleMap};
|
||||
use syscall::error::{Error, Result, EACCES, EBADF, ENODEV};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
|
||||
use spin::Mutex;
|
||||
|
||||
const NUM_SUB_BUFFS: usize = 32;
|
||||
const SUB_BUFF_SIZE: usize = 2048;
|
||||
|
||||
enum Handle {
|
||||
Todo,
|
||||
SchemeRoot,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Sb16 {
|
||||
handles: Mutex<HandleMap<Handle>>,
|
||||
pub(crate) irqs: Vec<u8>,
|
||||
dmas: Vec<u8>,
|
||||
// Regs
|
||||
/* 0x04 */ mixer_addr: WriteOnly<Pio<u8>>,
|
||||
/* 0x05 */ mixer_data: Pio<u8>,
|
||||
/* 0x06 */ dsp_reset: WriteOnly<Pio<u8>>,
|
||||
/* 0x0A */ dsp_read_data: ReadOnly<Pio<u8>>,
|
||||
/* 0x0C */ dsp_write_data: WriteOnly<Pio<u8>>,
|
||||
/* 0x0C */ dsp_write_status: ReadOnly<Pio<u8>>,
|
||||
/* 0x0E */ dsp_read_status: ReadOnly<Pio<u8>>,
|
||||
}
|
||||
|
||||
impl Sb16 {
|
||||
pub unsafe fn new(addr: u16) -> Result<Self> {
|
||||
let mut module = Sb16 {
|
||||
handles: Mutex::new(HandleMap::new()),
|
||||
irqs: Vec::new(),
|
||||
dmas: Vec::new(),
|
||||
// Regs
|
||||
mixer_addr: WriteOnly::new(Pio::new(addr + 0x04)),
|
||||
mixer_data: Pio::new(addr + 0x05),
|
||||
dsp_reset: WriteOnly::new(Pio::new(addr + 0x06)),
|
||||
dsp_read_data: ReadOnly::new(Pio::new(addr + 0x0A)),
|
||||
dsp_write_data: WriteOnly::new(Pio::new(addr + 0x0C)),
|
||||
dsp_write_status: ReadOnly::new(Pio::new(addr + 0x0C)),
|
||||
dsp_read_status: ReadOnly::new(Pio::new(addr + 0x0E)),
|
||||
};
|
||||
|
||||
module.init()?;
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fn mixer_read(&mut self, index: u8) -> u8 {
|
||||
self.mixer_addr.write(index);
|
||||
self.mixer_data.read()
|
||||
}
|
||||
|
||||
fn mixer_write(&mut self, index: u8, value: u8) {
|
||||
self.mixer_addr.write(index);
|
||||
self.mixer_data.write(value);
|
||||
}
|
||||
|
||||
fn dsp_read(&mut self) -> Result<u8> {
|
||||
// Bit 7 must be 1 before data can be sent
|
||||
while !self.dsp_read_status.readf(1 << 7) {
|
||||
//TODO: timeout!
|
||||
std::thread::yield_now();
|
||||
}
|
||||
|
||||
Ok(self.dsp_read_data.read())
|
||||
}
|
||||
|
||||
fn dsp_write(&mut self, value: u8) -> Result<()> {
|
||||
// Bit 7 must be 0 before data can be sent
|
||||
while self.dsp_write_status.readf(1 << 7) {
|
||||
//TODO: timeout!
|
||||
std::thread::yield_now();
|
||||
}
|
||||
|
||||
self.dsp_write_data.write(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init(&mut self) -> Result<()> {
|
||||
// Perform DSP reset
|
||||
{
|
||||
// Write 1 to reset port
|
||||
self.dsp_reset.write(1);
|
||||
|
||||
// Wait 3us
|
||||
thread::sleep(time::Duration::from_micros(3));
|
||||
|
||||
// Write 0 to reset port
|
||||
self.dsp_reset.write(0);
|
||||
|
||||
//TODO: Wait for ready byte (0xAA) using read status
|
||||
thread::sleep(time::Duration::from_micros(100));
|
||||
|
||||
let ready = self.dsp_read()?;
|
||||
if ready != 0xAA {
|
||||
log::error!("ready byte was 0x{:02X} instead of 0xAA", ready);
|
||||
return Err(Error::new(ENODEV));
|
||||
}
|
||||
}
|
||||
|
||||
// Read DSP version
|
||||
{
|
||||
self.dsp_write(0xE1)?;
|
||||
|
||||
let major = self.dsp_read()?;
|
||||
let minor = self.dsp_read()?;
|
||||
log::info!("DSP version {}.{:02}", major, minor);
|
||||
|
||||
if major != 4 {
|
||||
log::error!("Unsupported DSP major version {}", major);
|
||||
return Err(Error::new(ENODEV));
|
||||
}
|
||||
}
|
||||
|
||||
// Get available IRQs and DMAs
|
||||
{
|
||||
self.irqs.clear();
|
||||
let irq_mask = self.mixer_read(0x80);
|
||||
if (irq_mask & (1 << 0)) != 0 {
|
||||
self.irqs.push(2);
|
||||
}
|
||||
if (irq_mask & (1 << 1)) != 0 {
|
||||
self.irqs.push(5);
|
||||
}
|
||||
if (irq_mask & (1 << 2)) != 0 {
|
||||
self.irqs.push(7);
|
||||
}
|
||||
if (irq_mask & (1 << 3)) != 0 {
|
||||
self.irqs.push(10);
|
||||
}
|
||||
|
||||
self.dmas.clear();
|
||||
let dma_mask = self.mixer_read(0x81);
|
||||
if (dma_mask & (1 << 0)) != 0 {
|
||||
self.dmas.push(0);
|
||||
}
|
||||
if (dma_mask & (1 << 1)) != 0 {
|
||||
self.dmas.push(1);
|
||||
}
|
||||
if (dma_mask & (1 << 3)) != 0 {
|
||||
self.dmas.push(3);
|
||||
}
|
||||
if (dma_mask & (1 << 5)) != 0 {
|
||||
self.dmas.push(5);
|
||||
}
|
||||
if (dma_mask & (1 << 6)) != 0 {
|
||||
self.dmas.push(6);
|
||||
}
|
||||
if (dma_mask & (1 << 7)) != 0 {
|
||||
self.dmas.push(7);
|
||||
}
|
||||
|
||||
log::info!("IRQs {:02X?} DMAs {:02X?}", self.irqs, self.dmas);
|
||||
}
|
||||
|
||||
// Set output sample rate to 44100 Hz (Redox OS standard)
|
||||
{
|
||||
let rate = 44100u16;
|
||||
self.dsp_write(0x41)?;
|
||||
self.dsp_write((rate >> 8) as u8)?;
|
||||
self.dsp_write(rate as u8)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn irq(&mut self) -> bool {
|
||||
//TODO
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for Sb16 {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.lock().insert(Handle::SchemeRoot))
|
||||
}
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
_path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
{
|
||||
let handles = self.handles.lock();
|
||||
let handle = handles.get(dirfd)?;
|
||||
if !matches!(handle, Handle::SchemeRoot) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
}
|
||||
if ctx.uid == 0 {
|
||||
let id = self.handles.lock().insert(Handle::Todo);
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: id,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(EACCES))
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
_id: usize,
|
||||
_buf: &[u8],
|
||||
_offset: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
//TODO
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||
FpathWriter::with(buf, "audiohw", |_| Ok(()))
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.lock().remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
use libredox::{flag, Fd};
|
||||
use redox_scheme::scheme::register_sync_scheme;
|
||||
use redox_scheme::Socket;
|
||||
use scheme_utils::ReadinessBased;
|
||||
use std::{env, usize};
|
||||
|
||||
use event::{user_data, EventQueue};
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub mod device;
|
||||
|
||||
fn main() {
|
||||
daemon::Daemon::new(daemon);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
let addr_str = args.next().unwrap_or("220".to_string());
|
||||
let addr = u16::from_str_radix(&addr_str, 16).expect("sb16: failed to parse address");
|
||||
|
||||
println!(" + sb16 at 0x{:X}\n", addr);
|
||||
|
||||
common::setup_logging(
|
||||
"audio",
|
||||
"pci",
|
||||
"sb16",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
common::acquire_port_io_rights().expect("sb16d: failed to acquire port IO rights");
|
||||
|
||||
let socket = Socket::nonblock().expect("sb16d: failed to create socket");
|
||||
let mut device = unsafe { device::Sb16::new(addr).expect("sb16d: failed to allocate device") };
|
||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
||||
|
||||
//TODO: error on multiple IRQs?
|
||||
let irq_file = match device.irqs.first() {
|
||||
Some(irq) => Fd::open(&format!("/scheme/irq/{}", irq), flag::O_RDWR, 0)
|
||||
.expect("sb16d: failed to open IRQ file"),
|
||||
None => panic!("sb16d: no IRQs found"),
|
||||
};
|
||||
user_data! {
|
||||
enum Source {
|
||||
Irq,
|
||||
Scheme,
|
||||
}
|
||||
}
|
||||
|
||||
let event_queue = EventQueue::<Source>::new().expect("sb16d: Could not create event queue.");
|
||||
event_queue
|
||||
.subscribe(irq_file.raw(), Source::Irq, event::EventFlags::READ)
|
||||
.unwrap();
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
register_sync_scheme(&socket, "sb16d", &mut device)
|
||||
.expect("sb16d: failed to register audiohw scheme to namespace");
|
||||
|
||||
daemon.ready();
|
||||
|
||||
libredox::call::setrens(0, 0).expect("sb16d: failed to enter null namespace");
|
||||
|
||||
let all = [Source::Irq, Source::Scheme];
|
||||
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("sb16d: failed to get next event").user_data))
|
||||
{
|
||||
match event {
|
||||
Source::Irq => {
|
||||
let mut irq = [0; 8];
|
||||
irq_file.read(&mut irq).unwrap();
|
||||
|
||||
if !device.irq() {
|
||||
continue;
|
||||
}
|
||||
irq_file.write(&mut irq).unwrap();
|
||||
|
||||
readiness_based
|
||||
.poll_all_requests(&mut device)
|
||||
.expect("sb16d: failed to poll requests");
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("sb16d: failed to write to socket");
|
||||
|
||||
/*
|
||||
let next_read = device_irq.next_read();
|
||||
if next_read > 0 {
|
||||
return Ok(Some(next_read));
|
||||
}
|
||||
*/
|
||||
}
|
||||
Source::Scheme => {
|
||||
readiness_based
|
||||
.read_and_process_requests(&mut device)
|
||||
.expect("sb16d: failed to read from socket");
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("sb16d: failed to write to socket");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "common"
|
||||
description = "Shared driver code library"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libredox.workspace = true
|
||||
log.workspace = true
|
||||
redox_syscall = { workspace = true, features = ["std"] }
|
||||
redox-log.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,265 +0,0 @@
|
||||
use std::mem::{self, size_of, MaybeUninit};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use libredox::call::MmapArgs;
|
||||
use libredox::{error::Result, flag, Fd};
|
||||
use syscall::PAGE_SIZE;
|
||||
|
||||
use crate::{memory_root_fd, MemoryType, VirtaddrTranslationHandle};
|
||||
|
||||
/// Defines the platform-specific memory type for DMA operations
|
||||
///
|
||||
/// - On x86 systems, DMA uses Write-back memory ([`MemoryType::Writeback`])
|
||||
/// - On aarch64 systems, DMA uses uncacheable memory ([`MemoryType::Uncacheable`])
|
||||
const DMA_MEMTY: MemoryType = {
|
||||
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
|
||||
// x86 ensures cache coherence with DMA memory
|
||||
MemoryType::Writeback
|
||||
} else if cfg!(target_arch = "aarch64") {
|
||||
// aarch64 currently must map DMA memory without caching to ensure coherence
|
||||
MemoryType::Uncacheable
|
||||
} else if cfg!(target_arch = "riscv64") {
|
||||
// FIXME check this out more
|
||||
MemoryType::Uncacheable
|
||||
} else {
|
||||
panic!("invalid arch")
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns a file descriptor for zeroized physically-contiguous DMA memory.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [Result] containing:
|
||||
/// - '[Ok]' - A [Fd] (file descriptor) to zeroized, physically continuous DMA usable memory
|
||||
/// - '[Err]' - The error returned by the provider of the /scheme/memory/zeroed scheme.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return an error in the following case:
|
||||
///
|
||||
/// - The request for the physical memory fails.
|
||||
pub(crate) fn phys_contiguous_fd() -> Result<Fd> {
|
||||
memory_root_fd().openat(
|
||||
&format!("zeroed@{DMA_MEMTY}?phys_contiguous"),
|
||||
flag::O_CLOEXEC,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Allocates a chunk of physical memory for DMA, and then maps it to virtual memory.
|
||||
///
|
||||
/// # Arguments
|
||||
/// 'length: [usize]' - The length of the memory region. Must be a multiple of [`PAGE_SIZE`]
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This function returns a [Result] containing the following:
|
||||
/// - A '[Ok]([usize], *[mut] ())' containing a Tuple of the physical address of the region, and a raw pointer to that region in virtual memory.
|
||||
/// - An '[Err]' - containing the error for the operation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function asserts if:
|
||||
/// - length is not a multiple of [`PAGE_SIZE`]
|
||||
///
|
||||
/// This function returns an error if:
|
||||
/// - A file descriptor to physically contiguous memory of type [`DMA_MEMTY`] could not be acquired
|
||||
/// - A virtual mapping for the physically contiguous memory could not be created
|
||||
/// - The virtual address returned by the memory manager was invalid.
|
||||
fn alloc_and_map(length: usize, handle: &VirtaddrTranslationHandle) -> Result<(usize, *mut ())> {
|
||||
assert_eq!(length % PAGE_SIZE, 0);
|
||||
unsafe {
|
||||
let fd = phys_contiguous_fd()?;
|
||||
let virt = libredox::call::mmap(MmapArgs {
|
||||
fd: fd.raw(),
|
||||
offset: 0, // ignored
|
||||
addr: core::ptr::null_mut(), // ignored
|
||||
length,
|
||||
flags: flag::MAP_PRIVATE,
|
||||
prot: flag::PROT_READ | flag::PROT_WRITE,
|
||||
})?;
|
||||
let phys = handle.translate(virt as usize)?;
|
||||
for i in 1..length.div_ceil(PAGE_SIZE) {
|
||||
debug_assert_eq!(
|
||||
handle.translate(virt as usize + i * PAGE_SIZE),
|
||||
Ok(phys + i * PAGE_SIZE),
|
||||
"NOT CONTIGUOUS"
|
||||
);
|
||||
}
|
||||
Ok((phys, virt as *mut ()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A safe accessor for DMA memory.
|
||||
pub struct Dma<T: ?Sized> {
|
||||
/// The physical address of the memory
|
||||
phys: usize,
|
||||
/// The page-aligned length of the memory. Will be a multiple of [`PAGE_SIZE`]
|
||||
aligned_len: usize,
|
||||
/// The pointer to the Dma memory in the virtual address space.
|
||||
virt: *mut T,
|
||||
}
|
||||
|
||||
impl<T> Dma<T> {
|
||||
/// [Dma] constructor that allocates and initializes a region of DMA memory with the page-aligned
|
||||
/// size and initial value of some T
|
||||
///
|
||||
/// # Arguments
|
||||
/// 'value: T' - The initial value to write to the allocated region
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This function returns a [Result] containing the following:
|
||||
///
|
||||
/// - A '[Ok] (`[Dma]<T>`)' containing the initialized region
|
||||
/// - An '[Err]' containing an error.
|
||||
pub fn new(value: T) -> Result<Self> {
|
||||
unsafe {
|
||||
let mut zeroed = Self::zeroed()?;
|
||||
zeroed.as_mut_ptr().write(value);
|
||||
Ok(zeroed.assume_init())
|
||||
}
|
||||
}
|
||||
|
||||
/// [Dma] constructor that allocates and zeroizes a memory region of the page-aligned size of T
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This function returns a [Result] containing the following:
|
||||
///
|
||||
/// - A '[Ok] (`[Dma]<[MaybeUninit]<T>>`)' containing the allocated and zeroized memory
|
||||
/// - An '[Err]' containing an error.
|
||||
pub fn zeroed() -> Result<Dma<MaybeUninit<T>>> {
|
||||
let aligned_len = size_of::<T>().next_multiple_of(PAGE_SIZE);
|
||||
let (phys, virt) = alloc_and_map(aligned_len, &*VIRTTOPHYS_HANDLE)?;
|
||||
Ok(Dma {
|
||||
phys,
|
||||
virt: virt.cast(),
|
||||
aligned_len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Dma<MaybeUninit<T>> {
|
||||
/// Assumes that possibly uninitialized DMA memory has been initialized, and returns a new
|
||||
/// instance of an object of type `[Dma]<T>`.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `[Dma]<T>` - The original structure without the [`MaybeUninit`] wrapper around its contents.
|
||||
///
|
||||
/// # Notes
|
||||
/// - This is unsafe because it assumes that the memory stored within the `[Dma]<T>` is a valid
|
||||
/// instance of T. If it isn't (for example -- if it was initialized with [`Dma::zeroed`]),
|
||||
/// then the underlying memory may not contain the expected T structure.
|
||||
pub unsafe fn assume_init(self) -> Dma<T> {
|
||||
let Dma {
|
||||
phys,
|
||||
aligned_len,
|
||||
virt,
|
||||
} = self;
|
||||
mem::forget(self);
|
||||
|
||||
Dma {
|
||||
phys,
|
||||
aligned_len,
|
||||
virt: virt.cast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: ?Sized> Dma<T> {
|
||||
/// Returns the physical address of the physical memory that this [Dma] structure references.
|
||||
///
|
||||
/// # Returns
|
||||
/// [usize] - The physical address of the memory.
|
||||
pub fn physical(&self) -> usize {
|
||||
self.phys
|
||||
}
|
||||
}
|
||||
// TODO: there should exist a "context" struct that drivers create at start, which would be passed
|
||||
// to the respective functions
|
||||
static VIRTTOPHYS_HANDLE: LazyLock<VirtaddrTranslationHandle> = LazyLock::new(|| {
|
||||
VirtaddrTranslationHandle::new().expect("failed to acquire virttophys translation handle")
|
||||
});
|
||||
|
||||
impl<T> Dma<[T]> {
|
||||
/// Returns a [Dma] object containing a zeroized slice of T with a given count.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - 'count: [usize]' - The number of elements of type T in the allocated slice.
|
||||
pub fn zeroed_slice(count: usize) -> Result<Dma<[MaybeUninit<T>]>> {
|
||||
let aligned_len = count
|
||||
.checked_mul(size_of::<T>())
|
||||
.unwrap()
|
||||
.next_multiple_of(PAGE_SIZE);
|
||||
let (phys, virt) = alloc_and_map(aligned_len, &*VIRTTOPHYS_HANDLE)?;
|
||||
|
||||
Ok(Dma {
|
||||
phys,
|
||||
aligned_len,
|
||||
virt: ptr::slice_from_raw_parts_mut(virt.cast(), count),
|
||||
})
|
||||
}
|
||||
|
||||
/// Casts the slice from type T to type U.
|
||||
///
|
||||
/// # Returns
|
||||
/// '`[DMA]<U>`' - A cast handle to the Dma memory.
|
||||
pub unsafe fn cast_slice<U>(self) -> Dma<[U]> {
|
||||
let Dma {
|
||||
phys,
|
||||
virt,
|
||||
aligned_len,
|
||||
} = self;
|
||||
core::mem::forget(self);
|
||||
|
||||
Dma {
|
||||
phys,
|
||||
virt: virt as *mut [U],
|
||||
aligned_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Dma<[MaybeUninit<T>]> {
|
||||
/// See [`Dma<MaybeUninit<T>>::assume_init`]
|
||||
pub unsafe fn assume_init(self) -> Dma<[T]> {
|
||||
let &Dma {
|
||||
phys,
|
||||
aligned_len,
|
||||
virt,
|
||||
} = &self;
|
||||
mem::forget(self);
|
||||
|
||||
Dma {
|
||||
phys,
|
||||
aligned_len,
|
||||
virt: virt as *mut [T],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for Dma<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
unsafe { &*self.virt }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> DerefMut for Dma<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
unsafe { &mut *self.virt }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Drop for Dma<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ptr::drop_in_place(self.virt);
|
||||
let _ = libredox::call::munmap(self.virt as *mut (), self.aligned_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
use core::{
|
||||
cmp::PartialEq,
|
||||
ops::{BitAnd, BitOr, Not},
|
||||
};
|
||||
|
||||
mod mmio;
|
||||
mod mmio_ptr;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod pio;
|
||||
|
||||
pub use mmio::*;
|
||||
pub use mmio_ptr::*;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub use pio::*;
|
||||
|
||||
/// IO abstraction
|
||||
pub trait Io {
|
||||
/// Value type for IO, usually some unsigned number
|
||||
type Value: Copy
|
||||
+ PartialEq
|
||||
+ BitAnd<Output = Self::Value>
|
||||
+ BitOr<Output = Self::Value>
|
||||
+ Not<Output = Self::Value>;
|
||||
|
||||
/// Read the underlying valu2e
|
||||
fn read(&self) -> Self::Value;
|
||||
/// Write the underlying value
|
||||
fn write(&mut self, value: Self::Value);
|
||||
|
||||
/// Check whether the underlying value contains bit flags
|
||||
#[inline(always)]
|
||||
fn readf(&self, flags: Self::Value) -> bool {
|
||||
(self.read() & flags) as Self::Value == flags
|
||||
}
|
||||
|
||||
/// Enable or disable specific bit flags
|
||||
#[inline(always)]
|
||||
fn writef(&mut self, flags: Self::Value, value: bool) {
|
||||
let tmp: Self::Value = match value {
|
||||
true => self.read() | flags,
|
||||
false => self.read() & !flags,
|
||||
};
|
||||
self.write(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only IO
|
||||
#[repr(transparent)]
|
||||
pub struct ReadOnly<I> {
|
||||
inner: I,
|
||||
}
|
||||
|
||||
impl<I: Io> ReadOnly<I> {
|
||||
/// Wraps IO
|
||||
pub const fn new(inner: I) -> ReadOnly<I> {
|
||||
ReadOnly { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Io> ReadOnly<I> {
|
||||
/// Calls [`Io::read`]
|
||||
#[inline(always)]
|
||||
pub fn read(&self) -> I::Value {
|
||||
self.inner.read()
|
||||
}
|
||||
|
||||
/// Calls [`Io::readf`]
|
||||
#[inline(always)]
|
||||
pub fn readf(&self, flags: I::Value) -> bool {
|
||||
self.inner.readf(flags)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
/// Write-only IO
|
||||
pub struct WriteOnly<I> {
|
||||
inner: I,
|
||||
}
|
||||
|
||||
impl<I: Io> WriteOnly<I> {
|
||||
/// Wraps IO
|
||||
pub const fn new(inner: I) -> WriteOnly<I> {
|
||||
WriteOnly { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Io> WriteOnly<I> {
|
||||
/// Calls [`Io::write`]
|
||||
#[inline(always)]
|
||||
pub fn write(&mut self, value: I::Value) {
|
||||
self.inner.write(value)
|
||||
}
|
||||
|
||||
// writef requires read which is not valid when write-only
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
use core::{mem::MaybeUninit, ptr};
|
||||
|
||||
use super::Io;
|
||||
|
||||
/// MMIO abstraction
|
||||
#[repr(C, packed)]
|
||||
pub struct Mmio<T> {
|
||||
value: MaybeUninit<T>,
|
||||
}
|
||||
|
||||
impl<T> Mmio<T> {
|
||||
/// Creates a zeroed instance
|
||||
pub unsafe fn zeroed() -> Self {
|
||||
Self {
|
||||
value: MaybeUninit::zeroed(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an unitialized instance
|
||||
pub unsafe fn uninit() -> Self {
|
||||
Self {
|
||||
value: MaybeUninit::uninit(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new instance
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self {
|
||||
value: MaybeUninit::new(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generic implementation (WARNING: requires aligned pointers!)
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
impl<T> Io for Mmio<T>
|
||||
where
|
||||
T: Copy
|
||||
+ PartialEq
|
||||
+ core::ops::BitAnd<Output = T>
|
||||
+ core::ops::BitOr<Output = T>
|
||||
+ core::ops::Not<Output = T>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn read(&self) -> T {
|
||||
unsafe { ptr::read_volatile(ptr::addr_of!(self.value).cast::<T>()) }
|
||||
}
|
||||
|
||||
fn write(&mut self, value: T) {
|
||||
unsafe { ptr::write_volatile(ptr::addr_of_mut!(self.value).cast::<T>(), value) };
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u8 implementation
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl Io for Mmio<u8> {
|
||||
type Value = u8;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov {}, [{}]",
|
||||
out(reg_byte) value,
|
||||
in(reg) ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov [{}], {}",
|
||||
in(reg) ptr,
|
||||
in(reg_byte) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u16 implementation
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl Io for Mmio<u16> {
|
||||
type Value = u16;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov {:x}, [{}]",
|
||||
out(reg) value,
|
||||
in(reg) ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov [{}], {:x}",
|
||||
in(reg) ptr,
|
||||
in(reg) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u32 implementation
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl Io for Mmio<u32> {
|
||||
type Value = u32;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov {:e}, [{}]",
|
||||
out(reg) value,
|
||||
in(reg) ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov [{}], {:e}",
|
||||
in(reg) ptr,
|
||||
in(reg) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u64 implementation (x86_64 only)
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl Io for Mmio<u64> {
|
||||
type Value = u64;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov {:r}, [{}]",
|
||||
out(reg) value,
|
||||
in(reg) ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
||||
core::arch::asm!(
|
||||
"mov [{}], {:r}",
|
||||
in(reg) ptr,
|
||||
in(reg) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
use super::Io;
|
||||
|
||||
/// MMIO using pointer instead of wrapped type
|
||||
pub struct MmioPtr<T> {
|
||||
ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<T> MmioPtr<T> {
|
||||
//TODO: reads and writes are unsafe, not new.
|
||||
/// Creates a `MmioPtr`.
|
||||
pub unsafe fn new(ptr: *mut T) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Creates a const pointer from a `MmioPtr`.
|
||||
pub const fn as_ptr(&self) -> *const T {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Creates a mutable pointer from a `MmioPtr`.
|
||||
pub const fn as_mut_ptr(&mut self) -> *mut T {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
// Generic implementation (WARNING: requires aligned pointers!)
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
impl<T> Io for MmioPtr<T>
|
||||
where
|
||||
T: Copy
|
||||
+ PartialEq
|
||||
+ core::ops::BitAnd<Output = T>
|
||||
+ core::ops::BitOr<Output = T>
|
||||
+ core::ops::Not<Output = T>,
|
||||
{
|
||||
type Value = T;
|
||||
|
||||
fn read(&self) -> T {
|
||||
unsafe { core::ptr::read_volatile(self.ptr) }
|
||||
}
|
||||
|
||||
fn write(&mut self, value: T) {
|
||||
unsafe { core::ptr::write_volatile(self.ptr, value) };
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u8 implementation
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl Io for MmioPtr<u8> {
|
||||
type Value = u8;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
core::arch::asm!(
|
||||
"mov {}, [{}]",
|
||||
out(reg_byte) value,
|
||||
in(reg) self.ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"mov [{}], {}",
|
||||
in(reg) self.ptr,
|
||||
in(reg_byte) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u16 implementation
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl Io for MmioPtr<u16> {
|
||||
type Value = u16;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
core::arch::asm!(
|
||||
"mov {:x}, [{}]",
|
||||
out(reg) value,
|
||||
in(reg) self.ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"mov [{}], {:x}",
|
||||
in(reg) self.ptr,
|
||||
in(reg) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u32 implementation
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
impl Io for MmioPtr<u32> {
|
||||
type Value = u32;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
core::arch::asm!(
|
||||
"mov {:e}, [{}]",
|
||||
out(reg) value,
|
||||
in(reg) self.ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"mov [{}], {:e}",
|
||||
in(reg) self.ptr,
|
||||
in(reg) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x86 u64 implementation (x86_64 only)
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
impl Io for MmioPtr<u64> {
|
||||
type Value = u64;
|
||||
|
||||
fn read(&self) -> Self::Value {
|
||||
unsafe {
|
||||
let value: Self::Value;
|
||||
core::arch::asm!(
|
||||
"mov {:r}, [{}]",
|
||||
out(reg) value,
|
||||
in(reg) self.ptr
|
||||
);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, value: Self::Value) {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"mov [{}], {:r}",
|
||||
in(reg) self.ptr,
|
||||
in(reg) value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use core::{arch::asm, marker::PhantomData};
|
||||
|
||||
use super::Io;
|
||||
|
||||
/// Generic PIO
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Pio<T> {
|
||||
port: u16,
|
||||
value: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Pio<T> {
|
||||
/// Create a PIO from a given port
|
||||
pub const fn new(port: u16) -> Self {
|
||||
Pio::<T> {
|
||||
port,
|
||||
value: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read/Write for byte PIO
|
||||
impl Io for Pio<u8> {
|
||||
type Value = u8;
|
||||
|
||||
/// Read
|
||||
#[inline(always)]
|
||||
fn read(&self) -> u8 {
|
||||
let value: u8;
|
||||
unsafe {
|
||||
asm!("in al, dx", in("dx") self.port, out("al") value, options(nostack, nomem, preserves_flags));
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// Write
|
||||
#[inline(always)]
|
||||
fn write(&mut self, value: u8) {
|
||||
unsafe {
|
||||
asm!("out dx, al", in("dx") self.port, in("al") value, options(nostack, nomem, preserves_flags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read/Write for word PIO
|
||||
impl Io for Pio<u16> {
|
||||
type Value = u16;
|
||||
|
||||
/// Read
|
||||
#[inline(always)]
|
||||
fn read(&self) -> u16 {
|
||||
let value: u16;
|
||||
unsafe {
|
||||
asm!("in ax, dx", in("dx") self.port, out("ax") value, options(nostack, nomem, preserves_flags));
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// Write
|
||||
#[inline(always)]
|
||||
fn write(&mut self, value: u16) {
|
||||
unsafe {
|
||||
asm!("out dx, ax", in("dx") self.port, in("ax") value, options(nostack, nomem, preserves_flags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read/Write for doubleword PIO
|
||||
impl Io for Pio<u32> {
|
||||
type Value = u32;
|
||||
|
||||
/// Read
|
||||
#[inline(always)]
|
||||
fn read(&self) -> u32 {
|
||||
let value: u32;
|
||||
unsafe {
|
||||
asm!("in eax, dx", in("dx") self.port, out("eax") value, options(nostack, nomem, preserves_flags));
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
/// Write
|
||||
#[inline(always)]
|
||||
fn write(&mut self, value: u32) {
|
||||
unsafe {
|
||||
asm!("out dx, eax", in("dx") self.port, in("eax") value, options(nostack, nomem, preserves_flags));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
//! This crate provides various abstractions for use by all drivers in the Redox drivers repo.
|
||||
//!
|
||||
//! This includes direct memory access via [dma], and Scatter-Gather List support via [sgl]. It also
|
||||
//! provides various memory management structures for use with drivers, and some logging support.
|
||||
|
||||
use libredox::call::MmapArgs;
|
||||
use libredox::flag::{self, O_CLOEXEC, O_RDONLY, O_RDWR, O_WRONLY};
|
||||
use libredox::{
|
||||
errno::EINVAL,
|
||||
error::{Error, Result},
|
||||
Fd,
|
||||
};
|
||||
use syscall::{ProcSchemeVerb, PAGE_SIZE};
|
||||
|
||||
/// The Direct Memory Access (DMA) API for drivers
|
||||
pub mod dma;
|
||||
/// MMIO utilities
|
||||
pub mod io;
|
||||
mod logger;
|
||||
/// The Scatter Gather List (SGL) API for drivers.
|
||||
pub mod sgl;
|
||||
/// Low latency timeout for driver loops
|
||||
pub mod timeout;
|
||||
|
||||
pub use logger::{file_level, output_level, setup_logging};
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static MEMORY_ROOT_FD: OnceLock<libredox::Fd> = OnceLock::new();
|
||||
|
||||
/// Initializes a file descriptor to be used as the root memory for a driver.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if:
|
||||
/// - `libredox` is unable to open a file descriptor.
|
||||
/// - The memory root file descriptor has already been set (this function has already been called).
|
||||
pub fn init() {
|
||||
if MEMORY_ROOT_FD
|
||||
.set(
|
||||
libredox::Fd::open("/scheme/memory/scheme-root", 0, 0)
|
||||
.expect("drivers common: failed to open memory root fd"),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
panic!("drivers common: failed to set memory root fd");
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the memory root file descriptor.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if `init` has not already been called first.
|
||||
pub fn memory_root_fd() -> &'static libredox::Fd {
|
||||
MEMORY_ROOT_FD
|
||||
.get()
|
||||
.expect("drivers common: memory root fd not initialized. Please call `common::init` in your main function.")
|
||||
}
|
||||
|
||||
/// Specifies the write behavior for a specific region of memory
|
||||
///
|
||||
/// These types indicate to the driver how writes to a specific memory region are handled by the
|
||||
/// system. This usually refers to the caching behavior that the processor or I/O device responsible
|
||||
/// for that memory implements.
|
||||
///
|
||||
/// aarch64 and x86 have very different cache-coherency rules, so this API as written is likely
|
||||
/// not sufficient to describe the memory caching behavior in a cross-platform manner. As such,
|
||||
/// consider this API unstable.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum MemoryType {
|
||||
/// A region of memory that implements Write-back caching.
|
||||
///
|
||||
/// In write-back caching, the processor will first store data in its local cache, and then
|
||||
/// flush it to the actual storage location at regular intervals, or as applications access
|
||||
/// the data.
|
||||
Writeback,
|
||||
/// A region of memory that does not implement caching. Writes to these regions are immediate.
|
||||
Uncacheable,
|
||||
/// A region of memory that implements write combining.
|
||||
///
|
||||
/// Write combining memory regions store all writes in a temporary buffer called a Write
|
||||
/// Combine Buffer. Multiple writes to the location are stored in a single buffer, and then
|
||||
/// released to the memory location in an unspecified order. Write-Combine memory does not
|
||||
/// guarantee that the order at which you write to it is the order at which those writes are
|
||||
/// committed to memory.
|
||||
WriteCombining,
|
||||
/// Memory stored in an intermediate Write Combine Buffer and released later
|
||||
/// Memory-Mapped I/O. This is an aarch64-specific term.
|
||||
DeviceMemory,
|
||||
}
|
||||
impl Default for MemoryType {
|
||||
fn default() -> Self {
|
||||
Self::Writeback
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the protection level of an area of memory.
|
||||
///
|
||||
/// This structure shouldn't be used directly -- instead, use the [`Prot::RO`] (Read-Only),
|
||||
/// [`Prot::WO`] (Write-Only) and [`Prot::RW`] (Read-Write) constants to specify the memory's protection
|
||||
/// level.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Prot {
|
||||
/// The memory is readable
|
||||
pub read: bool,
|
||||
/// The memory is writeable
|
||||
pub write: bool,
|
||||
}
|
||||
|
||||
/// Implements the memory protection level constants
|
||||
impl Prot {
|
||||
/// A constant representing Read-Only memory.
|
||||
pub const RO: Self = Self {
|
||||
read: true,
|
||||
write: false,
|
||||
};
|
||||
|
||||
/// A constant representing Write-Only memory
|
||||
pub const WO: Self = Self {
|
||||
read: false,
|
||||
write: true,
|
||||
};
|
||||
|
||||
/// A constant representing Read-Write memory
|
||||
pub const RW: Self = Self {
|
||||
read: true,
|
||||
write: true,
|
||||
};
|
||||
}
|
||||
|
||||
/// Maps physical memory to virtual memory
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * '`base_phys`: [usize]' - The base address of the physical memory to map.
|
||||
/// * 'len: [usize]' - The length of the physical memory to map (Should be a multiple of [`PAGE_SIZE`]
|
||||
/// * '_: [Prot]' - The memory protection level of the mapping.
|
||||
/// * 'type: [`MemoryType`]' - The caching behavior specification of the memory.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A '[Result]<*mut ()>' which is:
|
||||
/// - '[Ok]' containing a raw pointer to the mapped memory.
|
||||
/// - '[Err]' which contains an error on failure.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if:
|
||||
/// - An invalid value is provided to 'read' or 'write'
|
||||
/// - The system could not open a file descriptor to the memory scheme for the specified [`MemoryType`].
|
||||
/// - The system failed to map the physical address to a virtual address. See [`libredox::call::mmap`]
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Safe, as the kernel ensures it doesn't conflict with any other memory described in the memory
|
||||
/// map for regular RAM.
|
||||
///
|
||||
/// # Notes
|
||||
/// - This function is unsafe, and upon using it you will be responsible for freeing the memory with
|
||||
/// [`libredox::call::munmap`]. If you want a safe accessor, use [`PhysBorrowed`] instead.
|
||||
/// - The `MemoryType` specified is used to tell the function which memory scheme to access. (i.e
|
||||
/// /scheme/memory/physical@wb, /scheme/memory/physical@uc, etc).
|
||||
pub unsafe fn physmap(
|
||||
base_phys: usize,
|
||||
len: usize,
|
||||
Prot { read, write }: Prot,
|
||||
ty: MemoryType,
|
||||
) -> Result<*mut ()> {
|
||||
// TODO: arraystring?
|
||||
|
||||
//Return an error rather than potentially crash the kernel.
|
||||
if base_phys == 0 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
|
||||
let path = format!(
|
||||
"physical@{}",
|
||||
match ty {
|
||||
MemoryType::Writeback => "wb",
|
||||
MemoryType::Uncacheable => "uc",
|
||||
MemoryType::WriteCombining => "wc",
|
||||
MemoryType::DeviceMemory => "dev",
|
||||
}
|
||||
);
|
||||
let mode = match (read, write) {
|
||||
(true, true) => O_RDWR,
|
||||
(true, false) => O_RDONLY,
|
||||
(false, true) => O_WRONLY,
|
||||
(false, false) => return Err(Error::new(EINVAL)),
|
||||
};
|
||||
let mut prot = 0;
|
||||
if read {
|
||||
prot |= flag::PROT_READ;
|
||||
}
|
||||
if write {
|
||||
prot |= flag::PROT_WRITE;
|
||||
}
|
||||
|
||||
let fd = memory_root_fd().openat(&path, O_CLOEXEC | mode, 0)?;
|
||||
Ok(libredox::call::mmap(MmapArgs {
|
||||
fd: fd.raw(),
|
||||
offset: base_phys as u64,
|
||||
length: len.next_multiple_of(PAGE_SIZE),
|
||||
flags: flag::MAP_SHARED,
|
||||
prot,
|
||||
addr: core::ptr::null_mut(),
|
||||
})? as *mut ())
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MemoryType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Writeback => "wb",
|
||||
Self::Uncacheable => "uc",
|
||||
Self::WriteCombining => "wc",
|
||||
Self::DeviceMemory => "dev",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A safe virtual mapping to physical memory that unmaps the memory when the structure goes out
|
||||
/// of scope.
|
||||
///
|
||||
/// This function provides a safe binding to [physmap]. It implements Drop to free the mapped memory
|
||||
/// when the structure goes out of scope.
|
||||
pub struct PhysBorrowed {
|
||||
mem: *mut (),
|
||||
len: usize,
|
||||
}
|
||||
impl PhysBorrowed {
|
||||
/// Constructs a `PhysBorrowed` instance.
|
||||
///
|
||||
/// # Arguments
|
||||
/// See [physmap] for a description of the parameters.
|
||||
///
|
||||
/// # Returns
|
||||
/// A '[Result]' which contains the following:
|
||||
/// - A '[`PhysBorrowed`]' which represents the newly mapped region.
|
||||
/// - An 'Err' if a memory mapping error occurs.
|
||||
///
|
||||
/// # Errors
|
||||
/// See [physmap] for a description of the error cases.
|
||||
pub fn map(base_phys: usize, len: usize, prot: Prot, ty: MemoryType) -> Result<Self> {
|
||||
let mem = unsafe { physmap(base_phys, len, prot, ty)? };
|
||||
Ok(Self {
|
||||
mem,
|
||||
len: len.next_multiple_of(PAGE_SIZE),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets a raw pointer to the borrowed region.
|
||||
///
|
||||
/// # Returns
|
||||
/// - self.mem - A pointer to the mapped region in virtual memory.
|
||||
///
|
||||
/// # Notes
|
||||
/// - The pointer may live beyond the lifetime of [`PhysBorrowed`], so dereferences to the pointer
|
||||
/// must be treated as unsafe.
|
||||
///
|
||||
pub fn as_ptr(&self) -> *mut () {
|
||||
self.mem
|
||||
}
|
||||
|
||||
/// Gets the length of the mapped region.
|
||||
///
|
||||
/// # Returns
|
||||
/// - self.len - The length of the mapped region. It should be a multiple of [`PAGE_SIZE`]
|
||||
pub fn mapped_len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PhysBorrowed {
|
||||
/// Frees the mapped memory region.
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = libredox::call::munmap(self.mem, self.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Instructs the kernel to enable I/O ports for this (usermode) process (x86-specific).
|
||||
///
|
||||
/// On Redox, x86 privilege ring 3 represents userspace. Most Redox drivers run in userspace to
|
||||
/// prevent system instability caused by a faulty driver. Processes with (bitmap-enabled) IO port
|
||||
/// rights can use the IN/OUT instructions. This is not the same as IOPL 3; the CLI instruction is
|
||||
/// still not allowed.
|
||||
pub fn acquire_port_io_rights() -> Result<()> {
|
||||
extern "C" {
|
||||
fn redox_cur_thrfd_v0() -> usize;
|
||||
}
|
||||
let kernel_fd = syscall::dup(unsafe { redox_cur_thrfd_v0() }, b"open_via_dup")?;
|
||||
let res = libredox::call::call_wo(
|
||||
kernel_fd,
|
||||
&[],
|
||||
syscall::CallFlags::empty(),
|
||||
&[ProcSchemeVerb::Iopl as u64],
|
||||
);
|
||||
let _ = syscall::close(kernel_fd);
|
||||
res?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Kernel handle for translating virtual addresses in the current address space, to their
|
||||
/// underlying physical addresses.
|
||||
///
|
||||
/// It is currently unspecified whether this handle is specific to the address space at the time it
|
||||
/// was created, or whether all calls reference the currently active address space.
|
||||
pub struct VirtaddrTranslationHandle {
|
||||
fd: Fd,
|
||||
}
|
||||
|
||||
impl VirtaddrTranslationHandle {
|
||||
/// Create a new handle, requires uid=0 but this may change.
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
fd: memory_root_fd().openat("translation", O_CLOEXEC, 0)?,
|
||||
})
|
||||
}
|
||||
/// Translate physical => virtual.
|
||||
pub fn translate(&self, physical: usize) -> Result<usize> {
|
||||
let mut buf = physical.to_ne_bytes();
|
||||
libredox::call::call_ro(self.fd.raw(), &mut buf, syscall::CallFlags::empty(), &[])?;
|
||||
Ok(usize::from_ne_bytes(buf))
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use libredox::{flag, Fd};
|
||||
use redox_log::{OutputBuilder, RedoxLogger};
|
||||
|
||||
/// Get the log verbosity for the output level.
|
||||
pub fn output_level() -> log::LevelFilter {
|
||||
log::LevelFilter::Info
|
||||
}
|
||||
|
||||
/// Get the log verbosity for the file level.
|
||||
pub fn file_level() -> log::LevelFilter {
|
||||
log::LevelFilter::Info
|
||||
}
|
||||
|
||||
/// Configures logging for a single driver.
|
||||
#[cfg_attr(not(target_os = "redox"), allow(unused_variables, unused_mut))]
|
||||
pub fn setup_logging(
|
||||
category: &str,
|
||||
subcategory: &str,
|
||||
logfile_base: &str,
|
||||
mut output_level: log::LevelFilter,
|
||||
file_level: log::LevelFilter,
|
||||
) {
|
||||
RedoxLogger::init_timezone();
|
||||
if let Some(log_level) = read_bootloader_log_level_env(category, subcategory) {
|
||||
output_level = log_level;
|
||||
}
|
||||
|
||||
let mut logger = RedoxLogger::new().with_output(
|
||||
OutputBuilder::stderr()
|
||||
.with_filter(output_level) // limit global output to important info
|
||||
.with_ansi_escape_codes()
|
||||
.flush_on_newline(true)
|
||||
.build(),
|
||||
);
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
match OutputBuilder::in_redox_logging_scheme(
|
||||
category,
|
||||
subcategory,
|
||||
format!("{logfile_base}.log"),
|
||||
) {
|
||||
Ok(b) => {
|
||||
logger = logger.with_output(b.with_filter(file_level).flush_on_newline(true).build())
|
||||
}
|
||||
Err(error) => eprintln!("Failed to create {logfile_base}.log: {}", error),
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
match OutputBuilder::in_redox_logging_scheme(
|
||||
category,
|
||||
subcategory,
|
||||
format!("{logfile_base}.ansi.log"),
|
||||
) {
|
||||
Ok(b) => {
|
||||
logger = logger.with_output(
|
||||
b.with_filter(file_level)
|
||||
.with_ansi_escape_codes()
|
||||
.flush_on_newline(true)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
Err(error) => eprintln!("Failed to create {logfile_base}.ansi.log: {}", error),
|
||||
}
|
||||
|
||||
logger.enable().expect("failed to set default logger");
|
||||
}
|
||||
|
||||
fn read_bootloader_log_level_env(category: &str, subcategory: &str) -> Option<log::LevelFilter> {
|
||||
let mut env_bytes = [0_u8; 4096];
|
||||
|
||||
// TODO: Have the kernel env can specify prefixed env key instead of having to read all of them
|
||||
let envs = {
|
||||
let Ok(fd) = Fd::open("/scheme/sys/env", flag::O_RDONLY | flag::O_CLOEXEC, 0) else {
|
||||
return None;
|
||||
};
|
||||
let Ok(bytes_read) = fd.read(&mut env_bytes) else {
|
||||
return None;
|
||||
};
|
||||
if bytes_read >= env_bytes.len() {
|
||||
return None;
|
||||
}
|
||||
let env_bytes = &mut env_bytes[..bytes_read];
|
||||
|
||||
env_bytes
|
||||
.split(|&c| c == b'\n')
|
||||
.filter(|var| var.starts_with(b"DRIVER_"))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let log_env_keys = [
|
||||
format!("DRIVER_{}_LOG_LEVEL=", subcategory.to_ascii_uppercase()),
|
||||
format!("DRIVER_{}_LOG_LEVEL=", category.to_ascii_uppercase()),
|
||||
"DRIVER_LOG_LEVEL=".to_string(),
|
||||
];
|
||||
|
||||
for log_env_key in log_env_keys {
|
||||
let log_env_key = log_env_key.as_bytes();
|
||||
if let Some(log_env) = envs.iter().find_map(|var| var.strip_prefix(log_env_key)) {
|
||||
if let Ok(Ok(log_level)) = str::from_utf8(&log_env).map(log::LevelFilter::from_str) {
|
||||
return Some(log_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use libredox::call::MmapArgs;
|
||||
use libredox::errno::EINVAL;
|
||||
use libredox::error::{Error, Result};
|
||||
use libredox::flag::{MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
|
||||
use syscall::{MAP_FIXED, PAGE_SIZE};
|
||||
|
||||
use crate::dma::phys_contiguous_fd;
|
||||
use crate::VirtaddrTranslationHandle;
|
||||
|
||||
/// A Scatter-Gather List data structure
|
||||
///
|
||||
/// See: <https://en.wikipedia.org/wiki/Gather/scatter_(vector_addressing)>
|
||||
#[derive(Debug)]
|
||||
pub struct Sgl {
|
||||
/// A raw pointer to the SGL in virtual memory
|
||||
virt: *mut u8,
|
||||
/// The length of the allocated memory, guaranteed to be a multiple of [`PAGE_SIZE`].
|
||||
aligned_length: usize,
|
||||
/// The length of the allocated memory. This value is NOT guaranteed to be a multiple of [`PAGE_SIZE`]
|
||||
unaligned_length: NonZeroUsize,
|
||||
/// The vector of chunks tracked by this [Sgl] object. This is the sparsely-populated vector in the SGL algorithm.
|
||||
chunks: Vec<Chunk>,
|
||||
}
|
||||
|
||||
/// A structure representing a chunk of memory in the sparsely-populated vector of the SGL
|
||||
#[derive(Debug)]
|
||||
pub struct Chunk {
|
||||
/// The offset of the chunk in the sparsely-populated vector.
|
||||
pub offset: usize,
|
||||
/// The physical address of the chunk
|
||||
pub phys: usize,
|
||||
/// A raw pointer to the chunk in virtual memory
|
||||
pub virt: *mut u8,
|
||||
/// The length of the chunk in bytes.
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
impl Sgl {
|
||||
/// Constructor for the scatter/gather list.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// '`unaligned_length`: [usize]' - The length of the SGL, not necessarily aligned to the nearest
|
||||
/// page.
|
||||
pub fn new(unaligned_length: usize) -> Result<Self> {
|
||||
let unaligned_length = NonZeroUsize::new(unaligned_length).ok_or(Error::new(EINVAL))?;
|
||||
|
||||
// TODO: Both PAGE_SIZE and MAX_ALLOC_SIZE should be dynamic.
|
||||
let aligned_length = unaligned_length.get().next_multiple_of(PAGE_SIZE);
|
||||
const MAX_ALLOC_SIZE: usize = 1 << 22;
|
||||
|
||||
unsafe {
|
||||
let virt = libredox::call::mmap(MmapArgs {
|
||||
flags: MAP_PRIVATE,
|
||||
prot: PROT_NONE,
|
||||
length: aligned_length,
|
||||
|
||||
offset: 0,
|
||||
fd: !0,
|
||||
addr: core::ptr::null_mut(),
|
||||
})?
|
||||
.cast::<u8>();
|
||||
|
||||
let mut this = Self {
|
||||
virt,
|
||||
aligned_length,
|
||||
unaligned_length,
|
||||
chunks: Vec::new(),
|
||||
};
|
||||
|
||||
// TODO: SglContext to avoid reopening these fds?
|
||||
let phys_contiguous_fd = phys_contiguous_fd()?;
|
||||
let virttophys_handle = VirtaddrTranslationHandle::new()?;
|
||||
|
||||
let mut offset = 0;
|
||||
while offset < aligned_length {
|
||||
let preferred_chunk_length = (aligned_length - offset)
|
||||
.min(MAX_ALLOC_SIZE)
|
||||
.next_power_of_two();
|
||||
let chunk_length = if preferred_chunk_length > aligned_length - offset {
|
||||
preferred_chunk_length / 2
|
||||
} else {
|
||||
preferred_chunk_length
|
||||
};
|
||||
libredox::call::mmap(MmapArgs {
|
||||
addr: virt.add(offset).cast(),
|
||||
flags: MAP_PRIVATE | (MAP_FIXED.bits() as u32),
|
||||
prot: PROT_READ | PROT_WRITE,
|
||||
length: chunk_length,
|
||||
fd: phys_contiguous_fd.raw(),
|
||||
|
||||
offset: 0,
|
||||
})?;
|
||||
let phys = virttophys_handle.translate(virt as usize + offset)?;
|
||||
this.chunks.push(Chunk {
|
||||
offset,
|
||||
phys,
|
||||
length: (unaligned_length.get() - offset).min(chunk_length),
|
||||
virt: virt.add(offset),
|
||||
});
|
||||
offset += chunk_length;
|
||||
}
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
/// Returns an immutable reference to the vector of chunks
|
||||
pub fn chunks(&self) -> &[Chunk] {
|
||||
&self.chunks
|
||||
}
|
||||
|
||||
/// Returns a raw pointer to the vector of chunks in virtual memory
|
||||
pub fn as_ptr(&self) -> *mut u8 {
|
||||
self.virt
|
||||
}
|
||||
/// Returns the length of the scatter-gather list.
|
||||
pub fn len(&self) -> usize {
|
||||
self.unaligned_length.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Sgl {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = libredox::call::munmap(self.virt.cast(), self.aligned_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Represents an amount of time for a driver to give up to the OS scheduler.
|
||||
pub struct Timeout {
|
||||
instant: Instant,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
impl Timeout {
|
||||
/// Create a new `Timeout` from a `Duration`.
|
||||
#[inline]
|
||||
pub fn new(duration: Duration) -> Self {
|
||||
Self {
|
||||
instant: Instant::now(),
|
||||
duration,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Timeout` by specifying the amount of microseconds.
|
||||
#[inline]
|
||||
pub fn from_micros(micros: u64) -> Self {
|
||||
Self::new(Duration::from_micros(micros))
|
||||
}
|
||||
|
||||
/// Create a new `Timeout` by specifying the amount of milliseconds.
|
||||
#[inline]
|
||||
pub fn from_millis(millis: u64) -> Self {
|
||||
Self::new(Duration::from_millis(millis))
|
||||
}
|
||||
|
||||
/// Create a new `Timeout` by specifying the amount of seconds.
|
||||
#[inline]
|
||||
pub fn from_secs(secs: u64) -> Self {
|
||||
Self::new(Duration::from_secs(secs))
|
||||
}
|
||||
|
||||
/// Execute the `Timeout`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an `Err` if the duration of the `Timeout` has already elapsed
|
||||
/// between creating the `Timeout` and calling this function.
|
||||
#[inline]
|
||||
pub fn run(&self) -> Result<(), ()> {
|
||||
if self.instant.elapsed() < self.duration {
|
||||
// Sleeps in Redox are only evaluated on PIT ticks (a few ms), which is not
|
||||
// short enough for a reasonably responsive timeout. However, the clock is
|
||||
// highly accurate. So, we yield instead of sleep to reduce latency.
|
||||
//TODO: allow timeout that spins instead of yields?
|
||||
std::thread::yield_now();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "executor"
|
||||
description = "Asynchronous framework for queue-based hardware interfaces"
|
||||
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
redox_event.workspace = true
|
||||
slab.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,396 +0,0 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use std::future::{Future, IntoFuture};
|
||||
use std::hash::Hash;
|
||||
use std::io::{Read, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::pin::Pin;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::task;
|
||||
|
||||
use event::{EventFlags, RawEventQueue};
|
||||
use slab::Slab;
|
||||
|
||||
type EventUserData = usize;
|
||||
|
||||
type FutIdx = usize;
|
||||
|
||||
pub trait Hardware: Sized {
|
||||
type CmdId: Clone + Copy + Debug + Hash + Eq + PartialEq;
|
||||
type CqId: Clone + Copy + Debug + Hash + Eq + PartialEq;
|
||||
type SqId: Clone + Copy + Debug + Hash + Eq + PartialEq;
|
||||
type Sqe: Debug + Clone + Copy;
|
||||
type Cqe;
|
||||
type Iv: Clone + Copy + Debug;
|
||||
|
||||
type GlobalCtxt;
|
||||
|
||||
// TODO: the kernel should also do this automatically before sending EOI messages to the IC
|
||||
fn mask_vector(ctxt: &Self::GlobalCtxt, iv: Self::Iv);
|
||||
fn unmask_vector(ctxt: &Self::GlobalCtxt, iv: Self::Iv);
|
||||
|
||||
fn set_sqe_cmdid(sqe: &mut Self::Sqe, id: Self::CmdId);
|
||||
fn get_cqe_cmdid(cqe: &Self::Cqe) -> Self::CmdId;
|
||||
|
||||
// TODO: support multiple SQs per CQ or vice versa?
|
||||
fn sq_cq(ctxt: &Self::GlobalCtxt, id: Self::CqId) -> Self::SqId;
|
||||
|
||||
fn current() -> Rc<LocalExecutor<Self>>;
|
||||
fn vtable() -> &'static task::RawWakerVTable;
|
||||
|
||||
fn try_submit(
|
||||
ctxt: &Self::GlobalCtxt,
|
||||
sq_id: Self::SqId,
|
||||
success: impl FnOnce(Self::CmdId) -> Self::Sqe,
|
||||
fail: impl FnOnce(),
|
||||
) -> Option<(Self::CqId, Self::CmdId)>;
|
||||
fn poll_cqes(ctxt: &Self::GlobalCtxt, handle: impl FnMut(Self::CqId, Self::Cqe));
|
||||
}
|
||||
|
||||
/// Async executor, single IV, thread-per-core architecture
|
||||
pub struct LocalExecutor<Hw: Hardware> {
|
||||
global_ctxt: Hw::GlobalCtxt,
|
||||
|
||||
queue: RawEventQueue,
|
||||
vector: Hw::Iv,
|
||||
irq_handle: File,
|
||||
intx: bool,
|
||||
|
||||
// TODO: One IV and SQ/CQ per core (where the admin queue can be managed by the main thread).
|
||||
awaiting_submission: RefCell<HashMap<Hw::SqId, VecDeque<FutIdx>>>,
|
||||
awaiting_completion:
|
||||
RefCell<HashMap<Hw::CqId, HashMap<Hw::CmdId, (FutIdx, NonNull<Option<Hw::Cqe>>)>>>,
|
||||
|
||||
external_event: RefCell<HashMap<EventUserData, (FutIdx, NonNull<EventFlags>)>>,
|
||||
next_user_data: Cell<usize>,
|
||||
|
||||
ready_futures: RefCell<VecDeque<FutIdx>>,
|
||||
futures: RefCell<Slab<Pin<Box<dyn Future<Output = ()> + 'static>>>>,
|
||||
is_polling: Cell<bool>,
|
||||
}
|
||||
|
||||
impl<Hw: Hardware> LocalExecutor<Hw> {
|
||||
pub fn register_external_event(
|
||||
&self,
|
||||
fd: usize,
|
||||
flags: event::EventFlags,
|
||||
) -> ExternalEventSource<Hw> {
|
||||
let user_data = self.next_user_data.get();
|
||||
self.next_user_data.set(user_data.checked_add(1).unwrap());
|
||||
|
||||
self.queue
|
||||
.subscribe(fd, user_data, flags)
|
||||
.expect("failed to subscribe to event");
|
||||
|
||||
ExternalEventSource {
|
||||
flags: event::EventFlags::empty(),
|
||||
user_data,
|
||||
_not_send_or_unpin: PhantomData,
|
||||
}
|
||||
}
|
||||
pub fn current() -> Rc<Self> {
|
||||
Hw::current()
|
||||
}
|
||||
pub fn poll(&self) -> usize {
|
||||
assert!(!self.is_polling.replace(true));
|
||||
|
||||
let mut finished = 0;
|
||||
|
||||
for future_idx in self.ready_futures.borrow_mut().drain(..) {
|
||||
let waker = waker::<Hw>(future_idx);
|
||||
|
||||
let mut futures = self.futures.borrow_mut();
|
||||
let res = match std::panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
futures[future_idx]
|
||||
.as_mut()
|
||||
.poll(&mut task::Context::from_waker(&waker))
|
||||
})) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
log::error!("Task panicked!");
|
||||
core::mem::forget(futures.remove(future_idx));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if res.is_ready() {
|
||||
drop(futures.remove(future_idx));
|
||||
finished += 1;
|
||||
}
|
||||
}
|
||||
self.is_polling.set(false);
|
||||
|
||||
finished
|
||||
}
|
||||
pub fn spawn(&self, fut: impl IntoFuture<Output = ()> + 'static) {
|
||||
let idx = self
|
||||
.futures
|
||||
.borrow_mut()
|
||||
.insert(Box::pin(fut.into_future()));
|
||||
self.ready_futures.borrow_mut().push_back(idx);
|
||||
}
|
||||
pub fn block_on<'a, O: 'a>(&self, fut: impl IntoFuture<Output = O> + 'a) -> O {
|
||||
let retval = Rc::new(RefCell::new(None));
|
||||
|
||||
let retval2 = Rc::clone(&retval);
|
||||
let idx = self.futures.borrow_mut().insert({
|
||||
let t1: Pin<Box<dyn Future<Output = ()> + 'a>> = Box::pin(async move {
|
||||
*retval2.borrow_mut() = Some(fut.await);
|
||||
});
|
||||
// SAFETY: Apart from the lifetimes, the types are exactly the same. We also know
|
||||
// block_on simply cannot return without having fully awaited and dropped the future,
|
||||
// even if that future panics (cf. the catch_unwind invocation).
|
||||
let t2: Pin<Box<dyn Future<Output = ()> + 'static>> =
|
||||
unsafe { std::mem::transmute(t1) };
|
||||
|
||||
t2
|
||||
});
|
||||
|
||||
self.ready_futures.borrow_mut().push_front(idx);
|
||||
|
||||
loop {
|
||||
let finished = self.poll();
|
||||
if retval.borrow().is_some() {
|
||||
break;
|
||||
}
|
||||
if finished == 0 {
|
||||
self.react();
|
||||
}
|
||||
}
|
||||
|
||||
let o = retval.borrow_mut().take().unwrap();
|
||||
o
|
||||
}
|
||||
fn react(&self) {
|
||||
let event = self.queue.next_event().expect("failed to get next event");
|
||||
|
||||
if event.user_data != 0 {
|
||||
let Some((fut_idx, flags_ptr)) =
|
||||
self.external_event.borrow_mut().remove(&event.user_data)
|
||||
else {
|
||||
// Spurious event
|
||||
return;
|
||||
};
|
||||
unsafe {
|
||||
flags_ptr
|
||||
.as_ptr()
|
||||
.write(event::EventFlags::from_bits_retain(event.flags));
|
||||
}
|
||||
self.ready_futures.borrow_mut().push_back(fut_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.intx {
|
||||
let mut buf = [0_u8; core::mem::size_of::<usize>()];
|
||||
if (&self.irq_handle).read(&mut buf).unwrap() != 0 {
|
||||
(&self.irq_handle).write(&buf).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The kernel should probably do the masking (when using MSI/MSI-X at least), which
|
||||
// should happen before EOI messages to the interrupt controller.
|
||||
Hw::mask_vector(&self.global_ctxt, self.vector);
|
||||
Hw::poll_cqes(&self.global_ctxt, |cq_id, cqe| {
|
||||
if let Some((fut_idx, comp_ptr)) = self
|
||||
.awaiting_completion
|
||||
.borrow_mut()
|
||||
.get_mut(&cq_id)
|
||||
.and_then(|per_cmd| per_cmd.remove(&Hw::get_cqe_cmdid(&cqe)))
|
||||
{
|
||||
unsafe {
|
||||
comp_ptr.as_ptr().write(Some(cqe));
|
||||
}
|
||||
self.ready_futures.borrow_mut().push_back(fut_idx);
|
||||
|
||||
if let Some(submitting) = self
|
||||
.awaiting_submission
|
||||
.borrow_mut()
|
||||
.get_mut(&Hw::sq_cq(&self.global_ctxt, cq_id))
|
||||
.and_then(|q| q.pop_front())
|
||||
{
|
||||
self.ready_futures.borrow_mut().push_back(submitting);
|
||||
}
|
||||
}
|
||||
});
|
||||
Hw::unmask_vector(&self.global_ctxt, self.vector);
|
||||
}
|
||||
pub async fn submit(&self, sq_id: Hw::SqId, cmd: Hw::Sqe) -> Hw::Cqe {
|
||||
CqeFuture::<Hw> {
|
||||
state: State::<Hw>::Submitting { sq_id, cmd },
|
||||
comp: None,
|
||||
_not_send: PhantomData,
|
||||
}
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
struct CqeFuture<Hw: Hardware> {
|
||||
pub state: State<Hw>,
|
||||
pub comp: Option<Hw::Cqe>,
|
||||
pub _not_send: PhantomData<*const ()>,
|
||||
}
|
||||
enum State<Hw: Hardware> {
|
||||
Submitting { sq_id: Hw::SqId, cmd: Hw::Sqe },
|
||||
Completing { cq_id: Hw::CqId, cmd_id: Hw::CmdId },
|
||||
}
|
||||
|
||||
fn current_executor_and_idx<Hw: Hardware>(
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> (Rc<LocalExecutor<Hw>>, FutIdx) {
|
||||
let executor = LocalExecutor::current();
|
||||
|
||||
let idx = cx.waker().data() as FutIdx;
|
||||
assert_eq!(
|
||||
cx.waker().vtable() as *const _,
|
||||
Hw::vtable(),
|
||||
"incompatible executor for CqeFuture"
|
||||
);
|
||||
|
||||
(executor, idx)
|
||||
}
|
||||
|
||||
impl<Hw: Hardware> Future for CqeFuture<Hw> {
|
||||
type Output = Hw::Cqe;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
let (executor, idx) = current_executor_and_idx::<Hw>(cx);
|
||||
|
||||
match this.state {
|
||||
State::Submitting { sq_id, mut cmd } => {
|
||||
let mut awaiting = executor.awaiting_submission.borrow_mut();
|
||||
|
||||
if let Some((cq_id, cmd_id)) = Hw::try_submit(
|
||||
&executor.global_ctxt,
|
||||
sq_id,
|
||||
|cmd_id| {
|
||||
Hw::set_sqe_cmdid(&mut cmd, cmd_id);
|
||||
log::trace!("About to submit {cmd:?}");
|
||||
cmd
|
||||
},
|
||||
|| {
|
||||
awaiting.entry(sq_id).or_default().push_back(idx);
|
||||
},
|
||||
) {
|
||||
executor
|
||||
.awaiting_completion
|
||||
.borrow_mut()
|
||||
.entry(cq_id)
|
||||
.or_default()
|
||||
.insert(cmd_id, (idx, (&mut this.comp).into()));
|
||||
this.state = State::Completing { cq_id, cmd_id };
|
||||
}
|
||||
task::Poll::Pending
|
||||
}
|
||||
State::Completing { cq_id, cmd_id } => match this.comp.take() {
|
||||
Some(comp) => {
|
||||
log::trace!("ready!");
|
||||
task::Poll::Ready(comp)
|
||||
}
|
||||
|
||||
// Shouldn't technically be possible
|
||||
None => {
|
||||
log::trace!("spurious poll");
|
||||
executor
|
||||
.awaiting_completion
|
||||
.borrow_mut()
|
||||
.entry(cq_id)
|
||||
.or_default()
|
||||
.insert(cmd_id, (idx, (&mut this.comp).into()));
|
||||
task::Poll::Pending
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn vt_clone<Hw: Hardware>(idx: *const ()) -> task::RawWaker {
|
||||
task::RawWaker::new(idx, Hw::vtable())
|
||||
}
|
||||
unsafe fn vt_drop(_idx: *const ()) {}
|
||||
unsafe fn vt_wake<Hw: Hardware>(idx: *const ()) {
|
||||
Hw::current()
|
||||
.ready_futures
|
||||
.borrow_mut()
|
||||
.push_back(idx as FutIdx);
|
||||
}
|
||||
|
||||
fn waker<Hw: Hardware>(idx: FutIdx) -> task::Waker {
|
||||
unsafe { task::Waker::from_raw(task::RawWaker::new(idx as *const (), Hw::vtable())) }
|
||||
}
|
||||
pub const fn vtable<Hw: Hardware>() -> task::RawWakerVTable {
|
||||
task::RawWakerVTable::new(vt_clone::<Hw>, vt_wake::<Hw>, vt_wake::<Hw>, vt_drop)
|
||||
}
|
||||
|
||||
pub struct ExternalEventSource<Hw: Hardware> {
|
||||
flags: event::EventFlags,
|
||||
user_data: EventUserData,
|
||||
_not_send_or_unpin: PhantomData<(*const (), fn() -> Hw)>,
|
||||
}
|
||||
pub struct Event {
|
||||
flags: event::EventFlags,
|
||||
_not_send: PhantomData<*const ()>,
|
||||
}
|
||||
impl Event {
|
||||
pub fn flags(&self) -> event::EventFlags {
|
||||
self.flags
|
||||
}
|
||||
}
|
||||
impl<Hw: Hardware> ExternalEventSource<Hw> {
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context) -> task::Poll<Option<Event>> {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
let flags = std::mem::take(&mut this.flags);
|
||||
|
||||
if flags.is_empty() {
|
||||
let (executor, idx) = current_executor_and_idx::<Hw>(cx);
|
||||
executor
|
||||
.external_event
|
||||
.borrow_mut()
|
||||
.insert(this.user_data, (idx, (&mut this.flags).into()));
|
||||
return task::Poll::Pending;
|
||||
}
|
||||
task::Poll::Ready(Some(Event {
|
||||
flags,
|
||||
_not_send: PhantomData,
|
||||
}))
|
||||
}
|
||||
pub async fn next(mut self: Pin<&mut Self>) -> Option<Event> {
|
||||
core::future::poll_fn(|cx| self.as_mut().poll_next(cx)).await
|
||||
}
|
||||
}
|
||||
pub fn init_raw<Hw: Hardware>(
|
||||
global_ctxt: Hw::GlobalCtxt,
|
||||
vector: Hw::Iv,
|
||||
intx: bool,
|
||||
irq_handle: File,
|
||||
) -> LocalExecutor<Hw> {
|
||||
let queue = RawEventQueue::new().expect("failed to allocate event queue for local executor");
|
||||
|
||||
// TODO: Multiple CPUs
|
||||
queue
|
||||
.subscribe(irq_handle.as_raw_fd() as usize, 0, EventFlags::READ)
|
||||
.expect("failed to subscribe to IRQ event");
|
||||
|
||||
LocalExecutor {
|
||||
global_ctxt,
|
||||
|
||||
queue,
|
||||
vector,
|
||||
intx,
|
||||
irq_handle,
|
||||
|
||||
awaiting_submission: RefCell::new(HashMap::new()),
|
||||
awaiting_completion: RefCell::new(HashMap::new()),
|
||||
external_event: RefCell::new(HashMap::new()),
|
||||
next_user_data: Cell::new(1),
|
||||
ready_futures: RefCell::new(VecDeque::new()),
|
||||
futures: RefCell::new(Slab::with_capacity(16)),
|
||||
is_polling: Cell::new(false),
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "gpiod"
|
||||
description = "GPIO controller registry daemon"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
redox_syscall = { workspace = true, features = ["std"] }
|
||||
libredox.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
ron.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,496 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::process;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult, Socket};
|
||||
use scheme_utils::{Blocking, HandleMap};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct GpioControllerInfo {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub pin_count: usize,
|
||||
pub supports_interrupt: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum GpioControlRequest {
|
||||
RegisterController { info: GpioControllerInfo },
|
||||
ReadPin { controller_id: u32, pin: u32 },
|
||||
WritePin { controller_id: u32, pin: u32, value: bool },
|
||||
ConfigurePin { controller_id: u32, pin: u32, config: PinConfig },
|
||||
ListControllers,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PinConfig {
|
||||
pub direction: PinDirection,
|
||||
pub pull: PullMode,
|
||||
pub interrupt_mode: Option<InterruptMode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PinDirection {
|
||||
Input,
|
||||
Output,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PullMode {
|
||||
None,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum InterruptMode {
|
||||
EdgeRising,
|
||||
EdgeFalling,
|
||||
EdgeBoth,
|
||||
LevelHigh,
|
||||
LevelLow,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
enum GpioControlResponse {
|
||||
ControllerRegistered { id: u32 },
|
||||
Controllers(Vec<GpioControllerInfo>),
|
||||
Controller(GpioControllerInfo),
|
||||
PinValue(bool),
|
||||
Ack,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum PinOpKind {
|
||||
Read,
|
||||
Write,
|
||||
Configure,
|
||||
}
|
||||
|
||||
enum Handle {
|
||||
SchemeRoot,
|
||||
Register { pending: Vec<u8> },
|
||||
Provider { controller_id: u32, pending: Vec<u8> },
|
||||
ControllersDir { pending: Vec<u8> },
|
||||
ControllerDetail { id: u32, pending: Vec<u8> },
|
||||
PinOp { kind: PinOpKind, pending: Vec<u8> },
|
||||
}
|
||||
|
||||
struct ControllerEntry {
|
||||
info: GpioControllerInfo,
|
||||
provider_handle: usize,
|
||||
}
|
||||
|
||||
struct GpioDaemon {
|
||||
handles: HandleMap<Handle>,
|
||||
controllers: BTreeMap<u32, ControllerEntry>,
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
impl GpioDaemon {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
handles: HandleMap::new(),
|
||||
controllers: BTreeMap::new(),
|
||||
next_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn controller_list(&self) -> Vec<GpioControllerInfo> {
|
||||
self.controllers
|
||||
.values()
|
||||
.map(|entry| entry.info.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn serialize_response(response: &GpioControlResponse) -> syscall::Result<Vec<u8>> {
|
||||
ron::ser::to_string(response)
|
||||
.map(|text| text.into_bytes())
|
||||
.map_err(|err| {
|
||||
log::error!("gpiod: failed to serialize control response: {err}");
|
||||
SysError::new(EINVAL)
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize_request(buf: &[u8]) -> syscall::Result<GpioControlRequest> {
|
||||
let text = std::str::from_utf8(buf).map_err(|err| {
|
||||
log::warn!("gpiod: invalid UTF-8 request payload: {err}");
|
||||
SysError::new(EINVAL)
|
||||
})?;
|
||||
|
||||
ron::from_str(text).map_err(|err| {
|
||||
log::warn!("gpiod: failed to decode control request: {err}");
|
||||
SysError::new(EINVAL)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_pending_response(handle: &mut Handle, response: GpioControlResponse) -> syscall::Result<()> {
|
||||
let pending = Self::serialize_response(&response)?;
|
||||
Self::set_pending_bytes(handle, pending)
|
||||
}
|
||||
|
||||
fn set_pending_bytes(handle: &mut Handle, pending: Vec<u8>) -> syscall::Result<()> {
|
||||
match handle {
|
||||
Handle::Register { pending: slot }
|
||||
| Handle::Provider { pending: slot, .. }
|
||||
| Handle::ControllersDir { pending: slot }
|
||||
| Handle::ControllerDetail { pending: slot, .. }
|
||||
| Handle::PinOp { pending: slot, .. } => {
|
||||
*slot = pending;
|
||||
Ok(())
|
||||
}
|
||||
Handle::SchemeRoot => Err(SysError::new(EBADF)),
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
|
||||
let pending = match handle {
|
||||
Handle::Register { pending }
|
||||
| Handle::Provider { pending, .. }
|
||||
| Handle::ControllersDir { pending }
|
||||
| Handle::ControllerDetail { pending, .. }
|
||||
| Handle::PinOp { pending, .. } => pending,
|
||||
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
|
||||
};
|
||||
|
||||
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
|
||||
if offset >= pending.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let copy_len = buf.len().min(pending.len() - offset);
|
||||
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
|
||||
Ok(copy_len)
|
||||
}
|
||||
|
||||
fn validate_pin_target(
|
||||
&self,
|
||||
controller_id: u32,
|
||||
pin: u32,
|
||||
) -> std::result::Result<GpioControllerInfo, String> {
|
||||
let entry = self
|
||||
.controllers
|
||||
.get(&controller_id)
|
||||
.ok_or_else(|| format!("unknown controller {controller_id}"))?;
|
||||
if usize::try_from(pin)
|
||||
.ok()
|
||||
.filter(|pin| *pin < entry.info.pin_count)
|
||||
.is_none()
|
||||
{
|
||||
return Err(format!(
|
||||
"pin {pin} is out of range for controller {} (pin_count={})",
|
||||
entry.info.name, entry.info.pin_count
|
||||
));
|
||||
}
|
||||
Ok(entry.info.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for GpioDaemon {
|
||||
fn scheme_root(&mut self) -> syscall::Result<usize> {
|
||||
Ok(self.handles.insert(Handle::SchemeRoot))
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> syscall::Result<OpenResult> {
|
||||
let handle = self.handles.get(dirfd)?;
|
||||
let segments = path.trim_matches('/');
|
||||
|
||||
let new_handle = match handle {
|
||||
Handle::SchemeRoot => {
|
||||
if segments.is_empty() {
|
||||
return Err(SysError::new(EINVAL));
|
||||
}
|
||||
|
||||
let mut parts = segments.split('/');
|
||||
match parts.next() {
|
||||
Some("register") if parts.next().is_none() => Handle::Register {
|
||||
pending: Vec::new(),
|
||||
},
|
||||
Some("controllers") => match parts.next() {
|
||||
None => Handle::ControllersDir {
|
||||
pending: Vec::new(),
|
||||
},
|
||||
Some(id) if parts.next().is_none() => Handle::ControllerDetail {
|
||||
id: id.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
|
||||
pending: Vec::new(),
|
||||
},
|
||||
_ => return Err(SysError::new(EINVAL)),
|
||||
},
|
||||
Some("read_pin") if parts.next().is_none() => Handle::PinOp {
|
||||
kind: PinOpKind::Read,
|
||||
pending: Vec::new(),
|
||||
},
|
||||
Some("write_pin") if parts.next().is_none() => Handle::PinOp {
|
||||
kind: PinOpKind::Write,
|
||||
pending: Vec::new(),
|
||||
},
|
||||
Some("configure_pin") if parts.next().is_none() => Handle::PinOp {
|
||||
kind: PinOpKind::Configure,
|
||||
pending: Vec::new(),
|
||||
},
|
||||
_ => return Err(SysError::new(ENOENT)),
|
||||
}
|
||||
}
|
||||
Handle::ControllersDir { .. } => {
|
||||
if segments.is_empty() {
|
||||
return Err(SysError::new(EINVAL));
|
||||
}
|
||||
|
||||
Handle::ControllerDetail {
|
||||
id: segments.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
|
||||
pending: Vec::new(),
|
||||
}
|
||||
}
|
||||
_ => return Err(SysError::new(EACCES)),
|
||||
};
|
||||
|
||||
let fd = self.handles.insert(new_handle);
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: fd,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> syscall::Result<usize> {
|
||||
let controllers = self.controller_list();
|
||||
let detail = match self.handles.get(id)? {
|
||||
Handle::ControllerDetail { id, .. } => self.controllers.get(id).map(|entry| entry.info.clone()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
match handle {
|
||||
Handle::ControllersDir { pending } if pending.is_empty() => {
|
||||
*pending = Self::serialize_response(&GpioControlResponse::Controllers(controllers))?;
|
||||
}
|
||||
Handle::ControllerDetail { id, pending } if pending.is_empty() => {
|
||||
let info = detail.ok_or(SysError::new(ENOENT))?;
|
||||
*pending = Self::serialize_response(&GpioControlResponse::Controller(info))?;
|
||||
log::debug!("gpiod: served controller detail for id={id}");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Self::copy_pending(handle, buf, offset)
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> syscall::Result<usize> {
|
||||
let request = Self::deserialize_request(buf)?;
|
||||
|
||||
match request {
|
||||
GpioControlRequest::RegisterController { mut info } => {
|
||||
if !matches!(self.handles.get(id)?, Handle::Register { .. }) {
|
||||
return Err(SysError::new(EINVAL));
|
||||
}
|
||||
|
||||
let controller_id = self.next_id;
|
||||
self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?;
|
||||
info.id = controller_id;
|
||||
self.controllers.insert(
|
||||
controller_id,
|
||||
ControllerEntry {
|
||||
info: info.clone(),
|
||||
provider_handle: id,
|
||||
},
|
||||
);
|
||||
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
*handle = Handle::Provider {
|
||||
controller_id,
|
||||
pending: Self::serialize_response(&GpioControlResponse::ControllerRegistered {
|
||||
id: controller_id,
|
||||
})?,
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"RB_GPIOD_CONTROLLER_REGISTERED id={} name={} pin_count={} supports_interrupt={}",
|
||||
info.id,
|
||||
info.name,
|
||||
info.pin_count,
|
||||
info.supports_interrupt,
|
||||
);
|
||||
Ok(buf.len())
|
||||
}
|
||||
GpioControlRequest::ListControllers => {
|
||||
let controllers = self.controller_list();
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
Self::set_pending_response(handle, GpioControlResponse::Controllers(controllers))?;
|
||||
Ok(buf.len())
|
||||
}
|
||||
GpioControlRequest::ReadPin { controller_id, pin } => {
|
||||
let validation = self.validate_pin_target(controller_id, pin);
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
match handle {
|
||||
Handle::PinOp {
|
||||
kind: PinOpKind::Read,
|
||||
..
|
||||
} => {
|
||||
match validation {
|
||||
Ok(info) => {
|
||||
log::info!(
|
||||
"RB_GPIOD_PIN_READ controller_id={} name={} pin={} routed=stub",
|
||||
controller_id,
|
||||
info.name,
|
||||
pin,
|
||||
);
|
||||
Self::set_pending_response(handle, GpioControlResponse::PinValue(false))?;
|
||||
}
|
||||
Err(message) => {
|
||||
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
|
||||
}
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
_ => Err(SysError::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
GpioControlRequest::WritePin {
|
||||
controller_id,
|
||||
pin,
|
||||
value,
|
||||
} => {
|
||||
let validation = self.validate_pin_target(controller_id, pin);
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
match handle {
|
||||
Handle::PinOp {
|
||||
kind: PinOpKind::Write,
|
||||
..
|
||||
} => {
|
||||
match validation {
|
||||
Ok(info) => {
|
||||
log::info!(
|
||||
"RB_GPIOD_PIN_WRITE controller_id={} name={} pin={} value={} routed=stub",
|
||||
controller_id,
|
||||
info.name,
|
||||
pin,
|
||||
value,
|
||||
);
|
||||
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
|
||||
}
|
||||
Err(message) => {
|
||||
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
|
||||
}
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
_ => Err(SysError::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
GpioControlRequest::ConfigurePin {
|
||||
controller_id,
|
||||
pin,
|
||||
config,
|
||||
} => {
|
||||
let validation = self.validate_pin_target(controller_id, pin);
|
||||
let handle = self.handles.get_mut(id)?;
|
||||
match handle {
|
||||
Handle::PinOp {
|
||||
kind: PinOpKind::Configure,
|
||||
..
|
||||
} => {
|
||||
match validation {
|
||||
Ok(info) => {
|
||||
log::info!(
|
||||
"RB_GPIOD_PIN_CONFIG controller_id={} name={} pin={} direction={:?} pull={:?} interrupt={:?} routed=stub",
|
||||
controller_id,
|
||||
info.name,
|
||||
pin,
|
||||
config.direction,
|
||||
config.pull,
|
||||
config.interrupt_mode,
|
||||
);
|
||||
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
|
||||
}
|
||||
Err(message) => {
|
||||
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
|
||||
}
|
||||
}
|
||||
Ok(buf.len())
|
||||
}
|
||||
_ => Err(SysError::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
let Some(handle) = self.handles.remove(id) else {
|
||||
return;
|
||||
};
|
||||
if let Handle::Provider { controller_id, .. } = handle {
|
||||
if let Some(entry) = self.controllers.remove(&controller_id) {
|
||||
log::info!(
|
||||
"RB_GPIOD_CONTROLLER_REMOVED id={} name={} provider_handle={}",
|
||||
controller_id,
|
||||
entry.info.name,
|
||||
entry.provider_handle,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
|
||||
let socket = Socket::create().context("failed to create gpio scheme socket")?;
|
||||
let mut scheme = GpioDaemon::new();
|
||||
let handler = Blocking::new(&socket, 16);
|
||||
|
||||
daemon
|
||||
.ready_sync_scheme(&socket, &mut scheme)
|
||||
.context("failed to publish gpio scheme root")?;
|
||||
|
||||
log::info!("RB_GPIOD_SCHEMA version=1");
|
||||
|
||||
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
|
||||
|
||||
handler
|
||||
.process_requests_blocking(scheme)
|
||||
.context("failed to process gpiod requests")?;
|
||||
}
|
||||
|
||||
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
|
||||
if let Err(err) = run_daemon(daemon) {
|
||||
log::error!("gpiod: {err:#}");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
common::setup_logging(
|
||||
"gpio",
|
||||
"gpio",
|
||||
"gpiod",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
daemon::SchemeDaemon::new(daemon_runner);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "i2c-gpio-expanderd"
|
||||
description = "I2C GPIO expander bridge daemon"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
redox_syscall = { workspace = true, features = ["std"] }
|
||||
libredox.workspace = true
|
||||
serde.workspace = true
|
||||
ron.workspace = true
|
||||
|
||||
acpi-resource = { path = "../../acpi-resource" }
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
i2c-interface = { path = "../../i2c/i2c-interface" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,454 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
|
||||
use acpi_resource::{GpioDescriptor, I2cSerialBusDescriptor, ResourceDescriptor};
|
||||
use anyhow::{Context, Result};
|
||||
use i2c_interface::{
|
||||
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
|
||||
I2cTransferResponse, I2cTransferSegment,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AmlSymbol {
|
||||
name: String,
|
||||
value: AmlValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
enum AmlValue {
|
||||
Integer(u64),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ExpanderResources {
|
||||
i2c: I2cSerialBusDescriptor,
|
||||
pin_count: usize,
|
||||
gpio_int_count: usize,
|
||||
gpio_io_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ExpanderDescriptor {
|
||||
device: String,
|
||||
hid: String,
|
||||
resources: ExpanderResources,
|
||||
}
|
||||
|
||||
struct RegisteredExpander {
|
||||
_registration: File,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct GpioControllerInfo {
|
||||
id: u32,
|
||||
name: String,
|
||||
pin_count: usize,
|
||||
supports_interrupt: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
enum GpioControlRequest {
|
||||
RegisterController { info: GpioControllerInfo },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
enum GpioControlResponse {
|
||||
ControllerRegistered { id: u32 },
|
||||
Error(String),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
common::setup_logging(
|
||||
"gpio",
|
||||
"i2c-gpio-expander",
|
||||
"i2c-gpio-expanderd",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
daemon::Daemon::new(daemon_runner);
|
||||
}
|
||||
|
||||
fn daemon_runner(daemon: daemon::Daemon) -> ! {
|
||||
if let Err(err) = daemon_main(daemon) {
|
||||
log::error!("i2c-gpio-expanderd: {err:#}");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
||||
let expanders = discover_expanders().context("failed to discover ACPI I2C GPIO expanders")?;
|
||||
if expanders.is_empty() {
|
||||
log::info!("i2c-gpio-expanderd: no probable ACPI I2C GPIO expanders found");
|
||||
}
|
||||
|
||||
let adapters = list_i2c_adapters().unwrap_or_else(|err| {
|
||||
log::warn!("i2c-gpio-expanderd: unable to query i2cd adapters: {err:#}");
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let mut registered = Vec::new();
|
||||
for expander in expanders {
|
||||
match register_expander(expander, &adapters) {
|
||||
Ok(expander) => registered.push(expander),
|
||||
Err(err) => log::warn!("i2c-gpio-expanderd: expander registration skipped: {err:#}"),
|
||||
}
|
||||
}
|
||||
|
||||
daemon.ready();
|
||||
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
|
||||
|
||||
log::info!("i2c-gpio-expanderd: registered {} expander(s)", registered.len());
|
||||
|
||||
loop {
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
|
||||
fn discover_expanders() -> Result<Vec<ExpanderDescriptor>> {
|
||||
let mut matched = BTreeMap::new();
|
||||
|
||||
let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||
Ok(entries) => entries,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||
log::debug!("i2c-gpio-expanderd: ACPI symbols are not ready yet");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.context("failed to read ACPI symbol directory entry")?;
|
||||
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||
continue;
|
||||
};
|
||||
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(id) = read_symbol_id(&entry.path())? else {
|
||||
continue;
|
||||
};
|
||||
if is_excluded_device_id(&id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(device) = file_name
|
||||
.strip_suffix("_HID")
|
||||
.or_else(|| file_name.strip_suffix("_CID"))
|
||||
.map(str::to_owned)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let resources = match read_expander_resources(&device) {
|
||||
Ok(resources) => resources,
|
||||
Err(err) => {
|
||||
log::debug!("i2c-gpio-expanderd: skipping {device}: {err:#}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if resources.gpio_int_count == 0 && resources.gpio_io_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
matched.entry(device).or_insert((id, resources));
|
||||
}
|
||||
|
||||
let mut expanders = Vec::new();
|
||||
for (device, (hid, resources)) in matched {
|
||||
expanders.push(ExpanderDescriptor {
|
||||
device,
|
||||
hid,
|
||||
resources,
|
||||
});
|
||||
}
|
||||
Ok(expanders)
|
||||
}
|
||||
|
||||
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
|
||||
let contents = fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
|
||||
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
|
||||
Ok(symbol) => symbol,
|
||||
Err(err) => {
|
||||
log::debug!(
|
||||
"i2c-gpio-expanderd: skipping {} because the symbol payload was not a scalar ID: {err}",
|
||||
path.display(),
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let id = match symbol.value {
|
||||
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
|
||||
AmlValue::String(string) => string,
|
||||
};
|
||||
|
||||
log::debug!("i2c-gpio-expanderd: {} -> {id}", symbol.name);
|
||||
Ok(Some(id))
|
||||
}
|
||||
|
||||
fn read_expander_resources(device: &str) -> Result<ExpanderResources> {
|
||||
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
|
||||
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
|
||||
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
|
||||
.with_context(|| format!("failed to decode RON resources for {device}"))?;
|
||||
|
||||
let mut i2c = None;
|
||||
let mut pin_count = 0usize;
|
||||
let mut gpio_int_count = 0usize;
|
||||
let mut gpio_io_count = 0usize;
|
||||
|
||||
for resource in resources {
|
||||
match resource {
|
||||
ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus),
|
||||
ResourceDescriptor::GpioInt(descriptor) => {
|
||||
gpio_int_count += 1;
|
||||
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
|
||||
}
|
||||
ResourceDescriptor::GpioIo(descriptor) => {
|
||||
gpio_io_count += 1;
|
||||
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ExpanderResources {
|
||||
i2c: i2c.context("no I2cSerialBus resource was found")?,
|
||||
pin_count,
|
||||
gpio_int_count,
|
||||
gpio_io_count,
|
||||
})
|
||||
}
|
||||
|
||||
fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize {
|
||||
descriptor
|
||||
.pins
|
||||
.iter()
|
||||
.copied()
|
||||
.max()
|
||||
.map(|pin| usize::from(pin).saturating_add(1))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn is_excluded_device_id(id: &str) -> bool {
|
||||
matches!(
|
||||
id,
|
||||
"PNP0C50"
|
||||
| "ACPI0C50"
|
||||
| "INT34C5"
|
||||
| "INTC1055"
|
||||
| "INT33C2"
|
||||
| "INT33C3"
|
||||
| "INT3432"
|
||||
| "INT3433"
|
||||
| "INTC10EF"
|
||||
| "AMDI0010"
|
||||
| "AMDI0019"
|
||||
| "AMDI0510"
|
||||
| "PNP0CA0"
|
||||
| "AMDI0042"
|
||||
) || id.starts_with("ELAN")
|
||||
|| id.starts_with("CYAP")
|
||||
|| id.starts_with("SYNA")
|
||||
}
|
||||
|
||||
fn register_expander(expander: ExpanderDescriptor, adapters: &[I2cAdapterInfo]) -> Result<RegisteredExpander> {
|
||||
let ExpanderDescriptor {
|
||||
device,
|
||||
hid,
|
||||
resources,
|
||||
} = expander;
|
||||
|
||||
let adapter_name = resources
|
||||
.i2c
|
||||
.resource_source
|
||||
.as_ref()
|
||||
.map(|source| source.source.clone())
|
||||
.filter(|source| !source.is_empty())
|
||||
.unwrap_or_else(|| String::from("ACPI-I2C"));
|
||||
let adapter = match match_i2c_adapter(adapters, &adapter_name) {
|
||||
Some(adapter) => Some(adapter.clone()),
|
||||
None => {
|
||||
log::warn!(
|
||||
"i2c-gpio-expanderd: unable to resolve I2C adapter {} for {}",
|
||||
adapter_name,
|
||||
device,
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(adapter) = adapter.as_ref() {
|
||||
if let Err(err) = probe_expander(adapter, &adapter_name, resources.i2c.slave_address) {
|
||||
log::warn!(
|
||||
"i2c-gpio-expanderd: expander {} probe on {}@{:04x} failed: {err:#}",
|
||||
device,
|
||||
adapter_name,
|
||||
resources.i2c.slave_address,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let info = GpioControllerInfo {
|
||||
id: 0,
|
||||
name: format!("i2c-gpio-expander:{device}"),
|
||||
pin_count: resources.pin_count,
|
||||
supports_interrupt: resources.gpio_int_count > 0,
|
||||
};
|
||||
let mut registration = register_with_gpiod(&info)
|
||||
.with_context(|| format!("failed to register {device} with gpiod"))?;
|
||||
let response = read_gpio_registration_response(&mut registration)
|
||||
.with_context(|| format!("failed to read gpiod registration response for {device}"))?;
|
||||
|
||||
match response {
|
||||
GpioControlResponse::ControllerRegistered { id } => {
|
||||
log::info!(
|
||||
"RB_I2C_GPIO_EXPANDERD_DEVICE device={} hid={} controller_id={} adapter={} addr={:04x} pin_count={} gpio_int={} gpio_io={}",
|
||||
device,
|
||||
hid,
|
||||
id,
|
||||
adapter_name,
|
||||
resources.i2c.slave_address,
|
||||
info.pin_count,
|
||||
resources.gpio_int_count,
|
||||
resources.gpio_io_count,
|
||||
);
|
||||
}
|
||||
GpioControlResponse::Error(message) => {
|
||||
anyhow::bail!("gpiod rejected expander {device}: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RegisteredExpander {
|
||||
_registration: registration,
|
||||
})
|
||||
}
|
||||
|
||||
fn list_i2c_adapters() -> Result<Vec<I2cAdapterInfo>> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/scheme/i2c/adapters")
|
||||
.context("failed to open /scheme/i2c/adapters")?;
|
||||
|
||||
let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters)
|
||||
.context("failed to encode I2C list-adapters request")?;
|
||||
file.write_all(payload.as_bytes())
|
||||
.context("failed to request I2C adapter list")?;
|
||||
|
||||
let response = read_i2c_control_response(&mut file)?;
|
||||
match response {
|
||||
I2cControlResponse::AdapterList(adapters) => Ok(adapters),
|
||||
I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"),
|
||||
other => anyhow::bail!("unexpected i2cd list-adapters response: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> {
|
||||
adapters
|
||||
.iter()
|
||||
.find(|adapter| adapter.name == wanted)
|
||||
.or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted)))
|
||||
.or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name)))
|
||||
}
|
||||
|
||||
fn probe_expander(adapter: &I2cAdapterInfo, adapter_name: &str, address: u16) -> Result<I2cTransferResponse> {
|
||||
let request = I2cTransferRequest {
|
||||
adapter: adapter_name.to_string(),
|
||||
segments: vec![I2cTransferSegment::read(address, 1)],
|
||||
stop: true,
|
||||
};
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/scheme/i2c/transfer")
|
||||
.context("failed to open /scheme/i2c/transfer")?;
|
||||
let payload = ron::ser::to_string(&I2cControlRequest::Transfer {
|
||||
adapter_id: adapter.id,
|
||||
request,
|
||||
})
|
||||
.context("failed to encode I2C expander probe request")?;
|
||||
file.write_all(payload.as_bytes())
|
||||
.context("failed to send I2C expander probe request")?;
|
||||
|
||||
let response = read_i2c_control_response(&mut file)?;
|
||||
match response {
|
||||
I2cControlResponse::TransferResult(result) => {
|
||||
if !result.ok {
|
||||
let detail = result
|
||||
.error
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::from("unknown I2C transfer failure"));
|
||||
anyhow::bail!("I2C probe failed: {detail}");
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"),
|
||||
other => anyhow::bail!("unexpected I2C transfer response: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_with_gpiod(info: &GpioControllerInfo) -> Result<File> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/scheme/gpio/register")
|
||||
.context("failed to open /scheme/gpio/register")?;
|
||||
let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() })
|
||||
.context("failed to encode GPIO controller registration")?;
|
||||
file.write_all(payload.as_bytes())
|
||||
.context("failed to send GPIO controller registration")?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
fn read_gpio_registration_response(file: &mut File) -> Result<GpioControlResponse> {
|
||||
let mut buffer = vec![0_u8; 4096];
|
||||
let count = file
|
||||
.read(&mut buffer)
|
||||
.context("failed to read GPIO registration response")?;
|
||||
buffer.truncate(count);
|
||||
let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?;
|
||||
ron::from_str(text).context("failed to decode GPIO registration response")
|
||||
}
|
||||
|
||||
fn read_i2c_control_response(file: &mut File) -> Result<I2cControlResponse> {
|
||||
let mut buffer = vec![0_u8; 4096];
|
||||
let count = file
|
||||
.read(&mut buffer)
|
||||
.context("failed to read I2C control response")?;
|
||||
buffer.truncate(count);
|
||||
let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?;
|
||||
let trimmed = text.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Ok(I2cControlResponse::AdapterList(Vec::new()));
|
||||
}
|
||||
ron::from_str(trimmed).context("failed to decode I2C control response")
|
||||
}
|
||||
|
||||
fn eisa_id_from_integer(integer: u64) -> String {
|
||||
let vendor = integer & 0xFFFF;
|
||||
let device = (integer >> 16) & 0xFFFF;
|
||||
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
|
||||
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
|
||||
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
|
||||
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
|
||||
let device_1 = (device >> 4) & 0xF;
|
||||
let device_2 = (device >> 0) & 0xF;
|
||||
let device_3 = (device >> 12) & 0xF;
|
||||
let device_4 = (device >> 8) & 0xF;
|
||||
|
||||
format!(
|
||||
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user