minix/drivers/ahci/ahci.c
David van Moolenbroek 25202e8ee6 work-3.2.1 WIP, jenkins testing only
Squashed commit of the following:

commit 8646fb1e83ab28403b022b57681340d1cc0ccf46
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Oct 5 20:24:25 2013 +0200

    3.3.0 sync fixes

    Change-Id: Ic91e326623f6dbad58754152c4a632e4406f1117

commit 63108441f284ea07a47dc899f8774ba0e0ccd9c7
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Oct 2 13:37:49 2013 +0200

    keymaps: improve keypad slash support

    Now that the keymaps can distinguish between the regular slash key
    and the slash key on the numeric keypad, we can avoid localization
    of the latter.

    Change-Id: I20ead7d26a9baa82f5a522562524fd75d44efb42

commit 3c406fb627b8f38efd6796ab4df48b07ca1d1330
Author: Mikal Villa <mikal.villa@gmail.com>
Date:   Wed Oct 2 12:30:03 2013 +0200

    Norwegian keymap

    Change-Id: I181234afc8f1a058e92af6c1fe88979463aaff45

commit 1878c8160e755ada0976f7786f00cbad5abc89d5
Author: Thomas Cort <tcort@minix3.org>
Date:   Fri Sep 6 21:40:42 2013 -0400

    Importing usr.bin/uname

    Change-Id: I4c316221e288edd839e26a2af4cb59f28bf722c1

commit 7e885159803dd37c4c4d9780f8d00fe2f09637f4
Author: Thomas Cort <tcort@minix3.org>
Date:   Fri Sep 6 21:40:31 2013 -0400

    uname: normalize release and version

    Most systems provide the full version number in the
    'release' field and the kernel version in 'version'.
    Minix used to split the full version number between
    release and version which caused problems for pkgsrc
    and other applications. This patch brings Minix's
    uname in line with other systems such as NetBSD.
    It also brings the getty banner in line with NetBSD.

    Old Minix uname:
    	sysname->Minix
    	nodename->10.0.2.15
    	release->3
    	version->2.1
    	machine->i686

    New Minix uname:
    	sysname->Minix
    	nodename->10.0.2.15
    	release->3.2.1
    	version->Minix 3.2.1 (GENERIC)
    	machine->i686

    Change-Id: I966633dfdcf2f9485966bb0d0d042afc45bbeb7d

commit 2009480d378df66cb06410a8826692d1da7568fa
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Thu Sep 19 10:57:10 2013 +0200

    Replacing timer_t by netbsd's timer_t

     * Renamed struct timer to struct minix_timer
     * Renamed timer_t to minix_timer_t
     * Ensured all the code uses the minix_timer_t typedef
     * Removed ifdef around _BSD_TIMER_T
     * Removed include/timers.h and merged it into include/minix/timers.h
     * Resolved prototype conflict by renaming kernel's (re)set_timer
       to (re)set_kernel_timer.

    Change-Id: I56f0f30dfed96e1a0575d92492294cf9a06468a5

commit 4ca61b56bc2ee3a92a3c263ee087a4285cb59e23
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Wed Oct 2 10:56:24 2013 +0200

    ARM serial driver: Comment termios_baud_rate.

    The B0-B115200 defines are flags, and not the actual speed they
    represent.

    This fixes an incoherency for B0 handling, and documents why it is
    required to call the function again after changing the speed flag.

    DFL_BAUD is set to one of the flag, so to translate it to an actual
    speed, the function calls itself again, which will always be able to
    finish without inducing another recursive call.

    Change-Id: I04ebfaefee31a88d05f0b726352d1581a966147b

commit faa74cbd048e0a88e88069371c16d3e94cb0d416
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Oct 1 23:25:01 2013 +0200

    TTY: skip /dev/log checks if console is serial

    It is unclear why /dev/log has its own open/close rules, but those
    rules conflict with serial console redirection.  This does not solve
    the root of the problem, but it puts back in place more or less the
    same workaround that was already in place before the TTY overhaul.

    Change-Id: Ib53abbc28a76c1f2b0befc8448aeed0173bc96a5

commit ebe655e3eaa75d699c0ddd779d2d933f6a633536
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Oct 5 19:45:42 2013 +0200

    Corrections to keep VFS ABI-compatible

    Change-Id: Id8a53a687577c1b6f83c11c2e5fb0b8ed1cd2a08

commit e84ddc012c46e8ff9a0d28845802bf8bacafe69d
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Oct 5 19:35:11 2013 +0200

    Remove support for obsolete 3.2.1 ABI

    Change-Id: I76b4960bda41f55d9c42f8c99c5beae3424ca851

commit ec33810903eb97304def78622512eae77a030813
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Aug 31 22:59:44 2013 +0200

    Fix various file system warnings

    Change-Id: Ied10498c3ae14f9f2fd06914f23239df330fa296

commit 8aa6eb34ef649be235eaea0c5790993dd0d25cc6
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Oct 5 19:22:13 2013 +0200

    VFS/FS: replace protocol version with flag field

    The main motivation for this change is that only Loris supports
    multithreading, and Loris supports dynamic thread allocation, so the
    number of supported threads can be implemented as a bit flag (i.e.,
    either 1 or "at least as many as VFS has"). The ABI break obviates the
    need to support file system versioning at this time, and several
    other aspects are better implemented as flags as well. Other changes:

    - replace peek/bpeek test upon mount with FS flag as well;
    - mark libsffs as 64-bit file size capable;
    - remove old (3.2.1) getdents support.

    Change-Id: I313eace9c50ed816656c31cd47d969033d952a03

commit 238277f6362c53f6ebbdf5c5a1a1f9e0d2d064f7
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Fri Apr 19 13:16:51 2013 +0200

    usr.bin/stat Update

    Change-Id: I029160c73baab1b3465bc5397a36c55886db225b

commit 43b5aee7aed02dde0a4492281018dcac5bb03960
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Fri Apr 19 09:54:51 2013 +0200

    almost aligned ioctl prototype

    Change-Id: I7f3eaa99d2a9767f71e8387cea5c7f56dcb28f99

commit db4481b36d7a1fc9af693a9723a4db75b6a05cad
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Thu Apr 18 14:42:15 2013 +0200

    32 to 64 bits fsblkcnt_t and fsfilcnt_t.

    Change-Id: I432229143c85cd178262b802a76ac606801ac59a

commit c12a12d3a34c0404b7ec7894fc9af76b78e9a060
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Thu Apr 18 11:08:16 2013 +0200

    moving prototypes to lib.h

    Change-Id: If53d3f5ee761b10e0f3d4346a0c5b39ba7901c65

commit 9faa3c17db034876f3be63730ae6af30abf788da
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Thu Apr 18 11:07:44 2013 +0200

    struct uucred

    Change-Id: Ia97cb6c38bb566be30d568a252ae7b76142a21dd

commit 30af4bde8a0a19b70045456e2beae76af0bc6e6a
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Tue Apr 16 11:48:54 2013 +0200

    Alignement on netbsd types, part 1

    The following types are modified (old -> new):
     * _BSD_USECONDS_T_ int       -> unsigned int
     * __socklen_t      __int32_t -> __uint32_t
     * blksize_t        uint32_t  -> int32_t
     * rlim_t           uint32_t  -> uint64_t
    On ARM:
     * _BSD_CLOCK_T_    int       -> unsigned int
    On Intel:
     * _BSD_CLOCK_T_    int       -> unsigned long

    bin/cat is also updated in order to fix warnings.

    _BSD_TIMER_T_ has still to be aligned.

    Change-Id: I2b4fda024125a19901120546c4e22e443ba5e9d7

commit ab33eaee106a01e573a538aa02beb40dcc34fcb7
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Fri Aug 23 20:27:27 2013 +0200

    Adapt the type used for adjtime_delta

    clock_t is currently a signed type, but in NetBSD this is not the
    case. As we plan on aligning our types we have to change this as this
    prevents negative delta from being correctly used.

    Change-Id: I9bccdee2b41626b0262471dc1900de505a1991a7

commit 8493229abca046bbb3af93196450d80d10839051
Author: Lionel Sambuc <lionel@minix3.org>
Date:   Wed Apr 17 13:01:55 2013 +0200

    fix sysroot clang

    Change-Id: I63adf8460e7fdbe3d9d5ad6c828ae5758cb1643c

commit 23dfcbfb12a6e9f2589e10377a2ae926745f281d
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Oct 5 19:01:19 2013 +0200

    Butchered version of "Bumping version.."

    Change-Id: Ieb5f31fe037bcf45cc86a906f983a0ab3f636468

commit bfd7276abd1ec5e6f4af7f8276e97036faf2f621
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Oct 5 18:57:04 2013 +0200

    Trimmed version of "VFS: use 64-bit file..."

    Change-Id: I1b53ef05e939f6afed97f507d443a1d60ed81a21

commit f3fe36d509fda37908b86a64e045e6e098a6642b
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Oct 5 18:37:39 2013 +0200

    Trimmed version of "stat.h: remove some big_ types"

    Change-Id: I6c17d6e5e9d2ab4c1bbf33e461cec4c37b870266

commit 102318751f05b39cc8b21c1e63b04c4980d0363b
Author: Thomas Veerman <thomas@minix3.org>
Date:   Thu Mar 7 14:46:21 2013 +0000

    Define protocol version of {mode,ino,uid,gid}_t

    Change-Id: Ia2027749f2ce55a561d19eb895a5618505e9a2ac

commit 987b05d80433dd5cdc1ccca348c1156f68698394
Author: Thomas Veerman <thomas@minix3.org>
Date:   Mon Mar 25 21:09:10 2013 +0000

    VFS-FS protocol: add versioning

    Change-Id: Ice6fbfd4b535b7435653fa08b27a3378d1cfdbf8

commit 59d748a0c59b0b0b335deaddc48d64f4075768be
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Sep 28 14:46:21 2013 +0200

    Input infrastructure, INPUT server, PCKBD driver

    This commit separates the low-level keyboard driver from TTY, putting
    it in a separate driver (PCKBD). The commit also separates management
    of raw input devices from TTY, and puts it in a separate server
    (INPUT). All keyboard and mouse input from hardware is sent by drivers
    to the INPUT server, which either sends it to a process that has
    opened a raw input device, or otherwise forwards it to TTY for
    standard processing.

    Design by Dirk Vogt. Prototype by Uli Kastlunger.

    Additional changes made to the prototype:

    - the event communication is now based on USB HID codes; all input
      drivers have to use USB codes to describe events;
    - all TTY keymaps have been converted to USB format, with the effect
      that a single keymap covers all keys; there is no (static) escaped
      keymap anymore;
    - further keymap tweaks now allow remapping of literally all keys;
    - input device renumbering and protocol rewrite;
    - INPUT server rewrite, with added support for cancel and select;
    - PCKBD reimplementation, including PC/AT-to-USB translation;
    - support for manipulating keyboard LEDs has been added;
    - keyboard and mouse multiplexer devices have been added to INPUT,
      primarily so that an X server need only open two devices;
    - a new "libinputdriver" library abstracts away protocol details from
      input drivers, and should be used by all future input drivers;
    - both INPUT and PCKBD can be restarted;
    - TTY is now scheduled by KERNEL, so that it won't be punished for
      running a lot; without this, simply running "yes" on the console
      kills the system;
    - the KIOCBELL IOCTL has been moved to /dev/console;
    - support for the SCANCODES termios setting has been removed;
    - obsolete keymap compression has been removed;
    - the obsolete Olivetti M24 keymap has been removed.

    Change-Id: I3a672fb8c4fd566734e4b46d3994b4b7fc96d578

commit fff12f52f32208f3cc97d0f39080eb3e19288b57
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Sep 27 11:56:29 2013 +0000

    TTY: allow selecting on translated minors

    Due to the existence of /dev/console and /dev/log, and the new
    "console=" setting, it is now possible that a single non-PTY object
    (e.g. serial) is accessible through two different minor numbers.  This
    poses a problem when sending late select replies (CDEV_SEL2_REPLY),
    because the object's minor number can not be used to identify the
    device.  Since selecting on such objects through translated minor
    numbers is actually required, we now save the minor number used to
    initiate the select query in order to send a late reply.

    The solution is suboptimal, as it is not possible to use two different
    minors to select on the same object at once.  In the future, there
    should be at least one select record for each minor that can be used
    with each object.

    Change-Id: I4d39681d2ffd68b4047daf933d45b7bafe3c885e

commit 09cd72d9d415fd83f7bc0c652ba54ac9936916b8
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Sep 21 17:35:15 2013 +0200

    Take LOG out of the boot image

    Change-Id: Id2629776b53aae46629b04a42c15cbbacac9b949

commit 717b780db6338a136a97bb1ea1464933b7cef90e
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Sep 21 15:03:20 2013 +0200

    Kernel: make SIGKMESS target process list dynamic

    The set of processes to which a SIGKMESS signal is sent whenever new
    diagnostics messages are added to the kernel's message buffer, is now
    no longer hardcoded. Instead, processes can (un)register themselves
    to receive such notifications, by means of sys_diagctl().

    Change-Id: I9d6ac006a5d9bbfad2757587a068fc1ec3cc083e

commit bdf6de1805fcdb3c1b706a8f980e4b65a65e085a
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Sep 21 00:58:46 2013 +0200

    Rename SYSCTL kernel call to DIAGCTL

    Change-Id: I1b17373f01808d887dcbeab493838946fbef4ef6

commit 0270b4ac40f985bb3c3dcca9ff9c6c030755a017
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 20:13:59 2013 +0200

    Prevent Jenkins from breaking on old df(1)

    Change-Id: Ie8e1758bd9de4d5c95c597302f80d568058fbb68

commit e17a2a5337d16d451bdcb0cbc7d1dfb05b1cc383
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 14:09:47 2013 +0200

    Add testvnd.sh test script

    As part of this, change the "run" script to allow certain scripts to
    be run as root only.

    Change-Id: I846e41037f9d4f6c7fc0b5ea8250303a7bd72f5d

commit b2528b7a6f782fd45e16f75016c32f1543e32a08
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 14:06:46 2013 +0200

    Import NetBSD vndconfig(8)

    The tool has been changed heavily to match our VND driver model.

    NetBSD is in the process of renaming it from vnconfig(8) to
    vndconfig(8). To keep things in sync, we have to play along.

    Change-Id: Ie86df184f03ab00573ea76b43c9caa0412e8321d

commit 2d73d3838881f14c3fe1817103e1b91e9674f33c
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 14:02:17 2013 +0200

    Add VND driver, providing loopback devices

    Change-Id: I40fa695e28c67477a75383e6f1550e451afcab41

commit 976a539cafddfe3c802a9e361bebd8cf45635e7d
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 13:55:15 2013 +0200

    VFS: add dupfrom(2) call

    This call copies a file descriptor from a remote process into the
    calling process. The call is for the VND driver only, and in the
    future, ACLs will prevent any other process from using this call.

    Change-Id: Ib16fdd1f1a12cb38a70d7e441dad91bc86898f6d

commit 524aa989d316c33be15087f7865570bad6382dd0
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 11:46:08 2013 +0200

    tests: do not skip installed shell tests

    When installed, the test scripts lose their ".sh" suffix, causing them
    to be skipped by the "run" script. With this patch, the tests are no
    longer specified with ".sh" suffix in the run script, and the suffix
    is added automatically as necessary.

    Change-Id: I0b72312e79992b9818559c6546a0e52cd95184c2

commit e4da523dd76a4fafb2c756a85a7abf5b0def91b9
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 13:41:56 2013 +0200

    blocktest: prepare to be run as part of tests

    - fail SEF initialization if any of the subtests failed, so that the
      party invoking the "service up" can tell whether the test succeeded;
    - add "nocontig" option, because VM isn't particularly good at
      allocating contiguous memory;
    - add "silent" option, because it floods the console otherwise;
    - allow the device size to be smaller than the maximum transfer size;
    - install files to installed test directory.

    Change-Id: I45c818f817c11d90c5f94ae26a2fc49e36e6761e

commit e1494963470e8fb83db31bba836f00e2e8e74f57
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 18 13:38:36 2013 +0200

    Enable devname(3)

    There is no support for a device name database yet, so this call is
    expected to be fairly slow.

    Change-Id: I73aa5f267e2b6921b7d3bbdcc4beac463931132c

commit b993fcb1ace807ad5c0718dac891efa4bc9b5097
Author: David van Moolenbroek <david@minix3.org>
Date:   Sun Sep 15 13:09:00 2013 +0200

    libbdev: be less noisy about clean driver restarts

    Change-Id: Ie02a459c9b544d361ab00bac431ef99de53b0c5f

commit 896eb4d7df19ac1f3b4ad6475fd95c544054e9c2
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Sep 14 14:43:53 2013 +0200

    Straighten ioctl.h

    - include all ioctl subheaders, properly listing all letters;
    - change FBD's ioctl calls to use 'B' instead of 'F', in
      preparation of the VND driver.
    Change-Id: Ia718979568cc057f47cf505a89238d5b3b6695d4

commit b1339cb33d20d9e5e6409b84bec7defa7a8bac21
Author: David van Moolenbroek <david@minix3.org>
Date:   Sun Sep 15 18:55:42 2013 +0200

    VM: readd support for forgetting cached FS blocks

    Not all services involved in block I/O go through VM to access the
    blocks they need.  As a result, the blocks in VM may become stale,
    possibly causing corruption when the stale copy is restored by a
    service that does go through VM later on.  This patch restores support
    for forgetting cached blocks that belong to a particular device, and
    makes the relevant file systems use this functionality 1) when
    requested by VFS through REQ_FLUSH, and 2) upon unmount.

    Change-Id: I0758c5ed8fe4b5ba81d432595d2113175776aff8

commit a8ca37a5eb1456777125fed7de0fedb1a78e5078
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 11 14:50:18 2013 +0200

    filter: use libblockdriver

    Change-Id: Ifbca2482e996ddca58036d45f557165e636fb3fa

commit 4bfc7de552a592b7784f330939a91dc67e2e6ee9
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 10 20:25:01 2013 +0200

    Rewrite character driver protocol

    As a side effect, remove the clone style, as the normal device style
    supports device cloning now.

    Change-Id: Ie82d1ef0385514a04a8faa139129a617895780b5

commit 70382bd94fb84be40c7b4ed31c4ea0e8e36a1bc8
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 10 16:06:37 2013 +0200

    Remove support for reopening character devices

    Previously, VFS would reopen a character device after a driver crash
    if the associated file descriptor was opened with the O_REOPEN flag.
    This patch removes support for this feature. The code was complex,
    full of uncovered corner cases, and hard to test. Moreover, it did not
    actually hide the crash from user applications: they would get an
    error code to indicate that something went wrong, and have to decide
    based on the nature of the underlying device how to continue.

    - remove support for O_REOPEN, and make playwave(1) reopen its device;
    - remove support for the DEV_REOPEN protocol message;
    - remove all code in VFS related to reopening character devices;
    - no longer change VFS filp reference count and FD bitmap upon filp
      invalidation; instead, make get_filp* fail all calls on invalidated
      FDs except when obtained with the locktype VNODE_OPCL which is used
      by close_fd only;
    - remove the VFS fproc file descriptor bitmap entirely, returning to
      the situation that a FD is in use if its slot points to a filp; use
      FILP_CLOSED as single means of marking a filp as invalidated.

    Change-Id: I34f6bc69a036b3a8fc667c1f80435ff3af56558f

commit 772e09daec0d3fef3e6e7954cead545af5fa1c80
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 10 12:19:08 2013 +0200

    VFS: rework device code

    - block the calling thread on character device close;
    - fully separate block and character open/close routines;
    - reuse generic open/close code for the cloning case;
    - zero all messages to drivers before filling them;
    - use appropriate types for major/minor device numbers.

    Change-Id: Ia90e6fe5688f212f835c5ee1bfca831cb249cf51

commit 39e49a673d2710d291ae7d7936da74b7cf082f9a
Author: David van Moolenbroek <david@minix3.org>
Date:   Mon Sep 9 00:04:12 2013 +0200

    Make PFS backcalls regular VFS calls

    - prefix them with VFS_ as they are going to VFS;
    - give these calls normal call numbers;
    - give them their own set of message field aliases;
    - also make do_mapdriver a regular call.

    Change-Id: I2140439f288b06d699a1f65438bd8306509b259e

commit 317bafb80c03a081475a49b7a17adfefb1e235ac
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 11 12:48:10 2013 +0200

    TTY: use libchardriver; clean up

    - writing to a PTY master side blocks if there is not already a
      blocked reader on the slave side, and select now reflects this;
    - internally, TTY now uses a test based on "caller != NONE" rather
      than "grant != GRANT_INVALID" to identify whether a call is
      currently ongoing;
    - "offset" fields have been removed as they equal the corresponding
      "cum" fields;
    - improved variable typing and function naming here and there;
    - various other small fixes.

    Change-Id: I6b51452888942e864b4e034e8c8490576184a23e

commit c371bff479200bb97f7b27e89709f77c4b975e5e
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 11 01:13:59 2013 +0200

    VFS: select(2) fixes

    - check each file descriptor's open access mode (filp_mode);
    - treat an error returned by a character driver as a select error;
    - check all filps in each set before finishing select;
    - do not copy back file descriptor sets if an error occurred;
    - remove the hardcoded list of supported character major devices,
      since all drivers should now be capable of responding properly;
    - add tests to test40 and fix its error count aggregation.

    Change-Id: I57ef58d3afb82640fc50b59c859ee4b25f02db17

commit 70195f8d567829c409c58f1d5b8b92c9e775df72
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 11 01:07:28 2013 +0200

    Retire EBADIOCTL in favor of ENOTTY

    Change-Id: I6bd0e301d21ab7f2336e350e7e6e15d238c2c93d

commit 9e863893aef0e0c559dc7789b180d84bff73f6eb
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 4 15:42:36 2013 +0000

    libnetsock: use libchardriver

    Change-Id: Ia5b780cad0b0c636db9bd866c7223da0d38ef6ea

commit 55765275729397486365e2656fa1c5f0476eeb28
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 11 00:50:36 2013 +0200

    LWIP: move chardev message parsing into libnetsock

    Change-Id: Ie23fd003c9fa35811548f388c8e9b55e8d9de8d7

commit 883472c8d1ba8cc9990202af1105c13eb81216f7
Author: David van Moolenbroek <david@minix3.org>
Date:   Mon Sep 9 14:53:28 2013 +0000

    INET: use libchardriver

    Change-Id: Icf8a1a5769ce0aede1cc28da99b9daf7d328182c

commit daff5f2ac40d7fd0866d19af91526da66bab523d
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 3 02:00:20 2013 +0200

    PFS: use libchardriver; clean up

    - simplify and repair UDS request handling state machine;
    - simplify interface used between internal modules;
    - implement missing support for nonblocking I/O;
    - fix select implementation;
    - clean up global variables.

    Change-Id: Ia82c5c6f05cc3f0a498efc9a26de14b1cde6eace

commit e17e4ed7a0c6dcb975041fd144df17037c96281a
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 3 01:59:20 2013 +0200

    libaudiodriver: use libchardriver

    Change-Id: I299d58d110ad14b69076276ba46c4325875c34ca

commit 6be0c5d8bc6b7ce6cc4af506e12f0f3cc0bcf307
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 3 01:57:41 2013 +0200

    printer: use libchardriver

    Change-Id: Ifa3cabeada74c32df2613b8279c00f86e831c775

commit 0ccb2597747ec1e495623e149cbe2bb9301c91ec
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 3 01:49:38 2013 +0200

    libchardriver: full API rewrite

    The new API now covers the entire character driver protocol, while
    hiding all the message details. It should therefore be used by all
    new character drivers. All existing drivers that already made use of
    libchardriver have been changed to use the new API.

    As one of the most important API changes, support for scatter and
    gather transfers has been removed, as several key drivers already
    did not support this, and it could be supported at the safecopy
    level instead (for a future readv/writev).

    Additional changes include:

    - respond to block device open requests to avoid hanging VFS threads;
    - add support for sef_cancel.

    Change-Id: I1bab6c1cb66916c71b87aeb1db54a9bdf171fe6b

commit b96fbc55af87c31a76cc95c7962758f1cf1d9c18
Author: David van Moolenbroek <david@minix3.org>
Date:   Thu Aug 1 18:20:33 2013 +0200

    blocktest: add support for no alignment

    Some block drivers do not impose any alignment requirements, and this
    patch allows such block drivers to pass the test set. As a side effect,
    minimal support for min_write is added, but this part of blocktest is
    in need of further improvement.

commit 993c1060f876de2b7ef2169a79957d709ff1137e
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Apr 18 00:04:28 2012 +0200

    libutil: let opendisk(3) try /dev

    If a device node is given without path, and opening the node fails
    initially, prepend "/dev/" to the node name and try opening again.
    This is more in line with NetBSD behavior.

commit 0aba92d3d77f635308ca422e7c72b4410dce4816
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 10 23:57:32 2013 +0200

    Block drivers: make IOCTL request unsigned long

    The block driver protocol and libblockdriver's bdr_ioctl hook are
    changed, as well as the users of this hook. Other parts of the system
    are expected to change accordingly eventually, since the ioctl(2)
    prototype has been aligned with NetBSD's.

    Change-Id: Ide46245b22cfa89ed267a38088fb0ab7696eba92

commit d96dbd5c64cfcb609845aff224b914a672c9b8cb
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Jul 27 00:49:49 2013 +0200

    Block protocol: add user endpoint to IOCTL request

    I/O control requests now come with the endpoint of the user process
    that initiated the ioctl(2) call. It is stored in a new BDEV_USER
    field, which is an alias for BDEV_FLAGS. The contents of this field
    are to be used only in highly specific situations. It should be
    preserved (not replaced!) by services that forward IOCTL requests,
    and may be set to NONE for service-initiated IOCTL requests.

    Change-Id: I68a01b9ce43eca00e61b985a9cf87f55ba683de4

commit c86d0cdf09efc6d596a9d390b7ce71193ee716a1
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Jul 27 00:49:49 2013 +0200

    Block protocol: use own [RW]_BIT definitions

    The original R_BIT and W_BIT definitions have nothing to do with the
    way these bits are used. Their distinct usage is more apparent when
    they have different names.

    Change-Id: Ia984457f900078b2e3502ceed565fead4e5bb965

commit e7e243cbb6cefc29c28bc7412ebd58a968ba8ad0
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Sep 10 23:35:15 2013 +0200

    libblockdriver: expose BLOCKDRIVER_MAX_DEVICES

    This constant determines the range of valid device_id_t values that
    a block driver can return from the bdr_device hook: a value between
    0 and (BLOCKDRIVER_MAX_DEVICES - 1) inclusive.

    Change-Id: I80fac469e88ac13d4b869007e6f2c2f7569da433

commit 8cdecd40a59bcd267de5379125f00ed2548ce260
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Sep 11 13:33:00 2013 +0200

    libblockdriver: various updates

    - internal structure rearrangement;
    - respond to char device open requests to avoid hanging VFS threads;
    - make drivers use designated initializers;
    - use devminor_t for all minor device numbers;
    - change bdr_other hook to take ipc_status and return nothing;
    - fix default geometry computation;
    - add support for sef_cancel.

    Change-Id: Ia063a136a3ddb2b78de36180feda870605753d70

commit 4fcf5cb40702a0157ab7609053eb871515d834b5
Author: David van Moolenbroek <david@minix3.org>
Date:   Sun Sep 1 14:34:17 2013 +0200

    Block drivers: reply ENOTTY to unknown IOCTLs

    Change-Id: Ie2e82d2491d546f4dd73b009100646e249a147b5

commit 6e1eaef728d42dcfb606c0c375ad6cca11eb7495
Author: David van Moolenbroek <david@minix3.org>
Date:   Sun Jul 28 14:03:07 2013 +0200

    Move SUB_PER_DRIVE definition into minix/drvlib.h

commit 6720bd98fd9975fb40b3f195680c3414093479f0
Author: David van Moolenbroek <david@minix3.org>
Date:   Mon Sep 2 17:34:44 2013 +0200

    VFS: update filp_pos on chardev I/O (workaround)

    Previously, reading from or writing to a character device would not
    update the file position on the corresponding filp object.  Performing
    this update correctly is not trivial: during and after the I/O
    operation, the filp object must not be locked.  Ideally, read/write
    requests on a filp that is already involved in a read/write operation,
    should be queued.  For now, we optimistically update the file position
    at the start of the I/O; this works under the assumptions listed in
    the corresponding comment.

    Change-Id: I172a61781850423709924390ae3df1f2d1f94707

commit 3fac54b08b9034f6f3506632b07e31c90fd630cf
Author: David van Moolenbroek <david@minix3.org>
Date:   Mon Sep 2 13:45:02 2013 +0200

    I2C: change BUSC_I2C_xxx to use own protocol

    Previously it would use bits of the character driver protocol, which
    will change heavily.  In the new situation, the BUSC_I2C_xxx requests
    use a protocol more in line with the PCI protocol, with the reply code
    in m_type.

    Change-Id: I51597b3f191078c8178ce17372de123031f7a4c4

commit 96e365d6e440c7a9885da17b9552c21415770d65
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Aug 31 16:13:37 2013 +0200

    tests: add test77 for opening/closing PTYs

    Change-Id: I30e3418f75137aa08037fa6581ff3d4cce32a114

commit 46a0bba56f3317acee4b1dbebc3a2a829926198f
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Aug 31 16:08:25 2013 +0200

    TTY: fix for PTY open/close logic

    Opening and closing the master side of a pseudo terminal without
    opening the slave side would result in the pseudo terminal becoming
    permanently unavailable.  In addition, reopening the slave side
    would be possible but not allow for I/O.  Finally, attempting to
    open an in-use master would wipe its I/O state.  These issues have
    been resolved.

    Change-Id: I9235e3d9aba321803f9280b86b6b5e3646ad5ef3

commit 5fe976c95b584d8d37362c28efd074993b4273fc
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 18:48:56 2013 +0200

    tests: remove select subdirectory

    This test set has been obsoleted by test40.

    Change-Id: I55439152824906778ad07d409dfb327ac10bea70

commit fbcc5fd0878cf1a74f4e75c62bf4f9ee32fc601e
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 18:43:23 2013 +0200

    tests: add test76 for interrupting VFS operations

    Change-Id: Ic436cac61de8c42e0c7ee2d442c647528654cde9

commit a33a803a70ca467e4bc1ee684aa468815344c785
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Aug 28 13:08:16 2013 +0200

    VFS: fix interruption of blocking pipe operations

    POSIX states that when interrupted, partially successful pipe
    operations should return the partial result rather than EINTR. VFS
    previously wouldn't look at the partial result, and not clear it
    either, which would result in a panic upon the next pipe operation.

    Change-Id: Ia1eb72b4b77394051444e63a1390d49bb315eb04

commit 20d872e753b796c315f5cd7f66ac92f592d000db
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 00:57:16 2013 +0200

    libmthread: do not dump stack for free threads

    Change-Id: Ic438a252f5bddaf1513f554c71173e6fffb0c674

commit 7c8902e360a0b2ca4d411196a64227117defdc6b
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 14:00:50 2013 +0200

    VFS: worker thread model overhaul

    The main purpose of this patch is to fix handling of unpause calls
    from PM while another call is ongoing. The solution to this problem
    sparked a full revision of the threading model, consisting of a large
    number of related changes:

    - all active worker threads are now always associated with a process,
      and every process has at most one active thread working for it;
    - the process lock is always held by a process's worker thread;
    - a process can now have both normal work and postponed PM work
      associated to it;
    - timer expiry and non-postponed PM work is done from the main thread;
    - filp garbage collection is done from a thread associated with VFS;
    - reboot calls from PM are now done from a thread associated with PM;
    - the DS events handler is protected from starting multiple threads;
    - support for a system worker thread has been removed;
    - the deadlock recovery thread has been replaced by a parameter to the
      worker_start() function; the number of worker threads has
      consequently been increased by one;
    - saving and restoring of global but per-thread variables is now
      centralized in worker_suspend() and worker_resume(); err_code is now
      saved and restored in all cases;
    - the concept of jobs has been removed, and job_m_in now points to a
      message stored in the worker thread structure instead;
    - the PM lock has been removed;
    - the separate exec lock has been replaced by a lock on the VM
      process, which was already being locked for exec calls anyway;
    - PM_UNPAUSE is now processed as a postponed PM request, from a thread
      associated with the target process;
    - the FP_DROP_WORK flag has been removed, since it is no longer more
      than just an optimization and only applied to processes operating on
      a pipe when getting killed;
    - assignment to "fp" now takes place only when obtaining new work in
      the main thread or a worker thread, when resuming execution of a
      thread, and in the special case of exiting processes during reboot;
    - there are no longer special cases where the yield() call is used to
      force a thread to run.

    Change-Id: I7a97b9b95c2450454a9b5318dfa0e6150d4e6858

commit 07c7bf859263dd44effd6fbefc065cd1ddafafe8
Author: David van Moolenbroek <david@minix3.org>
Date:   Wed Aug 28 15:58:30 2013 +0200

    Retire ptrace(T_DUMPCORE), dumpcore(1), gcore(1)

    The T_DUMPCORE implementation was not only broken - it would currently
    produce a coredump of the tracer process rather than the traced
    process - but also deeply flawed, and fixing it would require serious
    alteration of PM's internal state machine. It should be possible to
    implement the same functionality in userland, and that is now the
    suggested way forward. For now, also remove the (identical) utilities
    using T_DUMPCORE: dumpcore(1) and gcore(1).

    Change-Id: I1d51be19c739362b8a5833de949b76382a1edbcc

commit 63d7dd8fd0da01b217e57b137724a0d79c715432
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 13:42:51 2013 +0200

    VFS: process char driver replies from main thread

    Previously, processing of some replies coming from character drivers
    could block on locks, and therefore, such processing was done from
    threads that were associated to the character driver process. The
    hidden consequence of this was that if all threads were in use, VFS
    could drop replies coming from the driver. This patch returns VFS to
    a situation where the replies from character drivers are processed
    instantly from the main thread, by removing the situations that may
    cause VFS to block while handling those replies.

    - change the locking model for select, so that it will never block
      on any processing that happens after the select call has been set
      up, in particular processing of character driver select replies;
    - clearly mark all select routines that may never block;
    - protect against race conditions in do_select as result of the
      locking that still does happen there (as is required for pipes);
    - also handle select timers from the main thread;
    - move processing of character driver replies into device.c.

    Change-Id: I4dc8e69f265cbd178de0fbf321d35f58f067cc57

commit 3afa0ff4b95928cd25a82e13740018a4c702120d
Author: David van Moolenbroek <david@minix3.org>
Date:   Sun Aug 25 00:26:38 2013 +0200

    VFS: properly cancel select queries on unpause

    Change-Id: I16e71db3f5c1bcc7ba6045bc9f02b13d71dc31eb

commit 36453271e54fbdf0917219163107fb16f03b89ec
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 13:33:56 2013 +0200

    VFS: remove support for sync char driver protocol

    Change-Id: I57cc870a053b813b3a3fc45da46606ea84fe4cb1

commit acdbe00dc78feb4d453943d1e98d335e8cff0240
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 13:00:44 2013 +0200

    VFS: remove FP_BLOCKED_ON_DOPEN

    These days, DEV_OPEN calls to character drivers block the calling
    thread until completion or failure, and thus never return SUSPEND to
    the caller. The same already applied to BDEV_OPEN calls to block
    drivers. It has thus become impossible for a process to enter a state
    of being blocked on a device open call.

    There is currently no support for restarting device open calls to
    restarted character drivers. This support was present in the _DOPEN
    logic, but was already no longer triggering. In the future, this case
    should be handled by the thread performing the open request.

    Change-Id: I6cc1e7b4c9ed116c6ce160b315e6e060124dce00

commit 5b4173a09a354fba3c0e9caf2746228e3980159a
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 12:18:27 2013 +0200

    PFS: remember request information for IOCTLs

    Not doing so caused PFS to commit protocol violations by relying on
    stale information when sending replies. This stale information always
    happened to be correct, which is why the problem went unnoticed.
    Change-Id: Ia42ca670718d6e731193cd2c34a3ff455f8a94d3

commit c58bdb3a337dfff55998d9f9722e63886c803fac
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 11:14:03 2013 +0200

    Retire the synchronous character driver protocol

    - change all sync char drivers into async drivers;
    - retire support for the sync protocol in libchardev;
    - remove async dev style, as this is now the default;
    - remove dev_status from VFS;
    - clean up now-unused protocol messages.

    Change-Id: I6aacff712292f6b29f2ccd51bc1e7d7003723e87

commit a349640fa6bb49b30a1b704004180113fa2c0ee7
Author: David van Moolenbroek <david@minix3.org>
Date:   Fri Aug 30 10:48:34 2013 +0200

    Sync char protocol: add nonblocking transfer flag

    The async char protocol already has this, so this patch closes the
    gap between the two protocols a bit. Support for this flag has been
    added to all sync char drivers that support CANCEL at all.

    The LOG driver was already using the asynchronous protocol, but it
    did not support the nonblocking transfer flag. This has been fixed
    as well.

    Change-Id: Ia55432c9f102765b59ad3feb45a8bd47a782c93f

commit c1a0e4f59c24e899e5dd9858856a3363f418b93c
Author: David van Moolenbroek <david@minix3.org>
Date:   Sat Aug 24 12:29:39 2013 +0200

    VFS: set w_drv_sendrec only when needed

    As with w_task, this ensures that the field remains cleared if it is
    not used. Without this, worker_stop could mistakenly identify a thread
    as talking to a device driver rather than a (crashed) file server.

    Change-Id: I7d3ebed3efc3cd4f5c891f61c67a6463109b6376

commit 1d481920bb981c095fd8b5a69926bc3726a1e06f
Author: Thomas Veerman <thomas@minix3.org>
Date:   Sat Aug 24 12:23:41 2013 +0200

    VFS: set w_task only when needed

    It was always set, but not always cleared, when talking to asynchronous
    drivers. This could cause erratic behavior upon a driver crash.

    Normally, a worker thread's w_task field is set when it's about to
    communicate with a driver or FS. Then upon receiving a reply we can
    do sanity checks (that the thread we want to wake up was actually
    waiting for a reply). Also, when a driver/FS crashes, we can identify
    which worker threads were talking to the crashed endpoint and handle
    the error gracefully.

    Asynchronous drivers are a bit special, though. In most cases, the
    sender of the request is not interested in the reply (the sender was
    suspended and only wants to know whether the request was successfully
    caried out or not). However, the open request is special, as the reply
    carries information needed by the sender. This is the only request
    where a worker thread actually yields and waits for the result. This is
    also the only case where we're interested in setting w_task for
    asynchronous drivers.

    Change-Id: Ia1ce2747937df376122b5e13b6a069de27fcc379

commit 5f765ae525474824f5c5ead2c960bca167beb160
Author: David van Moolenbroek <david@minix3.org>
Date:   Mon Aug 19 20:34:15 2013 +0200

    Import NetBSD df(1)

    Change-Id: Ia60f8b23b961e4132efece1e7e38f0d63597c13b

commit d5859394ded95c5f0609072d605a2c47aaca4f22
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Aug 20 01:33:43 2013 +0200

    Enable getmntinfo(3)

    Change-Id: Id9d11a67cdcad331a52030127f7c7ead3c847919

commit d120973fa9b8a014c5606bd257dd0f305c6dbe63
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Aug 20 00:49:07 2013 +0200

    test55: add tests for getvfsstat(2)

    Change-Id: Iad4567068a82bb0e780891a478ba5a06b63f1d48

commit 885cd5462015d5a5137b8e5e2a946d4bebdef19a
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Aug 20 01:28:23 2013 +0200

    Implement support for [f]statvfs1(2)

    The [f]statvfs(3) calls now use [f]statvfs1(2).

    Change-Id: I56c92fe1c70670f476631c4a50e47351562712c6

commit 5de979e5ddd2f8e1be84748ea7071a3f52c458ae
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Aug 20 01:39:47 2013 +0200

    Implement support for getvfsstat(2)

    Change-Id: I4680c071b94fa855edfb6220b03cff6b20f3c1c9

commit 17edae161bda15f5e2a0f7228a8f08c2ec0a7d84
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Aug 20 01:37:18 2013 +0200

    Redo mount(2)/umount(2) ABI

    - pass in file system type through mount(2), and return this type in
      statvfs structures as generated by [f]statvfs(2);
    - align mount flags field with NetBSD's, splitting out service flags
      which are not to be passed to VFS;
    - remove limitation of mount ABI to 16-byte labels, so that labels
      can be made larger in the future;
    - introduce new m11 message union type for mount(2) as side effect.

    Change-Id: Ia9ed4566d88af5239749c57274312be26bb61448

commit ec063ff840f9717f86b2c36802c3cd1d07bdf360
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Aug 20 01:35:35 2013 +0200

    Align "struct statvfs" with NetBSD

    This is a requirement for implementing calls such as getmntinfo(3).
    VFS is now responsible for filling in some of the structure's fields.

    Change-Id: I457b463c70ea2f326b9c50de954a91cfefba0065

commit ca09db743f192508b7078e9adef85f1b7cf11073
Author: David van Moolenbroek <david@minix3.org>
Date:   Tue Aug 20 00:55:49 2013 +0200

    VFS/FS: remove fstatfs(2) and REQ_FSTATFS

    The fstatfs(3) call now uses fstatvfs(2).

    Change-Id: Ic6bbb040e1a8f01aaffe1a6a809dd4c7a2eae2f6

Change-Id: I14141beaf9804e10d9cf3de706bdc9b9d9cc947b
2013-10-10 19:03:20 +00:00

2738 lines
83 KiB
C

/* Advanced Host Controller Interface (AHCI) driver, by D.C. van Moolenbroek
* - Multithreading support by Arne Welzel
* - Native Command Queuing support by Raja Appuswamy
*/
/*
* This driver is based on the following specifications:
* - Serial ATA Advanced Host Controller Interface (AHCI) 1.3
* - Serial ATA Revision 2.6
* - AT Attachment with Packet Interface 7 (ATA/ATAPI-7)
* - ATAPI Removable Rewritable Media Devices 1.3 (SFF-8070)
*
* The driver supports device hot-plug, active device status tracking,
* nonremovable ATA and removable ATAPI devices, custom logical sector sizes,
* sector-unaligned reads, native command queuing and parallel requests to
* different devices.
*
* It does not implement transparent failure recovery, power management, or
* port multiplier support.
*/
/*
* An AHCI controller exposes a number of ports (up to 32), each of which may
* or may not have one device attached (port multipliers are not supported).
* Each port is maintained independently.
*
* The following figure depicts the possible transitions between port states.
* The NO_PORT state is not included; no transitions can be made from or to it.
*
* +----------+ +----------+
* | SPIN_UP | ------+ +-----> | BAD_DEV | ------------------+
* +----------+ | | +----------+ |
* | | | ^ |
* v v | | |
* +----------+ +----------+ +----------+ +----------+ |
* | NO_DEV | --> | WAIT_DEV | --> | WAIT_ID | --> | GOOD_DEV | |
* +----------+ +----------+ +----------+ +----------+ |
* ^ | | | |
* +----------------+----------------+----------------+--------+
*
* At driver startup, all physically present ports are put in SPIN_UP state.
* This state differs from NO_DEV in that BDEV_OPEN calls will be deferred
* until either the spin-up timer expires, or a device has been identified on
* that port. This prevents early BDEV_OPEN calls from failing erroneously at
* startup time if the device has not yet been able to announce its presence.
*
* If a device is detected, either at startup time or after hot-plug, its
* signature is checked and it is identified, after which it may be determined
* to be a usable ("good") device, which means that the device is considered to
* be in a working state. If these steps fail, the device is marked as unusable
* ("bad"). At any point in time, the device may be disconnected; the port is
* then put back into NO_DEV state.
*
* A device in working state (GOOD_DEV) may or may not have a medium. All ATA
* devices are assumed to be fixed; all ATAPI devices are assumed to have
* removable media. To prevent erroneous access to switched devices and media,
* the driver makes devices inaccessible until they are fully closed (the open
* count is zero) when a device (hot-plug) or medium change is detected.
* For hot-plug changes, access is prevented by setting the BARRIER flag until
* the device is fully closed and then reopened. For medium changes, access is
* prevented by not acknowledging the medium change until the device is fully
* closed and reopened. Removable media are not locked in the drive while
* opened, because the driver author is uncomfortable with that concept.
*
* Ports may leave the group of states where a device is connected (that is,
* WAIT_ID, GOOD_DEV, and BAD_DEV) in two ways: either due to a hot-unplug
* event, or due to a hard reset after a serious failure. For simplicity, we
* we perform a hard reset after a hot-unplug event as well, so that the link
* to the device is broken. Thus, in both cases, a transition to NO_DEV is
* made, after which the link to the device may or may not be reestablished.
* In both cases, ongoing requests are cancelled and the BARRIER flag is set.
*
* The following table lists for each state, whether the port is started
* (PxCMD.ST is set), whether a timer is running, what the PxIE mask is to be
* set to, and what BDEV_OPEN calls on this port should return.
*
* State Started Timer PxIE BDEV_OPEN
* --------- --------- --------- --------- ---------
* NO_PORT no no (none) ENXIO
* SPIN_UP no yes PCE (wait)
* NO_DEV no no PCE ENXIO
* WAIT_DEV no yes PCE (wait)
* BAD_DEV no no PRCE ENXIO
* WAIT_ID yes yes PRCE+ (wait)
* GOOD_DEV yes per-command PRCE+ OK
*
* In order to continue deferred BDEV_OPEN calls, the BUSY flag must be unset
* when changing from SPIN_UP to any state but WAIT_DEV, and when changing from
* WAIT_DEV to any state but WAIT_ID, and when changing from WAIT_ID to any
* other state.
*/
/*
* The maximum byte size of a single transfer (MAX_TRANSFER) is currently set
* to 4MB. This limit has been chosen for a number of reasons:
* - The size that can be specified in a Physical Region Descriptor (PRD) is
* limited to 4MB for AHCI. Limiting the total transfer size to at most this
* size implies that no I/O vector element needs to be split up across PRDs.
* This means that the maximum number of needed PRDs can be predetermined.
* - The limit is below what can be transferred in a single ATA request, namely
* 64k sectors (i.e., at least 32MB). This means that transfer requests need
* never be split up into smaller chunks, reducing implementation complexity.
* - A single, static timeout can be used for transfers. Very large transfers
* can legitimately take up to several minutes -- well beyond the appropriate
* timeout range for small transfers. The limit obviates the need for a
* timeout scheme that takes into account the transfer size.
* - Similarly, the transfer limit reduces the opportunity for buggy/malicious
* clients to keep the driver busy for a long time with a single request.
* - The limit is high enough for all practical purposes. The transfer setup
* overhead is already relatively negligible at this size, and even larger
* requests will not help maximize throughput. As NR_IOREQS is currently set
* to 64, the limit still allows file systems to perform I/O requests with
* vectors completely filled with 64KB-blocks.
*/
#include <minix/drivers.h>
#include <minix/blockdriver_mt.h>
#include <minix/drvlib.h>
#include <machine/pci.h>
#include <sys/ioc_disk.h>
#include <sys/mman.h>
#include <assert.h>
#include "ahci.h"
/* Host Bus Adapter (HBA) state. */
static struct {
volatile u32_t *base; /* base address of memory-mapped registers */
size_t size; /* size of memory-mapped register area */
int nr_ports; /* addressable number of ports (1..NR_PORTS) */
int nr_cmds; /* maximum number of commands per port */
int has_ncq; /* NCQ support flag */
int has_clo; /* CLO support flag */
int irq; /* IRQ number */
int hook_id; /* IRQ hook ID */
} hba_state;
#define hba_read(r) (hba_state.base[r])
#define hba_write(r, v) (hba_state.base[r] = (v))
/* Port state. */
static struct port_state {
int state; /* port state */
unsigned int flags; /* port flags */
volatile u32_t *reg; /* memory-mapped port registers */
u8_t *mem_base; /* primary memory buffer virtual address */
phys_bytes mem_phys; /* primary memory buffer physical address */
vir_bytes mem_size; /* primary memory buffer size */
/* the FIS, CL, CT[0] and TMP buffers are all in the primary buffer */
u32_t *fis_base; /* FIS receive buffer virtual address */
phys_bytes fis_phys; /* FIS receive buffer physical address */
u32_t *cl_base; /* command list buffer virtual address */
phys_bytes cl_phys; /* command list buffer physical address */
u8_t *ct_base[NR_CMDS]; /* command table virtual address */
phys_bytes ct_phys[NR_CMDS]; /* command table physical address */
u8_t *tmp_base; /* temporary storage buffer virtual address */
phys_bytes tmp_phys; /* temporary storage buffer physical address */
u8_t *pad_base; /* sector padding buffer virtual address */
phys_bytes pad_phys; /* sector padding buffer physical address */
vir_bytes pad_size; /* sector padding buffer size */
u64_t lba_count; /* number of valid Logical Block Addresses */
u32_t sector_size; /* medium sector size in bytes */
int open_count; /* number of times this port is opened */
int device; /* associated device number, or NO_DEVICE */
struct device part[DEV_PER_DRIVE]; /* partition bases and sizes */
struct device subpart[SUB_PER_DRIVE]; /* same for subpartitions */
minix_timer_t timer; /* port-specific timeout timer */
int left; /* number of tries left before giving up */
/* (only used for signature probing) */
int queue_depth; /* NCQ queue depth */
u32_t pend_mask; /* commands not yet complete */
struct {
thread_id_t tid;/* ID of the worker thread */
minix_timer_t timer; /* timer associated with each request */
int result; /* success/failure result of the commands */
} cmd_info[NR_CMDS];
} port_state[NR_PORTS];
#define port_read(ps, r) ((ps)->reg[r])
#define port_write(ps, r, v) ((ps)->reg[r] = (v))
static int ahci_instance; /* driver instance number */
static int ahci_verbose; /* verbosity level (0..4) */
/* Timeout-related values. */
static clock_t ahci_spinup_timeout;
static clock_t ahci_device_timeout;
static clock_t ahci_device_delay;
static unsigned int ahci_device_checks;
static clock_t ahci_command_timeout;
static clock_t ahci_transfer_timeout;
static clock_t ahci_flush_timeout;
/* Timeout environment variable names and default values. */
static struct {
char *name; /* environment variable name */
u32_t default_ms; /* default in milliseconds */
clock_t *ptr; /* clock ticks value pointer */
} ahci_timevar[] = {
{ "ahci_init_timeout", SPINUP_TIMEOUT, &ahci_spinup_timeout },
{ "ahci_device_timeout", DEVICE_TIMEOUT, &ahci_device_timeout },
{ "ahci_cmd_timeout", COMMAND_TIMEOUT, &ahci_command_timeout },
{ "ahci_io_timeout", TRANSFER_TIMEOUT, &ahci_transfer_timeout },
{ "ahci_flush_timeout", FLUSH_TIMEOUT, &ahci_flush_timeout }
};
static int ahci_map[MAX_DRIVES]; /* device-to-port mapping */
static int ahci_exiting = FALSE; /* exit after last close? */
#define BUILD_ARG(port, tag) (((port) << 8) | (tag))
#define GET_PORT(arg) ((arg) >> 8)
#define GET_TAG(arg) ((arg) & 0xFF)
#define dprintf(v,s) do { \
if (ahci_verbose >= (v)) \
printf s; \
} while (0)
/* Convert milliseconds to clock ticks. Round up. */
#define millis_to_hz(ms) (((ms) * sys_hz() + 999) / 1000)
static void port_set_cmd(struct port_state *ps, int cmd, cmd_fis_t *fis,
u8_t packet[ATAPI_PACKET_SIZE], prd_t *prdt, int nr_prds, int write);
static void port_issue(struct port_state *ps, int cmd, clock_t timeout);
static int port_exec(struct port_state *ps, int cmd, clock_t timeout);
static void port_timeout(minix_timer_t *tp);
static void port_disconnect(struct port_state *ps);
static char *ahci_portname(struct port_state *ps);
static int ahci_open(devminor_t minor, int access);
static int ahci_close(devminor_t minor);
static ssize_t ahci_transfer(devminor_t minor, int do_write, u64_t position,
endpoint_t endpt, iovec_t *iovec, unsigned int count, int flags);
static struct device *ahci_part(devminor_t minor);
static void ahci_alarm(clock_t stamp);
static int ahci_ioctl(devminor_t minor, unsigned long request,
endpoint_t endpt, cp_grant_id_t grant, endpoint_t user_endpt);
static void ahci_intr(unsigned int mask);
static int ahci_device(devminor_t minor, device_id_t *id);
static struct port_state *ahci_get_port(devminor_t minor);
/* AHCI driver table. */
static struct blockdriver ahci_dtab = {
.bdr_type = BLOCKDRIVER_TYPE_DISK,
.bdr_open = ahci_open,
.bdr_close = ahci_close,
.bdr_transfer = ahci_transfer,
.bdr_ioctl = ahci_ioctl,
.bdr_part = ahci_part,
.bdr_intr = ahci_intr,
.bdr_alarm = ahci_alarm,
.bdr_device = ahci_device
};
/*===========================================================================*
* atapi_exec *
*===========================================================================*/
static int atapi_exec(struct port_state *ps, int cmd,
u8_t packet[ATAPI_PACKET_SIZE], size_t size, int write)
{
/* Execute an ATAPI command. Return OK or error.
*/
cmd_fis_t fis;
prd_t prd[1];
int nr_prds = 0;
assert(size <= AHCI_TMP_SIZE);
/* Fill in the command table with a FIS, a packet, and if a data
* transfer is requested, also a PRD.
*/
memset(&fis, 0, sizeof(fis));
fis.cf_cmd = ATA_CMD_PACKET;
if (size > 0) {
fis.cf_feat = ATA_FEAT_PACKET_DMA;
if (!write && (ps->flags & FLAG_USE_DMADIR))
fis.cf_feat |= ATA_FEAT_PACKET_DMADIR;
prd[0].vp_addr = ps->tmp_phys;
prd[0].vp_size = size;
nr_prds++;
}
/* Start the command, and wait for it to complete or fail. */
port_set_cmd(ps, cmd, &fis, packet, prd, nr_prds, write);
return port_exec(ps, cmd, ahci_command_timeout);
}
/*===========================================================================*
* atapi_test_unit *
*===========================================================================*/
static int atapi_test_unit(struct port_state *ps, int cmd)
{
/* Test whether the ATAPI device and medium are ready.
*/
u8_t packet[ATAPI_PACKET_SIZE];
memset(packet, 0, sizeof(packet));
packet[0] = ATAPI_CMD_TEST_UNIT;
return atapi_exec(ps, cmd, packet, 0, FALSE);
}
/*===========================================================================*
* atapi_request_sense *
*===========================================================================*/
static int atapi_request_sense(struct port_state *ps, int cmd, int *sense)
{
/* Request error (sense) information from an ATAPI device, and return
* the sense key. The additional sense codes are not used at this time.
*/
u8_t packet[ATAPI_PACKET_SIZE];
int r;
memset(packet, 0, sizeof(packet));
packet[0] = ATAPI_CMD_REQUEST_SENSE;
packet[4] = ATAPI_REQUEST_SENSE_LEN;
r = atapi_exec(ps, cmd, packet, ATAPI_REQUEST_SENSE_LEN, FALSE);
if (r != OK)
return r;
dprintf(V_REQ, ("%s: ATAPI SENSE: sense %x ASC %x ASCQ %x\n",
ahci_portname(ps), ps->tmp_base[2] & 0xF, ps->tmp_base[12],
ps->tmp_base[13]));
*sense = ps->tmp_base[2] & 0xF;
return OK;
}
/*===========================================================================*
* atapi_load_eject *
*===========================================================================*/
static int atapi_load_eject(struct port_state *ps, int cmd, int load)
{
/* Load or eject a medium in an ATAPI device.
*/
u8_t packet[ATAPI_PACKET_SIZE];
memset(packet, 0, sizeof(packet));
packet[0] = ATAPI_CMD_START_STOP;
packet[4] = load ? ATAPI_START_STOP_LOAD : ATAPI_START_STOP_EJECT;
return atapi_exec(ps, cmd, packet, 0, FALSE);
}
/*===========================================================================*
* atapi_read_capacity *
*===========================================================================*/
static int atapi_read_capacity(struct port_state *ps, int cmd)
{
/* Retrieve the LBA count and sector size of an ATAPI medium.
*/
u8_t packet[ATAPI_PACKET_SIZE], *buf;
int r;
memset(packet, 0, sizeof(packet));
packet[0] = ATAPI_CMD_READ_CAPACITY;
r = atapi_exec(ps, cmd, packet, ATAPI_READ_CAPACITY_LEN, FALSE);
if (r != OK)
return r;
/* Store the number of LBA blocks and sector size. */
buf = ps->tmp_base;
ps->lba_count = (u64_t) ((buf[0] << 24) | (buf[1] << 16) |
(buf[2] << 8) | buf[3]) + 1;
ps->sector_size =
(buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
if (ps->sector_size == 0 || (ps->sector_size & 1)) {
dprintf(V_ERR, ("%s: invalid medium sector size %u\n",
ahci_portname(ps), ps->sector_size));
return EINVAL;
}
dprintf(V_INFO,
("%s: medium detected (%u byte sectors, %llu MB size)\n",
ahci_portname(ps), ps->sector_size,
ps->lba_count * ps->sector_size / (1024*1024)));
return OK;
}
/*===========================================================================*
* atapi_check_medium *
*===========================================================================*/
static int atapi_check_medium(struct port_state *ps, int cmd)
{
/* Check whether a medium is present in a removable-media ATAPI device.
* If a new medium is detected, get its total and sector size. Return
* OK only if a usable medium is present, and an error otherwise.
*/
int sense;
/* Perform a readiness check. */
if (atapi_test_unit(ps, cmd) != OK) {
ps->flags &= ~FLAG_HAS_MEDIUM;
/* If the check failed due to a unit attention condition, retry
* reading the medium capacity. Otherwise, assume that there is
* no medium available.
*/
if (atapi_request_sense(ps, cmd, &sense) != OK ||
sense != ATAPI_SENSE_UNIT_ATT)
return ENXIO;
}
/* If a medium is newly detected, try reading its capacity now. */
if (!(ps->flags & FLAG_HAS_MEDIUM)) {
if (atapi_read_capacity(ps, cmd) != OK)
return EIO;
ps->flags |= FLAG_HAS_MEDIUM;
}
return OK;
}
/*===========================================================================*
* atapi_id_check *
*===========================================================================*/
static int atapi_id_check(struct port_state *ps, u16_t *buf)
{
/* Determine whether we support this ATAPI device based on the
* identification data it returned, and store some of its properties.
*/
/* The device must be an ATAPI device; it must have removable media;
* it must support DMA without DMADIR, or DMADIR for DMA.
*/
if ((buf[ATA_ID_GCAP] & (ATA_ID_GCAP_ATAPI_MASK |
ATA_ID_GCAP_REMOVABLE | ATA_ID_GCAP_INCOMPLETE)) !=
(ATA_ID_GCAP_ATAPI | ATA_ID_GCAP_REMOVABLE) ||
((buf[ATA_ID_CAP] & ATA_ID_CAP_DMA) != ATA_ID_CAP_DMA &&
(buf[ATA_ID_DMADIR] & (ATA_ID_DMADIR_DMADIR |
ATA_ID_DMADIR_DMA)) != (ATA_ID_DMADIR_DMADIR |
ATA_ID_DMADIR_DMA))) {
dprintf(V_ERR, ("%s: unsupported ATAPI device\n",
ahci_portname(ps)));
dprintf(V_DEV, ("%s: GCAP %04x CAP %04x DMADIR %04x\n",
ahci_portname(ps), buf[ATA_ID_GCAP], buf[ATA_ID_CAP],
buf[ATA_ID_DMADIR]));
return FALSE;
}
/* Remember whether to use the DMADIR flag when appropriate. */
if (buf[ATA_ID_DMADIR] & ATA_ID_DMADIR_DMADIR)
ps->flags |= FLAG_USE_DMADIR;
/* ATAPI CD-ROM devices are considered read-only. */
if (((buf[ATA_ID_GCAP] & ATA_ID_GCAP_TYPE_MASK) >>
ATA_ID_GCAP_TYPE_SHIFT) == ATAPI_TYPE_CDROM)
ps->flags |= FLAG_READONLY;
if ((buf[ATA_ID_SUP1] & ATA_ID_SUP1_VALID_MASK) == ATA_ID_SUP1_VALID &&
!(ps->flags & FLAG_READONLY)) {
/* Save write cache related capabilities of the device. It is
* possible, although unlikely, that a device has support for
* either of these but not both.
*/
if (buf[ATA_ID_SUP0] & ATA_ID_SUP0_WCACHE)
ps->flags |= FLAG_HAS_WCACHE;
if (buf[ATA_ID_SUP1] & ATA_ID_SUP1_FLUSH)
ps->flags |= FLAG_HAS_FLUSH;
}
return TRUE;
}
/*===========================================================================*
* atapi_transfer *
*===========================================================================*/
static int atapi_transfer(struct port_state *ps, int cmd, u64_t start_lba,
unsigned int count, int write, prd_t *prdt, int nr_prds)
{
/* Perform data transfer from or to an ATAPI device.
*/
cmd_fis_t fis;
u8_t packet[ATAPI_PACKET_SIZE];
/* Fill in a Register Host to Device FIS. */
memset(&fis, 0, sizeof(fis));
fis.cf_cmd = ATA_CMD_PACKET;
fis.cf_feat = ATA_FEAT_PACKET_DMA;
if (!write && (ps->flags & FLAG_USE_DMADIR))
fis.cf_feat |= ATA_FEAT_PACKET_DMADIR;
/* Fill in a packet. */
memset(packet, 0, sizeof(packet));
packet[0] = write ? ATAPI_CMD_WRITE : ATAPI_CMD_READ;
packet[2] = (start_lba >> 24) & 0xFF;
packet[3] = (start_lba >> 16) & 0xFF;
packet[4] = (start_lba >> 8) & 0xFF;
packet[5] = start_lba & 0xFF;
packet[6] = (count >> 24) & 0xFF;
packet[7] = (count >> 16) & 0xFF;
packet[8] = (count >> 8) & 0xFF;
packet[9] = count & 0xFF;
/* Start the command, and wait for it to complete or fail. */
port_set_cmd(ps, cmd, &fis, packet, prdt, nr_prds, write);
return port_exec(ps, cmd, ahci_transfer_timeout);
}
/*===========================================================================*
* ata_id_check *
*===========================================================================*/
static int ata_id_check(struct port_state *ps, u16_t *buf)
{
/* Determine whether we support this ATA device based on the
* identification data it returned, and store some of its properties.
*/
/* This must be an ATA device; it must not have removable media;
* it must support LBA and DMA; it must support the FLUSH CACHE
* command; it must support 48-bit addressing.
*/
if ((buf[ATA_ID_GCAP] & (ATA_ID_GCAP_ATA_MASK | ATA_ID_GCAP_REMOVABLE |
ATA_ID_GCAP_INCOMPLETE)) != ATA_ID_GCAP_ATA ||
(buf[ATA_ID_CAP] & (ATA_ID_CAP_LBA | ATA_ID_CAP_DMA)) !=
(ATA_ID_CAP_LBA | ATA_ID_CAP_DMA) ||
(buf[ATA_ID_SUP1] & (ATA_ID_SUP1_VALID_MASK |
ATA_ID_SUP1_FLUSH | ATA_ID_SUP1_LBA48)) !=
(ATA_ID_SUP1_VALID | ATA_ID_SUP1_FLUSH | ATA_ID_SUP1_LBA48)) {
dprintf(V_ERR, ("%s: unsupported ATA device\n",
ahci_portname(ps)));
dprintf(V_DEV, ("%s: GCAP %04x CAP %04x SUP1 %04x\n",
ahci_portname(ps), buf[ATA_ID_GCAP], buf[ATA_ID_CAP],
buf[ATA_ID_SUP1]));
return FALSE;
}
/* Get number of LBA blocks, and sector size. */
ps->lba_count = ((u64_t) buf[ATA_ID_LBA3] << 48) |
((u64_t) buf[ATA_ID_LBA2] << 32) |
(buf[ATA_ID_LBA1] << 16) | buf[ATA_ID_LBA0];
/* Determine the queue depth of the device. */
if (hba_state.has_ncq &&
(buf[ATA_ID_SATA_CAP] & ATA_ID_SATA_CAP_NCQ)) {
ps->flags |= FLAG_HAS_NCQ;
ps->queue_depth =
(buf[ATA_ID_QDEPTH] & ATA_ID_QDEPTH_MASK) + 1;
if (ps->queue_depth > hba_state.nr_cmds)
ps->queue_depth = hba_state.nr_cmds;
}
/* For now, we only support long logical sectors. Long physical sector
* support may be added later. Note that the given value is in words.
*/
if ((buf[ATA_ID_PLSS] & (ATA_ID_PLSS_VALID_MASK | ATA_ID_PLSS_LLS)) ==
(ATA_ID_PLSS_VALID | ATA_ID_PLSS_LLS))
ps->sector_size =
((buf[ATA_ID_LSS1] << 16) | buf[ATA_ID_LSS0]) << 1;
else
ps->sector_size = ATA_SECTOR_SIZE;
if (ps->sector_size < ATA_SECTOR_SIZE) {
dprintf(V_ERR, ("%s: invalid sector size %u\n",
ahci_portname(ps), ps->sector_size));
return FALSE;
}
ps->flags |= FLAG_HAS_MEDIUM | FLAG_HAS_FLUSH;
/* FLUSH CACHE is mandatory for ATA devices; write caches are not. */
if (buf[ATA_ID_SUP0] & ATA_ID_SUP0_WCACHE)
ps->flags |= FLAG_HAS_WCACHE;
/* Check Force Unit Access capability of the device. */
if ((buf[ATA_ID_ENA2] & (ATA_ID_ENA2_VALID_MASK | ATA_ID_ENA2_FUA)) ==
(ATA_ID_ENA2_VALID | ATA_ID_ENA2_FUA))
ps->flags |= FLAG_HAS_FUA;
return TRUE;
}
/*===========================================================================*
* ata_transfer *
*===========================================================================*/
static int ata_transfer(struct port_state *ps, int cmd, u64_t start_lba,
unsigned int count, int write, int force, prd_t *prdt, int nr_prds)
{
/* Perform data transfer from or to an ATA device.
*/
cmd_fis_t fis;
assert(count <= ATA_MAX_SECTORS);
/* Special case for sector counts: 65536 is specified as 0. */
if (count == ATA_MAX_SECTORS)
count = 0;
memset(&fis, 0, sizeof(fis));
fis.cf_dev = ATA_DEV_LBA;
if (ps->flags & FLAG_HAS_NCQ) {
if (write) {
if (force && (ps->flags & FLAG_HAS_FUA))
fis.cf_dev |= ATA_DEV_FUA;
fis.cf_cmd = ATA_CMD_WRITE_FPDMA_QUEUED;
} else {
fis.cf_cmd = ATA_CMD_READ_FPDMA_QUEUED;
}
}
else {
if (write) {
if (force && (ps->flags & FLAG_HAS_FUA))
fis.cf_cmd = ATA_CMD_WRITE_DMA_FUA_EXT;
else
fis.cf_cmd = ATA_CMD_WRITE_DMA_EXT;
}
else {
fis.cf_cmd = ATA_CMD_READ_DMA_EXT;
}
}
fis.cf_lba = start_lba & 0x00FFFFFFUL;
fis.cf_lba_exp = (start_lba >> 24) & 0x00FFFFFFUL;
fis.cf_sec = count & 0xFF;
fis.cf_sec_exp = (count >> 8) & 0xFF;
/* Start the command, and wait for it to complete or fail. */
port_set_cmd(ps, cmd, &fis, NULL /*packet*/, prdt, nr_prds, write);
return port_exec(ps, cmd, ahci_transfer_timeout);
}
/*===========================================================================*
* gen_identify *
*===========================================================================*/
static int gen_identify(struct port_state *ps, int blocking)
{
/* Identify an ATA or ATAPI device. If the blocking flag is set, block
* until the command has completed; otherwise return immediately.
*/
cmd_fis_t fis;
prd_t prd;
/* Set up a command, and a single PRD for the result. */
memset(&fis, 0, sizeof(fis));
if (ps->flags & FLAG_ATAPI)
fis.cf_cmd = ATA_CMD_IDENTIFY_PACKET;
else
fis.cf_cmd = ATA_CMD_IDENTIFY;
prd.vp_addr = ps->tmp_phys;
prd.vp_size = ATA_ID_SIZE;
/* Start the command, and possibly wait for the result. */
port_set_cmd(ps, 0, &fis, NULL /*packet*/, &prd, 1, FALSE /*write*/);
if (blocking)
return port_exec(ps, 0, ahci_command_timeout);
port_issue(ps, 0, ahci_command_timeout);
return OK;
}
/*===========================================================================*
* gen_flush_wcache *
*===========================================================================*/
static int gen_flush_wcache(struct port_state *ps)
{
/* Flush the device's write cache.
*/
cmd_fis_t fis;
/* The FLUSH CACHE command may not be supported by all (writable ATAPI)
* devices.
*/
if (!(ps->flags & FLAG_HAS_FLUSH))
return EINVAL;
/* Use the FLUSH CACHE command for both ATA and ATAPI. We are not
* interested in the disk location of a failure, so there is no reason
* to use the ATA-only FLUSH CACHE EXT command. Either way, the command
* may indeed fail due to a disk error, in which case it should be
* repeated. For now, we shift this responsibility onto the caller.
*/
memset(&fis, 0, sizeof(fis));
fis.cf_cmd = ATA_CMD_FLUSH_CACHE;
/* Start the command, and wait for it to complete or fail.
* The flush command may take longer than regular I/O commands.
*/
port_set_cmd(ps, 0, &fis, NULL /*packet*/, NULL /*prdt*/, 0,
FALSE /*write*/);
return port_exec(ps, 0, ahci_flush_timeout);
}
/*===========================================================================*
* gen_get_wcache *
*===========================================================================*/
static int gen_get_wcache(struct port_state *ps, int *val)
{
/* Retrieve the status of the device's write cache.
*/
int r;
/* Write caches are not mandatory. */
if (!(ps->flags & FLAG_HAS_WCACHE))
return EINVAL;
/* Retrieve information about the device. */
if ((r = gen_identify(ps, TRUE /*blocking*/)) != OK)
return r;
/* Return the current setting. */
*val = !!(((u16_t *) ps->tmp_base)[ATA_ID_ENA0] & ATA_ID_ENA0_WCACHE);
return OK;
}
/*===========================================================================*
* gen_set_wcache *
*===========================================================================*/
static int gen_set_wcache(struct port_state *ps, int enable)
{
/* Enable or disable the device's write cache.
*/
cmd_fis_t fis;
clock_t timeout;
/* Write caches are not mandatory. */
if (!(ps->flags & FLAG_HAS_WCACHE))
return EINVAL;
/* Disabling the write cache causes a (blocking) cache flush. Cache
* flushes may take much longer than regular commands.
*/
timeout = enable ? ahci_command_timeout : ahci_flush_timeout;
/* Set up a command. */
memset(&fis, 0, sizeof(fis));
fis.cf_cmd = ATA_CMD_SET_FEATURES;
fis.cf_feat = enable ? ATA_SF_EN_WCACHE : ATA_SF_DI_WCACHE;
/* Start the command, and wait for it to complete or fail. */
port_set_cmd(ps, 0, &fis, NULL /*packet*/, NULL /*prdt*/, 0,
FALSE /*write*/);
return port_exec(ps, 0, timeout);
}
/*===========================================================================*
* ct_set_fis *
*===========================================================================*/
static vir_bytes ct_set_fis(u8_t *ct, cmd_fis_t *fis, unsigned int tag)
{
/* Fill in the Frame Information Structure part of a command table,
* and return the resulting FIS size (in bytes). We only support the
* command Register - Host to Device FIS type.
*/
memset(ct, 0, ATA_H2D_SIZE);
ct[ATA_FIS_TYPE] = ATA_FIS_TYPE_H2D;
ct[ATA_H2D_FLAGS] = ATA_H2D_FLAGS_C;
ct[ATA_H2D_CMD] = fis->cf_cmd;
ct[ATA_H2D_LBA_LOW] = fis->cf_lba & 0xFF;
ct[ATA_H2D_LBA_MID] = (fis->cf_lba >> 8) & 0xFF;
ct[ATA_H2D_LBA_HIGH] = (fis->cf_lba >> 16) & 0xFF;
ct[ATA_H2D_DEV] = fis->cf_dev;
ct[ATA_H2D_LBA_LOW_EXP] = fis->cf_lba_exp & 0xFF;
ct[ATA_H2D_LBA_MID_EXP] = (fis->cf_lba_exp >> 8) & 0xFF;
ct[ATA_H2D_LBA_HIGH_EXP] = (fis->cf_lba_exp >> 16) & 0xFF;
ct[ATA_H2D_CTL] = fis->cf_ctl;
if (ATA_IS_FPDMA_CMD(fis->cf_cmd)) {
ct[ATA_H2D_FEAT] = fis->cf_sec;
ct[ATA_H2D_FEAT_EXP] = fis->cf_sec_exp;
ct[ATA_H2D_SEC] = tag << ATA_SEC_TAG_SHIFT;
ct[ATA_H2D_SEC_EXP] = 0;
} else {
ct[ATA_H2D_FEAT] = fis->cf_feat;
ct[ATA_H2D_FEAT_EXP] = fis->cf_feat_exp;
ct[ATA_H2D_SEC] = fis->cf_sec;
ct[ATA_H2D_SEC_EXP] = fis->cf_sec_exp;
}
return ATA_H2D_SIZE;
}
/*===========================================================================*
* ct_set_packet *
*===========================================================================*/
static void ct_set_packet(u8_t *ct, u8_t packet[ATAPI_PACKET_SIZE])
{
/* Fill in the packet part of a command table.
*/
memcpy(&ct[AHCI_CT_PACKET_OFF], packet, ATAPI_PACKET_SIZE);
}
/*===========================================================================*
* ct_set_prdt *
*===========================================================================*/
static void ct_set_prdt(u8_t *ct, prd_t *prdt, int nr_prds)
{
/* Fill in the PRDT part of a command table.
*/
u32_t *p;
int i;
p = (u32_t *) &ct[AHCI_CT_PRDT_OFF];
for (i = 0; i < nr_prds; i++, prdt++) {
*p++ = prdt->vp_addr;
*p++ = 0;
*p++ = 0;
*p++ = prdt->vp_size - 1;
}
}
/*===========================================================================*
* port_set_cmd *
*===========================================================================*/
static void port_set_cmd(struct port_state *ps, int cmd, cmd_fis_t *fis,
u8_t packet[ATAPI_PACKET_SIZE], prd_t *prdt, int nr_prds, int write)
{
/* Prepare the given command for execution, by constructing a command
* table and setting up a command list entry pointing to the table.
*/
u8_t *ct;
u32_t *cl;
vir_bytes size;
/* Set a port-specific flag that tells us if the command being
* processed is a NCQ command or not.
*/
if (ATA_IS_FPDMA_CMD(fis->cf_cmd)) {
ps->flags |= FLAG_NCQ_MODE;
} else {
assert(!ps->pend_mask);
ps->flags &= ~FLAG_NCQ_MODE;
}
/* Construct a command table, consisting of a command FIS, optionally
* a packet, and optionally a number of PRDs (making up the actual PRD
* table).
*/
ct = ps->ct_base[cmd];
assert(ct != NULL);
assert(nr_prds <= NR_PRDS);
size = ct_set_fis(ct, fis, cmd);
if (packet != NULL)
ct_set_packet(ct, packet);
ct_set_prdt(ct, prdt, nr_prds);
/* Construct a command list entry, pointing to the command's table.
* Current assumptions: callers always provide a Register - Host to
* Device type FIS, and all non-NCQ commands are prefetchable.
*/
cl = &ps->cl_base[cmd * AHCI_CL_ENTRY_DWORDS];
memset(cl, 0, AHCI_CL_ENTRY_SIZE);
cl[0] = (nr_prds << AHCI_CL_PRDTL_SHIFT) |
((!ATA_IS_FPDMA_CMD(fis->cf_cmd) &&
(nr_prds > 0 || packet != NULL)) ? AHCI_CL_PREFETCHABLE : 0) |
(write ? AHCI_CL_WRITE : 0) |
((packet != NULL) ? AHCI_CL_ATAPI : 0) |
((size / sizeof(u32_t)) << AHCI_CL_CFL_SHIFT);
cl[2] = ps->ct_phys[cmd];
}
/*===========================================================================*
* port_finish_cmd *
*===========================================================================*/
static void port_finish_cmd(struct port_state *ps, int cmd, int result)
{
/* Finish a command that has either succeeded or failed.
*/
assert(cmd < ps->queue_depth);
dprintf(V_REQ, ("%s: command %d %s\n", ahci_portname(ps),
cmd, (result == RESULT_SUCCESS) ? "succeeded" : "failed"));
/* Update the command result, and clear it from the pending list. */
ps->cmd_info[cmd].result = result;
assert(ps->pend_mask & (1 << cmd));
ps->pend_mask &= ~(1 << cmd);
/* Wake up the thread, unless it is the main thread. This can happen
* during initialization, as the gen_identify function is called by the
* main thread itself.
*/
if (ps->state != STATE_WAIT_ID)
blockdriver_mt_wakeup(ps->cmd_info[cmd].tid);
}
/*===========================================================================*
* port_fail_cmds *
*===========================================================================*/
static void port_fail_cmds(struct port_state *ps)
{
/* Fail all ongoing commands for a device.
*/
int i;
for (i = 0; ps->pend_mask != 0 && i < ps->queue_depth; i++)
if (ps->pend_mask & (1 << i))
port_finish_cmd(ps, i, RESULT_FAILURE);
}
/*===========================================================================*
* port_check_cmds *
*===========================================================================*/
static void port_check_cmds(struct port_state *ps)
{
/* Check what commands have completed, and finish them.
*/
u32_t mask, done;
int i;
/* See which commands have completed. */
if (ps->flags & FLAG_NCQ_MODE)
mask = port_read(ps, AHCI_PORT_SACT);
else
mask = port_read(ps, AHCI_PORT_CI);
/* Wake up threads corresponding to completed commands. */
done = ps->pend_mask & ~mask;
for (i = 0; i < ps->queue_depth; i++)
if (done & (1 << i))
port_finish_cmd(ps, i, RESULT_SUCCESS);
}
/*===========================================================================*
* port_find_cmd *
*===========================================================================*/
static int port_find_cmd(struct port_state *ps)
{
/* Find a free command tag to queue the current request.
*/
int i;
for (i = 0; i < ps->queue_depth; i++)
if (!(ps->pend_mask & (1 << i)))
break;
/* We should always be able to find a free slot, since a thread runs
* only when it is free, and thus, only because a slot is available.
*/
assert(i < ps->queue_depth);
return i;
}
/*===========================================================================*
* port_get_padbuf *
*===========================================================================*/
static int port_get_padbuf(struct port_state *ps, size_t size)
{
/* Make available a temporary buffer for use by this port. Enlarge the
* previous buffer if applicable and necessary, potentially changing
* its physical address.
*/
if (ps->pad_base != NULL && ps->pad_size >= size)
return OK;
if (ps->pad_base != NULL)
free_contig(ps->pad_base, ps->pad_size);
ps->pad_size = size;
ps->pad_base = alloc_contig(ps->pad_size, 0, &ps->pad_phys);
if (ps->pad_base == NULL) {
dprintf(V_ERR, ("%s: unable to allocate a padding buffer of "
"size %lu\n", ahci_portname(ps),
(unsigned long) size));
return ENOMEM;
}
dprintf(V_INFO, ("%s: allocated padding buffer of size %lu\n",
ahci_portname(ps), (unsigned long) size));
return OK;
}
/*===========================================================================*
* sum_iovec *
*===========================================================================*/
static int sum_iovec(struct port_state *ps, endpoint_t endpt,
iovec_s_t *iovec, int nr_req, vir_bytes *total)
{
/* Retrieve the total size of the given I/O vector. Check for alignment
* requirements along the way. Return OK (and the total request size)
* or an error.
*/
vir_bytes size, bytes;
int i;
bytes = 0;
for (i = 0; i < nr_req; i++) {
size = iovec[i].iov_size;
if (size == 0 || (size & 1) || size > LONG_MAX) {
dprintf(V_ERR, ("%s: bad size %lu in iovec from %d\n",
ahci_portname(ps), size, endpt));
return EINVAL;
}
bytes += size;
if (bytes > LONG_MAX) {
dprintf(V_ERR, ("%s: iovec size overflow from %d\n",
ahci_portname(ps), endpt));
return EINVAL;
}
}
*total = bytes;
return OK;
}
/*===========================================================================*
* setup_prdt *
*===========================================================================*/
static int setup_prdt(struct port_state *ps, endpoint_t endpt,
iovec_s_t *iovec, int nr_req, vir_bytes size, vir_bytes lead,
int write, prd_t *prdt)
{
/* Convert (the first part of) an I/O vector to a Physical Region
* Descriptor Table describing array that can later be used to set the
* command's real PRDT. The resulting table as a whole should be
* sector-aligned; leading and trailing local buffers may have to be
* used for padding as appropriate. Return the number of PRD entries,
* or a negative error code.
*/
struct vumap_vir vvec[NR_PRDS];
size_t bytes, trail;
int i, r, pcount, nr_prds = 0;
if (lead > 0) {
/* Allocate a buffer for the data we don't want. */
if ((r = port_get_padbuf(ps, ps->sector_size)) != OK)
return r;
prdt[nr_prds].vp_addr = ps->pad_phys;
prdt[nr_prds].vp_size = lead;
nr_prds++;
}
/* The sum of lead, size, trail has to be sector-aligned. */
trail = (ps->sector_size - (lead + size)) % ps->sector_size;
/* Get the physical addresses of the given buffers. */
for (i = 0; i < nr_req && size > 0; i++) {
bytes = MIN(iovec[i].iov_size, size);
if (endpt == SELF)
vvec[i].vv_addr = (vir_bytes) iovec[i].iov_grant;
else
vvec[i].vv_grant = iovec[i].iov_grant;
vvec[i].vv_size = bytes;
size -= bytes;
}
pcount = i;
if ((r = sys_vumap(endpt, vvec, i, 0, write ? VUA_READ : VUA_WRITE,
&prdt[nr_prds], &pcount)) != OK) {
dprintf(V_ERR, ("%s: unable to map memory from %d (%d)\n",
ahci_portname(ps), endpt, r));
return r;
}
assert(pcount > 0 && pcount <= i);
/* Make sure all buffers are physically contiguous and word-aligned. */
for (i = 0; i < pcount; i++) {
if (vvec[i].vv_size != prdt[nr_prds].vp_size) {
dprintf(V_ERR, ("%s: non-contiguous memory from %d\n",
ahci_portname(ps), endpt));
return EINVAL;
}
if (prdt[nr_prds].vp_addr & 1) {
dprintf(V_ERR, ("%s: bad physical address from %d\n",
ahci_portname(ps), endpt));
return EINVAL;
}
nr_prds++;
}
if (trail > 0) {
assert(nr_prds < NR_PRDS);
prdt[nr_prds].vp_addr = ps->pad_phys + lead;
prdt[nr_prds].vp_size = trail;
nr_prds++;
}
return nr_prds;
}
/*===========================================================================*
* port_transfer *
*===========================================================================*/
static ssize_t port_transfer(struct port_state *ps, u64_t pos, u64_t eof,
endpoint_t endpt, iovec_s_t *iovec, int nr_req, int write, int flags)
{
/* Perform an I/O transfer on a port.
*/
prd_t prdt[NR_PRDS];
vir_bytes size, lead;
unsigned int count, nr_prds;
u64_t start_lba;
int r, cmd;
/* Get the total request size from the I/O vector. */
if ((r = sum_iovec(ps, endpt, iovec, nr_req, &size)) != OK)
return r;
dprintf(V_REQ, ("%s: %s for %lu bytes at pos %llx\n",
ahci_portname(ps), write ? "write" : "read", size, pos));
assert(ps->state == STATE_GOOD_DEV);
assert(ps->flags & FLAG_HAS_MEDIUM);
assert(ps->sector_size > 0);
/* Limit the maximum size of a single transfer.
* See the comments at the top of this file for details.
*/
if (size > MAX_TRANSFER)
size = MAX_TRANSFER;
/* If necessary, reduce the request size so that the request does not
* extend beyond the end of the partition. The caller already
* guarantees that the starting position lies within the partition.
*/
if (pos + size > eof)
size = (vir_bytes) (eof - pos);
start_lba = pos / ps->sector_size;
lead = (vir_bytes) (pos % ps->sector_size);
count = (lead + size + ps->sector_size - 1) / ps->sector_size;
/* Position must be word-aligned for read requests, and sector-aligned
* for write requests. We do not support read-modify-write for writes.
*/
if ((lead & 1) || (write && lead != 0)) {
dprintf(V_ERR, ("%s: unaligned position from %d\n",
ahci_portname(ps), endpt));
return EINVAL;
}
/* Write requests must be sector-aligned. Word alignment of the size is
* already guaranteed by sum_iovec().
*/
if (write && (size % ps->sector_size) != 0) {
dprintf(V_ERR, ("%s: unaligned size %lu from %d\n",
ahci_portname(ps), size, endpt));
return EINVAL;
}
/* Create a vector of physical addresses and sizes for the transfer. */
nr_prds = r = setup_prdt(ps, endpt, iovec, nr_req, size, lead, write,
prdt);
if (r < 0) return r;
/* Perform the actual transfer. */
cmd = port_find_cmd(ps);
if (ps->flags & FLAG_ATAPI)
r = atapi_transfer(ps, cmd, start_lba, count, write, prdt,
nr_prds);
else
r = ata_transfer(ps, cmd, start_lba, count, write,
!!(flags & BDEV_FORCEWRITE), prdt, nr_prds);
if (r != OK) return r;
return size;
}
/*===========================================================================*
* port_hardreset *
*===========================================================================*/
static void port_hardreset(struct port_state *ps)
{
/* Perform a port-level (hard) reset on the given port.
*/
port_write(ps, AHCI_PORT_SCTL, AHCI_PORT_SCTL_DET_INIT);
micro_delay(COMRESET_DELAY * 1000); /* COMRESET_DELAY is in ms */
port_write(ps, AHCI_PORT_SCTL, AHCI_PORT_SCTL_DET_NONE);
}
/*===========================================================================*
* port_override *
*===========================================================================*/
static void port_override(struct port_state *ps)
{
/* Override the port's BSY and/or DRQ flags. This may only be done
* prior to starting the port.
*/
u32_t cmd;
cmd = port_read(ps, AHCI_PORT_CMD);
port_write(ps, AHCI_PORT_CMD, cmd | AHCI_PORT_CMD_CLO);
SPIN_UNTIL(!(port_read(ps, AHCI_PORT_CMD) & AHCI_PORT_CMD_CLO),
PORTREG_DELAY);
dprintf(V_INFO, ("%s: overridden\n", ahci_portname(ps)));
}
/*===========================================================================*
* port_start *
*===========================================================================*/
static void port_start(struct port_state *ps)
{
/* Start the given port, allowing for the execution of commands and the
* transfer of data on that port.
*/
u32_t cmd;
/* Reset status registers. */
port_write(ps, AHCI_PORT_SERR, ~0);
port_write(ps, AHCI_PORT_IS, ~0);
/* Start the port. */
cmd = port_read(ps, AHCI_PORT_CMD);
port_write(ps, AHCI_PORT_CMD, cmd | AHCI_PORT_CMD_ST);
dprintf(V_INFO, ("%s: started\n", ahci_portname(ps)));
}
/*===========================================================================*
* port_stop *
*===========================================================================*/
static void port_stop(struct port_state *ps)
{
/* Stop the given port, if not already stopped.
*/
u32_t cmd;
cmd = port_read(ps, AHCI_PORT_CMD);
if (cmd & (AHCI_PORT_CMD_CR | AHCI_PORT_CMD_ST)) {
port_write(ps, AHCI_PORT_CMD, cmd & ~AHCI_PORT_CMD_ST);
SPIN_UNTIL(!(port_read(ps, AHCI_PORT_CMD) & AHCI_PORT_CMD_CR),
PORTREG_DELAY);
dprintf(V_INFO, ("%s: stopped\n", ahci_portname(ps)));
}
}
/*===========================================================================*
* port_restart *
*===========================================================================*/
static void port_restart(struct port_state *ps)
{
/* Restart a port after a fatal error has occurred.
*/
/* Fail all outstanding commands. */
port_fail_cmds(ps);
/* Stop the port. */
port_stop(ps);
/* If the BSY and/or DRQ flags are set, reset the port. */
if (port_read(ps, AHCI_PORT_TFD) &
(AHCI_PORT_TFD_STS_BSY | AHCI_PORT_TFD_STS_DRQ)) {
dprintf(V_ERR, ("%s: port reset\n", ahci_portname(ps)));
/* To keep this driver simple, we do not transparently recover
* ongoing requests. Instead, we mark the failing device as
* disconnected, and reset it. If the reset succeeds, the
* device (or, perhaps, eventually, another device) will come
* back up. Any current and future requests to this port will
* be failed until the port is fully closed and reopened.
*/
port_disconnect(ps);
/* Trigger a port reset. */
port_hardreset(ps);
return;
}
/* Start the port. */
port_start(ps);
}
/*===========================================================================*
* print_string *
*===========================================================================*/
static void print_string(u16_t *buf, int start, int end)
{
/* Print a string that is stored as little-endian words and padded with
* trailing spaces.
*/
int i, last = 0;
while (end >= start && buf[end] == 0x2020) end--;
if (end >= start && (buf[end] & 0xFF) == 0x20) end--, last++;
for (i = start; i <= end; i++)
printf("%c%c", buf[i] >> 8, buf[i] & 0xFF);
if (last)
printf("%c", buf[i] >> 8);
}
/*===========================================================================*
* port_id_check *
*===========================================================================*/
static void port_id_check(struct port_state *ps, int success)
{
/* The device identification command has either completed or timed out.
* Decide whether this device is usable or not, and store some of its
* properties.
*/
u16_t *buf;
assert(ps->state == STATE_WAIT_ID);
ps->flags &= ~FLAG_BUSY;
cancel_timer(&ps->cmd_info[0].timer);
if (!success) {
if (!(ps->flags & FLAG_ATAPI) &&
port_read(ps, AHCI_PORT_SIG) != ATA_SIG_ATA) {
dprintf(V_INFO, ("%s: may not be ATA, trying ATAPI\n",
ahci_portname(ps)));
ps->flags |= FLAG_ATAPI;
(void) gen_identify(ps, FALSE /*blocking*/);
return;
}
dprintf(V_ERR,
("%s: unable to identify\n", ahci_portname(ps)));
}
/* If the identify command itself succeeded, check the results and
* store some properties.
*/
if (success) {
buf = (u16_t *) ps->tmp_base;
if (ps->flags & FLAG_ATAPI)
success = atapi_id_check(ps, buf);
else
success = ata_id_check(ps, buf);
}
/* If the device has not been identified successfully, mark it as an
* unusable device.
*/
if (!success) {
port_stop(ps);
ps->state = STATE_BAD_DEV;
port_write(ps, AHCI_PORT_IE, AHCI_PORT_IE_PRCE);
return;
}
/* The device has been identified successfully, and hence usable. */
ps->state = STATE_GOOD_DEV;
/* Print some information about the device. */
if (ahci_verbose >= V_INFO) {
printf("%s: ATA%s, ", ahci_portname(ps),
(ps->flags & FLAG_ATAPI) ? "PI" : "");
print_string(buf, 27, 46);
if (ahci_verbose >= V_DEV) {
printf(" (");
print_string(buf, 10, 19);
printf(", ");
print_string(buf, 23, 26);
printf(")");
}
if (ps->flags & FLAG_HAS_MEDIUM)
printf(", %u byte sectors, %llu MB size",
ps->sector_size,
ps->lba_count * ps->sector_size / (1024*1024));
printf("\n");
}
}
/*===========================================================================*
* port_connect *
*===========================================================================*/
static void port_connect(struct port_state *ps)
{
/* A device has been found to be attached to this port. Start the port,
* and do timed polling for its signature to become available.
*/
u32_t status, sig;
dprintf(V_INFO, ("%s: device connected\n", ahci_portname(ps)));
port_start(ps);
/* The next check covers a purely hypothetical race condition, where
* the device would disappear right before we try to start it. This is
* possible because we have to clear PxSERR, and with that, the DIAG.N
* bit. Double-check the port status, and if it is not as we expect,
* infer a disconnection.
*/
status = port_read(ps, AHCI_PORT_SSTS) & AHCI_PORT_SSTS_DET_MASK;
if (status != AHCI_PORT_SSTS_DET_PHY) {
dprintf(V_ERR, ("%s: device vanished!\n", ahci_portname(ps)));
port_stop(ps);
ps->state = STATE_NO_DEV;
ps->flags &= ~FLAG_BUSY;
return;
}
/* Clear all state flags except the busy flag, which may be relevant if
* a BDEV_OPEN call is waiting for the device to become ready; the
* barrier flag, which prevents access to the device until it is
* completely closed and (re)opened; and, the thread suspension flag.
*/
ps->flags &= (FLAG_BUSY | FLAG_BARRIER | FLAG_SUSPENDED);
/* Check the port's signature. We only use the signature to speed up
* identification; we will try both ATA and ATAPI if the signature is
* neither ATA nor ATAPI.
*/
sig = port_read(ps, AHCI_PORT_SIG);
if (sig == ATA_SIG_ATAPI)
ps->flags |= FLAG_ATAPI;
/* Attempt to identify the device. Do this using continuation, because
* we may already be called from port_wait() here, and could end up
* confusing the timer expiration procedure.
*/
ps->state = STATE_WAIT_ID;
port_write(ps, AHCI_PORT_IE, AHCI_PORT_IE_MASK);
(void) gen_identify(ps, FALSE /*blocking*/);
}
/*===========================================================================*
* port_disconnect *
*===========================================================================*/
static void port_disconnect(struct port_state *ps)
{
/* The device has detached from this port. It has already been stopped.
*/
dprintf(V_INFO, ("%s: device disconnected\n", ahci_portname(ps)));
ps->state = STATE_NO_DEV;
port_write(ps, AHCI_PORT_IE, AHCI_PORT_IE_PCE);
ps->flags &= ~FLAG_BUSY;
/* Fail any ongoing request. The caller may already have done this. */
port_fail_cmds(ps);
/* Block any further access until the device is completely closed and
* reopened. This prevents arbitrary I/O to a newly plugged-in device
* without upper layers noticing.
*/
ps->flags |= FLAG_BARRIER;
/* Inform the blockdriver library to reduce the number of threads. */
blockdriver_mt_set_workers(ps->device, 1);
}
/*===========================================================================*
* port_dev_check *
*===========================================================================*/
static void port_dev_check(struct port_state *ps)
{
/* Perform device detection by means of polling.
*/
u32_t status, tfd;
assert(ps->state == STATE_WAIT_DEV);
status = port_read(ps, AHCI_PORT_SSTS) & AHCI_PORT_SSTS_DET_MASK;
dprintf(V_DEV, ("%s: polled status %u\n", ahci_portname(ps), status));
switch (status) {
case AHCI_PORT_SSTS_DET_PHY:
tfd = port_read(ps, AHCI_PORT_TFD);
/* If a Phy connection has been established, and the BSY and
* DRQ flags are cleared, the device is ready.
*/
if (!(tfd & (AHCI_PORT_TFD_STS_BSY | AHCI_PORT_TFD_STS_DRQ))) {
port_connect(ps);
return;
}
/* fall-through */
case AHCI_PORT_SSTS_DET_DET:
/* A device has been detected, but it is not ready yet. Try for
* a while before giving up. This may take seconds.
*/
if (ps->left > 0) {
ps->left--;
set_timer(&ps->cmd_info[0].timer, ahci_device_delay,
port_timeout, BUILD_ARG(ps - port_state, 0));
return;
}
}
dprintf(V_INFO, ("%s: device not ready\n", ahci_portname(ps)));
/* We get here on timeout, and if the HBA reports that there is no
* device present at all. In all cases, we change to another state.
*/
if (status == AHCI_PORT_SSTS_DET_PHY) {
/* Some devices may not correctly clear BSY/DRQ. Upon timeout,
* if we can override these flags, do so and start the
* identification process anyway.
*/
if (hba_state.has_clo) {
port_override(ps);
port_connect(ps);
return;
}
/* A device is present and initialized, but not ready. */
ps->state = STATE_BAD_DEV;
port_write(ps, AHCI_PORT_IE, AHCI_PORT_IE_PRCE);
} else {
/* A device may or may not be present, but it does not appear
* to be ready in any case. Ignore it until the next device
* initialization event.
*/
ps->state = STATE_NO_DEV;
ps->flags &= ~FLAG_BUSY;
}
}
/*===========================================================================*
* port_intr *
*===========================================================================*/
static void port_intr(struct port_state *ps)
{
/* Process an interrupt on this port.
*/
u32_t smask, emask;
int success;
if (ps->state == STATE_NO_PORT) {
dprintf(V_ERR, ("%s: interrupt for invalid port!\n",
ahci_portname(ps)));
return;
}
smask = port_read(ps, AHCI_PORT_IS);
emask = smask & port_read(ps, AHCI_PORT_IE);
/* Clear the interrupt flags that we saw were set. */
port_write(ps, AHCI_PORT_IS, smask);
dprintf(V_REQ, ("%s: interrupt (%08x)\n", ahci_portname(ps), smask));
/* Check if any commands have completed. */
port_check_cmds(ps);
if (emask & AHCI_PORT_IS_PCS) {
/* Clear the X diagnostics bit to clear this interrupt. */
port_write(ps, AHCI_PORT_SERR, AHCI_PORT_SERR_DIAG_X);
dprintf(V_DEV, ("%s: device attached\n", ahci_portname(ps)));
switch (ps->state) {
case STATE_SPIN_UP:
case STATE_NO_DEV:
/* Reportedly, a device has shown up. Start polling its
* status until it has become ready.
*/
if (ps->state == STATE_SPIN_UP)
cancel_timer(&ps->cmd_info[0].timer);
ps->state = STATE_WAIT_DEV;
ps->left = ahci_device_checks;
port_dev_check(ps);
break;
case STATE_WAIT_DEV:
/* Nothing else to do. */
break;
default:
/* Impossible. */
assert(0);
}
} else if (emask & AHCI_PORT_IS_PRCS) {
/* Clear the N diagnostics bit to clear this interrupt. */
port_write(ps, AHCI_PORT_SERR, AHCI_PORT_SERR_DIAG_N);
dprintf(V_DEV, ("%s: device detached\n", ahci_portname(ps)));
switch (ps->state) {
case STATE_WAIT_ID:
case STATE_GOOD_DEV:
/* The device is no longer ready. Stop the port, cancel
* ongoing requests, and disconnect the device.
*/
port_stop(ps);
/* fall-through */
case STATE_BAD_DEV:
port_disconnect(ps);
/* The device has become unusable to us at this point.
* Reset the port to make sure that once the device (or
* another device) becomes usable again, we will get a
* PCS interrupt as well.
*/
port_hardreset(ps);
break;
default:
/* Impossible. */
assert(0);
}
} else if (smask & AHCI_PORT_IS_MASK) {
/* We assume that any other interrupt indicates command
* completion or (command or device) failure. Unfortunately, if
* an NCQ command failed, we cannot easily determine which one
* it was. For that reason, after completing all successfully
* finished commands (above), we fail all other outstanding
* commands and restart the port. This can possibly be improved
* later by obtaining per-command status results from the HBA.
*/
success = !(port_read(ps, AHCI_PORT_TFD) &
(AHCI_PORT_TFD_STS_ERR | AHCI_PORT_TFD_STS_DF));
/* Check now for failure. There are fatal failures, and there
* are failures that set the TFD.STS.ERR field using a D2H
* FIS. In both cases, we just restart the port, failing all
* commands in the process.
*/
if ((port_read(ps, AHCI_PORT_TFD) &
(AHCI_PORT_TFD_STS_ERR | AHCI_PORT_TFD_STS_DF)) ||
(smask & AHCI_PORT_IS_RESTART)) {
port_restart(ps);
}
/* If we were waiting for ID verification, check now. */
if (ps->state == STATE_WAIT_ID)
port_id_check(ps, success);
}
}
/*===========================================================================*
* port_timeout *
*===========================================================================*/
static void port_timeout(minix_timer_t *tp)
{
/* A timeout has occurred on this port. Figure out what the timeout is
* for, and take appropriate action.
*/
struct port_state *ps;
int port, cmd;
port = GET_PORT(tmr_arg(tp)->ta_int);
cmd = GET_TAG(tmr_arg(tp)->ta_int);
assert(port >= 0 && port < hba_state.nr_ports);
ps = &port_state[port];
/* Regardless of the outcome of this timeout, wake up the thread if it
* is suspended. This applies only during the initialization.
*/
if (ps->flags & FLAG_SUSPENDED) {
assert(cmd == 0);
blockdriver_mt_wakeup(ps->cmd_info[0].tid);
}
/* If detection of a device after startup timed out, give up on initial
* detection and only look for hot plug events from now on.
*/
if (ps->state == STATE_SPIN_UP) {
/* One exception: if the PCS interrupt bit is set here, then we
* are probably running on VirtualBox, which is currently not
* always raising interrupts when setting interrupt bits (!).
*/
if (port_read(ps, AHCI_PORT_IS) & AHCI_PORT_IS_PCS) {
dprintf(V_INFO, ("%s: bad controller, no interrupt\n",
ahci_portname(ps)));
ps->state = STATE_WAIT_DEV;
ps->left = ahci_device_checks;
port_dev_check(ps);
return;
} else {
dprintf(V_INFO, ("%s: spin-up timeout\n",
ahci_portname(ps)));
/* If the busy flag is set, a BDEV_OPEN request is
* waiting for the detection to finish; clear the busy
* flag to return an error to the caller.
*/
ps->state = STATE_NO_DEV;
ps->flags &= ~FLAG_BUSY;
}
return;
}
/* If we are waiting for a device to become connected and initialized,
* check now.
*/
if (ps->state == STATE_WAIT_DEV) {
port_dev_check(ps);
return;
}
dprintf(V_ERR, ("%s: timeout\n", ahci_portname(ps)));
/* Restart the port, failing all current commands. */
port_restart(ps);
/* Finish up the identify operation. */
if (ps->state == STATE_WAIT_ID)
port_id_check(ps, FALSE);
}
/*===========================================================================*
* port_wait *
*===========================================================================*/
static void port_wait(struct port_state *ps)
{
/* Suspend the current thread until the given port is no longer busy,
* due to either command completion or timeout.
*/
ps->flags |= FLAG_SUSPENDED;
while (ps->flags & FLAG_BUSY)
blockdriver_mt_sleep();
ps->flags &= ~FLAG_SUSPENDED;
}
/*===========================================================================*
* port_issue *
*===========================================================================*/
static void port_issue(struct port_state *ps, int cmd, clock_t timeout)
{
/* Issue a command to the port, and set a timer to trigger a timeout
* if the command takes too long to complete.
*/
/* Set the corresponding NCQ command bit, if applicable. */
if (ps->flags & FLAG_HAS_NCQ)
port_write(ps, AHCI_PORT_SACT, 1 << cmd);
/* Make sure that the compiler does not delay any previous write
* operations until after the write to the command issue register.
*/
__insn_barrier();
/* Tell the controller that a new command is ready. */
port_write(ps, AHCI_PORT_CI, 1 << cmd);
/* Update pending commands. */
ps->pend_mask |= 1 << cmd;
/* Set a timer in case the command does not complete at all. */
set_timer(&ps->cmd_info[cmd].timer, timeout, port_timeout,
BUILD_ARG(ps - port_state, cmd));
}
/*===========================================================================*
* port_exec *
*===========================================================================*/
static int port_exec(struct port_state *ps, int cmd, clock_t timeout)
{
/* Execute a command on a port, wait for the command to complete or for
* a timeout, and return whether the command succeeded or not.
*/
port_issue(ps, cmd, timeout);
/* Put the thread to sleep until a timeout or a command completion
* happens. Earlier, we used to call port_wait which set the suspended
* flag. We now abandon it since the flag has to work on a per-thread,
* and hence per-tag basis and not on a per-port basis. Instead, we
* retain that call only to defer open calls during device/driver
* initialization. Instead, we call sleep here directly. Before
* sleeping, we register the thread.
*/
ps->cmd_info[cmd].tid = blockdriver_mt_get_tid();
blockdriver_mt_sleep();
/* Cancelling a timer that just triggered, does no harm. */
cancel_timer(&ps->cmd_info[cmd].timer);
assert(!(ps->flags & FLAG_BUSY));
dprintf(V_REQ, ("%s: end of command -- %s\n", ahci_portname(ps),
(ps->cmd_info[cmd].result == RESULT_FAILURE) ?
"failure" : "success"));
if (ps->cmd_info[cmd].result == RESULT_FAILURE)
return EIO;
return OK;
}
/*===========================================================================*
* port_alloc *
*===========================================================================*/
static void port_alloc(struct port_state *ps)
{
/* Allocate memory for the given port, and enable FIS receipt. We try
* to cram everything into one 4K-page in order to limit memory usage
* as much as possible. More memory may be allocated on demand later,
* but allocation failure should be fatal only here. Note that we do
* not allocate memory for sector padding here, because we do not know
* the device's sector size yet.
*/
size_t fis_off, tmp_off, ct_off; int i;
size_t ct_offs[NR_CMDS];
u32_t cmd;
fis_off = AHCI_CL_SIZE + AHCI_FIS_SIZE - 1;
fis_off -= fis_off % AHCI_FIS_SIZE;
tmp_off = fis_off + AHCI_FIS_SIZE + AHCI_TMP_ALIGN - 1;
tmp_off -= tmp_off % AHCI_TMP_ALIGN;
/* Allocate memory for all the commands. */
ct_off = tmp_off + AHCI_TMP_SIZE;
for (i = 0; i < NR_CMDS; i++) {
ct_off += AHCI_CT_ALIGN - 1;
ct_off -= ct_off % AHCI_CT_ALIGN;
ct_offs[i] = ct_off;
ps->mem_size = ct_off + AHCI_CT_SIZE;
ct_off = ps->mem_size;
}
ps->mem_base = alloc_contig(ps->mem_size, AC_ALIGN4K, &ps->mem_phys);
if (ps->mem_base == NULL)
panic("unable to allocate port memory");
memset(ps->mem_base, 0, ps->mem_size);
ps->cl_base = (u32_t *) ps->mem_base;
ps->cl_phys = ps->mem_phys;
assert(ps->cl_phys % AHCI_CL_SIZE == 0);
ps->fis_base = (u32_t *) (ps->mem_base + fis_off);
ps->fis_phys = ps->mem_phys + fis_off;
assert(ps->fis_phys % AHCI_FIS_SIZE == 0);
ps->tmp_base = (u8_t *) (ps->mem_base + tmp_off);
ps->tmp_phys = ps->mem_phys + tmp_off;
assert(ps->tmp_phys % AHCI_TMP_ALIGN == 0);
for (i = 0; i < NR_CMDS; i++) {
ps->ct_base[i] = ps->mem_base + ct_offs[i];
ps->ct_phys[i] = ps->mem_phys + ct_offs[i];
assert(ps->ct_phys[i] % AHCI_CT_ALIGN == 0);
}
/* Tell the controller about some of the physical addresses. */
port_write(ps, AHCI_PORT_FBU, 0);
port_write(ps, AHCI_PORT_FB, ps->fis_phys);
port_write(ps, AHCI_PORT_CLBU, 0);
port_write(ps, AHCI_PORT_CLB, ps->cl_phys);
/* Enable FIS receive. */
cmd = port_read(ps, AHCI_PORT_CMD);
port_write(ps, AHCI_PORT_CMD, cmd | AHCI_PORT_CMD_FRE);
ps->pad_base = NULL;
ps->pad_size = 0;
}
/*===========================================================================*
* port_free *
*===========================================================================*/
static void port_free(struct port_state *ps)
{
/* Disable FIS receipt for the given port, and free previously
* allocated memory.
*/
u32_t cmd;
int i;
/* Disable FIS receive. */
cmd = port_read(ps, AHCI_PORT_CMD);
if (cmd & (AHCI_PORT_CMD_FR | AHCI_PORT_CMD_FRE)) {
port_write(ps, AHCI_PORT_CMD, cmd & ~AHCI_PORT_CMD_FRE);
SPIN_UNTIL(!(port_read(ps, AHCI_PORT_CMD) & AHCI_PORT_CMD_FR),
PORTREG_DELAY);
}
if (ps->pad_base != NULL)
free_contig(ps->pad_base, ps->pad_size);
/* The first command table is part of the primary memory page. */
for (i = 1; i < hba_state.nr_cmds; i++)
if (ps->ct_base[i] != NULL)
free_contig(ps->ct_base[i], AHCI_CT_SIZE);
free_contig(ps->mem_base, ps->mem_size);
}
/*===========================================================================*
* port_init *
*===========================================================================*/
static void port_init(struct port_state *ps)
{
/* Initialize the given port.
*/
u32_t cmd;
int i;
/* Initialize the port state structure. */
ps->queue_depth = 1;
ps->state = STATE_SPIN_UP;
ps->flags = FLAG_BUSY;
ps->sector_size = 0;
ps->open_count = 0;
ps->pend_mask = 0;
for (i = 0; i < NR_CMDS; i++)
init_timer(&ps->cmd_info[i].timer);
ps->reg = (u32_t *) ((u8_t *) hba_state.base +
AHCI_MEM_BASE_SIZE + AHCI_MEM_PORT_SIZE * (ps - port_state));
/* Allocate memory for the port. */
port_alloc(ps);
/* Just listen for device connection events for now. */
port_write(ps, AHCI_PORT_IE, AHCI_PORT_IE_PCE);
/* Enable device spin-up for HBAs that support staggered spin-up.
* This is a no-op for HBAs that do not support it.
*/
cmd = port_read(ps, AHCI_PORT_CMD);
port_write(ps, AHCI_PORT_CMD, cmd | AHCI_PORT_CMD_SUD);
/* Trigger a port reset. */
port_hardreset(ps);
set_timer(&ps->cmd_info[0].timer, ahci_spinup_timeout,
port_timeout, BUILD_ARG(ps - port_state, 0));
}
/*===========================================================================*
* ahci_probe *
*===========================================================================*/
static int ahci_probe(int skip)
{
/* Find a matching PCI device.
*/
int r, devind;
u16_t vid, did;
pci_init();
r = pci_first_dev(&devind, &vid, &did);
if (r <= 0)
return -1;
while (skip--) {
r = pci_next_dev(&devind, &vid, &did);
if (r <= 0)
return -1;
}
pci_reserve(devind);
return devind;
}
/*===========================================================================*
* ahci_reset *
*===========================================================================*/
static void ahci_reset(void)
{
/* Reset the HBA. Do not enable AHCI mode afterwards.
*/
u32_t ghc;
ghc = hba_read(AHCI_HBA_GHC);
hba_write(AHCI_HBA_GHC, ghc | AHCI_HBA_GHC_AE);
hba_write(AHCI_HBA_GHC, ghc | AHCI_HBA_GHC_AE | AHCI_HBA_GHC_HR);
SPIN_UNTIL(!(hba_read(AHCI_HBA_GHC) & AHCI_HBA_GHC_HR), RESET_DELAY);
if (hba_read(AHCI_HBA_GHC) & AHCI_HBA_GHC_HR)
panic("unable to reset HBA");
}
/*===========================================================================*
* ahci_init *
*===========================================================================*/
static void ahci_init(int devind)
{
/* Initialize the device.
*/
u32_t base, size, cap, ghc, mask;
int r, port, ioflag;
if ((r = pci_get_bar(devind, PCI_BAR_6, &base, &size, &ioflag)) != OK)
panic("unable to retrieve BAR: %d", r);
if (ioflag)
panic("invalid BAR type");
/* There must be at least one port, and at most NR_PORTS ports. Limit
* the actual total number of ports to the size of the exposed area.
*/
if (size < AHCI_MEM_BASE_SIZE + AHCI_MEM_PORT_SIZE)
panic("HBA memory size too small: %u", size);
size = MIN(size, AHCI_MEM_BASE_SIZE + AHCI_MEM_PORT_SIZE * NR_PORTS);
hba_state.nr_ports = (size - AHCI_MEM_BASE_SIZE) / AHCI_MEM_PORT_SIZE;
/* Map the register area into local memory. */
hba_state.base = (u32_t *) vm_map_phys(SELF, (void *) base, size);
hba_state.size = size;
if (hba_state.base == MAP_FAILED)
panic("unable to map HBA memory");
/* Retrieve, allocate and enable the controller's IRQ. */
hba_state.irq = pci_attr_r8(devind, PCI_ILR);
hba_state.hook_id = 0;
if ((r = sys_irqsetpolicy(hba_state.irq, 0, &hba_state.hook_id)) != OK)
panic("unable to register IRQ: %d", r);
if ((r = sys_irqenable(&hba_state.hook_id)) != OK)
panic("unable to enable IRQ: %d", r);
/* Reset the HBA. */
ahci_reset();
/* Enable AHCI and interrupts. */
ghc = hba_read(AHCI_HBA_GHC);
hba_write(AHCI_HBA_GHC, ghc | AHCI_HBA_GHC_AE | AHCI_HBA_GHC_IE);
/* Limit the maximum number of commands to the controller's value. */
/* Note that we currently use only one command anyway. */
cap = hba_read(AHCI_HBA_CAP);
hba_state.has_ncq = !!(cap & AHCI_HBA_CAP_SNCQ);
hba_state.has_clo = !!(cap & AHCI_HBA_CAP_SCLO);
hba_state.nr_cmds = MIN(NR_CMDS,
((cap >> AHCI_HBA_CAP_NCS_SHIFT) & AHCI_HBA_CAP_NCS_MASK) + 1);
dprintf(V_INFO, ("AHCI%u: HBA v%d.%d%d, %ld ports, %ld commands, "
"%s queuing, IRQ %d\n",
ahci_instance,
(int) (hba_read(AHCI_HBA_VS) >> 16),
(int) ((hba_read(AHCI_HBA_VS) >> 8) & 0xFF),
(int) (hba_read(AHCI_HBA_VS) & 0xFF),
((cap >> AHCI_HBA_CAP_NP_SHIFT) & AHCI_HBA_CAP_NP_MASK) + 1,
((cap >> AHCI_HBA_CAP_NCS_SHIFT) & AHCI_HBA_CAP_NCS_MASK) + 1,
hba_state.has_ncq ? "supports" : "no", hba_state.irq));
dprintf(V_INFO, ("AHCI%u: CAP %08x, CAP2 %08x, PI %08x\n",
ahci_instance, cap, hba_read(AHCI_HBA_CAP2),
hba_read(AHCI_HBA_PI)));
/* Initialize each of the implemented ports. We ignore CAP.NP. */
mask = hba_read(AHCI_HBA_PI);
for (port = 0; port < hba_state.nr_ports; port++) {
port_state[port].device = NO_DEVICE;
port_state[port].state = STATE_NO_PORT;
if (mask & (1 << port))
port_init(&port_state[port]);
}
}
/*===========================================================================*
* ahci_stop *
*===========================================================================*/
static void ahci_stop(void)
{
/* Disable AHCI, and clean up resources to the extent possible.
*/
struct port_state *ps;
int r, port;
for (port = 0; port < hba_state.nr_ports; port++) {
ps = &port_state[port];
if (ps->state != STATE_NO_PORT) {
port_stop(ps);
port_free(ps);
}
}
ahci_reset();
if ((r = vm_unmap_phys(SELF, (void *) hba_state.base,
hba_state.size)) != OK)
panic("unable to unmap HBA memory: %d", r);
if ((r = sys_irqrmpolicy(&hba_state.hook_id)) != OK)
panic("unable to deregister IRQ: %d", r);
}
/*===========================================================================*
* ahci_alarm *
*===========================================================================*/
static void ahci_alarm(clock_t stamp)
{
/* Process an alarm.
*/
/* Call the port-specific handler for each port that timed out. */
expire_timers(stamp);
}
/*===========================================================================*
* ahci_intr *
*===========================================================================*/
static void ahci_intr(unsigned int UNUSED(mask))
{
/* Process an interrupt.
*/
struct port_state *ps;
u32_t mask;
int r, port;
/* Handle an interrupt for each port that has the interrupt bit set. */
mask = hba_read(AHCI_HBA_IS);
for (port = 0; port < hba_state.nr_ports; port++) {
if (mask & (1 << port)) {
ps = &port_state[port];
port_intr(ps);
/* After processing an interrupt, wake up the device
* thread if it is suspended and now no longer busy.
*/
if ((ps->flags & (FLAG_SUSPENDED | FLAG_BUSY)) ==
FLAG_SUSPENDED)
blockdriver_mt_wakeup(ps->cmd_info[0].tid);
}
}
/* Clear the bits that we processed. */
hba_write(AHCI_HBA_IS, mask);
/* Reenable the interrupt. */
if ((r = sys_irqenable(&hba_state.hook_id)) != OK)
panic("unable to enable IRQ: %d", r);
}
/*===========================================================================*
* ahci_get_params *
*===========================================================================*/
static void ahci_get_params(void)
{
/* Retrieve and parse parameters passed to this driver, except the
* device-to-port mapping, which has to be parsed later.
*/
long v;
unsigned int i;
/* Find out which driver instance we are. */
v = 0;
(void) env_parse("instance", "d", 0, &v, 0, 255);
ahci_instance = (int) v;
/* Initialize the verbosity level. */
v = V_ERR;
(void) env_parse("ahci_verbose", "d", 0, &v, V_NONE, V_REQ);
ahci_verbose = (int) v;
/* Initialize timeout-related values. */
for (i = 0; i < sizeof(ahci_timevar) / sizeof(ahci_timevar[0]); i++) {
v = ahci_timevar[i].default_ms;
(void) env_parse(ahci_timevar[i].name, "d", 0, &v, 1,
LONG_MAX);
*ahci_timevar[i].ptr = millis_to_hz(v);
}
ahci_device_delay = millis_to_hz(DEVICE_DELAY);
ahci_device_checks = (ahci_device_timeout + ahci_device_delay - 1) /
ahci_device_delay;
}
/*===========================================================================*
* ahci_set_mapping *
*===========================================================================*/
static void ahci_set_mapping(void)
{
/* Construct a mapping from device nodes to port numbers.
*/
char key[16], val[32], *p;
unsigned int port;
int i, j;
/* Start off with a mapping that includes implemented ports only, in
* order. We choose this mapping over an identity mapping to maximize
* the chance that the user will be able to access the first MAX_DRIVES
* devices. Note that we can only do this after initializing the HBA.
*/
for (i = j = 0; i < NR_PORTS && j < MAX_DRIVES; i++)
if (port_state[i].state != STATE_NO_PORT)
ahci_map[j++] = i;
for ( ; j < MAX_DRIVES; j++)
ahci_map[j] = NO_PORT;
/* See if the user specified a custom mapping. Unlike all other
* configuration options, this is a per-instance setting.
*/
strlcpy(key, "ahci0_map", sizeof(key));
key[4] += ahci_instance;
if (env_get_param(key, val, sizeof(val)) == OK) {
/* Parse the mapping, which is assumed to be a comma-separated
* list of zero-based port numbers.
*/
p = val;
for (i = 0; i < MAX_DRIVES; i++) {
if (*p) {
port = (unsigned int) strtoul(p, &p, 0);
if (*p) p++;
ahci_map[i] = port % NR_PORTS;
}
else ahci_map[i] = NO_PORT;
}
}
/* Create a reverse mapping. */
for (i = 0; i < MAX_DRIVES; i++)
if ((j = ahci_map[i]) != NO_PORT)
port_state[j].device = i;
}
/*===========================================================================*
* sef_cb_init_fresh *
*===========================================================================*/
static int sef_cb_init_fresh(int type, sef_init_info_t *UNUSED(info))
{
/* Initialize the driver.
*/
int devind;
/* Get command line parameters. */
ahci_get_params();
/* Probe for recognized devices, skipping matches as appropriate. */
devind = ahci_probe(ahci_instance);
if (devind < 0)
panic("no matching device found");
/* Initialize the device we found. */
ahci_init(devind);
/* Create a mapping from device nodes to port numbers. */
ahci_set_mapping();
/* Announce that we are up. */
blockdriver_announce(type);
return OK;
}
/*===========================================================================*
* sef_cb_signal_handler *
*===========================================================================*/
static void sef_cb_signal_handler(int signo)
{
/* In case of a termination signal, shut down this driver.
*/
int port;
if (signo != SIGTERM) return;
/* If any ports are still opened, assume that the system is being shut
* down, and stay up until the last device has been closed.
*/
ahci_exiting = TRUE;
for (port = 0; port < hba_state.nr_ports; port++)
if (port_state[port].open_count > 0)
return;
/* If not, stop the driver and exit immediately. */
ahci_stop();
exit(0);
}
/*===========================================================================*
* sef_local_startup *
*===========================================================================*/
static void sef_local_startup(void)
{
/* Set callbacks and initialize the System Event Framework (SEF).
*/
/* Register init callbacks. */
sef_setcb_init_fresh(sef_cb_init_fresh);
sef_setcb_init_lu(sef_cb_init_fresh);
/* Register signal callbacks. */
sef_setcb_signal_handler(sef_cb_signal_handler);
/* Let SEF perform startup. */
sef_startup();
}
/*===========================================================================*
* ahci_portname *
*===========================================================================*/
static char *ahci_portname(struct port_state *ps)
{
/* Return a printable name for the given port. Whenever we can, print a
* "Dx" device number rather than a "Pxx" port number, because the user
* may not be aware of the mapping currently in use.
*/
static char name[] = "AHCI0-P00";
name[4] = '0' + ahci_instance;
if (ps->device == NO_DEVICE) {
name[6] = 'P';
name[7] = '0' + (ps - port_state) / 10;
name[8] = '0' + (ps - port_state) % 10;
}
else {
name[6] = 'D';
name[7] = '0' + ps->device;
name[8] = 0;
}
return name;
}
/*===========================================================================*
* ahci_map_minor *
*===========================================================================*/
static struct port_state *ahci_map_minor(devminor_t minor, struct device **dvp)
{
/* Map a minor device number to a port and a pointer to the partition's
* device structure. Return NULL if this minor device number does not
* identify an actual device.
*/
struct port_state *ps;
int port;
ps = NULL;
if (minor >= 0 && minor < NR_MINORS) {
port = ahci_map[minor / DEV_PER_DRIVE];
if (port == NO_PORT)
return NULL;
ps = &port_state[port];
*dvp = &ps->part[minor % DEV_PER_DRIVE];
}
else if ((unsigned) (minor -= MINOR_d0p0s0) < NR_SUBDEVS) {
port = ahci_map[minor / SUB_PER_DRIVE];
if (port == NO_PORT)
return NULL;
ps = &port_state[port];
*dvp = &ps->subpart[minor % SUB_PER_DRIVE];
}
return ps;
}
/*===========================================================================*
* ahci_part *
*===========================================================================*/
static struct device *ahci_part(devminor_t minor)
{
/* Return a pointer to the partition information structure of the given
* minor device.
*/
struct device *dv;
if (ahci_map_minor(minor, &dv) == NULL)
return NULL;
return dv;
}
/*===========================================================================*
* ahci_open *
*===========================================================================*/
static int ahci_open(devminor_t minor, int access)
{
/* Open a device.
*/
struct port_state *ps;
int r;
ps = ahci_get_port(minor);
/* Only one open request can be processed at a time, due to the fact
* that it is an exclusive operation. The thread that handles this call
* can therefore freely register itself at slot zero.
*/
ps->cmd_info[0].tid = blockdriver_mt_get_tid();
/* If we are still in the process of initializing this port or device,
* wait for completion of that phase first.
*/
if (ps->flags & FLAG_BUSY)
port_wait(ps);
/* The device may only be opened if it is now properly functioning. */
if (ps->state != STATE_GOOD_DEV)
return ENXIO;
/* Some devices may only be opened in read-only mode. */
if ((ps->flags & FLAG_READONLY) && (access & BDEV_W_BIT))
return EACCES;
if (ps->open_count == 0) {
/* The first open request. Clear the barrier flag, if set. */
ps->flags &= ~FLAG_BARRIER;
/* Recheck media only when nobody is using the device. */
if ((ps->flags & FLAG_ATAPI) &&
(r = atapi_check_medium(ps, 0)) != OK)
return r;
/* After rechecking the media, the partition table must always
* be read. This is also a convenient time to do it for
* nonremovable devices. Start by resetting the partition
* tables and setting the working size of the entire device.
*/
memset(ps->part, 0, sizeof(ps->part));
memset(ps->subpart, 0, sizeof(ps->subpart));
ps->part[0].dv_size = ps->lba_count * ps->sector_size;
partition(&ahci_dtab, ps->device * DEV_PER_DRIVE, P_PRIMARY,
!!(ps->flags & FLAG_ATAPI));
blockdriver_mt_set_workers(ps->device, ps->queue_depth);
}
else {
/* If the barrier flag is set, deny new open requests until the
* device is fully closed first.
*/
if (ps->flags & FLAG_BARRIER)
return ENXIO;
}
ps->open_count++;
return OK;
}
/*===========================================================================*
* ahci_close *
*===========================================================================*/
static int ahci_close(devminor_t minor)
{
/* Close a device.
*/
struct port_state *ps;
int port;
ps = ahci_get_port(minor);
/* Decrease the open count. */
if (ps->open_count <= 0) {
dprintf(V_ERR, ("%s: closing already-closed port\n",
ahci_portname(ps)));
return EINVAL;
}
ps->open_count--;
if (ps->open_count > 0)
return OK;
/* The device is now fully closed. That also means that the threads for
* this device are not needed anymore, so we reduce the count to one.
*/
blockdriver_mt_set_workers(ps->device, 1);
if (ps->state == STATE_GOOD_DEV && !(ps->flags & FLAG_BARRIER)) {
dprintf(V_INFO, ("%s: flushing write cache\n",
ahci_portname(ps)));
(void) gen_flush_wcache(ps);
}
/* If the entire driver has been told to terminate, check whether all
* devices are now closed. If so, tell libblockdriver to quit after
* replying to the close request.
*/
if (ahci_exiting) {
for (port = 0; port < hba_state.nr_ports; port++)
if (port_state[port].open_count > 0)
break;
if (port == hba_state.nr_ports) {
ahci_stop();
blockdriver_mt_terminate();
}
}
return OK;
}
/*===========================================================================*
* ahci_transfer *
*===========================================================================*/
static ssize_t ahci_transfer(devminor_t minor, int do_write, u64_t position,
endpoint_t endpt, iovec_t *iovec, unsigned int count, int flags)
{
/* Perform data transfer on the selected device.
*/
struct port_state *ps;
struct device *dv;
u64_t pos, eof;
ps = ahci_get_port(minor);
dv = ahci_part(minor);
if (ps->state != STATE_GOOD_DEV || (ps->flags & FLAG_BARRIER))
return EIO;
if (count > NR_IOREQS)
return EINVAL;
/* Check for basic end-of-partition condition: if the start position of
* the request is outside the partition, return success immediately.
* The size of the request is obtained, and possibly reduced, later.
*/
if (position >= dv->dv_size)
return OK;
pos = dv->dv_base + position;
eof = dv->dv_base + dv->dv_size;
return port_transfer(ps, pos, eof, endpt, (iovec_s_t *) iovec, count,
do_write, flags);
}
/*===========================================================================*
* ahci_ioctl *
*===========================================================================*/
static int ahci_ioctl(devminor_t minor, unsigned long request,
endpoint_t endpt, cp_grant_id_t grant, endpoint_t UNUSED(user_endpt))
{
/* Process I/O control requests.
*/
struct port_state *ps;
int r, val;
ps = ahci_get_port(minor);
switch (request) {
case DIOCEJECT:
if (ps->state != STATE_GOOD_DEV || (ps->flags & FLAG_BARRIER))
return EIO;
if (!(ps->flags & FLAG_ATAPI))
return EINVAL;
return atapi_load_eject(ps, 0, FALSE /*load*/);
case DIOCOPENCT:
return sys_safecopyto(endpt, grant, 0,
(vir_bytes) &ps->open_count, sizeof(ps->open_count));
case DIOCFLUSH:
if (ps->state != STATE_GOOD_DEV || (ps->flags & FLAG_BARRIER))
return EIO;
return gen_flush_wcache(ps);
case DIOCSETWC:
if (ps->state != STATE_GOOD_DEV || (ps->flags & FLAG_BARRIER))
return EIO;
if ((r = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &val,
sizeof(val))) != OK)
return r;
return gen_set_wcache(ps, val);
case DIOCGETWC:
if (ps->state != STATE_GOOD_DEV || (ps->flags & FLAG_BARRIER))
return EIO;
if ((r = gen_get_wcache(ps, &val)) != OK)
return r;
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &val,
sizeof(val));
}
return ENOTTY;
}
/*===========================================================================*
* ahci_device *
*===========================================================================*/
static int ahci_device(devminor_t minor, device_id_t *id)
{
/* Map a minor device number to a device ID.
*/
struct port_state *ps;
struct device *dv;
if ((ps = ahci_map_minor(minor, &dv)) == NULL)
return ENXIO;
*id = ps->device;
return OK;
}
/*===========================================================================*
* ahci_get_port *
*===========================================================================*/
static struct port_state *ahci_get_port(devminor_t minor)
{
/* Get the port structure associated with the given minor device.
* Called only from worker threads, so the minor device is already
* guaranteed to map to a port.
*/
struct port_state *ps;
struct device *dv;
if ((ps = ahci_map_minor(minor, &dv)) == NULL)
panic("device mapping for minor %d disappeared", minor);
return ps;
}
/*===========================================================================*
* main *
*===========================================================================*/
int main(int argc, char **argv)
{
/* Driver task.
*/
env_setargs(argc, argv);
sef_local_startup();
blockdriver_mt_task(&ahci_dtab);
return 0;
}