// SPDX-License-Identifier: GPL-2.0 /* * Greybus Component Authentication Protocol (CAP) Driver. * * Copyright 2016 Google Inc. * Copyright 2016 Linaro Ltd. */ #include #include #include #include #include #include "greybus_authentication.h" #include "firmware.h" #define CAP_TIMEOUT_MS 1000 /* * Number of minor devices this driver supports. * There will be exactly one required per Interface. */ #define NUM_MINORS U8_MAX struct gb_cap { struct device *parent; struct gb_connection *connection; struct kref kref; struct list_head node; bool disabled; /* connection getting disabled */ struct mutex mutex; struct cdev cdev; struct device *class_device; dev_t dev_num; }; static struct class *cap_class; static dev_t cap_dev_num; static DEFINE_IDA(cap_minors_map); static LIST_HEAD(cap_list); static DEFINE_MUTEX(list_mutex); static void cap_kref_release(struct kref *kref) { struct gb_cap *cap = container_of(kref, struct gb_cap, kref); kfree(cap); } /* * All users of cap take a reference (from within list_mutex lock), before * they get a pointer to play with. And the structure will be freed only after * the last user has put the reference to it. */ static void put_cap(struct gb_cap *cap) { kref_put(&cap->kref, cap_kref_release); } /* Caller must call put_cap() after using struct gb_cap */ static struct gb_cap *get_cap(struct cdev *cdev) { struct gb_cap *cap; mutex_lock(&list_mutex); list_for_each_entry(cap, &cap_list, node) { if (&cap->cdev == cdev) { kref_get(&cap->kref); goto unlock; } } cap = NULL; unlock: mutex_unlock(&list_mutex); return cap; } static int cap_get_endpoint_uid(struct gb_cap *cap, u8 *euid) { struct gb_connection *connection = cap->connection; struct gb_cap_get_endpoint_uid_response response; int ret; ret = gb_operation_sync(connection, GB_CAP_TYPE_GET_ENDPOINT_UID, NULL, 0, &response, sizeof(response)); if (ret) { dev_err(cap->parent, "failed to get endpoint uid (%d)\n", ret); return ret; } memcpy(euid, response.uid, sizeof(response.uid)); return 0; } static int cap_get_ims_certificate(struct gb_cap *cap, u32 class, u32 id, u8 *certificate, u32 *size, u8 *result) { struct gb_connection *connection = cap->connection; struct gb_cap_get_ims_certificate_request *request; struct gb_cap_get_ims_certificate_response *response; size_t max_size = gb_operation_get_payload_size_max(connection); struct gb_operation *op; int ret; op = gb_operation_create_flags(connection, GB_CAP_TYPE_GET_IMS_CERTIFICATE, sizeof(*request), max_size, GB_OPERATION_FLAG_SHORT_RESPONSE, GFP_KERNEL); if (!op) return -ENOMEM; request = op->request->payload; request->certificate_class = cpu_to_le32(class); request->certificate_id = cpu_to_le32(id); ret = gb_operation_request_send_sync(op); if (ret) { dev_err(cap->parent, "failed to get certificate (%d)\n", ret); goto done; } response = op->response->payload; *result = response->result_code; *size = op->response->payload_size - sizeof(*response); memcpy(certificate, response->certificate, *size); done: gb_operation_put(op); return ret; } static int cap_authenticate(struct gb_cap *cap, u32 auth_type, u8 *uid, u8 *challenge, u8 *result, u8 *auth_response, u32 *signature_size, u8 *signature) { struct gb_connection *connection = cap->connection; struct gb_cap_authenticate_request *request; struct gb_cap_authenticate_response *response; size_t max_size = gb_operation_get_payload_size_max(connection); struct gb_operation *op; int ret; op = gb_operation_create_flags(connection, GB_CAP_TYPE_AUTHENTICATE, sizeof(*request), max_size, GB_OPERATION_FLAG_SHORT_RESPONSE, GFP_KERNEL); if (!op) return -ENOMEM; request = op->request->payload; request->auth_type = cpu_to_le32(auth_type); memcpy(request->uid, uid, sizeof(request->uid)); memcpy(request->challenge, challenge, sizeof(request->challenge)); ret = gb_operation_request_send_sync(op); if (ret) { dev_err(cap->parent, "failed to authenticate (%d)\n", ret); goto done; } response = op->response->payload; *result = response->result_code; *signature_size = op->response->payload_size - sizeof(*response); memcpy(auth_response, response->response, sizeof(response->response)); memcpy(signature, response->signature, *signature_size); done: gb_operation_put(op); return ret; } /* Char device fops */ static int cap_open(struct inode *inode, struct file *file) { struct gb_cap *cap = get_cap(inode->i_cdev); /* cap structure can't get freed until file descriptor is closed */ if (cap) { file->private_data = cap; return 0; } return -ENODEV; } static int cap_release(struct inode *inode, struct file *file) { struct gb_cap *cap = file->private_data; put_cap(cap); return 0; } static int cap_ioctl(struct gb_cap *cap, unsigned int cmd, void __user *buf) { struct cap_ioc_get_endpoint_uid endpoint_uid; struct cap_ioc_get_ims_certificate *ims_cert; struct cap_ioc_authenticate *authenticate; size_t size; int ret; switch (cmd) { case CAP_IOC_GET_ENDPOINT_UID: ret = cap_get_endpoint_uid(cap, endpoint_uid.uid); if (ret) return ret; if (copy_to_user(buf, &endpoint_uid, sizeof(endpoint_uid))) return -EFAULT; return 0; case CAP_IOC_GET_IMS_CERTIFICATE: size = sizeof(*ims_cert); ims_cert = memdup_user(buf, size); if (IS_ERR(ims_cert)) return PTR_ERR(ims_cert); ret = cap_get_ims_certificate(cap, ims_cert->certificate_class, ims_cert->certificate_id, ims_cert->certificate, &ims_cert->cert_size, &ims_cert->result_code); if (!ret && copy_to_user(buf, ims_cert, size)) ret = -EFAULT; kfree(ims_cert); return ret; case CAP_IOC_AUTHENTICATE: size = sizeof(*authenticate); authenticate = memdup_user(buf, size); if (IS_ERR(authenticate)) return PTR_ERR(authenticate); ret = cap_authenticate(cap, authenticate->auth_type, authenticate->uid, authenticate->challenge, &authenticate->result_code, authenticate->response, &authenticate->signature_size, authenticate->signature); if (!ret && copy_to_user(buf, authenticate, size)) ret = -EFAULT; kfree(authenticate); return ret; default: return -ENOTTY; } } static long cap_ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg) { struct gb_cap *cap = file->private_data; struct gb_bundle *bundle = cap->connection->bundle; int ret = -ENODEV; /* * Serialize ioctls. * * We don't want the user to do multiple authentication operations in * parallel. * * This is also used to protect ->disabled, which is used to check if * the connection is getting disconnected, so that we don't start any * new operations. */ mutex_lock(&cap->mutex); if (!cap->disabled) { ret = gb_pm_runtime_get_sync(bundle); if (!ret) { ret = cap_ioctl(cap, cmd, (void __user *)arg); gb_pm_runtime_put_autosuspend(bundle); } } mutex_unlock(&cap->mutex); return ret; } static const struct file_operations cap_fops = { .owner = THIS_MODULE, .open = cap_open, .release = cap_release, .unlocked_ioctl = cap_ioctl_unlocked, }; int gb_cap_connection_init(struct gb_connection *connection) { struct gb_cap *cap; int ret, minor; if (!connection) return 0; cap = kzalloc(sizeof(*cap), GFP_KERNEL); if (!cap) return -ENOMEM; cap->parent = &connection->bundle->dev; cap->connection = connection; mutex_init(&cap->mutex); gb_connection_set_data(connection, cap); kref_init(&cap->kref); mutex_lock(&list_mutex); list_add(&cap->node, &cap_list); mutex_unlock(&list_mutex); ret = gb_connection_enable(connection); if (ret) goto err_list_del; minor = ida_simple_get(&cap_minors_map, 0, NUM_MINORS, GFP_KERNEL); if (minor < 0) { ret = minor; goto err_connection_disable; } /* Add a char device to allow userspace to interact with cap */ cap->dev_num = MKDEV(MAJOR(cap_dev_num), minor); cdev_init(&cap->cdev, &cap_fops); ret = cdev_add(&cap->cdev, cap->dev_num, 1); if (ret) goto err_remove_ida; /* Add a soft link to the previously added char-dev within the bundle */ cap->class_device = device_create(cap_class, cap->parent, cap->dev_num, NULL, "gb-authenticate-%d", minor); if (IS_ERR(cap->class_device)) { ret = PTR_ERR(cap->class_device); goto err_del_cdev; } return 0; err_del_cdev: cdev_del(&cap->cdev); err_remove_ida: ida_simple_remove(&cap_minors_map, minor); err_connection_disable: gb_connection_disable(connection); err_list_del: mutex_lock(&list_mutex); list_del(&cap->node); mutex_unlock(&list_mutex); put_cap(cap); return ret; } void gb_cap_connection_exit(struct gb_connection *connection) { struct gb_cap *cap; if (!connection) return; cap = gb_connection_get_data(connection); device_destroy(cap_class, cap->dev_num); cdev_del(&cap->cdev); ida_simple_remove(&cap_minors_map, MINOR(cap->dev_num)); /* * Disallow any new ioctl operations on the char device and wait for * existing ones to finish. */ mutex_lock(&cap->mutex); cap->disabled = true; mutex_unlock(&cap->mutex); /* All pending greybus operations should have finished by now */ gb_connection_disable(cap->connection); /* Disallow new users to get access to the cap structure */ mutex_lock(&list_mutex); list_del(&cap->node); mutex_unlock(&list_mutex); /* * All current users of cap would have taken a reference to it by * now, we can drop our reference and wait the last user will get * cap freed. */ put_cap(cap); } int cap_init(void) { int ret; cap_class = class_create(THIS_MODULE, "gb_authenticate"); if (IS_ERR(cap_class)) return PTR_ERR(cap_class); ret = alloc_chrdev_region(&cap_dev_num, 0, NUM_MINORS, "gb_authenticate"); if (ret) goto err_remove_class; return 0; err_remove_class: class_destroy(cap_class); return ret; } void cap_exit(void) { unregister_chrdev_region(cap_dev_num, NUM_MINORS); class_destroy(cap_class); ida_destroy(&cap_minors_map); }