| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (c) 2015-2021, Linaro Limited | |
| */ | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| #include <linux/arm-smccc.h> | |
| #include <linux/errno.h> | |
| #include <linux/slab.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/tee_core.h> | |
| #include "optee_private.h" | |
| struct notif_entry { | |
| struct list_head link; | |
| struct completion c; | |
| u_int key; | |
| }; | |
| static bool have_key(struct optee *optee, u_int key) | |
| { | |
| struct notif_entry *entry; | |
| list_for_each_entry(entry, &optee->notif.db, link) | |
| if (entry->key == key) | |
| return true; | |
| return false; | |
| } | |
| int optee_notif_wait(struct optee *optee, u_int key, u32 timeout) | |
| { | |
| unsigned long flags; | |
| struct notif_entry *entry; | |
| int rc = 0; | |
| if (key > optee->notif.max_key) | |
| return -EINVAL; | |
| entry = kmalloc_obj(*entry); | |
| if (!entry) | |
| return -ENOMEM; | |
| init_completion(&entry->c); | |
| entry->key = key; | |
| spin_lock_irqsave(&optee->notif.lock, flags); | |
| /* | |
| * If the bit is already set it means that the key has already | |
| * been posted and we must not wait. | |
| */ | |
| if (test_bit(key, optee->notif.bitmap)) { | |
| clear_bit(key, optee->notif.bitmap); | |
| goto out; | |
| } | |
| /* | |
| * Check if someone is already waiting for this key. If there is | |
| * it's a programming error. | |
| */ | |
| if (have_key(optee, key)) { | |
| rc = -EBUSY; | |
| goto out; | |
| } | |
| list_add_tail(&entry->link, &optee->notif.db); | |
| /* | |
| * Unlock temporarily and wait for completion. | |
| */ | |
| spin_unlock_irqrestore(&optee->notif.lock, flags); | |
| if (timeout != 0) { | |
| if (!wait_for_completion_timeout(&entry->c, timeout)) | |
| rc = -ETIMEDOUT; | |
| } else { | |
| wait_for_completion(&entry->c); | |
| } | |
| spin_lock_irqsave(&optee->notif.lock, flags); | |
| list_del(&entry->link); | |
| out: | |
| spin_unlock_irqrestore(&optee->notif.lock, flags); | |
| kfree(entry); | |
| return rc; | |
| } | |
| int optee_notif_send(struct optee *optee, u_int key) | |
| { | |
| unsigned long flags; | |
| struct notif_entry *entry; | |
| if (key > optee->notif.max_key) | |
| return -EINVAL; | |
| spin_lock_irqsave(&optee->notif.lock, flags); | |
| list_for_each_entry(entry, &optee->notif.db, link) | |
| if (entry->key == key) { | |
| complete(&entry->c); | |
| goto out; | |
| } | |
| /* Only set the bit in case there where nobody waiting */ | |
| set_bit(key, optee->notif.bitmap); | |
| out: | |
| spin_unlock_irqrestore(&optee->notif.lock, flags); | |
| return 0; | |
| } | |
| int optee_notif_init(struct optee *optee, u_int max_key) | |
| { | |
| spin_lock_init(&optee->notif.lock); | |
| INIT_LIST_HEAD(&optee->notif.db); | |
| optee->notif.bitmap = bitmap_zalloc(max_key, GFP_KERNEL); | |
| if (!optee->notif.bitmap) | |
| return -ENOMEM; | |
| optee->notif.max_key = max_key; | |
| return 0; | |
| } | |
| void optee_notif_uninit(struct optee *optee) | |
| { | |
| bitmap_free(optee->notif.bitmap); | |
| } |