diff --git a/repos/os/run/usb_block.run b/repos/os/run/usb_block.run new file mode 100644 index 000000000..6acd5632c --- /dev/null +++ b/repos/os/run/usb_block.run @@ -0,0 +1,161 @@ +set use_qemu [have_include "power_on/qemu"] + +# +# Build +# + +set build_components { + core init + drivers/timer + drivers/usb + drivers/usb_block + server/report_rom + test/blk/cli + test/blk/bench +} + +lappend_if [have_spec gpio] build_components drivers/gpio + +source ${genode_dir}/repos/base/run/platform_drv.inc +append_platform_drv_build_components + +build $build_components + +create_boot_directory + +# +# Generate config +# + +set config { + + + + + + + + + + + + + + + + + } + +append_platform_drv_config + +append_if [have_spec gpio] config { + + + + + } + +append config { + + + + + + + + + + + + + + + + + } +append_if [expr !$use_qemu] config { + + + + + + + + + + + +} +append_if $use_qemu config { + } +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# +# Boot modules +# + +# generic modules +set boot_modules { + core init timer report_rom usb_drv usb_block_drv + test-blk-cli test-blk-bench + ld.lib.so libc.lib.so +} + +append_platform_drv_boot_modules + +build_boot_image $boot_modules + +# +# Execute test case +# +set disk_image "bin/test.img" +set cmd "dd if=/dev/zero of=$disk_image bs=1M count=16" +if {$use_qemu} { + puts "creating disk image:\n$cmd" + catch { exec sh -c $cmd } +} + +# +# Qemu opts for EHCI +# +append qemu_args " -m 128 -nographic -M pc -boot order=d " +append qemu_args " -drive if=none,id=disk,file=$disk_image " +append qemu_args " -device usb-ehci,id=ehci -device usb-storage,bus=ehci.0,drive=disk " + +run_genode_until {.*child "test-usb" exited with exit value 0.*} 100 diff --git a/repos/os/src/drivers/usb_block/README b/repos/os/src/drivers/usb_block/README new file mode 100644 index 000000000..0190d8149 --- /dev/null +++ b/repos/os/src/drivers/usb_block/README @@ -0,0 +1,71 @@ +This directory contains an USB Mass Storage Bulk-Only driver. It uses +the Usb session interface to access the USB device and provides Genode's +Block service to its client. + +Behavior +-------- + +This driver only supports USB Mass Storage Bulk-Only devices that use the +SCSI Block Commands set (direct-access). Devices using different command +sets, e.g, SD/HC devices or some external disc drives will not work properly +if at all. The following configuration snippets demonstrates how to use the +driver: + +! +! +! +! +! +! +! +! +! + +The drivers 'config' node features a few attributes. First, there is the 'label' +attribute. This attribute specifies the label used when opening the Usb session +connection. A matching policy has to be configured at the USB host controller +driver: + +! +! +! +! +! +! +! +! +! + +For static configurations the 'label' value may be chosen freely or may even +be ommitted entirely. On the other hand it is best for dynamic configurations +to choose a unique label, e.g. 'usb--', at run-time (this most likely +involves other components that generate configurations for the 'usb_block_drv' +as well as the 'usb_drv'). + + +The second attribute is 'report'. If its value is 'yes' the driver +will generate a 'devices' report that contains meta information about the +USB device it is accessing and hence the Block session it is provding, e.g.: + +! +! +! + +The report contains a 'device' node that includes the device's vendor name and +the product description as well as its block size and the number of blocks. +Although the parent node is called 'devices' the driver is only able to access +one and the same device and the report will therefore always contain one device +only. + +In addition to other attributes that can be used to configure sepecific aspects +of the driver. The 'writeable' attribute denotes the permission of the Block +session client to write to the USB device. Independent thereof the driver will +query the device and will set the Block session operations accordingly. The +'interface' specifies the USB interface the driver should use. If the device +provides multiple SCSI devices the 'lun' attribute is used to select the right +one. + +The configuration of the USB block driver cannot be changed at run-time. The +driver is either used in a static system configuration where it is configured +once or in case of a dynamic system configuration a new driver instance with +a proper configuration is created on demand. diff --git a/repos/os/src/drivers/usb_block/cbw_csw.h b/repos/os/src/drivers/usb_block/cbw_csw.h new file mode 100644 index 000000000..45a4e32c9 --- /dev/null +++ b/repos/os/src/drivers/usb_block/cbw_csw.h @@ -0,0 +1,257 @@ +/* + * \brief USB Command Block and Status Wrapper + * \author Josef Soentgen + * \date 2016-02-08 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _CBW_CSW_H_ +#define _CBW_CSW_H_ + +#include + +namespace Usb { + struct Cbw; + struct Csw; +} + + +using Genode::uint8_t; +using Genode::uint32_t; +using Genode::uint64_t; +using Genode::size_t; +using Genode::addr_t; + + +/***************************************************** + ** USB Command Block/Status Wrapper implementation ** + *****************************************************/ + +struct Usb::Cbw : Genode::Mmio +{ + enum { LENGTH = 31 }; + + enum { SIG = 0x43425355U }; + struct Sig : Register<0x0, 32> { }; /* signature */ + struct Tag : Register<0x4, 32> { }; /* transaction unique identifier */ + struct Dtl : Register<0x8, 32> { }; /* data transfer length */ + struct Flg : Register<0xc, 8> { }; /* flags */ + struct Lun : Register<0xd, 8> { }; /* logical unit number */ + struct Cbl : Register<0xe, 8> { }; /* command buffer length */ + + Cbw(addr_t addr, uint32_t t, uint32_t d, + uint8_t f, uint8_t l, uint8_t len) : Mmio(addr) + { + write(SIG); + write(t); + write(d); + write(f); + write(l); + write(len); + } + + void dump() + { + PLOG("Sig: 0x%04x", read()); + PLOG("Tag: %u", read()); + PLOG("Dtl: %u", read()); + PLOG("Flg: 0x%x", read()); + PLOG("Lun: %u", read()); + PLOG("Cbl: %u", read()); + } +}; + + +struct Test_unit_ready : Usb::Cbw, + Scsi::Test_unit_ready +{ + Test_unit_ready(addr_t addr, uint32_t tag, uint8_t lun) + : + Cbw(addr, tag, 0, Usb::ENDPOINT_IN, lun, + Scsi::Test_unit_ready::LENGTH), + Scsi::Test_unit_ready(addr+15) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump TEST_UNIT_READY command --"); + Cbw::dump(); + Scsi::Cmd_6::dump(); + } +}; + + +struct Request_sense : Usb::Cbw, Scsi::Request_sense +{ + Request_sense(addr_t addr, uint32_t tag, uint8_t lun) + : + Cbw(addr, tag, Scsi::Request_sense_response::LENGTH, + Usb::ENDPOINT_IN, lun, Scsi::Request_sense::LENGTH), + Scsi::Request_sense(addr+15) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump REQUEST_SENSE command --"); + Cbw::dump(); + Scsi::Cmd_6::dump(); + } +}; + + +struct Inquiry : Usb::Cbw, Scsi::Inquiry +{ + Inquiry(addr_t addr, uint32_t tag, uint8_t lun) + : + Cbw(addr, tag, Scsi::Inquiry_response::LENGTH, + Usb::ENDPOINT_IN, lun, Scsi::Inquiry::LENGTH), + Scsi::Inquiry(addr+15) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump INQUIRY command --"); + Cbw::dump(); + Scsi::Cmd_6::dump(); + } +}; + + +struct Read_capacity_10 : Usb::Cbw, Scsi::Read_capacity_10 +{ + Read_capacity_10(addr_t addr, uint32_t tag, uint8_t lun) + : + Cbw(addr, tag, Scsi::Capacity_response_10::LENGTH, + Usb::ENDPOINT_IN, lun, Scsi::Read_capacity_10::LENGTH), + Scsi::Read_capacity_10(addr+15) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump READ_CAPACITY_10 command --"); + Cbw::dump(); + Scsi::Cmd_10::dump(); + } +}; + + +struct Read_10 : Usb::Cbw, Scsi::Read_10 +{ + Read_10(addr_t addr, uint32_t tag, uint8_t lun, + uint32_t lba, uint32_t len, uint32_t block_size) + : + Cbw(addr, tag, len * block_size, + Usb::ENDPOINT_IN, lun, Scsi::Read_10::LENGTH), + Scsi::Read_10(addr+15, lba, len) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump READ_10 command --"); + Cbw::dump(); + Scsi::Cmd_10::dump(); + } +}; + + +struct Write_10 : Usb::Cbw, Scsi::Write_10 +{ + Write_10(addr_t addr, uint32_t tag, uint8_t lun, + uint32_t lba, uint32_t len, uint32_t block_size) + : + Cbw(addr, tag, len * block_size, + Usb::ENDPOINT_OUT, lun, Scsi::Write_10::LENGTH), + Scsi::Write_10(addr+15, lba, len) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump WRITE_10 command --"); + Cbw::dump(); + Scsi::Cmd_10::dump(); + } +}; + + +struct Read_capacity_16 : Usb::Cbw, Scsi::Read_capacity_16 +{ + Read_capacity_16(addr_t addr, uint32_t tag, uint8_t lun) + : + Cbw(addr, tag, Scsi::Capacity_response_16::LENGTH, + Usb::ENDPOINT_IN, lun, Scsi::Read_capacity_16::LENGTH), + Scsi::Read_capacity_16(addr+15) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump READ_CAPACITY_16 command --"); + Cbw::dump(); + Scsi::Cmd_16::dump(); + } +}; + + +struct Read_16 : Usb::Cbw, Scsi::Read_16 +{ + Read_16(addr_t addr, uint32_t tag, uint8_t lun, + uint32_t lba, uint32_t len, uint32_t block_size) + : + Cbw(addr, tag, len * block_size, + Usb::ENDPOINT_IN, lun, Scsi::Read_16::LENGTH), + Scsi::Read_16(addr+15, lba, len) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump READ_16 command --"); + Cbw::dump(); + Scsi::Cmd_16::dump(); + } +}; + + +struct Write_16 : Usb::Cbw, Scsi::Write_16 +{ + Write_16(addr_t addr, uint32_t tag, uint8_t lun, + uint32_t lba, uint32_t len, uint32_t block_size) + : + Cbw(addr, tag, len * block_size, + Usb::ENDPOINT_OUT, lun, Scsi::Write_16::LENGTH), + Scsi::Write_16(addr+15, lba, len) + { if (verbose_scsi) dump(); } + + void dump() + { + PLOG("--- Dump WRITE_16 command --"); + Cbw::dump(); + Scsi::Cmd_16::dump(); + } +}; + + +struct Usb::Csw : Genode::Mmio +{ + enum { LENGTH = 13 }; + + enum { SIG = 0x53425355U }; + struct Sig : Register<0x0, 32> { }; /* signature */ + struct Tag : Register<0x4, 32> { }; /* transaction identifier */ + struct Dr : Register<0x8, 32> { }; /* data residue */ + enum { PASSED = 0, FAILED = 1, PHASE_ERROR = 2 }; + struct Sts : Register<0xc, 8> { }; /* status */ + + Csw(addr_t addr) : Mmio(addr) { } + + uint32_t sig() const { return read(); } + uint32_t tag() const { return read(); } + uint32_t dr() const { return read(); } + uint32_t sts() const { return read(); } +}; + +#endif /* _CBW_CSW_H_ */ diff --git a/repos/os/src/drivers/usb_block/main.cc b/repos/os/src/drivers/usb_block/main.cc new file mode 100644 index 000000000..ddccdd3db --- /dev/null +++ b/repos/os/src/drivers/usb_block/main.cc @@ -0,0 +1,803 @@ +/* + * \brief Usb session to Block session translator + * \author Josef Soentgen + * \date 2016-02-08 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static bool verbose_scsi = false; + +/* local includes */ +#include + + +namespace Usb { + using namespace Genode; + + struct Block_driver; +} + + +/********************************************************* + ** USB Mass Storage (BBB) Block::Driver implementation ** + *********************************************************/ + +struct Usb::Block_driver : Usb::Completion, + Block::Driver +{ + Server::Entrypoint &ep; + + Genode::Signal_context_capability announce_sigh; + + /* + * Pending block request + */ + struct Block_request + { + Block::Packet_descriptor packet; + Block::sector_t lba; + char *buffer; + size_t size; + bool read; + bool pending = false; + } req; + + bool initialized = false; + bool device_plugged = false; + + /** + * Handle stage change signal + */ + void handle_state_change(unsigned) + { + if (!usb.plugged()) { + PDBG("Device unplugged"); + device_plugged = false; + return; + } + + if (initialized) { + PERR("Device was already initialized"); + return; + } + + PDBG("Device plugged"); + + if (initialize()) + Genode::Signal_transmitter(announce_sigh).submit(); + } + + Server::Signal_rpc_member state_change_dispatcher = { + ep, *this, &Block_driver::handle_state_change }; + + /* + * Read Usb session label from the configuration + */ + static char const *get_label() + { + static Genode::String<256> usb_label; + try { + Genode::config()->xml_node().attribute("label").value(&usb_label); + return usb_label.string(); + } catch (...) { } + + return "usb_storage"; + } + + Genode::Allocator_avl alloc; + Usb::Connection usb { &alloc, get_label(), 2 * (1<<20), state_change_dispatcher }; + Usb::Device device; + + Genode::Reporter reporter { "devices" }; + bool _report_device = false; + + Block::Session::Operations _block_ops; + Block::sector_t _block_count; + Genode::size_t _block_size; + + bool _writeable = false; + + bool force_cmd_10 = false; + + uint8_t active_interface = 0; + uint8_t active_lun = 0; + + uint32_t active_tag = 0; + uint32_t new_tag() { return ++active_tag % 0xffffffu; } + + enum Tags { + INQ_TAG = 0x01, RDY_TAG = 0x02, CAP_TAG = 0x04, + REQ_TAG = 0x08, SS_TAG = 0x10 + }; + enum Endpoints { IN = 0, OUT = 1 }; + + /* + * Completion used while initializing the device + */ + struct Init_completion : Usb::Completion + { + bool inquiry = false; + bool unit_ready = false; + bool read_capacity = false; + bool request_sense = false; + + bool no_medium = false; + bool try_again = false; + + Usb::Device &device; + uint8_t interface; + + Block::sector_t block_count; + Genode::size_t block_size; + + char vendor[Scsi::Inquiry_response::Vid::ITEMS+1]; + char product[Scsi::Inquiry_response::Pid::ITEMS+1]; + + Init_completion(Usb::Device &device, uint8_t interface) + : device(device), interface(interface) { } + + void complete(Packet_descriptor &p) + { + Interface iface = device.interface(interface); + + if (p.type != Packet_descriptor::BULK) { + PERR("Can only handle BULK packets"); + iface.release(p); + return; + } + + if (!p.succeded) { + PERR("init complete error: packet not succeded"); + iface.release(p); + return; + } + + /* OUT transfer finished */ + if (!p.is_read_transfer()) { + iface.release(p); + return; + } + + int const actual_size = p.transfer.actual_size; + char * const data = reinterpret_cast(iface.content(p)); + + using namespace Scsi; + + switch (actual_size) { + case 36: /* min INQUIRY data size */ + case Inquiry_response::LENGTH: + { + Inquiry_response r((addr_t)data); + if (verbose_scsi) r.dump(); + + if (!r.sbc()) + PWRN("Device does not use SCSI Block Commands and may not work"); + + r.get_id(vendor, sizeof(vendor)); + r.get_id(product, sizeof(product)); + break; + } + case Capacity_response_10::LENGTH: + { + Capacity_response_10 r((addr_t)data); + if (verbose_scsi) r.dump(); + + block_count = r.block_count(); + block_size = r.block_size(); + break; + } + case Capacity_response_16::LENGTH: + { + Capacity_response_16 r((addr_t)data); + if (verbose_scsi) r.dump(); + + block_count = r.block_count(); + block_size = r.block_size(); + break; + } + case Request_sense_response::LENGTH: + { + Request_sense_response r((addr_t)data); + if (verbose_scsi) r.dump(); + + uint8_t const asc = r.read(); + uint8_t const asq = r.read(); + + enum { MEDIUM_NOT_PRESENT = 0x3a, NOT_READY_TO_READY_CHANGE = 0x28 }; + switch (asc) { + case MEDIUM_NOT_PRESENT: + PERR("Not ready - medium not present"); + no_medium = true; + break; + case NOT_READY_TO_READY_CHANGE: /* asq == 0x00 */ + PWRN("Not ready - try again"); + try_again = true; + break; + default: + PERR("Request_sense_response asc: 0x%02x asq: 0x%02x", asc, asq); + break; + } + break; + } + case Csw::LENGTH: + { + Csw csw((addr_t)data); + + uint32_t const sig = csw.sig(); + if (sig != Csw::SIG) { + PERR("CSW signature does not match: 0x%04x", sig); + break; + } + + uint32_t const tag = csw.tag(); + uint8_t const status = csw.sts(); + if (status != Csw::PASSED) { + PERR("CSW failed: 0x%02x tag: %u", status, tag); + break; + } + + inquiry |= tag & INQ_TAG; + unit_ready |= tag & RDY_TAG; + read_capacity |= tag & CAP_TAG; + request_sense |= tag & REQ_TAG; + break; + } + default: break; + } + + iface.release(p); + } + } init { device, active_interface }; + + /** + * Send CBW + */ + void cbw(void *cb, Completion *c, bool block = false) + { + enum { CBW_VALID_SIZE = Cbw::LENGTH }; + Usb::Interface &iface = device.interface(active_interface); + Usb::Endpoint &ep = iface.endpoint(OUT); + Usb::Packet_descriptor p = iface.alloc(CBW_VALID_SIZE); + memcpy(iface.content(p), cb, CBW_VALID_SIZE); + iface.bulk_transfer(p, ep, 0, block, c); + } + + /** + * Receive CSW + */ + void csw(Completion *c, bool block = false) + { + enum { CSW_VALID_SIZE = Csw::LENGTH }; + Usb::Interface &iface = device.interface(active_interface); + Usb::Endpoint &ep = iface.endpoint(IN); + Usb::Packet_descriptor p = iface.alloc(CSW_VALID_SIZE); + iface.bulk_transfer(p, ep, 0, block, c); + } + + /** + * Receive response + */ + void resp(size_t size, Completion *c, bool block = false) + { + Usb::Interface &iface = device.interface(active_interface); + Usb::Endpoint &ep = iface.endpoint(IN); + Usb::Packet_descriptor p = iface.alloc(size); + iface.bulk_transfer(p, ep, 0, block, c); + } + + /** + * Report block device + */ + void report_device(char const *vendor, char const *product, + Block::sector_t count, size_t size) + { + try { + Genode::Reporter::Xml_generator xml(reporter, [&] () { + xml.node("device", [&] () { + xml.attribute("vendor", vendor); + xml.attribute("product", product); + xml.attribute("block_count", count); + xml.attribute("block_size", size); + xml.attribute("writeable", _writeable); + }); + }); + } catch (...) { PWRN("Could not report block device"); } + } + + /** + * Initialize device + * + * All USB transfers in this method are done synchronously. First we reset + * the device, than we query the max LUN. Afterwards we start sending CBWs. + * + * Since it might take some time for the device to get ready to use, we + * have to check the SCSI logical unit several times. + */ + bool initialize() + { + device.update_config(); + + Interface &iface = device.interface(active_interface); + try { iface.claim(); } + catch (Usb::Session::Interface_already_claimed) { + PERR("Device already claimed"); + return false; + } catch (Usb::Session::Interface_not_found) { + PERR("Interface not found"); + return false; + } + + enum { + ICLASS_MASS_STORAGE = 8, + ISUBCLASS_SCSI = 6, + IPROTO_BULK_ONLY = 80 + }; + try { + Alternate_interface &alt_iface = iface.alternate_interface(0); + iface.set_alternate_interface(alt_iface); + + if (alt_iface.iclass != ICLASS_MASS_STORAGE + || alt_iface.isubclass != ISUBCLASS_SCSI + || alt_iface.iprotocol != IPROTO_BULK_ONLY) { + PERR("No mass storage SCSI bulk-only device"); + return false; + } + } catch (Usb::Session::Interface_not_found) { + PERR("Interface not found"); + return false; + } + + try { + /* reset */ + Usb::Packet_descriptor p = iface.alloc(0); + iface.control_transfer(p, 0x21, 0xff, 0, active_interface, 100); + if (!p.succeded) { + PERR("Could not reset device"); + throw -1; + } + + /* + * Let us do GetMaxLUN and simply ignore the return value because none + * of the devices that were tested did infact report another value than 0. + */ + p = iface.alloc(1); + iface.control_transfer(p, 0xa1, 0xfe, 0, active_interface, 100); + uint8_t max_lun = *(uint8_t*)iface.content(p); + if (p.succeded && max_lun == 0) { max_lun = 1; } + iface.release(p); + + /* + * Query device + */ + + char cbw_buffer[Cbw::LENGTH]; + + /* + * We should probably execute the SCSI REPORT_LUNS command first + * but we will receive LOGICAL UNIT NOT SUPPORTED if we try to + * access an invalid unit. The user has to specify the LUN in + * the configuration anyway. + */ + + /* Scsi::Opcode::INQUIRY */ + Inquiry inq((addr_t)cbw_buffer, INQ_TAG, active_lun); + + cbw(cbw_buffer, &init, true); + resp(Scsi::Inquiry_response::LENGTH, &init, true); + csw(&init, true); + + if (!init.inquiry) { + PWRN("Inquiry_cmd failed"); + throw -1; + } + + /* Scsi::Opcode::TEST_UNIT_READY */ + { + Timer::Connection timer; + /* + * It might take some time for devices to get ready (e.g. the ZTE Open C + * takes 3 retries to actually present us a medium and another try to + * let us use the medium. + */ + enum { MAX_RETRIES = 10 }; + int retries; + for (retries = 0; retries < MAX_RETRIES; retries++) { + Test_unit_ready unit_ready((addr_t)cbw_buffer, RDY_TAG, active_lun); + + cbw(cbw_buffer, &init, true); + csw(&init, true); + + if (!init.unit_ready) { + Request_sense sense((addr_t)cbw_buffer, REQ_TAG, active_lun); + + cbw(cbw_buffer, &init, true); + resp(Scsi::Request_sense_response::LENGTH, &init, true); + csw(&init, true); + if (!init.request_sense) { + PWRN("Request_sense failed"); + throw -1; + } + + if (init.no_medium) { + /* do nothing for now */ + } else if (init.try_again) { + init.try_again = false; + } else break; + } else break; + + timer.msleep(1000); + } + if (retries == MAX_RETRIES) { + PWRN("Test_unit_ready_cmd failed"); + throw -1; + } + } + + /* Scsi::Opcode::READ_CAPACITY_16 */ + Read_capacity_16 read_cap((addr_t)cbw_buffer, CAP_TAG, active_lun); + + cbw(cbw_buffer, &init, true); + resp(Scsi::Capacity_response_16::LENGTH, &init, true); + csw(&init, true); + + if (!init.read_capacity) { + /* try Scsi::Opcode::READ_CAPACITY_10 next */ + Read_capacity_10 read_cap((addr_t)cbw_buffer, CAP_TAG, active_lun); + + cbw(cbw_buffer, &init, true); + resp(Scsi::Capacity_response_10::LENGTH, &init, true); + csw(&init, true); + + if (!init.read_capacity) { + PWRN("Read_capacity_cmd failed"); + throw -1; + } + + PWRN("Device does not support CDB 16-byte commands, force 10-byte commands"); + force_cmd_10 = true; + } + + _block_size = init.block_size; + _block_count = init.block_count; + + initialized = true; + device_plugged = true; + + char vendor[32]; + char product[32]; + + device.manufactorer_string.to_char(vendor, sizeof(vendor)); + device.product_string.to_char(product, sizeof(product)); + + PINF("Found USB device: %s (%s) block size: %zu count: %llu", + vendor, product, _block_size, _block_count); + + if (_report_device) + report_device(init.vendor, init.product, + init.block_count, init.block_size); + return true; + } catch (int) { + /* handle command failures */ + PERR("Could not initialize storage device"); + return false; + } catch (...) { + /* handle Usb::Session failures */ + PERR("Could not initialize storage device"); + throw; + } + return false; + } + + /** + * Execute pending request + * + * Called after the CBW has been successfully received by the device + * to initiate read/write transaction. + */ + bool execute_pending_request() + { + Usb::Interface &iface = device.interface(active_interface); + Usb::Endpoint ep = iface.endpoint(req.read ? IN : OUT); + Usb::Packet_descriptor p = iface.alloc(req.size); + + if (!req.read) memcpy(iface.content(p), req.buffer, req.size); + + iface.bulk_transfer(p, ep, 0, false, this); + + return true; + } + + /** + * Acknowledge currently pending request + * + * After receiving the CSW ack the request at the Block session. + */ + void ack_pending_request(bool success = true) + { + /* + * Needs to be reset bevor calling ack_packet to prevent getting a new + * request imediately and throwing Request_congestion() in io() again. + */ + req.pending = false; + + Block::Packet_descriptor p = req.packet; + ack_packet(p, success); + } + + /** + * Handle packet completion + * + * This method is called several times while doing one transaction. First + * the CWB is sent, than the payload read or written. At the end, the CSW + * is requested. + */ + void complete(Packet_descriptor &p) + { + Interface iface = device.interface(active_interface); + + if (p.type != Packet_descriptor::BULK) { + PERR("No BULK packet"); + iface.release(p); + return; + } + + if (!p.succeded) { + PERR("complete error: packet not succeded"); + if (req.pending) { + PERR("req.pending: tag: %u is_read: %d buffer: %p lba: %llu size: %zu", + active_tag, req.read, req.buffer, req.lba, req.size); + ack_pending_request(false); + } + iface.release(p); + return; + } + + static bool request_executed = false; + if (!p.is_read_transfer()) { + /* send read/write request */ + if (req.pending) { + + /* + * The CBW was successfully sent to the device, now read/write the + * actual content. + */ + if (!request_executed) { + request_executed = execute_pending_request(); + } else { + /* the content was successfully written, get the CSW */ + csw(this); + } + } + + iface.release(p); + return; + } + + int actual_size = p.transfer.actual_size; + if (actual_size < 0) { + PERR("Transfer actual size: %d", actual_size); + actual_size = 0; + } + + /* the size indicates an IN I/O packet */ + if ((uint32_t)actual_size >= _block_size) { + if (req.pending) { + + /* the content was successfully read, get the CSW */ + memcpy(req.buffer, iface.content(p), actual_size); + csw(this); + } + + iface.release(p); + return; + } + + /* when ending up here, we should have gotten an CSW packet */ + if (actual_size != Csw::LENGTH) + PWRN("This is not the actual size you are looking for"); + + do { + Csw csw((addr_t)iface.content(p)); + + uint32_t const sig = csw.sig(); + if (sig != Csw::SIG) { + PERR("CSW signature does not match: 0x%04x", sig); + break; + } + + uint32_t const tag = csw.tag(); + if (tag != active_tag) { + PERR("CSW tag mismatch. Got %u expected: %u", tag, active_tag); + break; + } + + uint8_t const status = csw.sts(); + if (status != Csw::PASSED) { + PERR("CSW failed: 0x%02x tag: %u req.read: %d req.buffer: %p " + "req.lba: %llu req.size: %zu", status, tag, req.read, + req.buffer, req.lba, req.size); + break; + } + + uint32_t const dr = csw.dr(); + if (dr) PWRN("CSW data residue: %u not considered", dr); + + /* ack Block::Packet_descriptor */ + request_executed = false; + ack_pending_request(); + } while (0); + + iface.release(p); + } + + /** + * Parse configuration + */ + void parse_config() + { + Genode::Xml_node config = Genode::config()->xml_node(); + + _block_ops.set_operation(Block::Packet_descriptor::READ); + + _writeable = config.attribute_value("writeable", false); + if (_writeable) + _block_ops.set_operation(Block::Packet_descriptor::WRITE); + + _report_device = config.attribute_value("report", false); + + active_interface = config.attribute_value("interface", 0); + active_lun = config.attribute_value("lun", 0); + + verbose_scsi = config.attribute_value("verbose_scsi", false); + } + + /** + * Constructor + * + * \param alloc allocator used by Usb::Connection + * \param ep Server::Endpoint + * \param sigh signal context used for annoucing Block service + */ + Block_driver(Genode::Allocator &alloc, Server::Entrypoint &ep, + Genode::Signal_context_capability sigh) + : + ep(ep), announce_sigh(sigh), alloc(Genode::env()->heap()), + device(Genode::env()->heap(), usb, ep) + { + parse_config(); + reporter.enabled(true); + + /* USB device gets initialized by handle_state_change() */ + } + + /** + * Send CBW + */ + void send_cbw(Block::sector_t lba, size_t len, bool read) + { + uint32_t const t = new_tag(); + + char cb[Cbw::LENGTH]; + if (read) { + if (!force_cmd_10) Read_16 r((addr_t)cb, t, active_lun, lba, len, _block_size); + else Read_10 r((addr_t)cb, t, active_lun, lba, len, _block_size); + } else { + if (!force_cmd_10) Write_16 w((addr_t)cb, t, active_lun, lba, len, _block_size); + else Write_10 w((addr_t)cb, t, active_lun, lba, len, _block_size); + } + + cbw(cb, this); + } + + /** + * Perform IO/ request + * + * \param read set to true when reading, false when writting + * \param lba address of the starting block + * \param buffer source/destination buffer + * \param p Block::Packet_descriptor + */ + void io(bool read, Block::sector_t lba, size_t count, + char *buffer, Block::Packet_descriptor &p) + { + if (!device_plugged) throw Io_error(); + if (lba+count > _block_count) throw Io_error(); + if (req.pending) throw Request_congestion(); + + req.pending = true; + req.packet = p; + req.lba = lba; + req.size = count * _block_size; + req.buffer = buffer; + req.read = read; + + send_cbw(lba, count, read); + } + + /******************************* + ** Block::Driver interface ** + *******************************/ + + size_t block_size() override { return _block_size; } + Block::sector_t block_count() override { return _block_count; } + Block::Session::Operations ops() override { return _block_ops; } + + void read(Block::sector_t lba, size_t count, + char *buffer, Block::Packet_descriptor &p) override { + io(true, lba, count, buffer, p); } + + void write(Block::sector_t lba, size_t count, + char const *buffer, Block::Packet_descriptor &p) override { + io(false, lba, count, const_cast(buffer), p); } + + void sync() override { /* maybe implement SYNCHRONIZE_CACHE_10/16? */ } +}; + + +struct Main +{ + Server::Entrypoint &ep; + + void announce(unsigned) + { + Genode::env()->parent()->announce(ep.manage(root)); + } + + Server::Signal_rpc_member
announce_dispatcher { + ep, *this, &Main::announce }; + + struct Factory : Block::Driver_factory + { + Genode::Allocator &alloc; + Server::Entrypoint &ep; + Genode::Signal_context_capability sigh; + + Usb::Block_driver *driver = nullptr; + + Factory(Genode::Allocator &alloc, Server::Entrypoint &ep, + Genode::Signal_context_capability sigh) + : alloc(alloc), ep(ep), sigh(sigh) + { + driver = new (Genode::env()->heap()) Usb::Block_driver(alloc, ep, sigh); + } + + Block::Driver *create() { return driver; } + + void destroy(Block::Driver *driver) { } + }; + + Factory factory { *Genode::env()->heap(), ep, announce_dispatcher }; + Block::Root root; + + Main(Server::Entrypoint &ep) + : ep(ep), root(ep, Genode::env()->heap(), factory) { } +}; + + +/************ + ** Server ** + ************/ + +namespace Server { + char const *name() { return "usb_block_ep"; } + size_t stack_size() { return 2*1024*sizeof(long); } + void construct(Entrypoint &ep) { static Main main(ep); } +} diff --git a/repos/os/src/drivers/usb_block/scsi.h b/repos/os/src/drivers/usb_block/scsi.h new file mode 100644 index 000000000..12b26dcab --- /dev/null +++ b/repos/os/src/drivers/usb_block/scsi.h @@ -0,0 +1,427 @@ +/* + * \brief SCSI Block Commands + * \author Josef Soentgen + * \date 2016-02-08 + */ + +/* + * Copyright (C) 2016 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +#ifndef _SCSI_H_ +#define _SCSI_H_ + +#include +#include +#include + +namespace Scsi { + + using namespace Genode; + + uint16_t be16(uint16_t val); + uint32_t be32(uint32_t val); + uint64_t be64(uint64_t val); + + /****************** + * SCSI commands ** + ******************/ + + enum Opcode { + TEST_UNIT_READY = 0x00, + REQUEST_SENSE = 0x03, + INQUIRY = 0x12, + START_STOP = 0x1B, + READ_CAPACITY_10 = 0x25, + READ_10 = 0x28, + WRITE_10 = 0x2a, + READ_16 = 0x88, + WRITE_16 = 0x8a, + READ_CAPACITY_16 = 0x9e, + }; + + struct Inquiry_response; + struct Request_sense_response; + struct Capacity_response_10; + struct Capacity_response_16; + + struct Cmd_6; + struct Test_unit_ready; + struct Request_sense; + struct Inquiry; + struct Start_stop; + + struct Cmd_10; + struct Read_capacity_10; + struct Io_10; + struct Read_10; + struct Write_10; + + struct Cmd_16; + struct Read_capacity_16; + struct Io_16; + struct Read_16; + struct Write_16; +} + + +/******************* +** Endian helper ** +*******************/ + +Genode::uint16_t Scsi::be16(Genode::uint16_t val) +{ + Genode::uint8_t *p = reinterpret_cast(&val); + return (p[1]<<0)|(p[0]<<8); +} + +Genode::uint32_t Scsi::be32(Genode::uint32_t val) +{ + Genode::uint8_t *p = reinterpret_cast(&val); + return (p[3]<<0)|(p[2]<<8)|(p[1]<<16)|(p[0]<<24); +} + +Genode::uint64_t Scsi::be64(Genode::uint64_t val) +{ + Genode::uint8_t *p = reinterpret_cast(&val); + return ((((Genode::uint64_t)(p[3]<<0)|(p[2]<<8)|(p[1]<<16)|(p[0]<<24))<<32)| + (((Genode::uint32_t)(p[7]<<0)|(p[6]<<8)|(p[5]<<16)|(p[4]<<24)))); +} + + +/*************************** + * SCSI command responses ** + ***************************/ + +struct Scsi::Inquiry_response : Genode::Mmio +{ + enum { LENGTH = 36 /* default */ + 20 /* drive serial number and vendor unique */}; + + struct Dt : Register<0x0, 8> { }; /* device type */ + struct Rm : Register<0x1, 8> { struct Rmb : Bitfield<7, 1> { }; }; /* removable media */ + struct Ver : Register<0x2, 8> { }; /* version */ + struct Rdf : Register<0x3, 8> { }; /* response data format */ + struct Al : Register<0x4, 8> { }; /* additional ength */ + struct Flg : Register<0x7, 8> { }; /* flags */ + struct Vid : Register_array<0x8, 8, 8, 8> { }; /* vendor identification */ + struct Pid : Register_array<0x10, 8, 16, 8> { }; /* product identification */ + struct Rev : Register_array<0x20, 8, 4, 8> { }; /* product revision level */ + + Inquiry_response(addr_t addr) : Mmio(addr) { } + + bool sbc() const { return read
() == 0x00; } + bool removable() const { return read(); } + + template + bool get_id(char *dst, size_t len) + { + if (len < ID::ITEMS+1) return false; + for (uint32_t i = 0; i < ID::ITEMS; i++) dst[i] = read(i); + dst[ID::ITEMS] = 0; + return true; + } + + void dump() + { + PLOG("--- Dump INQUIRY data ---"); + PLOG("Dt: 0x%02x", read
()); + PLOG("Rm::Rmb: %u ", read()); + PLOG("Ver: 0x%02x", read()); + PLOG("Rdf: 0x%02x", read()); + PLOG("Al: %u", read()); + PLOG("Flg: 0x%02x", read()); + } +}; + + +struct Scsi::Request_sense_response : Genode::Mmio +{ + enum { LENGTH = 18 }; + + struct Rc : Register<0x0, 8> + { + struct V : Bitfield<6, 1> { }; /* valid bit */ + struct Ec : Bitfield<0, 7> { }; /* error code */ + }; /* response code */ + struct Flg : Register<0x2, 8> + { + struct Sk : Bitfield<0, 4> { }; /* sense key */ + }; /* flags */ + struct Inf : Register<0x3, 32> { }; /* information BE */ + struct Asl : Register<0x7, 8> { }; /* additional sense length */ + struct Csi : Register<0x8, 32> { }; /* command specific information BE */ + struct Asc : Register<0xc, 8> { }; /* additional sense code */ + struct Asq : Register<0xd, 8> { }; /* additional sense code qualifier */ + struct Fru : Register<0xe, 8> { }; /* field replaceable unit code */ + struct Sks : Register<0xf, 32> { }; /* sense key specific (3 byte) */ + + Request_sense_response(addr_t addr) : Mmio(addr) { } + + void dump() + { + PLOG("--- Dump REQUEST_SENSE data ---"); + PLOG("Rc::V: %u", read()); + PLOG("Rc::Ec: 0x%02x", read()); + PLOG("Flg::Sk: 0x%02x", read()); + PLOG("Asc: 0x%02x", read()); + PLOG("Asq: 0x%02x", read()); + } +}; + + +struct Scsi::Capacity_response_10 : Genode::Mmio +{ + enum { LENGTH = 8 }; + + struct Bc : Register<0x0, 32> { }; + struct Bs : Register<0x4, 32> { }; + + Capacity_response_10(addr_t addr) : Mmio(addr) { } + + uint32_t block_count() const { return be32(read()); } + uint32_t block_size() const { return be32(read()); } + + void dump() + { + PLOG("--- Dump READ_CAPACITY_10 data ---"); + PLOG("Bc: 0x%04x", block_count()); + PLOG("Bs: 0x%04x", block_size()); + } +}; + + +struct Scsi::Capacity_response_16 : Genode::Mmio +{ + enum { LENGTH = 32 }; + + struct Bc : Register<0x0, 64> { }; + struct Bs : Register<0x8, 32> { }; + + Capacity_response_16(addr_t addr) : Mmio(addr) { } + + uint64_t block_count() const { return be64(read()); } + uint32_t block_size() const { return be32(read()); } + + void dump() + { + PLOG("--- Dump READ_CAPACITY_16 data ---"); + PLOG("Bc: 0x%08llx", block_count()); + PLOG("Bs: 0x%04x", block_size()); + } +}; + + +/************************* + ** CBD 6 byte commands ** + *************************/ + +struct Scsi::Cmd_6 : Genode::Mmio +{ + enum { LENGTH = 6 }; + struct Op : Register<0x0, 8> { }; /* SCSI command */ + struct Lba : Register<0x2, 16> { }; /* logical block address */ + struct Len : Register<0x4, 8> { }; /* transfer length */ + struct Ctl : Register<0x5, 8> { }; /* controll */ + + Cmd_6(addr_t addr) : Mmio(addr) { memset((void*)addr, 0, LENGTH); } + + void dump() + { + PLOG("Op: 0x%02x", read()); + PLOG("Lba: 0x%02x", be16(read())); + PLOG("Len: %u", read()); + PLOG("Ctl: 0x%02x", read()); + } +}; + + +struct Scsi::Test_unit_ready : Cmd_6 +{ + Test_unit_ready(addr_t addr) : Cmd_6(addr) + { + write(Opcode::TEST_UNIT_READY); + } +}; + + +struct Scsi::Request_sense : Cmd_6 +{ + Request_sense(addr_t addr) : Cmd_6(addr) + { + write(Opcode::REQUEST_SENSE); + write(Request_sense_response::LENGTH); + } +}; + + +struct Scsi::Inquiry : Cmd_6 +{ + Inquiry(addr_t addr) : Cmd_6(addr) + { + write(Opcode::INQUIRY); + write(Inquiry_response::LENGTH); + } +}; + + +/* not in use for now but might come in handy in the future */ +struct Scsi::Start_stop : Genode::Mmio +{ + enum { LENGTH = 6 }; + struct Op : Register<0x0, 8> { }; /* SCSI command */ + struct I : Register<0x1, 8> { + struct Immed : Bitfield<0, 1> { }; }; /* immediate */ + struct Flg : Register<0x4, 8> + { + struct Pwc : Bitfield<4, 4> { }; /* power condition */ + struct Loej : Bitfield<1, 1> { }; /* load eject */ + struct St : Bitfield<0, 1> { }; /* start */ + }; /* flags */ + + Start_stop(addr_t addr) : Mmio(addr) + { + memset((void*)addr, 0, LENGTH); + + write(Opcode::START_STOP); + write(1); + write(0); + write(1); + write(1); + } + + void dump() + { + PLOG("Op: 0x%02x", read()); + PLOG("I::Immed: %u", read()); + PLOG("Flg::Pwc: 0x%02x", read()); + PLOG("Flg::Loej: %u", read()); + PLOG("Flg::St: %u", read()); + } +}; + + +/************************** + ** CBD 10 byte commands ** + **************************/ + +struct Scsi::Cmd_10 : Genode::Mmio +{ + enum { LENGTH = 10 }; + struct Op : Register<0x0, 8> { }; /* SCSI command */ + struct Lba : Register<0x2, 32> { }; /* logical block address */ + struct Len : Register<0x7, 16> { }; /* transfer length */ + struct Ctl : Register<0x9, 8> { }; /* controll */ + + Cmd_10(addr_t addr) : Mmio(addr) { memset((void*)addr, 0, LENGTH); } + + void dump() + { + PLOG("Op: 0x%0x", read()); + PLOG("Lba: 0x%0x", be32(read())); + PLOG("Len: %u", be16(read())); + PLOG("Ctl: 0x%0x", read()); + } +}; + + +struct Scsi::Read_capacity_10 : Cmd_10 +{ + Read_capacity_10(addr_t addr) : Cmd_10(addr) + { + write(Opcode::READ_CAPACITY_10); + } +}; + + +struct Scsi::Io_10 : Cmd_10 +{ + Io_10(addr_t addr, uint32_t lba, uint16_t len) : Cmd_10(addr) + { + write(be32(lba)); + write(be16(len)); + } +}; + + +struct Scsi::Read_10 : Io_10 +{ + Read_10(addr_t addr, uint32_t lba, uint16_t len) : Io_10(addr, lba, len) + { + write(Opcode::READ_10); + } +}; + + +struct Scsi::Write_10 : Io_10 +{ + Write_10(addr_t addr, uint32_t lba, uint16_t len) : Io_10(addr, lba, len) + { + write(Opcode::WRITE_10); + } +}; + + +/*********************************** + ** CBD 16 long LBA byte commands ** + ***********************************/ + +struct Scsi::Cmd_16 : Genode::Mmio +{ + enum { LENGTH = 16 }; + struct Op : Register<0x0, 8> { }; /* SCSI command */ + struct Lba : Register<0x2, 64> { }; /* logical block address */ + struct Len : Register<0xa, 32> { }; /* transfer length */ + struct Ctl : Register<0xf, 8> { }; /* controll */ + + Cmd_16(addr_t addr) : Mmio(addr) { memset((void*)addr, 0, LENGTH); } + + void dump() + { + PLOG("Op: 0x%0x", read()); + PLOG("Lba: 0x%0llx", be64(read())); + PLOG("Len: %u", be32(read())); + PLOG("Ctl: 0x%0x", read()); + } +}; + + +struct Scsi::Read_capacity_16 : Cmd_16 +{ + Read_capacity_16(addr_t addr) : Cmd_16(addr) + { + write(Opcode::READ_CAPACITY_16); + } +}; + + +struct Scsi::Io_16 : Cmd_16 +{ + Io_16(addr_t addr, uint64_t lba, uint32_t len) : Cmd_16(addr) + { + write(be64(lba)); + write(be32(len)); + } +}; + + +struct Scsi::Read_16 : Io_16 +{ + Read_16(addr_t addr, uint64_t lba, uint32_t len) : Io_16(addr, lba, len) + { + write(Opcode::READ_16); + } +}; + + +struct Scsi::Write_16 : Io_16 +{ + Write_16(addr_t addr, uint64_t lba, uint32_t len) : Io_16(addr, lba, len) + { + write(Opcode::WRITE_16); + } +}; + +#endif /* _SCSI_H_ */ diff --git a/repos/os/src/drivers/usb_block/target.mk b/repos/os/src/drivers/usb_block/target.mk new file mode 100644 index 000000000..65e674716 --- /dev/null +++ b/repos/os/src/drivers/usb_block/target.mk @@ -0,0 +1,4 @@ +TARGET = usb_block_drv +SRC_CC = main.cc +INC_DIR = $(PRG_DIR) +LIBS = base config server