[NEUS7920-133]wlbt: Lerna character device
authorGarry Rank <g.rank@samsung.com>
Wed, 20 Mar 2019 13:51:41 +0000 (13:51 +0000)
committerYoungmin Nam <youngmin.nam@samsung.com>
Tue, 21 May 2019 10:45:22 +0000 (19:45 +0900)
Create character device for userspace <-> firmware configuration
communications.

Change-Id: I45662d621280b0cec3a973f5489333c6c6103b57
SCSC-Bug-Id: SSB-50557

drivers/misc/samsung/scsc/Makefile
drivers/misc/samsung/scsc/mxman.c
drivers/misc/samsung/scsc/mxman.h
drivers/misc/samsung/scsc/mxmgmt_transport_format.h
drivers/misc/samsung/scsc/scsc_lerna.c [new file with mode: 0644]
drivers/misc/samsung/scsc/scsc_lerna.h [new file with mode: 0644]
include/scsc/scsc_logring.h

index 8f3335003afdc172386d03a4d206b0046f1bd709..a5fdb79ba5ee761c88dca5e3783add543356fc47 100644 (file)
@@ -87,7 +87,8 @@ scsc_mx-$(CONFIG_SCSC_CORE_CM) += \
        mifstream.o \
        mxmgmt_transport.o \
        gdb_transport.o \
-       mx140_file.o
+       mx140_file.o \
+       scsc_lerna.o
 
 # Maxwell logger
 scsc_mx-$(CONFIG_SCSC_MXLOGGER) += mxlogger.o
index 525b20847bc5d03fe00faa0df2c76a0d1324422a..fb506f5efe963c505b4c5682f5623178ed69e581 100755 (executable)
@@ -50,6 +50,8 @@
 static struct work_struct      wlbtd_work;
 #endif
 
+#include "scsc_lerna.h"
+
 #include <asm/page.h>
 #include <scsc/api/bt_audio.h>
 
@@ -344,6 +346,7 @@ enum {
        MM_FW_CONFIG = 5,
        MM_HALT_RSP = 6,
        MM_FM_RADIO_CONFIG = 7,
+       MM_LERNA_CONFIG = 8
 } ma_msg;
 
 /**
@@ -625,7 +628,12 @@ static void mxman_message_handler(const void *message, void *data)
                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 */
@@ -1999,10 +2007,12 @@ void mxman_init(struct mxman *mxman, struct scsc_mx *mx)
 #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
@@ -2377,3 +2387,44 @@ int mxman_unregister_firmware_notifier(struct notifier_block *nb)
        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;
+}
index 7614d1b19ec5636f6ff776ec88cb22f647e5d6ce..12afc87071acd654568259984fd4396ea705a223 100755 (executable)
@@ -33,6 +33,7 @@ void mxman_fm_on_halt_ldos_on(void);
 void mxman_fm_on_halt_ldos_off(void);
 int mxman_fm_set_params(struct wlbt_fm_params *params);
 #endif
+int mxman_lerna_send(struct mxman *mxman, void *data, u32 size);
 
 enum mxman_state {
        MXMAN_STATE_STOPPED,
index 32200dfc5f15e99be9b6cbab7fcba6682e2b21ed..291faca9b644b706a3dac3aa7198fcca5af1793a 100644 (file)
@@ -7,7 +7,7 @@
 #ifndef MXMGR_TRANSPORT_FORMAT_H__
 #define MXMGR_TRANSPORT_FORMAT_H__
 
-#define MXMGR_MESSAGE_PAYLOAD_SIZE 7
+#define MXMGR_MESSAGE_PAYLOAD_SIZE 127
 
 /**
  * Layout of messages across the manager transport streams.
diff --git a/drivers/misc/samsung/scsc/scsc_lerna.c b/drivers/misc/samsung/scsc/scsc_lerna.c
new file mode 100644 (file)
index 0000000..7818a46
--- /dev/null
@@ -0,0 +1,276 @@
+/****************************************************************************
+ *
+ * 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);
+}
diff --git a/drivers/misc/samsung/scsc/scsc_lerna.h b/drivers/misc/samsung/scsc/scsc_lerna.h
new file mode 100644 (file)
index 0000000..98fd963
--- /dev/null
@@ -0,0 +1,16 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2014 - 2019 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ ****************************************************************************/
+
+#ifndef _SCSC_LERNA_H
+#define _SCSC_LERNA_H
+
+int scsc_lerna_init(void);
+
+void scsc_lerna_deinit(void);
+
+void scsc_lerna_response(const void *message);
+
+#endif /* _SCSC_LERNA_H */
index 65ce407c88082db647c0caca8d036c6643c37322..0eddc9ec8d5deee0a97b45d4b62d237eff468663 100644 (file)
@@ -89,6 +89,7 @@ enum scsc_logring_tags {
        SLSI_FLOWC,
        SLSI_SMAPPER,
 #endif
+       LERNA,
        TEST_ME,
        MAX_TAG = TEST_ME /* keep it last */
 };