/* Copyright (c) 2013-2014, The 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 #include #include #include #include #include #include #include "loopback_smsm.h" static int MOCK_SMSM_NUM_ENTRIES = 8; static int mock_smsm_inited; #define MOCK_SMSM_IPC_PAGES 5 void *mock_smsm_ipc_log_txt; #define MOCK_SMSM_LOG(fmt, args...) \ do { \ if (mock_smsm_ipc_log_txt) { \ ipc_log_string(mock_smsm_ipc_log_txt, fmt, args); \ } \ } while (0) /** * struct mock_smsm_state_cb_info - callback registration context * @cb_list: list head struct to allow addition and removal to lists * @mask: state mask defining when callback should be called * @data: data to be passed as first argument to callback * @notify: callback to be called */ struct mock_smsm_state_cb_info { struct list_head cb_list; uint32_t mask; void *data; void (*notify)(void *data, uint32_t old_state, uint32_t new_state); }; /** * struct mock_smsm_cb_work_struct - struct used to defer callbacks to later * time * @work: worker item used to enqueue this to a workqueue * @data: data to be passed to the callback * @old_state: bitmask of old state when work was queued to be passed to * callback * @new_state: bitmask of new state when work was queued to be passed to * callback * @notify: the callback * * This struct is used to defer state callbacks to another point in time. */ struct mock_smsm_cb_work_struct { struct work_struct work; void *data; uint32_t old_state; uint32_t new_state; void (*notify)(void *data, uint32_t old_state, uint32_t new_state); }; /** * struct mock_smsm_state_info - maintains the state as well as a list of * callbacks * @callbacks: list_head of the state callbacks * @state: bitmask of the state * * This struct maintains the state, as well as a list of callbacks that will * be called at various state changes. */ struct mock_smsm_state_info { struct list_head callbacks; uint32_t state; }; static struct mock_smsm_state_info *mock_smsm_states; static struct workqueue_struct *mock_smsm_cb_wq; DEFINE_SPINLOCK(mock_smsm_entry_lock); DEFINE_SPINLOCK(mock_smsm_inited_lock); static int mock_smsm_cb_init(void); static int mock_smsm_states_alloc(void); static int mock_smsm_cb_wq_init(void); static void mock_smsm_states_entries_init(void); static void mock_smsm_state_cb_execute(uint32_t smsm_entry, uint32_t old_state); static void mock_smsm_cb_handler(struct work_struct *work); static int mock_smsm_entry_is_error(uint32_t smsm_entry); #define DEFAULT_SSLEEP 5 static struct workqueue_struct *dl_init_wq; static void dl_init_handler(struct work_struct *work); /** * struct dl_init_struct - used to initialize downlink * @work: work item used for enqueueing to workqueue * @smsm_entry: the smsm entry whose state should be changed * @clear_mask: bitmask to clear in entry * @set_mask: bitmask to set in entry * * Struct passed to dl_init_handler to initialize downlink. */ struct dl_init_struct { struct work_struct work; unsigned int sleep_secs; uint32_t smsm_entry; uint32_t clear_mask; uint32_t set_mask; }; /** * mock_smsm_change_state() - mocks the call to smsm_change_state() * to provide loopback support * @smsm_entry: smsm_entry * @clear_mask: bits of entry to clear * @set_mask: bits of entry to set * * Mocks the call to smsm_change_state. First applies the clear mask and the * set mask to the smsm_entry specified, calling any applicable state * callbacks that may have been triggered. * * Return: 0 on success, error code otherwise */ int mock_smsm_change_state(uint32_t smsm_entry, uint32_t clear_mask, uint32_t set_mask) { uint32_t old_state; int err = 0; unsigned long flags; err = mock_smsm_entry_is_error(smsm_entry); if (err) { MOCK_SMSM_LOG("%s: invalid smsm_entry=%u\n", __func__, smsm_entry); goto exit; } spin_lock_irqsave(&mock_smsm_entry_lock, flags); old_state = mock_smsm_states[smsm_entry].state; mock_smsm_states[smsm_entry].state &= (~clear_mask); mock_smsm_states[smsm_entry].state |= set_mask; mock_smsm_state_cb_execute(smsm_entry, old_state); spin_unlock_irqrestore(&mock_smsm_entry_lock, flags); exit: return err; } /** * mock_smsm_get_state() - mocks the call to smsm_get_state() * to provide loopback support * @smsm_entry: target smsm_entry * * Mocks the call to smsm_get_state() to provide loopback support. Searches for * the specified smsm entry and returns its state. * * Return: bitmask of state for input enty */ uint32_t mock_smsm_get_state(uint32_t smsm_entry) { uint32_t state; int err = 0; unsigned long flags; err = mock_smsm_entry_is_error(smsm_entry); if (err) { MOCK_SMSM_LOG("%s: invalid smsm_entry=%u\n", __func__, smsm_entry); pr_err("mock_smsm_get_state: Invalid entry %d", smsm_entry); return err; } spin_lock_irqsave(&mock_smsm_entry_lock, flags); state = mock_smsm_states[smsm_entry].state; spin_unlock_irqrestore(&mock_smsm_entry_lock, flags); return state; } /** * mock_smsm_state_cb_register() - mocks the call to smsm_state_cb_register() * to provide loopback support * @smsm_entry: target smsm_entry to register * @mask: bits to register * @notify: callback function to register * @data: data to be passed into callback * * Mocks the call to smsm_state_cb_register() to provide loopback support. * Registers callback for the give bitmask of the given smsm_entry. * * Return: Error code, 0 - inserted new entry, or 1 - updated mask of existing * entry */ int mock_smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask, void (*notify)(void *, uint32_t old_state, uint32_t new_state), void *data) { struct mock_smsm_state_info *state; struct mock_smsm_state_cb_info *cb_info; struct mock_smsm_state_cb_info *cb_found = 0; int ret = 0, err = 0; unsigned long flags; if (err) { MOCK_SMSM_LOG("%s: smsm_entry error\n", __func__); return err; } spin_lock_irqsave(&mock_smsm_entry_lock, flags); state = &mock_smsm_states[smsm_entry]; list_for_each_entry(cb_info, &state->callbacks, cb_list) { if (!ret && (cb_info->notify == notify) && (cb_info->data == data)) { cb_info->mask |= mask; cb_found = cb_info; ret = 1; } } if (!cb_found) { cb_info = kmalloc(sizeof(struct mock_smsm_state_cb_info), GFP_ATOMIC); if (!cb_info) { ret = -ENOMEM; MOCK_SMSM_LOG("%s: cb_info alloc fail, entry %u\n", __func__, smsm_entry); goto cleanup; } cb_info->mask = mask; cb_info->notify = notify; cb_info->data = data; INIT_LIST_HEAD(&cb_info->cb_list); list_add_tail(&cb_info->cb_list, &state->callbacks); } cleanup: spin_unlock_irqrestore(&mock_smsm_entry_lock, flags); return ret; } /** * mock_smsm_state_cb_deregister() - mocks the call to * smsm_state_cb_deregister() to provide loopback support * @smsm_entry: target smsm_entry to deregister * @mask: bits to deregister(if result is 0, callback is removed) * @notify: callback function to deregister * @data: data passed to callback * * Mocks the call to smsm_state_cb_deregister() to provide loopback support. * Deregisters the callback for the mask of the given entry. * * Return: error code, 0 - not found, 1 - updated mask, or 2 - removed callback */ int mock_smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask, void (*notify)(void *, uint32_t, uint32_t), void *data) { struct mock_smsm_state_cb_info *cb_info; struct mock_smsm_state_cb_info *cb_tmp; struct mock_smsm_state_info *state; int ret = 0, err = 0; unsigned long flags; err = mock_smsm_entry_is_error(smsm_entry); if (err) { MOCK_SMSM_LOG("%s: Mock SMSM invalid entry\n", __func__); return err; } spin_lock_irqsave(&mock_smsm_entry_lock, flags); state = &mock_smsm_states[smsm_entry]; list_for_each_entry_safe(cb_info, cb_tmp, &state->callbacks, cb_list) { if (!ret && (cb_info->notify == notify) && (cb_info->data == data)) { cb_info->mask &= ~mask; ret = 1; if (!cb_info->mask) { /* no mask bits set, remove callback */ list_del(&cb_info->cb_list); kfree(cb_info); ret = 2; continue; } } } spin_unlock_irqrestore(&mock_smsm_entry_lock, flags); return ret; } /** * dl_init_handler - handler for initializing downlink * @work: work struct used to enqueue work * * Gathers the dl_init_workstruct and sets the state specified to initialize * downlink. */ static void dl_init_handler(struct work_struct *work) { struct dl_init_struct *init_struct; if (work == NULL) { MOCK_SMSM_LOG("%s: work struct null\n", __func__); return; } init_struct = container_of(work, struct dl_init_struct, work); if (init_struct == NULL) { MOCK_SMSM_LOG("%s: dl_init struct null\n", __func__); return; } ssleep(init_struct->sleep_secs); mock_smsm_change_state(init_struct->smsm_entry, init_struct->clear_mask, init_struct->set_mask); kfree(init_struct); } /** * mock_smsm_init() - initializes all of the mock_smsm functionality * * Initialized the mock_smsm functionality, such as workqueues and ipc * logging. * * Return: 0 on success, error code otherwise */ int mock_smsm_init(void) { int ret = 0; unsigned long flags; struct dl_init_struct *init_struct; mock_smsm_ipc_log_txt = ipc_log_context_create(MOCK_SMSM_IPC_PAGES, "mock_smsm", 0); ret = mock_smsm_cb_init(); if (ret) { pr_err("%s: mock_smsm_cb_init fail:%d\n", __func__, ret); goto exit; } init_struct = (struct dl_init_struct *) kmalloc(sizeof(struct dl_init_struct), GFP_KERNEL); if (init_struct) { INIT_WORK(&(init_struct->work), dl_init_handler); init_struct->sleep_secs = DEFAULT_SSLEEP; init_struct->smsm_entry = SMSM_MODEM_STATE; init_struct->clear_mask = 0; init_struct->set_mask = SMSM_A2_POWER_CONTROL; queue_work(dl_init_wq, &(init_struct->work)); } else { pr_err("%s: init struct alloc fail\n", __func__); ret = -ENOMEM; goto exit; } spin_lock_irqsave(&mock_smsm_inited_lock, flags); mock_smsm_inited = 1; spin_unlock_irqrestore(&mock_smsm_inited_lock, flags); exit: return ret; } /** * mock_smsm_cb_init() - initializes the callback functionality for the * mock_smsm portion of loopback * * Initializes the state callbacks for the mock smsm. * * Return: 0 on success, error code otherwise */ static int mock_smsm_cb_init(void) { int ret = 0; ret = mock_smsm_states_alloc(); if (ret) { MOCK_SMSM_LOG("%s: Mock SMSM state alloc failed\n", __func__); goto exit; } ret = mock_smsm_cb_wq_init(); if (ret) { MOCK_SMSM_LOG("%s:Mock SMSM wq init failed\n", __func__); kfree(mock_smsm_states); goto exit; } mock_smsm_states_entries_init(); exit: return ret; } /** * mock_smsm_states_alloc() - allocates memory for mock_smsm_states * * Allocates memory for mock_smsm_states. * * Return: 0 on success, error code otherwise */ static int mock_smsm_states_alloc(void) { int ret = 0; mock_smsm_states = kmalloc(sizeof(struct mock_smsm_state_info) * MOCK_SMSM_NUM_ENTRIES, GFP_ATOMIC); if (!mock_smsm_states) { MOCK_SMSM_LOG("%s:Mock SMSM state alloc failed\n", __func__); pr_err("%s:Mock SMSM state alloc failed\n", __func__); ret = -ENOMEM; } return ret; } /** * mock_smsm_cb_wq_init() - initializes the callback workqueue * * Initializes the workqueue where the callbacks will be handled. * * Return: 0 on success, error code otherwise */ static int mock_smsm_cb_wq_init(void) { int ret = 0; mock_smsm_cb_wq = create_singlethread_workqueue("mock_smsm_cb_wq"); if (!mock_smsm_cb_wq) { MOCK_SMSM_LOG("%s: mock_smsm_cb_wq creation failed\n", __func__); pr_err("%s: mock_smsm_cb_wq creation failed\n", __func__); ret = -EFAULT; goto exit; } dl_init_wq = create_singlethread_workqueue("dl_init_wq"); if (!dl_init_wq) { MOCK_SMSM_LOG("%s: dl_init_wq creation failed\n", __func__); pr_err("%s: dl_init_wq creation failed\n", __func__); ret = -EFAULT; } exit: return ret; } /** * mock_smsm_state_entries_init() - initializes each smsm_state entry * * Initializes each smsm entry. */ static void mock_smsm_states_entries_init(void) { struct mock_smsm_state_info *mock_state_info; int n; unsigned long flags; spin_lock_irqsave(&mock_smsm_entry_lock, flags); for (n = 0; n < MOCK_SMSM_NUM_ENTRIES; n++) { mock_state_info = &mock_smsm_states[n]; mock_state_info->state = 0x0; INIT_LIST_HEAD(&mock_state_info->callbacks); } spin_unlock_irqrestore(&mock_smsm_entry_lock, flags); } /** * mock_smsm_state_cb_execute() - executes smsm_entry's callbacks * corresponding to the change from old_state to its current state * @smsm_entry: entry whose state was changed * @old_state: previous state of smsm_entry * * Executes smsm_entry's callbacks corresponding to the change from its old * state to its current state. Note, must be called with mock_smsm_entry_lock * held, and entry validated. */ static void mock_smsm_state_cb_execute(uint32_t smsm_entry, uint32_t old_state) { struct mock_smsm_state_cb_info *cb_info; struct mock_smsm_state_info *state; struct mock_smsm_cb_work_struct *cb_work_struct; uint32_t new_state, changed_state; state = &mock_smsm_states[smsm_entry]; new_state = state->state; changed_state = old_state ^ new_state; list_for_each_entry(cb_info, &state->callbacks, cb_list) { if (cb_info->mask & changed_state) { MOCK_SMSM_LOG("%s: entry %u caused callback\n", __func__, smsm_entry); cb_work_struct = ( struct mock_smsm_cb_work_struct *)(kmalloc( sizeof(struct mock_smsm_cb_work_struct), GFP_ATOMIC)); if (cb_work_struct) { INIT_WORK(&(cb_work_struct->work), mock_smsm_cb_handler); cb_work_struct->notify = cb_info->notify; cb_work_struct->data = cb_info->data; cb_work_struct->old_state = old_state; cb_work_struct->new_state = new_state; queue_work(mock_smsm_cb_wq, &(cb_work_struct->work)); } else { MOCK_SMSM_LOG("%s: work struct alloc fail\n", __func__); } } } } /** * mock_smsm_cb_handler() - handler function for callbacks for the mock_smsm * @wrk: work_struct pointer passed to handler by convention * * Handler function responsible for deferring the work of the state callbacks */ static void mock_smsm_cb_handler(struct work_struct *wrk) { struct mock_smsm_cb_work_struct *cb_work_struct = container_of(wrk, struct mock_smsm_cb_work_struct, work); if (!cb_work_struct) { MOCK_SMSM_LOG("%s: work struct null\n", __func__); return; } if (!cb_work_struct->notify) { MOCK_SMSM_LOG("%s: notify null\n", __func__); kfree(cb_work_struct); return; } (*(cb_work_struct->notify))(cb_work_struct->data, cb_work_struct->old_state, cb_work_struct->new_state); kfree(cb_work_struct); } /** * mock_smsm_entry_is_error() - ensures that mock_smsm is * inited at that smsm_entry is valid. * @smsm_entry: entry to be validated * * Ensures that there is no error with the specified smsm_entry. * * Return: 0 if valid entry, error code otherwise */ static int mock_smsm_entry_is_error(uint32_t smsm_entry) { int err = 0; unsigned long flags; spin_lock_irqsave(&mock_smsm_inited_lock, flags); if (!mock_smsm_inited) { err = -ENODEV; spin_unlock_irqrestore(&mock_smsm_inited_lock, flags); MOCK_SMSM_LOG("%s: mock smsm not inited\n", __func__); goto exit; } spin_unlock_irqrestore(&mock_smsm_inited_lock, flags); if (smsm_entry >= MOCK_SMSM_NUM_ENTRIES) { err = -EINVAL; MOCK_SMSM_LOG("%s: entry=%u max=%u\n", __func__, smsm_entry, MOCK_SMSM_NUM_ENTRIES); } exit: return err; }