| /* Copyright (c) 2009-2013, Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/miscdevice.h> |
| #include <linux/wait.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/string.h> |
| #include <linux/errno.h> |
| #include <linux/init.h> |
| #include <linux/types.h> |
| #include <linux/mm.h> |
| #include <linux/fs.h> |
| #include <linux/err.h> |
| #include <linux/sched.h> |
| #include <linux/wakelock.h> |
| #include <linux/rmt_storage_client.h> |
| #include <linux/debugfs.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/reboot.h> |
| #include <asm/uaccess.h> |
| #include <asm/pgtable.h> |
| #include <mach/msm_rpcrouter.h> |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| #include <mach/sdio_smem.h> |
| #endif |
| #include <mach/msm_smem.h> |
| |
| enum { |
| RMT_STORAGE_EVNT_OPEN = 0, |
| RMT_STORAGE_EVNT_CLOSE, |
| RMT_STORAGE_EVNT_WRITE_BLOCK, |
| RMT_STORAGE_EVNT_GET_DEV_ERROR, |
| RMT_STORAGE_EVNT_WRITE_IOVEC, |
| RMT_STORAGE_EVNT_SEND_USER_DATA, |
| RMT_STORAGE_EVNT_READ_IOVEC, |
| RMT_STORAGE_EVNT_ALLOC_RMT_BUF, |
| } rmt_storage_event; |
| |
| struct shared_ramfs_entry { |
| uint32_t client_id; /* Client id to uniquely identify a client */ |
| uint32_t base_addr; /* Base address of shared RAMFS memory */ |
| uint32_t size; /* Size of the shared RAMFS memory */ |
| uint32_t client_sts; /* This will be initialized to 1 when |
| remote storage RPC client is ready |
| to process requests */ |
| }; |
| struct shared_ramfs_table { |
| uint32_t magic_id; /* Identify RAMFS details in SMEM */ |
| uint32_t version; /* Version of shared_ramfs_table */ |
| uint32_t entries; /* Total number of valid entries */ |
| /* List all entries */ |
| struct shared_ramfs_entry ramfs_entry[MAX_RAMFS_TBL_ENTRIES]; |
| }; |
| |
| struct rmt_storage_client_info { |
| unsigned long cids; |
| struct list_head shrd_mem_list; /* List of shared memory entries */ |
| int open_excl; |
| atomic_t total_events; |
| wait_queue_head_t event_q; |
| struct list_head event_list; |
| struct list_head client_list; /* List of remote storage clients */ |
| /* Lock to protect lists */ |
| spinlock_t lock; |
| /* Wakelock to be acquired when processing requests from modem */ |
| struct wake_lock wlock; |
| atomic_t wcount; |
| struct workqueue_struct *workq; |
| }; |
| |
| struct rmt_storage_kevent { |
| struct list_head list; |
| struct rmt_storage_event event; |
| }; |
| |
| /* Remote storage server on modem */ |
| struct rmt_storage_srv { |
| uint32_t prog; |
| int sync_token; |
| struct platform_driver plat_drv; |
| struct msm_rpc_client *rpc_client; |
| struct delayed_work restart_work; |
| }; |
| |
| /* Remote storage client on modem */ |
| struct rmt_storage_client { |
| uint32_t handle; |
| uint32_t sid; /* Storage ID */ |
| char path[MAX_PATH_NAME]; |
| struct rmt_storage_srv *srv; |
| struct list_head list; |
| }; |
| |
| struct rmt_shrd_mem { |
| struct list_head list; |
| struct rmt_shrd_mem_param param; |
| struct shared_ramfs_entry *smem_info; |
| struct rmt_storage_srv *srv; |
| }; |
| |
| static struct rmt_storage_srv *rmt_storage_get_srv(uint32_t prog); |
| static uint32_t rmt_storage_get_sid(const char *path); |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| static void rmt_storage_sdio_smem_work(struct work_struct *work); |
| #endif |
| |
| static struct rmt_storage_client_info *rmc; |
| struct rmt_storage_srv *rmt_srv; |
| |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| DECLARE_DELAYED_WORK(sdio_smem_work, rmt_storage_sdio_smem_work); |
| #endif |
| |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| #define MDM_LOCAL_BUF_SZ 0xC0000 |
| static struct sdio_smem_client *sdio_smem; |
| #endif |
| |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| struct rmt_storage_op_stats { |
| unsigned long count; |
| ktime_t start; |
| ktime_t min; |
| ktime_t max; |
| ktime_t total; |
| }; |
| struct rmt_storage_stats { |
| char path[MAX_PATH_NAME]; |
| struct rmt_storage_op_stats rd_stats; |
| struct rmt_storage_op_stats wr_stats; |
| }; |
| static struct rmt_storage_stats client_stats[MAX_NUM_CLIENTS]; |
| static struct dentry *stats_dentry; |
| #endif |
| |
| #define MSM_RMT_STORAGE_APIPROG 0x300000A7 |
| #define MDM_RMT_STORAGE_APIPROG 0x300100A7 |
| |
| #define RMT_STORAGE_OP_FINISH_PROC 2 |
| #define RMT_STORAGE_REGISTER_OPEN_PROC 3 |
| #define RMT_STORAGE_REGISTER_WRITE_IOVEC_PROC 4 |
| #define RMT_STORAGE_REGISTER_CB_PROC 5 |
| #define RMT_STORAGE_UN_REGISTER_CB_PROC 6 |
| #define RMT_STORAGE_FORCE_SYNC_PROC 7 |
| #define RMT_STORAGE_GET_SYNC_STATUS_PROC 8 |
| #define RMT_STORAGE_REGISTER_READ_IOVEC_PROC 9 |
| #define RMT_STORAGE_REGISTER_ALLOC_RMT_BUF_PROC 10 |
| |
| #define RMT_STORAGE_OPEN_CB_TYPE_PROC 1 |
| #define RMT_STORAGE_WRITE_IOVEC_CB_TYPE_PROC 2 |
| #define RMT_STORAGE_EVENT_CB_TYPE_PROC 3 |
| #define RMT_STORAGE_READ_IOVEC_CB_TYPE_PROC 4 |
| #define RMT_STORAGE_ALLOC_RMT_BUF_CB_TYPE_PROC 5 |
| |
| #define RAMFS_INFO_MAGICNUMBER 0x654D4D43 |
| #define RAMFS_INFO_VERSION 0x00000001 |
| #define RAMFS_DEFAULT 0xFFFFFFFF |
| |
| /* MSM EFS*/ |
| #define RAMFS_MODEMSTORAGE_ID 0x4D454653 |
| #define RAMFS_SHARED_EFS_RAM_BASE 0x46100000 |
| #define RAMFS_SHARED_EFS_RAM_SIZE (3 * 1024 * 1024) |
| |
| /* MDM EFS*/ |
| #define RAMFS_MDM_STORAGE_ID 0x4D4583A1 |
| /* SSD */ |
| #define RAMFS_SSD_STORAGE_ID 0x00535344 |
| #define RAMFS_SHARED_SSD_RAM_BASE 0x42E00000 |
| #define RAMFS_SHARED_SSD_RAM_SIZE 0x2000 |
| |
| static struct rmt_storage_client *rmt_storage_get_client(uint32_t handle) |
| { |
| struct rmt_storage_client *rs_client; |
| list_for_each_entry(rs_client, &rmc->client_list, list) |
| if (rs_client->handle == handle) |
| return rs_client; |
| return NULL; |
| } |
| |
| static struct rmt_storage_client * |
| rmt_storage_get_client_by_path(const char *path) |
| { |
| struct rmt_storage_client *rs_client; |
| list_for_each_entry(rs_client, &rmc->client_list, list) |
| if (!strncmp(path, rs_client->path, MAX_PATH_NAME)) |
| return rs_client; |
| return NULL; |
| } |
| |
| static struct rmt_shrd_mem_param *rmt_storage_get_shrd_mem(uint32_t sid) |
| { |
| struct rmt_shrd_mem *shrd_mem; |
| struct rmt_shrd_mem_param *shrd_mem_param = NULL; |
| |
| spin_lock(&rmc->lock); |
| list_for_each_entry(shrd_mem, &rmc->shrd_mem_list, list) |
| if (shrd_mem->param.sid == sid) |
| shrd_mem_param = &shrd_mem->param; |
| spin_unlock(&rmc->lock); |
| |
| return shrd_mem_param; |
| } |
| |
| static int rmt_storage_add_shrd_mem(uint32_t sid, uint32_t start, |
| uint32_t size, void *base, |
| struct shared_ramfs_entry *smem_info, |
| struct rmt_storage_srv *srv) |
| { |
| struct rmt_shrd_mem *shrd_mem; |
| |
| shrd_mem = kzalloc(sizeof(struct rmt_shrd_mem), GFP_KERNEL); |
| if (!shrd_mem) |
| return -ENOMEM; |
| shrd_mem->param.sid = sid; |
| shrd_mem->param.start = start; |
| shrd_mem->param.size = size; |
| shrd_mem->param.base = base; |
| shrd_mem->smem_info = smem_info; |
| shrd_mem->srv = srv; |
| |
| spin_lock(&rmc->lock); |
| list_add(&shrd_mem->list, &rmc->shrd_mem_list); |
| spin_unlock(&rmc->lock); |
| return 0; |
| } |
| |
| static struct msm_rpc_client *rmt_storage_get_rpc_client(uint32_t handle) |
| { |
| struct rmt_storage_client *rs_client; |
| |
| rs_client = rmt_storage_get_client(handle); |
| if (!rs_client) |
| return NULL; |
| return rs_client->srv->rpc_client; |
| } |
| |
| static int rmt_storage_validate_iovec(uint32_t handle, |
| struct rmt_storage_iovec_desc *xfer) |
| { |
| struct rmt_storage_client *rs_client; |
| struct rmt_shrd_mem_param *shrd_mem; |
| |
| rs_client = rmt_storage_get_client(handle); |
| if (!rs_client) |
| return -EINVAL; |
| shrd_mem = rmt_storage_get_shrd_mem(rs_client->sid); |
| if (!shrd_mem) |
| return -EINVAL; |
| |
| if ((xfer->data_phy_addr < shrd_mem->start) || |
| ((xfer->data_phy_addr + RAMFS_BLOCK_SIZE * xfer->num_sector) > |
| (shrd_mem->start + shrd_mem->size))) |
| return -EINVAL; |
| return 0; |
| } |
| |
| static int rmt_storage_send_sts_arg(struct msm_rpc_client *client, |
| struct msm_rpc_xdr *xdr, void *data) |
| { |
| struct rmt_storage_send_sts *args = data; |
| |
| xdr_send_uint32(xdr, &args->handle); |
| xdr_send_uint32(xdr, &args->err_code); |
| xdr_send_uint32(xdr, &args->data); |
| return 0; |
| } |
| |
| static void put_event(struct rmt_storage_client_info *rmc, |
| struct rmt_storage_kevent *kevent) |
| { |
| spin_lock(&rmc->lock); |
| list_add_tail(&kevent->list, &rmc->event_list); |
| spin_unlock(&rmc->lock); |
| } |
| |
| static struct rmt_storage_kevent *get_event(struct rmt_storage_client_info *rmc) |
| { |
| struct rmt_storage_kevent *kevent = NULL; |
| |
| spin_lock(&rmc->lock); |
| if (!list_empty(&rmc->event_list)) { |
| kevent = list_first_entry(&rmc->event_list, |
| struct rmt_storage_kevent, list); |
| list_del(&kevent->list); |
| } |
| spin_unlock(&rmc->lock); |
| return kevent; |
| } |
| |
| static int rmt_storage_event_open_cb(struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| uint32_t cid, len, event_type; |
| char *path; |
| int ret; |
| struct rmt_storage_srv *srv; |
| struct rmt_storage_client *rs_client = NULL; |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| struct rmt_storage_stats *stats; |
| #endif |
| |
| srv = rmt_storage_get_srv(event_args->usr_data); |
| if (!srv) |
| return -EINVAL; |
| |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_OPEN) |
| return -1; |
| |
| pr_info("%s: open callback received\n", __func__); |
| |
| ret = xdr_recv_bytes(xdr, (void **)&path, &len); |
| if (ret || !path) { |
| pr_err("%s: Invalid path\n", __func__); |
| if (!ret) |
| ret = -1; |
| goto free_rs_client; |
| } |
| |
| rs_client = rmt_storage_get_client_by_path(path); |
| if (rs_client) { |
| pr_debug("%s: Handle %d found for %s\n", |
| __func__, rs_client->handle, path); |
| event_args->id = RMT_STORAGE_NOOP; |
| cid = rs_client->handle; |
| goto end_open_cb; |
| } |
| |
| rs_client = kzalloc(sizeof(struct rmt_storage_client), GFP_KERNEL); |
| if (!rs_client) { |
| pr_err("%s: Error allocating rmt storage client\n", __func__); |
| ret = -ENOMEM; |
| goto free_path; |
| } |
| |
| memcpy(event_args->path, path, len); |
| rs_client->sid = rmt_storage_get_sid(event_args->path); |
| if (!rs_client->sid) { |
| pr_err("%s: No storage id found for %s\n", __func__, |
| event_args->path); |
| ret = -EINVAL; |
| goto free_path; |
| } |
| strncpy(rs_client->path, event_args->path, MAX_PATH_NAME); |
| |
| cid = find_first_zero_bit(&rmc->cids, sizeof(rmc->cids) * 8); |
| if (cid > MAX_NUM_CLIENTS) { |
| pr_err("%s: Max clients are reached\n", __func__); |
| cid = 0; |
| return cid; |
| } |
| __set_bit(cid, &rmc->cids); |
| pr_info("open partition %s handle=%d\n", event_args->path, cid); |
| |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| stats = &client_stats[cid - 1]; |
| memcpy(stats->path, event_args->path, len); |
| memset(stats->rd_stats, 0, sizeof(struct rmt_storage_op_stats)); |
| memset(stats->wr_stats, 0, sizeof(struct rmt_storage_op_stats)); |
| stats->rd_stats.min.tv64 = KTIME_MAX; |
| stats->wr_stats.min.tv64 = KTIME_MAX; |
| #endif |
| event_args->id = RMT_STORAGE_OPEN; |
| event_args->sid = rs_client->sid; |
| event_args->handle = cid; |
| |
| rs_client->handle = event_args->handle; |
| rs_client->srv = srv; |
| INIT_LIST_HEAD(&rs_client->list); |
| spin_lock(&rmc->lock); |
| list_add_tail(&rs_client->list, &rmc->client_list); |
| spin_unlock(&rmc->lock); |
| |
| end_open_cb: |
| kfree(path); |
| return cid; |
| |
| free_path: |
| kfree(path); |
| free_rs_client: |
| kfree(rs_client); |
| return ret; |
| } |
| |
| struct rmt_storage_close_args { |
| uint32_t handle; |
| }; |
| |
| struct rmt_storage_rw_block_args { |
| uint32_t handle; |
| uint32_t data_phy_addr; |
| uint32_t sector_addr; |
| uint32_t num_sector; |
| }; |
| |
| struct rmt_storage_get_err_args { |
| uint32_t handle; |
| }; |
| |
| struct rmt_storage_user_data_args { |
| uint32_t handle; |
| uint32_t data; |
| }; |
| |
| struct rmt_storage_event_params { |
| uint32_t type; |
| union { |
| struct rmt_storage_close_args close; |
| struct rmt_storage_rw_block_args block; |
| struct rmt_storage_get_err_args get_err; |
| struct rmt_storage_user_data_args user_data; |
| } params; |
| }; |
| |
| static int rmt_storage_parse_params(struct msm_rpc_xdr *xdr, |
| struct rmt_storage_event_params *event) |
| { |
| xdr_recv_uint32(xdr, &event->type); |
| |
| switch (event->type) { |
| case RMT_STORAGE_EVNT_CLOSE: { |
| struct rmt_storage_close_args *args; |
| args = &event->params.close; |
| |
| xdr_recv_uint32(xdr, &args->handle); |
| break; |
| } |
| |
| case RMT_STORAGE_EVNT_WRITE_BLOCK: { |
| struct rmt_storage_rw_block_args *args; |
| args = &event->params.block; |
| |
| xdr_recv_uint32(xdr, &args->handle); |
| xdr_recv_uint32(xdr, &args->data_phy_addr); |
| xdr_recv_uint32(xdr, &args->sector_addr); |
| xdr_recv_uint32(xdr, &args->num_sector); |
| break; |
| } |
| |
| case RMT_STORAGE_EVNT_GET_DEV_ERROR: { |
| struct rmt_storage_get_err_args *args; |
| args = &event->params.get_err; |
| |
| xdr_recv_uint32(xdr, &args->handle); |
| break; |
| } |
| |
| case RMT_STORAGE_EVNT_SEND_USER_DATA: { |
| struct rmt_storage_user_data_args *args; |
| args = &event->params.user_data; |
| |
| xdr_recv_uint32(xdr, &args->handle); |
| xdr_recv_uint32(xdr, &args->data); |
| break; |
| } |
| |
| default: |
| pr_err("%s: unknown event %d\n", __func__, event->type); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int rmt_storage_event_close_cb(struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| struct rmt_storage_event_params *event; |
| struct rmt_storage_close_args *close; |
| struct rmt_storage_client *rs_client; |
| uint32_t event_type; |
| int ret; |
| |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_CLOSE) |
| return -1; |
| |
| pr_debug("%s: close callback received\n", __func__); |
| ret = xdr_recv_pointer(xdr, (void **)&event, |
| sizeof(struct rmt_storage_event_params), |
| rmt_storage_parse_params); |
| |
| if (ret || !event) |
| return -1; |
| |
| close = &event->params.close; |
| event_args->handle = close->handle; |
| event_args->id = RMT_STORAGE_CLOSE; |
| __clear_bit(event_args->handle, &rmc->cids); |
| rs_client = rmt_storage_get_client(event_args->handle); |
| if (rs_client) { |
| list_del(&rs_client->list); |
| kfree(rs_client); |
| } |
| kfree(event); |
| return RMT_STORAGE_NO_ERROR; |
| } |
| |
| static int rmt_storage_event_write_block_cb( |
| struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| struct rmt_storage_event_params *event; |
| struct rmt_storage_rw_block_args *write_block; |
| struct rmt_storage_iovec_desc *xfer; |
| uint32_t event_type; |
| int ret; |
| |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_WRITE_BLOCK) |
| return -1; |
| |
| pr_debug("%s: write block callback received\n", __func__); |
| ret = xdr_recv_pointer(xdr, (void **)&event, |
| sizeof(struct rmt_storage_event_params), |
| rmt_storage_parse_params); |
| |
| if (ret || !event) |
| return -1; |
| |
| write_block = &event->params.block; |
| event_args->handle = write_block->handle; |
| xfer = &event_args->xfer_desc[0]; |
| xfer->sector_addr = write_block->sector_addr; |
| xfer->data_phy_addr = write_block->data_phy_addr; |
| xfer->num_sector = write_block->num_sector; |
| |
| ret = rmt_storage_validate_iovec(event_args->handle, xfer); |
| if (ret) |
| return -1; |
| event_args->xfer_cnt = 1; |
| event_args->id = RMT_STORAGE_WRITE; |
| |
| if (atomic_inc_return(&rmc->wcount) == 1) |
| wake_lock(&rmc->wlock); |
| |
| pr_debug("sec_addr = %u, data_addr = %x, num_sec = %d\n\n", |
| xfer->sector_addr, xfer->data_phy_addr, |
| xfer->num_sector); |
| |
| kfree(event); |
| return RMT_STORAGE_NO_ERROR; |
| } |
| |
| static int rmt_storage_event_get_err_cb(struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| struct rmt_storage_event_params *event; |
| struct rmt_storage_get_err_args *get_err; |
| uint32_t event_type; |
| int ret; |
| |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_GET_DEV_ERROR) |
| return -1; |
| |
| pr_debug("%s: get err callback received\n", __func__); |
| ret = xdr_recv_pointer(xdr, (void **)&event, |
| sizeof(struct rmt_storage_event_params), |
| rmt_storage_parse_params); |
| |
| if (ret || !event) |
| return -1; |
| |
| get_err = &event->params.get_err; |
| event_args->handle = get_err->handle; |
| kfree(event); |
| /* Not implemented */ |
| return -1; |
| |
| } |
| |
| static int rmt_storage_event_user_data_cb(struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| struct rmt_storage_event_params *event; |
| struct rmt_storage_user_data_args *user_data; |
| uint32_t event_type; |
| int ret; |
| |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_SEND_USER_DATA) |
| return -1; |
| |
| pr_info("%s: send user data callback received\n", __func__); |
| ret = xdr_recv_pointer(xdr, (void **)&event, |
| sizeof(struct rmt_storage_event_params), |
| rmt_storage_parse_params); |
| |
| if (ret || !event) |
| return -1; |
| |
| user_data = &event->params.user_data; |
| event_args->handle = user_data->handle; |
| event_args->usr_data = user_data->data; |
| event_args->id = RMT_STORAGE_SEND_USER_DATA; |
| |
| kfree(event); |
| return RMT_STORAGE_NO_ERROR; |
| } |
| |
| static int rmt_storage_event_write_iovec_cb( |
| struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| struct rmt_storage_iovec_desc *xfer; |
| uint32_t i, ent, event_type; |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| struct rmt_storage_stats *stats; |
| #endif |
| |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_WRITE_IOVEC) |
| return -EINVAL; |
| |
| pr_info("%s: write iovec callback received\n", __func__); |
| xdr_recv_uint32(xdr, &event_args->handle); |
| xdr_recv_uint32(xdr, &ent); |
| pr_debug("handle = %d\n", event_args->handle); |
| |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| stats = &client_stats[event_args->handle - 1]; |
| stats->wr_stats.start = ktime_get(); |
| #endif |
| for (i = 0; i < ent; i++) { |
| xfer = &event_args->xfer_desc[i]; |
| xdr_recv_uint32(xdr, &xfer->sector_addr); |
| xdr_recv_uint32(xdr, &xfer->data_phy_addr); |
| xdr_recv_uint32(xdr, &xfer->num_sector); |
| |
| if (rmt_storage_validate_iovec(event_args->handle, xfer)) |
| return -EINVAL; |
| |
| pr_debug("sec_addr = %u, data_addr = %x, num_sec = %d\n", |
| xfer->sector_addr, xfer->data_phy_addr, |
| xfer->num_sector); |
| } |
| xdr_recv_uint32(xdr, &event_args->xfer_cnt); |
| event_args->id = RMT_STORAGE_WRITE; |
| if (atomic_inc_return(&rmc->wcount) == 1) |
| wake_lock(&rmc->wlock); |
| |
| pr_debug("iovec transfer count = %d\n\n", event_args->xfer_cnt); |
| return RMT_STORAGE_NO_ERROR; |
| } |
| |
| static int rmt_storage_event_read_iovec_cb( |
| struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| struct rmt_storage_iovec_desc *xfer; |
| uint32_t i, ent, event_type; |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| struct rmt_storage_stats *stats; |
| #endif |
| |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_READ_IOVEC) |
| return -EINVAL; |
| |
| pr_info("%s: read iovec callback received\n", __func__); |
| xdr_recv_uint32(xdr, &event_args->handle); |
| xdr_recv_uint32(xdr, &ent); |
| pr_debug("handle = %d\n", event_args->handle); |
| |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| stats = &client_stats[event_args->handle - 1]; |
| stats->rd_stats.start = ktime_get(); |
| #endif |
| for (i = 0; i < ent; i++) { |
| xfer = &event_args->xfer_desc[i]; |
| xdr_recv_uint32(xdr, &xfer->sector_addr); |
| xdr_recv_uint32(xdr, &xfer->data_phy_addr); |
| xdr_recv_uint32(xdr, &xfer->num_sector); |
| |
| if (rmt_storage_validate_iovec(event_args->handle, xfer)) |
| return -EINVAL; |
| |
| pr_debug("sec_addr = %u, data_addr = %x, num_sec = %d\n", |
| xfer->sector_addr, xfer->data_phy_addr, |
| xfer->num_sector); |
| } |
| xdr_recv_uint32(xdr, &event_args->xfer_cnt); |
| event_args->id = RMT_STORAGE_READ; |
| if (atomic_inc_return(&rmc->wcount) == 1) |
| wake_lock(&rmc->wlock); |
| |
| pr_debug("iovec transfer count = %d\n\n", event_args->xfer_cnt); |
| return RMT_STORAGE_NO_ERROR; |
| } |
| |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| static int sdio_smem_cb(int event) |
| { |
| pr_debug("%s: Received event %d\n", __func__, event); |
| |
| switch (event) { |
| case SDIO_SMEM_EVENT_READ_DONE: |
| pr_debug("Read done\n"); |
| break; |
| case SDIO_SMEM_EVENT_READ_ERR: |
| pr_err("Read overflow\n"); |
| return -EIO; |
| default: |
| pr_err("Unhandled event\n"); |
| } |
| return 0; |
| } |
| |
| static int rmt_storage_sdio_smem_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| struct rmt_shrd_mem_param *shrd_mem; |
| |
| sdio_smem = container_of(pdev, struct sdio_smem_client, plat_dev); |
| |
| /* SDIO SMEM is supported only for MDM */ |
| shrd_mem = rmt_storage_get_shrd_mem(RAMFS_MDM_STORAGE_ID); |
| if (!shrd_mem) { |
| pr_err("%s: No shared mem entry for sid=0x%08x\n", |
| __func__, (uint32_t)RAMFS_MDM_STORAGE_ID); |
| return -ENOMEM; |
| } |
| sdio_smem->buf = __va(shrd_mem->start); |
| sdio_smem->size = shrd_mem->size; |
| sdio_smem->cb_func = sdio_smem_cb; |
| ret = sdio_smem_register_client(); |
| if (ret) |
| pr_info("%s: Error (%d) registering sdio_smem client\n", |
| __func__, ret); |
| return ret; |
| } |
| |
| static int rmt_storage_sdio_smem_remove(struct platform_device *pdev) |
| { |
| sdio_smem_unregister_client(); |
| queue_delayed_work(rmc->workq, &sdio_smem_work, 0); |
| return 0; |
| } |
| |
| static int sdio_smem_drv_registered; |
| static struct platform_driver sdio_smem_drv = { |
| .probe = rmt_storage_sdio_smem_probe, |
| .remove = rmt_storage_sdio_smem_remove, |
| .driver = { |
| .name = "SDIO_SMEM_CLIENT", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static void rmt_storage_sdio_smem_work(struct work_struct *work) |
| { |
| platform_driver_unregister(&sdio_smem_drv); |
| sdio_smem_drv_registered = 0; |
| } |
| #endif |
| |
| static int rmt_storage_event_alloc_rmt_buf_cb( |
| struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr) |
| { |
| struct rmt_storage_client *rs_client; |
| struct rmt_shrd_mem_param *shrd_mem; |
| uint32_t event_type, handle, size; |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| int ret; |
| #endif |
| xdr_recv_uint32(xdr, &event_type); |
| if (event_type != RMT_STORAGE_EVNT_ALLOC_RMT_BUF) |
| return -EINVAL; |
| |
| pr_info("%s: Alloc rmt buf callback received\n", __func__); |
| xdr_recv_uint32(xdr, &handle); |
| xdr_recv_uint32(xdr, &size); |
| |
| pr_debug("%s: handle=0x%x size=0x%x\n", __func__, handle, size); |
| |
| rs_client = rmt_storage_get_client(handle); |
| if (!rs_client) { |
| pr_err("%s: Unable to find client for handle=%d\n", |
| __func__, handle); |
| return -EINVAL; |
| } |
| |
| rs_client->sid = rmt_storage_get_sid(rs_client->path); |
| if (!rs_client->sid) { |
| pr_err("%s: No storage id found for %s\n", |
| __func__, rs_client->path); |
| return -EINVAL; |
| } |
| |
| shrd_mem = rmt_storage_get_shrd_mem(rs_client->sid); |
| if (!shrd_mem) { |
| pr_err("%s: No shared memory entry found\n", |
| __func__); |
| return -ENOMEM; |
| } |
| if (shrd_mem->size < size) { |
| pr_err("%s: Size mismatch for handle=%d\n", |
| __func__, rs_client->handle); |
| return -EINVAL; |
| } |
| pr_debug("%s: %d bytes at phys=0x%x for handle=%d found\n", |
| __func__, size, shrd_mem->start, rs_client->handle); |
| |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| if (rs_client->srv->prog == MDM_RMT_STORAGE_APIPROG) { |
| if (!sdio_smem_drv_registered) { |
| ret = platform_driver_register(&sdio_smem_drv); |
| if (!ret) |
| sdio_smem_drv_registered = 1; |
| else |
| pr_err("%s: Cant register sdio smem client\n", |
| __func__); |
| } |
| } |
| #endif |
| event_args->id = RMT_STORAGE_NOOP; |
| return (int)shrd_mem->start; |
| } |
| |
| static int handle_rmt_storage_call(struct msm_rpc_client *client, |
| struct rpc_request_hdr *req, |
| struct msm_rpc_xdr *xdr) |
| { |
| int rc; |
| uint32_t result = RMT_STORAGE_NO_ERROR; |
| uint32_t rpc_status = RPC_ACCEPTSTAT_SUCCESS; |
| struct rmt_storage_event *event_args; |
| struct rmt_storage_kevent *kevent; |
| |
| kevent = kzalloc(sizeof(struct rmt_storage_kevent), GFP_KERNEL); |
| if (!kevent) { |
| rpc_status = RPC_ACCEPTSTAT_SYSTEM_ERR; |
| goto out; |
| } |
| event_args = &kevent->event; |
| |
| switch (req->procedure) { |
| case RMT_STORAGE_OPEN_CB_TYPE_PROC: |
| /* client created in cb needs a ref. to its server */ |
| event_args->usr_data = client->prog; |
| /* fall through */ |
| |
| case RMT_STORAGE_WRITE_IOVEC_CB_TYPE_PROC: |
| /* fall through */ |
| |
| case RMT_STORAGE_READ_IOVEC_CB_TYPE_PROC: |
| /* fall through */ |
| |
| case RMT_STORAGE_ALLOC_RMT_BUF_CB_TYPE_PROC: |
| /* fall through */ |
| |
| case RMT_STORAGE_EVENT_CB_TYPE_PROC: { |
| uint32_t cb_id; |
| int (*cb_func)(struct rmt_storage_event *event_args, |
| struct msm_rpc_xdr *xdr); |
| |
| xdr_recv_uint32(xdr, &cb_id); |
| cb_func = msm_rpc_get_cb_func(client, cb_id); |
| |
| if (!cb_func) { |
| rpc_status = RPC_ACCEPTSTAT_GARBAGE_ARGS; |
| kfree(kevent); |
| goto out; |
| } |
| |
| rc = cb_func(event_args, xdr); |
| if (IS_ERR_VALUE(rc)) { |
| pr_err("%s: Invalid parameters received\n", __func__); |
| if (req->procedure == RMT_STORAGE_OPEN_CB_TYPE_PROC) |
| result = 0; /* bad handle to signify err */ |
| else |
| result = RMT_STORAGE_ERROR_PARAM; |
| kfree(kevent); |
| goto out; |
| } |
| result = (uint32_t) rc; |
| break; |
| } |
| |
| default: |
| kfree(kevent); |
| pr_err("%s: unknown procedure %d\n", __func__, req->procedure); |
| rpc_status = RPC_ACCEPTSTAT_PROC_UNAVAIL; |
| goto out; |
| } |
| |
| if (kevent->event.id != RMT_STORAGE_NOOP) { |
| put_event(rmc, kevent); |
| atomic_inc(&rmc->total_events); |
| wake_up(&rmc->event_q); |
| } else |
| kfree(kevent); |
| |
| out: |
| pr_debug("%s: Sending result=0x%x\n", __func__, result); |
| xdr_start_accepted_reply(xdr, rpc_status); |
| xdr_send_uint32(xdr, &result); |
| rc = xdr_send_msg(xdr); |
| if (rc) |
| pr_err("%s: send accepted reply failed: %d\n", __func__, rc); |
| |
| return rc; |
| } |
| |
| static int rmt_storage_open(struct inode *ip, struct file *fp) |
| { |
| int ret = 0; |
| |
| spin_lock(&rmc->lock); |
| if (!rmc->open_excl) |
| rmc->open_excl = 1; |
| else |
| ret = -EBUSY; |
| spin_unlock(&rmc->lock); |
| |
| return ret; |
| } |
| |
| static int rmt_storage_release(struct inode *ip, struct file *fp) |
| { |
| spin_lock(&rmc->lock); |
| rmc->open_excl = 0; |
| spin_unlock(&rmc->lock); |
| |
| return 0; |
| } |
| |
| static long rmt_storage_ioctl(struct file *fp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int ret = 0; |
| struct rmt_storage_kevent *kevent; |
| struct rmt_storage_send_sts status; |
| static struct msm_rpc_client *rpc_client; |
| struct rmt_shrd_mem_param usr_shrd_mem, *shrd_mem; |
| |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| struct rmt_storage_stats *stats; |
| struct rmt_storage_op_stats *op_stats; |
| ktime_t curr_stat; |
| #endif |
| |
| switch (cmd) { |
| |
| case RMT_STORAGE_SHRD_MEM_PARAM: |
| pr_debug("%s: get shared memory parameters ioctl\n", __func__); |
| if (copy_from_user(&usr_shrd_mem, (void __user *)arg, |
| sizeof(struct rmt_shrd_mem_param))) { |
| pr_err("%s: copy from user failed\n\n", __func__); |
| ret = -EFAULT; |
| break; |
| } |
| |
| shrd_mem = rmt_storage_get_shrd_mem(usr_shrd_mem.sid); |
| if (!shrd_mem) { |
| pr_err("%s: invalid sid (0x%x)\n", __func__, |
| usr_shrd_mem.sid); |
| ret = -EFAULT; |
| break; |
| } |
| |
| if (copy_to_user((void __user *)arg, shrd_mem, |
| sizeof(struct rmt_shrd_mem_param))) { |
| pr_err("%s: copy to user failed\n\n", __func__); |
| ret = -EFAULT; |
| } |
| break; |
| |
| case RMT_STORAGE_WAIT_FOR_REQ: |
| pr_debug("%s: wait for request ioctl\n", __func__); |
| if (atomic_read(&rmc->total_events) == 0) { |
| ret = wait_event_interruptible(rmc->event_q, |
| atomic_read(&rmc->total_events) != 0); |
| } |
| if (ret < 0) |
| break; |
| atomic_dec(&rmc->total_events); |
| |
| kevent = get_event(rmc); |
| WARN_ON(kevent == NULL); |
| if (copy_to_user((void __user *)arg, &kevent->event, |
| sizeof(struct rmt_storage_event))) { |
| pr_err("%s: copy to user failed\n\n", __func__); |
| ret = -EFAULT; |
| } |
| kfree(kevent); |
| break; |
| |
| case RMT_STORAGE_SEND_STATUS: |
| pr_info("%s: send status ioctl\n", __func__); |
| if (copy_from_user(&status, (void __user *)arg, |
| sizeof(struct rmt_storage_send_sts))) { |
| pr_err("%s: copy from user failed\n\n", __func__); |
| ret = -EFAULT; |
| if (atomic_dec_return(&rmc->wcount) == 0) |
| wake_unlock(&rmc->wlock); |
| break; |
| } |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| stats = &client_stats[status.handle - 1]; |
| if (status.xfer_dir == RMT_STORAGE_WRITE) |
| op_stats = &stats->wr_stats; |
| else |
| op_stats = &stats->rd_stats; |
| curr_stat = ktime_sub(ktime_get(), op_stats->start); |
| op_stats->total = ktime_add(op_stats->total, curr_stat); |
| op_stats->count++; |
| if (curr_stat.tv64 < stats->min.tv64) |
| op_stats->min = curr_stat; |
| if (curr_stat.tv64 > stats->max.tv64) |
| op_stats->max = curr_stat; |
| #endif |
| pr_debug("%s: \thandle=%d err_code=%d data=0x%x\n", __func__, |
| status.handle, status.err_code, status.data); |
| rpc_client = rmt_storage_get_rpc_client(status.handle); |
| if (rpc_client) |
| ret = msm_rpc_client_req2(rpc_client, |
| RMT_STORAGE_OP_FINISH_PROC, |
| rmt_storage_send_sts_arg, |
| &status, NULL, NULL, -1); |
| else |
| ret = -EINVAL; |
| if (ret < 0) |
| pr_err("%s: send status failed with ret val = %d\n", |
| __func__, ret); |
| if (atomic_dec_return(&rmc->wcount) == 0) |
| wake_unlock(&rmc->wlock); |
| break; |
| |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| struct rmt_storage_sync_recv_arg { |
| int data; |
| }; |
| |
| static int rmt_storage_receive_sync_arg(struct msm_rpc_client *client, |
| struct msm_rpc_xdr *xdr, void *data) |
| { |
| struct rmt_storage_sync_recv_arg *args = data; |
| struct rmt_storage_srv *srv; |
| |
| srv = rmt_storage_get_srv(client->prog); |
| if (!srv) |
| return -EINVAL; |
| xdr_recv_int32(xdr, &args->data); |
| srv->sync_token = args->data; |
| return 0; |
| } |
| |
| static int rmt_storage_force_sync(struct msm_rpc_client *client) |
| { |
| struct rmt_storage_sync_recv_arg args; |
| int rc; |
| rc = msm_rpc_client_req2(client, |
| RMT_STORAGE_FORCE_SYNC_PROC, NULL, NULL, |
| rmt_storage_receive_sync_arg, &args, -1); |
| if (rc) { |
| pr_err("%s: force sync RPC req failed: %d\n", __func__, rc); |
| return rc; |
| } |
| return 0; |
| } |
| |
| struct rmt_storage_sync_sts_arg { |
| int token; |
| }; |
| |
| static int rmt_storage_send_sync_sts_arg(struct msm_rpc_client *client, |
| struct msm_rpc_xdr *xdr, void *data) |
| { |
| struct rmt_storage_sync_sts_arg *req = data; |
| |
| xdr_send_int32(xdr, &req->token); |
| return 0; |
| } |
| |
| static int rmt_storage_receive_sync_sts_arg(struct msm_rpc_client *client, |
| struct msm_rpc_xdr *xdr, void *data) |
| { |
| struct rmt_storage_sync_recv_arg *args = data; |
| |
| xdr_recv_int32(xdr, &args->data); |
| return 0; |
| } |
| |
| static int rmt_storage_get_sync_status(struct msm_rpc_client *client) |
| { |
| struct rmt_storage_sync_recv_arg recv_args; |
| struct rmt_storage_sync_sts_arg send_args; |
| struct rmt_storage_srv *srv; |
| int rc; |
| |
| srv = rmt_storage_get_srv(client->prog); |
| if (!srv) |
| return -EINVAL; |
| |
| if (srv->sync_token < 0) |
| return -EINVAL; |
| |
| send_args.token = srv->sync_token; |
| rc = msm_rpc_client_req2(client, |
| RMT_STORAGE_GET_SYNC_STATUS_PROC, |
| rmt_storage_send_sync_sts_arg, &send_args, |
| rmt_storage_receive_sync_sts_arg, &recv_args, -1); |
| if (rc) { |
| pr_err("%s: sync status RPC req failed: %d\n", __func__, rc); |
| return rc; |
| } |
| return recv_args.data; |
| } |
| |
| static int rmt_storage_mmap(struct file *file, struct vm_area_struct *vma) |
| { |
| unsigned long vsize = vma->vm_end - vma->vm_start; |
| int ret = -EINVAL; |
| |
| vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
| |
| ret = io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, |
| vsize, vma->vm_page_prot); |
| if (ret < 0) |
| pr_err("%s: failed with return val %d\n", __func__, ret); |
| return ret; |
| } |
| |
| struct rmt_storage_reg_cb_args { |
| uint32_t event; |
| uint32_t cb_id; |
| }; |
| |
| static int rmt_storage_arg_cb(struct msm_rpc_client *client, |
| struct msm_rpc_xdr *xdr, void *data) |
| { |
| struct rmt_storage_reg_cb_args *args = data; |
| |
| xdr_send_uint32(xdr, &args->event); |
| xdr_send_uint32(xdr, &args->cb_id); |
| return 0; |
| } |
| |
| static int rmt_storage_reg_cb(struct msm_rpc_client *client, |
| uint32_t proc, uint32_t event, void *callback) |
| { |
| struct rmt_storage_reg_cb_args args; |
| int rc, cb_id; |
| int retries = 10; |
| |
| cb_id = msm_rpc_add_cb_func(client, callback); |
| if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) |
| return cb_id; |
| |
| args.event = event; |
| args.cb_id = cb_id; |
| |
| while (retries) { |
| rc = msm_rpc_client_req2(client, proc, rmt_storage_arg_cb, |
| &args, NULL, NULL, -1); |
| if (rc != -ETIMEDOUT) |
| break; |
| retries--; |
| udelay(1000); |
| } |
| if (rc) |
| pr_err("%s: Failed to register callback for event %d\n", |
| __func__, event); |
| return rc; |
| } |
| |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| static int rmt_storage_stats_open(struct inode *inode, struct file *file) |
| { |
| return 0; |
| } |
| |
| static ssize_t rmt_storage_stats_read(struct file *file, char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| uint32_t tot_clients; |
| char buf[512]; |
| int max, j, i = 0; |
| struct rmt_storage_stats *stats; |
| |
| max = sizeof(buf) - 1; |
| tot_clients = find_first_zero_bit(&rmc->cids, sizeof(rmc->cids)) - 1; |
| |
| for (j = 0; j < tot_clients; j++) { |
| stats = &client_stats[j]; |
| i += scnprintf(buf + i, max - i, "stats for partition %s:\n", |
| stats->path); |
| i += scnprintf(buf + i, max - i, "Min read time: %lld us\n", |
| ktime_to_us(stats->rd_stats.min)); |
| i += scnprintf(buf + i, max - i, "Max read time: %lld us\n", |
| ktime_to_us(stats->rd_stats.max)); |
| i += scnprintf(buf + i, max - i, "Total read time: %lld us\n", |
| ktime_to_us(stats->rd_stats.total)); |
| i += scnprintf(buf + i, max - i, "Total read requests: %ld\n", |
| stats->rd_stats.count); |
| if (stats->count) |
| i += scnprintf(buf + i, max - i, |
| "Avg read time: %lld us\n", |
| div_s64(ktime_to_us(stats->total), |
| stats->rd_stats.count)); |
| |
| i += scnprintf(buf + i, max - i, "Min write time: %lld us\n", |
| ktime_to_us(stats->wr_stats.min)); |
| i += scnprintf(buf + i, max - i, "Max write time: %lld us\n", |
| ktime_to_us(stats->wr_stats.max)); |
| i += scnprintf(buf + i, max - i, "Total write time: %lld us\n", |
| ktime_to_us(stats->wr_stats.total)); |
| i += scnprintf(buf + i, max - i, "Total read requests: %ld\n", |
| stats->wr_stats.count); |
| if (stats->count) |
| i += scnprintf(buf + i, max - i, |
| "Avg write time: %lld us\n", |
| div_s64(ktime_to_us(stats->total), |
| stats->wr_stats.count)); |
| } |
| return simple_read_from_buffer(ubuf, count, ppos, buf, i); |
| } |
| |
| static const struct file_operations debug_ops = { |
| .owner = THIS_MODULE, |
| .open = rmt_storage_stats_open, |
| .read = rmt_storage_stats_read, |
| }; |
| #endif |
| |
| const struct file_operations rmt_storage_fops = { |
| .owner = THIS_MODULE, |
| .open = rmt_storage_open, |
| .unlocked_ioctl = rmt_storage_ioctl, |
| .mmap = rmt_storage_mmap, |
| .release = rmt_storage_release, |
| }; |
| |
| static struct miscdevice rmt_storage_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "rmt_storage", |
| .fops = &rmt_storage_fops, |
| }; |
| |
| static int rmt_storage_get_ramfs(struct rmt_storage_srv *srv) |
| { |
| struct shared_ramfs_table *ramfs_table; |
| struct shared_ramfs_entry *ramfs_entry; |
| int index, ret; |
| |
| if (srv->prog != MSM_RMT_STORAGE_APIPROG) |
| return 0; |
| |
| ramfs_table = smem_alloc(SMEM_SEFS_INFO, |
| sizeof(struct shared_ramfs_table)); |
| |
| if (!ramfs_table) { |
| pr_err("%s: No RAMFS table in SMEM\n", __func__); |
| return -ENOENT; |
| } |
| |
| if ((ramfs_table->magic_id != (u32) RAMFS_INFO_MAGICNUMBER) || |
| (ramfs_table->version != (u32) RAMFS_INFO_VERSION)) { |
| pr_err("%s: Magic / Version mismatch:, " |
| "magic_id=%#x, format_version=%#x\n", __func__, |
| ramfs_table->magic_id, ramfs_table->version); |
| return -ENOENT; |
| } |
| |
| for (index = 0; index < ramfs_table->entries; index++) { |
| ramfs_entry = &ramfs_table->ramfs_entry[index]; |
| if (!ramfs_entry->client_id || |
| ramfs_entry->client_id == (u32) RAMFS_DEFAULT) |
| break; |
| |
| pr_info("%s: RAMFS entry: addr = 0x%08x, size = 0x%08x\n", |
| __func__, ramfs_entry->base_addr, ramfs_entry->size); |
| |
| ret = rmt_storage_add_shrd_mem(ramfs_entry->client_id, |
| ramfs_entry->base_addr, |
| ramfs_entry->size, |
| NULL, |
| ramfs_entry, |
| srv); |
| if (ret) { |
| pr_err("%s: Error (%d) adding shared mem\n", |
| __func__, ret); |
| return ret; |
| } |
| } |
| return 0; |
| } |
| |
| static ssize_t |
| show_force_sync(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct platform_device *pdev; |
| struct rpcsvr_platform_device *rpc_pdev; |
| struct rmt_storage_srv *srv; |
| |
| pdev = container_of(dev, struct platform_device, dev); |
| rpc_pdev = container_of(pdev, struct rpcsvr_platform_device, base); |
| srv = rmt_storage_get_srv(rpc_pdev->prog); |
| if (!srv) { |
| pr_err("%s: Unable to find prog=0x%x\n", __func__, |
| rpc_pdev->prog); |
| return -EINVAL; |
| } |
| |
| return rmt_storage_force_sync(srv->rpc_client); |
| } |
| |
| /* Returns -EINVAL for invalid sync token and an error value for any failure |
| * in RPC call. Upon success, it returns a sync status of 1 (sync done) |
| * or 0 (sync still pending). |
| */ |
| static ssize_t |
| show_sync_sts(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct platform_device *pdev; |
| struct rpcsvr_platform_device *rpc_pdev; |
| struct rmt_storage_srv *srv; |
| |
| pdev = container_of(dev, struct platform_device, dev); |
| rpc_pdev = container_of(pdev, struct rpcsvr_platform_device, base); |
| srv = rmt_storage_get_srv(rpc_pdev->prog); |
| if (!srv) { |
| pr_err("%s: Unable to find prog=0x%x\n", __func__, |
| rpc_pdev->prog); |
| return -EINVAL; |
| } |
| return snprintf(buf, PAGE_SIZE, "%d\n", |
| rmt_storage_get_sync_status(srv->rpc_client)); |
| } |
| |
| /* |
| * Initiate the remote storage force sync and wait until |
| * sync status is done or maximum 4 seconds in the reboot notifier. |
| * Usually RMT storage sync is not taking more than 2 seconds |
| * for encryption and sync. |
| */ |
| #define MAX_GET_SYNC_STATUS_TRIES 200 |
| #define RMT_SLEEP_INTERVAL_MS 20 |
| static int rmt_storage_reboot_call( |
| struct notifier_block *this, unsigned long code, void *cmd) |
| { |
| int ret, count = 0; |
| |
| /* |
| * In recovery mode RMT daemon is not available, |
| * so return from reboot notifier without initiating |
| * force sync. |
| */ |
| spin_lock(&rmc->lock); |
| if (!rmc->open_excl) { |
| spin_unlock(&rmc->lock); |
| msm_rpc_unregister_client(rmt_srv->rpc_client); |
| return NOTIFY_DONE; |
| } |
| |
| spin_unlock(&rmc->lock); |
| switch (code) { |
| case SYS_RESTART: |
| case SYS_HALT: |
| case SYS_POWER_OFF: |
| pr_info("%s: Sending force-sync RPC request\n", __func__); |
| ret = rmt_storage_force_sync(rmt_srv->rpc_client); |
| if (ret) |
| break; |
| |
| do { |
| count++; |
| msleep(RMT_SLEEP_INTERVAL_MS); |
| ret = rmt_storage_get_sync_status(rmt_srv->rpc_client); |
| } while (ret != 1 && count < MAX_GET_SYNC_STATUS_TRIES); |
| |
| if (ret == 1) |
| pr_info("%s: Final-sync successful\n", __func__); |
| else |
| pr_err("%s: Final-sync failed\n", __func__); |
| |
| /* |
| * Check if any ongoing efs_sync triggered just before force |
| * sync is pending. If so, wait for 4sec for completing efs_sync |
| * before unregistring client. |
| */ |
| count = 0; |
| while (count < MAX_GET_SYNC_STATUS_TRIES) { |
| if (atomic_read(&rmc->wcount) == 0) { |
| break; |
| } else { |
| count++; |
| msleep(RMT_SLEEP_INTERVAL_MS); |
| } |
| } |
| if (atomic_read(&rmc->wcount)) |
| pr_err("%s: Efs_sync still incomplete\n", __func__); |
| |
| pr_info("%s: Un-register RMT storage client\n", __func__); |
| msm_rpc_unregister_client(rmt_srv->rpc_client); |
| break; |
| |
| default: |
| break; |
| } |
| return NOTIFY_DONE; |
| } |
| |
| /* |
| * For the RMT storage sync, RPC channels are required. If we do not |
| * give max priority to RMT storage reboot notifier, RPC channels may get |
| * closed before RMT storage sync completed if RPC reboot notifier gets |
| * executed before this remotefs reboot notifier. Hence give the maximum |
| * priority to this reboot notifier. |
| */ |
| static struct notifier_block rmt_storage_reboot_notifier = { |
| .notifier_call = rmt_storage_reboot_call, |
| .priority = INT_MAX, |
| }; |
| |
| static int rmt_storage_init_ramfs(struct rmt_storage_srv *srv) |
| { |
| struct shared_ramfs_table *ramfs_table; |
| |
| if (srv->prog != MSM_RMT_STORAGE_APIPROG) |
| return 0; |
| |
| ramfs_table = smem_alloc(SMEM_SEFS_INFO, |
| sizeof(struct shared_ramfs_table)); |
| |
| if (!ramfs_table) { |
| pr_err("%s: No RAMFS table in SMEM\n", __func__); |
| return -ENOENT; |
| } |
| |
| if (ramfs_table->magic_id == RAMFS_INFO_MAGICNUMBER) { |
| pr_debug("RAMFS table already filled... skipping %s", \ |
| __func__); |
| return 0; |
| } |
| |
| ramfs_table->ramfs_entry[0].client_id = RAMFS_MODEMSTORAGE_ID; |
| ramfs_table->ramfs_entry[0].base_addr = RAMFS_SHARED_EFS_RAM_BASE; |
| ramfs_table->ramfs_entry[0].size = RAMFS_SHARED_EFS_RAM_SIZE; |
| ramfs_table->ramfs_entry[0].client_sts = RAMFS_DEFAULT; |
| |
| ramfs_table->ramfs_entry[1].client_id = RAMFS_SSD_STORAGE_ID; |
| ramfs_table->ramfs_entry[1].base_addr = RAMFS_SHARED_SSD_RAM_BASE; |
| ramfs_table->ramfs_entry[1].size = RAMFS_SHARED_SSD_RAM_SIZE; |
| ramfs_table->ramfs_entry[1].client_sts = RAMFS_DEFAULT; |
| |
| ramfs_table->entries = 2; |
| ramfs_table->version = RAMFS_INFO_VERSION; |
| ramfs_table->magic_id = RAMFS_INFO_MAGICNUMBER; |
| |
| return 0; |
| } |
| |
| static void rmt_storage_set_client_status(struct rmt_storage_srv *srv, |
| int enable) |
| { |
| struct rmt_shrd_mem *shrd_mem; |
| |
| spin_lock(&rmc->lock); |
| list_for_each_entry(shrd_mem, &rmc->shrd_mem_list, list) |
| if (shrd_mem->srv->prog == srv->prog) |
| if (shrd_mem->smem_info) |
| shrd_mem->smem_info->client_sts = !!enable; |
| spin_unlock(&rmc->lock); |
| } |
| |
| static DEVICE_ATTR(force_sync, S_IRUGO | S_IWUSR, show_force_sync, NULL); |
| static DEVICE_ATTR(sync_sts, S_IRUGO | S_IWUSR, show_sync_sts, NULL); |
| static struct attribute *dev_attrs[] = { |
| &dev_attr_force_sync.attr, |
| &dev_attr_sync_sts.attr, |
| NULL, |
| }; |
| static struct attribute_group dev_attr_grp = { |
| .attrs = dev_attrs, |
| }; |
| |
| static void handle_restart_teardown(struct msm_rpc_client *client) |
| { |
| struct rmt_storage_srv *srv; |
| |
| srv = rmt_storage_get_srv(client->prog); |
| if (!srv) |
| return; |
| pr_debug("%s: Modem restart for 0x%08x\n", __func__, srv->prog); |
| cancel_delayed_work_sync(&srv->restart_work); |
| } |
| |
| #define RESTART_WORK_DELAY_MS 1000 |
| |
| static void handle_restart_setup(struct msm_rpc_client *client) |
| { |
| struct rmt_storage_srv *srv; |
| |
| srv = rmt_storage_get_srv(client->prog); |
| if (!srv) |
| return; |
| pr_debug("%s: Scheduling restart for 0x%08x\n", __func__, srv->prog); |
| queue_delayed_work(rmc->workq, &srv->restart_work, |
| msecs_to_jiffies(RESTART_WORK_DELAY_MS)); |
| } |
| |
| static int rmt_storage_reg_callbacks(struct msm_rpc_client *client) |
| { |
| int ret; |
| |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_OPEN_PROC, |
| RMT_STORAGE_EVNT_OPEN, |
| rmt_storage_event_open_cb); |
| if (ret) |
| return ret; |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_CB_PROC, |
| RMT_STORAGE_EVNT_CLOSE, |
| rmt_storage_event_close_cb); |
| if (ret) |
| return ret; |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_CB_PROC, |
| RMT_STORAGE_EVNT_WRITE_BLOCK, |
| rmt_storage_event_write_block_cb); |
| if (ret) |
| return ret; |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_CB_PROC, |
| RMT_STORAGE_EVNT_GET_DEV_ERROR, |
| rmt_storage_event_get_err_cb); |
| if (ret) |
| return ret; |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_WRITE_IOVEC_PROC, |
| RMT_STORAGE_EVNT_WRITE_IOVEC, |
| rmt_storage_event_write_iovec_cb); |
| if (ret) |
| return ret; |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_READ_IOVEC_PROC, |
| RMT_STORAGE_EVNT_READ_IOVEC, |
| rmt_storage_event_read_iovec_cb); |
| if (ret) |
| return ret; |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_CB_PROC, |
| RMT_STORAGE_EVNT_SEND_USER_DATA, |
| rmt_storage_event_user_data_cb); |
| if (ret) |
| return ret; |
| ret = rmt_storage_reg_cb(client, |
| RMT_STORAGE_REGISTER_ALLOC_RMT_BUF_PROC, |
| RMT_STORAGE_EVNT_ALLOC_RMT_BUF, |
| rmt_storage_event_alloc_rmt_buf_cb); |
| if (ret) |
| pr_info("%s: Unable (%d) registering aloc_rmt_buf\n", |
| __func__, ret); |
| |
| pr_debug("%s: Callbacks (re)registered for 0x%08x\n\n", __func__, |
| client->prog); |
| return 0; |
| } |
| |
| static void rmt_storage_restart_work(struct work_struct *work) |
| { |
| struct rmt_storage_srv *srv; |
| int ret; |
| |
| srv = container_of((struct delayed_work *)work, |
| struct rmt_storage_srv, restart_work); |
| if (!rmt_storage_get_srv(srv->prog)) { |
| pr_err("%s: Invalid server\n", __func__); |
| return; |
| } |
| |
| ret = rmt_storage_reg_callbacks(srv->rpc_client); |
| if (!ret) |
| return; |
| |
| pr_err("%s: Error (%d) re-registering callbacks for0x%08x\n", |
| __func__, ret, srv->prog); |
| |
| if (!msm_rpc_client_in_reset(srv->rpc_client)) |
| queue_delayed_work(rmc->workq, &srv->restart_work, |
| msecs_to_jiffies(RESTART_WORK_DELAY_MS)); |
| } |
| |
| static int rmt_storage_probe(struct platform_device *pdev) |
| { |
| struct rpcsvr_platform_device *dev; |
| int ret; |
| |
| dev = container_of(pdev, struct rpcsvr_platform_device, base); |
| rmt_srv = rmt_storage_get_srv(dev->prog); |
| |
| if (!rmt_srv) { |
| pr_err("%s: Invalid prog = %#x\n", __func__, dev->prog); |
| return -ENXIO; |
| } |
| |
| rmt_storage_init_ramfs(rmt_srv); |
| rmt_storage_get_ramfs(rmt_srv); |
| |
| INIT_DELAYED_WORK(&rmt_srv->restart_work, rmt_storage_restart_work); |
| |
| /* Client Registration */ |
| rmt_srv->rpc_client = msm_rpc_register_client2("rmt_storage", |
| dev->prog, dev->vers, 1, |
| handle_rmt_storage_call); |
| if (IS_ERR(rmt_srv->rpc_client)) { |
| pr_err("%s: Unable to register client (prog %.8x vers %.8x)\n", |
| __func__, dev->prog, dev->vers); |
| ret = PTR_ERR(rmt_srv->rpc_client); |
| return ret; |
| } |
| |
| ret = msm_rpc_register_reset_callbacks(rmt_srv->rpc_client, |
| handle_restart_teardown, |
| handle_restart_setup); |
| if (ret) |
| goto unregister_client; |
| |
| pr_info("%s: Remote storage RPC client (0x%x)initialized\n", |
| __func__, dev->prog); |
| |
| /* register server callbacks */ |
| ret = rmt_storage_reg_callbacks(rmt_srv->rpc_client); |
| if (ret) |
| goto unregister_client; |
| |
| /* For targets that poll SMEM, set status to ready */ |
| rmt_storage_set_client_status(rmt_srv, 1); |
| |
| ret = register_reboot_notifier(&rmt_storage_reboot_notifier); |
| if (ret) { |
| pr_err("%s: Failed to register reboot notifier", __func__); |
| goto unregister_client; |
| } |
| |
| ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp); |
| if (ret) |
| pr_err("%s: Failed to create sysfs node: %d\n", __func__, ret); |
| |
| return 0; |
| |
| unregister_client: |
| msm_rpc_unregister_client(rmt_srv->rpc_client); |
| return ret; |
| } |
| |
| static void rmt_storage_client_shutdown(struct platform_device *pdev) |
| { |
| struct rpcsvr_platform_device *dev; |
| struct rmt_storage_srv *srv; |
| |
| dev = container_of(pdev, struct rpcsvr_platform_device, base); |
| srv = rmt_storage_get_srv(dev->prog); |
| rmt_storage_set_client_status(srv, 0); |
| } |
| |
| static void rmt_storage_destroy_rmc(void) |
| { |
| wake_lock_destroy(&rmc->wlock); |
| } |
| |
| static void __init rmt_storage_init_client_info(void) |
| { |
| /* Initialization */ |
| init_waitqueue_head(&rmc->event_q); |
| spin_lock_init(&rmc->lock); |
| atomic_set(&rmc->total_events, 0); |
| INIT_LIST_HEAD(&rmc->event_list); |
| INIT_LIST_HEAD(&rmc->client_list); |
| INIT_LIST_HEAD(&rmc->shrd_mem_list); |
| /* The client expects a non-zero return value for |
| * its open requests. Hence reserve 0 bit. */ |
| __set_bit(0, &rmc->cids); |
| atomic_set(&rmc->wcount, 0); |
| wake_lock_init(&rmc->wlock, WAKE_LOCK_SUSPEND, "rmt_storage"); |
| } |
| |
| static struct rmt_storage_srv msm_srv = { |
| .prog = MSM_RMT_STORAGE_APIPROG, |
| .plat_drv = { |
| .probe = rmt_storage_probe, |
| .shutdown = rmt_storage_client_shutdown, |
| .driver = { |
| .name = "rs300000a7", |
| .owner = THIS_MODULE, |
| }, |
| }, |
| }; |
| |
| static struct rmt_storage_srv mdm_srv = { |
| .prog = MDM_RMT_STORAGE_APIPROG, |
| .plat_drv = { |
| .probe = rmt_storage_probe, |
| .shutdown = rmt_storage_client_shutdown, |
| .driver = { |
| .name = "rs300100a7", |
| .owner = THIS_MODULE, |
| }, |
| }, |
| }; |
| |
| static struct rmt_storage_srv *rmt_storage_get_srv(uint32_t prog) |
| { |
| if (prog == MSM_RMT_STORAGE_APIPROG) |
| return &msm_srv; |
| if (prog == MDM_RMT_STORAGE_APIPROG) |
| return &mdm_srv; |
| return NULL; |
| } |
| |
| |
| static uint32_t rmt_storage_get_sid(const char *path) |
| { |
| if (!strncmp(path, "/boot/modem_fs1", MAX_PATH_NAME)) |
| return RAMFS_MODEMSTORAGE_ID; |
| if (!strncmp(path, "/boot/modem_fs2", MAX_PATH_NAME)) |
| return RAMFS_MODEMSTORAGE_ID; |
| if (!strncmp(path, "/boot/modem_fsg", MAX_PATH_NAME)) |
| return RAMFS_MODEMSTORAGE_ID; |
| if (!strncmp(path, "/q6_fs1_parti_id_0x59", MAX_PATH_NAME)) |
| return RAMFS_MDM_STORAGE_ID; |
| if (!strncmp(path, "/q6_fs2_parti_id_0x5A", MAX_PATH_NAME)) |
| return RAMFS_MDM_STORAGE_ID; |
| if (!strncmp(path, "/q6_fsg_parti_id_0x5B", MAX_PATH_NAME)) |
| return RAMFS_MDM_STORAGE_ID; |
| if (!strncmp(path, "ssd", MAX_PATH_NAME)) |
| return RAMFS_SSD_STORAGE_ID; |
| return 0; |
| } |
| |
| static int __init rmt_storage_init(void) |
| { |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| void *mdm_local_buf; |
| #endif |
| int ret = 0; |
| |
| rmc = kzalloc(sizeof(struct rmt_storage_client_info), GFP_KERNEL); |
| if (!rmc) { |
| pr_err("%s: Unable to allocate memory\n", __func__); |
| return -ENOMEM; |
| } |
| rmt_storage_init_client_info(); |
| |
| ret = platform_driver_register(&msm_srv.plat_drv); |
| if (ret) { |
| pr_err("%s: Unable to register MSM RPC driver\n", __func__); |
| goto rmc_free; |
| } |
| |
| ret = platform_driver_register(&mdm_srv.plat_drv); |
| if (ret) { |
| pr_err("%s: Unable to register MDM RPC driver\n", __func__); |
| goto unreg_msm_rpc; |
| } |
| |
| ret = misc_register(&rmt_storage_device); |
| if (ret) { |
| pr_err("%s: Unable to register misc device %d\n", __func__, |
| MISC_DYNAMIC_MINOR); |
| goto unreg_mdm_rpc; |
| } |
| |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| mdm_local_buf = kzalloc(MDM_LOCAL_BUF_SZ, GFP_KERNEL); |
| if (!mdm_local_buf) { |
| pr_err("%s: Unable to allocate shadow mem\n", __func__); |
| ret = -ENOMEM; |
| goto unreg_misc; |
| } |
| |
| ret = rmt_storage_add_shrd_mem(RAMFS_MDM_STORAGE_ID, |
| __pa(mdm_local_buf), |
| MDM_LOCAL_BUF_SZ, |
| NULL, NULL, &mdm_srv); |
| if (ret) { |
| pr_err("%s: Unable to add shadow mem entry\n", __func__); |
| goto free_mdm_local_buf; |
| } |
| |
| pr_debug("%s: Shadow memory at %p (phys=%lx), %d bytes\n", __func__, |
| mdm_local_buf, __pa(mdm_local_buf), MDM_LOCAL_BUF_SZ); |
| #endif |
| |
| rmc->workq = create_singlethread_workqueue("rmt_storage"); |
| if (!rmc->workq) |
| return -ENOMEM; |
| |
| #ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS |
| stats_dentry = debugfs_create_file("rmt_storage_stats", 0444, 0, |
| NULL, &debug_ops); |
| if (!stats_dentry) |
| pr_err("%s: Failed to create stats debugfs file\n", __func__); |
| #endif |
| return 0; |
| |
| #ifdef CONFIG_MSM_SDIO_SMEM |
| free_mdm_local_buf: |
| kfree(mdm_local_buf); |
| unreg_misc: |
| misc_deregister(&rmt_storage_device); |
| #endif |
| unreg_mdm_rpc: |
| platform_driver_unregister(&mdm_srv.plat_drv); |
| unreg_msm_rpc: |
| platform_driver_unregister(&msm_srv.plat_drv); |
| rmc_free: |
| rmt_storage_destroy_rmc(); |
| kfree(rmc); |
| return ret; |
| } |
| |
| module_init(rmt_storage_init); |
| MODULE_DESCRIPTION("Remote Storage RPC Client"); |
| MODULE_LICENSE("GPL v2"); |