diff --git a/libraries/libvapours/include/vapours/prfile2/pf/prfile2_pf_types.hpp b/libraries/libvapours/include/vapours/prfile2/pf/prfile2_pf_types.hpp index 6944cd977..aa855443d 100644 --- a/libraries/libvapours/include/vapours/prfile2/pf/prfile2_pf_types.hpp +++ b/libraries/libvapours/include/vapours/prfile2/pf/prfile2_pf_types.hpp @@ -117,6 +117,34 @@ namespace ams::prfile2::pf { HandleType partition_handle; DriveCharacter drive_char; u8 status; + + template + constexpr ALWAYS_INLINE bool GetStatusBit() const { + constexpr u32 Mask = (1u << Ix); + return (this->status & Mask) != 0; + } + + template + constexpr ALWAYS_INLINE void SetStatusBit(bool en) { + constexpr u32 Mask = (1u << Ix); + if (en) { + this->status |= Mask; + } else { + this->status &= ~Mask; + } + } + + constexpr bool IsAttached() const { return this->GetStatusBit<0>(); } + constexpr void SetAttached(bool en) { this->SetStatusBit<0>(en); } + + constexpr bool IsMounted() const { return this->GetStatusBit<1>(); } + constexpr void SetMounted(bool en) { this->SetStatusBit<1>(en); } + + constexpr bool IsDiskInserted() const { return this->GetStatusBit<2>(); } + constexpr void SetDiskInserted(bool en) { this->SetStatusBit<2>(en); } + + constexpr bool IsFlag12() const { return this->GetStatusBit<12>(); } + constexpr void SetFlag12(bool en) { this->SetStatusBit<12>(en); } }; using TailBuf = u32; diff --git a/libraries/libvapours/include/vapours/prfile2/prfile2_cache.hpp b/libraries/libvapours/include/vapours/prfile2/prfile2_cache.hpp new file mode 100644 index 000000000..41a975853 --- /dev/null +++ b/libraries/libvapours/include/vapours/prfile2/prfile2_cache.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::prfile2 { + + constexpr inline const auto MinimumFatBufferSize = 1; + constexpr inline const auto MaximumFatBufferSize = (1 << 16) - 1; + constexpr inline const auto MinimumDataBufferSize = 1; + constexpr inline const auto MaximumDataBufferSize = (1 << 15) - 1; + + constexpr inline const auto MinimumFatPages = 1; + constexpr inline const auto MinimumDataPages = 4; + + struct SectorCache { + u32 mode; + u16 num_fat_pages; + u16 num_data_pages; + pf::CachePage *current_fat; + pf::CachePage *current_data; + pf::SectorBuffer *buffers; + u32 fat_buf_size; + u32 data_buf_size; + void *signature; + }; + +} + +namespace ams::prfile2::cache { + + /* ... */ + +} diff --git a/libraries/libvapours/include/vapours/prfile2/prfile2_common.hpp b/libraries/libvapours/include/vapours/prfile2/prfile2_common.hpp index 7aec135ff..4e339aa25 100644 --- a/libraries/libvapours/include/vapours/prfile2/prfile2_common.hpp +++ b/libraries/libvapours/include/vapours/prfile2/prfile2_common.hpp @@ -64,7 +64,8 @@ namespace ams::prfile2 { Error_SYSTEM = -1, }; - constexpr inline const u32 InvalidSector = std::numeric_limits::max(); + constexpr inline const u32 InvalidSector = std::numeric_limits::max(); + constexpr inline const u32 InvalidCluster = std::numeric_limits::max(); enum OpenMode { OpenMode_None = 0, diff --git a/libraries/libvapours/include/vapours/prfile2/prfile2_volume.hpp b/libraries/libvapours/include/vapours/prfile2/prfile2_volume.hpp index fe06f6e9f..db972b798 100644 --- a/libraries/libvapours/include/vapours/prfile2/prfile2_volume.hpp +++ b/libraries/libvapours/include/vapours/prfile2/prfile2_volume.hpp @@ -15,6 +15,7 @@ */ #pragma once #include +#include #include #include #include @@ -129,6 +130,12 @@ namespace ams::prfile2 { }; }; + namespace pdm { + + struct Partition; + + } + struct Volume { BiosParameterBlock bpb; u32 num_free_clusters; @@ -142,7 +149,7 @@ namespace ams::prfile2 { u32 num_open_files; u32 num_open_directories; /* ... */ - /* TODO: SectorCache cache; */ + SectorCache cache; VolumeContext *context; u64 context_id; DirectoryTail tail_entry; @@ -156,13 +163,41 @@ namespace ams::prfile2 { pf::Error last_error; pf::Error last_driver_error; /* ... */ - void *partition; + HandleType partition_handle; VolumeCallback callback; const u8 *format_param; /* ... */ /* TODO: ExtensionTable extension_table; */ /* TODO: ExtensionData ext; */ /* ... */ + + template + constexpr ALWAYS_INLINE bool GetFlagsBit() const { + constexpr u32 Mask = (1u << Ix); + return (this->flags & Mask) != 0; + } + + template + constexpr ALWAYS_INLINE void SetFlagsBit(bool en) { + constexpr u32 Mask = (1u << Ix); + if (en) { + this->flags |= Mask; + } else { + this->flags &= ~Mask; + } + } + + constexpr bool IsAttached() const { return this->GetFlagsBit<0>(); } + constexpr void SetAttached(bool en) { this->SetFlagsBit<0>(en); } + + constexpr bool IsMounted() const { return this->GetFlagsBit<1>(); } + constexpr void SetMounted(bool en) { this->SetFlagsBit<1>(en); } + + constexpr bool IsDiskInserted() const { return this->GetFlagsBit<2>(); } + constexpr void SetDiskInserted(bool en) { this->SetFlagsBit<2>(en); } + + constexpr bool IsFlag12() const { return this->GetFlagsBit<12>(); } + constexpr void SetFlag12(bool en) { this->SetFlagsBit<12>(en); } }; struct VolumeSet { diff --git a/libraries/libvapours/source/prfile2/prfile2_volume.cpp b/libraries/libvapours/source/prfile2/prfile2_volume.cpp index 88c8b96c9..68d653c47 100644 --- a/libraries/libvapours/source/prfile2/prfile2_volume.cpp +++ b/libraries/libvapours/source/prfile2/prfile2_volume.cpp @@ -55,6 +55,48 @@ namespace ams::prfile2::vol { return nullptr; } + VolumeContext *GetCurrentVolumeContext(u64 *out_context_id) { + /* Get the volume set. */ + auto &vol_set = GetVolumeSet(); + + /* Acquire exclusive access to the volume set. */ + ScopedCriticalSection lk(vol_set.critical_section); + + /* Get the current context id. */ + u64 context_id = 0; + system::GetCurrentContextId(std::addressof(context_id)); + if (out_context_id != nullptr) { + *out_context_id = context_id; + } + + if (auto *ctx = GetVolumeContextById(context_id); ctx != nullptr) { + return ctx; + } else { + return std::addressof(vol_set.default_context); + } + } + + bool IsValidDriveCharacter(pf::DriveCharacter drive_char) { + return static_cast((drive_char & 0xDF) - 'A') < 26; + } + + Volume *GetVolumeByDriveCharacter(pf::DriveCharacter drive_char) { + /* Get the volume set. */ + auto &vol_set = GetVolumeSet(); + + /* Calculate the volume index. */ + const auto index = std::toupper(static_cast(drive_char)) - 'A'; + + /* Acquire exclusive access to the volume set. */ + ScopedCriticalSection lk(vol_set.critical_section); + + if (index < MaxVolumes) { + return std::addressof(vol_set.volumes[index]); + } else { + return nullptr; + } + } + } pf::Error Initialize(u32 config, void *param) { @@ -120,8 +162,168 @@ namespace ams::prfile2::vol { } pf::Error Attach(pf::DriveTable *drive_table) { - AMS_UNUSED(drive_table); - AMS_ABORT("vol::Attach"); + /* Get the volume set. */ + auto &vol_set = GetVolumeSet(); + + /* Get the volume context for error tracking. */ + u64 context_id = 0; + auto *vol_ctx = GetCurrentVolumeContext(std::addressof(context_id)); + + auto SetLastErrorAndReturn = [&] ALWAYS_INLINE_LAMBDA (pf::Error err) -> pf::Error { vol_ctx->last_error = err; return err; }; + + /* Check the drive table. */ + if (drive_table == nullptr) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + + /* Clear the drive table's character/status. */ + const auto drive_char = drive_table->drive_char; + drive_table->drive_char = 0; + drive_table->status = 0; + + /* Check that we can attach. */ + if (vol_set.num_attached_drives >= MaxVolumes) { + return SetLastErrorAndReturn(pf::Error_TooManyVolumesAttached); + } + + /* Check the cache setting. */ + auto *cache_setting = drive_table->cache; + if (cache_setting == nullptr) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (cache_setting->fat_buf_size > MaximumFatBufferSize) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (cache_setting->data_buf_size > MaximumDataBufferSize) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (cache_setting->num_fat_pages < MinimumFatPages) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (cache_setting->num_data_pages < MinimumDataPages) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (cache_setting->pages == nullptr) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (cache_setting->buffers == nullptr) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (!util::IsAligned(reinterpret_cast(cache_setting->pages), alignof(u32))) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if (!util::IsAligned(reinterpret_cast(cache_setting->buffers), alignof(u32))) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + + /* Adjust the cache setting. */ + cache_setting->fat_buf_size = std::max(cache_setting->fat_buf_size, MinimumFatBufferSize); + cache_setting->data_buf_size = std::max(cache_setting->data_buf_size, MinimumDataBufferSize); + + /* Check the adjusted setting. */ + if (cache_setting->fat_buf_size > cache_setting->num_fat_pages) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + if ((cache_setting->num_data_pages / cache_setting->data_buf_size) < MinimumDataPages) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + + /* Validate the drive character. */ + if (drive_char != 0) { + if (!IsValidDriveCharacter(drive_char)) { + return SetLastErrorAndReturn(pf::Error_InvalidParameter); + } + + if (auto *vol = GetVolumeByDriveCharacter(drive_char); vol == nullptr || vol->IsAttached()) { + return SetLastErrorAndReturn(pf::Error_InvalidVolumeLabel); + } + } + + /* Perform the bulk of the attach. */ + Volume *vol = nullptr; + { + /* Lock the volume set. */ + ScopedCriticalSection lk(vol_set.critical_section); + + /* Find a free volume. */ + for (auto &v : vol_set.volumes) { + if (!v.IsAttached()) { + vol = std::addressof(v); + break; + } + } + if (vol == nullptr) { + return SetLastErrorAndReturn(pf::Error_TooManyVolumesAttached); + } + const auto vol_id = vol - vol_set.volumes; + + /* Clear the volume. */ + std::memset(vol, 0, sizeof(*vol)); + + /* Initialize the volume. */ + vol->num_free_clusters = InvalidCluster; + vol->num_free_clusters_ = InvalidCluster; + vol->last_free_cluster = InvalidCluster; + vol->partition_handle = drive_table->partition_handle; + InitializeCriticalSection(std::addressof(vol->critical_section)); + vol->drive_char = 'A' + vol_id; + + /* Setup directory tail. */ + vol->tail_entry.tracker_size = util::size(vol->tail_entry.tracker_buf); + vol->tail_entry.tracker_bits = vol->tail_entry.tracker_buf; + + /* Initialize driver for volume. */ + /* TODO: drv::Initialize(vol); + error checking */ + + /* Setup the cache. */ + /* TODO: cache::SetCache(vol, drive_table->cache->pages, drive_table->cache->buffers, drive_table->cache->num_fat_pages, drive_table->cache->num_data_pages); */ + /* TODO: cache::SetFatBufferSize(vol, drive_table->cache->fat_buf_size); */ + /* TODO: cache::SetDataBufferSize(vol, drive_table->cache->data_buf_size); */ + + /* Set flags. */ + vol->SetAttached(true); + vol->SetFlag12(true); + + /* Update the drive table. */ + drive_table->SetAttached(true); + drive_table->drive_char = vol->drive_char; + + /* Update the number of attached drives. */ + if ((vol_set.num_attached_drives++) == 0) { + vol_set.default_context.volume_id = vol_id; + } + } + + /* Acquire exclusive access to the volume set. */ + ScopedCriticalSection lk(vol_set.critical_section); + + /* Associate the volume to our context while we operate on it. */ + vol->context = vol_ctx; + vol->context_id = context_id; + ON_SCOPE_EXIT { vol->context_id = 0; }; + + /* TODO: Copy volume root dir entry to all contexts. */ + + /* TODO: Clear tracking fields at the end of the volume. */ + + /* Perform mount as appropriate. */ + const auto check_mount_err = /* TODO vol::CheckMediaInsertForAttachMount(vol) */ pf::Error_Ok; + const bool inserted = /* TODO: drv::IsInserted(vol) */ false; + if (check_mount_err != pf::Error_Ok) { + if (inserted) { + drive_table->SetDiskInserted(true); + } + vol_ctx->last_error = check_mount_err; + } else if (inserted) { + drive_table->SetDiskInserted(true); + if (auto mount_err = /* TODO vol::MountImpl(vol, 0x1B, false) */pf::Error_InternalError; mount_err != pf::Error_Ok) { + vol_ctx->last_error = mount_err; + } else { + drive_table->SetMounted(true); + } + } + + return pf::Error_Ok; } VolumeContext *RegisterContext(u64 *out_context_id) {