static struct work_struct wlbtd_work;
#endif
+#include "scsc_lerna.h"
+
#include <asm/page.h>
#include <scsc/api/bt_audio.h>
MM_FW_CONFIG = 5,
MM_HALT_RSP = 6,
MM_FM_RADIO_CONFIG = 7,
+ MM_LERNA_CONFIG = 8
} ma_msg;
/**
break;
case MM_HALT_RSP:
complete(&mxman->mm_msg_halt_rsp_completion);
- SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP message from the firmware");
+ SCSC_TAG_INFO(MXMAN, "Received MM_HALT_RSP message from the firmware\n");
+ break;
+ case MM_LERNA_CONFIG:
+ /* Message response to a firmware configuration query. */
+ SCSC_TAG_INFO(MXMAN, "Received MM_LERNA_CONFIG message from firmware\n");
+ scsc_lerna_response(message);
break;
default:
/* HERE: Unknown message, raise fault */
#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000
mxman_create_sysfs_memdump();
#endif
+ scsc_lerna_init();
}
void mxman_deinit(struct mxman *mxman)
{
+ scsc_lerna_deinit();
#if defined(ANDROID_VERSION) && ANDROID_VERSION >= 90000
mxman_destroy_sysfs_memdump();
#endif
return blocking_notifier_chain_unregister(&firmware_chain, nb);
}
EXPORT_SYMBOL(mxman_unregister_firmware_notifier);
+
+
+int mxman_lerna_send(struct mxman *mxman, void *message, u32 message_size)
+{
+ struct srvman *srvman = NULL;
+
+ /* May be called when WLBT is off, so find the context in this case */
+ if (!mxman)
+ mxman = active_mxman;
+
+ if (!active_mxman) {
+ SCSC_TAG_ERR(MXMAN, "No active MXMAN\n");
+ return -EINVAL;
+ }
+
+ if (!message || (message_size == 0)) {
+ SCSC_TAG_INFO(MXMAN, "No lerna request provided.\n");
+ return 0;
+ }
+
+ mutex_lock(&active_mxman->mxman_mutex);
+ srvman = scsc_mx_get_srvman(active_mxman->mx);
+ if (srvman && srvman->error) {
+ mutex_unlock(&active_mxman->mxman_mutex);
+ SCSC_TAG_INFO(MXMAN, "Lerna configuration called during error - ignore\n");
+ return 0;
+ }
+
+ if (active_mxman->mxman_state == MXMAN_STATE_STARTED) {
+ SCSC_TAG_INFO(MXMAN, "MM_LERNA_CONFIG\n");
+ mxmgmt_transport_send(scsc_mx_get_mxmgmt_transport(active_mxman->mx),
+ MMTRANS_CHAN_ID_MAXWELL_MANAGEMENT, message,
+ message_size);
+ mutex_unlock(&active_mxman->mxman_mutex);
+ return 0;
+ }
+
+ SCSC_TAG_INFO(MXMAN, "MXMAN is NOT STARTED...cannot send MM_LERNA_CONFIG msg.\n");
+ mutex_unlock(&active_mxman->mxman_mutex);
+ return -EAGAIN;
+}
--- /dev/null
+/****************************************************************************
+ *
+ * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ ****************************************************************************/
+
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h> /* Required for copy_to_user. */
+#include <linux/completion.h>
+#include <linux/atomic.h>
+
+#include <scsc/scsc_logring.h>
+
+#include "mxman.h"
+#include "mxmgmt_transport_format.h" /* Required for MXMGR_MESSAGE_PAYLOAD_SIZE. */
+
+#define DEVICE_NAME "lerna"
+#define DEVICE_CLASS "scsc_config"
+#define DEVICE_COUNT (1)
+
+static const void *scsc_lerna_pending;
+#define SCSC_LERNA_WAIT_TIMEOUT (2000)
+static DECLARE_COMPLETION(scsc_lerna_wait);
+
+/**
+ * MSMGR_MESSAGE_PAYLOAD_SIZE is not a nice power of 2, so use sizeof(msmgr_message)
+ * just for something more aesthetically pleasing.
+ */
+#define SCSC_LERNA_BUFFER_SIZE (sizeof(struct mxmgr_message))
+static uint8_t scsc_lerna_request_buffer[SCSC_LERNA_BUFFER_SIZE];
+static uint8_t scsc_lerna_response_buffer[SCSC_LERNA_BUFFER_SIZE];
+
+static dev_t scsc_lerna_device_id;
+static struct class *scsc_lerna_class_p;
+static struct device *scsc_lerna_device_p;
+static struct cdev scsc_lerna_cdev;
+
+static int scsc_lerna_chardev_open(struct inode *inodep, struct file *filep);
+static ssize_t scsc_lerna_chardev_read(struct file *filep, char *buffer, size_t len, loff_t *offset);
+static ssize_t scsc_lerna_chardev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset);
+static int scsc_lerna_chardev_release(struct inode *inodep, struct file *filep);
+
+static struct file_operations scsc_lerna_fops = {
+ .open = scsc_lerna_chardev_open,
+ .read = scsc_lerna_chardev_read,
+ .write = scsc_lerna_chardev_write,
+ .release = scsc_lerna_chardev_release,
+};
+
+static atomic_t scsc_lerna_atomic;
+
+struct scsc_lerna_cmd_header {
+ uint8_t magic_number; /* Set to 0x08. */
+ uint8_t cid; /* Action command identifier. */
+ uint16_t payload_length; /* Payload length. 0 for value query. */
+ uint16_t psid; /* PSID to query. */
+ uint8_t row_index; /* Row index, or 0 for non-table querying. */
+ uint8_t group_index; /* Group index, or 0 for default (group not assigned). */
+};
+
+static int scsc_lerna_chardev_open(struct inode *inodep, struct file *filep)
+{
+ (void)inodep;
+ (void)filep;
+
+ if (atomic_inc_return(&scsc_lerna_atomic) > 1) {
+ atomic_dec(&scsc_lerna_atomic);
+ /* Someone already has this open. Denied. */
+ SCSC_TAG_DEBUG(LERNA, "character device busy, try again later.\n");
+ return -EBUSY;
+ }
+
+ SCSC_TAG_DEBUG(LERNA, "opening lerna character device.\n");
+ return 0;
+}
+
+static ssize_t scsc_lerna_chardev_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
+{
+ const struct scsc_lerna_cmd_header *header;
+ unsigned long wait_result;
+ ssize_t read_count;
+ int error_count;
+
+ (void)filep;
+ (void)offset;
+
+ wait_result = wait_for_completion_timeout(&scsc_lerna_wait, msecs_to_jiffies(SCSC_LERNA_WAIT_TIMEOUT));
+ if (wait_result == 0) {
+ SCSC_TAG_ERR(LERNA, "read timeout; firmware not responding, or read without write.\n");
+ return -ETIMEDOUT;
+ }
+
+ if (!scsc_lerna_pending) {
+ /* Pointer is NULL, indicating that a reply hasn't been sent from firmware. */
+ SCSC_TAG_DEBUG(LERNA, "pending reply is null.\n");
+ return -ENOMSG;
+ }
+
+ header = (const struct scsc_lerna_cmd_header *)(scsc_lerna_pending);
+ read_count = sizeof(struct scsc_lerna_cmd_header) + header->payload_length;
+
+ /* Make sure there's enough space to read out the buffer. */
+ if (len < read_count) {
+ SCSC_TAG_ERR(LERNA, "insufficient buffer space supplied for read.\n");
+ return -ENOBUFS;
+ }
+
+ error_count = copy_to_user(buffer, scsc_lerna_pending, read_count);
+
+ if (error_count) {
+ SCSC_TAG_ERR(LERNA, "could not read from lerna character device.\n");
+ return -EFAULT;
+ }
+
+ SCSC_TAG_DEBUG(LERNA, "read buffer of size: %lu\n", read_count);
+ /* Value was read out, and is no longer considered valid. Need to write before another read. */
+ scsc_lerna_pending = NULL;
+ return read_count;
+}
+
+static ssize_t scsc_lerna_chardev_write(struct file *filep, const char *buffer, size_t len, loff_t *offset)
+{
+ SCSC_TAG_DEBUG(LERNA, "writing buffer of size: %lu\n", len);
+ /* At a minimum, any request (read or write) must include a command header. */
+ if (len >= sizeof(struct scsc_lerna_cmd_header)) {
+ /* Header at least fits, but maybe a write value wants more... */
+ if (len <= SCSC_LERNA_BUFFER_SIZE) {
+ copy_from_user(scsc_lerna_request_buffer, buffer, len);
+ mxman_lerna_send(NULL, scsc_lerna_request_buffer, len);
+ } else {
+ /* Message size too long, don't write anything. */
+ return -EMSGSIZE;
+ }
+ } else {
+ return -EBADR;
+ }
+
+ return len;
+}
+
+static int scsc_lerna_chardev_release(struct inode *inodep, struct file *filep)
+{
+ (void)inodep;
+ (void)filep;
+ if (atomic_read(&scsc_lerna_atomic) == 0) {
+ SCSC_TAG_ALERT(LERNA, "character device release without open.\n");
+ } else {
+ /* Done with the character device, release the lock on it. */
+ atomic_dec(&scsc_lerna_atomic);
+ }
+
+ SCSC_TAG_DEBUG(LERNA, "lerna character device closed.\n");
+ return 0;
+}
+
+
+int scsc_lerna_init(void)
+{
+ int result;
+
+ /**
+ * Reset important globals to some kind of sane value. This should be done
+ * whenever the module is loaded explicitly to be sure global values haven't
+ * been previously trashed.
+ */
+ scsc_lerna_device_id = 0;
+ scsc_lerna_class_p = NULL;
+ scsc_lerna_device_p = NULL;
+
+ /* Make sure to initialise the atomic used to lock char device access. */
+ atomic_set(&scsc_lerna_atomic, 0);
+
+ /**
+ * Allocate device id(s) for the character device. Use alloc_register_chrdev
+ * because this is the new way of doing things, and it will dynamically allocate
+ * a major number. Returns non-zero on failure.
+ */
+ result = alloc_chrdev_region(&scsc_lerna_device_id, 0, DEVICE_COUNT, DEVICE_NAME);
+ if (result) {
+ /* Failure to register char dev, auto fail to initialise module. */
+ SCSC_TAG_ALERT(LERNA, "lerna failed to register character device.\n");
+ return result;
+ }
+
+ scsc_lerna_class_p = class_create(THIS_MODULE, DEVICE_CLASS);
+ if (IS_ERR(scsc_lerna_class_p)) {
+ /* Could not create class, failure, remember to unregister device id(s). */
+ unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT);
+ SCSC_TAG_ALERT(LERNA, "lerna failed to create character class.\n");
+ return PTR_ERR(scsc_lerna_class_p);
+ }
+
+ scsc_lerna_device_p = device_create(scsc_lerna_class_p, NULL, scsc_lerna_device_id, NULL, DEVICE_NAME);
+ if (IS_ERR(scsc_lerna_device_p)) {
+ class_destroy(scsc_lerna_class_p);
+ unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT);
+ SCSC_TAG_ALERT(LERNA, "lerna failed to create character device.\n");
+ return PTR_ERR(scsc_lerna_device_p);
+ }
+
+ /**
+ * At this point, the device is registered, along with class definition. The character device
+ * itself can now be initialised to provide the kernel with callback information for various
+ * actions taken on the device.
+ */
+ cdev_init(&scsc_lerna_cdev, &scsc_lerna_fops);
+ scsc_lerna_cdev.owner = THIS_MODULE;
+
+ result = cdev_add(&scsc_lerna_cdev, scsc_lerna_device_id, DEVICE_COUNT);
+ if (result) {
+ /* Failure to add character device to file system. */
+ cdev_del(&scsc_lerna_cdev);
+ class_destroy(scsc_lerna_class_p);
+ unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT);
+ SCSC_TAG_ALERT(LERNA, "lerna failed to add character device.\n");
+ return result;
+ }
+ /* At this point, the cdev is live and can be used. */
+
+ SCSC_TAG_INFO(LERNA, "lerna intialisation complete.\n");
+ return 0; /* 0 for module loaded, non-zero for module load failure. */
+}
+
+void scsc_lerna_deinit(void)
+{
+ /* Character device needs deleting. */
+ cdev_del(&scsc_lerna_cdev);
+
+ /* Destroy device. */
+ device_destroy(scsc_lerna_class_p, scsc_lerna_device_id);
+
+ /* Unregister the device class. Not sure if this means that a register earlier is required. */
+ class_unregister(scsc_lerna_class_p);
+
+ /* Destroy created class. Be careful of the order this is called in. */
+ class_destroy(scsc_lerna_class_p);
+
+ /**
+ * Don't forget to unregister device id(s). Major number is dynamically allocated,
+ * so the base id is remembered and passed along to the unregister here.
+ */
+ unregister_chrdev_region(scsc_lerna_device_id, DEVICE_COUNT);
+
+ SCSC_TAG_INFO(LERNA, "lerna shutdown complete.\n");
+}
+
+void scsc_lerna_response(const void *message)
+{
+ /**
+ * Buffer the response from the firmware so that future messages from firmware
+ * don't overwrite this accidentally. This means async messages are allowed while
+ * waiting for the character device read from userspace, without impacting lerna's
+ * request/response communications.
+ */
+ const struct scsc_lerna_cmd_header *header;
+ ssize_t read_count;
+
+ if (message != NULL) {
+ header = (const struct scsc_lerna_cmd_header *)(message);
+ read_count = sizeof(struct scsc_lerna_cmd_header) + header->payload_length;
+
+ if (read_count <= SCSC_LERNA_BUFFER_SIZE) {
+ memcpy(scsc_lerna_response_buffer, message, read_count);
+ scsc_lerna_pending = scsc_lerna_response_buffer;
+ } else {
+ SCSC_TAG_DEBUG(LERNA, "readout too large for response buffering.\n");
+ /* No response possible, let the userspace application deal with it. */
+ scsc_lerna_pending = NULL;
+ }
+ }
+ complete(&scsc_lerna_wait);
+}