From 81b925eea09fcd5739fed74eacbe9314f2c2e77e Mon Sep 17 00:00:00 2001 From: Drew Galbraith Date: Wed, 7 Jun 2023 08:24:10 -0700 Subject: [PATCH] Add a basic IPC setup with Channel Object. Pass a process a channel endpoint on startup that it will use to get it's initial capabilities. --- lib/mammoth/src/process.cpp | 13 ++++++- sys/test2.cpp | 11 +++++- zion/CMakeLists.txt | 1 + zion/capability/capability.cpp | 8 ++++ zion/capability/capability.h | 2 + zion/include/zcall.h | 22 ++++++++++- zion/include/zerrors.h | 1 + zion/lib/pair.h | 13 +++++++ zion/object/channel.cpp | 61 +++++++++++++++++++++++++++++ zion/object/channel.h | 40 +++++++++++++++++++ zion/object/process.cpp | 28 +++++++++++++- zion/object/process.h | 5 +++ zion/syscall/syscall.cpp | 58 +++++++++++++++++++++++++++ zion/usr/zcall.cpp | 71 +++++++++++++++++++++++++++------- zion/usr/zcall_internal.h | 27 +++++++++++++ 15 files changed, 341 insertions(+), 20 deletions(-) create mode 100644 zion/lib/pair.h create mode 100644 zion/object/channel.cpp create mode 100644 zion/object/channel.h diff --git a/lib/mammoth/src/process.cpp b/lib/mammoth/src/process.cpp index 839a926..ced946f 100644 --- a/lib/mammoth/src/process.cpp +++ b/lib/mammoth/src/process.cpp @@ -83,10 +83,16 @@ uint64_t LoadElfProgram(uint64_t base, uint64_t as_cap) { } // namespace uint64_t SpawnProcessFromElfRegion(uint64_t program) { + dbgln("Channel Create"); + uint64_t local_chan; + uint64_t foreign_chan; + check(ZChannelCreate(&local_chan, &foreign_chan)); + dbgln("Spawn"); uint64_t proc_cap; uint64_t as_cap; - check(ZProcessSpawn(Z_INIT_PROC_SELF, &proc_cap, &as_cap)); + check(ZProcessSpawn(Z_INIT_PROC_SELF, foreign_chan, &proc_cap, &as_cap, + &foreign_chan)); uint64_t entry_point = LoadElfProgram(program, as_cap); dbgln("Thread Create"); @@ -94,7 +100,10 @@ uint64_t SpawnProcessFromElfRegion(uint64_t program) { check(ZThreadCreate(proc_cap, &thread_cap)); dbgln("Thread start"); - check(ZThreadStart(thread_cap, entry_point, 0, 0)); + check(ZThreadStart(thread_cap, entry_point, foreign_chan, 0)); + + const uint8_t* msg = reinterpret_cast("Hello!"); + check(ZChannelSend(local_chan, 0, 7, msg, 0, 0)); return Z_OK; } diff --git a/sys/test2.cpp b/sys/test2.cpp index ba7bf52..f3fc31e 100644 --- a/sys/test2.cpp +++ b/sys/test2.cpp @@ -1,5 +1,6 @@ #include #include +#include #define CHECK(expr) \ { \ @@ -15,7 +16,7 @@ void thread_entry(void* a) { dbgln(static_cast(a)); } -int main() { +int main(uint64_t bootstrap_cap) { dbgln("Main thread"); const char* a = "a"; @@ -24,5 +25,13 @@ int main() { const char* d = "dee"; Thread t1(thread_entry, a); Thread t2(thread_entry, b); + + uint64_t num_bytes = 10; + uint64_t num_caps; + uint8_t bytes[10]; + uint64_t type; + check(ZChannelRecv(bootstrap_cap, num_bytes, bytes, 0, 0, &type, &num_bytes, + &num_caps)); + dbgln(reinterpret_cast(bytes)); return 0; } diff --git a/zion/CMakeLists.txt b/zion/CMakeLists.txt index c6b6471..eaa3ce9 100644 --- a/zion/CMakeLists.txt +++ b/zion/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(zion memory/physical_memory.cpp memory/user_stack_manager.cpp object/address_space.cpp + object/channel.cpp object/memory_object.cpp object/process.cpp object/thread.cpp diff --git a/zion/capability/capability.cpp b/zion/capability/capability.cpp index fb76cd0..67d3443 100644 --- a/zion/capability/capability.cpp +++ b/zion/capability/capability.cpp @@ -34,3 +34,11 @@ RefPtr Capability::obj() { } return StaticCastRefPtr(obj_); } + +template <> +RefPtr Capability::obj() { + if (type_ != CHANNEL) { + panic("Accessing %u cap as object.", type_); + } + return StaticCastRefPtr(obj_); +} diff --git a/zion/capability/capability.h b/zion/capability/capability.h index 3b5bf93..5bc4332 100644 --- a/zion/capability/capability.h +++ b/zion/capability/capability.h @@ -16,6 +16,7 @@ class Capability : public RefCounted { THREAD, ADDRESS_SPACE, MEMORY_OBJECT, + CHANNEL, }; Capability(const RefPtr& obj, Type type, uint64_t id, uint64_t permissions) @@ -30,6 +31,7 @@ class Capability : public RefCounted { RefPtr obj(); uint64_t id() { return id_; } + void set_id(uint64_t id) { id_ = id; } bool CheckType(Type type) { return type_ == type; } diff --git a/zion/include/zcall.h b/zion/include/zcall.h index e09f413..aa504b4 100644 --- a/zion/include/zcall.h +++ b/zion/include/zcall.h @@ -32,13 +32,21 @@ #define Z_INIT_BOOT_VMMO 0x31 +// IPC Calls +#define Z_CHANNEL_CREATE 0x40 +#define Z_CHANNEL_SEND 0x41 +#define Z_CHANNEL_RECV 0x42 +#define Z_CHANNEL_SENDRECV 0x43 + // Debugging Calls. #define Z_DEBUG_PRINT 0x10000000 void ZProcessExit(uint64_t code); -[[nodiscard]] uint64_t ZProcessSpawn(uint64_t proc_cap, uint64_t* new_proc_cap, - uint64_t* new_vmas_cap); +[[nodiscard]] uint64_t ZProcessSpawn(uint64_t proc_cap, uint64_t bootstrap_cap, + uint64_t* new_proc_cap, + uint64_t* new_vmas_cap, + uint64_t* new_bootstrap_cap); // UNUSED for now, I think we can get away with just starting a thread. [[nodiscard]] uint64_t ZProcessStart(uint64_t proc_cap, uint64_t thread_cap, @@ -56,4 +64,14 @@ void ZThreadExit(); uint64_t vmmo_cap, uint64_t* vaddr); [[nodiscard]] uint64_t ZMemoryObjectCreate(uint64_t size, uint64_t* vmmo_cap); +[[nodiscard]] uint64_t ZChannelCreate(uint64_t* channel1, uint64_t* channel2); +[[nodiscard]] uint64_t ZChannelSend(uint64_t chan_cap, uint64_t type, + uint64_t num_bytes, const uint8_t* bytes, + uint64_t num_caps, const uint64_t* caps); +[[nodiscard]] uint64_t ZChannelRecv(uint64_t chan_cap, uint64_t num_bytes, + uint8_t* bytes, uint64_t num_caps, + uint64_t* caps, uint64_t* type, + uint64_t* actual_bytes, + uint64_t* actual_caps); + [[nodiscard]] uint64_t ZDebug(const char* message); diff --git a/zion/include/zerrors.h b/zion/include/zerrors.h index 9d3e160..4dcf6fb 100644 --- a/zion/include/zerrors.h +++ b/zion/include/zerrors.h @@ -5,3 +5,4 @@ #define ZE_INVALID 0x2 #define ZE_DENIED 0x4 #define ZE_UNIMPLEMENTED 0x8 +#define ZE_BUFF_SIZE 0x10 diff --git a/zion/lib/pair.h b/zion/lib/pair.h new file mode 100644 index 0000000..71dae30 --- /dev/null +++ b/zion/lib/pair.h @@ -0,0 +1,13 @@ +#pragma once + +template +class Pair { + public: + Pair(const T& first, const U& second) : first_(first), second_(second) {} + T& first() { return first_; } + U& second() { return second_; } + + private: + T first_; + U second_; +}; diff --git a/zion/object/channel.cpp b/zion/object/channel.cpp new file mode 100644 index 0000000..b9ec308 --- /dev/null +++ b/zion/object/channel.cpp @@ -0,0 +1,61 @@ +#include "object/channel.h" + +#include "include/zerrors.h" + +Pair, RefPtr> Channel::CreateChannelPair() { + auto c1 = MakeRefCounted(); + auto c2 = MakeRefCounted(); + c1->SetPeer(c2); + c2->SetPeer(c1); + return {c1, c2}; +} + +uint64_t Channel::Write(const ZMessage& msg) { + return peer_->EnqueueMessage(msg); +} + +uint64_t Channel::Read(ZMessage& msg) { + if (pending_messages_.size() == 0) { + dbgln("Unimplemented add blocking."); + return ZE_UNIMPLEMENTED; + } + Message next_msg = pending_messages_.PeekFront(); + if (next_msg.num_bytes > msg.num_bytes) { + return ZE_BUFF_SIZE; + } + + msg.type = next_msg.type; + msg.num_bytes = next_msg.num_bytes; + msg.num_caps = 0; + + for (uint64_t i = 0; i < msg.num_bytes; i++) { + msg.bytes[i] = next_msg.bytes[i]; + } + + pending_messages_.PopFront(); + + return Z_OK; +} + +uint64_t Channel::EnqueueMessage(const ZMessage& msg) { + if (msg.num_caps > 0) { + dbgln("Unimplemented passing caps on channel"); + return ZE_UNIMPLEMENTED; + } + + if (msg.num_bytes > 0x1000) { + dbgln("Large message size unimplemented: %x", msg.num_bytes); + return ZE_INVALID; + } + + Message message{ + .type = msg.type, + .num_bytes = msg.num_bytes, + .bytes = new uint8_t[msg.num_bytes], + }; + for (uint64_t i = 0; i < msg.num_bytes; i++) { + message.bytes[i] = msg.bytes[i]; + } + pending_messages_.PushBack(message); + return Z_OK; +} diff --git a/zion/object/channel.h b/zion/object/channel.h new file mode 100644 index 0000000..ac6b3cc --- /dev/null +++ b/zion/object/channel.h @@ -0,0 +1,40 @@ +#pragma once + +#include "capability/capability.h" +#include "lib/linked_list.h" +#include "lib/pair.h" +#include "lib/ref_ptr.h" +#include "object/kernel_object.h" +#include "usr/zcall_internal.h" + +class Channel : public KernelObject { + public: + static Pair, RefPtr> CreateChannelPair(); + + RefPtr peer() { return peer_; } + + uint64_t Write(const ZMessage& msg); + uint64_t Read(ZMessage& msg); + + private: + // FIXME: We will likely never close the channel based on this + // circular dependency. + RefPtr peer_{nullptr}; + + struct Message { + uint64_t type; + + uint64_t num_bytes; + uint8_t* bytes; + }; + + // FIXME: This is probably dangerous because of an + // implicit shallow copy. + LinkedList pending_messages_; + + friend class MakeRefCountedFriend; + Channel() {} + void SetPeer(const RefPtr& peer) { peer_ = peer; } + + uint64_t EnqueueMessage(const ZMessage& msg); +}; diff --git a/zion/object/process.cpp b/zion/object/process.cpp index b55d6a4..932a5f9 100644 --- a/zion/object/process.cpp +++ b/zion/object/process.cpp @@ -62,10 +62,25 @@ void Process::CheckState() { state_ = FINISHED; } +RefPtr Process::ReleaseCapability(uint64_t cid) { + auto iter = caps_.begin(); + while (iter != caps_.end()) { + if (*iter && iter->id() == cid) { + auto cap = *iter; + *iter = {nullptr}; + return cap; + } + ++iter; + } + dbgln("Bad cap access"); + dbgln("Num caps: %u", caps_.size()); + return {}; +} + RefPtr Process::GetCapability(uint64_t cid) { auto iter = caps_.begin(); while (iter != caps_.end()) { - if (iter->id() == cid) { + if (*iter && iter->id() == cid) { return *iter; } ++iter; @@ -75,6 +90,11 @@ RefPtr Process::GetCapability(uint64_t cid) { return {}; } +uint64_t Process::AddCapability(const RefPtr& cap) { + cap->set_id(next_cap_id_++); + caps_.PushBack(cap); + return cap->id(); +} uint64_t Process::AddCapability(const RefPtr& thread) { uint64_t cap_id = next_cap_id_++; caps_.PushBack( @@ -100,6 +120,12 @@ uint64_t Process::AddCapability(const RefPtr& vmmo) { cap_id, ZC_WRITE)); return cap_id; } +uint64_t Process::AddCapability(const RefPtr& chan) { + uint64_t cap_id = next_cap_id_++; + caps_.PushBack(MakeRefCounted(chan, Capability::CHANNEL, cap_id, + ZC_WRITE | ZC_READ)); + return cap_id; +} void Process::AddCapability(uint64_t cap_id, const RefPtr& vmmo) { caps_.PushBack(MakeRefCounted(vmmo, Capability::MEMORY_OBJECT, diff --git a/zion/object/process.h b/zion/object/process.h index 7dd7ac6..75e3988 100644 --- a/zion/object/process.h +++ b/zion/object/process.h @@ -6,6 +6,7 @@ #include "lib/linked_list.h" #include "lib/ref_ptr.h" #include "object/address_space.h" +#include "object/channel.h" // Forward decl due to cyclic dependency. class Thread; @@ -27,11 +28,15 @@ class Process : public KernelObject { RefPtr CreateThread(); RefPtr GetThread(uint64_t tid); + RefPtr ReleaseCapability(uint64_t cid); RefPtr GetCapability(uint64_t cid); + // FIXME: We can't reset the cap id here. + uint64_t AddCapability(const RefPtr& cap); uint64_t AddCapability(const RefPtr& t); uint64_t AddCapability(const RefPtr& p); uint64_t AddCapability(const RefPtr& vmas); uint64_t AddCapability(const RefPtr& vmmo); + uint64_t AddCapability(const RefPtr& chan); void AddCapability(uint64_t cap_id, const RefPtr& vmmo); // Checks the state of all child threads and transitions to diff --git a/zion/syscall/syscall.cpp b/zion/syscall/syscall.cpp index 6689a4f..0342482 100644 --- a/zion/syscall/syscall.cpp +++ b/zion/syscall/syscall.cpp @@ -5,6 +5,7 @@ #include "debug/debug.h" #include "include/zcall.h" #include "include/zerrors.h" +#include "object/channel.h" #include "object/process.h" #include "scheduler/process_manager.h" #include "scheduler/scheduler.h" @@ -74,6 +75,15 @@ uint64_t ProcessSpawn(ZProcessSpawnReq* req, ZProcessSpawnResp* resp) { resp->proc_cap = curr_proc.AddCapability(proc); resp->vmas_cap = curr_proc.AddCapability(proc->vmas()); + if (req->bootstrap_cap != 0) { + auto cap = curr_proc.ReleaseCapability(req->bootstrap_cap); + if (!cap) { + return ZE_NOT_FOUND; + } + // FIXME: Check permissions. + resp->bootstrap_cap = proc->AddCapability(cap); + } + return Z_OK; } @@ -153,6 +163,48 @@ uint64_t MemoryObjectCreate(ZMemoryObjectCreateReq* req, return Z_OK; } +uint64_t ChannelCreate(ZChannelCreateResp* resp) { + auto& proc = gScheduler->CurrentProcess(); + auto chan_pair = Channel::CreateChannelPair(); + resp->chan_cap1 = proc.AddCapability(chan_pair.first()); + resp->chan_cap2 = proc.AddCapability(chan_pair.second()); + return Z_OK; +} + +uint64_t ChannelSend(ZChannelSendReq* req) { + auto& proc = gScheduler->CurrentProcess(); + auto chan_cap = proc.GetCapability(req->chan_cap); + if (!chan_cap) { + return ZE_NOT_FOUND; + } + if (!chan_cap->CheckType(Capability::CHANNEL)) { + return ZE_INVALID; + } + if (!chan_cap->HasPermissions(ZC_WRITE)) { + return ZE_DENIED; + } + auto chan = chan_cap->obj(); + chan->Write(req->message); + return Z_OK; +} + +uint64_t ChannelRecv(ZChannelRecvReq* req) { + auto& proc = gScheduler->CurrentProcess(); + auto chan_cap = proc.GetCapability(req->chan_cap); + if (!chan_cap) { + return ZE_NOT_FOUND; + } + if (!chan_cap->CheckType(Capability::CHANNEL)) { + return ZE_INVALID; + } + if (!chan_cap->HasPermissions(ZC_READ)) { + return ZE_DENIED; + } + auto chan = chan_cap->obj(); + chan->Read(req->message); + return Z_OK; +} + extern "C" uint64_t SyscallHandler(uint64_t call_id, void* req, void* resp) { Thread& thread = gScheduler->CurrentThread(); switch (call_id) { @@ -182,6 +234,12 @@ extern "C" uint64_t SyscallHandler(uint64_t call_id, void* req, void* resp) { return MemoryObjectCreate( reinterpret_cast(req), reinterpret_cast(resp)); + case Z_CHANNEL_CREATE: + return ChannelCreate(reinterpret_cast(resp)); + case Z_CHANNEL_SEND: + return ChannelSend(reinterpret_cast(req)); + case Z_CHANNEL_RECV: + return ChannelRecv(reinterpret_cast(req)); case Z_DEBUG_PRINT: dbgln("[Debug] %s", req); return Z_OK; diff --git a/zion/usr/zcall.cpp b/zion/usr/zcall.cpp index 49833b3..3f809b0 100644 --- a/zion/usr/zcall.cpp +++ b/zion/usr/zcall.cpp @@ -4,18 +4,6 @@ #include "usr/zcall_internal.h" -uint64_t SysCall0(uint64_t number) { - uint64_t return_code; - asm("syscall" : "=a"(return_code) : "D"(number)); - return return_code; -} - -uint64_t SysCall1(uint64_t number, const void* first) { - uint64_t return_code; - asm("syscall" : "=a"(return_code) : "D"(number), "S"(first) : "rcx", "r11"); - return return_code; -} - uint64_t SysCall2(uint64_t number, const void* first, const void* second) { uint64_t return_code; asm("syscall" @@ -25,19 +13,28 @@ uint64_t SysCall2(uint64_t number, const void* first, const void* second) { return return_code; } +uint64_t SysCall0(uint64_t number) { return SysCall2(number, 0, 0); } + +uint64_t SysCall1(uint64_t number, const void* first) { + return SysCall2(number, first, 0); +} + void ZProcessExit(uint64_t code) { SysCall1(Z_PROCESS_EXIT, reinterpret_cast(code)); } -uint64_t ZProcessSpawn(uint64_t proc_cap, uint64_t* new_proc_cap, - uint64_t* new_vmas_cap) { +uint64_t ZProcessSpawn(uint64_t proc_cap, uint64_t bootstrap_cap, + uint64_t* new_proc_cap, uint64_t* new_vmas_cap, + uint64_t* new_bootstrap_cap) { ZProcessSpawnReq req{ .proc_cap = proc_cap, + .bootstrap_cap = bootstrap_cap, }; ZProcessSpawnResp resp; uint64_t ret = SysCall2(Z_PROCESS_SPAWN, &req, &resp); *new_proc_cap = resp.proc_cap; *new_vmas_cap = resp.vmas_cap; + *new_bootstrap_cap = resp.bootstrap_cap; return ret; } @@ -86,6 +83,52 @@ uint64_t ZMemoryObjectCreate(uint64_t size, uint64_t* vmmo_cap) { return ret; } +uint64_t ZChannelCreate(uint64_t* channel1, uint64_t* channel2) { + ZChannelCreateResp resp; + uint64_t ret = SysCall2(Z_CHANNEL_CREATE, 0, &resp); + *channel1 = resp.chan_cap1; + *channel2 = resp.chan_cap2; + return ret; +} + +uint64_t ZChannelSend(uint64_t chan_cap, uint64_t type, uint64_t num_bytes, + const uint8_t* bytes, uint64_t num_caps, + const uint64_t* caps) { + ZChannelSendReq req{ + .chan_cap = chan_cap, + .message = + { + .type = type, + .num_bytes = num_bytes, + .bytes = const_cast(bytes), + .num_caps = num_caps, + .caps = const_cast(caps), + }, + }; + return SysCall1(Z_CHANNEL_SEND, &req); +} + +uint64_t ZChannelRecv(uint64_t chan_cap, uint64_t num_bytes, uint8_t* bytes, + uint64_t num_caps, uint64_t* caps, uint64_t* type, + uint64_t* actual_bytes, uint64_t* actual_caps) { + ZChannelRecvReq req{ + .chan_cap = chan_cap, + .message = + { + .type = 0, + .num_bytes = num_bytes, + .bytes = bytes, + .num_caps = num_caps, + .caps = caps, + }, + }; + uint64_t ret = SysCall1(Z_CHANNEL_RECV, &req); + *type = req.message.type; + *actual_bytes = req.message.num_bytes; + *actual_caps = req.message.num_caps; + return ret; +} + uint64_t ZDebug(const char* message) { return SysCall1(Z_DEBUG_PRINT, message); } diff --git a/zion/usr/zcall_internal.h b/zion/usr/zcall_internal.h index 9b6ce3c..676a7b6 100644 --- a/zion/usr/zcall_internal.h +++ b/zion/usr/zcall_internal.h @@ -4,11 +4,13 @@ struct ZProcessSpawnReq { uint64_t proc_cap; + uint64_t bootstrap_cap; }; struct ZProcessSpawnResp { uint64_t proc_cap; uint64_t vmas_cap; + uint64_t bootstrap_cap; }; struct ZThreadCreateReq { @@ -43,3 +45,28 @@ struct ZMemoryObjectCreateReq { struct ZMemoryObjectCreateResp { uint64_t vmmo_cap; }; + +struct ZChannelCreateResp { + uint64_t chan_cap1; + uint64_t chan_cap2; +}; + +struct ZMessage { + uint64_t type; + + uint64_t num_bytes; + uint8_t* bytes; + + uint64_t num_caps; + uint64_t* caps; +}; + +struct ZChannelSendReq { + uint64_t chan_cap; + ZMessage message; +}; + +struct ZChannelRecvReq { + uint64_t chan_cap; + ZMessage message; +};