/*
 * include/vservices/service.h
 *
 * Copyright (c) 2012-2018 General Dynamics
 * Copyright (c) 2014 Open Kernel Labs, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This file defines the driver and device types for vServices client and
 * server drivers. These are generally defined by generated protocol-layer
 * code. However, they can also be defined directly by applications that
 * don't require protocol generation.
 */

#ifndef _VSERVICE_SERVICE_H_
#define _VSERVICE_SERVICE_H_

#include <linux/version.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/wait.h>
#include <linux/err.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 38)
#include <asm/atomic.h>
#else
#include <linux/atomic.h>
#endif

#include <vservices/transport.h>
#include <vservices/session.h>
#include <vservices/types.h>

struct vs_mbuf;

/**
 * struct vs_service_driver - Virtual service driver structure
 * @protocol: Protocol name for this driver
 * @is_server: True if this is a server driver, false if it is a client driver
 * @rx_atomic: If set to false then the receive message handlers are run from
 *	     workqueue context and are allowed to sleep. If set to true
 *	     the message handlers are run from tasklet context and may not
 *	     sleep. For this purpose, tx_ready is considered a receive
 *	     message handler.
 * @tx_atomic: If this is set to true along with rx_atomic, the driver is
 *	allowed to send messages from softirq contexts other than the receive
 *	message handlers, after calling vs_service_state_lock_bh. Otherwise,
 *	messages may only be sent from the receive message handlers, or from
 *	task context after calling vs_service_state_lock.
 * @probe: Probe function for this service
 * @remove: Remove function for this service
 * --- Callbacks ---
 * @receive: Message handler function for this service
 * @notify: Incoming notification handler function for this service
 * @start: Callback which is run when this service is started
 * @reset: Callback which is run when this service is reset
 * @tx_ready: Callback which is run when the service has dropped below its
 *	    send quota
 * --- Resource requirements (valid for server only) ---
 * @in_quota_min: minimum number of input messages for protocol functionality
 * @in_quota_best: suggested number of input messages
 * @out_quota_min: minimum number of output messages for protocol functionality
 * @out_quota_best: suggested number of output messages
 * @in_notify_count: number of input notification bits used
 * @out_notify_count: number of output notification bits used
 * --- Internal ---
 * @driver: Linux device model driver structure
 *
 * The callback functions for a virtual service driver are all called from
 * the virtual service device's work queue.
 */
struct vs_service_driver {
	const char *protocol;
	bool is_server;
	bool rx_atomic, tx_atomic;

	int (*probe)(struct vs_service_device *service);
	int (*remove)(struct vs_service_device *service);

	int (*receive)(struct vs_service_device *service,
		struct vs_mbuf *mbuf);
	void (*notify)(struct vs_service_device *service, u32 flags);

	void (*start)(struct vs_service_device *service);
	void (*reset)(struct vs_service_device *service);

	int (*tx_ready)(struct vs_service_device *service);

	unsigned in_quota_min;
	unsigned in_quota_best;
	unsigned out_quota_min;
	unsigned out_quota_best;
	unsigned in_notify_count;
	unsigned out_notify_count;

	struct device_driver driver;
};

#define to_vs_service_driver(d) \
	container_of(d, struct vs_service_driver, driver)

/* The vServices server/client bus types */
extern struct bus_type vs_client_bus_type;
extern struct bus_type vs_server_bus_type;

/**
 * struct vs_service_stats - Virtual service statistics
 * @over_quota_time: Internal counter for tracking over quota time.
 * @sent_mbufs: Total number of message buffers sent.
 * @sent_bytes: Total bytes sent.
 * @send_failures: Total number of send failures.
 * @recv_mbufs: Total number of message buffers received.
 * @recv_bytes: Total number of bytes recevied.
 * @recv_failures: Total number of receive failures.
 * @nr_over_quota: Number of times an mbuf allocation has failed because the
 *                 service is over quota.
 * @nr_tx_ready: Number of times the service has run its tx_ready handler
 * @over_quota_time_total: The total amount of time in milli-seconds that the
 *                         service has spent over quota. Measured as the time
 *                         between exceeding quota in mbuf allocation and
 *                         running the tx_ready handler.
 * @over_quota_time_avg: The average amount of time in milli-seconds that the
 *                       service is spending in the over quota state.
 */
struct vs_service_stats {
	unsigned long	over_quota_time;

	atomic_t        sent_mbufs;
	atomic_t        sent_bytes;
	atomic_t	send_failures;
	atomic_t        recv_mbufs;
	atomic_t        recv_bytes;
	atomic_t	recv_failures;
	atomic_t        nr_over_quota;
	atomic_t        nr_tx_ready;
	atomic_t        over_quota_time_total;
	atomic_t        over_quota_time_avg;
};

/**
 * struct vs_service_device - Virtual service device
 * @id: Unique ID (to the session) for this service
 * @name: Service name
 * @sysfs_name: The sysfs name for the service
 * @protocol: Service protocol name
 * @is_server: True if this device is server, false if it is a client
 * @owner: service responsible for managing this service. This must be
 *     on the same session, and is NULL iff this is the core service.
 *     It must not be a service whose driver has tx_atomic set.
 * @lock_subclass: the number of generations of owners between this service
 *     and the core service; 0 for the core service, 1 for anything directly
 *     created by it, and so on. This is only used for verifying lock
 *     ordering (when lockdep is enabled), hence the name.
 * @ready_lock: mutex protecting readiness, disable_count and driver_probed.
 *     This depends on the state_mutex of the service's owner, if any. Acquire
 *     it using mutex_lock_nested(ready_lock, lock_subclass).
 * @readiness: Service's readiness state, owned by session layer.
 * @disable_count: Number of times the service has been disabled without
 *     a matching enable.
 * @driver_probed: True if a driver has been probed (and not removed)
 * @work_queue: Work queue for this service's task-context work.
 * @rx_tasklet: Tasklet for handling incoming messages. This is only used
 *     if the service driver has rx_atomic set to true. Otherwise
 *     incoming messages are handled on the workqueue by rx_work.
 * @rx_work: Work structure for handling incoming messages. This is only
 *     used if the service driver has rx_atomic set to false.
 * @rx_lock: Spinlock which protects access to rx_queue and tx_ready
 * @rx_queue: Queue of incoming messages
 * @tx_ready: Flag indicating that a tx_ready event is pending
 * @tx_batching: Flag indicating that outgoing messages are being batched
 * @state_spinlock: spinlock used to protect the service state if the
 *     service driver has tx_atomic (and rx_atomic) set to true. This
 *     depends on the service's ready_lock. Acquire it only by
 *     calling vs_service_state_lock_bh().
 * @state_mutex: mutex used to protect the service state if the service
 *     driver has tx_atomic set to false. This depends on the service's
 *     ready_lock, and if rx_atomic is true, the rx_tasklet must be
 *     disabled while it is held. Acquire it only by calling
 *     vs_service_state_lock().
 * @state_spinlock_used: Flag to check if the state spinlock has been acquired.
 * @state_mutex_used: Flag to check if the state mutex has been acquired.
 * @reset_work: Work to reset the service after a driver fails
 * @pending_reset: Set if reset_work has been queued and not completed.
 * @ready_work: Work to make service ready after a throttling delay
 * @cooloff_work: Work for cooling off reset throttling after the reset
 * throttling limit was hit
 * @cleanup_work: Work for cleaning up and freeing the service structure
 * @last_reset: Time in jiffies at which this service last reset
 * @last_reset_request: Time in jiffies the last reset request for this
 *     service occurred at
 * @last_ready: Time in jiffies at which this service last became ready
 * @reset_delay: Time in jiffies that the next throttled reset will be
 *     delayed for. A value of zero means that reset throttling is not in
 *     effect.
 * @is_over_quota: Internal flag for whether the service is over quota. This
 *                 flag is only used for stats accounting.
 * @quota_wq: waitqueue that is woken whenever the available send quota
 *            increases.
 * @notify_send_bits: The number of bits allocated for outgoing notifications.
 * @notify_send_offset: The first bit allocated for outgoing notifications.
 * @notify_recv_bits: The number of bits allocated for incoming notifications.
 * @notify_recv_offset: The first bit allocated for incoming notifications.
 * @send_quota: The maximum number of outgoing messages.
 * @recv_quota: The maximum number of incoming messages.
 * @in_quota_set: For servers, the number of client->server messages
 *     requested during system configuration (sysfs or environment).
 * @out_quota_set: For servers, the number of server->client messages
 *     requested during system configuration (sysfs or environment).
 * @dev: Linux device model device structure
 * @stats: Service statistics
 */
struct vs_service_device {
	vs_service_id_t id;
	char *name;
	char *sysfs_name;
	char *protocol;
	bool is_server;

	struct vs_service_device *owner;
	unsigned lock_subclass;

	struct mutex ready_lock;
	unsigned readiness;
	int disable_count;
	bool driver_probed;

	struct workqueue_struct *work_queue;

	struct tasklet_struct rx_tasklet;
	struct work_struct rx_work;

	spinlock_t rx_lock;
	struct list_head rx_queue;
	bool tx_ready, tx_batching;

	spinlock_t state_spinlock;
	struct mutex state_mutex;

	struct work_struct reset_work;
	bool pending_reset;
	struct delayed_work ready_work;
	struct delayed_work cooloff_work;
	struct work_struct cleanup_work;

	unsigned long last_reset;
	unsigned long last_reset_request;
	unsigned long last_ready;
	unsigned long reset_delay;

	atomic_t is_over_quota;
	wait_queue_head_t quota_wq;

	unsigned notify_send_bits;
	unsigned notify_send_offset;
	unsigned notify_recv_bits;
	unsigned notify_recv_offset;
	unsigned send_quota;
	unsigned recv_quota;

	unsigned in_quota_set;
	unsigned out_quota_set;

	void *transport_priv;

	struct device dev;
	struct vs_service_stats stats;

#ifdef CONFIG_VSERVICES_LOCK_DEBUG
	bool state_spinlock_used;
	bool state_mutex_used;
#endif
};

#define to_vs_service_device(d) container_of(d, struct vs_service_device, dev)

/**
 * vs_service_get_session - Return the session for a service
 * @service: Service to get the session for
 */
static inline struct vs_session_device *
vs_service_get_session(struct vs_service_device *service)
{
	return to_vs_session_device(service->dev.parent);
}

/**
 * vs_service_send - Send a message from a service
 * @service: Service to send the message from
 * @mbuf: Message buffer to send
 */
static inline int
vs_service_send(struct vs_service_device *service, struct vs_mbuf *mbuf)
{
	struct vs_session_device *session = vs_service_get_session(service);
	const struct vs_transport_vtable *vt = session->transport->vt;
	const unsigned long flags =
		service->tx_batching ?  VS_TRANSPORT_SEND_FLAGS_MORE : 0;
	size_t msg_size = vt->mbuf_size(mbuf);
	int err;

	err = vt->send(session->transport, service, mbuf, flags);
	if (!err) {
		atomic_inc(&service->stats.sent_mbufs);
		atomic_add(msg_size, &service->stats.sent_bytes);
	} else {
		atomic_inc(&service->stats.send_failures);
	}

	return err;
}

/**
 * vs_service_alloc_mbuf - Allocate a message buffer for a service
 * @service: Service to allocate the buffer for
 * @size: Size of the data buffer to allocate
 * @flags: Flags to pass to the buffer allocation
 */
static inline struct vs_mbuf *
vs_service_alloc_mbuf(struct vs_service_device *service, size_t size,
		gfp_t flags)
{
	struct vs_session_device *session = vs_service_get_session(service);
	struct vs_mbuf *mbuf;

	mbuf = session->transport->vt->alloc_mbuf(session->transport,
			service, size, flags);
	if (IS_ERR(mbuf) && PTR_ERR(mbuf) == -ENOBUFS) {
		/* Over quota accounting */
		if (atomic_cmpxchg(&service->is_over_quota, 0, 1) == 0) {
			service->stats.over_quota_time = jiffies;
			atomic_inc(&service->stats.nr_over_quota);
		}
	}

	/*
	 * The transport drivers should return either a valid message buffer
	 * pointer or an ERR_PTR value. Warn here if a transport driver is
	 * returning NULL on message buffer allocation failure.
	 */
	if (WARN_ON_ONCE(!mbuf))
		return ERR_PTR(-ENOMEM);

	return mbuf;
}

/**
 * vs_service_free_mbuf - Deallocate a message buffer for a service
 * @service: Service the message buffer was allocated for
 * @mbuf: Message buffer to deallocate
 */
static inline void
vs_service_free_mbuf(struct vs_service_device *service, struct vs_mbuf *mbuf)
{
	struct vs_session_device *session = vs_service_get_session(service);

	session->transport->vt->free_mbuf(session->transport, service, mbuf);
}

/**
 * vs_service_notify - Send a notification from a service
 * @service: Service to send the notification from
 * @flags: Notification bits to send
 */
static inline int
vs_service_notify(struct vs_service_device *service, u32 flags)
{
	struct vs_session_device *session = vs_service_get_session(service);

	return session->transport->vt->notify(session->transport,
			service, flags);
}

/**
 * vs_service_has_atomic_rx - Return whether or not a service's receive
 * message handler runs in atomic context. This function should only be
 * called for services which are bound to a driver.
 *
 * @service: Service to check
 */
static inline bool
vs_service_has_atomic_rx(struct vs_service_device *service)
{
	if (WARN_ON(!service->dev.driver))
		return false;

	return to_vs_service_driver(service->dev.driver)->rx_atomic;
}

/**
 * vs_session_max_mbuf_size - Return the maximum allocation size of a message
 * buffer.
 * @service: The service to check
 */
static inline size_t
vs_service_max_mbuf_size(struct vs_service_device *service)
{
	struct vs_session_device *session = vs_service_get_session(service);

	return session->transport->vt->max_mbuf_size(session->transport);
}

/**
 * vs_service_send_mbufs_available - Return the number of mbufs which can be
 * allocated for sending before going over quota.
 * @service: The service to check
 */
static inline ssize_t
vs_service_send_mbufs_available(struct vs_service_device *service)
{
	struct vs_session_device *session = vs_service_get_session(service);

	return session->transport->vt->service_send_avail(session->transport,
			service);
}

/**
 * vs_service_has_atomic_tx - Return whether or not a service is allowed to
 * transmit from atomic context (other than its receive message handler).
 * This function should only be called for services which are bound to a
 * driver.
 *
 * @service: Service to check
 */
static inline bool
vs_service_has_atomic_tx(struct vs_service_device *service)
{
	if (WARN_ON(!service->dev.driver))
		return false;

	return to_vs_service_driver(service->dev.driver)->tx_atomic;
}

/**
 * vs_service_state_lock - Acquire a lock allowing service state operations
 * from external task contexts.
 *
 * @service: Service to lock.
 *
 * This must be used to protect any service state accesses that occur in task
 * contexts outside of a callback from the vservices protocol layer. It must
 * not be called from a protocol layer callback, nor from atomic context.
 *
 * If this service's state is also accessed from softirq contexts other than
 * vservices protocol layer callbacks, use vs_service_state_lock_bh instead,
 * and set the driver's tx_atomic flag.
 *
 * If this is called from outside the service's workqueue, the calling driver
 * must provide its own guarantee that it has not been detached from the
 * service. If that is not possible, use vs_state_lock_safe().
 */
static inline void
vs_service_state_lock(struct vs_service_device *service)
__acquires(service)
{
#ifdef CONFIG_VSERVICES_LOCK_DEBUG
	WARN_ON_ONCE(vs_service_has_atomic_tx(service));
#endif

	mutex_lock_nested(&service->state_mutex, service->lock_subclass);

#ifdef CONFIG_VSERVICES_LOCK_DEBUG
	if (WARN_ON_ONCE(service->state_spinlock_used))
		dev_err(&service->dev, "Service is using both the state spinlock and mutex - Fix your driver\n");
	service->state_mutex_used = true;
#endif

	if (vs_service_has_atomic_rx(service))
		tasklet_disable(&service->rx_tasklet);

	__acquire(service);
}

/**
 * vs_service_state_unlock - Release the lock acquired by vs_service_state_lock.
 *
 * @service: Service to unlock.
 */
static inline void
vs_service_state_unlock(struct vs_service_device *service)
__releases(service)
{
	__release(service);

	mutex_unlock(&service->state_mutex);

	if (vs_service_has_atomic_rx(service)) {
		tasklet_enable(&service->rx_tasklet);

		/* Kick the tasklet if there is RX work to do */
		if (!list_empty(&service->rx_queue))
			tasklet_schedule(&service->rx_tasklet);
	}
}

/**
 * vs_service_state_lock_bh - Acquire a lock allowing service state operations
 * from external task or softirq contexts.
 *
 * @service: Service to lock.
 *
 * This is an alternative to vs_service_state_lock for drivers that receive
 * messages in atomic context (i.e. have their rx_atomic flag set), *and* must
 * transmit messages from softirq contexts other than their own message
 * receive and tx_ready callbacks. Such drivers must set their tx_atomic
 * flag, so generated protocol drivers perform correct locking.
 *
 * This should replace all calls to vs_service_state_lock for services that
 * need it. Do not use both locking functions in one service driver.
 *
 * The calling driver must provide its own guarantee that it has not been
 * detached from the service. If that is not possible, use
 * vs_state_lock_safe_bh().
 */
static inline void
vs_service_state_lock_bh(struct vs_service_device *service)
__acquires(service)
__acquires(&service->state_spinlock)
{
#ifdef CONFIG_VSERVICES_LOCK_DEBUG
	WARN_ON_ONCE(!vs_service_has_atomic_rx(service));
	WARN_ON_ONCE(!vs_service_has_atomic_tx(service));
#endif

#ifdef CONFIG_SMP
	/* Not necessary on UP because it's implied by spin_lock_bh(). */
	tasklet_disable(&service->rx_tasklet);
#endif

	spin_lock_bh(&service->state_spinlock);

#ifdef CONFIG_VSERVICES_LOCK_DEBUG
	if (WARN_ON_ONCE(service->state_mutex_used))
		dev_err(&service->dev, "Service is using both the state spinlock and mutex - Fix your driver\n");
	service->state_spinlock_used = true;
#endif

	__acquire(service);
}

/**
 * vs_service_state_unlock_bh - Release the lock acquired by
 * vs_service_state_lock_bh.
 *
 * @service: Service to unlock.
 */
static inline void
vs_service_state_unlock_bh(struct vs_service_device *service)
__releases(service)
__releases(&service->state_spinlock)
{
	__release(service);

	spin_unlock_bh(&service->state_spinlock);

#ifdef CONFIG_SMP
	tasklet_enable(&service->rx_tasklet);
#endif
}

/* Convenience macros for locking a state structure rather than a service. */
#define vs_state_lock(state) vs_service_state_lock((state)->service)
#define vs_state_unlock(state) vs_service_state_unlock((state)->service)
#define vs_state_lock_bh(state) vs_service_state_lock_bh((state)->service)
#define vs_state_unlock_bh(state) vs_service_state_unlock_bh((state)->service)

/**
 * vs_state_lock_safe[_bh] - Aqcuire a lock for a state structure's service,
 * when the service may have been detached from the state.
 *
 * This is useful for blocking operations that can't easily be terminated
 * before returning from the service reset handler, such as file I/O. To use
 * this, the state structure should be reference-counted rather than freed in
 * the release callback, and the driver should retain its own reference to the
 * service until the state structure is freed.
 *
 * This macro acquires the lock and returns true if the state has not been
 * detached from the service. Otherwise, it returns false.
 *
 * Note that the _bh variant cannot be used from atomic context, because it
 * acquires a mutex.
 */
#define __vs_state_lock_safe(_state, _lock, _unlock) ({ \
	bool __ok = true;						\
	typeof(_state) __state = (_state);				\
	struct vs_service_device *__service = __state->service;		\
	mutex_lock_nested(&__service->ready_lock,			\
			__service->lock_subclass);			\
	__ok = !READ_ONCE(__state->released);				\
	if (__ok) {							\
		_lock(__state);						\
		__ok = !READ_ONCE(__state->released);			\
		if (!__ok)						\
			_unlock(__state);				\
	}								\
	mutex_unlock(&__service->ready_lock);				\
	__ok;								\
})
#define vs_state_lock_safe(_state) \
	__vs_state_lock_safe((_state), vs_state_lock, vs_state_unlock)
#define vs_state_lock_safe_bh(_state) \
	__vs_state_lock_safe((_state), vs_state_lock_bh, vs_state_unlock_bh)

/**
 * vs_get_service - Get a reference to a service.
 * @service: Service to get a reference to.
 */
static inline struct vs_service_device *
vs_get_service(struct vs_service_device *service)
{
	if (service)
		get_device(&service->dev);
	return service;
}

/**
 * vs_put_service - Put a reference to a service.
 * @service: The service to put the reference to.
 */
static inline void
vs_put_service(struct vs_service_device *service)
{
	put_device(&service->dev);
}

extern int vs_service_reset(struct vs_service_device *service,
		struct vs_service_device *caller);
extern void vs_service_reset_nosync(struct vs_service_device *service);

/**
 * vs_service_send_batch_start - Start a batch of outgoing messages
 * @service: The service that is starting a batch
 * @flush: Finish any previously started batch (if false, then duplicate
 * calls to this function have no effect)
 */
static inline void
vs_service_send_batch_start(struct vs_service_device *service, bool flush)
{
	if (flush && service->tx_batching) {
		struct vs_session_device *session =
			vs_service_get_session(service);
		const struct vs_transport_vtable *vt = session->transport->vt;
		if (vt->flush)
			vt->flush(session->transport, service);
	} else {
		service->tx_batching = true;
	}
}

/**
 * vs_service_send_batch_end - End a batch of outgoing messages
 * @service: The service that is ending a batch
 * @flush: Start sending the batch immediately (if false, the batch will
 * be flushed when the next message is sent)
 */
static inline void
vs_service_send_batch_end(struct vs_service_device *service, bool flush)
{
	service->tx_batching = false;
	if (flush) {
		struct vs_session_device *session =
			vs_service_get_session(service);
		const struct vs_transport_vtable *vt = session->transport->vt;
		if (vt->flush)
			vt->flush(session->transport, service);
	}
}


#endif /* _VSERVICE_SERVICE_H_ */
