// SPDX-License-Identifier: GPL-2.0
/*
* HMS Anybus-S Host Driver
*
* Copyright (C) 2018 Arcx Inc
*/
/*
* Architecture Overview
* =====================
* This driver (running on the CPU/SoC) and the Anybus-S card communicate
* by reading and writing data to/from the Anybus-S Dual-Port RAM (dpram).
* This is memory connected to both the SoC and Anybus-S card, which both sides
* can access freely and concurrently.
*
* Synchronization happens by means of two registers located in the dpram:
* IND_AB: written exclusively by the Anybus card; and
* IND_AP: written exclusively by this driver.
*
* Communication happens using one of the following mechanisms:
* 1. reserve, read/write, release dpram memory areas:
* using an IND_AB/IND_AP protocol, the driver is able to reserve certain
* memory areas. no dpram memory can be read or written except if reserved.
* (with a few limited exceptions)
* 2. send and receive data structures via a shared mailbox:
* using an IND_AB/IND_AP protocol, the driver and Anybus card are able to
* exchange commands and responses using a shared mailbox.
* 3. receive software interrupts:
* using an IND_AB/IND_AP protocol, the Anybus card is able to notify the
* driver of certain events such as: bus online/offline, data available.
* note that software interrupt event bits are located in a memory area
* which must be reserved before it can be accessed.
*
* The manual[1] is silent on whether these mechanisms can happen concurrently,
* or how they should be synchronized. However, section 13 (Driver Example)
* provides the following suggestion for developing a driver:
* a) an interrupt handler which updates global variables;
* b) a continuously-running task handling area requests (1 above)
* c) a continuously-running task handling mailbox requests (2 above)
* The example conspicuously leaves out software interrupts (3 above), which
* is the thorniest issue to get right (see below).
*
* The naive, straightforward way to implement this would be:
* - create an isr which updates shared variables;
* - create a work_struct which handles software interrupts on a queue;
* - create a function which does reserve/update/unlock in a loop;
* - create a function which does mailbox send/receive in a loop;
* - call the above functions from the driver's read/write/ioctl;
* - synchronize using mutexes/spinlocks:
* + only one area request at a time
* + only one mailbox request at a time
* + protect AB_IND, AB_IND against data hazards (e.g. read-after-write)
*
* Unfortunately, the presence of the software interrupt causes subtle yet
* considerable synchronization issues; especially problematic is the
* requirement to reserve/release the area which contains the status bits.
*
* The driver architecture presented here sidesteps these synchronization issues
* by accessing the dpram from a single kernel thread only. User-space throws
* "tasks" (i.e. 1, 2 above) into a task queue, waits for their completion,
* and the kernel thread runs them to completion.
*
* Each task has a task_function, which is called/run by the queue thread.
* That function communicates with the Anybus card, and returns either
* 0 (OK), a negative error code (error), or -EINPROGRESS (waiting).
* On OK or error, the queue thread completes and dequeues the task,
* which also releases the user space thread which may still be waiting for it.
* On -EINPROGRESS (waiting), the queue thread will leave the task on the queue,
* and revisit (call again) whenever an interrupt event comes in.
*
* Each task has a state machine, which is run by calling its task_function.
* It ensures that the task will go through its various stages over time,
* returning -EINPROGRESS if it wants to wait for an event to happen.
*
* Note that according to the manual's driver example, the following operations
* may run independent of each other:
* - area reserve/read/write/release (point 1 above)
* - mailbox operations (point 2 above)
* - switching power on/off
*
* To allow them to run independently, each operation class gets its own queue.
*
* Userspace processes A, B, C, D post tasks to the appropriate queue,