Commit faed4c6f authored by M Chetan Kumar's avatar M Chetan Kumar Committed by David S. Miller
Browse files

net: iosm: shared memory protocol



1) Defines messaging protocol for handling Transfer Descriptor
   in both UL/DL direction.
2) Ring buffer management.

Signed-off-by: default avatarM Chetan Kumar <m.chetan.kumar@intel.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent be8c936e
Loading
Loading
Loading
Loading
+283 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2020-21 Intel Corporation.
 */

#include "iosm_ipc_imem.h"
#include "iosm_ipc_protocol.h"
#include "iosm_ipc_protocol_ops.h"
#include "iosm_ipc_pm.h"
#include "iosm_ipc_task_queue.h"

int ipc_protocol_tq_msg_send(struct iosm_protocol *ipc_protocol,
			     enum ipc_msg_prep_type msg_type,
			     union ipc_msg_prep_args *prep_args,
			     struct ipc_rsp *response)
{
	int index = ipc_protocol_msg_prep(ipc_protocol->imem, msg_type,
					  prep_args);

	/* Store reference towards caller specified response in response ring
	 * and signal CP
	 */
	if (index >= 0 && index < IPC_MEM_MSG_ENTRIES) {
		ipc_protocol->rsp_ring[index] = response;
		ipc_protocol_msg_hp_update(ipc_protocol->imem);
	}

	return index;
}

/* Callback for message send */
static int ipc_protocol_tq_msg_send_cb(struct iosm_imem *ipc_imem, int arg,
				       void *msg, size_t size)
{
	struct ipc_call_msg_send_args *send_args = msg;
	struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol;

	return ipc_protocol_tq_msg_send(ipc_protocol, send_args->msg_type,
					send_args->prep_args,
					send_args->response);
}

/* Remove reference to a response. This is typically used when a requestor timed
 * out and is no longer interested in the response.
 */
static int ipc_protocol_tq_msg_remove(struct iosm_imem *ipc_imem, int arg,
				      void *msg, size_t size)
{
	struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol;

	ipc_protocol->rsp_ring[arg] = NULL;
	return 0;
}

int ipc_protocol_msg_send(struct iosm_protocol *ipc_protocol,
			  enum ipc_msg_prep_type prep,
			  union ipc_msg_prep_args *prep_args)
{
	struct ipc_call_msg_send_args send_args;
	unsigned int exec_timeout;
	struct ipc_rsp response;
	int index;

	exec_timeout = (ipc_protocol_get_ap_exec_stage(ipc_protocol) ==
					IPC_MEM_EXEC_STAGE_RUN ?
				IPC_MSG_COMPLETE_RUN_DEFAULT_TIMEOUT :
				IPC_MSG_COMPLETE_BOOT_DEFAULT_TIMEOUT);

	/* Trap if called from non-preemptible context */
	might_sleep();

	response.status = IPC_MEM_MSG_CS_INVALID;
	init_completion(&response.completion);

	send_args.msg_type = prep;
	send_args.prep_args = prep_args;
	send_args.response = &response;

	/* Allocate and prepare message to be sent in tasklet context.
	 * A positive index returned form tasklet_call references the message
	 * in case it needs to be cancelled when there is a timeout.
	 */
	index = ipc_task_queue_send_task(ipc_protocol->imem,
					 ipc_protocol_tq_msg_send_cb, 0,
					 &send_args, 0, true);

	if (index < 0) {
		dev_err(ipc_protocol->dev, "msg %d failed", prep);
		return index;
	}

	/* Wait for the device to respond to the message */
	switch (wait_for_completion_timeout(&response.completion,
					    msecs_to_jiffies(exec_timeout))) {
	case 0:
		/* Timeout, there was no response from the device.
		 * Remove the reference to the local response completion
		 * object as we are no longer interested in the response.
		 */
		ipc_task_queue_send_task(ipc_protocol->imem,
					 ipc_protocol_tq_msg_remove, index,
					 NULL, 0, true);
		dev_err(ipc_protocol->dev, "msg timeout");
		ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT);
		break;
	default:
		/* We got a response in time; check completion status: */
		if (response.status != IPC_MEM_MSG_CS_SUCCESS) {
			dev_err(ipc_protocol->dev,
				"msg completion status error %d",
				response.status);
			return -EIO;
		}
	}

	return 0;
}

static int ipc_protocol_msg_send_host_sleep(struct iosm_protocol *ipc_protocol,
					    u32 state)
{
	union ipc_msg_prep_args prep_args = {
		.sleep.target = 0,
		.sleep.state = state,
	};

	return ipc_protocol_msg_send(ipc_protocol, IPC_MSG_PREP_SLEEP,
				     &prep_args);
}

void ipc_protocol_doorbell_trigger(struct iosm_protocol *ipc_protocol,
				   u32 identifier)
{
	ipc_pm_signal_hpda_doorbell(&ipc_protocol->pm, identifier, true);
}

bool ipc_protocol_pm_dev_sleep_handle(struct iosm_protocol *ipc_protocol)
{
	u32 ipc_status = ipc_protocol_get_ipc_status(ipc_protocol);
	u32 requested;

	if (ipc_status != IPC_MEM_DEVICE_IPC_RUNNING) {
		dev_err(ipc_protocol->dev,
			"irq ignored, CP IPC state is %d, should be RUNNING",
			ipc_status);

		/* Stop further processing. */
		return false;
	}

	/* Get a copy of the requested PM state by the device and the local
	 * device PM state.
	 */
	requested = ipc_protocol_pm_dev_get_sleep_notification(ipc_protocol);

	return ipc_pm_dev_slp_notification(&ipc_protocol->pm, requested);
}

static int ipc_protocol_tq_wakeup_dev_slp(struct iosm_imem *ipc_imem, int arg,
					  void *msg, size_t size)
{
	struct iosm_pm *ipc_pm = &ipc_imem->ipc_protocol->pm;

	/* Wakeup from device sleep if it is not ACTIVE */
	ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, true);

	ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, false);

	return 0;
}

void ipc_protocol_s2idle_sleep(struct iosm_protocol *ipc_protocol, bool sleep)
{
	ipc_pm_set_s2idle_sleep(&ipc_protocol->pm, sleep);
}

bool ipc_protocol_suspend(struct iosm_protocol *ipc_protocol)
{
	if (!ipc_pm_prepare_host_sleep(&ipc_protocol->pm))
		goto err;

	ipc_task_queue_send_task(ipc_protocol->imem,
				 ipc_protocol_tq_wakeup_dev_slp, 0, NULL, 0,
				 true);

	if (!ipc_pm_wait_for_device_active(&ipc_protocol->pm)) {
		ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT);
		goto err;
	}

	/* Send the sleep message for sync sys calls. */
	dev_dbg(ipc_protocol->dev, "send TARGET_HOST, ENTER_SLEEP");
	if (ipc_protocol_msg_send_host_sleep(ipc_protocol,
					     IPC_HOST_SLEEP_ENTER_SLEEP)) {
		/* Sending ENTER_SLEEP message failed, we are still active */
		ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE;
		goto err;
	}

	ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP;
	return true;
err:
	return false;
}

bool ipc_protocol_resume(struct iosm_protocol *ipc_protocol)
{
	if (!ipc_pm_prepare_host_active(&ipc_protocol->pm))
		return false;

	dev_dbg(ipc_protocol->dev, "send TARGET_HOST, EXIT_SLEEP");
	if (ipc_protocol_msg_send_host_sleep(ipc_protocol,
					     IPC_HOST_SLEEP_EXIT_SLEEP)) {
		ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP;
		return false;
	}

	ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE;

	return true;
}

struct iosm_protocol *ipc_protocol_init(struct iosm_imem *ipc_imem)
{
	struct iosm_protocol *ipc_protocol =
		kzalloc(sizeof(*ipc_protocol), GFP_KERNEL);
	struct ipc_protocol_context_info *p_ci;
	u64 addr;

	if (!ipc_protocol)
		return NULL;

	ipc_protocol->dev = ipc_imem->dev;
	ipc_protocol->pcie = ipc_imem->pcie;
	ipc_protocol->imem = ipc_imem;
	ipc_protocol->p_ap_shm = NULL;
	ipc_protocol->phy_ap_shm = 0;

	ipc_protocol->old_msg_tail = 0;

	ipc_protocol->p_ap_shm =
		pci_alloc_consistent(ipc_protocol->pcie->pci,
				     sizeof(*ipc_protocol->p_ap_shm),
				     &ipc_protocol->phy_ap_shm);

	if (!ipc_protocol->p_ap_shm) {
		dev_err(ipc_protocol->dev, "pci shm alloc error");
		kfree(ipc_protocol);
		return NULL;
	}

	/* Prepare the context info for CP. */
	addr = ipc_protocol->phy_ap_shm;
	p_ci = &ipc_protocol->p_ap_shm->ci;
	p_ci->device_info_addr =
		addr + offsetof(struct ipc_protocol_ap_shm, device_info);
	p_ci->head_array =
		addr + offsetof(struct ipc_protocol_ap_shm, head_array);
	p_ci->tail_array =
		addr + offsetof(struct ipc_protocol_ap_shm, tail_array);
	p_ci->msg_head = addr + offsetof(struct ipc_protocol_ap_shm, msg_head);
	p_ci->msg_tail = addr + offsetof(struct ipc_protocol_ap_shm, msg_tail);
	p_ci->msg_ring_addr =
		addr + offsetof(struct ipc_protocol_ap_shm, msg_ring);
	p_ci->msg_ring_entries = cpu_to_le16(IPC_MEM_MSG_ENTRIES);
	p_ci->msg_irq_vector = IPC_MSG_IRQ_VECTOR;
	p_ci->device_info_irq_vector = IPC_DEVICE_IRQ_VECTOR;

	ipc_mmio_set_contex_info_addr(ipc_imem->mmio, addr);

	ipc_pm_init(ipc_protocol);

	return ipc_protocol;
}

void ipc_protocol_deinit(struct iosm_protocol *proto)
{
	pci_free_consistent(proto->pcie->pci, sizeof(*proto->p_ap_shm),
			    proto->p_ap_shm, proto->phy_ap_shm);

	ipc_pm_deinit(proto);
	kfree(proto);
}
+237 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only
 *
 * Copyright (C) 2020-21 Intel Corporation.
 */

#ifndef IOSM_IPC_PROTOCOL_H
#define IOSM_IPC_PROTOCOL_H

#include "iosm_ipc_imem.h"
#include "iosm_ipc_pm.h"
#include "iosm_ipc_protocol_ops.h"

/* Trigger the doorbell interrupt on CP. */
#define IPC_DOORBELL_IRQ_HPDA 0
#define IPC_DOORBELL_IRQ_IPC 1
#define IPC_DOORBELL_IRQ_SLEEP 2

/* IRQ vector number. */
#define IPC_DEVICE_IRQ_VECTOR 0
#define IPC_MSG_IRQ_VECTOR 0
#define IPC_UL_PIPE_IRQ_VECTOR 0
#define IPC_DL_PIPE_IRQ_VECTOR 0

#define IPC_MEM_MSG_ENTRIES 128

/* Default time out for sending IPC messages like open pipe, close pipe etc.
 * during run mode.
 *
 * If the message interface lock to CP times out, the link to CP is broken.
 * mode : run mode (IPC_MEM_EXEC_STAGE_RUN)
 * unit : milliseconds
 */
#define IPC_MSG_COMPLETE_RUN_DEFAULT_TIMEOUT 500 /* 0.5 seconds */

/* Default time out for sending IPC messages like open pipe, close pipe etc.
 * during boot mode.
 *
 * If the message interface lock to CP times out, the link to CP is broken.
 * mode : boot mode
 * (IPC_MEM_EXEC_STAGE_BOOT | IPC_MEM_EXEC_STAGE_PSI | IPC_MEM_EXEC_STAGE_EBL)
 * unit : milliseconds
 */
#define IPC_MSG_COMPLETE_BOOT_DEFAULT_TIMEOUT 500 /* 0.5 seconds */

/**
 * struct ipc_protocol_context_info - Structure of the context info
 * @device_info_addr:		64 bit address to device info
 * @head_array:			64 bit address to head pointer arr for the pipes
 * @tail_array:			64 bit address to tail pointer arr for the pipes
 * @msg_head:			64 bit address to message head pointer
 * @msg_tail:			64 bit address to message tail pointer
 * @msg_ring_addr:		64 bit pointer to the message ring buffer
 * @msg_ring_entries:		This field provides the number of entries which
 *				the MR can hold
 * @msg_irq_vector:		This field provides the IRQ which shall be
 *				generated by the EP device when generating
 *				completion for Messages.
 * @device_info_irq_vector:	This field provides the IRQ which shall be
 *				generated by the EP dev after updating Dev. Info
 */
struct ipc_protocol_context_info {
	phys_addr_t device_info_addr;
	phys_addr_t head_array;
	phys_addr_t tail_array;
	phys_addr_t msg_head;
	phys_addr_t msg_tail;
	phys_addr_t msg_ring_addr;
	__le16 msg_ring_entries;
	u8 msg_irq_vector;
	u8 device_info_irq_vector;
};

/**
 * struct ipc_protocol_device_info - Structure for the device information
 * @execution_stage:		CP execution stage
 * @ipc_status:			IPC states
 * @device_sleep_notification:	Requested device pm states
 */
struct ipc_protocol_device_info {
	__le32 execution_stage;
	__le32 ipc_status;
	__le32 device_sleep_notification;
};

/**
 * struct ipc_protocol_ap_shm - Protocol Shared Memory Structure
 * @ci:			Context information struct
 * @device_info:	Device information struct
 * @msg_head:		Point to msg head
 * @head_array:		Array of head pointer
 * @msg_tail:		Point to msg tail
 * @tail_array:		Array of tail pointer
 * @msg_ring:		Circular buffers for the read/tail and write/head
 *			indeces.
 */
struct ipc_protocol_ap_shm {
	struct ipc_protocol_context_info ci;
	struct ipc_protocol_device_info device_info;
	__le32 msg_head;
	__le32 head_array[IPC_MEM_MAX_PIPES];
	__le32 msg_tail;
	__le32 tail_array[IPC_MEM_MAX_PIPES];
	union ipc_mem_msg_entry msg_ring[IPC_MEM_MSG_ENTRIES];
};

/**
 * struct iosm_protocol - Structure for IPC protocol.
 * @p_ap_shm:		Pointer to Protocol Shared Memory Structure
 * @pm:			Instance to struct iosm_pm
 * @pcie:		Pointer to struct iosm_pcie
 * @imem:		Pointer to struct iosm_imem
 * @rsp_ring:		Array of OS completion objects to be triggered once CP
 *			acknowledges a request in the message ring
 * @dev:		Pointer to device structure
 * @phy_ap_shm:		Physical/Mapped representation of the shared memory info
 * @old_msg_tail:	Old msg tail ptr, until AP has handled ACK's from CP
 */
struct iosm_protocol {
	struct ipc_protocol_ap_shm *p_ap_shm;
	struct iosm_pm pm;
	struct iosm_pcie *pcie;
	struct iosm_imem *imem;
	struct ipc_rsp *rsp_ring[IPC_MEM_MSG_ENTRIES];
	struct device *dev;
	phys_addr_t phy_ap_shm;
	u32 old_msg_tail;
};

/**
 * struct ipc_call_msg_send_args - Structure for message argument for
 *				   tasklet function.
 * @prep_args:		Arguments for message preparation function
 * @response:		Can be NULL if result can be ignored
 * @msg_type:		Message Type
 */
struct ipc_call_msg_send_args {
	union ipc_msg_prep_args *prep_args;
	struct ipc_rsp *response;
	enum ipc_msg_prep_type msg_type;
};

/**
 * ipc_protocol_tq_msg_send - prepare the msg and send to CP
 * @ipc_protocol:	Pointer to ipc_protocol instance
 * @msg_type:		Message type
 * @prep_args:		Message arguments
 * @response:		Pointer to a response object which has a
 *			completion object and return code.
 *
 * Returns: 0 on success and failure value on error
 */
int ipc_protocol_tq_msg_send(struct iosm_protocol *ipc_protocol,
			     enum ipc_msg_prep_type msg_type,
			     union ipc_msg_prep_args *prep_args,
			     struct ipc_rsp *response);

/**
 * ipc_protocol_msg_send - Send ipc control message to CP and wait for response
 * @ipc_protocol:	Pointer to ipc_protocol instance
 * @prep:		Message type
 * @prep_args:		Message arguments
 *
 * Returns: 0 on success and failure value on error
 */
int ipc_protocol_msg_send(struct iosm_protocol *ipc_protocol,
			  enum ipc_msg_prep_type prep,
			  union ipc_msg_prep_args *prep_args);

/**
 * ipc_protocol_suspend - Signal to CP that host wants to go to sleep (suspend).
 * @ipc_protocol:	Pointer to ipc_protocol instance
 *
 * Returns: true if host can suspend, false if suspend must be aborted.
 */
bool ipc_protocol_suspend(struct iosm_protocol *ipc_protocol);

/**
 * ipc_protocol_s2idle_sleep - Call PM function to set PM variables in s2idle
 *			       sleep/active case
 * @ipc_protocol:	Pointer to ipc_protocol instance
 * @sleep:		True for sleep/False for active
 */
void ipc_protocol_s2idle_sleep(struct iosm_protocol *ipc_protocol, bool sleep);

/**
 * ipc_protocol_resume - Signal to CP that host wants to resume operation.
 * @ipc_protocol:	Pointer to ipc_protocol instance
 *
 * Returns: true if host can resume, false if there is a problem.
 */
bool ipc_protocol_resume(struct iosm_protocol *ipc_protocol);

/**
 * ipc_protocol_pm_dev_sleep_handle - Handles the Device Sleep state change
 *				      notification.
 * @ipc_protocol:	Pointer to ipc_protocol instance.
 *
 * Returns: true if sleep notification handled, false otherwise.
 */
bool ipc_protocol_pm_dev_sleep_handle(struct iosm_protocol *ipc_protocol);

/**
 * ipc_protocol_doorbell_trigger - Wrapper for PM function which wake up the
 *				   device if it is in low power mode
 *				   and trigger a head pointer update interrupt.
 * @ipc_protocol:	Pointer to ipc_protocol instance.
 * @identifier:		Specifies what component triggered hpda
 *			update irq
 */
void ipc_protocol_doorbell_trigger(struct iosm_protocol *ipc_protocol,
				   u32 identifier);

/**
 * ipc_protocol_sleep_notification_string - Returns last Sleep Notification as
 *					    string.
 * @ipc_protocol:	Instance pointer of Protocol module.
 *
 * Returns: Pointer to string.
 */
const char *
ipc_protocol_sleep_notification_string(struct iosm_protocol *ipc_protocol);

/**
 * ipc_protocol_init - Allocates IPC protocol instance
 * @ipc_imem:		Pointer to iosm_imem structure
 *
 * Returns: Address of IPC  protocol instance on success & NULL on failure.
 */
struct iosm_protocol *ipc_protocol_init(struct iosm_imem *ipc_imem);

/**
 * ipc_protocol_deinit - Deallocates IPC protocol instance
 * @ipc_protocol:	pointer to the IPC protocol instance
 */
void ipc_protocol_deinit(struct iosm_protocol *ipc_protocol);

#endif