From 3a4557d417ca4c2aac5878d19812c94b43a39268 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Fri, 31 Mar 2023 00:49:46 +0300 Subject: [PATCH] Kernel: Cleanup ATA device initialization --- kernel/include/kernel/Storage/ATAController.h | 76 ++-- kernel/kernel/Storage/ATAController.cpp | 337 +++++++++--------- 2 files changed, 213 insertions(+), 200 deletions(-) diff --git a/kernel/include/kernel/Storage/ATAController.h b/kernel/include/kernel/Storage/ATAController.h index 75c73945..a83211c8 100644 --- a/kernel/include/kernel/Storage/ATAController.h +++ b/kernel/include/kernel/Storage/ATAController.h @@ -8,43 +8,62 @@ namespace Kernel { - struct ATABus; class ATAController; class ATADevice final : public StorageDevice { public: + static BAN::ErrorOr create(ATAController*, uint16_t, uint16_t, uint8_t); + virtual BAN::ErrorOr read_sectors(uint64_t, uint8_t, uint8_t*) override; virtual BAN::ErrorOr write_sectors(uint64_t, uint8_t, const uint8_t*) override; - virtual uint32_t sector_size() const override { return sector_words * 2; } - virtual uint64_t total_size() const override { return lba_count * sector_size(); } + virtual uint32_t sector_size() const override { return m_sector_words * 2; } + virtual uint64_t total_size() const override { return m_lba_count * sector_size(); } + + BAN::StringView model() const { return m_model; } + + private: + ATADevice(ATAController* controller, uint16_t base, uint16_t ctrl, uint8_t index) + : m_controller(controller) + , m_base(base) + , m_ctrl(ctrl) + , m_index(index) + , m_slave_bit((index & 0x01) << 4) + { } + BAN::ErrorOr initialize(); + + uint8_t io_read(uint16_t); + void io_write(uint16_t, uint8_t); + void read_buffer(uint16_t, uint16_t*, size_t); + void write_buffer(uint16_t, const uint16_t*, size_t); + BAN::ErrorOr wait(bool); + BAN::Error error(); private: enum class DeviceType { - Unknown, ATA, ATAPI, }; - DeviceType type; - uint8_t slave_bit; // 0x00 for master, 0x10 for slave - uint16_t signature; - uint16_t capabilities; - uint32_t command_set; - uint32_t sector_words; - uint64_t lba_count; - char model[41]; + ATAController* m_controller; + const uint16_t m_base; + const uint16_t m_ctrl; + const uint8_t m_index; + const uint8_t m_slave_bit; - ATABus* bus; - ATAController* controller; + DeviceType m_type; + uint16_t m_signature; + uint16_t m_capabilities; + uint32_t m_command_set; + uint32_t m_sector_words; + uint64_t m_lba_count; + char m_model[41]; friend class ATAController; - char device_name[4] {}; - public: - virtual ino_t ino() const override { return !!slave_bit; } + virtual ino_t ino() const override { return m_index; } virtual Mode mode() const override { return { Mode::IFBLK }; } virtual nlink_t nlink() const override { return 1; } virtual uid_t uid() const override { return 0; } @@ -55,23 +74,12 @@ namespace Kernel virtual dev_t dev() const override; virtual dev_t rdev() const override { return 0x5429; } - virtual BAN::StringView name() const override { return BAN::StringView(device_name, sizeof(device_name) - 1); } + virtual BAN::StringView name() const override { return BAN::StringView(m_device_name, sizeof(m_device_name) - 1); } virtual BAN::ErrorOr read(size_t, void*, size_t) override; - }; - struct ATABus - { - uint16_t base; - uint16_t ctrl; - ATADevice devices[2]; - - uint8_t read(uint16_t); - void read_buffer(uint16_t, uint16_t*, size_t); - void write(uint16_t, uint8_t); - void write_buffer(uint16_t, const uint16_t*, size_t); - BAN::ErrorOr wait(bool); - BAN::Error error(); + public: + char m_device_name[4] {}; }; class ATAController final : public StorageController @@ -80,15 +88,13 @@ namespace Kernel static BAN::ErrorOr create(const PCIDevice&); private: - ATAController(const PCIDevice& device) : m_pci_device(device) {} - BAN::ErrorOr initialize(); + ATAController() = default; + BAN::ErrorOr initialize(const PCIDevice& device); BAN::ErrorOr read(ATADevice*, uint64_t, uint8_t, uint8_t*); BAN::ErrorOr write(ATADevice*, uint64_t, uint8_t, const uint8_t*); private: - ATABus m_buses[2]; - const PCIDevice& m_pci_device; SpinLock m_lock; friend class ATADevice; diff --git a/kernel/kernel/Storage/ATAController.cpp b/kernel/kernel/Storage/ATAController.cpp index d083f39d..75ebec77 100644 --- a/kernel/kernel/Storage/ATAController.cpp +++ b/kernel/kernel/Storage/ATAController.cpp @@ -1,10 +1,8 @@ +#include #include #include #include -#define ATA_PRIMARY 0 -#define ATA_SECONDARY 1 - #define ATA_PORT_DATA 0x00 #define ATA_PORT_ERROR 0x00 #define ATA_PORT_SECTOR_COUNT 0x02 @@ -18,6 +16,8 @@ #define ATA_PORT_CONTROL 0x10 #define ATA_PORT_ALT_STATUS 0x10 +#define ATA_CONTROL_nIEN 0x02 + #define ATA_ERROR_AMNF 0x01 #define ATA_ERROR_TKZNF 0x02 #define ATA_ERROR_ABRT 0x04 @@ -34,9 +34,9 @@ #define ATA_COMMAND_READ_SECTORS 0x20 #define ATA_COMMAND_WRITE_SECTORS 0x30 +#define ATA_COMMAND_IDENTIFY_PACKET 0xA1 #define ATA_COMMAND_CACHE_FLUSH 0xE7 #define ATA_COMMAND_IDENTIFY 0xEC -#define ATA_COMMAND_IDENTIFY_PACKET 0xA1 #define ATA_IDENTIFY_SIGNATURE 0 #define ATA_IDENTIFY_MODEL 27 @@ -52,148 +52,155 @@ namespace Kernel { + BAN::ErrorOr ATADevice::create(ATAController* controller, uint16_t base, uint16_t ctrl, uint8_t index) + { + ATADevice* device = new ATADevice(controller, base, ctrl, index); + if (device == nullptr) + return BAN::Error::from_errno(ENOMEM); + BAN::ScopeGuard guard([device] { device->unref(); }); + TRY(device->initialize()); + guard.disable(); + return device; + } + BAN::ErrorOr ATAController::create(const PCIDevice& device) { - ATAController* controller = new ATAController(device); + ATAController* controller = new ATAController(); if (controller == nullptr) return BAN::Error::from_errno(ENOMEM); - TRY(controller->initialize()); + BAN::ScopeGuard guard([controller] { controller->unref(); }); + TRY(controller->initialize(device)); + guard.disable(); return controller; } - BAN::ErrorOr ATAController::initialize() + BAN::ErrorOr ATADevice::initialize() { - m_buses[ATA_PRIMARY].base = 0x1F0; - m_buses[ATA_PRIMARY].ctrl = 0x3F6; - m_buses[ATA_PRIMARY].write(ATA_PORT_CONTROL, 2); + io_write(ATA_PORT_CONTROL, ATA_CONTROL_nIEN); - m_buses[ATA_SECONDARY].base = 0x170; - m_buses[ATA_SECONDARY].ctrl = 0x376; - m_buses[ATA_SECONDARY].write(ATA_PORT_CONTROL, 2); + io_write(ATA_PORT_DRIVE_SELECT, 0xA0 | m_slave_bit); + PIT::sleep(1); - uint8_t prog_if = m_pci_device.read_byte(0x09); + io_write(ATA_PORT_COMMAND, ATA_COMMAND_IDENTIFY); + PIT::sleep(1); + + if (io_read(ATA_PORT_STATUS) == 0) + return BAN::Error::from_c_string(""); + + uint8_t status = 0; + while (true) + { + status = io_read(ATA_PORT_STATUS); + if (status & ATA_STATUS_ERR) + break; + if (!(status & ATA_STATUS_BSY) && (status && ATA_STATUS_DRQ)) + break; + } + + if (status & ATA_STATUS_ERR) + { + uint8_t lba1 = io_read(ATA_PORT_LBA1); + uint8_t lba2 = io_read(ATA_PORT_LBA2); + + if (lba1 == 0x14 && lba2 == 0xEB) + m_type = ATADevice::DeviceType::ATAPI; + else if (lba1 == 0x69 && lba2 == 0x96) + m_type = ATADevice::DeviceType::ATAPI; + else + return BAN::Error::from_c_string("Not ATA/ATAPI device"); + + io_write(ATA_PORT_COMMAND, ATA_COMMAND_IDENTIFY_PACKET); + PIT::sleep(1); + } + else + { + m_type = ATADevice::DeviceType::ATA; + } + + uint16_t buffer[256] {}; + read_buffer(ATA_PORT_DATA, buffer, 256); + + m_signature = *(uint16_t*)(buffer + ATA_IDENTIFY_SIGNATURE); + m_capabilities = *(uint16_t*)(buffer + ATA_IDENTIFY_CAPABILITIES); + m_command_set = *(uint32_t*)(buffer + ATA_IDENTIFY_COMMAND_SET); + + if ((buffer[ATA_IDENTIFY_SECTOR_INFO] & (1 << 15)) == 0 && + (buffer[ATA_IDENTIFY_SECTOR_INFO] & (1 << 14)) != 0 && + (buffer[ATA_IDENTIFY_SECTOR_INFO] & (1 << 12)) != 0) + { + m_sector_words = *(uint32_t*)(buffer + ATA_IDENTIFY_SECTOR_WORDS); + } + else + { + m_sector_words = 256; + } + + m_lba_count = 0; + if (m_command_set & ATA_COMMANDSET_LBA48_SUPPORTED) + m_lba_count = *(uint64_t*)(buffer + ATA_IDENTIFY_LBA_COUNT_EXT); + if (m_lba_count < (1 << 28)) + m_lba_count = *(uint32_t*)(buffer + ATA_IDENTIFY_LBA_COUNT); + + for (int i = 0; i < 20; i++) + { + uint16_t word = buffer[ATA_IDENTIFY_MODEL + i]; + m_model[2 * i + 0] = word >> 8; + m_model[2 * i + 1] = word & 0xFF; + } + m_model[40] = 0; + + m_device_name[0] = 'h'; + m_device_name[1] = 'd'; + m_device_name[2] = 'a' + m_index; + m_device_name[3] = '\0'; + + TRY(initialize_partitions()); + return {}; + } + + BAN::ErrorOr ATAController::initialize(const PCIDevice& pci_device) + { + struct Bus + { + uint16_t base; + uint16_t ctrl; + }; + + Bus buses[2]; + buses[0].base = 0x1F0; + buses[0].ctrl = 0x3F6; + + buses[1].base = 0x170; + buses[1].ctrl = 0x376; + + uint8_t prog_if = pci_device.read_byte(0x09); if (prog_if & 0x01) { - m_buses[ATA_PRIMARY].base = m_pci_device.read_dword(0x10) & 0xFFFFFFFC; - m_buses[ATA_PRIMARY].ctrl = m_pci_device.read_dword(0x14) & 0xFFFFFFFC; + buses[0].base = pci_device.read_dword(0x10) & 0xFFFFFFFC; + buses[0].ctrl = pci_device.read_dword(0x14) & 0xFFFFFFFC; } if (prog_if & 0x04) { - m_buses[ATA_SECONDARY].base = m_pci_device.read_dword(0x18) & 0xFFFFFFFC; - m_buses[ATA_SECONDARY].ctrl = m_pci_device.read_dword(0x1C) & 0xFFFFFFFC; + buses[1].base = pci_device.read_dword(0x18) & 0xFFFFFFFC; + buses[1].ctrl = pci_device.read_dword(0x1C) & 0xFFFFFFFC; } - for (uint8_t bus_index = 0; bus_index < 2; bus_index++) + for (uint8_t drive = 0; drive < 4; drive++) { - for (uint8_t device_index = 0; device_index < 2; device_index++) + auto device = ATADevice::create(this, buses[drive / 2].base, buses[drive / 2].ctrl, drive); + if (device.is_error()) { - ATABus& bus = m_buses[bus_index]; - ATADevice& device = bus.devices[device_index]; - - device.type = ATADevice::DeviceType::Unknown; - device.slave_bit = device_index << 4; - device.bus = &bus; - device.controller = this; - - device.device_name[0] = 'h'; - device.device_name[1] = 'd'; - device.device_name[2] = 'a' + bus_index * 2 + device_index; - device.device_name[3] = '\0'; - - bus.write(ATA_PORT_DRIVE_SELECT, 0xA0 | device.slave_bit); - PIT::sleep(1); - - bus.write(ATA_PORT_COMMAND, ATA_COMMAND_IDENTIFY); - PIT::sleep(1); - - if (bus.read(ATA_PORT_STATUS) == 0) - continue; - - uint8_t status = 0; - while (true) - { - status = bus.read(ATA_PORT_STATUS); - if (status & ATA_STATUS_ERR) - break; - if (!(status & ATA_STATUS_BSY) && (status && ATA_STATUS_DRQ)) - break; - } - - auto type = ATADevice::DeviceType::ATA; - - // Not a ATA device, maybe ATAPI - if (status & ATA_STATUS_ERR) - { - uint8_t lba1 = bus.read(ATA_PORT_LBA1); - uint8_t lba2 = bus.read(ATA_PORT_LBA2); - - if (lba1 == 0x14 && lba2 == 0xEB) - type = ATADevice::DeviceType::ATAPI; - else if (lba1 == 0x69 && lba2 == 0x96) - type = ATADevice::DeviceType::ATAPI; - else - continue; - - bus.write(ATA_PORT_COMMAND, ATA_COMMAND_IDENTIFY_PACKET); - PIT::sleep(1); - } - - uint16_t buffer[256] {}; - bus.read_buffer(ATA_PORT_DATA, buffer, 256); - - device.type = type; - device.signature = *(uint16_t*)(buffer + ATA_IDENTIFY_SIGNATURE); - device.capabilities = *(uint16_t*)(buffer + ATA_IDENTIFY_CAPABILITIES); - device.command_set = *(uint32_t*)(buffer + ATA_IDENTIFY_COMMAND_SET); - - device.sector_words = 256; - if ((buffer[ATA_IDENTIFY_SECTOR_INFO] & (1 << 15)) == 0 && - (buffer[ATA_IDENTIFY_SECTOR_INFO] & (1 << 14)) != 0 && - (buffer[ATA_IDENTIFY_SECTOR_INFO] & (1 << 12)) != 0) - { - device.sector_words = *(uint32_t*)(buffer + ATA_IDENTIFY_SECTOR_WORDS); - } - - device.lba_count = 0; - if (device.command_set & ATA_COMMANDSET_LBA48_SUPPORTED) - device.lba_count = *(uint64_t*)(buffer + ATA_IDENTIFY_LBA_COUNT_EXT); - if (device.lba_count < (1 << 28)) - device.lba_count = *(uint32_t*)(buffer + ATA_IDENTIFY_LBA_COUNT); - - for (int i = 0; i < 20; i++) - { - uint16_t word = buffer[ATA_IDENTIFY_MODEL + i]; - device.model[2 * i + 0] = word >> 8; - device.model[2 * i + 1] = word & 0xFF; - } - device.model[40] = 0; - - if (auto res = device.initialize_partitions(); res.is_error()) - { - dprintln("could not initialize partitions on device {}", device.device_name); - device.type = ATADevice::DeviceType::Unknown; - } - else - { - add_device(&device); - } + if (strlen(device.error().get_message()) > 0) + dprintln("{}", device.error()); + continue; } + add_device(device.value()); } - for (uint32_t i = 0; i < 2; i++) + for (StorageDevice* device_ : devices()) { - for (uint32_t j = 0; j < 2; j++) - { - ATADevice& device = m_buses[i].devices[j]; - if (device.type == ATADevice::DeviceType::Unknown) - continue; - constexpr uint32_t words_per_mib = 1024 * 1024 / 2; - const char* device_type = - device.type == ATADevice::DeviceType::ATA ? "ATA" : - device.type == ATADevice::DeviceType::ATAPI ? "ATAPI" : - "Unknown"; - dprintln("Found {} Drive ({} MiB) model {}", device_type, device.lba_count * device.sector_words / words_per_mib, device.model); - } + ATADevice& device = *(ATADevice*)device_; + dprintln("Initialized drive {} ({} MiB) model {}", device.name(), device.total_size() / (1024 * 1024), device.model()); } return {}; @@ -201,7 +208,7 @@ namespace Kernel BAN::ErrorOr ATAController::read(ATADevice* device, uint64_t lba, uint8_t sector_count, uint8_t* buffer) { - if (lba + sector_count > device->lba_count) + if (lba + sector_count > device->m_lba_count) return BAN::Error::from_c_string("Attempted to read outside of the device boundaries"); LockGuard _(m_lock); @@ -209,17 +216,17 @@ namespace Kernel if (lba < (1 << 28)) { // LBA28 - device->bus->write(ATA_PORT_DRIVE_SELECT, 0xE0 | device->slave_bit | ((lba >> 24) & 0x0F)); - device->bus->write(ATA_PORT_SECTOR_COUNT, sector_count); - device->bus->write(ATA_PORT_LBA0, (uint8_t)(lba >> 0)); - device->bus->write(ATA_PORT_LBA1, (uint8_t)(lba >> 8)); - device->bus->write(ATA_PORT_LBA2, (uint8_t)(lba >> 16)); - device->bus->write(ATA_PORT_COMMAND, ATA_COMMAND_READ_SECTORS); + device->io_write(ATA_PORT_DRIVE_SELECT, 0xE0 | device->m_slave_bit | ((lba >> 24) & 0x0F)); + device->io_write(ATA_PORT_SECTOR_COUNT, sector_count); + device->io_write(ATA_PORT_LBA0, (uint8_t)(lba >> 0)); + device->io_write(ATA_PORT_LBA1, (uint8_t)(lba >> 8)); + device->io_write(ATA_PORT_LBA2, (uint8_t)(lba >> 16)); + device->io_write(ATA_PORT_COMMAND, ATA_COMMAND_READ_SECTORS); for (uint32_t sector = 0; sector < sector_count; sector++) { - TRY(device->bus->wait(true)); - device->bus->read_buffer(ATA_PORT_DATA, (uint16_t*)buffer + sector * device->sector_words, device->sector_words); + TRY(device->wait(true)); + device->read_buffer(ATA_PORT_DATA, (uint16_t*)buffer + sector * device->m_sector_words, device->m_sector_words); } } else @@ -233,7 +240,7 @@ namespace Kernel BAN::ErrorOr ATAController::write(ATADevice* device, uint64_t lba, uint8_t sector_count, const uint8_t* buffer) { - if (lba + sector_count > device->lba_count) + if (lba + sector_count > device->m_lba_count) return BAN::Error::from_c_string("Attempted to read outside of the device boundaries"); LockGuard _(m_lock); @@ -241,17 +248,17 @@ namespace Kernel if (lba < (1 << 28)) { // LBA28 - device->bus->write(ATA_PORT_DRIVE_SELECT, 0xE0 | device->slave_bit | ((lba >> 24) & 0x0F)); - device->bus->write(ATA_PORT_SECTOR_COUNT, sector_count); - device->bus->write(ATA_PORT_LBA0, (uint8_t)(lba >> 0)); - device->bus->write(ATA_PORT_LBA1, (uint8_t)(lba >> 8)); - device->bus->write(ATA_PORT_LBA2, (uint8_t)(lba >> 16)); - device->bus->write(ATA_PORT_COMMAND, ATA_COMMAND_WRITE_SECTORS); + device->io_write(ATA_PORT_DRIVE_SELECT, 0xE0 | device->m_slave_bit | ((lba >> 24) & 0x0F)); + device->io_write(ATA_PORT_SECTOR_COUNT, sector_count); + device->io_write(ATA_PORT_LBA0, (uint8_t)(lba >> 0)); + device->io_write(ATA_PORT_LBA1, (uint8_t)(lba >> 8)); + device->io_write(ATA_PORT_LBA2, (uint8_t)(lba >> 16)); + device->io_write(ATA_PORT_COMMAND, ATA_COMMAND_WRITE_SECTORS); for (uint32_t sector = 0; sector < sector_count; sector++) { - TRY(device->bus->wait(false)); - device->bus->write_buffer(ATA_PORT_DATA, (uint16_t*)buffer + sector * device->sector_words, device->sector_words); + TRY(device->wait(false)); + device->write_buffer(ATA_PORT_DATA, (uint16_t*)buffer + sector * device->m_sector_words, device->m_sector_words); } } else @@ -260,71 +267,71 @@ namespace Kernel ASSERT(false); } - TRY(device->bus->wait(false)); - device->bus->write(ATA_PORT_COMMAND, ATA_COMMAND_CACHE_FLUSH); + TRY(device->wait(false)); + device->io_write(ATA_PORT_COMMAND, ATA_COMMAND_CACHE_FLUSH); return {}; } BAN::ErrorOr ATADevice::read_sectors(uint64_t lba, uint8_t sector_count, uint8_t* buffer) { - TRY(controller->read(this, lba, sector_count, buffer)); + TRY(m_controller->read(this, lba, sector_count, buffer)); return {}; } BAN::ErrorOr ATADevice::write_sectors(uint64_t lba, uint8_t sector_count, const uint8_t* buffer) { - TRY(controller->write(this, lba, sector_count, buffer)); + TRY(m_controller->write(this, lba, sector_count, buffer)); return {}; } - uint8_t ATABus::read(uint16_t port) + uint8_t ATADevice::io_read(uint16_t port) { if (port <= 0x07) - return IO::inb(base + port); + return IO::inb(m_base + port); if (0x10 <= port && port <= 0x11) - return IO::inb(ctrl + port - 0x10); + return IO::inb(m_ctrl + port - 0x10); ASSERT_NOT_REACHED(); } - void ATABus::read_buffer(uint16_t port, uint16_t* buffer, size_t words) + void ATADevice::read_buffer(uint16_t port, uint16_t* buffer, size_t words) { if (port <= 0x07) - return IO::insw(base + port - 0x00, buffer, words); + return IO::insw(m_base + port - 0x00, buffer, words); if (0x10 <= port && port <= 0x11) - return IO::insw(ctrl + port - 0x10, buffer, words); + return IO::insw(m_ctrl + port - 0x10, buffer, words); ASSERT_NOT_REACHED(); } - void ATABus::write(uint16_t port, uint8_t data) + void ATADevice::io_write(uint16_t port, uint8_t data) { if (port <= 0x07) - return IO::outb(base + port, data); + return IO::outb(m_base + port, data); if (0x10 <= port && port <= 0x11) - return IO::outb(ctrl + port - 0x10, data); + return IO::outb(m_ctrl + port - 0x10, data); ASSERT_NOT_REACHED(); } - void ATABus::write_buffer(uint16_t port, const uint16_t* buffer, size_t words) + void ATADevice::write_buffer(uint16_t port, const uint16_t* buffer, size_t words) { uint16_t io_port = 0; if (port <= 0x07) - io_port = base + port; + io_port = m_base + port; if (0x10 <= port && port <= 0x11) - io_port = ctrl + port - 0x10; + io_port = m_ctrl + port - 0x10; ASSERT(io_port); for (size_t i = 0; i < words; i++) IO::outw(io_port, buffer[i]); } - BAN::ErrorOr ATABus::wait(bool wait_drq) + BAN::ErrorOr ATADevice::wait(bool wait_drq) { for (uint32_t i = 0; i < 4; i++) - read(ATA_PORT_ALT_STATUS); + io_read(ATA_PORT_ALT_STATUS); uint8_t status = ATA_STATUS_BSY; while (status & ATA_STATUS_BSY) - status = read(ATA_PORT_STATUS); + status = io_read(ATA_PORT_STATUS); while (wait_drq && !(status & ATA_STATUS_DRQ)) { @@ -332,15 +339,15 @@ namespace Kernel return error(); if (status & ATA_STATUS_DF) return BAN::Error::from_errno(EIO); - status = read(ATA_PORT_STATUS); + status = io_read(ATA_PORT_STATUS); } return {}; } - BAN::Error ATABus::error() + BAN::Error ATADevice::error() { - uint8_t err = read(ATA_PORT_ERROR); + uint8_t err = io_read(ATA_PORT_ERROR); if (err & ATA_ERROR_AMNF) return BAN::Error::from_c_string("Address mark not found."); if (err & ATA_ERROR_TKZNF) @@ -362,8 +369,8 @@ namespace Kernel dev_t ATADevice::dev() const { - ASSERT(controller); - return controller->dev(); + ASSERT(m_controller); + return m_controller->dev(); } BAN::ErrorOr ATADevice::read(size_t offset, void* buffer, size_t bytes)