linux/drivers/staging/fieldbus/anybuss/host.c

// 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,
 * and wait for task completion:
 *
 *	process A	B	C	D
 *		|	|	|	|
 *		v	v	v	v
 *	|<-----	========================================
 *	|		|	   |		|
 *	|		v	   v		v-------<-------+
 *	|	+--------------------------------------+	|
 *	|	| power q     | mbox q    | area q     |	|
 *	|	|------------|------------|------------|	|
 *	|	| task       | task       | task       |	|
 *	|	| task       | task       | task       |	|
 *	|	| task wait  | task wait  | task wait  |	|
 *	|	+--------------------------------------+	|
 *	|		^	   ^		^		|
 *	|		|	   |		|		^
 *	|	+--------------------------------------+	|
 *	|	|	     queue thread	       |	|
 *	|	|--------------------------------------|	|
 *	|	| single-threaded:		       |	|
 *	|	| loop:				       |	|
 *	v	|   for each queue:		       |	|
 *	|	|     run task state machine	       |	|
 *	|	|     if task waiting:		       |	|
 *	|	|       leave on queue		       |	|
 *	|	|     if task done:		       |	|
 *	|	|       complete task, remove from q   |	|
 *	|	|   if software irq event bits set:    |	|
 *	|	|     notify userspace		       |	|
 *	|	|     post clear event bits task------>|>-------+
 *	|	|   wait for IND_AB changed event OR   |
 *	|	|            task added event	  OR   |
 *	|	|	     timeout		       |
 *	|	| end loop			       |
 *	|	+--------------------------------------+
 *	|	+		wake up		       +
 *	|	+--------------------------------------+
 *	|		^			^
 *	|		|			|
 *	+-------->-------			|
 *						|
 *		+--------------------------------------+
 *		|	interrupt service routine      |
 *		|--------------------------------------|
 *		| wake up queue thread on IND_AB change|
 *		+--------------------------------------+
 *
 * Note that the Anybus interrupt is dual-purpose:
 * - after a reset, triggered when the card becomes ready;
 * - during normal operation, triggered when AB_IND changes.
 * This is why the interrupt service routine doesn't just wake up the
 * queue thread, but also completes the card_boot completion.
 *
 * [1] https://www.anybus.com/docs/librariesprovider7/default-document-library/
 *	manuals-design-guides/hms-hmsi-27-275.pdf
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/atomic.h>
#include <linux/kthread.h>
#include <linux/kfifo.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/random.h>
#include <linux/kref.h>
#include <linux/of_address.h>

/* move to <linux/anybuss-*.h> when taking this out of staging */
#include "anybuss-client.h"
#include "anybuss-controller.h"

#define DPRAM_SIZE
#define MAX_MBOX_MSG_SZ
#define TIMEOUT
#define MAX_DATA_AREA_SZ
#define MAX_FBCTRL_AREA_SZ

#define REG_BOOTLOADER_V
#define REG_API_V
#define REG_FIELDBUS_V
#define REG_SERIAL_NO
#define REG_FIELDBUS_TYPE
#define REG_MODULE_SW_V
#define REG_IND_AB
#define REG_IND_AP
#define REG_EVENT_CAUSE
#define MBOX_IN_AREA
#define MBOX_OUT_AREA
#define DATA_IN_AREA
#define DATA_OUT_AREA
#define FBCTRL_AREA

#define EVENT_CAUSE_DC
#define EVENT_CAUSE_FBOF
#define EVENT_CAUSE_FBON

#define IND_AB_UPDATED
#define IND_AX_MIN
#define IND_AX_MOUT
#define IND_AX_IN
#define IND_AX_OUT
#define IND_AX_FBCTRL
#define IND_AP_LOCK
#define IND_AP_ACTION
#define IND_AX_EVNT
#define IND_AP_ABITS

#define INFO_TYPE_FB
#define INFO_TYPE_APP
#define INFO_COMMAND

#define OP_MODE_FBFC
#define OP_MODE_FBS
#define OP_MODE_CD

#define CMD_START_INIT
#define CMD_ANYBUS_INIT
#define CMD_END_INIT

/*
 * ---------------------------------------------------------------
 * Anybus mailbox messages - definitions
 * ---------------------------------------------------------------
 * note that we're depending on the layout of these structures being
 * exactly as advertised.
 */

struct anybus_mbox_hdr {};

struct msg_anybus_init {};

/* ------------- ref counted tasks ------------- */

struct ab_task;
ab_task_fn_t;
ab_done_fn_t;

struct area_priv {};

struct mbox_priv {};

struct ab_task {};

static struct ab_task *ab_task_create_get(struct kmem_cache *cache,
					  ab_task_fn_t task_fn)
{}

static void __ab_task_destroy(struct kref *refcount)
{}

static void ab_task_put(struct ab_task *t)
{}

static struct ab_task *__ab_task_get(struct ab_task *t)
{}

static void __ab_task_finish(struct ab_task *t, struct anybuss_host *cd)
{}

static void
ab_task_dequeue_finish_put(struct kfifo *q, struct anybuss_host *cd)
{}

static int
ab_task_enqueue(struct ab_task *t, struct kfifo *q, spinlock_t *slock,
		wait_queue_head_t *wq)
{}

static int
ab_task_enqueue_wait(struct ab_task *t, struct kfifo *q, spinlock_t *slock,
		     wait_queue_head_t *wq)
{}

/* ------------------------ anybus hardware ------------------------ */

struct anybuss_host {};

static void reset_assert(struct anybuss_host *cd)
{}

static void reset_deassert(struct anybuss_host *cd)
{}

static int test_dpram(struct regmap *regmap)
{}

static int read_ind_ab(struct regmap *regmap)
{}

static int write_ind_ap(struct regmap *regmap, unsigned int ind_ap)
{}

static irqreturn_t irq_handler(int irq, void *data)
{}

/* ------------------------ power on/off tasks --------------------- */

static int task_fn_power_off(struct anybuss_host *cd,
			     struct ab_task *t)
{}

static int task_fn_power_on_2(struct anybuss_host *cd,
			      struct ab_task *t)
{}

static int task_fn_power_on(struct anybuss_host *cd,
			    struct ab_task *t)
{}

int anybuss_set_power(struct anybuss_client *client, bool power_on)
{}
EXPORT_SYMBOL_GPL();

/* ---------------------------- area tasks ------------------------ */

static int task_fn_area_3(struct anybuss_host *cd, struct ab_task *t)
{}

static int task_fn_area_2(struct anybuss_host *cd, struct ab_task *t)
{}

static int task_fn_area(struct anybuss_host *cd, struct ab_task *t)
{}

static struct ab_task *
create_area_reader(struct kmem_cache *qcache, u16 flags, u16 addr,
		   size_t count)
{}

static struct ab_task *
create_area_writer(struct kmem_cache *qcache, u16 flags, u16 addr,
		   const void *buf, size_t count)
{}

static struct ab_task *
create_area_user_writer(struct kmem_cache *qcache, u16 flags, u16 addr,
			const void __user *buf, size_t count)
{}

static bool area_range_ok(u16 addr, size_t count, u16 area_start,
			  size_t area_sz)
{}

/* -------------------------- mailbox tasks ----------------------- */

static int task_fn_mbox_2(struct anybuss_host *cd, struct ab_task *t)
{}

static int task_fn_mbox(struct anybuss_host *cd, struct ab_task *t)
{}

static void log_invalid_other(struct device *dev,
			      struct anybus_mbox_hdr *hdr)
{}

static const char * const EMSGS[] =;

static int mbox_cmd_err(struct device *dev, struct mbox_priv *mpriv)
{}

static int _anybus_mbox_cmd(struct anybuss_host *cd,
			    u16 cmd_num, bool is_fb_cmd,
				const void *msg_out, size_t msg_out_sz,
				void *msg_in, size_t msg_in_sz,
				const void *ext, size_t ext_sz)
{}

/* ------------------------ anybus queues ------------------------ */

static void process_q(struct anybuss_host *cd, struct kfifo *q)
{}

static bool qs_have_work(struct kfifo *qs, size_t num)
{}

static void process_qs(struct anybuss_host *cd)
{}

static void softint_ack(struct anybuss_host *cd)
{}

static void process_softint(struct anybuss_host *cd)
{}

static int qthread_fn(void *data)
{}

/* ------------------------ anybus exports ------------------------ */

int anybuss_start_init(struct anybuss_client *client,
		       const struct anybuss_memcfg *cfg)
{}
EXPORT_SYMBOL_GPL();

int anybuss_finish_init(struct anybuss_client *client)
{}
EXPORT_SYMBOL_GPL();

int anybuss_read_fbctrl(struct anybuss_client *client, u16 addr,
			void *buf, size_t count)
{}
EXPORT_SYMBOL_GPL();

int anybuss_write_input(struct anybuss_client *client,
			const char __user *buf, size_t size,
				loff_t *offset)
{}
EXPORT_SYMBOL_GPL();

int anybuss_read_output(struct anybuss_client *client,
			char __user *buf, size_t size,
				loff_t *offset)
{}
EXPORT_SYMBOL_GPL();

int anybuss_send_msg(struct anybuss_client *client, u16 cmd_num,
		     const void *buf, size_t count)
{}
EXPORT_SYMBOL_GPL();

int anybuss_send_ext(struct anybuss_client *client, u16 cmd_num,
		     const void *buf, size_t count)
{}
EXPORT_SYMBOL_GPL();

int anybuss_recv_msg(struct anybuss_client *client, u16 cmd_num,
		     void *buf, size_t count)
{}
EXPORT_SYMBOL_GPL();

/* ------------------------ bus functions ------------------------ */

static int anybus_bus_match(struct device *dev,
			    const struct device_driver *drv)
{}

static int anybus_bus_probe(struct device *dev)
{}

static void anybus_bus_remove(struct device *dev)
{}

static const struct bus_type anybus_bus =;

int anybuss_client_driver_register(struct anybuss_client_driver *drv)
{}
EXPORT_SYMBOL_GPL();

void anybuss_client_driver_unregister(struct anybuss_client_driver *drv)
{}
EXPORT_SYMBOL_GPL();

static void client_device_release(struct device *dev)
{}

static int taskq_alloc(struct device *dev, struct kfifo *q)
{}

static int anybus_of_get_host_idx(struct device_node *np)
{}

static struct device_node *
anybus_of_find_child_device(struct device *dev, int host_idx)
{}

struct anybuss_host * __must_check
anybuss_host_common_probe(struct device *dev,
			  const struct anybuss_ops *ops)
{}
EXPORT_SYMBOL_GPL();

void anybuss_host_common_remove(struct anybuss_host *host)
{}
EXPORT_SYMBOL_GPL();

static void host_release(void *res)
{}

struct anybuss_host * __must_check
devm_anybuss_host_common_probe(struct device *dev,
			       const struct anybuss_ops *ops)
{}
EXPORT_SYMBOL_GPL();

static int __init anybus_init(void)
{}
module_init();

static void __exit anybus_exit(void)
{}
module_exit(anybus_exit);

MODULE_DESCRIPTION();
MODULE_AUTHOR();
MODULE_LICENSE();