6db4831e98
Android 14
435 lines
12 KiB
C
435 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2010 - 2015 UNISYS CORPORATION
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/*
|
|
* This provides s-Par channel communication primitives, which are
|
|
* independent of the mechanism used to access the channel data.
|
|
*/
|
|
|
|
#include <linux/uuid.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/visorbus.h>
|
|
|
|
#include "visorbus_private.h"
|
|
#include "controlvmchannel.h"
|
|
|
|
#define VISOR_DRV_NAME "visorchannel"
|
|
|
|
#define VISOR_CONSOLEVIDEO_CHANNEL_GUID \
|
|
GUID_INIT(0x3cd6e705, 0xd6a2, 0x4aa5, \
|
|
0xad, 0x5c, 0x7b, 0x8, 0x88, 0x9d, 0xff, 0xe2)
|
|
|
|
static const guid_t visor_video_guid = VISOR_CONSOLEVIDEO_CHANNEL_GUID;
|
|
|
|
struct visorchannel {
|
|
u64 physaddr;
|
|
ulong nbytes;
|
|
void *mapped;
|
|
bool requested;
|
|
struct channel_header chan_hdr;
|
|
guid_t guid;
|
|
/*
|
|
* channel creator knows if more than one thread will be inserting or
|
|
* removing
|
|
*/
|
|
bool needs_lock;
|
|
/* protect head writes in chan_hdr */
|
|
spinlock_t insert_lock;
|
|
/* protect tail writes in chan_hdr */
|
|
spinlock_t remove_lock;
|
|
guid_t type;
|
|
guid_t inst;
|
|
};
|
|
|
|
void visorchannel_destroy(struct visorchannel *channel)
|
|
{
|
|
if (!channel)
|
|
return;
|
|
|
|
if (channel->mapped) {
|
|
memunmap(channel->mapped);
|
|
if (channel->requested)
|
|
release_mem_region(channel->physaddr, channel->nbytes);
|
|
}
|
|
kfree(channel);
|
|
}
|
|
|
|
u64 visorchannel_get_physaddr(struct visorchannel *channel)
|
|
{
|
|
return channel->physaddr;
|
|
}
|
|
|
|
ulong visorchannel_get_nbytes(struct visorchannel *channel)
|
|
{
|
|
return channel->nbytes;
|
|
}
|
|
|
|
char *visorchannel_guid_id(const guid_t *guid, char *s)
|
|
{
|
|
sprintf(s, "%pUL", guid);
|
|
return s;
|
|
}
|
|
|
|
char *visorchannel_id(struct visorchannel *channel, char *s)
|
|
{
|
|
return visorchannel_guid_id(&channel->guid, s);
|
|
}
|
|
|
|
char *visorchannel_zoneid(struct visorchannel *channel, char *s)
|
|
{
|
|
return visorchannel_guid_id(&channel->chan_hdr.zone_guid, s);
|
|
}
|
|
|
|
u64 visorchannel_get_clientpartition(struct visorchannel *channel)
|
|
{
|
|
return channel->chan_hdr.partition_handle;
|
|
}
|
|
|
|
int visorchannel_set_clientpartition(struct visorchannel *channel,
|
|
u64 partition_handle)
|
|
{
|
|
channel->chan_hdr.partition_handle = partition_handle;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* visorchannel_get_guid() - queries the GUID of the designated channel
|
|
* @channel: the channel to query
|
|
*
|
|
* Return: the GUID of the provided channel
|
|
*/
|
|
const guid_t *visorchannel_get_guid(struct visorchannel *channel)
|
|
{
|
|
return &channel->guid;
|
|
}
|
|
EXPORT_SYMBOL_GPL(visorchannel_get_guid);
|
|
|
|
int visorchannel_read(struct visorchannel *channel, ulong offset, void *dest,
|
|
ulong nbytes)
|
|
{
|
|
if (offset + nbytes > channel->nbytes)
|
|
return -EIO;
|
|
|
|
memcpy(dest, channel->mapped + offset, nbytes);
|
|
return 0;
|
|
}
|
|
|
|
int visorchannel_write(struct visorchannel *channel, ulong offset, void *dest,
|
|
ulong nbytes)
|
|
{
|
|
size_t chdr_size = sizeof(struct channel_header);
|
|
size_t copy_size;
|
|
|
|
if (offset + nbytes > channel->nbytes)
|
|
return -EIO;
|
|
|
|
if (offset < chdr_size) {
|
|
copy_size = min(chdr_size - offset, nbytes);
|
|
memcpy(((char *)(&channel->chan_hdr)) + offset,
|
|
dest, copy_size);
|
|
}
|
|
memcpy(channel->mapped + offset, dest, nbytes);
|
|
return 0;
|
|
}
|
|
|
|
void *visorchannel_get_header(struct visorchannel *channel)
|
|
{
|
|
return &channel->chan_hdr;
|
|
}
|
|
|
|
/*
|
|
* Return offset of a specific SIGNAL_QUEUE_HEADER from the beginning of a
|
|
* channel header
|
|
*/
|
|
static int sig_queue_offset(struct channel_header *chan_hdr, int q)
|
|
{
|
|
return ((chan_hdr)->ch_space_offset +
|
|
((q) * sizeof(struct signal_queue_header)));
|
|
}
|
|
|
|
/*
|
|
* Return offset of a specific queue entry (data) from the beginning of a
|
|
* channel header
|
|
*/
|
|
static int sig_data_offset(struct channel_header *chan_hdr, int q,
|
|
struct signal_queue_header *sig_hdr, int slot)
|
|
{
|
|
return (sig_queue_offset(chan_hdr, q) + sig_hdr->sig_base_offset +
|
|
(slot * sig_hdr->signal_size));
|
|
}
|
|
|
|
/*
|
|
* Write the contents of a specific field within a SIGNAL_QUEUE_HEADER back into
|
|
* host memory
|
|
*/
|
|
#define SIG_WRITE_FIELD(channel, queue, sig_hdr, FIELD) \
|
|
visorchannel_write(channel, \
|
|
sig_queue_offset(&channel->chan_hdr, queue) + \
|
|
offsetof(struct signal_queue_header, FIELD), \
|
|
&((sig_hdr)->FIELD), \
|
|
sizeof((sig_hdr)->FIELD))
|
|
|
|
static int sig_read_header(struct visorchannel *channel, u32 queue,
|
|
struct signal_queue_header *sig_hdr)
|
|
{
|
|
if (channel->chan_hdr.ch_space_offset < sizeof(struct channel_header))
|
|
return -EINVAL;
|
|
|
|
/* Read the appropriate SIGNAL_QUEUE_HEADER into local memory. */
|
|
return visorchannel_read(channel,
|
|
sig_queue_offset(&channel->chan_hdr, queue),
|
|
sig_hdr, sizeof(struct signal_queue_header));
|
|
}
|
|
|
|
static int sig_read_data(struct visorchannel *channel, u32 queue,
|
|
struct signal_queue_header *sig_hdr, u32 slot,
|
|
void *data)
|
|
{
|
|
int signal_data_offset = sig_data_offset(&channel->chan_hdr, queue,
|
|
sig_hdr, slot);
|
|
|
|
return visorchannel_read(channel, signal_data_offset,
|
|
data, sig_hdr->signal_size);
|
|
}
|
|
|
|
static int sig_write_data(struct visorchannel *channel, u32 queue,
|
|
struct signal_queue_header *sig_hdr, u32 slot,
|
|
void *data)
|
|
{
|
|
int signal_data_offset = sig_data_offset(&channel->chan_hdr, queue,
|
|
sig_hdr, slot);
|
|
|
|
return visorchannel_write(channel, signal_data_offset,
|
|
data, sig_hdr->signal_size);
|
|
}
|
|
|
|
static int signalremove_inner(struct visorchannel *channel, u32 queue,
|
|
void *msg)
|
|
{
|
|
struct signal_queue_header sig_hdr;
|
|
int error;
|
|
|
|
error = sig_read_header(channel, queue, &sig_hdr);
|
|
if (error)
|
|
return error;
|
|
/* No signals to remove; have caller try again. */
|
|
if (sig_hdr.head == sig_hdr.tail)
|
|
return -EAGAIN;
|
|
sig_hdr.tail = (sig_hdr.tail + 1) % sig_hdr.max_slots;
|
|
error = sig_read_data(channel, queue, &sig_hdr, sig_hdr.tail, msg);
|
|
if (error)
|
|
return error;
|
|
sig_hdr.num_received++;
|
|
/*
|
|
* For each data field in SIGNAL_QUEUE_HEADER that was modified, update
|
|
* host memory. Required for channel sync.
|
|
*/
|
|
mb();
|
|
error = SIG_WRITE_FIELD(channel, queue, &sig_hdr, tail);
|
|
if (error)
|
|
return error;
|
|
error = SIG_WRITE_FIELD(channel, queue, &sig_hdr, num_received);
|
|
if (error)
|
|
return error;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* visorchannel_signalremove() - removes a message from the designated
|
|
* channel/queue
|
|
* @channel: the channel the message will be removed from
|
|
* @queue: the queue the message will be removed from
|
|
* @msg: the message to remove
|
|
*
|
|
* Return: integer error code indicating the status of the removal
|
|
*/
|
|
int visorchannel_signalremove(struct visorchannel *channel, u32 queue,
|
|
void *msg)
|
|
{
|
|
int rc;
|
|
unsigned long flags;
|
|
|
|
if (channel->needs_lock) {
|
|
spin_lock_irqsave(&channel->remove_lock, flags);
|
|
rc = signalremove_inner(channel, queue, msg);
|
|
spin_unlock_irqrestore(&channel->remove_lock, flags);
|
|
} else {
|
|
rc = signalremove_inner(channel, queue, msg);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(visorchannel_signalremove);
|
|
|
|
static bool queue_empty(struct visorchannel *channel, u32 queue)
|
|
{
|
|
struct signal_queue_header sig_hdr;
|
|
|
|
if (sig_read_header(channel, queue, &sig_hdr))
|
|
return true;
|
|
return (sig_hdr.head == sig_hdr.tail);
|
|
}
|
|
|
|
/**
|
|
* visorchannel_signalempty() - checks if the designated channel/queue contains
|
|
* any messages
|
|
* @channel: the channel to query
|
|
* @queue: the queue in the channel to query
|
|
*
|
|
* Return: boolean indicating whether any messages in the designated
|
|
* channel/queue are present
|
|
*/
|
|
bool visorchannel_signalempty(struct visorchannel *channel, u32 queue)
|
|
{
|
|
bool rc;
|
|
unsigned long flags;
|
|
|
|
if (!channel->needs_lock)
|
|
return queue_empty(channel, queue);
|
|
spin_lock_irqsave(&channel->remove_lock, flags);
|
|
rc = queue_empty(channel, queue);
|
|
spin_unlock_irqrestore(&channel->remove_lock, flags);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(visorchannel_signalempty);
|
|
|
|
static int signalinsert_inner(struct visorchannel *channel, u32 queue,
|
|
void *msg)
|
|
{
|
|
struct signal_queue_header sig_hdr;
|
|
int err;
|
|
|
|
err = sig_read_header(channel, queue, &sig_hdr);
|
|
if (err)
|
|
return err;
|
|
sig_hdr.head = (sig_hdr.head + 1) % sig_hdr.max_slots;
|
|
if (sig_hdr.head == sig_hdr.tail) {
|
|
sig_hdr.num_overflows++;
|
|
err = SIG_WRITE_FIELD(channel, queue, &sig_hdr, num_overflows);
|
|
if (err)
|
|
return err;
|
|
return -EIO;
|
|
}
|
|
err = sig_write_data(channel, queue, &sig_hdr, sig_hdr.head, msg);
|
|
if (err)
|
|
return err;
|
|
sig_hdr.num_sent++;
|
|
/*
|
|
* For each data field in SIGNAL_QUEUE_HEADER that was modified, update
|
|
* host memory. Required for channel sync.
|
|
*/
|
|
mb();
|
|
err = SIG_WRITE_FIELD(channel, queue, &sig_hdr, head);
|
|
if (err)
|
|
return err;
|
|
err = SIG_WRITE_FIELD(channel, queue, &sig_hdr, num_sent);
|
|
if (err)
|
|
return err;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* visorchannel_create() - creates the struct visorchannel abstraction for a
|
|
* data area in memory, but does NOT modify this data
|
|
* area
|
|
* @physaddr: physical address of start of channel
|
|
* @gfp: gfp_t to use when allocating memory for the data struct
|
|
* @guid: GUID that identifies channel type;
|
|
* @needs_lock: must specify true if you have multiple threads of execution
|
|
* that will be calling visorchannel methods of this
|
|
* visorchannel at the same time
|
|
*
|
|
* Return: pointer to visorchannel that was created if successful,
|
|
* otherwise NULL
|
|
*/
|
|
struct visorchannel *visorchannel_create(u64 physaddr, gfp_t gfp,
|
|
const guid_t *guid, bool needs_lock)
|
|
{
|
|
struct visorchannel *channel;
|
|
int err;
|
|
size_t size = sizeof(struct channel_header);
|
|
|
|
if (physaddr == 0)
|
|
return NULL;
|
|
|
|
channel = kzalloc(sizeof(*channel), gfp);
|
|
if (!channel)
|
|
return NULL;
|
|
channel->needs_lock = needs_lock;
|
|
spin_lock_init(&channel->insert_lock);
|
|
spin_lock_init(&channel->remove_lock);
|
|
/*
|
|
* Video driver constains the efi framebuffer so it will get a conflict
|
|
* resource when requesting its full mem region. Since we are only
|
|
* using the efi framebuffer for video we can ignore this. Remember that
|
|
* we haven't requested it so we don't try to release later on.
|
|
*/
|
|
channel->requested = request_mem_region(physaddr, size, VISOR_DRV_NAME);
|
|
if (!channel->requested && !guid_equal(guid, &visor_video_guid))
|
|
/* we only care about errors if this is not the video channel */
|
|
goto err_destroy_channel;
|
|
channel->mapped = memremap(physaddr, size, MEMREMAP_WB);
|
|
if (!channel->mapped) {
|
|
release_mem_region(physaddr, size);
|
|
goto err_destroy_channel;
|
|
}
|
|
channel->physaddr = physaddr;
|
|
channel->nbytes = size;
|
|
err = visorchannel_read(channel, 0, &channel->chan_hdr, size);
|
|
if (err)
|
|
goto err_destroy_channel;
|
|
size = (ulong)channel->chan_hdr.size;
|
|
memunmap(channel->mapped);
|
|
if (channel->requested)
|
|
release_mem_region(channel->physaddr, channel->nbytes);
|
|
channel->mapped = NULL;
|
|
channel->requested = request_mem_region(channel->physaddr, size,
|
|
VISOR_DRV_NAME);
|
|
if (!channel->requested && !guid_equal(guid, &visor_video_guid))
|
|
/* we only care about errors if this is not the video channel */
|
|
goto err_destroy_channel;
|
|
channel->mapped = memremap(channel->physaddr, size, MEMREMAP_WB);
|
|
if (!channel->mapped) {
|
|
release_mem_region(channel->physaddr, size);
|
|
goto err_destroy_channel;
|
|
}
|
|
channel->nbytes = size;
|
|
guid_copy(&channel->guid, guid);
|
|
return channel;
|
|
|
|
err_destroy_channel:
|
|
visorchannel_destroy(channel);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* visorchannel_signalinsert() - inserts a message into the designated
|
|
* channel/queue
|
|
* @channel: the channel the message will be added to
|
|
* @queue: the queue the message will be added to
|
|
* @msg: the message to insert
|
|
*
|
|
* Return: integer error code indicating the status of the insertion
|
|
*/
|
|
int visorchannel_signalinsert(struct visorchannel *channel, u32 queue,
|
|
void *msg)
|
|
{
|
|
int rc;
|
|
unsigned long flags;
|
|
|
|
if (channel->needs_lock) {
|
|
spin_lock_irqsave(&channel->insert_lock, flags);
|
|
rc = signalinsert_inner(channel, queue, msg);
|
|
spin_unlock_irqrestore(&channel->insert_lock, flags);
|
|
} else {
|
|
rc = signalinsert_inner(channel, queue, msg);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL_GPL(visorchannel_signalinsert);
|