blob: ef0cd5ee984c096add4c911378bc04f825c26730 [file] [log] [blame]
/*
* Google FaceAuth driver
*
* Copyright (C) 2018 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/faceauth.h>
#include <linux/faceauth_shared.h>
#include <linux/firmware.h>
#include <linux/init.h>
#include <linux/module.h>
#include <misc/faceauth_hypx.h>
#include <linux/mfd/abc-pcie.h>
#include <linux/mfd/abc-pcie-notifier.h>
#include <linux/miscdevice.h>
#include <linux/rwsem.h>
#include <linux/time.h>
#include <linux/uaccess.h>
#include <linux/uio.h>
#include <linux/workqueue.h>
#include <linux/mfd/abc-pcie-dma.h>
#include <uapi/linux/abc-pcie-dma.h>
#include <trace/events/systrace.h>
#define DEBUG_DATA_BIN_SIZE (2 * 1024 * 1024)
#define DEBUG_DATA_NUM_BINS 5
/* Timeout in ms */
#define FACEAUTH_TIMEOUT_MS 3000
#define M0_ENROLL_POLLING_PAUSE_US 50000
#define M0_AUTH_POLLING_PAUSE_US 15000
/* Polling interval in us */
#define M0_ENROLL_POLLING_INTERVAL_US 6000
#define M0_POLLING_INTERVAL_US 100000
struct faceauth_data {
/*
* M0 Verbosity Level Encoding
*
* 64 bits wide allocated as follows:
* Bit 0 Errors
* Bits 1-3 Performance
* Bits 4-7 Scheduler
* Bits 8-11 IPU
* Bits 12-15 TPU
* Bits 16-19 Post Process
* Bits 20-63 Reserved
*
* In these slots, the debug levels are specified as follows:
* Level 0: 0b0000
* Level 1: 0b1000
* Level 2: 0b0100
* Level 3: 0b0010
* Level 4: 0b0001
*
* Level 0 means errors only. The other levels yield increasingly more
* information.
*
* To set all these levels, you must write the number in either
* unsigned hexadecimal format or unisigned decimal format
* to a certain file: /d/faceauth/m0_verbosity_level
* If using hexadecimal, you need to put "0x" in front.
* For example:
* either
* adb shell "echo 0x108248 > /d/faceauth/m0_verbosity_level"
* or
* adb shell "echo 1081928 > /d/faceauth/m0_verbosity_level"
* will result in the following settings:
* general errors level 0 (meaning ON)
* performance level 2
* scheduler level 3
* IPU level 1
* TPU level 0
* post process level 4
*/
uint64_t m0_verbosity_level;
#if IS_ENABLED(CONFIG_DEBUG_FS)
struct dentry *debugfs_root;
#endif
bool can_transfer; /* Guarded by rwsem */
uint64_t retry_count;
atomic_t in_use;
struct rw_semaphore rwsem;
struct miscdevice misc_dev;
struct device *device;
struct delayed_work listener_init;
struct notifier_block pcie_link_blocking_nb;
bool is_secure_camera;
bool debug_enabled;
};
static int process_cache_flush_idxs(int16_t *flush_idxs, uint32_t flush_size);
static void clear_debug_data(void);
static void move_debug_data_to_tail(void);
static void enqueue_debug_data(struct faceauth_data *data, uint32_t ab_result);
static int dequeue_debug_data(struct faceauth_debug_data *debug_step_data);
struct {
int head_idx;
int tail_idx;
int count;
char *data_buffer;
} debug_data_queue;
static long faceauth_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
int err = 0;
int polling_interval = M0_POLLING_INTERVAL_US;
struct faceauth_start_data start_step_data = { 0 };
struct faceauth_init_data init_step_data = { 0 };
unsigned long stop;
bool send_images_data;
struct faceauth_data *data = file->private_data;
bool need_trace_end = false;
struct faceauth_debug_data debug_step_data;
unsigned int polling_pause = M0_AUTH_POLLING_PAUSE_US;
down_read(&data->rwsem);
if (!data->can_transfer && cmd != FACEAUTH_DEV_IOC_DEBUG_DATA) {
err = -EIO;
pr_info("Cannot do transfer due to link down\n");
goto exit;
}
switch (cmd) {
case FACEAUTH_DEV_IOC_INIT:
pr_info("el2: faceauth init IOCTL\n");
if (copy_from_user(&init_step_data, (const void __user *)arg,
sizeof(init_step_data))) {
err = -EFAULT;
goto exit;
}
err = el2_faceauth_wait_pil_dma_over();
if (err < 0)
goto exit;
data->is_secure_camera =
init_step_data.features & SECURE_CAMERA_DATA;
err = el2_faceauth_init(data->device, &init_step_data,
data->m0_verbosity_level);
if (err < 0)
goto exit;
break;
case FACEAUTH_DEV_IOC_START:
pr_info("el2: faceauth start IOCTL\n");
if (copy_from_user(&start_step_data, (const void __user *)arg,
sizeof(start_step_data))) {
err = -EFAULT;
goto exit;
}
send_images_data =
start_step_data.operation == COMMAND_ENROLL ||
start_step_data.operation == COMMAND_VALIDATE;
if (send_images_data) {
if (!start_step_data.image_dot_left_size) {
err = -EINVAL;
goto exit;
}
if (!start_step_data.image_dot_right_size) {
err = -EINVAL;
goto exit;
}
if (!start_step_data.image_flood_size) {
err = -EINVAL;
goto exit;
}
if (!start_step_data.image_flood_right_size) {
err = -EINVAL;
goto exit;
}
}
err = process_cache_flush_idxs(
start_step_data.cache_flush_indexes,
start_step_data.cache_flush_size);
if (err)
goto exit;
ATRACE_BLOCK("el2_faceauth_process", {
err = el2_faceauth_process(
data->device, &start_step_data,
data->is_secure_camera);
});
if (err)
goto exit;
/* Check completion flag */
pr_info("Waiting for completion.\n");
if (start_step_data.operation == COMMAND_ENROLL) {
polling_pause = M0_ENROLL_POLLING_PAUSE_US;
polling_interval = M0_ENROLL_POLLING_INTERVAL_US;
}
ATRACE_BLOCK("M0_POLLING_PAUSE_US", {
usleep_range(polling_pause, polling_pause + 1);
});
stop = jiffies + msecs_to_jiffies(FACEAUTH_TIMEOUT_MS);
need_trace_end = true;
ATRACE_BEGIN("get_faceauth_process_result");
for (;;) {
err = el2_faceauth_get_process_result(data->device,
&start_step_data);
if (err) {
pr_err("Failed to get results from EL2 %d\n",
err);
goto exit;
}
if (start_step_data.result !=
WORKLOAD_STATUS_NO_STATUS) {
/* We've got a non-zero status from AB executor
* Faceauth processing is completed
*/
break;
}
if (time_before(stop, jiffies)) {
if (start_step_data.ab_exception_number)
err = -EREMOTEIO;
else
err = -ETIME;
if (data->debug_enabled)
enqueue_debug_data(
data,
WORKLOAD_STATUS_NO_STATUS);
goto exit;
}
usleep_range(polling_interval, polling_interval + 1);
polling_interval = polling_interval > 1000 ?
polling_interval >> 2 :
1000;
}
ATRACE_END();
ATRACE_BEGIN("copy_faceauth_result_to_user");
if (data->debug_enabled)
enqueue_debug_data(data, start_step_data.result);
if (copy_to_user((void __user *)arg, &start_step_data,
sizeof(start_step_data))) {
err = -EFAULT;
goto exit;
}
ATRACE_END();
need_trace_end = false;
break;
case FACEAUTH_DEV_IOC_CLEANUP:
/* In case of EL2 cleanup happens in PIL callback */
/* TODO cleanup Airbrush DRAM */
pr_info("el2: faceauth cleanup IOCTL\n");
el2_faceauth_cleanup(data->device);
data->is_secure_camera = false;
break;
case FACEAUTH_DEV_IOC_DEBUG:
if (!data->debug_enabled) {
err = -EOPNOTSUPP;
break;
}
pr_info("el2: faceauth debug log IOCTL\n");
if (copy_from_user(&debug_step_data, (const void __user *)arg,
sizeof(debug_step_data))) {
err = -EFAULT;
goto exit;
}
ATRACE_BLOCK("el2_faceauth_gather_debug_log", {
err = el2_faceauth_gather_debug_log(
data->device, &debug_step_data);
});
break;
case FACEAUTH_DEV_IOC_DEBUG_DATA:
if (!data->debug_enabled) {
err = -EOPNOTSUPP;
break;
}
pr_info("el2: faceauth debug data IOCTL\n");
if (copy_from_user(&debug_step_data, (const void __user *)arg,
sizeof(debug_step_data))) {
err = -EFAULT;
goto exit;
}
if (debug_step_data.debug_buffer_size < DEBUG_DATA_BIN_SIZE) {
err = -EINVAL;
goto exit;
}
switch (debug_step_data.flags) {
case FACEAUTH_GET_DEBUG_DATA_FROM_FIFO:
err = dequeue_debug_data(&debug_step_data);
break;
case FACEAUTH_GET_DEBUG_DATA_MOST_RECENT:
move_debug_data_to_tail();
err = dequeue_debug_data(&debug_step_data);
break;
case FACEAUTH_GET_DEBUG_DATA_FROM_AB_DRAM:
if (!data->can_transfer) {
pr_info("Cannot do transfer due to link down\n");
err = -EIO;
goto exit;
}
clear_debug_data();
enqueue_debug_data(data, WORKLOAD_STATUS_NO_STATUS);
err = dequeue_debug_data(&debug_step_data);
break;
default:
err = -EINVAL;
break;
}
break;
default:
err = -EOPNOTSUPP;
goto exit;
}
exit:
if (need_trace_end)
ATRACE_END();
up_read(&data->rwsem);
return err;
}
static int faceauth_open(struct inode *inode, struct file *file)
{
struct miscdevice *m = file->private_data;
struct faceauth_data *data =
container_of(m, struct faceauth_data, misc_dev);
file->private_data = data;
if (atomic_cmpxchg(&data->in_use, 0, 1))
return -EBUSY;
return 0;
}
static int faceauth_release(struct inode *inode, struct file *file)
{
struct faceauth_data *data = file->private_data;
atomic_set(&data->in_use, 0);
return 0;
}
static const struct file_operations faceauth_dev_operations = {
.owner = THIS_MODULE,
.unlocked_ioctl = faceauth_dev_ioctl,
.compat_ioctl = faceauth_dev_ioctl,
.open = faceauth_open,
.release = faceauth_release,
};
static int process_cache_flush_idxs(int16_t *flush_idxs, uint32_t flush_size)
{
int i;
if (flush_size > FACEAUTH_MAX_CACHE_FLUSH_SIZE) {
pr_err("Wrong cache flush size\n");
return -EINVAL;
}
if (flush_size < FACEAUTH_MAX_CACHE_FLUSH_SIZE) {
flush_idxs[flush_size] = -1;
}
for (i = 0; i < flush_size; ++i) {
if (flush_idxs[i] < 0
|| flush_idxs[i] >= MAX_CACHE_ENROLLMENT) {
pr_err("Wrong cache flush index\n");
return -EINVAL;
}
}
return 0;
}
static void enqueue_debug_data(struct faceauth_data *data, uint32_t ab_result)
{
void *bin_addr;
int err;
struct faceauth_debug_entry *debug_entry;
bin_addr = debug_data_queue.data_buffer +
(DEBUG_DATA_BIN_SIZE * debug_data_queue.head_idx);
err = el2_gather_debug_data(data->device, bin_addr,
DEBUG_DATA_BIN_SIZE);
if (err) {
pr_err("Debug data gathering failed: %d\n", err);
return;
}
debug_data_queue.head_idx =
(debug_data_queue.head_idx + 1) % DEBUG_DATA_NUM_BINS;
if (debug_data_queue.count == DEBUG_DATA_NUM_BINS) {
debug_data_queue.tail_idx =
(debug_data_queue.tail_idx + 1) % DEBUG_DATA_NUM_BINS;
} else {
debug_data_queue.count++;
}
debug_entry = bin_addr;
debug_entry->status = ab_result;
do_gettimeofday(&(debug_entry->timestamp));
}
static void move_debug_data_to_tail(void)
{
int delta;
if (debug_data_queue.count <= 1)
return;
delta = debug_data_queue.count - 1;
debug_data_queue.count -= delta;
debug_data_queue.tail_idx =
(debug_data_queue.tail_idx + delta) % DEBUG_DATA_NUM_BINS;
}
static void clear_debug_data(void)
{
debug_data_queue.count = 0;
debug_data_queue.head_idx = 0;
debug_data_queue.tail_idx = 0;
}
static int dequeue_debug_data(struct faceauth_debug_data *debug_step_data)
{
void *bin_addr;
if (debug_data_queue.count == 0) {
return -ENODATA;
}
if (debug_step_data->debug_buffer_size < DEBUG_DATA_BIN_SIZE) {
return -ENOBUFS;
}
bin_addr = debug_data_queue.data_buffer +
(DEBUG_DATA_BIN_SIZE * debug_data_queue.tail_idx);
debug_data_queue.tail_idx =
(debug_data_queue.tail_idx + 1) % DEBUG_DATA_NUM_BINS;
debug_data_queue.count--;
if (copy_to_user((void __user *)(debug_step_data->debug_buffer),
bin_addr, DEBUG_DATA_BIN_SIZE)) {
return -EFAULT;
}
return 0;
}
static void faceauth_link_listener_init(struct work_struct *work)
{
struct faceauth_data *data =
container_of(work, struct faceauth_data, listener_init.work);
int err;
err = abc_register_pcie_link_blocking_event(
&data->pcie_link_blocking_nb);
/* TODO: Use retry count to dynamiclly adjust retry timeout */
if (err == -EAGAIN) {
if (data->retry_count % 50 == 0)
pr_info("Retry faceauth link init later.\n");
data->retry_count++;
schedule_delayed_work(&data->listener_init,
msecs_to_jiffies(1000));
} else if (err)
pr_err("CANNOT init link listened event in faceauth driver, err code %d\n",
err);
else
pr_info("Successfully register link listener for faceauth driver");
return;
}
static int faceauth_pcie_blocking_listener(struct notifier_block *nb,
unsigned long action, void *data)
{
struct faceauth_data *dev_data =
container_of(nb, struct faceauth_data, pcie_link_blocking_nb);
if (action & ABC_PCIE_LINK_ENTER_EL2) {
down_read(&dev_data->rwsem);
if (!dev_data->can_transfer)
pr_err("ERROR: Wrong state, receive ENTER_EL2 while link down");
up_read(&dev_data->rwsem);
return NOTIFY_OK;
}
if (action & ABC_PCIE_LINK_EXIT_EL2) {
down_read(&dev_data->rwsem);
if (!dev_data->can_transfer)
pr_err("ERROR: Wrong state, receive EXIT_EL2 while link down");
up_read(&dev_data->rwsem);
return NOTIFY_OK;
}
if (action & ABC_PCIE_LINK_ERROR) {
/*
* Take a reader lock and update the flag as soon as possible.
*/
down_read(&dev_data->rwsem);
dev_data->can_transfer = false;
up_read(&dev_data->rwsem);
return NOTIFY_OK;
}
if (action & ABC_PCIE_LINK_PRE_DISABLE) {
/* Use the writer lock to prevent any incoming reader */
down_write(&dev_data->rwsem);
dev_data->can_transfer = false;
pr_info("All ongoing ioctls are finished, confirm disable");
up_write(&dev_data->rwsem);
return NOTIFY_OK;
}
if (action & ABC_PCIE_LINK_POST_ENABLE) {
/*
* Under this scenerio, this is actually a reader
* There's no need to block any other reader since
* they'll bail out when they got the value.
*/
down_read(&dev_data->rwsem);
dev_data->can_transfer = true;
up_read(&dev_data->rwsem);
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
#if IS_ENABLED(CONFIG_DEBUG_FS)
static int faceauth_m0_verbosity_set(void *ptr, u64 val)
{
struct faceauth_data *data = ptr;
data->m0_verbosity_level = val;
return 0;
}
static int faceauth_m0_verbosity_get(void *ptr, u64 *val)
{
struct faceauth_data *data = ptr;
*val = data->m0_verbosity_level;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_m0_verbosity, faceauth_m0_verbosity_get,
faceauth_m0_verbosity_set, "0x%016llx\n");
static int faceauth_debug_enabled_set(void *ptr, u64 val)
{
struct faceauth_data *data = ptr;
data->debug_enabled = !!val;
return 0;
}
static int faceauth_debug_enabled_get(void *ptr, u64 *val)
{
struct faceauth_data *data = ptr;
*val = data->debug_enabled;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_debug_enabled, faceauth_debug_enabled_get,
faceauth_debug_enabled_set, "%llu\n");
static void faceauth_debugfs_init(struct faceauth_data *data)
{
struct dentry *debugfs_root;
struct dentry *m0_verbosity_level;
struct dentry *debug_enabled;
int err = 0;
debugfs_root = debugfs_create_dir("faceauth", NULL);
if (IS_ERR_OR_NULL(debugfs_root)) {
pr_err("Failed to create faceauth debugfs");
err = -EIO;
goto exit;
}
data->debugfs_root = debugfs_root;
m0_verbosity_level =
debugfs_create_file("m0_verbosity_level", 0660, debugfs_root,
data, &fops_m0_verbosity);
if (!m0_verbosity_level) {
err = -EIO;
goto exit;
}
debug_enabled =
debugfs_create_file("debug_enabled", 0660, debugfs_root,
data, &fops_debug_enabled);
if (!debug_enabled) {
err = -EIO;
goto exit;
}
return;
exit:
debugfs_remove_recursive(debugfs_root);
data->debugfs_root = NULL;
pr_err("faceauth debugfs initialization failed: %d\n", err);
}
static void faceauth_debugfs_remove(struct faceauth_data *data)
{
if (!data->debugfs_root)
return;
debugfs_remove_recursive(data->debugfs_root);
}
#else /* CONFIG_DEBUG_FS */
static void faceauth_debugfs_init(struct faceauth_data *data)
{
}
static void faceauth_debugfs_remove(struct faceauth_data *data)
{
}
#endif /* CONFIG_DEBUG_FS */
static int faceauth_probe(struct platform_device *pdev)
{
int err;
struct faceauth_data *data;
int i;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
init_rwsem(&data->rwsem);
data->device = &pdev->dev;
data->can_transfer = true;
data->retry_count = 0;
data->misc_dev.minor = MISC_DYNAMIC_MINOR,
data->misc_dev.name = "faceauth",
data->misc_dev.fops = &faceauth_dev_operations;
data->pcie_link_blocking_nb.notifier_call =
faceauth_pcie_blocking_listener;
INIT_DELAYED_WORK(&data->listener_init, faceauth_link_listener_init);
schedule_delayed_work(&data->listener_init, msecs_to_jiffies(1000));
err = misc_register(&data->misc_dev);
if (err)
goto exit1;
faceauth_debugfs_init(data);
el2_faceauth_probe(data->device);
clear_debug_data();
debug_data_queue.data_buffer =
vmalloc(DEBUG_DATA_BIN_SIZE * DEBUG_DATA_NUM_BINS);
if (debug_data_queue.data_buffer == NULL) {
err = -ENOMEM;
goto exit3;
}
for (i = 0; i < DEBUG_DATA_NUM_BINS; i++) {
memset(debug_data_queue.data_buffer + (DEBUG_DATA_BIN_SIZE * i),
0, sizeof(struct faceauth_debug_entry));
}
data->debug_enabled = true;
return 0;
exit3:
faceauth_debugfs_remove(data);
misc_deregister(&data->misc_dev);
exit1:
abc_unregister_pcie_link_blocking_event(&data->pcie_link_blocking_nb);
return err;
}
static int faceauth_remove(struct platform_device *pdev)
{
struct faceauth_data *data = platform_get_drvdata(pdev);
el2_faceauth_remove(data->device);
abc_unregister_pcie_link_blocking_event(&data->pcie_link_blocking_nb);
misc_deregister(&data->misc_dev);
faceauth_debugfs_remove(data);
clear_debug_data();
vfree(debug_data_queue.data_buffer);
return 0;
}
static struct platform_driver faceauth_driver = {
.probe = faceauth_probe,
.remove = faceauth_remove,
.driver = {
.name = "faceauth",
.owner = THIS_MODULE,
},
};
struct platform_device *faceauth_pdev;
static int __init faceauth_init(void)
{
int ret;
faceauth_pdev =
platform_device_register_simple("faceauth", -1, NULL, 0);
if (IS_ERR(faceauth_pdev))
return PTR_ERR(faceauth_pdev);
arch_setup_dma_ops(&faceauth_pdev->dev, 0, U64_MAX, NULL, false);
ret = dma_coerce_mask_and_coherent(&faceauth_pdev->dev,
DMA_BIT_MASK(47));
if (ret) {
pr_err("Can't set DMA mask for faceauth device: %d\n", ret);
goto error;
}
ret = platform_driver_register(&faceauth_driver);
if (ret) {
pr_err("Can't register Faceauth driver: %d\n", ret);
goto error;
}
return 0;
error:
platform_device_unregister(faceauth_pdev);
return ret;
}
module_init(faceauth_init);
static void __exit faceauth_exit(void)
{
platform_driver_unregister(&faceauth_driver);
platform_device_unregister(faceauth_pdev);
}
module_exit(faceauth_exit);
MODULE_AUTHOR("Anatol Pomazau <anatol@google.com>, Lei Liu <leliu@google.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Google FaceAuth driver");