blob: 125a893ad7d33403dc6873389582b4f2c249760a [file] [log] [blame]
/*
* Google LWIS Fence
*
* Copyright (c) 2022 Google, LLC
*
* 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.
*/
#include <linux/anon_inodes.h>
#include <linux/file.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include "lwis_device_top.h"
#include "lwis_commands.h"
#include "lwis_fence.h"
#include "lwis_transaction.h"
#define HASH_CLIENT(x) hash_ptr(x, LWIS_CLIENTS_HASH_BITS)
bool lwis_fence_debug;
module_param(lwis_fence_debug, bool, 0644);
static int lwis_fence_release(struct inode *node, struct file *fp);
static ssize_t lwis_fence_get_status(struct file *fp, char __user *user_buffer, size_t len,
loff_t *offset);
static ssize_t lwis_fence_write_status(struct file *fp, const char __user *user_buffer, size_t len,
loff_t *offset);
static unsigned int lwis_fence_poll(struct file *fp, poll_table *wait);
static const struct file_operations fence_file_ops = {
.owner = THIS_MODULE,
.release = lwis_fence_release,
.read = lwis_fence_get_status,
.write = lwis_fence_write_status,
.poll = lwis_fence_poll,
};
static int get_fence_status(struct lwis_fence *lwis_fence)
{
int status;
unsigned long flags;
spin_lock_irqsave(&lwis_fence->lock, flags);
status = lwis_fence->status;
spin_unlock_irqrestore(&lwis_fence->lock, flags);
return status;
}
/*
* lwis_fence_release: Closing an instance of a LWIS fence
*/
static int lwis_fence_release(struct inode *node, struct file *fp)
{
struct lwis_fence *lwis_fence = fp->private_data;
struct lwis_fence_trigger_transaction_list *tx_list;
struct lwis_pending_transaction_id *transaction_id;
/* Temporary vars for traversal */
struct hlist_node *n;
struct list_head *it_tran, *it_tran_tmp;
int i;
if (lwis_fence_debug) {
dev_info(lwis_fence->lwis_top_dev->dev, "Releasing lwis_fence fd-%d",
lwis_fence->fd);
}
if (lwis_fence->status == LWIS_FENCE_STATUS_NOT_SIGNALED) {
dev_err(lwis_fence->lwis_top_dev->dev,
"lwis_fence fd-%d release without being signaled", lwis_fence->fd);
}
if (!hash_empty(lwis_fence->transaction_list)) {
hash_for_each_safe (lwis_fence->transaction_list, i, n, tx_list, node) {
if (!list_empty(&tx_list->list)) {
list_for_each_safe (it_tran, it_tran_tmp, &tx_list->list) {
transaction_id =
list_entry(it_tran,
struct lwis_pending_transaction_id,
list_node);
list_del(&transaction_id->list_node);
kfree(transaction_id);
}
}
hash_del(&tx_list->node);
kfree(tx_list);
}
}
kfree(lwis_fence);
return 0;
}
/*
* lwis_fence_get_status: Read the LWIS fence's status
*/
static ssize_t lwis_fence_get_status(struct file *fp, char __user *user_buffer, size_t len,
loff_t *offset)
{
int status = 0;
struct lwis_fence *lwis_fence = fp->private_data;
int max_len, read_len;
if (!lwis_fence) {
return -EFAULT;
}
max_len = sizeof(status) - *offset;
if (len > max_len) {
len = max_len;
}
status = get_fence_status(lwis_fence);
read_len = len - copy_to_user((void __user *)user_buffer, (void *)&status + *offset, len);
return read_len;
}
/*
* lwis_fence_write_status: Signal fence with the error code from user
*/
static ssize_t lwis_fence_write_status(struct file *fp, const char __user *user_buffer, size_t len,
loff_t *offset)
{
int ret = 0;
int status = 0;
struct lwis_fence *lwis_fence = fp->private_data;
if (!lwis_fence) {
return -EFAULT;
}
if (len != sizeof(lwis_fence->status)) {
dev_err(lwis_fence->lwis_top_dev->dev,
"Signal lwis_fence fd-%d with incorrect buffer length\n", lwis_fence->fd);
return -EINVAL;
}
/* Set lwis_fence's status if not signaled */
len = len - copy_from_user(&status, (void __user *)user_buffer, len);
ret = lwis_fence_signal(lwis_fence, status);
if (ret) {
return ret;
}
return len;
}
int lwis_fence_signal(struct lwis_fence *lwis_fence, int status)
{
unsigned long flags;
struct lwis_fence_trigger_transaction_list *tx_list;
/* Temporary vars for hash table traversal */
struct hlist_node *n;
int i;
if (!lwis_fence) {
return -EFAULT;
}
spin_lock_irqsave(&lwis_fence->lock, flags);
if (lwis_fence->status != LWIS_FENCE_STATUS_NOT_SIGNALED) {
/* Return error if fence is already signaled */
dev_err(lwis_fence->lwis_top_dev->dev,
"Cannot signal a lwis_fence fd-%d already signaled, status is %d\n",
lwis_fence->fd, lwis_fence->status);
spin_unlock_irqrestore(&lwis_fence->lock, flags);
return -EINVAL;
}
lwis_fence->status = status;
spin_unlock_irqrestore(&lwis_fence->lock, flags);
wake_up_interruptible(&lwis_fence->status_wait_queue);
hash_for_each_safe (lwis_fence->transaction_list, i, n, tx_list, node) {
hash_del(&tx_list->node);
lwis_transaction_fence_trigger(tx_list->owner, lwis_fence, &tx_list->list);
if (!list_empty(&tx_list->list)) {
dev_err(lwis_fence->lwis_top_dev->dev,
"Fail to trigger all transactions\n");
}
kfree(tx_list);
}
return 0;
}
/*
* lwis_fence_poll: Poll status function of LWIS fence
*/
static unsigned int lwis_fence_poll(struct file *fp, poll_table *wait)
{
unsigned long flags;
int status = 0;
struct lwis_fence *lwis_fence = fp->private_data;
if (!lwis_fence) {
return POLLERR;
}
poll_wait(fp, &lwis_fence->status_wait_queue, wait);
spin_lock_irqsave(&lwis_fence->lock, flags);
status = lwis_fence->status;
spin_unlock_irqrestore(&lwis_fence->lock, flags);
/* Check if the fence is already signaled */
if (status != LWIS_FENCE_STATUS_NOT_SIGNALED) {
return POLLIN;
}
return 0;
}
int lwis_fence_create(struct lwis_device *lwis_dev)
{
int fd_or_err;
struct lwis_fence *new_fence;
/* Allocate a new instance of lwis_fence struct */
new_fence = kmalloc(sizeof(struct lwis_fence), GFP_ATOMIC);
if (!new_fence) {
return -ENOMEM;
}
/* Open a new fd for the new fence */
fd_or_err =
anon_inode_getfd("lwis_fence_file", &fence_file_ops, new_fence, O_RDWR | O_CLOEXEC);
if (fd_or_err < 0) {
kfree(new_fence);
dev_err(lwis_dev->dev, "Failed to create a new file instance for lwis_fence\n");
return fd_or_err;
}
new_fence->fd = fd_or_err;
new_fence->lwis_top_dev = lwis_dev->top_dev;
new_fence->status = LWIS_FENCE_STATUS_NOT_SIGNALED;
spin_lock_init(&new_fence->lock);
init_waitqueue_head(&new_fence->status_wait_queue);
if (lwis_fence_debug) {
dev_info(lwis_dev->dev, "lwis_fence created new LWIS fence fd: %d", new_fence->fd);
}
return fd_or_err;
}
static struct lwis_fence_trigger_transaction_list *transaction_list_find(struct lwis_fence *fence,
struct lwis_client *owner)
{
int hash_key = HASH_CLIENT(owner);
struct lwis_fence_trigger_transaction_list *tx_list;
hash_for_each_possible (fence->transaction_list, tx_list, node, hash_key) {
if (tx_list->owner == owner) {
return tx_list;
}
}
return NULL;
}
static struct lwis_fence_trigger_transaction_list *
transaction_list_create(struct lwis_fence *fence, struct lwis_client *owner)
{
struct lwis_fence_trigger_transaction_list *tx_list =
kmalloc(sizeof(struct lwis_fence_trigger_transaction_list), GFP_ATOMIC);
if (!tx_list) {
return NULL;
}
tx_list->owner = owner;
INIT_LIST_HEAD(&tx_list->list);
hash_add(fence->transaction_list, &tx_list->node, HASH_CLIENT(owner));
return tx_list;
}
static struct lwis_fence_trigger_transaction_list *
transaction_list_find_or_create(struct lwis_fence *fence, struct lwis_client *owner)
{
struct lwis_fence_trigger_transaction_list *list = transaction_list_find(fence, owner);
return (list == NULL) ? transaction_list_create(fence, owner) : list;
}
static int lwis_trigger_fence_add_transaction(int fence_fd, struct lwis_client *client,
struct lwis_transaction *transaction)
{
unsigned long flags;
struct file *fp;
struct lwis_fence *lwis_fence;
struct lwis_pending_transaction_id *pending_transaction_id;
struct lwis_fence_trigger_transaction_list *tx_list;
int ret = 0;
if (transaction->num_trigger_fences >= LWIS_TRIGGER_NODES_MAX_NUM) {
dev_err(client->lwis_dev->dev,
"Invalid num_trigger_fences value in transaction %d\n", fence_fd);
return -EINVAL;
}
fp = fget(fence_fd);
if (fp == NULL) {
dev_err(client->lwis_dev->dev, "Failed to find lwis_fence with fd %d\n", fence_fd);
return -EBADF;
}
lwis_fence = fp->private_data;
if (lwis_fence->fd != fence_fd) {
fput(fp);
dev_err(client->lwis_dev->dev,
"Invalid lwis_fence with fd %d. Contains stale data \n", fence_fd);
return -EBADF;
}
pending_transaction_id = kmalloc(sizeof(struct lwis_pending_transaction_id), GFP_ATOMIC);
if (!pending_transaction_id) {
fput(fp);
return -ENOMEM;
}
pending_transaction_id->id = transaction->info.id;
spin_lock_irqsave(&lwis_fence->lock, flags);
if (lwis_fence->status == LWIS_FENCE_STATUS_NOT_SIGNALED) {
transaction->trigger_fence_fps[transaction->num_trigger_fences++] = fp;
tx_list = transaction_list_find_or_create(lwis_fence, client);
list_add(&pending_transaction_id->list_node, &tx_list->list);
if (lwis_fence_debug) {
dev_info(client->lwis_dev->dev,
"lwis_fence transaction id %llu added to its trigger fence fd %d ",
transaction->info.id, lwis_fence->fd);
}
} else {
kfree(pending_transaction_id);
if (lwis_fence_debug) {
dev_info(
client->lwis_dev->dev,
"lwis_fence fd-%d not added to transaction id %llu, fence already signaled with error code %d \n",
fence_fd, transaction->info.id, lwis_fence->status);
}
if (!transaction->info.is_level_triggered) {
/* If level triggering is disabled, return an error. */
fput(fp);
ret = -EINVAL;
} else {
transaction->trigger_fence_fps[transaction->num_trigger_fences++] = fp;
/* If the transaction's trigger_condition evaluates to true, queue the
* transaction to be executed immediately.
*/
if (lwis_fence_triggered_condition_ready(transaction, lwis_fence->status)) {
if (lwis_fence->status != 0) {
transaction->resp->error_code = -ECANCELED;
}
transaction->queue_immediately = true;
}
}
}
spin_unlock_irqrestore(&lwis_fence->lock, flags);
return ret;
}
bool lwis_triggered_by_condition(struct lwis_transaction *transaction)
{
return (transaction->info.trigger_condition.num_nodes > 0);
}
bool lwis_event_triggered_condition_ready(struct lwis_transaction *transaction,
struct lwis_transaction *weak_transaction,
int64_t event_id, int64_t event_counter)
{
int32_t operator_type;
size_t all_signaled;
struct lwis_transaction_info_v2 *info = &transaction->info;
int i;
struct lwis_fence *lwis_fence;
bool is_node_signaled = false;
operator_type = info->trigger_condition.operator_type;
all_signaled = info->trigger_condition.num_nodes;
/*
* Three scenarios to consider a node signaled:
* 1) Event ID and event counter match,
* 2) Event ID match, event counter not specified but precondition fence signaled, or,
* 3) Event ID match, event counter and precondition fence not specified.
*/
for (i = 0; i < info->trigger_condition.num_nodes; i++) {
is_node_signaled = false;
if (info->trigger_condition.trigger_nodes[i].type == LWIS_TRIGGER_EVENT &&
info->trigger_condition.trigger_nodes[i].event.id == event_id) {
if (info->trigger_condition.trigger_nodes[i].event.counter ==
event_counter ||
(info->trigger_condition.trigger_nodes[i].event.counter ==
LWIS_EVENT_COUNTER_ON_NEXT_OCCURRENCE &&
weak_transaction->precondition_fence_fp == NULL)) {
is_node_signaled = true;
} else if (info->trigger_condition.trigger_nodes[i].event.counter ==
LWIS_EVENT_COUNTER_ON_NEXT_OCCURRENCE) {
lwis_fence = weak_transaction->precondition_fence_fp->private_data;
if (lwis_fence != NULL && get_fence_status(lwis_fence) == 0) {
is_node_signaled = true;
if (lwis_fence_debug) {
pr_info("TransactionId %lld: event 0x%llx (%lld), precondition fence %d signaled",
info->id, event_id, event_counter,
info->trigger_condition.trigger_nodes[i]
.event.precondition_fence_fd);
}
} else {
if (lwis_fence_debug) {
pr_info("TransactionId %lld: event 0x%llx (%lld), precondition fence %d NOT signaled yet",
info->id, event_id, event_counter,
info->trigger_condition.trigger_nodes[i]
.event.precondition_fence_fd);
}
}
}
if (is_node_signaled) {
transaction->signaled_count++;
list_del(&weak_transaction->event_list_node);
if (weak_transaction->precondition_fence_fp) {
fput(weak_transaction->precondition_fence_fp);
}
kfree(weak_transaction);
/* The break here assumes that this event ID only appears once in
the trigger expression. Might need to revisit this. */
break;
}
}
}
if (i >= info->trigger_condition.num_nodes) {
/* No event counter is matched */
return false;
}
if (operator_type == LWIS_TRIGGER_NODE_OPERATOR_AND &&
transaction->signaled_count == all_signaled) {
return true;
} else if (operator_type == LWIS_TRIGGER_NODE_OPERATOR_OR) {
return true;
} else if (operator_type == LWIS_TRIGGER_NODE_OPERATOR_NONE) {
return true;
}
return false;
}
bool lwis_fence_triggered_condition_ready(struct lwis_transaction *transaction, int fence_status)
{
int32_t operator_type;
size_t all_signaled;
operator_type = transaction->info.trigger_condition.operator_type;
all_signaled = transaction->info.trigger_condition.num_nodes;
transaction->signaled_count++;
if ((operator_type == LWIS_TRIGGER_NODE_OPERATOR_AND ||
operator_type == LWIS_TRIGGER_NODE_OPERATOR_OR) &&
transaction->signaled_count == all_signaled) {
return true;
} else if (operator_type == LWIS_TRIGGER_NODE_OPERATOR_AND && fence_status != 0) {
/*
This condition is ready to cancel transaction as long as there is
an error condition from fence with operator type "AND".
No matter whether all condition nodes are signaled.
*/
return true;
} else if (operator_type == LWIS_TRIGGER_NODE_OPERATOR_OR && fence_status == 0) {
return true;
} else if (operator_type == LWIS_TRIGGER_NODE_OPERATOR_NONE) {
return true;
}
return false;
}
int lwis_parse_trigger_condition(struct lwis_client *client, struct lwis_transaction *transaction)
{
struct lwis_transaction_info_v2 *info;
struct lwis_device *lwis_dev;
int i, ret;
if (!transaction || !client) {
return -EINVAL;
}
info = &transaction->info;
lwis_dev = client->lwis_dev;
if (info->trigger_condition.num_nodes > LWIS_TRIGGER_NODES_MAX_NUM) {
dev_err(lwis_dev->dev,
"Trigger condition contains %lu node, more than the limit of %d\n",
info->trigger_condition.num_nodes, LWIS_TRIGGER_NODES_MAX_NUM);
return -EINVAL;
}
for (i = 0; i < info->trigger_condition.num_nodes; i++) {
if (info->trigger_condition.trigger_nodes[i].type == LWIS_TRIGGER_EVENT) {
ret = lwis_trigger_event_add_weak_transaction(
client, info->id, info->trigger_condition.trigger_nodes[i].event.id,
info->trigger_condition.trigger_nodes[i]
.event.precondition_fence_fd);
} else {
ret = lwis_trigger_fence_add_transaction(
info->trigger_condition.trigger_nodes[i].fence_fd, client,
transaction);
}
if (ret) {
return ret;
}
}
return 0;
}
int ioctl_lwis_fence_create(struct lwis_device *lwis_dev, int32_t __user *msg)
{
int32_t fd_or_err;
fd_or_err = lwis_fence_create(lwis_dev);
if (fd_or_err < 0) {
return fd_or_err;
}
if (copy_to_user((void __user *)msg, &fd_or_err, sizeof(int32_t))) {
dev_err(lwis_dev->dev, "failed to copy to user\n");
return -EFAULT;
}
return 0;
}
int lwis_initialize_transaction_fences(struct lwis_client *client,
struct lwis_transaction *transaction)
{
struct lwis_transaction_info_v2 *info = &transaction->info;
struct lwis_device *lwis_dev = client->lwis_dev;
int i;
int fd_or_err;
if (!transaction || !client) {
return -EINVAL;
}
/* If triggered by trigger_condition */
if (lwis_triggered_by_condition(transaction)) {
/* Initialize all placeholder fences in the trigger_condition */
for (i = 0; i < info->trigger_condition.num_nodes; i++) {
if (info->trigger_condition.trigger_nodes[i].type ==
LWIS_TRIGGER_FENCE_PLACEHOLDER) {
fd_or_err = lwis_fence_create(lwis_dev);
if (fd_or_err < 0) {
return fd_or_err;
}
info->trigger_condition.trigger_nodes[i].fence_fd = fd_or_err;
}
}
}
/* Initialize completion fence if one is requested */
if (info->completion_fence_fd == LWIS_CREATE_COMPLETION_FENCE) {
fd_or_err = lwis_fence_create(client->lwis_dev);
if (fd_or_err < 0) {
return fd_or_err;
}
info->completion_fence_fd = fd_or_err;
}
return 0;
}
int lwis_add_completion_fence(struct lwis_client *client, struct lwis_transaction *transaction)
{
struct file *fp;
struct lwis_fence *lwis_fence;
struct lwis_fence_pending_signal *fence_pending_signal;
struct lwis_device *lwis_dev = client->lwis_dev;
int fence_fd = transaction->info.completion_fence_fd;
/* If completion fence is not requested, we can safely return */
if (fence_fd == LWIS_NO_COMPLETION_FENCE) {
return 0;
}
/* If completion fence is requested but not initialized, we cannot continue */
if (fence_fd == LWIS_CREATE_COMPLETION_FENCE) {
dev_err(lwis_dev->dev,
"Cannot add uninitialized completion fence to transaction\n");
return -EPERM;
}
fp = fget(fence_fd);
if (fp == NULL) {
dev_err(lwis_dev->dev, "Failed to find lwis_fence with fd %d\n", fence_fd);
return -EBADF;
}
lwis_fence = fp->private_data;
fence_pending_signal = lwis_fence_pending_signal_create(lwis_fence, fp);
if (fence_pending_signal == NULL) {
return -ENOMEM;
}
list_add(&fence_pending_signal->node, &transaction->completion_fence_list);
if (lwis_fence_debug) {
dev_info(client->lwis_dev->dev,
"lwis_fence transaction id %llu add completion fence fd %d ",
transaction->info.id, lwis_fence->fd);
}
return 0;
}
struct lwis_fence_pending_signal *lwis_fence_pending_signal_create(struct lwis_fence *fence,
struct file *fp)
{
struct lwis_fence_pending_signal *pending_fence_signal =
kmalloc(sizeof(struct lwis_fence_pending_signal), GFP_ATOMIC);
if (!pending_fence_signal) {
return NULL;
}
pending_fence_signal->fp = fp;
pending_fence_signal->fence = fence;
pending_fence_signal->pending_status = LWIS_FENCE_STATUS_NOT_SIGNALED;
return pending_fence_signal;
}
void lwis_fences_pending_signal_emit(struct lwis_device *lwis_device,
struct list_head *pending_fences)
{
int ret;
struct lwis_fence_pending_signal *pending_fence;
struct list_head *it_fence, *it_fence_tmp;
list_for_each_safe (it_fence, it_fence_tmp, pending_fences) {
pending_fence = list_entry(it_fence, struct lwis_fence_pending_signal, node);
ret = lwis_fence_signal(pending_fence->fence, pending_fence->pending_status);
if (ret) {
dev_err(lwis_device->dev, "Failed signaling fence with fd %d",
pending_fence->fence->fd);
}
list_del(&pending_fence->node);
fput(pending_fence->fp);
kfree(pending_fence);
}
}
void lwis_pending_fences_move_all(struct lwis_device *lwis_device,
struct lwis_transaction *transaction,
struct list_head *pending_fences, int error_code)
{
struct lwis_fence_pending_signal *pending_fence, *temp;
/* For each fence in transaction's signal list, move to pending_fences for signaling */
list_for_each_entry_safe (pending_fence, temp, &transaction->completion_fence_list, node) {
pending_fence->pending_status = error_code;
list_move_tail(&pending_fence->node, pending_fences);
}
}