DeviceExpansionROM.cpp 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. /*
  2. * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <Kernel/Bus/PCI/API.h>
  7. #include <Kernel/Bus/PCI/Access.h>
  8. #include <Kernel/FileSystem/SysFS/Subsystems/Bus/PCI/DeviceExpansionROM.h>
  9. #include <Kernel/Memory/TypedMapping.h>
  10. #include <Kernel/Sections.h>
  11. namespace Kernel {
  12. NonnullRefPtr<PCIDeviceExpansionROMSysFSComponent> PCIDeviceExpansionROMSysFSComponent::create(PCIDeviceSysFSDirectory const& device)
  13. {
  14. auto option_rom_size = PCI::get_expansion_rom_space_size(device.device_identifier());
  15. return adopt_ref(*new (nothrow) PCIDeviceExpansionROMSysFSComponent(device, option_rom_size));
  16. }
  17. PCIDeviceExpansionROMSysFSComponent::PCIDeviceExpansionROMSysFSComponent(PCIDeviceSysFSDirectory const& device, size_t option_rom_size)
  18. : SysFSComponent()
  19. , m_device(device)
  20. , m_option_rom_size(option_rom_size)
  21. {
  22. }
  23. ErrorOr<size_t> PCIDeviceExpansionROMSysFSComponent::read_bytes(off_t offset, size_t count, UserOrKernelBuffer& buffer, OpenFileDescription*) const
  24. {
  25. // NOTE: It might be that the PCI option ROM size is zero, indicating non-existing ROM.
  26. if (m_option_rom_size == 0)
  27. return Error::from_errno(EIO);
  28. // NOTE: This takes into account that `off_t offset` might be a negative number and
  29. // there's no meaningful way to handle negative values, so just return with an error.
  30. if (offset < 0)
  31. return Error::from_errno(EINVAL);
  32. auto unsigned_offset = static_cast<size_t>(offset);
  33. // NOTE: If the offset is beyond the PCI option ROM size, return EOF.
  34. if (unsigned_offset >= m_option_rom_size)
  35. return 0;
  36. auto blob = TRY(try_to_generate_buffer(unsigned_offset, count));
  37. if (static_cast<size_t>(offset) >= blob->size())
  38. return 0;
  39. ssize_t nread = min(static_cast<off_t>(blob->size() - offset), static_cast<off_t>(count));
  40. TRY(buffer.write(blob->data() + offset, nread));
  41. return nread;
  42. }
  43. ErrorOr<NonnullOwnPtr<KBuffer>> PCIDeviceExpansionROMSysFSComponent::try_to_generate_buffer(size_t offset_in_rom, size_t count) const
  44. {
  45. // NOTE: If the offset is beyond the PCI option ROM size, panic!.
  46. VERIFY(offset_in_rom < m_option_rom_size);
  47. auto temporary_buffer_size = TRY(Memory::page_round_up(count));
  48. auto temporary_buffer = TRY(KBuffer::try_create_with_size("SysFS DeviceExpansionROM Device"sv, temporary_buffer_size, Memory::Region::Access::ReadWrite));
  49. SpinlockLocker locker(m_device->device_identifier().operation_lock());
  50. // NOTE: These checks takes into account a couple of cases:
  51. // 1. Option ROM doesn't exist so the value of the ROM physical pointer is 0, and in that case
  52. // we should not allow mapping.
  53. // 2. Option ROM exists but for some (odd) reason, it is found in non-reserved (usable) physical memory
  54. // region, so access to it should be forbidden from this sysfs node.
  55. auto pci_option_rom_physical_pointer = PCI::read32_locked(m_device->device_identifier(), PCI::RegisterOffset::EXPANSION_ROM_POINTER);
  56. if (pci_option_rom_physical_pointer == 0)
  57. return Error::from_errno(EIO);
  58. if (pci_option_rom_physical_pointer & 1)
  59. dbgln("SysFS DeviceExpansionROM: Possible firmware bug! PCI option ROM was found already to be enabled.");
  60. auto offested_option_rom_memory_mapped_start_address = PhysicalAddress(pci_option_rom_physical_pointer + offset_in_rom);
  61. auto mapping_size = min(static_cast<off_t>(m_option_rom_size - offset_in_rom), static_cast<off_t>(count));
  62. if (!MM.is_allowed_to_read_physical_memory_for_userspace(offested_option_rom_memory_mapped_start_address, mapping_size))
  63. return Error::from_errno(EPERM);
  64. ScopeGuard unmap_option_rom_on_return([&] {
  65. // NOTE: In general, there's probably nothing wrong in leaving Option ROM being
  66. // mapped into physical memory.
  67. // For the sake of completeness, let's ensure we don't leave the Option ROM being
  68. // mapped into physical memory.
  69. // NOTE: It might be that in the future some driver will need to have the expansion ROM
  70. // being present in the physical address space. If that's the case then we should add a flag
  71. // to the PCI::DeviceIdentifier to indicate this condition!
  72. PCI::write32_locked(m_device->device_identifier(), PCI::RegisterOffset::EXPANSION_ROM_POINTER, pci_option_rom_physical_pointer);
  73. });
  74. // Note: Write the original value ORed with 1 to enable mapping into physical memory
  75. PCI::write32_locked(m_device->device_identifier(), PCI::RegisterOffset::EXPANSION_ROM_POINTER, pci_option_rom_physical_pointer | 1);
  76. auto do_io_transaction = [](Bytes bytes_buffer, Memory::TypedMapping<u8> const& mapping, size_t length_to_map) {
  77. VERIFY(length_to_map <= PAGE_SIZE);
  78. memcpy(bytes_buffer.data(), mapping.region->vaddr().offset(mapping.offset).as_ptr(), length_to_map);
  79. };
  80. size_t remaining_length = count;
  81. size_t nprocessed = 0;
  82. while (remaining_length > 0) {
  83. size_t length_to_map = min<size_t>(PAGE_SIZE, remaining_length);
  84. auto mapping = TRY(Memory::map_typed<u8>(offested_option_rom_memory_mapped_start_address.offset(nprocessed), length_to_map, Memory::Region::Access::Read));
  85. do_io_transaction(temporary_buffer->bytes().slice(nprocessed, length_to_map), mapping, length_to_map);
  86. nprocessed += length_to_map;
  87. remaining_length -= length_to_map;
  88. }
  89. return temporary_buffer;
  90. }
  91. }