280 lines
9.2 KiB
C
Executable File
280 lines
9.2 KiB
C
Executable File
/****************************************************************************
|
|
*
|
|
* 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) {
|
|
if (copy_from_user(scsc_lerna_request_buffer, buffer, len)) {
|
|
SCSC_TAG_ERR(LERNA, "copy_from_user failed.\n");
|
|
return -EFAULT;
|
|
}
|
|
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);
|
|
}
|