+/**************************************************************************/
+/* Locking */
+/**************************************************************************/
+/**
+ * The underlying function that controls access to the sata core
+ *
+ * @return non-zero indicates that you have acquired exclusive access to the
+ * sata core.
+ */
+static int __acquire_sata_core(
+ struct ata_host *ah,
+ int port_no,
+ oxnas_sata_isr_callback_t callback,
+ void *arg,
+ int may_sleep,
+ int timeout_jiffies,
+ int hw_access,
+ void *uid,
+ int locker_type)
+{
+ unsigned long end = jiffies + timeout_jiffies;
+ int acquired = 0;
+ unsigned long flags;
+ int timed_out = 0;
+ struct sata_oxnas_host_priv *hd;
+
+ DEFINE_WAIT(wait);
+
+ if (!ah)
+ return acquired;
+
+ hd = ah->private_data;
+
+ spin_lock_irqsave(&hd->core_lock, flags);
+
+ DPRINTK("Entered uid %p, port %d, h/w count %d, d count %d, "
+ "callback %p, hw_access %d, core_locked %d, "
+ "reentrant_port_no %d, isr_callback %p\n",
+ uid, port_no, hd->hw_lock_count, hd->direct_lock_count,
+ callback, hw_access, hd->core_locked, hd->reentrant_port_no,
+ hd->isr_callback);
+
+ while (!timed_out) {
+ if (hd->core_locked ||
+ (!hw_access && hd->scsi_nonblocking_attempts)) {
+ /* Can only allow access if from SCSI/SATA stack and if
+ * reentrant access is allowed and this access is to the
+ * same port for which the lock is current held
+ */
+ if (hw_access && (port_no == hd->reentrant_port_no)) {
+ BUG_ON(!hd->hw_lock_count);
+ ++(hd->hw_lock_count);
+
+ DPRINTK("Allow SCSI/SATA re-entrant access to "
+ "uid %p port %d\n", uid, port_no);
+ acquired = 1;
+ break;
+ } else if (!hw_access) {
+ if ((locker_type == SATA_READER) &&
+ (hd->current_locker_type == SATA_READER)) {
+ WARN(1,
+ "Already locked by reader, "
+ "uid %p, locker_uid %p, "
+ "port %d, h/w count %d, "
+ "d count %d, hw_access %d\n",
+ uid, hd->locker_uid, port_no,
+ hd->hw_lock_count,
+ hd->direct_lock_count,
+ hw_access);
+ goto check_uid;
+ }
+
+ if ((locker_type != SATA_READER) &&
+ (locker_type != SATA_WRITER)) {
+ goto wait_for_lock;
+ }
+
+check_uid:
+ WARN(uid == hd->locker_uid, "Attempt to lock "
+ "by locker type %d uid %p, already "
+ "locked by locker type %d with "
+ "locker_uid %p, port %d, "
+ "h/w count %d, d count %d, "
+ "hw_access %d\n", locker_type, uid,
+ hd->current_locker_type,
+ hd->locker_uid, port_no,
+ hd->hw_lock_count,
+ hd->direct_lock_count, hw_access);
+ }
+ } else {
+ WARN(hd->hw_lock_count || hd->direct_lock_count,
+ "Core unlocked but counts non-zero: uid %p, "
+ "locker_uid %p, port %d, h/w count %d, "
+ "d count %d, hw_access %d\n", uid,
+ hd->locker_uid, port_no, hd->hw_lock_count,
+ hd->direct_lock_count, hw_access);
+
+ BUG_ON(hd->current_locker_type != SATA_UNLOCKED);
+
+ WARN(hd->locker_uid, "Attempt to lock uid %p when "
+ "locker_uid %p is non-zero, port %d, "
+ "h/w count %d, d count %d, hw_access %d\n",
+ uid, hd->locker_uid, port_no, hd->hw_lock_count,
+ hd->direct_lock_count, hw_access);
+
+ if (!hw_access) {
+ /* Direct access attempting to acquire
+ * non-contented lock
+ */
+ /* Must have callback for direct access */
+ BUG_ON(!callback);
+ /* Sanity check lock state */
+ BUG_ON(hd->reentrant_port_no != -1);
+
+ hd->isr_callback = callback;
+ hd->isr_arg = arg;
+ ++(hd->direct_lock_count);
+
+ hd->current_locker_type = locker_type;
+ } else {
+ /* SCSI/SATA attempting to acquire
+ * non-contented lock
+ */
+ /* No callbacks for SCSI/SATA access */
+ BUG_ON(callback);
+ /* No callback args for SCSI/SATA access */
+ BUG_ON(arg);
+
+ /* Sanity check lock state */
+ BUG_ON(hd->isr_callback);
+ BUG_ON(hd->isr_arg);
+
+ ++(hd->hw_lock_count);
+ hd->reentrant_port_no = port_no;
+
+ hd->current_locker_type = SATA_SCSI_STACK;
+ }
+
+ hd->core_locked = 1;
+ hd->locker_uid = uid;
+ acquired = 1;
+ break;
+ }
+
+wait_for_lock:
+ if (!may_sleep) {
+ DPRINTK("Denying for uid %p locker_type %d, "
+ "hw_access %d, port %d, current_locker_type %d as "
+ "cannot sleep\n", uid, locker_type, hw_access, port_no,
+ hd->current_locker_type);
+
+ if (hw_access)
+ ++(hd->scsi_nonblocking_attempts);
+
+ break;
+ }
+
+ /* Core is locked and we're allowed to sleep, so wait to be
+ * awoken when the core is unlocked
+ */
+ for (;;) {
+ prepare_to_wait(hw_access ? &hd->scsi_wait_queue :
+ &hd->fast_wait_queue,
+ &wait, TASK_UNINTERRUPTIBLE);
+ if (!hd->core_locked &&
+ !(!hw_access && hd->scsi_nonblocking_attempts)) {
+ /* We're going to use variables that will have
+ * been changed by the waker prior to clearing
+ * core_locked so we need to ensure we see
+ * changes to all those variables
+ */
+ smp_rmb();
+ break;
+ }
+ if (time_after(jiffies, end)) {
+ printk(KERN_WARNING "__acquire_sata_core() "
+ "uid %p failing for port %d timed out, "
+ "locker_uid %p, h/w count %d, "
+ "d count %d, callback %p, hw_access %d, "
+ "core_locked %d, reentrant_port_no %d, "
+ "isr_callback %p, isr_arg %p\n", uid,
+ port_no, hd->locker_uid,
+ hd->hw_lock_count,
+ hd->direct_lock_count, callback,
+ hw_access, hd->core_locked,
+ hd->reentrant_port_no, hd->isr_callback,
+ hd->isr_arg);
+ timed_out = 1;
+ break;
+ }
+ spin_unlock_irqrestore(&hd->core_lock, flags);
+ if (!schedule_timeout(4*HZ)) {
+ printk(KERN_INFO "__acquire_sata_core() uid %p, "
+ "locker_uid %p, timed-out of "
+ "schedule(), checking overall timeout\n",
+ uid, hd->locker_uid);
+ }
+ spin_lock_irqsave(&hd->core_lock, flags);
+ }
+ finish_wait(hw_access ? &hd->scsi_wait_queue :
+ &hd->fast_wait_queue, &wait);
+ }
+
+ if (hw_access && acquired) {
+ if (hd->scsi_nonblocking_attempts)
+ hd->scsi_nonblocking_attempts = 0;
+
+ /* Wake any other SCSI/SATA waiters so they can get reentrant
+ * access to the same port if appropriate. This is because if
+ * the SATA core is locked by fast access, or SCSI/SATA access
+ * to other port, then can have >1 SCSI/SATA waiters on the wait
+ * list so want to give reentrant accessors a chance to get
+ * access ASAP
+ */
+ if (!list_empty(&hd->scsi_wait_queue.task_list))
+ wake_up(&hd->scsi_wait_queue);
+ }
+
+ DPRINTK("Leaving uid %p with acquired = %d, port %d, callback %p\n",
+ uid, acquired, port_no, callback);
+
+ spin_unlock_irqrestore(&hd->core_lock, flags);
+
+ return acquired;
+}
+
+int sata_core_has_fast_waiters(struct ata_host *ah)
+{
+ int has_waiters;
+ unsigned long flags;
+ struct sata_oxnas_host_priv *hd = ah->private_data;
+
+ spin_lock_irqsave(&hd->core_lock, flags);
+ has_waiters = !list_empty(&hd->fast_wait_queue.task_list);
+ spin_unlock_irqrestore(&hd->core_lock, flags);
+
+ return has_waiters;
+}
+EXPORT_SYMBOL(sata_core_has_fast_waiters);
+
+int sata_core_has_scsi_waiters(struct ata_host *ah)
+{
+ int has_waiters;
+ unsigned long flags;
+ struct sata_oxnas_host_priv *hd = ah->private_data;
+
+ spin_lock_irqsave(&hd->core_lock, flags);
+ has_waiters = hd->scsi_nonblocking_attempts ||
+ !list_empty(&hd->scsi_wait_queue.task_list);
+ spin_unlock_irqrestore(&hd->core_lock, flags);
+
+ return has_waiters;
+}
+EXPORT_SYMBOL(sata_core_has_scsi_waiters);
+
+/*
+ * ata_port operation to gain ownership of the SATA hardware prior to issuing
+ * a command against a SATA host. Allows any number of users of the port against
+ * which the lock was first acquired, thus enforcing that only one SATA core
+ * port may be operated on at once.
+ */
+static int sata_oxnas_acquire_hw(
+ struct ata_port *ap,
+ int may_sleep,
+ int timeout_jiffies)
+{
+ return __acquire_sata_core(ap->host, ap->port_no, NULL, 0, may_sleep,
+ timeout_jiffies, 1, (void *)HW_LOCKER_UID,
+ SATA_SCSI_STACK);
+}
+
+/*
+ * operation to release ownership of the SATA hardware
+ */
+static void sata_oxnas_release_hw(struct ata_port *ap)
+{
+ unsigned long flags;
+ int released = 0;
+ struct sata_oxnas_host_priv *hd = ap->host->private_data;
+
+ spin_lock_irqsave(&hd->core_lock, flags);
+
+ DPRINTK("Entered port_no = %d, h/w count %d, d count %d, "
+ "core locked = %d, reentrant_port_no = %d, isr_callback %p\n",
+ ap->port_no, hd->hw_lock_count, hd->direct_lock_count,
+ hd->core_locked, hd->reentrant_port_no, hd->isr_callback);
+
+ if (!hd->core_locked) {
+ /* Nobody holds the SATA lock */
+ printk(KERN_WARNING "Nobody holds SATA lock, port_no %d\n",
+ ap->port_no);
+ released = 1;
+ } else if (!hd->hw_lock_count) {
+ /* SCSI/SATA has released without holding the lock */
+ printk(KERN_WARNING "SCSI/SATA does not hold SATA lock, "
+ "port_no %d\n", ap->port_no);
+ } else {
+ /* Trap incorrect usage */
+ BUG_ON(hd->reentrant_port_no == -1);
+ BUG_ON(ap->port_no != hd->reentrant_port_no);
+ BUG_ON(hd->direct_lock_count);
+ BUG_ON(hd->current_locker_type != SATA_SCSI_STACK);
+
+ WARN(!hd->locker_uid || (hd->locker_uid != HW_LOCKER_UID),
+ "Invalid locker uid %p, h/w count %d, d count %d, "
+ "reentrant_port_no %d, core_locked %d, "
+ "isr_callback %p\n", hd->locker_uid, hd->hw_lock_count,
+ hd->direct_lock_count, hd->reentrant_port_no,
+ hd->core_locked, hd->isr_callback);
+
+ if (--(hd->hw_lock_count)) {
+ DPRINTK("Still nested port_no %d\n", ap->port_no);
+ } else {
+ DPRINTK("Release port_no %d\n", ap->port_no);
+ hd->reentrant_port_no = -1;
+ hd->isr_callback = NULL;
+ hd->current_locker_type = SATA_UNLOCKED;
+ hd->locker_uid = 0;
+ hd->core_locked = 0;
+ released = 1;
+ wake_up(!list_empty(&hd->scsi_wait_queue.task_list) ?
+ &hd->scsi_wait_queue :
+ &hd->fast_wait_queue);
+ }
+ }
+
+ DPRINTK("Leaving, port_no %d, count %d\n", ap->port_no,
+ hd->hw_lock_count);
+
+ spin_unlock_irqrestore(&hd->core_lock, flags);
+
+ /* CONFIG_SATA_OX820_DIRECT_HWRAID */
+ /* if (released)
+ ox820hwraid_restart_queue();
+ } */
+}
+