#pragma once #include "error/ErrorCode.hpp" #include "orbis-config.hpp" #include "rx/AddressRange.hpp" #include "rx/EnumBitSet.hpp" #include "rx/Rc.hpp" #include "rx/mem.hpp" #include "vmem.hpp" #include #include namespace orbis { enum OpenFlags { kOpenFlagReadOnly = 0x0, kOpenFlagWriteOnly = 0x1, kOpenFlagReadWrite = 0x2, kOpenFlagNonBlock = 0x4, kOpenFlagAppend = 0x8, kOpenFlagShLock = 0x10, kOpenFlagExLock = 0x20, kOpenFlagAsync = 0x40, kOpenFlagFsync = 0x80, kOpenFlagCreat = 0x200, kOpenFlagTrunc = 0x400, kOpenFlagExcl = 0x800, kOpenFlagDSync = 0x1000, kOpenFlagDirect = 0x10000, kOpenFlagDirectory = 0x20000, }; struct File; struct Thread; struct Process; struct Stat; struct StatFs; struct IoDevice : rx::RcBase { rx::EnumBitSet blockFlags{}; virtual ErrorCode open(rx::Ref *file, const char *path, std::uint32_t flags, std::uint32_t mode, Thread *thread) = 0; virtual ErrorCode statfs(const char *path, StatFs *sb, Thread *thread) { return ErrorCode::NOTSUP; } virtual ErrorCode stat(const char *path, Stat *sb, Thread *thread) { return ErrorCode::NOTSUP; } virtual ErrorCode unlink(const char *path, bool recursive, Thread *thread) { return ErrorCode::NOTSUP; } virtual ErrorCode createSymlink(const char *target, const char *linkPath, Thread *thread) { return ErrorCode::NOTSUP; } virtual ErrorCode mkdir(const char *path, int mode, Thread *thread) { return ErrorCode::NOTSUP; } virtual ErrorCode rmdir(const char *path, Thread *thread) { return ErrorCode::NOTSUP; } virtual ErrorCode rename(const char *from, const char *to, Thread *thread) { return ErrorCode::NOTSUP; } virtual ErrorCode ioctl(std::uint64_t request, ptr argp, Thread *thread); virtual ErrorCode map(rx::AddressRange range, std::int64_t offset, rx::EnumBitSet protection, File *file, Process *process); }; namespace ioctl { inline constexpr std::uint32_t VoidBit = 0x20000000; inline constexpr std::uint32_t OutBit = 0x40000000; inline constexpr std::uint32_t InBit = 0x80000000; inline constexpr std::uint32_t DirMask = VoidBit | InBit | OutBit; constexpr bool isVoid(std::uint32_t cmd) { return (cmd & DirMask) == VoidBit; } constexpr bool isIn(std::uint32_t cmd) { return (cmd & DirMask) == InBit; } constexpr bool isOut(std::uint32_t cmd) { return (cmd & DirMask) == OutBit; } constexpr bool isInOut(std::uint32_t cmd) { return (cmd & DirMask) == (OutBit | InBit); } constexpr std::uint32_t paramSize(std::uint32_t cmd) { return (cmd >> 16) & ((1 << 13) - 1); } constexpr std::uint32_t group(std::uint32_t cmd) { return (cmd >> 8) & 0xff; } constexpr std::uint32_t id(std::uint32_t cmd) { return cmd & 0xff; } } // namespace ioctl struct IoctlHandlerEntry; using IoctlHandler = ErrorCode (*)(Thread *thread, orbis::ptr arg, IoDevice *device, void (*impl)()); struct IoctlHandlerEntry { IoctlHandler handler; void (*impl)(); std::uint32_t cmd; }; template struct IoDeviceWithIoctl : IoDevice { IoctlHandlerEntry ioctlTable[256]{}; template requires requires { requires std::is_base_of_v; requires ioctl::paramSize(Cmd) == sizeof(T); requires ioctl::group(Cmd) == Group; requires std::is_const_v || ioctl::isInOut(Cmd); requires !std::is_const_v || ioctl::isIn(Cmd); } void addIoctl(ErrorCode (*handler)(Thread *thread, InstanceT *device, T &arg)) { constexpr auto id = ioctl::id(Cmd); assert(ioctlTable[id].handler == nullptr); IoctlHandlerEntry &entry = ioctlTable[id]; entry.handler = [](Thread *thread, orbis::ptr arg, IoDevice *device, void (*opaqueImpl)()) -> ErrorCode { auto impl = std::bit_cast(opaqueImpl); std::remove_cvref_t _arg; ORBIS_RET_ON_ERROR(orbis::uread(_arg, orbis::ptr(arg))); ORBIS_RET_ON_ERROR(impl(thread, static_cast(device), _arg)); if constexpr (ioctl::isInOut(Cmd)) { return orbis::uwrite(orbis::ptr(arg), _arg); } else { return {}; } }; entry.impl = std::bit_cast(handler); entry.cmd = Cmd; } template requires requires { requires std::is_base_of_v; requires ioctl::group(Cmd) == Group; requires ioctl::isVoid(Cmd); } void addIoctl(ErrorCode (*handler)(Thread *thread, InstanceT *device)) { constexpr auto id = ioctl::id(Cmd); assert(ioctlTable[id].handler == nullptr); IoctlHandlerEntry &entry = ioctlTable[id]; entry.handler = [](Thread *thread, orbis::ptr, IoDevice *device, void (*opaqueImpl)()) -> ErrorCode { auto impl = std::bit_cast(opaqueImpl); return impl(thread, static_cast(device)); }; entry.impl = std::bit_cast(handler); entry.cmd = Cmd; } template requires requires { requires std::is_base_of_v; requires ioctl::paramSize(Cmd) == sizeof(T); requires ioctl::group(Cmd) == Group; requires ioctl::isOut(Cmd); } void addIoctl(std::pair (*handler)(Thread *thread, InstanceT *device)) { constexpr auto id = ioctl::id(Cmd); assert(ioctlTable[id].handler == nullptr); IoctlHandlerEntry &entry = ioctlTable[id]; entry.handler = [](Thread *thread, orbis::ptr arg, IoDevice *device, void (*opaqueImpl)()) -> ErrorCode { auto impl = std::bit_cast(opaqueImpl); auto [errc, value] = impl(thread, static_cast(device)); ORBIS_RET_ON_ERROR(errc); return orbis::uwrite(orbis::ptr(arg), value); }; entry.impl = std::bit_cast(handler); entry.cmd = Cmd; } ErrorCode ioctl(std::uint64_t request, orbis::ptr argp, Thread *thread) override { auto id = request & 0xff; if (ioctlTable[id].handler == nullptr || ioctlTable[id].cmd != request) { return IoDevice::ioctl(request, argp, thread); } return ioctlTable[id].handler(thread, argp, this, ioctlTable[id].impl); } }; } // namespace orbis