241 lines
6.7 KiB
C
241 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Intel MIC Platform Software Stack (MPSS)
|
|
*
|
|
* Copyright(c) 2014 Intel Corporation.
|
|
*
|
|
* Intel SCIF driver.
|
|
*/
|
|
#include <linux/circ_buf.h>
|
|
#include <linux/types.h>
|
|
#include <linux/io.h>
|
|
#include <linux/errno.h>
|
|
|
|
#include "scif_rb.h"
|
|
|
|
#define scif_rb_ring_cnt(head, tail, size) CIRC_CNT(head, tail, size)
|
|
#define scif_rb_ring_space(head, tail, size) CIRC_SPACE(head, tail, size)
|
|
|
|
/**
|
|
* scif_rb_init - Initializes the ring buffer
|
|
* @rb: ring buffer
|
|
* @read_ptr: A pointer to the read offset
|
|
* @write_ptr: A pointer to the write offset
|
|
* @rb_base: A pointer to the base of the ring buffer
|
|
* @size: The size of the ring buffer in powers of two
|
|
*/
|
|
void scif_rb_init(struct scif_rb *rb, u32 *read_ptr, u32 *write_ptr,
|
|
void *rb_base, u8 size)
|
|
{
|
|
rb->rb_base = rb_base;
|
|
rb->size = (1 << size);
|
|
rb->read_ptr = read_ptr;
|
|
rb->write_ptr = write_ptr;
|
|
rb->current_read_offset = *read_ptr;
|
|
rb->current_write_offset = *write_ptr;
|
|
}
|
|
|
|
/* Copies a message to the ring buffer -- handles the wrap around case */
|
|
static void memcpy_torb(struct scif_rb *rb, void *header,
|
|
void *msg, u32 size)
|
|
{
|
|
u32 size1, size2;
|
|
|
|
if (header + size >= rb->rb_base + rb->size) {
|
|
/* Need to call two copies if it wraps around */
|
|
size1 = (u32)(rb->rb_base + rb->size - header);
|
|
size2 = size - size1;
|
|
memcpy_toio((void __iomem __force *)header, msg, size1);
|
|
memcpy_toio((void __iomem __force *)rb->rb_base,
|
|
msg + size1, size2);
|
|
} else {
|
|
memcpy_toio((void __iomem __force *)header, msg, size);
|
|
}
|
|
}
|
|
|
|
/* Copies a message from the ring buffer -- handles the wrap around case */
|
|
static void memcpy_fromrb(struct scif_rb *rb, void *header,
|
|
void *msg, u32 size)
|
|
{
|
|
u32 size1, size2;
|
|
|
|
if (header + size >= rb->rb_base + rb->size) {
|
|
/* Need to call two copies if it wraps around */
|
|
size1 = (u32)(rb->rb_base + rb->size - header);
|
|
size2 = size - size1;
|
|
memcpy_fromio(msg, (void __iomem __force *)header, size1);
|
|
memcpy_fromio(msg + size1,
|
|
(void __iomem __force *)rb->rb_base, size2);
|
|
} else {
|
|
memcpy_fromio(msg, (void __iomem __force *)header, size);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* scif_rb_space - Query space available for writing to the RB
|
|
* @rb: ring buffer
|
|
*
|
|
* Return: size available for writing to RB in bytes.
|
|
*/
|
|
u32 scif_rb_space(struct scif_rb *rb)
|
|
{
|
|
rb->current_read_offset = *rb->read_ptr;
|
|
/*
|
|
* Update from the HW read pointer only once the peer has exposed the
|
|
* new empty slot. This barrier is paired with the memory barrier
|
|
* scif_rb_update_read_ptr()
|
|
*/
|
|
mb();
|
|
return scif_rb_ring_space(rb->current_write_offset,
|
|
rb->current_read_offset, rb->size);
|
|
}
|
|
|
|
/**
|
|
* scif_rb_write - Write a message to the RB
|
|
* @rb: ring buffer
|
|
* @msg: buffer to send the message. Must be at least size bytes long
|
|
* @size: the size (in bytes) to be copied to the RB
|
|
*
|
|
* This API does not block if there isn't enough space in the RB.
|
|
* Returns: 0 on success or -ENOMEM on failure
|
|
*/
|
|
int scif_rb_write(struct scif_rb *rb, void *msg, u32 size)
|
|
{
|
|
void *header;
|
|
|
|
if (scif_rb_space(rb) < size)
|
|
return -ENOMEM;
|
|
header = rb->rb_base + rb->current_write_offset;
|
|
memcpy_torb(rb, header, msg, size);
|
|
/*
|
|
* Wait until scif_rb_commit(). Update the local ring
|
|
* buffer data, not the shared data until commit.
|
|
*/
|
|
rb->current_write_offset =
|
|
(rb->current_write_offset + size) & (rb->size - 1);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* scif_rb_commit - To submit the message to let the peer fetch it
|
|
* @rb: ring buffer
|
|
*/
|
|
void scif_rb_commit(struct scif_rb *rb)
|
|
{
|
|
/*
|
|
* We must ensure ordering between the all the data committed
|
|
* previously before we expose the new message to the peer by
|
|
* updating the write_ptr. This write barrier is paired with
|
|
* the read barrier in scif_rb_count(..)
|
|
*/
|
|
wmb();
|
|
WRITE_ONCE(*rb->write_ptr, rb->current_write_offset);
|
|
#ifdef CONFIG_INTEL_MIC_CARD
|
|
/*
|
|
* X100 Si bug: For the case where a Core is performing an EXT_WR
|
|
* followed by a Doorbell Write, the Core must perform two EXT_WR to the
|
|
* same address with the same data before it does the Doorbell Write.
|
|
* This way, if ordering is violated for the Interrupt Message, it will
|
|
* fall just behind the first Posted associated with the first EXT_WR.
|
|
*/
|
|
WRITE_ONCE(*rb->write_ptr, rb->current_write_offset);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* scif_rb_get - To get next message from the ring buffer
|
|
* @rb: ring buffer
|
|
* @size: Number of bytes to be read
|
|
*
|
|
* Return: NULL if no bytes to be read from the ring buffer, otherwise the
|
|
* pointer to the next byte
|
|
*/
|
|
static void *scif_rb_get(struct scif_rb *rb, u32 size)
|
|
{
|
|
void *header = NULL;
|
|
|
|
if (scif_rb_count(rb, size) >= size)
|
|
header = rb->rb_base + rb->current_read_offset;
|
|
return header;
|
|
}
|
|
|
|
/*
|
|
* scif_rb_get_next - Read from ring buffer.
|
|
* @rb: ring buffer
|
|
* @msg: buffer to hold the message. Must be at least size bytes long
|
|
* @size: Number of bytes to be read
|
|
*
|
|
* Return: number of bytes read if available bytes are >= size, otherwise
|
|
* returns zero.
|
|
*/
|
|
u32 scif_rb_get_next(struct scif_rb *rb, void *msg, u32 size)
|
|
{
|
|
void *header = NULL;
|
|
int read_size = 0;
|
|
|
|
header = scif_rb_get(rb, size);
|
|
if (header) {
|
|
u32 next_cmd_offset =
|
|
(rb->current_read_offset + size) & (rb->size - 1);
|
|
|
|
read_size = size;
|
|
rb->current_read_offset = next_cmd_offset;
|
|
memcpy_fromrb(rb, header, msg, size);
|
|
}
|
|
return read_size;
|
|
}
|
|
|
|
/**
|
|
* scif_rb_update_read_ptr
|
|
* @rb: ring buffer
|
|
*/
|
|
void scif_rb_update_read_ptr(struct scif_rb *rb)
|
|
{
|
|
u32 new_offset;
|
|
|
|
new_offset = rb->current_read_offset;
|
|
/*
|
|
* We must ensure ordering between the all the data committed or read
|
|
* previously before we expose the empty slot to the peer by updating
|
|
* the read_ptr. This barrier is paired with the memory barrier in
|
|
* scif_rb_space(..)
|
|
*/
|
|
mb();
|
|
WRITE_ONCE(*rb->read_ptr, new_offset);
|
|
#ifdef CONFIG_INTEL_MIC_CARD
|
|
/*
|
|
* X100 Si Bug: For the case where a Core is performing an EXT_WR
|
|
* followed by a Doorbell Write, the Core must perform two EXT_WR to the
|
|
* same address with the same data before it does the Doorbell Write.
|
|
* This way, if ordering is violated for the Interrupt Message, it will
|
|
* fall just behind the first Posted associated with the first EXT_WR.
|
|
*/
|
|
WRITE_ONCE(*rb->read_ptr, new_offset);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* scif_rb_count
|
|
* @rb: ring buffer
|
|
* @size: Number of bytes expected to be read
|
|
*
|
|
* Return: number of bytes that can be read from the RB
|
|
*/
|
|
u32 scif_rb_count(struct scif_rb *rb, u32 size)
|
|
{
|
|
if (scif_rb_ring_cnt(rb->current_write_offset,
|
|
rb->current_read_offset,
|
|
rb->size) < size) {
|
|
rb->current_write_offset = *rb->write_ptr;
|
|
/*
|
|
* Update from the HW write pointer if empty only once the peer
|
|
* has exposed the new message. This read barrier is paired
|
|
* with the write barrier in scif_rb_commit(..)
|
|
*/
|
|
smp_rmb();
|
|
}
|
|
return scif_rb_ring_cnt(rb->current_write_offset,
|
|
rb->current_read_offset,
|
|
rb->size);
|
|
}
|