/** * ish_set_host_ready() - reconfig ipc host registers * @dev: ishtp device pointer * * Set host to ready state * This API is called in some case: * fw is still on, but ipc is powered down. * such as OOB case. * * Return: 0 for success else error fault code
*/ void ish_set_host_ready(struct ishtp_device *dev)
{ if (ish_chk_host_rdy(dev)) return;
msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr); for (i = 0; i < buffer_length; i += sizeof(uint32_t))
*r_buf++ = ish_reg_read(dev, msg_offs + i);
return 0;
}
/** * write_ipc_from_queue() - try to write ipc msg from Tx queue to device * @dev: ishtp device pointer * * Check if DRBL is cleared. if it is - write the first IPC msg, then call * the callback function (unless it's NULL) * * Return: 0 for success else failure code
*/ staticint write_ipc_from_queue(struct ishtp_device *dev)
{ struct wr_msg_ctl_info *ipc_link; unsignedlong length; unsignedlong rem; unsignedlong flags;
uint32_t doorbell_val;
uint32_t *r_buf;
uint32_t reg_addr; int i; void (*ipc_send_compl)(void *); void *ipc_send_compl_prm;
if (dev->dev_state == ISHTP_DEV_DISABLED) return -EINVAL;
spin_lock_irqsave(&dev->wr_processing_spinlock, flags); if (!ish_is_input_ready(dev)) {
spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); return -EBUSY;
}
/* * if tx send list is empty - return 0; * may happen, as RX_COMPLETE handler doesn't check list emptiness.
*/ if (list_empty(&dev->wr_processing_list)) {
spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); return 0;
}
ipc_link = list_first_entry(&dev->wr_processing_list, struct wr_msg_ctl_info, link); /* first 4 bytes of the data is the doorbell value (IPC header) */
length = ipc_link->length - sizeof(uint32_t);
doorbell_val = *(uint32_t *)ipc_link->inline_data;
r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t));
/* If sending MNG_SYNC_FW_CLOCK, update clock again */ if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG &&
IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) {
uint64_t usec_system, usec_utc; struct ipc_time_update_msg time_update; struct time_sync_format ts_format;
/* * callback will be called out of spinlock, * after ipc_link returned to free list
*/ if (ipc_send_compl)
ipc_send_compl(ipc_send_compl_prm);
return 0;
}
/** * write_ipc_to_queue() - write ipc msg to Tx queue * @dev: ishtp device instance * @ipc_send_compl: Send complete callback * @ipc_send_compl_prm: Parameter to send in complete callback * @msg: Pointer to message * @length: Length of message * * Recived msg with IPC (and upper protocol) header and add it to the device * Tx-to-write list then try to send the first IPC waiting msg * (if DRBL is cleared) * This function returns negative value for failure (means free list * is empty, or msg too long) and 0 for success. * * Return: 0 for success else failure code
*/ staticint write_ipc_to_queue(struct ishtp_device *dev, void (*ipc_send_compl)(void *), void *ipc_send_compl_prm, unsignedchar *msg, int length)
{ struct wr_msg_ctl_info *ipc_link; unsignedlong flags;
/** * timed_wait_for_timeout() - wait special event with timeout * @dev: ISHTP device pointer * @condition: indicate the condition for waiting * @timeinc: time slice for every wait cycle, in ms * @timeout: time in ms for timeout * * This function will check special event to be ready in a loop, the loop * period is specificd in timeinc. Wait timeout will causes failure. * * Return: 0 for success else failure code
*/ staticint timed_wait_for_timeout(struct ishtp_device *dev, int condition, unsignedint timeinc, unsignedint timeout)
{ bool complete = false; int ret;
do { if (condition == WAIT_FOR_FW_RDY) {
complete = ishtp_fw_is_ready(dev);
} elseif (condition == WAIT_FOR_INPUT_RDY) {
complete = ish_is_input_ready(dev);
} else {
ret = -EINVAL; goto out;
}
/* ISHTP notification in IPC_RESET */
ishtp_reset_handler(dev);
if (!ish_is_input_ready(dev))
timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS);
/* ISH FW is dead */ if (!ish_is_input_ready(dev)) return -EPIPE;
/* Send clock sync at once after reset */
ishtp_dev->prev_sync = 0;
/* * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending * RESET_NOTIFY_ACK - FW will be checking for it
*/
ish_set_host_rdy(dev); /* Send RESET_NOTIFY_ACK (with reset_id) */
ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, sizeof(uint32_t));
/* Wait for ISH FW'es ILUP and ISHTP_READY */
timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS); if (!ishtp_fw_is_ready(dev)) { /* ISH FW is dead */
uint32_t ish_status;
ish_status = _ish_read_fw_sts_reg(dev);
dev_err(dev->devc, "[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n",
ish_status); return -ENODEV;
} return 0;
}
#define TIMEOUT_FOR_HW_RDY_MS 300
/** * fw_reset_work_fn() - FW reset worker function * @work: Work item * * Call ish_fw_reset_handler to complete FW reset
*/ staticvoid fw_reset_work_fn(struct work_struct *work)
{ int rv;
rv = ish_fw_reset_handler(ishtp_dev); if (!rv) { /* ISH is ILUP & ISHTP-ready. Restart ISHTP */
msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS);
ishtp_dev->recvd_hw_ready = 1;
wake_up_interruptible(&ishtp_dev->wait_hw_ready);
/** * _ish_sync_fw_clock() -Sync FW clock with the OS clock * @dev: ishtp device pointer * * Sync FW and OS time
*/ staticvoid _ish_sync_fw_clock(struct ishtp_device *dev)
{ struct ipc_time_update_msg time = {};
if (dev->prev_sync && time_before(jiffies, dev->prev_sync + 20 * HZ)) return;
dev->prev_sync = jiffies; /* The fields of time would be updated while sending message */
ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &time, sizeof(time));
}
/** * recv_ipc() - Receive and process IPC management messages * @dev: ishtp device instance * @doorbell_val: doorbell value * * This function runs in ISR context. * NOTE: Any other mng command than reset_notify and reset_notify_ack * won't wake BH handler
*/ staticvoid recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val)
{
uint32_t mng_cmd;
mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val);
switch (mng_cmd) { default: break;
case MNG_RX_CMPL_INDICATION: if (dev->suspend_flag) {
dev->suspend_flag = 0;
wake_up_interruptible(&dev->suspend_wait);
} if (dev->resume_flag) {
dev->resume_flag = 0;
wake_up_interruptible(&dev->resume_wait);
}
write_ipc_from_queue(dev); break;
case MNG_RESET_NOTIFY: if (!ishtp_dev) {
ishtp_dev = dev;
}
schedule_work(&fw_reset_work); break;
case MNG_RESET_NOTIFY_ACK:
dev->recvd_hw_ready = 1;
wake_up_interruptible(&dev->wait_hw_ready); break;
}
}
/** * ish_irq_handler() - ISH IRQ handler * @irq: irq number * @dev_id: ishtp device pointer * * ISH IRQ handler. If interrupt is generated and is for ISH it will process * the interrupt.
*/
irqreturn_t ish_irq_handler(int irq, void *dev_id)
{ struct ishtp_device *dev = dev_id;
uint32_t doorbell_val; bool interrupt_generated;
/* Check that it's interrupt from ISH (may be shared) */
interrupt_generated = check_generated_interrupt(dev);
if (!interrupt_generated) return IRQ_NONE;
doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL); if (!IPC_IS_BUSY(doorbell_val)) return IRQ_HANDLED;
if (dev->dev_state == ISHTP_DEV_DISABLED) return IRQ_HANDLED;
/* Sanity check: IPC dgram length in header */ if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) {
dev_err(dev->devc, "IPC hdr - bad length: %u; dropped\n",
(unsignedint)IPC_HEADER_GET_LENGTH(doorbell_val)); goto eoi;
}
switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) { default: break; case IPC_PROTOCOL_MNG:
recv_ipc(dev, doorbell_val); break; case IPC_PROTOCOL_ISHTP:
ishtp_recv(dev); break;
}
/** * ish_disable_dma() - disable dma communication between host and ISHFW * @dev: ishtp device pointer * * Clear the dma enable bit and wait for dma inactive. * * Return: 0 for success else error code.
*/ int ish_disable_dma(struct ishtp_device *dev)
{ unsignedint dma_delay;
/* Clear the dma enable bit */
ish_reg_write(dev, IPC_REG_ISH_RMP2, 0);
if (dma_delay >= MAX_DMA_DELAY) {
dev_err(dev->devc, "Wait for DMA inactive timeout\n"); return -EBUSY;
}
return 0;
}
/** * ish_wakeup() - wakeup ishfw from waiting-for-host state * @dev: ishtp device pointer * * Set the dma enable bit and send a void message to FW, * it wil wakeup FW from waiting-for-host state.
*/ staticvoid ish_wakeup(struct ishtp_device *dev)
{ /* Set dma enable bit */
ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
/* * Send 0 IPC message so that ISH FW wakes up if it was already * asleep.
*/
ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
/* Flush writes to doorbell and REMAP2 */
ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
}
/** * _ish_hw_reset() - HW reset * @dev: ishtp device pointer * * Reset ISH HW to recover if any error * * Return: 0 for success else error fault code
*/ staticint _ish_hw_reset(struct ishtp_device *dev)
{ struct pci_dev *pdev = dev->pdev; int rv;
uint16_t csr;
if (!pdev) return -ENODEV;
rv = pci_reset_function(pdev); if (!rv)
dev->dev_state = ISHTP_DEV_RESETTING;
if (!pdev->pm_cap) {
dev_err(&pdev->dev, "Can't reset - no PM caps\n"); return -EINVAL;
}
/* Disable dma communication between FW and host */ if (ish_disable_dma(dev)) {
dev_err(&pdev->dev, "Can't reset - stuck with DMA in-progress\n"); return -EBUSY;
}
wait_event_interruptible_timeout(dev->wait_hw_ready,
dev->recvd_hw_ready, 2 * HZ); if (!dev->recvd_hw_ready) {
dev_err(dev->devc, "Timed out waiting for HW ready\n");
rv = -ENODEV;
}
return rv;
}
/** * ish_hw_start() -Start ISH HW * @dev: ishtp device pointer * * Set host to ready state and wait for FW reset * * Return: 0 for success else error fault code
*/ int ish_hw_start(struct ishtp_device *dev)
{
ish_set_host_rdy(dev);
set_host_ready(dev);
/* After that we can enable ISH DMA operation and wakeup ISHFW */
ish_wakeup(dev);
/* wait for FW-initiated reset flow */ if (!dev->recvd_hw_ready)
wait_event_interruptible_timeout(dev->wait_hw_ready,
dev->recvd_hw_ready,
10 * HZ);
if (!dev->recvd_hw_ready) {
dev_err(dev->devc, "[ishtp-ish]: Timed out waiting for FW-initiated reset\n"); return -ENODEV;
}
return 0;
}
/** * ish_ipc_get_header() -Get doorbell value * @dev: ishtp device pointer * @length: length of message * @busy: busy status * * Get door bell value from message header * * Return: door bell value
*/ static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, int busy)
{
uint32_t drbl_val;
/** * _dma_no_cache_snooping() * * Check on current platform, DMA supports cache snooping or not. * This callback is used to notify uplayer driver if manully cache * flush is needed when do DMA operation. * * Please pay attention to this callback implementation, if declare * having cache snooping on a cache snooping not supported platform * will cause uplayer driver receiving mismatched data; and if * declare no cache snooping on a cache snooping supported platform * will cause cache be flushed twice and performance hit. * * @dev: ishtp device pointer * * Return: false - has cache snooping capability * true - no cache snooping, need manually cache flush
*/ staticbool _dma_no_cache_snooping(struct ishtp_device *dev)
{ return (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_LP ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_H ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_S ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_P);
}
/** * ish_dev_init() -Initialize ISH devoce * @pdev: PCI device * * Allocate ISHTP device and initialize IPC processing * * Return: ISHTP device instance on success else NULL
*/ struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
{ struct ishtp_device *dev; int i; int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(struct ishtp_device) + sizeof(struct ish_hw),
GFP_KERNEL); if (!dev) return NULL;
dev->devc = &pdev->dev;
ishtp_device_init(dev);
init_waitqueue_head(&dev->wait_hw_ready);
spin_lock_init(&dev->wr_processing_spinlock);
/* Init IPC processing and free lists */
INIT_LIST_HEAD(&dev->wr_processing_list);
INIT_LIST_HEAD(&dev->wr_free_list); for (i = 0; i < IPC_TX_FIFO_SIZE; i++) { struct wr_msg_ctl_info *tx_buf;
tx_buf = devm_kzalloc(&pdev->dev, sizeof(struct wr_msg_ctl_info),
GFP_KERNEL); if (!tx_buf) { /* * IPC buffers may be limited or not available * at all - although this shouldn't happen
*/
dev_err(dev->devc, "[ishtp-ish]: failure in Tx FIFO allocations (%d)\n",
i); break;
}
list_add_tail(&tx_buf->link, &dev->wr_free_list);
}
ret = devm_work_autocancel(&pdev->dev, &fw_reset_work, fw_reset_work_fn); if (ret) {
dev_err(dev->devc, "Failed to initialise FW reset work\n"); return NULL;
}
/** * ish_device_disable() - Disable ISH device * @dev: ISHTP device pointer * * Disable ISH by clearing host ready to inform firmware.
*/ void ish_device_disable(struct ishtp_device *dev)
{ struct pci_dev *pdev = dev->pdev;
if (!pdev) return;
/* Disable dma communication between FW and host */ if (ish_disable_dma(dev)) {
dev_err(&pdev->dev, "Can't reset - stuck with DMA in-progress\n"); return;
}
/* Put ISH to D3hot state for power saving */
pci_set_power_state(pdev, PCI_D3hot);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.