浏览代码

Merge remote-tracking branch 'SCSI2SD-V6/master'

Eric Helgeson 3 年之前
父节点
当前提交
3eac4b780d

+ 1 - 0
lib/SCSI2SD/.gitattributes

@@ -0,0 +1 @@
+* text=auto

+ 6 - 0
lib/SCSI2SD/.gitignore

@@ -0,0 +1,6 @@
+*.c.un~
+*.c~
+*.h.un~
+*.h~
+build/
+src/scsi2sd-util6/build

+ 0 - 0
lib/SCSI2SD/.gitmodules


+ 158 - 0
lib/SCSI2SD/CHANGELOG

@@ -0,0 +1,158 @@
+20220514        6.4.14
+    - Fix firmware version displaying as "0.0" in scsi2sd-util when there is no
+    SD card inserted.
+    - Reduce some delays for slight performance improvements
+    - Use SD High-Speed mode on V6 2021 hardware.
+
+20220121        6.4.13
+    - Fix SCSI writes with sector sizes larger than 512.
+    - Fix 2Gb SD cards being detected as 1Gb
+    - Fix for CD emulation stopping the drive when receiving a load/eject
+    request
+
+20210810        6.4.12
+    - Fix USB disconnect issue when no SD card is installed.
+
+20210628        6.4.11
+    - Remove the "Blind Writes" option from scsi2sd-util. The firmware no longer
+    requires this option for improved write performance
+    - Fix firmware hang if there was no activity for a few minutes
+
+20210508        6.4.4
+    - More bug fixes for firmware hanging during read/writes
+
+20210504        6.4.3
+    - More bug fixes while writing data.
+
+20210503        6.4.2
+    - Bug fix for errors while writing data
+    - Fix for scsi2sd-util6 under Windows which may fail to detect a board without a SD card inserted.
+
+20210426		6.4.1
+    - 2021 hardware support release.
+    - scsi2sd-util6 stability improvements (contributed by Jonathan Wakely.
+
+20201012		6.3.2
+	- Increase limit of READ/WRITE BUFFER command for improved compatibility
+	SGI Iris hosts
+	- Add new config option to enable blind writes to improve SD card write
+	performance. This was previously enabled default but causes problems
+	with some SCSI hosts.
+
+20200419		6.3.1
+	- Added checks to ensure the correct firmware version is loaded.
+	V6 Rev.f and older boards need the "firmware.V6.revF.dfu" firmware updates.
+	V6 2020c and newer boards need the "firmware.V6.2020.dfu" firmware updates.
+
+20200216		6.3.0
+	- Breaking change: Firmware updates on windows now require the UsbDK
+	driver to be installed.
+
+	- Fix regression preventing some machines from booting
+	- Update libusb to 1.0.23 for dfu-util
+	- Modify dfu-util to use the UsbDk driver via libusb.
+
+20200130		6.2.15
+	- Fix issue writing more than 512kb of data in one write command
+	(bug introduced 6.2.7)
+	- Fix possible data corruption bug when reading or writing more than
+	64kb per command (fixed in most cases by 6.2.14)
+
+20200101		6.2.14
+	- Fix for invalid CDROM READ TOC responses (Thanks Simon Gander)
+	- Fix for data corruption for hosts that transfer more than 64k per
+	write.
+
+20191208		6.2.9
+	- Fix to prevent sending floppy geometry mode page when not configured as
+	a floppy (Thanks Landon Rodgers)
+	- Fix for VMS 5.5-2 Inquiry allocation lengths. Requires setting "vms" quirk
+	mode in the XML config (Thanks Landon Rodgers)
+
+20191030		6.2.8
+	- Fix incorrect results from the self-test function.
+
+20191009		6.2.7
+	- Slight improvements to data throughput, which may assist SCSI hosts with
+	short timeouts.
+
+20190529		6.2.5
+	- Add scsi mode page 0 support
+	- Fix SD card hotswap bug when the SCSI host is constantly polling
+
+20190502		6.2.4 (Beta)
+	- Port XEBEC support from v5 firmware
+	- Add Flexible Disk Drive Geometry SCSI MODE page
+	- Stability improvements
+	- Fix regression from 6.1.3 firmware for Kurzweil K2000
+
+20181011		6.2.1
+	- Fix bug in USB disk interface with disks over 4GB
+
+20180926        6.2.0
+	- Fix bug with non-512 byte sectors.
+	- Fix bug when writing with multiple SCSI devices on the chain
+	- Performance improvements to write speeds.
+
+20180430		6.1.4
+	- Fix bug in self-test function
+
+20180131		6.1.3
+	- Fix bug that caused stability issues with 10MB/s transfers.
+
+20171128		6.1.2
+	- Fix synchronous negotiation bugs
+
+20170520		6.1.1
+	- Performance improvements to improve throughput at all scsi speeds
+	- Add new "turbo" speed option to boost speeds.
+		- May not be reliable, and use is not supported.
+		- Async timings trimmed
+		- Sync speeds boosted to theoretical 15.625MB/s, with 12.0MB/s measured
+		read througput.
+		- SD card put in "high speed" mode.
+		- USB for configuration/firmware updates is disabled in turbo mode when
+		processing SCSI commands. A power cycle may be required to connect
+		via USB to reset the 48MHz clock back to 48MHz.
+	- Fix scsi2sd-util6 size and sector-size inputs
+	- Fix crash when configured scsi disk starting sector is less than
+	SD card size
+
+20170329		6.1.0
+	- Enable synchronous transfers on SCSI1 hosts
+	- Support 4MB/s sync transfers for Amiga A590 (WD33C93)
+	- Merge v4.7 release changes, excluding custom mode/inquiry pages
+	- various bug fixes
+
+20161006		6.0.13
+	- Fixed SCSI timing issue
+	- Added glitch filter on SCSI signals.
+	- Re-implemented SCSI parity checking.
+
+20160912		6.0.10
+	- Fixed write issue with UHS-I Speed Class 3 SD cards.
+	- More stability bug fixes
+
+20160827		6.0.8
+	- Fixed "protocol error" issues when saving configuration to SD cards.
+	- Synchronous transfers supported ! 5MB/s and 10MB/s supported.
+	- Fix for accessing data via USB with more than 2 devices configured.
+
+20160815		6.0.6
+	- Fix performance bugs
+
+20160814		6.05
+	- More SCSI bug fixes (some timing issues resolved in the FPGA image)
+	- Firmware update support using scsi2sd-util6.
+
+20160716		6.03 (BETA3)
+	- SCSI bug fixes.
+
+20160616		6.01
+	- Improved SD card compatibility
+	- Fixed SCSI interfaces on slower SCSI controllers
+	- Significant performance improvements
+	- Added SD card hotswap support.
+
+20160528		6.0
+	- First BETA firmware for the 6.0 hardware version of the SCSI2SD.

+ 63 - 0
lib/SCSI2SD/COPYING

@@ -0,0 +1,63 @@
+SCSI2SD is distributed under the GNU General Public License version 3. Please
+find the license in the file gplv3.txt
+
+The additional license terms apply:
+
+////////////////////////////////////////////////////////////////////////////////////
+Code generated via STM32CubeMX:
+
+COPYRIGHT(c) 2015 STMicroelectronics
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+  1. Redistributions of source code must retain the above copyright notice,
+     this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright notice,
+     this list of conditions and the following disclaimer in the documentation
+     and/or other materials provided with the distribution.
+  3. Neither the name of STMicroelectronics nor the names of its contributors
+     may be used to endorse or promote products derived from this software
+     without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+////////////////////////////////////////////////////////////////////////////////////
+ARM CMSIS, distributed with STM32CubeMX:
+
+Copyright (c) 2009 - 2015 ARM LIMITED
+
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+- Neither the name of ARM nor the names of its contributors may be used
+  to endorse or promote products derived from this software without
+  specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+

+ 674 - 0
lib/SCSI2SD/gplv3.txt

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 252 - 0
lib/SCSI2SD/include/scsi2sd.h

@@ -0,0 +1,252 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef scsi2sd_h
+#define scsi2sd_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Common type definitions shared between the firmware and config tools
+
+	The configuration data is now stored on the SD card, occupying the
+	last 2 sectors.
+
+	BoardConfig
+	TargetConfig (disk 0)
+	TargetConfig (disk 1)
+	TargetConfig (disk 2)
+	TargetConfig (disk 3)
+	TargetConfig (disk 4)
+	TargetConfig (disk 5)
+	TargetConfig (disk 6)
+
+*/
+
+#include "stdint.h"
+
+#define S2S_MAX_TARGETS 7
+#define S2S_CFG_SIZE (S2S_MAX_TARGETS * sizeof(S2S_TargetCfg) + sizeof(S2S_BoardCfg))
+
+typedef enum
+{
+	S2S_CFG_TARGET_ID_BITS = 0x07,
+	S2S_CFG_TARGET_ENABLED = 0x80
+} S2S_CFG_TARGET_FLAGS;
+
+typedef enum
+{
+	S2S_CFG_ENABLE_UNIT_ATTENTION = 1,
+	S2S_CFG_ENABLE_PARITY = 2,
+	S2S_CFG_ENABLE_SCSI2 = 4,
+	S2S_CFG_DISABLE_GLITCH = 8,
+	S2S_CFG_ENABLE_CACHE = 16,
+	S2S_CFG_ENABLE_DISCONNECT = 32,
+	S2S_CFG_ENABLE_SEL_LATCH = 64,
+	S2S_CFG_MAP_LUNS_TO_IDS = 128
+} S2S_CFG_FLAGS;
+
+typedef enum
+{
+	S2S_CFG_ENABLE_TERMINATOR = 1
+	//S2S_CFG_ENABLE_BLIND_WRITES = 2, // Obsolete
+} S2S_CFG_FLAGS6;
+
+typedef enum
+{
+	S2S_CFG_FIXED,
+	S2S_CFG_REMOVEABLE,
+	S2S_CFG_OPTICAL,
+	S2S_CFG_FLOPPY_14MB,
+	S2S_CFG_MO,
+	S2S_CFG_SEQUENTIAL
+
+} S2S_CFG_TYPE;
+
+typedef enum
+{
+	S2S_CFG_QUIRKS_NONE = 0,
+	S2S_CFG_QUIRKS_APPLE = 1,
+	S2S_CFG_QUIRKS_OMTI = 2,
+	S2S_CFG_QUIRKS_XEBEC = 4,
+	S2S_CFG_QUIRKS_VMS = 8
+} S2S_CFG_QUIRKS;
+
+typedef enum
+{
+	S2S_CFG_SPEED_NoLimit,
+	S2S_CFG_SPEED_ASYNC_15,
+	S2S_CFG_SPEED_ASYNC_33,
+	S2S_CFG_SPEED_ASYNC_50,
+	S2S_CFG_SPEED_SYNC_5,
+	S2S_CFG_SPEED_SYNC_10,
+	S2S_CFG_SPEED_TURBO
+} S2S_CFG_SPEED;
+
+typedef struct __attribute__((packed))
+{
+	// bits 7 -> 3 = S2S_CFG_TARGET_FLAGS
+	// bits 2 -> 0 = target SCSI ID.
+	uint8_t scsiId;
+
+	uint8_t deviceType; // S2S_CFG_TYPE
+	uint8_t flagsDEPRECATED; // S2S_CFG_FLAGS, removed in v4.5
+	uint8_t deviceTypeModifier; // Used in INQUIRY response.
+
+	uint32_t sdSectorStart;
+	uint32_t scsiSectors;
+
+	uint16_t bytesPerSector;
+
+	// Max allowed by legacy IBM-PC bios is 6 bits (63)
+	uint16_t sectorsPerTrack;
+
+	// MS-Dos up to 7.10 will crash on >= 256 heads.
+	uint16_t headsPerCylinder;
+
+
+	char vendor[8];
+	char prodId[16];
+	char revision[4];
+	char serial[16];
+
+	uint16_t quirks; // S2S_CFG_QUIRKS
+
+	uint8_t reserved[64]; // Pad out to 128 bytes for main section.
+} S2S_TargetCfg;
+
+typedef struct __attribute__((packed))
+{
+	char magic[4]; // 'BCFG'
+	uint8_t flags; // S2S_CFG_FLAGS
+	uint8_t startupDelay; // Seconds.
+	uint8_t selectionDelay; // milliseconds. 255 = auto
+	uint8_t flags6; // S2S_CFG_FLAGS6
+
+	uint8_t scsiSpeed;
+
+	uint8_t reserved[119]; // Pad out to 128 bytes
+} S2S_BoardCfg;
+
+typedef enum
+{
+	S2S_CMD_NONE, // Invalid
+
+	// Command content:
+	// uint8_t S2S_CFG_PING
+	// Response:
+	// S2S_CFG_STATUS
+	S2S_CMD_PING,
+
+	// Command content:
+	// uint8_t S2S_CFG_WRITEFLASH
+	// uint8_t[256] flashData
+	// uint8_t flashArray
+	// uint8_t flashRow
+	// Response:
+	// S2S_CFG_STATUS
+	S2S_CMD_WRITEFLASH,
+
+	// Command content:
+	// uint8_t S2S_CFG_READFLASH
+	// uint8_t flashArray
+	// uint8_t flashRow
+	// Response:
+	// 256 bytes of flash
+	S2S_CMD_READFLASH,
+
+	// Command content:
+	// uint8_t S2S_CFG_REBOOT
+	// Response: None.
+	S2S_CMD_REBOOT,
+
+	// Command content:
+	// uint8_t S2S_CFG_INFO
+	// Response:
+	// uint8_t[16] CSD
+	// uint8_t[16] CID
+	S2S_CMD_SDINFO,
+
+	// Command content:
+	// uint8_t S2S_CFG_SCSITEST
+	// Response:
+	// S2S_CFG_STATUS
+	// uint8_t result code (0 = passed)
+	S2S_CMD_SCSITEST,
+
+	// Command content:
+	// uint8_t S2S_CFG_DEVINFO
+	// Response:
+	// uint16_t protocol version (MSB)
+	// uint16_t firmware version (MSB)
+	// uint32_t SD capacity(MSB)
+	S2S_CMD_DEVINFO,
+
+	// Command content:
+	// uint8_t S2S_CFG_SD_WRITE
+	// uint32_t Sector Number (MSB)
+	// uint8_t[512] data
+	// Response:
+	// S2S_CFG_STATUS
+	S2S_CMD_SD_WRITE,
+
+	// Command content:
+	// uint8_t S2S_CFG_SD_READ
+	// uint32_t Sector Number (MSB)
+	// Response:
+	// 512 bytes of data
+	S2S_CMD_SD_READ,
+
+	// Command content:
+	// uint8_t S2S_CFG_DEBUG
+	// Response:
+	S2S_CMD_DEBUG,
+} S2S_COMMAND;
+
+typedef enum
+{
+	S2S_CFG_STATUS_GOOD,
+	S2S_CFG_STATUS_ERR,
+	S2S_CFG_STATUS_BUSY
+} S2S_CFG_STATUS;
+
+
+
+
+#ifdef __cplusplus
+} // extern "C"
+
+	#include <type_traits>
+	static_assert(
+		std::is_pod<S2S_TargetCfg>::value, "Misuse of TargetConfig struct"
+		);
+	static_assert(
+		sizeof(S2S_TargetCfg) == 128,
+		"TargetConfig struct size mismatch"
+		);
+
+	static_assert(
+		std::is_pod<S2S_BoardCfg>::value, "Misuse of BoardConfig struct"
+		);
+	static_assert(
+		sizeof(S2S_BoardCfg) == 128,
+		"BoardConfig struct size mismatch"
+		);
+
+#endif
+
+#endif

+ 13 - 0
lib/SCSI2SD/library.json

@@ -0,0 +1,13 @@
+{
+    "name": "SCSI2SD",
+    "version": "6.4.13",
+    "repository": { "type": "git", "url": "git://www.codesrc.com/git/SCSI2SD-V6" },
+    "authors": [{ "name": "Michael McMaster", "email": "michael@codesrc.com" }],
+    "license": "GPL-3.0-or-later",
+    "homepage": "http://www.codesrc.com/mediawiki/index.php/SCSI2SD",
+    "frameworks": "*",
+    "platforms": "*",
+    "build": {
+        "flags": ["-Iinclude", "-Isrc/firmware"]
+    }
+}

+ 324 - 0
lib/SCSI2SD/src/firmware/cdrom.c

@@ -0,0 +1,324 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "cdrom.h"
+
+#include <string.h>
+
+static const uint8_t SimpleTOC[] =
+{
+	0x00, // toc length, MSB
+	0x12, // toc length, LSB
+	0x01, // First track number
+	0x01, // Last track number,
+	// TRACK 1 Descriptor
+	0x00, // reserved
+	0x14, // Q sub-channel encodes current position, Digital track
+	0x01, // Track 1,
+	0x00, // Reserved
+	0x00,0x00,0x00,0x00, // Track start sector (LBA)
+	0x00, // reserved
+	0x14, // Q sub-channel encodes current position, Digital track
+	0xAA, // Leadout Track
+	0x00, // Reserved
+	0x00,0x00,0x00,0x00, // Track start sector (LBA)
+};
+
+static const uint8_t SessionTOC[] =
+{
+	0x00, // toc length, MSB
+	0x0A, // toc length, LSB
+	0x01, // First session number
+	0x01, // Last session number,
+	// TRACK 1 Descriptor
+	0x00, // reserved
+	0x14, // Q sub-channel encodes current position, Digital track
+	0x01, // First track number in last complete session
+	0x00, // Reserved
+	0x00,0x00,0x00,0x00 // LBA of first track in last session
+};
+
+
+static const uint8_t FullTOC[] =
+{
+	0x00, // toc length, MSB
+	0x44, // toc length, LSB
+	0x01, // First session number
+	0x01, // Last session number,
+	// A0 Descriptor
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0xA0, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x01, // First Track number.
+	0x00, // Disc type 00 = Mode 1
+	0x00,  // PFRAME
+	// A1
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0xA1, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x01, // Last Track number
+	0x00, // PSEC
+	0x00,  // PFRAME
+	// A2
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0xA2, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x79, // LEADOUT position BCD
+	0x59, // leadout PSEC BCD
+	0x74, // leadout PFRAME BCD
+	// TRACK 1 Descriptor
+	0x01, // session number
+	0x14, // ADR/Control
+	0x00, // TNO
+	0x01, // Point
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x00, // PMIN
+	0x00, // PSEC
+	0x00,  // PFRAME
+	// b0
+	0x01, // session number
+	0x54, // ADR/Control
+	0x00, // TNO
+	0xB1, // POINT
+	0x79, // Min BCD
+	0x59, // Sec BCD
+	0x74, // Frame BCD
+	0x00, // Zero
+	0x79, // PMIN BCD
+	0x59, // PSEC BCD
+	0x74,  // PFRAME BCD
+	// c0
+	0x01, // session number
+	0x54, // ADR/Control
+	0x00, // TNO
+	0xC0, // POINT
+	0x00, // Min
+	0x00, // Sec
+	0x00, // Frame
+	0x00, // Zero
+	0x00, // PMIN
+	0x00, // PSEC
+	0x00  // PFRAME
+};
+
+static void LBA2MSF(uint32_t LBA, uint8_t* MSF)
+{
+	MSF[0] = 0; // reserved.
+	MSF[3] = LBA % 75; // M
+	uint32_t rem = LBA / 75;
+
+	MSF[2] = rem % 60; // S
+	MSF[1] = rem / 60;
+
+}
+
+static void doReadTOC(int MSF, uint8_t track, uint16_t allocationLength)
+{
+	// We only support track 1.
+	// track 0 means "return all tracks"
+	if (track > 1)
+	{
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+	else
+	{
+		uint32_t len = sizeof(SimpleTOC);
+		memcpy(scsiDev.data, SimpleTOC, len);
+
+		uint32_t capacity = getScsiCapacity(
+			scsiDev.target->cfg->sdSectorStart,
+			scsiDev.target->liveCfg.bytesPerSector,
+			scsiDev.target->cfg->scsiSectors);
+
+		// Replace start of leadout track
+		if (MSF)
+		{
+			LBA2MSF(capacity, scsiDev.data + 0x10);
+		}
+		else
+		{
+			scsiDev.data[0x10] = capacity >> 24;
+			scsiDev.data[0x11] = capacity >> 16;
+			scsiDev.data[0x12] = capacity >> 8;
+			scsiDev.data[0x13] = capacity;
+		}
+
+		if (len > allocationLength)
+		{
+			len = allocationLength;
+		}
+		scsiDev.dataLen = len;
+		scsiDev.phase = DATA_IN;
+	}
+}
+
+static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
+{
+	uint32_t len = sizeof(SessionTOC);
+	memcpy(scsiDev.data, SessionTOC, len);
+
+	if (len > allocationLength)
+	{
+		len = allocationLength;
+	}
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
+static inline uint8_t
+fromBCD(uint8_t val)
+{
+	return ((val >> 4) * 10) + (val & 0xF);
+}
+
+static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLength)
+{
+	// We only support session 1.
+	if (session > 1)
+	{
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+	else
+	{
+		uint32_t len = sizeof(FullTOC);
+		memcpy(scsiDev.data, FullTOC, len);
+
+		if (convertBCD)
+		{
+			int descriptor = 4;
+			while (descriptor < len)
+			{
+				int i;
+				for (i = 0; i < 7; ++i)
+				{
+					scsiDev.data[descriptor + i] =
+						fromBCD(scsiDev.data[descriptor + 4 + i]);
+				}
+				descriptor += 11;
+			}
+
+		}
+
+		if (len > allocationLength)
+		{
+			len = allocationLength;
+		}
+		scsiDev.dataLen = len;
+		scsiDev.phase = DATA_IN;
+	}
+}
+
+static uint8_t SimpleHeader[] =
+{
+	0x01, // 2048byte user data, L-EC in 288 byte aux field.
+	0x00, // reserved
+	0x00, // reserved
+	0x00, // reserved
+	0x00,0x00,0x00,0x00 // Track start sector (LBA or MSF)
+};
+
+void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
+{
+	uint32_t len = sizeof(SimpleHeader);
+	memcpy(scsiDev.data, SimpleHeader, len);
+	if (len > allocationLength)
+	{
+		len = allocationLength;
+	}
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
+
+// Handle direct-access scsi device commands
+int scsiCDRomCommand()
+{
+	int commandHandled = 1;
+
+	uint8_t command = scsiDev.cdb[0];
+	if (command == 0x43)
+	{
+		// CD-ROM Read TOC
+		int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
+		uint8_t track = scsiDev.cdb[6];
+		uint16_t allocationLength =
+			(((uint32_t) scsiDev.cdb[7]) << 8) +
+			scsiDev.cdb[8];
+
+		// Reject MMC commands for now, otherwise the TOC data format
+		// won't be understood.
+		// The "format" field is reserved for SCSI-2
+		uint8_t format = scsiDev.cdb[2] & 0x0F;
+		switch (format)
+		{
+			case 0: doReadTOC(MSF, track, allocationLength); break; // SCSI-2
+			case 1: doReadSessionInfo(MSF, allocationLength); break; // MMC2
+			case 2: doReadFullTOC(0, track, allocationLength); break; // MMC2
+			case 3: doReadFullTOC(1, track, allocationLength); break; // MMC2
+			default:
+			{
+				scsiDev.status = CHECK_CONDITION;
+				scsiDev.target->sense.code = ILLEGAL_REQUEST;
+				scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+				scsiDev.phase = STATUS;
+			}
+		}
+	}
+	else if (command == 0x44)
+	{
+		// CD-ROM Read Header
+		int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
+		uint32_t lba = 0; // IGNORED for now
+		uint16_t allocationLength =
+			(((uint32_t) scsiDev.cdb[7]) << 8) +
+			scsiDev.cdb[8];
+		doReadHeader(MSF, lba, allocationLength);
+	}
+	else
+	{
+		commandHandled = 0;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/cdrom.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef CDROM_H
+#define CDROM_H
+
+int scsiCDRomCommand(void);
+
+#endif

+ 30 - 0
lib/SCSI2SD/src/firmware/config.h

@@ -0,0 +1,30 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_Config_H
+#define S2S_Config_H
+
+#include "scsi2sd.h"
+
+void s2s_configInit(S2S_BoardCfg* config);
+void s2s_debugInit(void);
+void s2s_configPoll(void);
+void s2s_configSave(int scsiId, uint16_t byesPerSector);
+
+const S2S_TargetCfg* s2s_getConfigByIndex(int index);
+const S2S_TargetCfg* s2s_getConfigById(int scsiId);
+
+#endif

+ 247 - 0
lib/SCSI2SD/src/firmware/diagnostic.c

@@ -0,0 +1,247 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "diagnostic.h"
+
+#include <string.h>
+
+static const uint8_t SupportedDiagnosticPages[] =
+{
+0x00, // Page Code
+0x00, // Reserved
+0x02, // Page length
+0x00, // Support "Supported diagnostic page"
+0x40  // Support "Translate address page"
+};
+
+void scsiSendDiagnostic()
+{
+	// SEND DIAGNOSTIC
+	// Pretend to do self-test. Actual data is returned via the
+	// RECEIVE DIAGNOSTIC RESULTS command.
+	int selfTest = scsiDev.cdb[1] & 0x04;
+	uint32_t paramLength =
+		(((uint32_t) scsiDev.cdb[3]) << 8) +
+		scsiDev.cdb[4];
+
+	if (!selfTest)
+	{
+		// Initiator sends us page data.
+		scsiDev.dataLen = paramLength;
+		scsiDev.phase = DATA_OUT;
+
+		if (scsiDev.dataLen > sizeof (scsiDev.data))
+		{
+			// Nowhere to store this data!
+			// Shouldn't happen - our buffer should be many magnitudes larger
+			// than the required size for diagnostic parameters.
+			scsiDev.target->sense.code = ILLEGAL_REQUEST;
+			scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+			scsiDev.status = CHECK_CONDITION;
+			scsiDev.phase = STATUS;
+		}
+	}
+	else
+	{
+		// Default command result will be a status of GOOD anyway.
+	}
+}
+
+void scsiReceiveDiagnostic()
+{
+	// RECEIVE DIAGNOSTIC RESULTS
+	// We assume scsiDev.data contains the contents of a previous
+	// SEND DIAGNOSTICS command.  We only care about the page-code part
+	// of the parameter list.
+	uint8_t pageCode = scsiDev.data[0];
+
+	int allocLength =
+		(((uint16_t) scsiDev.cdb[3]) << 8) +
+		scsiDev.cdb[4];
+
+
+	if (pageCode == 0x00)
+	{
+		memcpy(
+			scsiDev.data,
+			SupportedDiagnosticPages,
+			sizeof(SupportedDiagnosticPages));
+		scsiDev.dataLen = sizeof(SupportedDiagnosticPages);
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x40)
+	{
+		// Translate between logical block address, physical sector address, or
+		// physical bytes.
+		uint8_t suppliedFmt = scsiDev.data[4] & 0x7;
+		uint8_t translateFmt = scsiDev.data[5] & 0x7;
+
+		// Convert each supplied address back to a simple
+		// 64bit linear address, then convert back again.
+		uint64_t fromByteAddr =
+			scsiByteAddress(
+				scsiDev.target->liveCfg.bytesPerSector,
+				scsiDev.target->cfg->headsPerCylinder,
+				scsiDev.target->cfg->sectorsPerTrack,
+				suppliedFmt,
+				&scsiDev.data[6]);
+
+		scsiSaveByteAddress(
+			scsiDev.target->liveCfg.bytesPerSector,
+			scsiDev.target->cfg->headsPerCylinder,
+			scsiDev.target->cfg->sectorsPerTrack,
+			translateFmt,
+			fromByteAddr,
+			&scsiDev.data[6]);
+
+		// Fill out the rest of the response.
+		// (Clear out any optional bits).
+		scsiDev.data[4] = suppliedFmt;
+		scsiDev.data[5] = translateFmt;
+
+		scsiDev.dataLen = 14;
+		scsiDev.phase = DATA_IN;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+
+	if (scsiDev.phase == DATA_IN && scsiDev.dataLen > allocLength)
+	{
+		// simply truncate the response.
+		scsiDev.dataLen = allocLength;
+	}
+
+	{
+		// Set the first byte to indicate LUN presence.
+		if (scsiDev.lun) // We only support lun 0
+		{
+			scsiDev.data[0] = 0x7F;
+		}
+	}
+}
+
+void scsiReadBuffer()
+{
+	// READ BUFFER
+	// Used for testing the speed of the SCSI interface.
+	uint8_t mode = scsiDev.data[1] & 7;
+
+	int allocLength =
+		(((uint32_t) scsiDev.cdb[6]) << 16) +
+		(((uint32_t) scsiDev.cdb[7]) << 8) +
+		scsiDev.cdb[8];
+
+	if (mode == 0)
+	{
+		uint32_t maxSize = sizeof(scsiDev.data) - 4;
+		// 4 byte header
+		scsiDev.data[0] = 0;
+		scsiDev.data[1] = (maxSize >> 16) & 0xff;
+		scsiDev.data[2] = (maxSize >> 8) & 0xff;
+		scsiDev.data[3] = maxSize & 0xff;
+
+		scsiDev.dataLen =
+			(allocLength > sizeof(scsiDev.data)) ? sizeof(scsiDev.data) : allocLength;
+		scsiDev.phase = DATA_IN;
+	}
+	else if (mode == 0x2 && (scsiDev.cdb[2] == 0))
+	{
+		// TODO support BUFFER OFFSET fields in CDB
+		scsiDev.dataLen =
+			(allocLength > sizeof(scsiDev.data)) ? sizeof(scsiDev.data) : allocLength;
+		scsiDev.phase = DATA_IN;
+	}
+	else if (mode == 0x3)
+	{
+		uint32_t maxSize = sizeof(scsiDev.data) - 4;
+		// 4 byte header
+		scsiDev.data[0] = 0;
+		scsiDev.data[1] = (maxSize >> 16) & 0xff;
+		scsiDev.data[2] = (maxSize >> 8) & 0xff;
+		scsiDev.data[3] = maxSize & 0xff;
+
+		scsiDev.dataLen =
+			(allocLength > 4) ? 4: allocLength;
+		scsiDev.phase = DATA_IN;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+}
+
+// Callback after the DATA OUT phase is complete.
+static void doWriteBuffer(void)
+{
+	if (scsiDev.status == GOOD) // skip if we've already encountered an error
+	{
+		// scsiDev.dataLen bytes are in scsiDev.data
+		// Don't shift it down 4 bytes ... this space is taken by
+		// the read buffer header anyway
+		scsiDev.phase = STATUS;
+	}
+}
+
+void scsiWriteBuffer()
+{
+	// WRITE BUFFER
+	// Used for testing the speed of the SCSI interface.
+	uint8_t mode = scsiDev.data[1] & 7;
+
+	int allocLength =
+		(((uint32_t) scsiDev.cdb[6]) << 16) +
+		(((uint32_t) scsiDev.cdb[7]) << 8) +
+		scsiDev.cdb[8];
+
+	if ((mode == 0 || mode == 2) && allocLength <= sizeof(scsiDev.data))
+	{
+		scsiDev.dataLen = allocLength;
+		scsiDev.phase = DATA_OUT;
+		scsiDev.postDataOutHook = doWriteBuffer;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+}
+
+// XEBEC specific command. See
+// http://www.bitsavers.org/pdf/westernDigital/WD100x/79-000004_WD1002-SHD_OEM_Manual_Aug1984.pdf
+// Section 4.3.14
+void scsiWriteSectorBuffer()
+{
+	scsiDev.dataLen = scsiDev.target->liveCfg.bytesPerSector;
+	scsiDev.phase = DATA_OUT;
+	scsiDev.postDataOutHook = doWriteBuffer;
+}
+
+

+ 26 - 0
lib/SCSI2SD/src/firmware/diagnostic.h

@@ -0,0 +1,26 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef DIAGNOSTIC_H
+#define DIAGNOSTIC_H
+
+void scsiSendDiagnostic(void);
+void scsiReceiveDiagnostic(void);
+void scsiWriteBuffer(void);
+void scsiWriteSectorBuffer(void);
+void scsiReadBuffer(void);
+
+#endif

+ 58 - 0
lib/SCSI2SD/src/firmware/disk.h

@@ -0,0 +1,58 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef DISK_H
+#define DISK_H
+
+typedef enum
+{
+	// DISK_STARTED is stored per-target now as it's controlled by the
+	// START STOP UNIT command
+	OBSOLETE_DISK_STARTED = 1,
+	DISK_PRESENT = 2,     // SD card is physically present
+	DISK_INITIALISED = 4, // SD card responded to init sequence
+	DISK_WP = 8           // Write-protect.
+} DISK_STATE;
+
+typedef enum
+{
+	TRANSFER_READ,
+	TRANSFER_WRITE
+} TRANSFER_DIR;
+
+typedef struct
+{
+	int state;
+} BlockDevice;
+
+typedef struct
+{
+	int multiBlock; // True if we're using a multi-block SPI transfer.
+	uint32_t lba;
+	uint32_t blocks;
+
+	uint32_t currentBlock;
+} Transfer;
+
+extern BlockDevice blockDev;
+extern Transfer transfer;
+
+void scsiDiskInit(void);
+void scsiDiskReset(void);
+void scsiDiskPoll(void);
+int scsiDiskCommand(void);
+
+#endif

+ 228 - 0
lib/SCSI2SD/src/firmware/geometry.c

@@ -0,0 +1,228 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "geometry.h"
+#include "scsi.h"
+#include "sd.h"
+#include "config.h"
+
+#include <string.h>
+
+uint32_t getScsiCapacity(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSectors)
+{
+	uint32_t capacity =
+		(sdDev.capacity - sdSectorStart - S2S_CFG_SIZE) /
+			SDSectorsPerSCSISector(bytesPerSector);
+
+
+	if (sdDev.capacity == 0)
+	{
+		capacity = 0;
+	}
+	else if (sdSectorStart >= (sdDev.capacity - S2S_CFG_SIZE))
+	{
+		capacity = 0;
+	}
+	else if (scsiSectors && (capacity > scsiSectors))
+	{
+		capacity = scsiSectors;
+	}
+	return capacity;
+}
+
+
+uint32_t SCSISector2SD(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSector)
+{
+	return scsiSector * SDSectorsPerSCSISector(bytesPerSector) + sdSectorStart;
+}
+
+// Standard mapping according to ECMA-107 and ISO/IEC 9293:1994
+// Sector always starts at 1. There is no 0 sector.
+uint64_t CHS2LBA(
+	uint32_t c,
+	uint8_t h,
+	uint32_t s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack)
+{
+	return (
+		(((uint64_t)c) * headsPerCylinder + h) *
+			(uint64_t) sectorsPerTrack
+		) + (s - 1);
+}
+
+
+void LBA2CHS(
+	uint32_t lba,
+	uint32_t* c,
+	uint8_t* h,
+	uint32_t* s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack)
+{
+	*c = lba / (((uint32_t) sectorsPerTrack) * headsPerCylinder);
+	*h = (lba / sectorsPerTrack) % headsPerCylinder;
+	*s = (lba % sectorsPerTrack) + 1;
+}
+
+uint64_t scsiByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	const uint8_t* addr)
+{
+	uint64_t result;
+	switch (format)
+	{
+	case ADDRESS_BLOCK:
+	{
+		uint32_t lba =
+			(((uint32_t) addr[0]) << 24) +
+			(((uint32_t) addr[1]) << 16) +
+			(((uint32_t) addr[2]) << 8) +
+			addr[3];
+
+		result = (uint64_t) bytesPerSector * lba;
+	} break;
+
+	case ADDRESS_PHYSICAL_BYTE:
+	{
+		uint32_t cyl =
+			(((uint32_t) addr[0]) << 16) +
+			(((uint32_t) addr[1]) << 8) +
+			addr[2];
+
+		uint8_t head = addr[3];
+
+		uint32_t bytes =
+			(((uint32_t) addr[4]) << 24) +
+			(((uint32_t) addr[5]) << 16) +
+			(((uint32_t) addr[6]) << 8) +
+			addr[7];
+
+		result = CHS2LBA(cyl, head, 1, headsPerCylinder, sectorsPerTrack) *
+			(uint64_t) bytesPerSector + bytes;
+	} break;
+
+	case ADDRESS_PHYSICAL_SECTOR:
+	{
+		uint32_t cyl =
+			(((uint32_t) addr[0]) << 16) +
+			(((uint32_t) addr[1]) << 8) +
+			addr[2];
+
+		uint8_t head = scsiDev.data[3];
+
+		uint32_t sector =
+			(((uint32_t) addr[4]) << 24) +
+			(((uint32_t) addr[5]) << 16) +
+			(((uint32_t) addr[6]) << 8) +
+			addr[7];
+
+		result = CHS2LBA(cyl, head, sector, headsPerCylinder, sectorsPerTrack) * (uint64_t) bytesPerSector;
+	} break;
+
+	default:
+		result = (uint64_t) -1;
+	}
+
+	return result;
+}
+
+
+void scsiSaveByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	uint64_t byteAddr,
+	uint8_t* buf)
+{
+	uint32_t lba = byteAddr / bytesPerSector;
+	uint32_t byteOffset = byteAddr % bytesPerSector;
+
+	switch (format)
+	{
+	case ADDRESS_BLOCK:
+	{
+		buf[0] = lba >> 24;
+		buf[1] = lba >> 16;
+		buf[2] = lba >> 8;
+		buf[3] = lba;
+
+		buf[4] = 0;
+		buf[5] = 0;
+		buf[6] = 0;
+		buf[7] = 0;
+	} break;
+
+	case ADDRESS_PHYSICAL_BYTE:
+	{
+		uint32_t cyl;
+		uint8_t head;
+		uint32_t sector;
+		uint32_t bytes;
+
+		LBA2CHS(lba, &cyl, &head, &sector, headsPerCylinder, sectorsPerTrack);
+
+		bytes = sector * bytesPerSector + byteOffset;
+
+		buf[0] = cyl >> 16;
+		buf[1] = cyl >> 8;
+		buf[2] = cyl;
+
+		buf[3] = head;
+
+		buf[4] = bytes >> 24;
+		buf[5] = bytes >> 16;
+		buf[6] = bytes >> 8;
+		buf[7] = bytes;
+	} break;
+
+	case ADDRESS_PHYSICAL_SECTOR:
+	{
+		uint32_t cyl;
+		uint8_t head;
+		uint32_t sector;
+
+		LBA2CHS(lba, &cyl, &head, &sector, headsPerCylinder, sectorsPerTrack);
+
+		buf[0] = cyl >> 16;
+		buf[1] = cyl >> 8;
+		buf[2] = cyl;
+
+		buf[3] = head;
+
+		buf[4] = sector >> 24;
+		buf[5] = sector >> 16;
+		buf[6] = sector >> 8;
+		buf[7] = sector;
+	} break;
+
+	default:
+		memset(buf, 0, 8);
+	}
+
+}
+

+ 78 - 0
lib/SCSI2SD/src/firmware/geometry.h

@@ -0,0 +1,78 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef GEOMETRY_H
+#define GEOMETRY_H
+
+#include "config.h"
+// TODO #include "sd.h"
+#define SD_SECTOR_SIZE 512
+
+typedef enum
+{
+	ADDRESS_BLOCK = 0,
+	ADDRESS_PHYSICAL_BYTE = 4,
+	ADDRESS_PHYSICAL_SECTOR = 5
+} SCSI_ADDRESS_FORMAT;
+
+static inline int SDSectorsPerSCSISector(uint16_t bytesPerSector)
+{
+	return (bytesPerSector + SD_SECTOR_SIZE - 1) / SD_SECTOR_SIZE;
+}
+
+uint32_t getScsiCapacity(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSectors);
+
+uint32_t SCSISector2SD(
+	uint32_t sdSectorStart,
+	uint16_t bytesPerSector,
+	uint32_t scsiSector);
+
+uint64_t CHS2LBA(
+	uint32_t c,
+	uint8_t h,
+	uint32_t s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack);
+void LBA2CHS(
+	uint32_t lba,
+	uint32_t* c,
+	uint8_t* h,
+	uint32_t* s,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack);
+
+// Convert an address in the given SCSI_ADDRESS_FORMAT to
+// a linear byte address.
+// addr must be >= 8 bytes.
+uint64_t scsiByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	const uint8_t* addr);
+void scsiSaveByteAddress(
+	uint16_t bytesPerSector,
+	uint16_t headsPerCylinder,
+	uint16_t sectorsPerTrack,
+	int format,
+	uint64_t byteAddr,
+	uint8_t* buf);
+
+
+#endif

+ 267 - 0
lib/SCSI2SD/src/firmware/inquiry.c

@@ -0,0 +1,267 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//	Copyright (C) 2019 Landon Rodgers  <g.landon.rodgers@gmail.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "inquiry.h"
+
+#include <string.h>
+
+static uint8_t StandardResponse[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x00, // device type modifier
+0x02, // Complies with ANSI SCSI-2.
+0x01, // Response format is compatible with the old CCS format.
+0x1f, // standard length.
+0, 0, // Reserved
+0x18 // Enable sync and linked commands
+};
+// Vendor set by config 'c','o','d','e','s','r','c',' ',
+// prodId set by config'S','C','S','I','2','S','D',' ',' ',' ',' ',' ',' ',' ',' ',' ',
+// Revision set by config'2','.','0','a'
+
+
+static const uint8_t SupportedVitalPages[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x00, // Page Code
+0x00, // Reserved
+0x04, // Page length
+0x00, // Support "Supported vital product data pages"
+0x80, // Support "Unit serial number page"
+0x81, // Support "Implemented operating definition page"
+0x82 // Support "ASCII Implemented operating definition page"
+};
+
+static const uint8_t UnitSerialNumber[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x80, // Page Code
+0x00, // Reserved
+0x10, // Page length
+'c','o','d','e','s','r','c','-','1','2','3','4','5','6','7','8'
+};
+
+static const uint8_t ImpOperatingDefinition[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x81, // Page Code
+0x00, // Reserved
+0x03, // Page length
+0x03, // Current: SCSI-2 operating definition
+0x03, // Default: SCSI-2 operating definition
+0x03 // Supported (list): SCSI-2 operating definition.
+};
+
+static const uint8_t AscImpOperatingDefinition[] =
+{
+0x00, // "Direct-access device". AKA standard hard disk
+0x82, // Page Code
+0x00, // Reserved
+0x07, // Page length
+0x06, // Ascii length
+'S','C','S','I','-','2'
+};
+
+void s2s_scsiInquiry()
+{
+	uint8_t evpd = scsiDev.cdb[1] & 1; // enable vital product data.
+	uint8_t pageCode = scsiDev.cdb[2];
+	uint32_t allocationLength = scsiDev.cdb[4];
+
+	// SASI standard, X3T9.3_185_RevE  states that 0 == 256 bytes
+	// BUT SCSI 2 standard says 0 == 0.
+	if (scsiDev.compatMode <= COMPAT_SCSI1) // excludes COMPAT_SCSI2_DISABLED
+	{
+		if (allocationLength == 0) allocationLength = 256;
+	}
+
+	if (!evpd)
+	{
+		if (pageCode)
+		{
+			// error.
+			scsiDev.status = CHECK_CONDITION;
+			scsiDev.target->sense.code = ILLEGAL_REQUEST;
+			scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+			scsiDev.phase = STATUS;
+		}
+		else
+		{
+			const S2S_TargetCfg* config = scsiDev.target->cfg;
+			scsiDev.dataLen =
+				s2s_getStandardInquiry(
+					config,
+					scsiDev.data,
+					sizeof(scsiDev.data));
+			scsiDev.phase = DATA_IN;
+		}
+	}
+	else if (pageCode == 0x00)
+	{
+		memcpy(scsiDev.data, SupportedVitalPages, sizeof(SupportedVitalPages));
+		scsiDev.dataLen = sizeof(SupportedVitalPages);
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x80)
+	{
+		memcpy(scsiDev.data, UnitSerialNumber, sizeof(UnitSerialNumber));
+		scsiDev.dataLen = sizeof(UnitSerialNumber);
+        const S2S_TargetCfg* config = scsiDev.target->cfg;
+        memcpy(&scsiDev.data[4], config->serial, sizeof(config->serial));
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x81)
+	{
+		memcpy(
+			scsiDev.data,
+			ImpOperatingDefinition,
+			sizeof(ImpOperatingDefinition));
+		scsiDev.dataLen = sizeof(ImpOperatingDefinition);
+		scsiDev.phase = DATA_IN;
+	}
+	else if (pageCode == 0x82)
+	{
+		memcpy(
+			scsiDev.data,
+			AscImpOperatingDefinition,
+			sizeof(AscImpOperatingDefinition));
+		scsiDev.dataLen = sizeof(AscImpOperatingDefinition);
+		scsiDev.phase = DATA_IN;
+	}
+	else
+	{
+		// error.
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+
+
+	if (scsiDev.phase == DATA_IN)
+	{
+		// VAX workaround
+		if (allocationLength == 255 &&
+			(scsiDev.target->cfg->quirks & S2S_CFG_QUIRKS_VMS))
+		{
+			allocationLength = 254;
+		}
+
+		// "real" hard drives send back exactly allocationLenth bytes, padded
+		// with zeroes. This only seems to happen for Inquiry responses, and not
+		// other commands that also supply an allocation length such as Mode Sense or
+		// Request Sense.
+		// (See below for exception to this rule when 0 allocation length)
+		if (scsiDev.dataLen < allocationLength)
+		{
+			memset(
+				&scsiDev.data[scsiDev.dataLen],
+				0,
+				allocationLength - scsiDev.dataLen);
+		}
+		// Spec 8.2.5 requires us to simply truncate the response if it's
+		// too big.
+		scsiDev.dataLen = allocationLength;
+
+		// Set the device type as needed.
+		scsiDev.data[0] = getDeviceTypeQualifier();
+
+		switch (scsiDev.target->cfg->deviceType)
+		{
+		case S2S_CFG_OPTICAL:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+
+		case S2S_CFG_SEQUENTIAL:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+
+		case S2S_CFG_MO:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+
+		case S2S_CFG_FLOPPY_14MB:
+		case S2S_CFG_REMOVEABLE:
+			scsiDev.data[1] |= 0x80; // Removable bit.
+			break;
+		default:
+			// Accept defaults for a fixed disk.
+			break;
+		}
+	}
+
+	// Set the first byte to indicate LUN presence.
+	if (scsiDev.lun) // We only support lun 0
+	{
+		scsiDev.data[0] = 0x7F;
+	}
+}
+
+uint32_t s2s_getStandardInquiry(
+	const S2S_TargetCfg* cfg, uint8_t* out, uint32_t maxlen
+	)
+{
+	uint32_t buflen = sizeof(StandardResponse);
+	if (buflen > maxlen) buflen = maxlen;
+
+	memcpy(out, StandardResponse, buflen);
+	out[1] = cfg->deviceTypeModifier;
+
+	if (scsiDev.compatMode >= COMPAT_SCSI2)
+	{
+		out[3] = 2; // SCSI 2 response format.
+	}
+	memcpy(&out[8], cfg->vendor, sizeof(cfg->vendor));
+	memcpy(&out[16], cfg->prodId, sizeof(cfg->prodId));
+	memcpy(&out[32], cfg->revision, sizeof(cfg->revision));
+	return sizeof(StandardResponse) +
+		sizeof(cfg->vendor) +
+		sizeof(cfg->prodId) +
+		sizeof(cfg->revision);
+}
+
+uint8_t getDeviceTypeQualifier()
+{
+	// Set the device type as needed.
+	switch (scsiDev.target->cfg->deviceType)
+	{
+	case S2S_CFG_OPTICAL:
+		return 0x05;
+		break;
+
+	case S2S_CFG_SEQUENTIAL:
+		return 0x01;
+		break;
+
+	case S2S_CFG_MO:
+		return 0x07;
+		break;
+
+	case S2S_CFG_FLOPPY_14MB:
+	case S2S_CFG_REMOVEABLE:
+		return 0;
+		break;
+
+	default:
+		// Accept defaults for a fixed disk.
+		return 0;
+	}
+}
+

+ 25 - 0
lib/SCSI2SD/src/firmware/inquiry.h

@@ -0,0 +1,25 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_INQUIRY_H
+#define S2S_INQUIRY_H
+
+void s2s_scsiInquiry(void);
+uint32_t s2s_getStandardInquiry(const S2S_TargetCfg* cfg, uint8_t* out, uint32_t maxlen);
+uint8_t getDeviceTypeQualifier(void);
+
+
+#endif

+ 24 - 0
lib/SCSI2SD/src/firmware/led.h

@@ -0,0 +1,24 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_LED_H
+#define S2S_LED_H
+
+void s2s_ledInit(void);
+void s2s_ledOn(void);
+void s2s_ledOff(void);
+
+#endif

+ 39 - 0
lib/SCSI2SD/src/firmware/mo.c

@@ -0,0 +1,39 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "mo.h"
+
+
+// Handle magneto-optical scsi device commands
+int scsiMOCommand()
+{
+	int commandHandled = 0;
+
+	uint8_t command = scsiDev.cdb[0];
+	if ((command == 0x2C) || // ERASE(10)
+		(command == 0xAC)) // ERASE(12)
+	{
+		// TODO consider sending an erase command to the SD card.
+
+		commandHandled = 1;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/mo.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef MO_H
+#define MO_H
+
+int scsiMOCommand(void);
+
+#endif

+ 746 - 0
lib/SCSI2SD/src/firmware/mode.c

@@ -0,0 +1,746 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//  Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>
+//  Copyright (C) 2019 Landon Rodgers <g.landon.rodgers@gmail.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "mode.h"
+#include "disk.h"
+#include "inquiry.h"
+
+#include <string.h>
+
+// "Vendor" defined page which was included by Seagate, and required for\r
+// Amiga 500 using DKB SpitFire controller.\r
+static const uint8_t OperatingPage[] =
+{
+0x00, // Page code
+0x02, // Page length
+
+// Bit 4 = unit attension (0 = on, 1 = off).
+// Bit 7 = usage bit, EEPROM life exceeded warning = 1.
+0x80, 
+
+// Bit 7 = reserved.
+// Bits 0:6: Device type qualifier, as per Inquiry data
+0x00
+};
+
+static const uint8_t ReadWriteErrorRecoveryPage[] =
+{
+0x01, // Page code
+0x0A, // Page length
+
+// VMS 5.5-2 is very particular regarding the mode page values.
+// The required values for a SCSI2/NoTCQ device are:
+// AWRE=0 ARRE=0 TB=1 RC=0 EER=? PER=1 DTE=1 DCR=?
+// See ftp://www.digiater.nl/openvms/decus/vms94b/net94b/scsi_params_dkdriver.txt
+// X-Newsgroups: comp.os.vms
+// Subject: Re: VMS 6.1 vs. Seagate Disk Drives
+// Message-Id: <32g87h$8q@nntpd.lkg.dec.com>
+// From: weber@evms.enet.dec.com (Ralph O. Weber -- OpenVMS AXP)
+// Date: 12 Aug 1994 16:32:49 GMT
+0x26,
+
+0x00, // Don't try recovery algorithm during reads
+0x00, // Correction span 0
+0x00, // Head offset count 0,
+0x00, // Data strobe offset count 0,
+0x00, // Reserved
+0x00, // Don't try recovery algorithm during writes
+0x00, // Reserved
+0x00, 0x00 // Recovery time limit 0 (use default)*/
+};
+
+static const uint8_t ReadWriteErrorRecoveryPage_SCSI1[] =
+{
+0x01, // Page code
+0x06, // Page length
+0x26,
+0x00, // Don't try recovery algorithm during reads
+0x00, // Correction span 0
+0x00, // Head offset count 0,
+0x00, // Data strobe offset count 0,
+0xFF // Reserved
+};
+
+static const uint8_t DisconnectReconnectPage[] =
+{
+0x02, // Page code
+0x0E, // Page length
+0, // Buffer full ratio
+0, // Buffer empty ratio
+0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.
+0x00, 0x00, // Disconnect time limit
+0x00, 0x00, // Connect time limit
+0x00, 0x00, // Maximum burst size
+0x00 ,// DTDC. Not used.
+0x00, 0x00, 0x00 // Reserved
+};
+
+static const uint8_t DisconnectReconnectPage_SCSI1[] =
+{
+0x02, // Page code
+0x0A, // Page length
+0, // Buffer full ratio
+0, // Buffer empty ratio
+0x00, 10, // Bus inactivity limit, 100us increments. Allow 1ms.
+0x00, 0x00, // Disconnect time limit
+0x00, 0x00, // Connect time limit
+0x00, 0x00 // Maximum burst size
+};
+
+static const uint8_t FormatDevicePage[] =
+{
+0x03 | 0x80, // Page code | PS (persist) bit.
+0x16, // Page length
+0x00, 0x00, // Single zone
+0x00, 0x00, // No alternate sectors
+0x00, 0x00, // No alternate tracks
+0x00, 0x00, // No alternate tracks per lun
+0x00, 0x00, // Sectors per track, configurable
+0xFF, 0xFF, // Data bytes per physical sector. Configurable.
+0x00, 0x01, // Interleave
+0x00, 0x00, // Track skew factor
+0x00, 0x00, // Cylinder skew factor
+0xC0, // SSEC(set) HSEC(set) RMB SURF
+0x00, 0x00, 0x00 // Reserved
+};
+
+static const uint8_t RigidDiskDriveGeometry[] =
+{
+0x04, // Page code
+0x16, // Page length
+0xFF, 0xFF, 0xFF, // Number of cylinders
+0x00, // Number of heads (replaced by configured value)
+0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation
+0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current
+0x00, 0x1, // Drive step rate (units of 100ns)
+0x00, 0x00, 0x00, // Landing zone cylinder
+0x00, // RPL
+0x00, // Rotational offset
+0x00, // Reserved
+5400 >> 8, 5400 & 0xFF, // Medium rotation rate (RPM)
+0x00, 0x00 // Reserved
+};
+
+static const uint8_t FlexibleDiskDriveGeometry[] =
+{
+0x05, // Page code
+0x1E, // Page length
+0x01, 0xF4, // Transfer Rate (500kbits)
+0x01, // heads
+18, // sectors per track
+0x20,0x00, // bytes per sector
+0x00, 80, // Cylinders
+0x00, 0x80, // Write-precomp
+0x00, 0x80, // reduced current,
+0x00, 0x00, // Drive step rate
+0x00, // pulse width
+0x00, 0x00, // Head settle delay
+0x00, // motor on delay
+0x00,  // motor off delay
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00,
+0x00
+};
+
+static const uint8_t RigidDiskDriveGeometry_SCSI1[] =
+{
+0x04, // Page code
+0x12, // Page length
+0xFF, 0xFF, 0xFF, // Number of cylinders
+0x00, // Number of heads (replaced by configured value)
+0xFF, 0xFF, 0xFF, // Starting cylinder-write precompensation
+0xFF, 0xFF, 0xFF, // Starting cylinder-reduced write current
+0x00, 0x1, // Drive step rate (units of 100ns)
+0x00, 0x00, 0x00, // Landing zone cylinder
+0x00, // RPL
+0x00, // Rotational offset
+0x00 // Reserved
+};
+
+static const uint8_t CachingPage[] =
+{
+0x08, // Page Code
+0x0A, // Page length
+0x01, // Read cache disable
+0x00, // No useful rention policy.
+0x00, 0x00, // Pre-fetch always disabled
+0x00, 0x00, // Minimum pre-fetch
+0x00, 0x00, // Maximum pre-fetch
+0x00, 0x00, // Maximum pre-fetch ceiling
+};
+
+// Old CCS SCSI-1 cache page
+static const uint8_t CCSCachingPage[] =
+{
+0x38, // Page Code
+0x0E, // Page length
+0x00, // Read cache disable
+0x00, // Prefetch threshold
+0x00, 0x00, // Max threshold / multiplier
+0x00, 0x00, // Min threshold / multiplier
+0x00, 0x00, // Reserved
+0x00, 0x00,
+0x00, 0x00,
+0x00, 0x00,
+};
+
+static const uint8_t ControlModePage[] =
+{
+0x0A, // Page code
+0x06, // Page length
+0x00, // No logging
+0x01, // Disable tagged queuing
+0x00, // No async event notifications
+0x00, // Reserved
+0x00, 0x00 // AEN holdoff period.
+};
+
+static const uint8_t SequentialDeviceConfigPage[] =
+{
+0x10, // page code
+0x0E, // Page length
+0x00, // CAP, CAF, Active Format
+0x00, // Active partition
+0x00, // Write buffer full ratio
+0x00, // Read buffer empty ratio
+0x00,0x01, // Write delay time, in 100ms units
+0x00, // Default gap size
+0x10, // auto-generation of default eod (end of data)
+0x00,0x00,0x00, // buffer-size at early warning
+0x00, // No data compression
+0x00 // reserved
+};
+
+// Allow Apple 68k Drive Setup to format this drive.
+// Code
+static const uint8_t AppleVendorPage[] =
+{
+0x30, // Page code
+23, // Page length
+'A','P','P','L','E',' ','C','O','M','P','U','T','E','R',',',' ','I','N','C',' ',' ',' ',0x00
+};
+
+static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen)
+{
+	memcpy(&scsiDev.data[dataIdx], pageData, pageLen);
+
+	if (pc == 0x01) // Mask out (un)changable values
+	{
+		memset(&scsiDev.data[dataIdx+2], 0, pageLen - 2);
+	}
+}
+
+static void doModeSense(
+	int sixByteCmd, int dbd, int pc, int pageCode, int allocLength)
+{
+	////////////// Mode Parameter Header
+	////////////////////////////////////
+
+	// Skip the Mode Data Length, we set that last.
+	int idx = 1;
+	if (!sixByteCmd) ++idx;
+
+	uint8_t mediumType = 0;
+	uint8_t deviceSpecificParam = 0;
+	uint8_t density = 0;
+	switch (scsiDev.target->cfg->deviceType)
+	{
+	case S2S_CFG_FIXED:
+	case S2S_CFG_REMOVEABLE:
+		mediumType = 0; // We should support various floppy types here!
+		// Contains cache bits (0) and a Write-Protect bit.
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0; // reserved for direct access
+		break;
+
+	case S2S_CFG_FLOPPY_14MB:
+		mediumType = 0x1E; // 90mm/3.5"
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0; // reserved for direct access
+		break;
+
+	case S2S_CFG_OPTICAL:
+		mediumType = 0x02; // 120mm CDROM, data only.
+		deviceSpecificParam = 0;
+		density = 0x01; // User data only, 2048bytes per sector.
+		break;
+
+	case S2S_CFG_SEQUENTIAL:
+		mediumType = 0; // reserved
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0x13; // DAT Data Storage, X3B5/88-185A 
+		break;
+
+	case S2S_CFG_MO:
+        mediumType = 0x03; // Optical reversible or erasable medium
+		deviceSpecificParam =
+			(blockDev.state & DISK_WP) ? 0x80 : 0;
+		density = 0x00; // Default
+		break;
+
+	};
+
+	scsiDev.data[idx++] = mediumType;
+	scsiDev.data[idx++] = deviceSpecificParam;
+
+	if (sixByteCmd)
+	{
+		if (dbd)
+		{
+			scsiDev.data[idx++] = 0; // No block descriptor
+		}
+		else
+		{
+			// One block descriptor of length 8 bytes.
+			scsiDev.data[idx++] = 8;
+		}
+	}
+	else
+	{
+		scsiDev.data[idx++] = 0; // Reserved
+		scsiDev.data[idx++] = 0; // Reserved
+		if (dbd)
+		{
+			scsiDev.data[idx++] = 0; // No block descriptor
+			scsiDev.data[idx++] = 0; // No block descriptor
+		}
+		else
+		{
+			// One block descriptor of length 8 bytes.
+			scsiDev.data[idx++] = 0;
+			scsiDev.data[idx++] = 8;
+		}
+	}
+
+	////////////// Block Descriptor
+	////////////////////////////////////
+	if (!dbd)
+	{
+		scsiDev.data[idx++] = density;
+		// Number of blocks
+		// Zero == all remaining blocks shall have the medium
+		// characteristics specified.
+		scsiDev.data[idx++] = 0;
+		scsiDev.data[idx++] = 0;
+		scsiDev.data[idx++] = 0;
+
+		scsiDev.data[idx++] = 0; // reserved
+
+		// Block length
+		uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
+		scsiDev.data[idx++] = bytesPerSector >> 16;
+		scsiDev.data[idx++] = bytesPerSector >> 8;
+		scsiDev.data[idx++] = bytesPerSector & 0xFF;
+	}
+
+	int pageFound = 0;
+
+	if (pageCode == 0x01 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			pageIn(pc, idx, ReadWriteErrorRecoveryPage, sizeof(ReadWriteErrorRecoveryPage));
+			idx += sizeof(ReadWriteErrorRecoveryPage);
+		}
+		else
+		{
+			pageIn(pc, idx, ReadWriteErrorRecoveryPage_SCSI1, sizeof(ReadWriteErrorRecoveryPage_SCSI1));
+			idx += sizeof(ReadWriteErrorRecoveryPage_SCSI1);
+		}
+	}
+
+	if (pageCode == 0x02 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			pageIn(pc, idx, DisconnectReconnectPage, sizeof(DisconnectReconnectPage));
+			idx += sizeof(DisconnectReconnectPage);
+		}
+		else
+		{
+			pageIn(pc, idx, DisconnectReconnectPage_SCSI1, sizeof(DisconnectReconnectPage_SCSI1));
+			idx += sizeof(DisconnectReconnectPage_SCSI1);
+		}
+	}
+
+	if (pageCode == 0x03 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));
+		if (pc != 0x01)
+		{
+			uint16_t sectorsPerTrack = scsiDev.target->cfg->sectorsPerTrack;
+			scsiDev.data[idx+10] = sectorsPerTrack >> 8;
+			scsiDev.data[idx+11] = sectorsPerTrack & 0xFF;
+
+			// Fill out the configured bytes-per-sector
+			uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
+			scsiDev.data[idx+12] = bytesPerSector >> 8;
+			scsiDev.data[idx+13] = bytesPerSector & 0xFF;
+		}
+		else
+		{
+			// Set a mask for the changeable values.
+			scsiDev.data[idx+12] = 0xFF;
+			scsiDev.data[idx+13] = 0xFF;
+		}
+
+		idx += sizeof(FormatDevicePage);
+	}
+
+	if (pageCode == 0x04 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			pageIn(pc, idx, RigidDiskDriveGeometry, sizeof(RigidDiskDriveGeometry));
+		}
+		else
+		{
+			pageIn(pc, idx, RigidDiskDriveGeometry_SCSI1, sizeof(RigidDiskDriveGeometry_SCSI1));
+		}
+
+		if (pc != 0x01)
+		{
+			// Need to fill out the number of cylinders.
+			uint32_t cyl;
+			uint8_t head;
+			uint32_t sector;
+			LBA2CHS(
+				getScsiCapacity(
+					scsiDev.target->cfg->sdSectorStart,
+					scsiDev.target->liveCfg.bytesPerSector,
+					scsiDev.target->cfg->scsiSectors),
+				&cyl,
+				&head,
+				&sector,
+				scsiDev.target->cfg->headsPerCylinder,
+				scsiDev.target->cfg->sectorsPerTrack);
+
+			scsiDev.data[idx+2] = cyl >> 16;
+			scsiDev.data[idx+3] = cyl >> 8;
+			scsiDev.data[idx+4] = cyl;
+
+			memcpy(&scsiDev.data[idx+6], &scsiDev.data[idx+2], 3);
+			memcpy(&scsiDev.data[idx+9], &scsiDev.data[idx+2], 3);
+
+			scsiDev.data[idx+5] = scsiDev.target->cfg->headsPerCylinder;
+		}
+
+		if ((scsiDev.compatMode >= COMPAT_SCSI2))
+		{
+			idx += sizeof(RigidDiskDriveGeometry);
+		}
+		else
+		{
+			idx += sizeof(RigidDiskDriveGeometry_SCSI1);
+		}
+	}
+
+	if ((pageCode == 0x05 || pageCode == 0x3F) &&
+		(scsiDev.target->cfg->deviceType == S2S_CFG_FLOPPY_14MB))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, FlexibleDiskDriveGeometry, sizeof(FlexibleDiskDriveGeometry));
+		idx += sizeof(FlexibleDiskDriveGeometry);
+	}
+
+	// DON'T output the following pages for SCSI1 hosts. They get upset when
+	// we have more data to send than the allocation length provided.
+	// (ie. Try not to output any more pages below this comment)
+
+
+	if ((scsiDev.compatMode >= COMPAT_SCSI2) &&
+		(pageCode == 0x08 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, CachingPage, sizeof(CachingPage));
+		idx += sizeof(CachingPage);
+	}
+
+	if ((scsiDev.compatMode >= COMPAT_SCSI2)
+		&& (pageCode == 0x0A || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, ControlModePage, sizeof(ControlModePage));
+		idx += sizeof(ControlModePage);
+	}
+
+	if ((scsiDev.target->cfg->deviceType == S2S_CFG_SEQUENTIAL) &&
+		(pageCode == 0x10 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(
+			pc,
+			idx,
+			SequentialDeviceConfigPage,
+			sizeof(SequentialDeviceConfigPage));
+		idx += sizeof(SequentialDeviceConfigPage);
+	}
+
+	if ((
+			(scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE) ||
+			(idx + sizeof(AppleVendorPage) <= allocLength)
+		) &&
+		(pageCode == 0x30 || pageCode == 0x3F))
+	{
+		pageFound = 1;
+		pageIn(pc, idx, AppleVendorPage, sizeof(AppleVendorPage));
+		idx += sizeof(AppleVendorPage);
+	}
+
+	if (pageCode == 0x38) // Don't send unless requested
+	{
+		pageFound = 1;
+		pageIn(pc, idx, CCSCachingPage, sizeof(CCSCachingPage));
+		idx += sizeof(CCSCachingPage);
+	}
+
+	// SCSI 2 standard says page 0 is always last.
+	if (pageCode == 0x00 || pageCode == 0x3F)
+	{
+		pageFound = 1;
+		pageIn(pc, idx, OperatingPage, sizeof(OperatingPage));
+
+		// Note inverted logic for the flag.
+		scsiDev.data[idx+2] =
+			(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION) ? 0x80 : 0x90;
+
+		scsiDev.data[idx+3] = getDeviceTypeQualifier();
+
+		idx += sizeof(OperatingPage);
+	}
+
+	if (!pageFound)
+	{
+		// Unknown Page Code
+		pageFound = 0;
+		scsiDev.status = CHECK_CONDITION;
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		scsiDev.phase = STATUS;
+	}
+	else
+	{
+		// Go back and fill out the mode data length
+		if (sixByteCmd)
+		{
+			// Cannot currently exceed limits. yay
+			scsiDev.data[0] = idx - 1;
+		}
+		else
+		{
+			scsiDev.data[0] = ((idx - 2) >> 8);
+			scsiDev.data[1] = (idx - 2);
+		}
+
+		scsiDev.dataLen = idx > allocLength ? allocLength : idx;
+		scsiDev.phase = DATA_IN;
+	}
+}
+
+
+// Callback after the DATA OUT phase is complete.
+static void doModeSelect(void)
+{
+	if (scsiDev.status == GOOD) // skip if we've already encountered an error
+	{
+		// scsiDev.dataLen bytes are in scsiDev.data
+
+		int idx;
+		int blockDescLen;
+		if (scsiDev.cdb[0] == 0x55)
+		{
+			blockDescLen =
+				(((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];
+			idx = 8;
+		}
+		else
+		{
+			blockDescLen = scsiDev.data[3];
+			idx = 4;
+		}
+
+		// The unwritten rule.  Blocksizes are normally set using the
+		// block descriptor value, not by changing page 0x03.
+		if (blockDescLen >= 8)
+		{
+			uint32_t bytesPerSector =
+				(((uint32_t)scsiDev.data[idx+5]) << 16) |
+				(((uint32_t)scsiDev.data[idx+6]) << 8) |
+				scsiDev.data[idx+7];
+			if ((bytesPerSector < MIN_SECTOR_SIZE) ||
+				(bytesPerSector > MAX_SECTOR_SIZE))
+			{
+				goto bad;
+			}
+			else
+			{
+				scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
+				if (bytesPerSector != scsiDev.target->cfg->bytesPerSector)
+				{
+					s2s_configSave(scsiDev.target->targetId, bytesPerSector);
+				}
+			}
+		}
+		idx += blockDescLen;
+
+		while (idx < scsiDev.dataLen)
+		{
+			int pageLen = scsiDev.data[idx + 1];
+			if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;
+
+			int pageCode = scsiDev.data[idx] & 0x3F;
+			switch (pageCode)
+			{
+			case 0x03: // Format Device Page
+			{
+				if (pageLen != 0x16) goto bad;
+
+				// Fill out the configured bytes-per-sector
+				uint16_t bytesPerSector =
+					(((uint16_t)scsiDev.data[idx+12]) << 8) |
+					scsiDev.data[idx+13];
+
+				// Sane values only, ok ?
+				if ((bytesPerSector < MIN_SECTOR_SIZE) ||
+					(bytesPerSector > MAX_SECTOR_SIZE))
+				{
+					goto bad;
+				}
+
+				scsiDev.target->liveCfg.bytesPerSector = bytesPerSector;
+				if (scsiDev.cdb[1] & 1) // SP Save Pages flag
+				{
+					s2s_configSave(scsiDev.target->targetId, bytesPerSector);
+				}
+			}
+			break;
+			//default:
+
+				// Easiest to just ignore for now. We'll get here when changing
+				// the SCSI block size via the descriptor header.
+			}
+			idx += 2 + pageLen;
+		}
+	}
+
+	goto out;
+bad:
+	scsiDev.status = CHECK_CONDITION;
+	scsiDev.target->sense.code = ILLEGAL_REQUEST;
+	scsiDev.target->sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;
+
+out:
+	scsiDev.phase = STATUS;
+}
+
+int scsiModeCommand()
+{
+	int commandHandled = 1;
+
+	uint8_t command = scsiDev.cdb[0];
+
+	// We don't currently support the setting of any parameters.
+	// (ie. no MODE SELECT(6) or MODE SELECT(10) commands)
+
+	if (command == 0x1A)
+	{
+		// MODE SENSE(6)
+		int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
+		int pc = scsiDev.cdb[2] >> 6; // Page Control
+		int pageCode = scsiDev.cdb[2] & 0x3F;
+		int allocLength = scsiDev.cdb[4];
+
+		// SCSI1 standard: (CCS X3T9.2/86-52)
+		// "An Allocation Length of zero indicates that no MODE SENSE data shall
+		// be transferred. This condition shall not be considered as an error."
+		doModeSense(1, dbd, pc, pageCode, allocLength);
+	}
+	else if (command == 0x5A)
+	{
+		// MODE SENSE(10)
+		int dbd = scsiDev.cdb[1] & 0x08; // Disable block descriptors
+		int pc = scsiDev.cdb[2] >> 6; // Page Control
+		int pageCode = scsiDev.cdb[2] & 0x3F;
+		int allocLength =
+			(((uint16_t) scsiDev.cdb[7]) << 8) +
+			scsiDev.cdb[8];
+		doModeSense(0, dbd, pc, pageCode, allocLength);
+	}
+	else if (command == 0x15)
+	{
+		// MODE SELECT(6)
+		int len = scsiDev.cdb[4];
+		if (len == 0)
+		{
+			// If len == 0, then transfer no data. From the SCSI 2 standard:
+			//      A parameter list length of zero indicates that no data shall
+			//      be transferred. This condition shall not be considered as an
+			//		error.
+			scsiDev.phase = STATUS;
+		}
+		else
+		{
+			scsiDev.dataLen = len;
+			scsiDev.phase = DATA_OUT;
+			scsiDev.postDataOutHook = doModeSelect;
+		}
+	}
+	else if (command == 0x55)
+	{
+		// MODE SELECT(10)
+		int allocLength = (((uint16_t) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];
+		if (allocLength == 0)
+		{
+			// If len == 0, then transfer no data. From the SCSI 2 standard:
+			//      A parameter list length of zero indicates that no data shall
+			//      be transferred. This condition shall not be considered as an
+			//		error.
+			scsiDev.phase = STATUS;
+		}
+		else
+		{
+			scsiDev.dataLen = allocLength;
+			scsiDev.phase = DATA_OUT;
+			scsiDev.postDataOutHook = doModeSelect;
+		}
+	}
+	else
+	{
+		commandHandled = 0;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/mode.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef MODE_H
+#define MODE_H
+
+int scsiModeCommand(void);
+
+#endif

+ 1283 - 0
lib/SCSI2SD/src/firmware/scsi.c

@@ -0,0 +1,1283 @@
+//	Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "scsiPhy.h"
+#include "config.h"
+#include "diagnostic.h"
+#include "disk.h"
+#include "inquiry.h"
+#include "led.h"
+#include "mode.h"
+#include "scsi2sd_time.h"
+#include "bsp.h"
+#include "cdrom.h"
+//#include "debug.h"
+#include "tape.h"
+#include "mo.h"
+#include "vendor.h"
+
+#include <string.h>
+
+// Global SCSI device state.
+ScsiDevice scsiDev S2S_DMA_ALIGN;
+
+static void enter_SelectionPhase(void);
+static void process_SelectionPhase(void);
+static void enter_MessageIn(uint8_t message);
+static void enter_Status(uint8_t status);
+static void enter_DataIn(int len);
+static void process_DataIn(void);
+static void process_DataOut(void);
+static void process_Command(void);
+
+static void doReserveRelease(void);
+
+void enter_BusFree()
+{
+	// This delay probably isn't needed for most SCSI hosts, but it won't
+	// hurt either. It's possible some of the samplers needed this delay.
+	if (scsiDev.compatMode < COMPAT_SCSI2)
+	{
+		s2s_delay_us(2);
+	}
+
+#if 0
+	if (scsiDev.status != GOOD)// && isDebugEnabled())
+	{
+		// We want to capture debug information for failure cases.
+		s2s_delay_ms(80);
+	}
+#endif
+
+
+	scsiEnterBusFree();
+
+	// Wait for the initiator to cease driving signals
+	// Bus settle delay + bus clear delay = 1200ns
+    // Just waiting the clear delay is sufficient.
+	s2s_delay_ns(800);
+
+	s2s_ledOff();
+	scsiDev.phase = BUS_FREE;
+	scsiDev.selFlag = 0;
+}
+
+static void enter_MessageIn(uint8_t message)
+{
+	scsiDev.msgIn = message;
+	scsiDev.phase = MESSAGE_IN;
+}
+
+int process_MessageIn(int releaseBusFree)
+{
+	scsiEnterPhase(MESSAGE_IN);
+	scsiWriteByte(scsiDev.msgIn);
+
+	if (unlikely(scsiDev.atnFlag))
+	{
+		// If there was a parity error, we go
+		// back to MESSAGE_OUT first, get out parity error message, then come
+		// back here.
+		return 0;
+	}
+	else if ((scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE) ||
+		(scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG))
+	{
+		// Go back to the command phase and start again.
+		scsiDev.phase = COMMAND;
+		scsiDev.dataPtr = 0;
+		scsiDev.savedDataPtr = 0;
+		scsiDev.dataLen = 0;
+		scsiDev.status = GOOD;
+		transfer.blocks = 0;
+		transfer.currentBlock = 0;
+		return 0;
+	}
+	else if (releaseBusFree) /*if (scsiDev.msgIn == MSG_COMMAND_COMPLETE)*/
+	{
+		enter_BusFree();
+		return 1;
+	}
+	else
+	{
+		return 1;
+	}
+}
+
+static void messageReject()
+{
+	scsiEnterPhase(MESSAGE_IN);
+	scsiWriteByte(MSG_REJECT);
+}
+
+static void enter_Status(uint8_t status)
+{
+	scsiDev.status = status;
+	scsiDev.phase = STATUS;
+
+	scsiDev.lastStatus = scsiDev.status;
+	scsiDev.lastSense = scsiDev.target->sense.code;
+	scsiDev.lastSenseASC = scsiDev.target->sense.asc;
+}
+
+void process_Status()
+{
+	scsiEnterPhase(STATUS);
+
+	uint8_t message;
+
+	uint8_t control = scsiDev.cdb[scsiDev.cdbLen - 1];
+
+	if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
+	{
+		// OMTI non-standard LINK control
+		if (control & 0x01)
+		{
+			scsiDev.phase = COMMAND;
+			return;
+		}
+	}
+
+	if ((scsiDev.status == GOOD) && (control & 0x01) &&
+		scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC)
+	{
+		// Linked command.
+		scsiDev.status = INTERMEDIATE;
+		if (control & 0x02)
+		{
+			message = MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG;
+		}
+		else
+		{
+			message = MSG_LINKED_COMMAND_COMPLETE;
+		}
+	}
+	else
+	{
+		message = MSG_COMMAND_COMPLETE;
+	}
+
+	if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+		// More non-standardness. Expects 2 status bytes (really status + msg)
+		// 00 d 000 err 0
+		// d == disk number
+		// ERR = 1 if error.
+		if (scsiDev.status == GOOD)
+		{
+			scsiWriteByte(scsiDev.cdb[1] & 0x20);
+		}
+		else
+		{
+			scsiWriteByte((scsiDev.cdb[1] & 0x20) | 0x2);
+		}
+		s2s_delay_us(10); // Seems to need a delay before changing phase bits.
+	}
+	else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_OMTI)
+	{
+		scsiDev.status |= (scsiDev.target->targetId & 0x03) << 5;
+		scsiWriteByte(scsiDev.status);
+	}
+	else
+	{
+		scsiWriteByte(scsiDev.status);
+	}
+
+	scsiDev.lastStatus = scsiDev.status;
+	scsiDev.lastSense = scsiDev.target->sense.code;
+	scsiDev.lastSenseASC = scsiDev.target->sense.asc;
+
+	// Command Complete occurs AFTER a valid status has been
+	// sent. then we go bus-free.
+	enter_MessageIn(message);
+}
+
+static void enter_DataIn(int len)
+{
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
+static void process_DataIn()
+{
+	uint32_t len;
+
+	if (scsiDev.dataLen > sizeof(scsiDev.data))
+	{
+		scsiDev.dataLen = sizeof(scsiDev.data);
+	}
+
+	len = scsiDev.dataLen - scsiDev.dataPtr;
+	if (len > 0)
+	{
+		scsiEnterPhase(DATA_IN);
+		scsiWrite(scsiDev.data + scsiDev.dataPtr, len);
+		scsiDev.dataPtr += len;
+	}
+
+	if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
+		(transfer.currentBlock == transfer.blocks))
+	{
+		enter_Status(GOOD);
+	}
+}
+
+static void process_DataOut()
+{
+	uint32_t len;
+
+	if (scsiDev.dataLen > sizeof(scsiDev.data))
+	{
+		scsiDev.dataLen = sizeof(scsiDev.data);
+	}
+
+	len = scsiDev.dataLen - scsiDev.dataPtr;
+	if (len > 0)
+	{
+		scsiEnterPhase(DATA_OUT);
+
+		int parityError = 0;
+		scsiRead(scsiDev.data + scsiDev.dataPtr, len, &parityError);
+		scsiDev.dataPtr += len;
+
+		if (parityError &&
+			(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+		{
+			scsiDev.target->sense.code = ABORTED_COMMAND;
+			scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
+			enter_Status(CHECK_CONDITION);
+		}
+	}
+
+	if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
+		(transfer.currentBlock == transfer.blocks))
+	{
+		if (scsiDev.postDataOutHook != NULL)
+		{
+			scsiDev.postDataOutHook();
+		}
+		else
+		{
+			enter_Status(GOOD);
+		}
+	}
+}
+
+static const uint8_t CmdGroupBytes[8] = {6, 10, 10, 6, 6, 12, 6, 6};
+static void process_Command()
+{
+	int group;
+	uint8_t command;
+	uint8_t control;
+
+	scsiEnterPhase(COMMAND);
+
+	memset(scsiDev.cdb + 6, 0, sizeof(scsiDev.cdb) - 6);
+	int parityError = 0;
+	scsiRead(scsiDev.cdb, 6, &parityError);
+
+	group = scsiDev.cdb[0] >> 5;
+	scsiDev.cdbLen = CmdGroupBytes[group];
+	if (parityError &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+	{
+		// Don't try and read more bytes, as we cannot be sure what group
+		// the command should be.
+	}
+	else if (scsiDev.cdbLen - 6 > 0)
+	{
+		scsiRead(scsiDev.cdb + 6, scsiDev.cdbLen - 6, &parityError);
+	}
+	command = scsiDev.cdb[0];
+
+	// Prefer LUN's set by IDENTIFY messages for newer hosts.
+	if (scsiDev.lun < 0)
+	{
+		if (command == 0xE0 || command == 0xE4) // XEBEC s1410
+		{
+			scsiDev.lun = 0;
+		}
+		else
+		{
+			scsiDev.lun = scsiDev.cdb[1] >> 5;
+		}
+	}
+
+
+	// For Philips P2000C with Xebec S1410 SASI/MFM adapter
+	// http://bitsavers.trailing-edge.com/pdf/xebec/104524C_S1410Man_Aug83.pdf
+	if ((scsiDev.lun > 0) && (scsiDev.boardCfg.flags & S2S_CFG_MAP_LUNS_TO_IDS))
+	{
+		int tgtIndex;
+		for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
+		{
+			if (scsiDev.targets[tgtIndex].targetId == scsiDev.lun)
+			{
+				scsiDev.target = &scsiDev.targets[tgtIndex];
+				scsiDev.lun = 0;
+				break;
+			}
+		}
+	}
+
+	control = scsiDev.cdb[scsiDev.cdbLen - 1];
+
+	scsiDev.cmdCount++;
+	const S2S_TargetCfg* cfg = scsiDev.target->cfg;
+
+	if (unlikely(scsiDev.resetFlag))
+	{
+		// Don't log bogus commands
+		scsiDev.cmdCount--;
+		memset(scsiDev.cdb, 0xff, sizeof(scsiDev.cdb));
+		return;
+	}
+	else if (parityError &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+	{
+		scsiDev.target->sense.code = ABORTED_COMMAND;
+		scsiDev.target->sense.asc = SCSI_PARITY_ERROR;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if ((control & 0x02) && ((control & 0x01) == 0) &&
+		// used for head step options on xebec.
+		likely(scsiDev.target->cfg->quirks != S2S_CFG_QUIRKS_XEBEC))
+	{
+		// FLAG set without LINK flag.
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (command == 0x12)
+	{
+		s2s_scsiInquiry();
+	}
+	else if (command == 0x03)
+	{
+		// REQUEST SENSE
+		uint32_t allocLength = scsiDev.cdb[4];
+
+		if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+		{
+			// Completely non-standard
+			allocLength = 4;
+			if (scsiDev.target->sense.code == NO_SENSE)
+				scsiDev.data[0] = 0;
+			else if (scsiDev.target->sense.code == ILLEGAL_REQUEST)
+				scsiDev.data[0] = 0x20; // Illegal command
+			else if (scsiDev.target->sense.code == NOT_READY)
+				scsiDev.data[0] = 0x04; // Drive not ready
+			else
+				scsiDev.data[0] = 0x11;  // Uncorrectable data error
+
+			scsiDev.data[1] = (scsiDev.cdb[1] & 0x20) | ((transfer.lba >> 16) & 0x1F);
+			scsiDev.data[2] = transfer.lba >> 8;
+			scsiDev.data[3] = transfer.lba;
+		}
+		else
+		{
+			// As specified by the SASI and SCSI1 standard.
+			// Newer initiators won't be specifying 0 anyway.
+			if (allocLength == 0) allocLength = 4;
+
+			memset(scsiDev.data, 0, 256); // Max possible alloc length
+			scsiDev.data[0] = 0xF0;
+			scsiDev.data[2] = scsiDev.target->sense.code & 0x0F;
+
+			scsiDev.data[3] = transfer.lba >> 24;
+			scsiDev.data[4] = transfer.lba >> 16;
+			scsiDev.data[5] = transfer.lba >> 8;
+			scsiDev.data[6] = transfer.lba;
+
+			// Additional bytes if there are errors to report
+			scsiDev.data[7] = 10; // additional length
+			scsiDev.data[12] = scsiDev.target->sense.asc >> 8;
+			scsiDev.data[13] = scsiDev.target->sense.asc;
+		}
+
+		// Silently truncate results. SCSI-2 spec 8.2.14.
+		enter_DataIn(allocLength);
+
+		// This is a good time to clear out old sense information.
+		scsiDev.target->sense.code = NO_SENSE;
+		scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
+	}
+	// Some old SCSI drivers do NOT properly support
+	// unitAttention. eg. the Mac Plus would trigger a SCSI reset
+	// on receiving the unit attention response on boot, thus
+	// triggering another unit attention condition.
+	else if (scsiDev.target->unitAttention &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_UNIT_ATTENTION))
+	{
+		scsiDev.target->sense.code = UNIT_ATTENTION;
+		scsiDev.target->sense.asc = scsiDev.target->unitAttention;
+
+		// If initiator doesn't do REQUEST SENSE for the next command, then
+		// data is lost.
+		scsiDev.target->unitAttention = 0;
+
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (scsiDev.lun)
+	{
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = LOGICAL_UNIT_NOT_SUPPORTED;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (command == 0x17 || command == 0x16)
+	{
+		doReserveRelease();
+	}
+	else if ((scsiDev.target->reservedId >= 0) &&
+		(scsiDev.target->reservedId != scsiDev.initiatorId))
+	{
+		enter_Status(CONFLICT);
+	}
+	// Handle odd device types first that may override basic read and
+	// write commands. Will fall-through to generic disk handling.
+	else if (((cfg->deviceType == S2S_CFG_OPTICAL) && scsiCDRomCommand()) ||
+		((cfg->deviceType == S2S_CFG_SEQUENTIAL) && scsiTapeCommand()) ||
+		((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()))
+	{
+		// Already handled.
+	}
+	else if (scsiDiskCommand())
+	{
+		// Already handled.
+		// check for the performance-critical read/write
+		// commands ASAP.
+	}
+	else if (command == 0x1C)
+	{
+		scsiReceiveDiagnostic();
+	}
+	else if (command == 0x1D)
+	{
+		scsiSendDiagnostic();
+	}
+	else if (command == 0x3B)
+	{
+		scsiWriteBuffer();
+	}
+	else if (command == 0x0f &&
+		scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+		scsiWriteSectorBuffer();
+	}
+	else if (command == 0x3C)
+	{
+		scsiReadBuffer();
+	}
+	else if (!scsiModeCommand() && !scsiVendorCommand())
+	{
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_COMMAND_OPERATION_CODE;
+		enter_Status(CHECK_CONDITION);
+	}
+
+	// Successful
+	if (scsiDev.phase == COMMAND) // No status set, and not in DATA_IN
+	{
+		enter_Status(GOOD);
+	}
+
+}
+
+static void doReserveRelease()
+{
+	int extentReservation = scsiDev.cdb[1] & 1;
+	int thirdPty = scsiDev.cdb[1] & 0x10;
+	int thirdPtyId = (scsiDev.cdb[1] >> 1) & 0x7;
+	uint8_t command = scsiDev.cdb[0];
+
+	int canRelease =
+		(!thirdPty && (scsiDev.initiatorId == scsiDev.target->reservedId)) ||
+			(thirdPty &&
+				(scsiDev.target->reserverId == scsiDev.initiatorId) &&
+				(scsiDev.target->reservedId == thirdPtyId)
+			);
+
+	if (extentReservation)
+	{
+		// Not supported.
+		scsiDev.target->sense.code = ILLEGAL_REQUEST;
+		scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+		enter_Status(CHECK_CONDITION);
+	}
+	else if (command == 0x17) // release
+	{
+		if ((scsiDev.target->reservedId < 0) || canRelease)
+		{
+			scsiDev.target->reservedId = -1;
+			scsiDev.target->reserverId = -1;
+		}
+		else
+		{
+			enter_Status(CONFLICT);
+		}
+	}
+	else // assume reserve.
+	{
+		if ((scsiDev.target->reservedId < 0) || canRelease)
+		{
+			scsiDev.target->reserverId = scsiDev.initiatorId;
+			if (thirdPty)
+			{
+				scsiDev.target->reservedId = thirdPtyId;
+			}
+			else
+			{
+				scsiDev.target->reservedId = scsiDev.initiatorId;
+			}
+		}
+		else
+		{
+			// Already reserved by someone else!
+			enter_Status(CONFLICT);
+		}
+	}
+}
+
+static uint32_t resetUntil = 0;
+
+static void scsiReset()
+{
+	scsiDev.rstCount++;
+	s2s_ledOff();
+
+	scsiPhyReset();
+
+	scsiDev.phase = BUS_FREE;
+	scsiDev.atnFlag = 0;
+	scsiDev.resetFlag = 0;
+	scsiDev.selFlag = 0;
+	scsiDev.lun = -1;
+	scsiDev.compatMode = COMPAT_UNKNOWN;
+
+	if (scsiDev.target)
+	{
+		if (scsiDev.target->unitAttention != POWER_ON_RESET)
+		{
+			scsiDev.target->unitAttention = SCSI_BUS_RESET;
+		}
+		scsiDev.target->reservedId = -1;
+		scsiDev.target->reserverId = -1;
+		scsiDev.target->sense.code = NO_SENSE;
+		scsiDev.target->sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
+	}
+	scsiDev.target = NULL;
+
+	for (int i = 0; i < S2S_MAX_TARGETS; ++i)
+	{
+		scsiDev.targets[i].syncOffset = 0;
+		scsiDev.targets[i].syncPeriod = 0;
+	}
+	scsiDev.minSyncPeriod = 0;
+
+	scsiDiskReset();
+
+	scsiDev.postDataOutHook = NULL;
+
+	scsiDev.sdUnderrunCount = 0;
+
+	// Sleep to allow the bus to settle down a bit.
+	// We must be ready again within the "Reset to selection time" of
+	// 250ms.
+	// There is no guarantee that the RST line will be negated by then.
+	// NOTE: We could be connected and powered by USB for configuration,
+	// in which case TERMPWR cannot be supplied, and reset will ALWAYS
+	// be true. Therefore, the sleep here must be slow to avoid slowing
+	// USB comms
+	resetUntil = s2s_getTime_ms() + 2; // At least 1ms.
+}
+
+static void enter_SelectionPhase()
+{
+	// Ignore stale versions of this flag, but ensure we know the
+	// current value if the flag is still set.
+	scsiDev.atnFlag = 0;
+	scsiDev.dataPtr = 0;
+	scsiDev.savedDataPtr = 0;
+	scsiDev.dataLen = 0;
+	scsiDev.status = GOOD;
+	scsiDev.phase = SELECTION;
+	scsiDev.lun = -1;
+	scsiDev.discPriv = 0;
+
+	scsiDev.initiatorId = -1;
+	scsiDev.target = NULL;
+
+	transfer.blocks = 0;
+	transfer.currentBlock = 0;
+
+	scsiDev.postDataOutHook = NULL;
+
+	scsiDev.needSyncNegotiationAck = 0;
+}
+
+static void process_SelectionPhase()
+{
+	// Selection delays.
+	// Many SCSI1 samplers that use a 5380 chip need a delay of at least 1ms.
+	// The Mac Plus boot-time (ie. rom code) selection abort time
+	// is < 1ms and must have no delay (standard suggests 250ms abort time)
+	// Most newer SCSI2 hosts don't care either way.
+	if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+	{
+		s2s_delay_ms(1); // Simply won't work if set to 0.
+	}
+	else if (scsiDev.boardCfg.selectionDelay == 255) // auto
+	{
+		if (scsiDev.compatMode < COMPAT_SCSI2)
+		{
+			s2s_delay_ms(1);
+		}
+	}
+	else if (scsiDev.boardCfg.selectionDelay != 0)
+	{
+		s2s_delay_ms(scsiDev.boardCfg.selectionDelay);
+	}
+
+	uint8_t selStatus = *SCSI_STS_SELECTED;
+	if ((selStatus == 0) && (scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SEL_LATCH))
+	{
+		selStatus = scsiDev.selFlag;
+	}
+
+	int tgtIndex;
+	TargetState* target = NULL;
+	for (tgtIndex = 0; tgtIndex < S2S_MAX_TARGETS; ++tgtIndex)
+	{
+		if (scsiDev.targets[tgtIndex].targetId == (selStatus & 7))
+		{
+			target = &scsiDev.targets[tgtIndex];
+			break;
+		}
+	}
+	if ((target != NULL) && (selStatus & 0x40))
+	{
+		// We've been selected!
+		// Assert BSY - Selection success!
+		// must happen within 200us (Selection abort time) of seeing our
+		// ID + SEL.
+		// (Note: the initiator will be waiting the "Selection time-out delay"
+		// for our BSY response, which is actually a very generous 250ms)
+		*SCSI_CTRL_BSY = 1;
+		s2s_ledOn();
+
+		scsiDev.target = target;
+
+		// Do we enter MESSAGE OUT immediately ? SCSI 1 and 2 standards says
+		// move to MESSAGE OUT if ATN is true before we assert BSY.
+		// The initiator should assert ATN with SEL.
+		scsiDev.atnFlag = selStatus & 0x80;
+
+
+		// Unit attention breaks many older SCSI hosts. Disable it completely
+		// for SCSI-1 (and older) hosts, regardless of our configured setting.
+		// Enable the compatability mode also as many SASI and SCSI1
+		// controllers don't generate parity bits.
+		if (!scsiDev.atnFlag)
+		{
+			target->unitAttention = 0;
+			scsiDev.compatMode = COMPAT_SCSI1;
+		}
+		else if (!(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_SCSI2))
+		{
+			scsiDev.compatMode = COMPAT_SCSI2_DISABLED;
+		}
+		else
+		{
+			scsiDev.compatMode = COMPAT_SCSI2;
+		}
+
+		scsiDev.selCount++;
+
+
+		// Save our initiator now that we're no longer in a time-critical
+		// section.
+		// SCSI1/SASI initiators may not set their own ID.
+		scsiDev.initiatorId = (selStatus >> 3) & 0x7;
+
+		// Wait until the end of the selection phase.
+		uint32_t selTimerBegin = s2s_getTime_ms();
+		while (likely(!scsiDev.resetFlag))
+		{
+			if (!scsiStatusSEL())
+			{
+				break;
+			}
+			else if (s2s_elapsedTime_ms(selTimerBegin) >= 10 &&
+				scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_XEBEC)
+			{
+				// XEBEC hosts may not bother releasing SEL at all until
+				// just before the command ends.
+				break;
+			}
+			else if (s2s_elapsedTime_ms(selTimerBegin) >= 250)
+			{
+				*SCSI_CTRL_BSY = 0;
+				scsiDev.resetFlag = 1;
+				break;
+			}
+		}
+
+		scsiDev.phase = COMMAND;
+	}
+	else if (!selStatus)
+	{
+		scsiDev.phase = BUS_BUSY;
+	}
+	scsiDev.selFlag = 0;
+}
+
+static void process_MessageOut()
+{
+	int wasNeedSyncNegotiationAck = scsiDev.needSyncNegotiationAck;
+	scsiDev.needSyncNegotiationAck = 0; // Successful on -most- messages.
+
+	scsiEnterPhase(MESSAGE_OUT);
+
+	scsiDev.atnFlag = 0;
+	scsiDev.msgOut = scsiReadByte();
+	scsiDev.msgCount++;
+
+	if (scsiParityError() &&
+		(scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY))
+	{
+		// Skip the remaining message bytes, and then start the MESSAGE_OUT
+		// phase again from the start. The initiator will re-send the
+		// same set of messages.
+		while (scsiStatusATN() && !scsiDev.resetFlag)
+		{
+			scsiReadByte();
+		}
+
+		// Go-back and try the message again.
+		scsiDev.atnFlag = 1;
+	}
+	else if (scsiDev.msgOut == 0x00)
+	{
+		// COMMAND COMPLETE. but why would the target be receiving this ? nfi.
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == 0x06)
+	{
+		// ABORT
+		scsiDiskReset();
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == 0x0C)
+	{
+		// BUS DEVICE RESET
+
+		scsiDiskReset();
+
+		scsiDev.target->unitAttention = SCSI_BUS_RESET;
+
+		// ANY initiator can reset the reservation state via this message.
+		scsiDev.target->reservedId = -1;
+		scsiDev.target->reserverId = -1;
+
+		// Cancel any sync negotiation
+		scsiDev.target->syncOffset = 0;
+		scsiDev.target->syncPeriod = 0;
+
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == 0x05)
+	{
+		// Initiate Detected Error
+		// Ignore for now
+	}
+	else if (scsiDev.msgOut == 0x0F)
+	{
+		// INITIATE RECOVERY
+		// Ignore for now
+	}
+	else if (scsiDev.msgOut == 0x10)
+	{
+		// RELEASE RECOVERY
+		// Ignore for now
+		enter_BusFree();
+	}
+	else if (scsiDev.msgOut == MSG_REJECT)
+	{
+		// Message Reject
+		// Oh well.
+
+		if (wasNeedSyncNegotiationAck)
+		{
+			scsiDev.target->syncOffset = 0;
+			scsiDev.target->syncPeriod = 0;
+		}
+	}
+	else if (scsiDev.msgOut == 0x08)
+	{
+		// NOP
+	}
+	else if (scsiDev.msgOut == 0x09)
+	{
+		// Message Parity Error
+		// Go back and re-send the last message.
+		scsiDev.phase = MESSAGE_IN;
+
+		if (wasNeedSyncNegotiationAck)
+		{
+			scsiDev.target->syncOffset = 0;
+			scsiDev.target->syncPeriod = 0;
+		}
+	}
+	else if (scsiDev.msgOut & 0x80) // 0x80 -> 0xFF
+	{
+		// IDENTIFY
+		if ((scsiDev.msgOut & 0x18) || // Reserved bits set.
+			(scsiDev.msgOut & 0x20))  // We don't have any target routines!
+		{
+			messageReject();
+		}
+
+		scsiDev.lun = scsiDev.msgOut & 0x7;
+		scsiDev.discPriv = 
+			((scsiDev.msgOut & 0x40) && (scsiDev.initiatorId >= 0))
+				? 1 : 0;
+	}
+	else if (scsiDev.msgOut >= 0x20 && scsiDev.msgOut <= 0x2F)
+	{
+		// Two byte message. We don't support these. read and discard.
+		scsiReadByte();
+
+		if (scsiDev.msgOut == 0x23) {
+			// Ignore Wide Residue. We're only 8 bit anyway.
+		} else {
+			messageReject();
+		}
+	}
+	else if (scsiDev.msgOut == 0x01)
+	{
+		int i;
+
+		// Extended message.
+		int msgLen = scsiReadByte();
+		if (msgLen == 0) msgLen = 256;
+		uint8_t extmsg[256];
+		for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i)
+		{
+			// Discard bytes.
+			extmsg[i] = scsiReadByte();
+		}
+
+		if (extmsg[0] == 3 && msgLen == 2) // Wide Data Request
+		{
+			// Negotiate down to 8bit
+			scsiEnterPhase(MESSAGE_IN);
+			static const uint8_t WDTR[] = {0x01, 0x02, 0x03, 0x00};
+			scsiWrite(WDTR, sizeof(WDTR));
+
+			// SDTR becomes invalidated.
+			scsiDev.target->syncOffset = 0;
+			scsiDev.target->syncPeriod = 0;
+		}
+		else if (extmsg[0] == 1 && msgLen == 3) // Synchronous data request
+		{
+			int oldPeriod = scsiDev.target->syncPeriod;
+			int oldOffset = scsiDev.target->syncOffset;
+
+			int transferPeriod = extmsg[1];
+			int offset = extmsg[2];
+
+			if ((
+					(transferPeriod > 0) &&
+					(transferPeriod < scsiDev.minSyncPeriod)) ||
+				(scsiDev.minSyncPeriod == 0))
+			{
+				scsiDev.minSyncPeriod = transferPeriod;
+			}
+
+			if ((transferPeriod > 80) || // 320ns, 3.125MB/s
+				// Amiga A590 (WD33C93 chip) only does 3.5MB/s sync
+				// After 80 we start to run out of bits in the fpga timing
+				// register.
+				(transferPeriod == 0) ||
+				(offset == 0) ||
+				((scsiDev.boardCfg.scsiSpeed != S2S_CFG_SPEED_NoLimit) &&
+					(scsiDev.boardCfg.scsiSpeed <= S2S_CFG_SPEED_ASYNC_50)))
+			{
+				scsiDev.target->syncOffset = 0;
+				scsiDev.target->syncPeriod = 0;
+			} else {
+				scsiDev.target->syncOffset = offset <= 15 ? offset : 15;
+				// FAST20 / 50ns / 20MHz is disabled for now due to
+				// data corruption while reading data. We can count the
+				// ACK's correctly, but can't save the data to a register
+				// before it changes. (ie. transferPeriod == 12)
+				if ((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO) &&
+					(transferPeriod <= 16))
+				{
+					scsiDev.target->syncPeriod = 16; // 15.6MB/s
+				}
+				else if (scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_TURBO)
+				{
+					scsiDev.target->syncPeriod = transferPeriod;
+				}
+				else if (transferPeriod <= 25 &&
+					((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
+						(scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
+				{
+					scsiDev.target->syncPeriod = 25; // 100ns, 10MB/s
+
+				} else if (transferPeriod < 50 &&
+					((scsiDev.boardCfg.scsiSpeed == S2S_CFG_SPEED_NoLimit) ||
+						(scsiDev.boardCfg.scsiSpeed >= S2S_CFG_SPEED_SYNC_10)))
+				{
+					scsiDev.target->syncPeriod = transferPeriod;
+				} else if (transferPeriod >= 50)
+				{
+					scsiDev.target->syncPeriod = transferPeriod;
+				} else {
+					scsiDev.target->syncPeriod = 50;
+				}
+			}
+
+			if (transferPeriod != oldPeriod ||
+				scsiDev.target->syncPeriod != oldPeriod ||
+				offset != oldOffset ||
+				scsiDev.target->syncOffset != oldOffset ||
+				!wasNeedSyncNegotiationAck) // Don't get into infinite loops negotiating.
+			{
+				scsiEnterPhase(MESSAGE_IN);
+				uint8_t SDTR[] = {0x01, 0x03, 0x01, scsiDev.target->syncPeriod, scsiDev.target->syncOffset};
+				scsiWrite(SDTR, sizeof(SDTR));
+				scsiDev.needSyncNegotiationAck = 1; // Check if this message is rejected.
+				scsiDev.sdUnderrunCount = 0;  // reset counter, may work now.
+
+				// Set to the theoretical speed, then adjust if we measure lower
+				// actual speeds.
+				scsiDev.hostSpeedKBs = s2s_getScsiRateKBs();
+				scsiDev.hostSpeedMeasured = 0;
+			}
+		}
+		else
+		{
+			// Not supported
+			messageReject();
+		}
+	}
+	else
+	{
+		messageReject();
+	}
+
+	// Re-check the ATN flag in case it stays asserted.
+	scsiDev.atnFlag |= scsiStatusATN();
+
+	if (!scsiDev.atnFlag)
+	{
+		// Message wasn't rejected!
+		scsiDev.needSyncNegotiationAck = 0;
+	}
+}
+
+void scsiPoll(void)
+{
+	if (resetUntil != 0 && resetUntil > s2s_getTime_ms())
+	{
+		return;
+	}
+	resetUntil = 0;
+
+	if (unlikely(scsiDev.resetFlag))
+	{
+		scsiReset();
+		// Still in reset phase for a few ms.
+		// Do not try and process any commands.
+		return;
+	}
+
+	switch (scsiDev.phase)
+	{
+	case BUS_FREE:
+		if (scsiStatusBSY())
+		{
+			scsiDev.phase = BUS_BUSY;
+		}
+		// The Arbitration phase is optional for SCSI1/SASI hosts if there is only
+		// one initiator in the chain. Support this by moving
+		// straight to selection if SEL is asserted.
+		// ie. the initiator won't assert BSY and it's own ID before moving to selection.
+		else if (scsiDev.selFlag || *SCSI_STS_SELECTED)
+		{
+			enter_SelectionPhase();
+		}
+	break;
+
+	case BUS_BUSY:
+		// Someone is using the bus. Perhaps they are trying to
+		// select us.
+		if (scsiDev.selFlag || *SCSI_STS_SELECTED)
+		{
+			enter_SelectionPhase();
+		}
+		else if (!scsiStatusBSY())
+		{
+			scsiDev.phase = BUS_FREE;
+		}
+	break;
+
+	case ARBITRATION:
+		// TODO Support reselection.
+		break;
+
+	case SELECTION:
+		process_SelectionPhase();
+	break;
+
+	case RESELECTION:
+		// Not currently supported!
+	break;
+
+	case COMMAND:
+		// Do not check ATN here. SCSI 1 & 2 initiators must set ATN
+		// and SEL together upon entering the selection phase if they
+		// want to send a message (IDENTIFY) immediately.
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_Command();
+		}
+	break;
+
+	case DATA_IN:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_DataIn();
+		}
+	break;
+
+	case DATA_OUT:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_DataOut();
+		}
+	break;
+
+	case STATUS:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_Status();
+		}
+	break;
+
+	case MESSAGE_IN:
+		scsiDev.atnFlag |= scsiStatusATN();
+		if (scsiDev.atnFlag)
+		{
+			process_MessageOut();
+		}
+		else
+		{
+			process_MessageIn(1);
+		}
+
+	break;
+
+	case MESSAGE_OUT:
+		process_MessageOut();
+	break;
+	}
+}
+
+void scsiInit()
+{
+	static int firstInit = 1;
+
+	scsiDev.atnFlag = 0;
+	scsiDev.resetFlag = 1;
+	scsiDev.selFlag = 0;
+	scsiDev.phase = BUS_FREE;
+	scsiDev.target = NULL;
+	scsiDev.compatMode = COMPAT_UNKNOWN;
+	scsiDev.hostSpeedKBs = 0;
+	scsiDev.hostSpeedMeasured = 0;
+
+	int i;
+	for (i = 0; i < S2S_MAX_TARGETS; ++i)
+	{
+		const S2S_TargetCfg* cfg = s2s_getConfigByIndex(i);
+		if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
+		{
+			scsiDev.targets[i].targetId = cfg->scsiId & S2S_CFG_TARGET_ID_BITS;
+			scsiDev.targets[i].cfg = cfg;
+
+			scsiDev.targets[i].liveCfg.bytesPerSector = cfg->bytesPerSector;
+		}
+		else
+		{
+			scsiDev.targets[i].targetId = 0xff;
+			scsiDev.targets[i].cfg = NULL;
+		}
+		scsiDev.targets[i].reservedId = -1;
+		scsiDev.targets[i].reserverId = -1;
+		if (firstInit)
+		{
+			scsiDev.targets[i].unitAttention = POWER_ON_RESET;
+		}
+		else
+		{
+			scsiDev.targets[i].unitAttention = PARAMETERS_CHANGED;
+		}
+		scsiDev.targets[i].sense.code = NO_SENSE;
+		scsiDev.targets[i].sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
+
+		scsiDev.targets[i].syncOffset = 0;
+		scsiDev.targets[i].syncPeriod = 0;
+
+		// Always "start" the device. Many systems (eg. Apple System 7)
+		// won't respond properly to
+		// LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED sense
+		// code
+		scsiDev.targets[i].started = 1;
+	}
+	firstInit = 0;
+}
+
+/* TODO REENABLE
+void scsiDisconnect()
+{
+	scsiEnterPhase(MESSAGE_IN);
+	scsiWriteByte(0x02); // save data pointer
+	scsiWriteByte(0x04); // disconnect msg.
+
+	// For now, the caller is responsible for tracking the disconnected
+	// state, and calling scsiReconnect.
+	// Ideally the client would exit their loop and we'd implement this
+	// as part of scsiPoll
+	int phase = scsiDev.phase;
+	enter_BusFree();
+	scsiDev.phase = phase;
+}
+*/
+
+/* TODO REENABLE
+int scsiReconnect()
+{
+	int reconnected = 0;
+
+	int sel = SCSI_ReadFilt(SCSI_Filt_SEL);
+	int bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+	if (!sel && !bsy)
+	{
+		s2s_delay_us(1);
+		sel = SCSI_ReadFilt(SCSI_Filt_SEL);
+		bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+	}
+
+	if (!sel && !bsy)
+	{
+		// Arbitrate.
+		s2s_ledOn();
+		uint8_t scsiIdMask = 1 << scsiDev.target->targetId;
+		SCSI_Out_Bits_Write(scsiIdMask);
+		SCSI_Out_Ctl_Write(1); // Write bits manually.
+		SCSI_SetPin(SCSI_Out_BSY);
+
+		s2s_delay_us(3); // arbitrate delay. 2.4us.
+
+		uint8_t dbx = scsiReadDBxPins();
+		sel = SCSI_ReadFilt(SCSI_Filt_SEL);
+		if (sel || ((dbx ^ scsiIdMask) > scsiIdMask))
+		{
+			// Lost arbitration.
+			SCSI_Out_Ctl_Write(0);
+			SCSI_ClearPin(SCSI_Out_BSY);
+			s2s_ledOff();
+		}
+		else
+		{
+			// Won arbitration
+			SCSI_SetPin(SCSI_Out_SEL);
+			s2s_delay_us(1); // Bus clear + Bus settle.
+
+			// Reselection phase
+			SCSI_CTL_PHASE_Write(__scsiphase_io);
+			SCSI_Out_Bits_Write(scsiIdMask | (1 << scsiDev.initiatorId));
+			scsiDeskewDelay(); // 2 deskew delays
+			scsiDeskewDelay(); // 2 deskew delays
+			SCSI_ClearPin(SCSI_Out_BSY);
+			s2s_delay_us(1);  // Bus Settle Delay
+
+			uint32_t waitStart_ms = getTime_ms();
+			bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+			// Wait for initiator.
+			while (
+				!bsy &&
+				!scsiDev.resetFlag &&
+				(elapsedTime_ms(waitStart_ms) < 250))
+			{
+				bsy = SCSI_ReadFilt(SCSI_Filt_BSY);
+			}
+
+			if (bsy)
+			{
+				SCSI_SetPin(SCSI_Out_BSY);
+				scsiDeskewDelay(); // 2 deskew delays
+				scsiDeskewDelay(); // 2 deskew delays
+				SCSI_ClearPin(SCSI_Out_SEL);
+
+				// Prepare for the initial IDENTIFY message.
+				SCSI_Out_Ctl_Write(0);
+				scsiEnterPhase(MESSAGE_IN);
+
+				// Send identify command
+				scsiWriteByte(0x80);
+
+				scsiEnterPhase(scsiDev.phase);
+				reconnected = 1;
+			}
+			else
+			{
+				// reselect timeout.
+				SCSI_Out_Ctl_Write(0);
+				SCSI_ClearPin(SCSI_Out_SEL);
+				SCSI_CTL_PHASE_Write(0);
+				s2s_ledOff();
+			}
+		}
+	}
+	return reconnected;
+}
+*/
+

+ 202 - 0
lib/SCSI2SD/src/firmware/scsi.h

@@ -0,0 +1,202 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef SCSI_H
+#define SCSI_H
+
+#include "geometry.h"
+#include "sense.h"
+
+#include <stdint.h>
+
+typedef enum
+{
+	// internal bits
+	__scsiphase_msg = 1,
+	__scsiphase_cd = 2,
+	__scsiphase_io = 4,
+
+	BUS_FREE = -1,
+	BUS_BUSY = -2,
+	ARBITRATION = -3,
+	SELECTION = -4,
+	RESELECTION = -5,
+	STATUS = __scsiphase_cd | __scsiphase_io,
+	COMMAND = __scsiphase_cd,
+	DATA_IN = __scsiphase_io,
+	DATA_OUT = 0,
+	MESSAGE_IN = __scsiphase_msg | __scsiphase_cd | __scsiphase_io,
+	MESSAGE_OUT = __scsiphase_msg | __scsiphase_cd
+} SCSI_PHASE;
+
+typedef enum
+{
+	GOOD = 0,
+	CHECK_CONDITION = 2,
+	BUSY = 0x8,
+	INTERMEDIATE = 0x10,
+	CONFLICT = 0x18
+} SCSI_STATUS;
+
+typedef enum
+{
+	MSG_COMMAND_COMPLETE = 0,
+	MSG_REJECT = 0x7,
+	MSG_LINKED_COMMAND_COMPLETE = 0x0A,
+	MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG = 0x0B
+} SCSI_MESSAGE;
+
+typedef enum
+{
+	COMPAT_UNKNOWN,
+	COMPAT_SCSI1,
+
+	// Messages are being used, yet SCSI 2 mode is disabled.
+	// This impacts interpretation of INQUIRY commands.
+	COMPAT_SCSI2_DISABLED,
+
+	COMPAT_SCSI2
+} SCSI_COMPAT_MODE;
+
+// Maximum value for bytes-per-sector.
+#ifndef MAX_SECTOR_SIZE
+#define MAX_SECTOR_SIZE 8192
+#endif
+
+#ifndef MIN_SECTOR_SIZE
+#define MIN_SECTOR_SIZE 64
+#endif
+
+#ifndef SCSI2SD_BUFFER_SIZE
+#define SCSI2SD_BUFFER_SIZE (MAX_SECTOR_SIZE * 8)
+#endif
+
+// Shadow parameters, possibly not saved to flash yet.
+// Set via Mode Select
+typedef struct
+{
+	uint16_t bytesPerSector;
+} LiveCfg;
+
+typedef struct
+{
+	uint8_t targetId;
+
+	const S2S_TargetCfg* cfg;
+
+	LiveCfg liveCfg;
+
+	ScsiSense sense;
+
+	uint16_t unitAttention; // Set to the sense qualifier key to be returned.
+
+	// Only let the reserved initiator talk to us.
+	// A 3rd party may be sending the RESERVE/RELEASE commands
+	int reservedId; // 0 -> 7 if reserved. -1 if not reserved.
+	int reserverId; // 0 -> 7 if reserved. -1 if not reserved.
+
+	uint8_t syncOffset;
+	uint8_t syncPeriod;
+
+	uint8_t started; // Controlled by START STOP UNIT
+} TargetState;
+
+typedef struct
+{
+	// TODO reduce this buffer size and add a proper cache
+	// Must be aligned for DMA
+	// 65536 bytes is the DMA limit
+	uint8_t data[SCSI2SD_BUFFER_SIZE];
+
+	TargetState targets[S2S_MAX_TARGETS];
+	TargetState* target;
+	S2S_BoardCfg boardCfg;
+
+
+	// Set to true (1) if the ATN flag was set, and we need to
+	// enter the MESSAGE_OUT phase.
+	int atnFlag;
+
+	// Set to true (1) if the RST flag was set.
+	volatile int resetFlag;
+
+	// Set to sel register if the SEL flag was set.
+	volatile int selFlag;
+
+	// Set to true (1) if a parity error was observed.
+	int parityError;
+
+	int phase;
+
+	int dataPtr; // Index into data, reset on [re]selection to savedDataPtr
+	int savedDataPtr; // Index into data, initially 0.
+	int dataLen;
+
+	uint8_t cdb[12]; // command descriptor block
+	uint8_t cdbLen; // 6, 10, or 12 byte message.
+	int8_t lun; // Target lun, set by IDENTIFY message.
+	uint8_t discPriv; // Disconnect priviledge.
+	uint8_t compatMode; // SCSI_COMPAT_MODE
+
+	// Only let the reserved initiator talk to us.
+	// A 3rd party may be sending the RESERVE/RELEASE commands
+	int initiatorId; // 0 -> 7. Set during the selection phase.
+
+	// SCSI_STATUS value.
+	// Change to CHECK_CONDITION when setting a SENSE value
+	uint8_t status;
+
+	uint8_t msgIn;
+	uint8_t msgOut;
+
+	void (*postDataOutHook)(void);
+
+	uint8_t cmdCount;
+	uint8_t selCount;
+	uint8_t rstCount;
+	uint8_t msgCount;
+	uint8_t watchdogTick;
+	uint8_t lastStatus;
+	uint8_t lastSense;
+	uint16_t lastSenseASC;
+	uint8_t minSyncPeriod; // Debug use only.
+
+	int needSyncNegotiationAck;
+	int sdUnderrunCount;
+
+	// Estimate of the SCSI host actual speed
+	uint32_t hostSpeedKBs;
+	int hostSpeedMeasured;
+} ScsiDevice;
+
+extern ScsiDevice scsiDev;
+
+void process_Status(void);
+int process_MessageIn(int releaseBusFree);
+void enter_BusFree(void);
+
+void scsiInit(void);
+void scsiPoll(void);
+void scsiDisconnect(void);
+int scsiReconnect(void);
+
+
+// Utility macros, consistent with the Linux Kernel code.
+#define likely(x)       __builtin_expect(!!(x), 1)
+#define unlikely(x)     __builtin_expect(!!(x), 0)
+//#define likely(x)       (x)
+//#define unlikely(x)     (x)
+#endif

+ 48 - 0
lib/SCSI2SD/src/firmware/sd.h

@@ -0,0 +1,48 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_SD_H
+#define S2S_SD_H
+
+#include <stdint.h>
+
+#define SD_SECTOR_SIZE 512
+
+typedef struct
+{
+	int version; // SDHC = version 2.
+	uint32_t capacity; // in 512 byte blocks
+
+	uint8_t csd[16]; // Unparsed CSD
+	uint8_t cid[16]; // Unparsed CID
+} SdDevice;
+
+extern SdDevice sdDev;
+
+int sdInit(void);
+
+void sdReadDMA(uint32_t lba, uint32_t sectors, uint8_t* outputBuffer);
+int sdReadDMAPoll(uint32_t remainingSectors);
+
+void sdReadCmd(uint32_t lba, uint32_t sectors);
+void sdReadPIOData(uint32_t sectors);
+
+void sdCompleteTransfer();
+void sdKeepAlive();
+
+int sdIsBusy();
+
+#endif

+ 178 - 0
lib/SCSI2SD/src/firmware/sense.h

@@ -0,0 +1,178 @@
+//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef SENSE_H
+#define SENSE_H
+
+#include <stdint.h>
+
+typedef enum
+{
+	NO_SENSE                                               = 0,
+	RECOVERED_ERROR                                        = 1,
+	NOT_READY                                              = 2,
+	MEDIUM_ERROR                                           = 3,
+	HARDWARE_ERROR                                         = 4,
+	ILLEGAL_REQUEST                                        = 5,
+	UNIT_ATTENTION                                         = 6,
+	DATA_PROTECT                                           = 7,
+	BLANK_CHECK                                            = 8,
+	VENDOR_SPECIFIC                                        = 9,
+	COPY_ABORTED                                           = 0xA,
+	ABORTED_COMMAND                                        = 0xB,
+	EQUAL                                                  = 0xC,
+	VOLUME_OVERFLOW                                        = 0xD,
+	MISCOMPARE                                             = 0xE,
+	RESERVED                                               = 0xF
+} SCSI_SENSE;
+
+// Top 8 bits = ASC. Lower 8 bits = ASCQ.
+// Enum only contains definitions for direct-access related codes.
+typedef enum
+{
+	ADDRESS_MARK_NOT_FOUND_FOR_DATA_FIELD                  = 0x1300,
+	ADDRESS_MARK_NOT_FOUND_FOR_ID_FIELD                    = 0x1200,
+	CANNOT_READ_MEDIUM_INCOMPATIBLE_FORMAT                 = 0x3002,
+	CANNOT_READ_MEDIUM_UNKNOWN_FORMAT                      = 0x3001,
+	CHANGED_OPERATING_DEFINITION                           = 0x3F02,
+	COMMAND_PHASE_ERROR                                    = 0x4A00,
+	COMMAND_SEQUENCE_ERROR                                 = 0x2C00,
+	COMMANDS_CLEARED_BY_ANOTHER_INITIATOR                  = 0x2F00,
+	COPY_CANNOT_EXECUTE_SINCE_HOST_CANNOT_DISCONNECT       = 0x2B00,
+	DATA_PATH_FAILURE                                      = 0x4100,
+	DATA_PHASE_ERROR                                       = 0x4B00,
+	DATA_SYNCHRONIZATION_MARK_ERROR                        = 0x1600,
+	DEFECT_LIST_ERROR                                      = 0x1900,
+	DEFECT_LIST_ERROR_IN_GROWN_LIST                        = 0x1903,
+	DEFECT_LIST_ERROR_IN_PRIMARY_LIST                      = 0x1902,
+	DEFECT_LIST_NOT_AVAILABLE                              = 0x1901,
+	DEFECT_LIST_NOT_FOUND                                  = 0x1C00,
+	DEFECT_LIST_UPDATE_FAILURE                             = 0x3201,
+	ERROR_LOG_OVERFLOW                                     = 0x0A00,
+	ERROR_TOO_LONG_TO_CORRECT                              = 0x1102,
+	FORMAT_COMMAND_FAILED                                  = 0x3101,
+	GROWN_DEFECT_LIST_NOT_FOUND                            = 0x1C02,
+	IO_PROCESS_TERMINATED                                  = 0x0006,
+	ID_CRC_OR_ECC_ERROR                                    = 0x1000,
+	ILLEGAL_FUNCTION                                       = 0x2200,
+	INCOMPATIBLE_MEDIUM_INSTALLED                          = 0x3000,
+	INITIATOR_DETECTED_ERROR_MESSAGE_RECEIVED              = 0x4800,
+	INQUIRY_DATA_HAS_CHANGED                               = 0x3F03,
+	INTERNAL_TARGET_FAILURE                                = 0x4400,
+	INVALID_BITS_IN_IDENTIFY_MESSAGE                       = 0x3D00,
+	INVALID_COMMAND_OPERATION_CODE                         = 0x2000,
+	INVALID_FIELD_IN_CDB                                   = 0x2400,
+	INVALID_FIELD_IN_PARAMETER_LIST                        = 0x2600,
+	INVALID_MESSAGE_ERROR                                  = 0x4900,
+	LOG_COUNTER_AT_MAXIMUM                                 = 0x5B02,
+	LOG_EXCEPTION                                          = 0x5B00,
+	LOG_LIST_CODES_EXHAUSTED                               = 0x5B03,
+	LOG_PARAMETERS_CHANGED                                 = 0x2A02,
+	LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE                     = 0x2100,
+	LOGICAL_UNIT_COMMUNICATION_FAILURE                     = 0x0800,
+	LOGICAL_UNIT_COMMUNICATION_PARITY_ERROR                = 0x0802,
+	LOGICAL_UNIT_COMMUNICATION_TIMEOUT                     = 0x0801,
+	LOGICAL_UNIT_DOES_NOT_RESPOND_TO_SELECTION             = 0x0500,
+	LOGICAL_UNIT_FAILED_SELF_CONFIGURATION                 = 0x4C00,
+	LOGICAL_UNIT_HAS_NOT_SELF_CONFIGURED_YET               = 0x3E00,
+	LOGICAL_UNIT_IS_IN_PROCESS_OF_BECOMING_READY           = 0x0401,
+	LOGICAL_UNIT_NOT_READY_CAUSE_NOT_REPORTABLE            = 0x0400,
+	LOGICAL_UNIT_NOT_READY_FORMAT_IN_PROGRESS              = 0x0404,
+	LOGICAL_UNIT_NOT_READY_INITIALIZING_COMMAND_REQUIRED   = 0x0402,
+	LOGICAL_UNIT_NOT_READY_MANUAL_INTERVENTION_REQUIRED    = 0x0403,
+	LOGICAL_UNIT_NOT_SUPPORTED                             = 0x2500,
+	MECHANICAL_POSITIONING_ERROR                           = 0x1501,
+	MEDIA_LOAD_OR_EJECT_FAILED                             = 0x5300,
+	MEDIUM_FORMAT_CORRUPTED                                = 0x3100,
+	MEDIUM_NOT_PRESENT                                     = 0x3A00,
+	MEDIUM_REMOVAL_PREVENTED                               = 0x5302,
+	MESSAGE_ERROR                                          = 0x4300,
+	MICROCODE_HAS_BEEN_CHANGED                             = 0x3F01,
+	MISCOMPARE_DURING_VERIFY_OPERATION                     = 0x1D00,
+	MISCORRECTED_ERROR                                     = 0x110A,
+	MODE_PARAMETERS_CHANGED                                = 0x2A01,
+	MULTIPLE_PERIPHERAL_DEVICES_SELECTED                   = 0x0700,
+	MULTIPLE_READ_ERRORS                                   = 0x1103,
+	NO_ADDITIONAL_SENSE_INFORMATION                        = 0x0000,
+	NO_DEFECT_SPARE_LOCATION_AVAILABLE                     = 0x3200,
+	NO_INDEX_SECTOR_SIGNAL                                 = 0x0100,
+	NO_REFERENCE_POSITION_FOUND                            = 0x0600,
+	NO_SEEK_COMPLETE                                       = 0x0200,
+	NOT_READY_TO_READY_TRANSITION_MEDIUM_MAY_HAVE_CHANGED  = 0x2800,
+	OPERATOR_MEDIUM_REMOVAL_REQUEST                        = 0x5A01,
+	OPERATOR_REQUEST_OR_STATE_CHANGE_INPUT                 = 0x5A00,
+	OPERATOR_SELECTED_WRITE_PERMIT                         = 0x5A03,
+	OPERATOR_SELECTED_WRITE_PROTECT                        = 0x5A02,
+	OVERLAPPED_COMMANDS_ATTEMPTED                          = 0x4E00,
+	PARAMETER_LIST_LENGTH_ERROR                            = 0x1A00,
+	PARAMETER_NOT_SUPPORTED                                = 0x2601,
+	PARAMETER_VALUE_INVALID                                = 0x2602,
+	PARAMETERS_CHANGED                                     = 0x2A00,
+	PERIPHERAL_DEVICE_WRITE_FAULT                          = 0x0300,
+	POSITIONING_ERROR_DETECTED_BY_READ_OF_MEDIUM           = 0x1502,
+	POWER_ON_RESET_OR_BUS_DEVICE_RESET_OCCURRED            = 0x2900,
+	POWER_ON_RESET                                         = 0x2901,	
+	POWER_ON_OR_SELF_TEST_FAILURE                          = 0x4200,
+	PRIMARY_DEFECT_LIST_NOT_FOUND                          = 0x1C01,
+	RAM_FAILURE                                            = 0x4000,
+	RANDOM_POSITIONING_ERROR                               = 0x1500,
+	READ_RETRIES_EXHAUSTED                                 = 0x1101,
+	RECORD_NOT_FOUND                                       = 0x1401,
+	RECORDED_ENTITY_NOT_FOUND                              = 0x1400,
+	RECOVERED_DATA_DATA_AUTO_REALLOCATED                   = 0x1802,
+	RECOVERED_DATA_RECOMMEND_REASSIGNMENT                  = 0x1805,
+	RECOVERED_DATA_RECOMMEND_REWRITE                       = 0x1806,
+	RECOVERED_DATA_USING_PREVIOUS_SECTOR_ID                = 0x1705,
+	RECOVERED_DATA_WITH_ERROR_CORRECTION_RETRIES_APPLIED   = 0x1801,
+	RECOVERED_DATA_WITH_ERROR_CORRECTION_APPLIED           = 0x1800,
+	RECOVERED_DATA_WITH_NEGATIVE_HEAD_OFFSET               = 0x1703,
+	RECOVERED_DATA_WITH_NO_ERROR_CORRECTION_APPLIED        = 0x1700,
+	RECOVERED_DATA_WITH_POSITIVE_HEAD_OFFSET               = 0x1702,
+	RECOVERED_DATA_WITH_RETRIES                            = 0x1701,
+	RECOVERED_DATA_WITHOUT_ECC_DATA_AUTO_REALLOCATED       = 0x1706,
+	RECOVERED_DATA_WITHOUT_ECC_RECOMMEND_REASSIGNMENT      = 0x1707,
+	RECOVERED_DATA_WITHOUT_ECC_RECOMMEND_REWRITE           = 0x1708,
+	RECOVERED_ID_WITH_ECC_CORRECTION                       = 0x1E00,
+	ROUNDED_PARAMETER                                      = 0x3700,
+	RPL_STATUS_CHANGE                                      = 0x5C00,
+	SAVING_PARAMETERS_NOT_SUPPORTED                        = 0x3900,
+	SCSI_BUS_RESET                                         = 0x2902,
+	SCSI_PARITY_ERROR                                      = 0x4700,
+	SELECT_OR_RESELECT_FAILURE                             = 0x4500,
+	SPINDLES_NOT_SYNCHRONIZED                              = 0x5C02,
+	SPINDLES_SYNCHRONIZED                                  = 0x5C01,
+	SYNCHRONOUS_DATA_TRANSFER_ERROR                        = 0x1B00,
+	TARGET_OPERATING_CONDITIONS_HAVE_CHANGED               = 0x3F00,
+	THRESHOLD_CONDITION_MET                                = 0x5B01,
+	THRESHOLD_PARAMETERS_NOT_SUPPORTED                     = 0x2603,
+	TRACK_FOLLOWING_ERROR                                  = 0x0900,
+	UNRECOVERED_READ_ERROR                                 = 0x1100,
+	UNRECOVERED_READ_ERROR_AUTO_REALLOCATE_FAILED          = 0x1104,
+	UNRECOVERED_READ_ERROR_RECOMMEND_REASSIGNMENT          = 0x110B,
+	UNRECOVERED_READ_ERROR_RECOMMEND_REWRITE_THE_DATA      = 0x110C,
+	UNSUCCESSFUL_SOFT_RESET                                = 0x4600,
+	WRITE_ERROR_AUTO_REALLOCATION_FAILED                   = 0x0C02,
+	WRITE_ERROR_RECOVERED_WITH_AUTO_REALLOCATION           = 0x0C01,
+	WRITE_PROTECTED                                        = 0x2700
+} SCSI_ASC_ASCQ;
+
+typedef struct
+{
+	uint8_t code;
+	uint16_t asc;
+} ScsiSense;
+
+#endif

+ 29 - 0
lib/SCSI2SD/src/firmware/tape.c

@@ -0,0 +1,29 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "config.h"
+#include "tape.h"
+
+// Handle sequential scsi device commands
+int scsiTapeCommand()
+{
+	// TODO handle tape-specific read/write commands and return 1
+
+	return 0;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/tape.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2015 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef TAPE_H
+#define TAPE_H
+
+int scsiTapeCommand(void);
+
+#endif

+ 58 - 0
lib/SCSI2SD/src/firmware/vendor.c

@@ -0,0 +1,58 @@
+//	Copyright (C) 2016 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "scsi.h"
+#include "vendor.h"
+
+
+// Callback after the DATA OUT phase is complete.
+static void doAssignDiskParameters(void)
+{
+	scsiDev.phase = STATUS;
+}
+
+int scsiVendorCommand()
+{
+	int commandHandled = 1;
+
+	uint8_t command = scsiDev.cdb[0];
+
+	if (command == 0xC0)
+	{
+		// Define flexible disk format
+		// OMTI-5204 controller
+		// http://bitsavers.informatik.uni-stuttgart.de/pdf/sms/OMTI_5x00.pdf
+		// Stub. Sectors-per-track should be configured by scsi2sd-util
+	}
+	else if (command == 0xC2)
+	{
+		// Assign Disk Parameters command
+		// OMTI-5204 controller
+		// http://bitsavers.informatik.uni-stuttgart.de/pdf/sms/OMTI_5x00.pdf
+		// Stub to read and discard 10 bytes.
+		scsiDev.dataLen = 10;
+		scsiDev.phase = DATA_OUT;
+		scsiDev.postDataOutHook = doAssignDiskParameters;
+	}
+	else
+	{
+		commandHandled = 0;
+	}
+
+	return commandHandled;
+}
+

+ 22 - 0
lib/SCSI2SD/src/firmware/vendor.h

@@ -0,0 +1,22 @@
+//	Copyright (C) 2016 Michael McMaster <michael@codesrc.com>
+//
+//	This file is part of SCSI2SD.
+//
+//	SCSI2SD is free software: you can redistribute it and/or modify
+//	it under the terms of the GNU General Public License as published by
+//	the Free Software Foundation, either version 3 of the License, or
+//	(at your option) any later version.
+//
+//	SCSI2SD is distributed in the hope that 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 SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.
+#ifndef S2S_VENDOR_H
+#define S2S_VENDOR_H
+
+int scsiVendorCommand(void);
+
+#endif