blob: f3effa8b25b9a8990c75bed5814fea5218fa0ddd [file] [log] [blame]
/* Copyright (c) 2011-2013, 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.
*/
/*
* SDIO-Downloader
*
* To be used with Qualcomm's SDIO-Client connected to this host.
*/
/* INCLUDES */
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/wakelock.h>
#include <linux/workqueue.h>
#include <linux/mmc/card.h>
#include <linux/dma-mapping.h>
#include <mach/dma.h>
#include <linux/mmc/sdio_func.h>
#include "sdio_al_private.h"
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/kthread.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/debugfs.h>
/* DEFINES AND MACROS */
#define MAX_NUM_DEVICES 1
#define TTY_SDIO_DEV "tty_sdio_0"
#define TTY_SDIO_DEV_TEST "tty_sdio_test_0"
#define SDIOC_MAILBOX_ADDRESS 0
#define SDIO_DL_BLOCK_SIZE 512
#define SDIO_DL_MAIN_THREAD_NAME "sdio_tty_main_thread"
#define SDIOC_DL_BUFF_ADDRESS 0
#define SDIOC_UP_BUFF_ADDRESS 0x4
#define SDIOC_DL_BUFF_SIZE_OFFSET 0x8
#define SDIOC_UP_BUFF_SIZE_OFFSET 0xC
#define SDIOC_DL_WR_PTR 0x10
#define SDIOC_DL_RD_PTR 0x14
#define SDIOC_UL_WR_PTR 0x18
#define SDIOC_UL_RD_PTR 0x1C
#define SDIOC_EXIT_PTR 0x20
#define SDIOC_OP_MODE_PTR 0x24
#define SDIOC_PTRS_OFFSET 0x10
#define SDIOC_PTR_REGS_SIZE 0x10
#define SDIOC_CFG_REGS_SIZE 0x10
#define WRITE_RETRIES 0xFFFFFFFF
#define INPUT_SPEED 4800
#define OUTPUT_SPEED 4800
#define SDIOC_EXIT_CODE 0xDEADDEAD
#define SLEEP_MS 10
#define PRINTING_GAP 200
#define TIMER_DURATION 10
#define PUSH_TIMER_DURATION 5000
#define MULTIPLE_RATIO 1
#define MS_IN_SEC 1000
#define BITS_IN_BYTE 8
#define BYTES_IN_KB 1024
#define WRITE_TILL_END_RETRIES 5
#define SDIO_DLD_NORMAL_MODE_NAME "SDIO DLD NORMAL MODE"
#define SDIO_DLD_BOOT_TEST_MODE_NAME "SDIO DLD BOOT TEST MODE"
#define SDIO_DLD_AMSS_TEST_MODE_NAME "SDIO DLD AMSS TEST MODE"
#define TEST_NAME_MAX_SIZE 30
#define PUSH_STRING
#define SDIO_DLD_OUTGOING_BUFFER_SIZE (48*1024*MULTIPLE_RATIO)
/* FORWARD DECLARATIONS */
static int sdio_dld_open(struct tty_struct *tty, struct file *file);
static void sdio_dld_close(struct tty_struct *tty, struct file *file);
static int sdio_dld_write_callback(struct tty_struct *tty,
const unsigned char *buf, int count);
static int sdio_dld_write_room(struct tty_struct *tty);
static int sdio_dld_main_task(void *card);
static void sdio_dld_print_info(void);
#ifdef CONFIG_DEBUG_FS
static int sdio_dld_debug_info_open(struct inode *inode, struct file *file);
static ssize_t sdio_dld_debug_info_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos);
#endif
static void sdio_dld_tear_down(struct work_struct *work);
DECLARE_WORK(cleanup, sdio_dld_tear_down);
/* STRUCTURES AND TYPES */
enum sdio_dld_op_mode {
SDIO_DLD_NO_MODE = 0,
SDIO_DLD_NORMAL_MODE = 1,
SDIO_DLD_BOOT_TEST_MODE = 2,
SDIO_DLD_AMSS_TEST_MODE = 3,
SDIO_DLD_NUM_OF_MODES,
};
struct sdioc_reg_sequential_chunk_ptrs {
unsigned int dl_wr_ptr;
unsigned int dl_rd_ptr;
unsigned int up_wr_ptr;
unsigned int up_rd_ptr;
};
struct sdioc_reg_sequential_chunk_cfg {
unsigned int dl_buff_address;
unsigned int up_buff_address;
unsigned int dl_buff_size;
unsigned int ul_buff_size;
};
struct sdioc_reg {
unsigned int reg_val;
unsigned int reg_offset;
};
struct sdioc_reg_chunk {
struct sdioc_reg dl_buff_address;
struct sdioc_reg up_buff_address;
struct sdioc_reg dl_buff_size;
struct sdioc_reg ul_buff_size;
struct sdioc_reg dl_wr_ptr;
struct sdioc_reg dl_rd_ptr;
struct sdioc_reg up_wr_ptr;
struct sdioc_reg up_rd_ptr;
struct sdioc_reg good_to_exit_ptr;
};
struct sdio_data {
char *data;
int offset_read_p;
int offset_write_p;
int buffer_size;
int num_of_bytes_in_use;
};
struct sdio_dld_data {
struct sdioc_reg_chunk sdioc_reg;
struct sdio_data incoming_data;
struct sdio_data outgoing_data;
};
struct sdio_dld_wait_event {
wait_queue_head_t wait_event;
int wake_up_signal;
};
struct sdio_dld_task {
struct task_struct *dld_task;
const char *task_name;
struct sdio_dld_wait_event exit_wait;
atomic_t please_close;
};
#ifdef CONFIG_DEBUG_FS
struct sdio_dloader_debug {
struct dentry *sdio_dld_debug_root;
struct dentry *sdio_al_dloader;
};
const struct file_operations sdio_dld_debug_info_ops = {
.open = sdio_dld_debug_info_open,
.write = sdio_dld_debug_info_write,
};
#endif
struct sdio_downloader {
int sdioc_boot_func;
struct sdio_dld_wait_event write_callback_event;
struct sdio_dld_task dld_main_thread;
struct tty_driver *tty_drv;
struct tty_struct *tty_str;
struct sdio_dld_data sdio_dloader_data;
struct mmc_card *card;
int(*done_callback)(void);
struct sdio_dld_wait_event main_loop_event;
struct timer_list timer;
unsigned int poll_ms;
struct timer_list push_timer;
unsigned int push_timer_ms;
enum sdio_dld_op_mode op_mode;
char op_mode_name[TEST_NAME_MAX_SIZE];
};
struct sdio_dld_global_info {
int global_bytes_write_toio;
int global_bytes_write_tty;
int global_bytes_read_fromio;
int global_bytes_push_tty;
u64 start_time;
u64 end_time;
u64 delta_jiffies;
unsigned int time_msec;
unsigned int throughput;
int cl_dl_wr_ptr;
int cl_dl_rd_ptr;
int cl_up_wr_ptr;
int cl_up_rd_ptr;
int host_read_ptr;
int host_write_ptr;
int cl_dl_buffer_size;
int cl_up_buffer_size;
int host_outgoing_buffer_size;
int cl_dl_buffer_address;
int cl_up_buffer_address;
};
static const struct tty_operations sdio_dloader_tty_ops = {
.open = sdio_dld_open,
.close = sdio_dld_close,
.write = sdio_dld_write_callback,
.write_room = sdio_dld_write_room,
};
/* GLOBAL VARIABLES */
struct sdio_downloader *sdio_dld;
struct sdio_dld_global_info sdio_dld_info;
static char outgoing_data_buffer[SDIO_DLD_OUTGOING_BUFFER_SIZE];
static DEFINE_SPINLOCK(lock1);
static unsigned long lock_flags1;
static DEFINE_SPINLOCK(lock2);
static unsigned long lock_flags2;
static atomic_t sdio_dld_in_use = ATOMIC_INIT(0);
static atomic_t sdio_dld_setup_done = ATOMIC_INIT(0);
/*
* sdio_op_mode sets the operation mode of the sdio_dloader -
* it may be in NORMAL_MODE, BOOT_TEST_MODE or AMSS_TEST_MODE
*/
static int sdio_op_mode = (int)SDIO_DLD_NORMAL_MODE;
module_param(sdio_op_mode, int, 0);
#ifdef CONFIG_DEBUG_FS
struct sdio_dloader_debug sdio_dld_debug;
#define ARR_SIZE 30000
#define SDIO_DLD_DEBUGFS_INIT_VALUE 87654321
#define SDIO_DLD_DEBUGFS_CASE_1_CODE 11111111
#define SDIO_DLD_DEBUGFS_CASE_2_CODE 22222222
#define SDIO_DLD_DEBUGFS_CASE_3_CODE 33333333
#define SDIO_DLD_DEBUGFS_CASE_4_CODE 44444444
#define SDIO_DLD_DEBUGFS_CASE_5_CODE 55555555
#define SDIO_DLD_DEBUGFS_CASE_6_CODE 66666666
#define SDIO_DLD_DEBUGFS_CASE_7_CODE 77777777
#define SDIO_DLD_DEBUGFS_CASE_8_CODE 88888888
#define SDIO_DLD_DEBUGFS_CASE_9_CODE 99999999
#define SDIO_DLD_DEBUGFS_CASE_10_CODE 10101010
#define SDIO_DLD_DEBUGFS_CASE_11_CODE 11001100
#define SDIO_DLD_DEBUGFS_CASE_12_CODE 12001200
#define SDIO_DLD_DEBUGFS_LOOP_WAIT 7
#define SDIO_DLD_DEBUGFS_LOOP_WAKEUP 8
#define SDIO_DLD_DEBUGFS_CB_WAIT 3
#define SDIO_DLD_DEBUGFS_CB_WAKEUP 4
static int curr_index;
struct ptrs {
int h_w_ptr;
int h_r_ptr;
int c_u_w_ptr;
int c_u_r_ptr;
int code;
int h_has_to_send;
int c_has_to_receive;
int min_of;
int reserve2;
int tty_count;
int write_tty;
int write_toio;
int loop_wait_wake;
int cb_wait_wake;
int c_d_w_ptr;
int c_d_r_ptr;
int to_read;
int push_to_tty;
int global_tty_send;
int global_sdio_send;
int global_tty_received;
int global_sdio_received;
int reserve22;
int reserve23;
int reserve24;
int reserve25;
int reserve26;
int reserve27;
int reserve28;
int reserve29;
int reserve30;
int reserve31;
};
struct global_data {
int curr_i;
int duration_ms;
int global_bytes_sent;
int throughput_Mbs;
int host_outgoing_buffer_size_KB;
int client_up_buffer_size_KB;
int client_dl_buffer_size_KB;
int client_dl_buffer_address;
int client_up_buffer_address;
int global_bytes_received;
int global_bytes_pushed;
int reserve11;
int reserve12;
int reserve13;
int reserve14;
int reserve15;
int reserve16;
int reserve17;
int reserve18;
int reserve19;
int reserve20;
int reserve21;
int reserve22;
int reserve23;
int reserve24;
int reserve25;
int reserve26;
int reserve27;
int reserve28;
int reserve29;
int reserve30;
int reserve31;
struct ptrs ptr_array[ARR_SIZE];
};
static struct global_data gd;
static struct debugfs_blob_wrapper blob;
static struct dentry *root;
static struct dentry *dld;
struct debugfs_global {
int global_8k_has;
int global_9k_has;
int global_min;
int global_count;
int global_write_tty;
int global_write_toio;
int global_bytes_cb_tty;
int global_to_read;
int global_push_to_tty;
int global_tty_send;
int global_sdio_send;
int global_sdio_received;
int global_tty_push;
};
static struct debugfs_global debugfs_glob;
static void update_standard_fields(int index)
{
gd.ptr_array[index].global_tty_send =
sdio_dld_info.global_bytes_write_tty;
gd.ptr_array[index].global_sdio_send =
sdio_dld_info.global_bytes_write_toio;
gd.ptr_array[index].global_tty_received =
sdio_dld_info.global_bytes_push_tty;
gd.ptr_array[index].global_sdio_received =
sdio_dld_info.global_bytes_read_fromio;
}
static void update_gd(int code)
{
struct sdioc_reg_chunk *reg_str =
&sdio_dld->sdio_dloader_data.sdioc_reg;
struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data;
int index = curr_index%ARR_SIZE;
gd.curr_i = curr_index;
gd.duration_ms = 0;
gd.global_bytes_sent = 0;
gd.throughput_Mbs = 0;
gd.host_outgoing_buffer_size_KB = 0;
gd.client_up_buffer_size_KB = 0;
gd.client_dl_buffer_size_KB = 0;
gd.client_dl_buffer_address = 0;
gd.client_up_buffer_address = 0;
gd.global_bytes_received = 0;
gd.global_bytes_pushed = 0;
gd.reserve11 = 0;
gd.reserve12 = 0;
gd.reserve13 = 0;
gd.reserve14 = 0;
gd.reserve15 = 0;
gd.reserve16 = 0;
gd.reserve17 = 0;
gd.reserve18 = 0;
gd.reserve19 = 0;
gd.reserve20 = 0;
gd.reserve21 = 0;
gd.reserve22 = 0;
gd.reserve23 = 0;
gd.reserve24 = 0;
gd.reserve25 = 0;
gd.reserve26 = 0;
gd.reserve27 = 0;
gd.reserve28 = 0;
gd.reserve29 = 0;
gd.reserve30 = 0;
gd.reserve31 = 0;
gd.ptr_array[index].h_w_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*0*/
gd.ptr_array[index].h_r_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*1*/
gd.ptr_array[index].c_u_w_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*2*/
gd.ptr_array[index].c_u_r_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*3*/
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_INIT_VALUE; /*4*/
gd.ptr_array[index].h_has_to_send = SDIO_DLD_DEBUGFS_INIT_VALUE;/*5*/
gd.ptr_array[index].c_has_to_receive =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*6*/
gd.ptr_array[index].min_of = SDIO_DLD_DEBUGFS_INIT_VALUE; /*7*/
gd.ptr_array[index].reserve2 = SDIO_DLD_DEBUGFS_INIT_VALUE; /*8*/
gd.ptr_array[index].tty_count = SDIO_DLD_DEBUGFS_INIT_VALUE; /*9*/
gd.ptr_array[index].write_tty = SDIO_DLD_DEBUGFS_INIT_VALUE; /*A*/
gd.ptr_array[index].write_toio = SDIO_DLD_DEBUGFS_INIT_VALUE; /*B*/
gd.ptr_array[index].loop_wait_wake =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*C*/
gd.ptr_array[index].cb_wait_wake = SDIO_DLD_DEBUGFS_INIT_VALUE; /*D*/
gd.ptr_array[index].c_d_w_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*E*/
gd.ptr_array[index].c_d_r_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*F*/
gd.ptr_array[index].to_read =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x10*/
gd.ptr_array[index].push_to_tty =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x11*/
gd.ptr_array[index].global_tty_send =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x12*/
gd.ptr_array[index].global_sdio_send =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x13*/
gd.ptr_array[index].global_tty_received =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x14*/
gd.ptr_array[index].global_sdio_received =
SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x15*/
gd.ptr_array[index].reserve22 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve23 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve24 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve25 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve26 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve27 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve28 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve29 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve30 = SDIO_DLD_DEBUGFS_INIT_VALUE;
gd.ptr_array[index].reserve31 = SDIO_DLD_DEBUGFS_INIT_VALUE;
switch (code) {
case SDIO_DLD_DEBUGFS_CASE_1_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_1_CODE;
gd.ptr_array[index].h_w_ptr = outgoing->offset_write_p;
gd.ptr_array[index].h_r_ptr = outgoing->offset_read_p;
gd.ptr_array[index].c_u_w_ptr = reg_str->up_wr_ptr.reg_val;
gd.ptr_array[index].c_u_r_ptr = reg_str->up_rd_ptr.reg_val;
gd.ptr_array[index].c_d_w_ptr = reg_str->dl_wr_ptr.reg_val;
gd.ptr_array[index].c_d_r_ptr = reg_str->dl_rd_ptr.reg_val;
break;
case SDIO_DLD_DEBUGFS_CASE_2_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_2_CODE;
gd.ptr_array[index].c_u_r_ptr = reg_str->up_rd_ptr.reg_val;
gd.ptr_array[index].c_u_w_ptr = reg_str->up_wr_ptr.reg_val;
gd.ptr_array[index].h_has_to_send = debugfs_glob.global_8k_has;
gd.ptr_array[index].c_has_to_receive =
debugfs_glob.global_9k_has;
gd.ptr_array[index].min_of = debugfs_glob.global_min;
break;
case SDIO_DLD_DEBUGFS_CASE_3_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_3_CODE;
gd.ptr_array[index].h_w_ptr = outgoing->offset_write_p;
gd.ptr_array[index].h_r_ptr = outgoing->offset_read_p;
gd.ptr_array[index].write_tty = debugfs_glob.global_write_tty;
break;
case SDIO_DLD_DEBUGFS_CASE_4_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_4_CODE;
gd.ptr_array[index].h_w_ptr = outgoing->offset_write_p;
gd.ptr_array[index].h_r_ptr = outgoing->offset_read_p;
gd.ptr_array[index].c_u_r_ptr = reg_str->up_rd_ptr.reg_val;
gd.ptr_array[index].c_u_w_ptr = reg_str->up_wr_ptr.reg_val;
gd.ptr_array[index].write_toio =
debugfs_glob.global_write_toio;
break;
case SDIO_DLD_DEBUGFS_CASE_5_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_5_CODE;
gd.ptr_array[index].tty_count = debugfs_glob.global_count;
break;
case SDIO_DLD_DEBUGFS_CASE_6_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_6_CODE;
gd.ptr_array[index].loop_wait_wake = 7;
break;
case SDIO_DLD_DEBUGFS_CASE_7_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_7_CODE;
gd.ptr_array[index].loop_wait_wake = 8;
break;
case SDIO_DLD_DEBUGFS_CASE_8_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_8_CODE;
gd.ptr_array[index].cb_wait_wake = 3;
break;
case SDIO_DLD_DEBUGFS_CASE_9_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_9_CODE;
gd.ptr_array[index].cb_wait_wake = 4;
break;
case SDIO_DLD_DEBUGFS_CASE_10_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_10_CODE;
gd.ptr_array[index].cb_wait_wake =
debugfs_glob.global_bytes_cb_tty;
break;
case SDIO_DLD_DEBUGFS_CASE_11_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_11_CODE;
gd.ptr_array[index].to_read = debugfs_glob.global_to_read;
break;
case SDIO_DLD_DEBUGFS_CASE_12_CODE:
gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_12_CODE;
gd.ptr_array[index].push_to_tty =
debugfs_glob.global_push_to_tty;
break;
default:
break;
}
update_standard_fields(index);
curr_index++;
}
static int bootloader_debugfs_init(void)
{
/* /sys/kernel/debug/bootloader there will be dld_arr file */
root = debugfs_create_dir("bootloader", NULL);
if (!root) {
pr_info(MODULE_NAME ": %s - creating root dir "
"failed\n", __func__);
return -ENODEV;
}
blob.data = &gd;
blob.size = sizeof(struct global_data);
dld = debugfs_create_blob("dld_arr", S_IRUGO, root, &blob);
if (!dld) {
debugfs_remove_recursive(root);
pr_err(MODULE_NAME ": %s, failed to create debugfs entry\n",
__func__);
return -ENODEV;
}
return 0;
}
/*
* for triggering the sdio_dld info use:
* echo 1 > /sys/kernel/debug/sdio_al_dld/sdio_al_dloader_info
*/
static int sdio_dld_debug_init(void)
{
sdio_dld_debug.sdio_dld_debug_root =
debugfs_create_dir("sdio_al_dld", NULL);
if (!sdio_dld_debug.sdio_dld_debug_root) {
pr_err(MODULE_NAME ": %s - Failed to create folder. "
"sdio_dld_debug_root is NULL",
__func__);
return -ENOENT;
}
sdio_dld_debug.sdio_al_dloader = debugfs_create_file(
"sdio_al_dloader_info",
S_IRUGO | S_IWUGO,
sdio_dld_debug.sdio_dld_debug_root,
NULL,
&sdio_dld_debug_info_ops);
if (!sdio_dld_debug.sdio_al_dloader) {
pr_err(MODULE_NAME ": %s - Failed to create a file. "
"sdio_al_dloader is NULL",
__func__);
debugfs_remove(sdio_dld_debug.sdio_dld_debug_root);
sdio_dld_debug.sdio_dld_debug_root = NULL;
return -ENOENT;
}
return 0;
}
static int sdio_dld_debug_info_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static ssize_t sdio_dld_debug_info_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
sdio_dld_print_info();
return count;
}
#endif /* CONFIG_DEBUG_FS */
static void sdio_dld_print_info(void)
{
sdio_dld_info.end_time = get_jiffies_64(); /* read the current time */
sdio_dld_info.delta_jiffies =
sdio_dld_info.end_time - sdio_dld_info.start_time;
sdio_dld_info.time_msec = jiffies_to_msecs(sdio_dld_info.delta_jiffies);
sdio_dld_info.throughput = sdio_dld_info.global_bytes_write_toio *
BITS_IN_BYTE / sdio_dld_info.time_msec;
sdio_dld_info.throughput /= MS_IN_SEC;
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - DURATION IN MSEC = %d\n",
__func__,
sdio_dld_info.time_msec);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - BYTES WRITTEN ON SDIO BUS "
"= %d...BYTES SENT BY TTY = %d",
__func__,
sdio_dld_info.global_bytes_write_toio,
sdio_dld_info.global_bytes_write_tty);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - BYTES RECEIVED ON SDIO BUS "
"= %d...BYTES SENT TO TTY = %d",
__func__,
sdio_dld_info.global_bytes_read_fromio,
sdio_dld_info.global_bytes_push_tty);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - THROUGHPUT=%d Mbit/Sec",
__func__, sdio_dld_info.throughput);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT DL_BUFFER_SIZE=%d"
" KB..CLIENT UL_BUFFER=%d KB\n",
__func__,
sdio_dld_info.cl_dl_buffer_size/BYTES_IN_KB,
sdio_dld_info.cl_up_buffer_size/BYTES_IN_KB);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - HOST OUTGOING BUFFER_SIZE"
"=%d KB",
__func__,
sdio_dld_info.host_outgoing_buffer_size/BYTES_IN_KB);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT DL BUFFER "
"ADDRESS = 0x%x", __func__,
sdio_dld_info.cl_dl_buffer_address);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT UP BUFFER "
"ADDRESS = 0x%x",
__func__,
sdio_dld_info.cl_up_buffer_address);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - UPLINK BUFFER - "
"READ POINTER = %d", __func__,
sdio_dld_info.cl_up_rd_ptr);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - UPLINK BUFFER - "
"WRITE POINTER = %d", __func__,
sdio_dld_info.cl_up_wr_ptr);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - DOWNLINK BUFFER - "
"READ POINTER = %d", __func__,
sdio_dld_info.cl_dl_rd_ptr);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - DOWNLINK BUFFER - "
"WRITE POINTER = %d", __func__,
sdio_dld_info.cl_dl_wr_ptr);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - HOST - OUTGOING BUFFER - "
"READ POINTER = %d", __func__,
sdio_dld_info.host_read_ptr);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - HOST - OUTGOING BUFFER - "
"WRITE POINTER = %d", __func__,
sdio_dld_info.host_write_ptr);
pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - END DEBUG INFO", __func__);
}
/**
* sdio_dld_set_op_mode
* sets the op_mode and the name of the op_mode. Also, in case
* it's invalid mode sets op_mode to SDIO_DLD_NORMAL_MODE
*
* @op_mode: the operation mode to be set
* @return NONE
*/
static void sdio_dld_set_op_mode(enum sdio_dld_op_mode op_mode)
{
sdio_dld->op_mode = op_mode;
switch (op_mode) {
case SDIO_DLD_NORMAL_MODE:
memcpy(sdio_dld->op_mode_name,
SDIO_DLD_NORMAL_MODE_NAME, TEST_NAME_MAX_SIZE);
break;
case SDIO_DLD_BOOT_TEST_MODE:
memcpy(sdio_dld->op_mode_name,
SDIO_DLD_BOOT_TEST_MODE_NAME, TEST_NAME_MAX_SIZE);
break;
case SDIO_DLD_AMSS_TEST_MODE:
memcpy(sdio_dld->op_mode_name,
SDIO_DLD_AMSS_TEST_MODE_NAME, TEST_NAME_MAX_SIZE);
break;
default:
sdio_dld->op_mode = SDIO_DLD_NORMAL_MODE;
pr_err(MODULE_NAME ": %s - Invalid Op_Mode = %d. Settings "
"Op_Mode to default - NORMAL_MODE\n",
__func__, op_mode);
memcpy(sdio_dld->op_mode_name,
SDIO_DLD_NORMAL_MODE_NAME, TEST_NAME_MAX_SIZE);
break;
}
if (sdio_dld->op_mode_name != NULL) {
pr_info(MODULE_NAME ": %s - FLASHLESS BOOT - Op_Mode is set to "
"%s\n", __func__, sdio_dld->op_mode_name);
} else {
pr_info(MODULE_NAME ": %s - FLASHLESS BOOT - op_mode_name is "
"NULL\n", __func__);
}
}
/**
* sdio_dld_allocate_local_buffers
* allocates local outgoing and incoming buffers and also sets
* threshold for outgoing data.
*
* @return 0 on success or negative value on error.
*/
static int sdio_dld_allocate_local_buffers(void)
{
struct sdioc_reg_chunk *reg_str = &sdio_dld->sdio_dloader_data.
sdioc_reg;
struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data;
struct sdio_data *incoming = &sdio_dld->sdio_dloader_data.incoming_data;
incoming->data =
kzalloc(reg_str->dl_buff_size.reg_val, GFP_KERNEL);
if (!incoming->data) {
pr_err(MODULE_NAME ": %s - param ""incoming->data"" is NULL. "
"Couldn't allocate incoming_data local buffer\n",
__func__);
return -ENOMEM;
}
incoming->buffer_size = reg_str->dl_buff_size.reg_val;
outgoing->data = outgoing_data_buffer;
outgoing->buffer_size = SDIO_DLD_OUTGOING_BUFFER_SIZE;
if (outgoing->buffer_size !=
reg_str->ul_buff_size.reg_val*MULTIPLE_RATIO) {
pr_err(MODULE_NAME ": %s - HOST outgoing buffer size (%d bytes)"
"must be a multiple of ClIENT uplink buffer size (%d "
"bytes). HOST_SIZE == n*CLIENT_SIZE.(n=1,2,3...)\n",
__func__,
SDIO_DLD_OUTGOING_BUFFER_SIZE,
reg_str->ul_buff_size.reg_val);
kfree(incoming->data);
return -EINVAL;
}
/* keep sdio_dld_info up to date */
sdio_dld_info.host_outgoing_buffer_size = outgoing->buffer_size;
return 0;
}
/**
* sdio_dld_dealloc_local_buffers frees incoming and outgoing
* buffers.
*
* @return None.
*/
static void sdio_dld_dealloc_local_buffers(void)
{
kfree((void *)sdio_dld->sdio_dloader_data.incoming_data.data);
}
/**
* mailbox_to_seq_chunk_read_cfg
* reads 4 configuration registers of mailbox from str_func, as
* a sequentail chunk in memory, and updates global struct
* accordingly.
*
* @str_func: a pointer to func struct.
* @return 0 on success or negative value on error.
*/
static int mailbox_to_seq_chunk_read_cfg(struct sdio_func *str_func)
{
struct sdioc_reg_sequential_chunk_cfg seq_chunk;
struct sdioc_reg_chunk *reg = &sdio_dld->sdio_dloader_data.sdioc_reg;
int status = 0;
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
sdio_claim_host(str_func);
/* reading SDIOC_MAILBOX_SIZE bytes from SDIOC_MAILBOX_ADDRESS */
status = sdio_memcpy_fromio(str_func,
(void *)&seq_chunk,
SDIOC_MAILBOX_ADDRESS,
SDIOC_CFG_REGS_SIZE);
if (status) {
pr_err(MODULE_NAME ": %s - sdio_memcpy_fromio()"
" READING CFG MAILBOX failed. status=%d.\n",
__func__, status);
}
sdio_release_host(str_func);
reg->dl_buff_address.reg_val = seq_chunk.dl_buff_address;
reg->up_buff_address.reg_val = seq_chunk.up_buff_address;
reg->dl_buff_size.reg_val = seq_chunk.dl_buff_size;
reg->ul_buff_size.reg_val = seq_chunk.ul_buff_size;
/* keep sdio_dld_info up to date */
sdio_dld_info.cl_dl_buffer_size = seq_chunk.dl_buff_size;
sdio_dld_info.cl_up_buffer_size = seq_chunk.ul_buff_size;
sdio_dld_info.cl_dl_buffer_address = seq_chunk.dl_buff_address;
sdio_dld_info.cl_up_buffer_address = seq_chunk.up_buff_address;
return status;
}
/**
* mailbox_to_seq_chunk_read_ptrs
* reads 4 pointers registers of mailbox from str_func, as a
* sequentail chunk in memory, and updates global struct
* accordingly.
*
* @str_func: a pointer to func struct.
* @return 0 on success or negative value on error.
*/
static int mailbox_to_seq_chunk_read_ptrs(struct sdio_func *str_func)
{
struct sdioc_reg_sequential_chunk_ptrs seq_chunk;
struct sdioc_reg_chunk *reg = &sdio_dld->sdio_dloader_data.sdioc_reg;
int status = 0;
struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data;
static int counter = 1;
static int offset_write_p;
static int offset_read_p;
static int up_wr_ptr;
static int up_rd_ptr;
static int dl_wr_ptr;
static int dl_rd_ptr;
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
sdio_claim_host(str_func);
/* reading SDIOC_MAILBOX_SIZE bytes from SDIOC_MAILBOX_ADDRESS */
status = sdio_memcpy_fromio(str_func,
(void *)&seq_chunk,
SDIOC_PTRS_OFFSET,
SDIOC_PTR_REGS_SIZE);
if (status) {
pr_err(MODULE_NAME ": %s - sdio_memcpy_fromio()"
" READING PTRS MAILBOX failed. status=%d.\n",
__func__, status);
}
sdio_release_host(str_func);
reg->dl_rd_ptr.reg_val = seq_chunk.dl_rd_ptr;
reg->dl_wr_ptr.reg_val = seq_chunk.dl_wr_ptr;
reg->up_rd_ptr.reg_val = seq_chunk.up_rd_ptr;
reg->up_wr_ptr.reg_val = seq_chunk.up_wr_ptr;
/* keeping sdio_dld_info up to date */
sdio_dld_info.cl_dl_rd_ptr = seq_chunk.dl_rd_ptr;
sdio_dld_info.cl_dl_wr_ptr = seq_chunk.dl_wr_ptr;
sdio_dld_info.cl_up_rd_ptr = seq_chunk.up_rd_ptr;
sdio_dld_info.cl_up_wr_ptr = seq_chunk.up_wr_ptr;
/* DEBUG - if there was a change in value */
if ((offset_write_p != outgoing->offset_write_p) ||
(offset_read_p != outgoing->offset_read_p) ||
(up_wr_ptr != reg->up_wr_ptr.reg_val) ||
(up_rd_ptr != reg->up_rd_ptr.reg_val) ||
(dl_wr_ptr != reg->dl_wr_ptr.reg_val) ||
(dl_rd_ptr != reg->dl_rd_ptr.reg_val) ||
(counter % PRINTING_GAP == 0)) {
counter = 1;
pr_debug(MODULE_NAME ": %s MailBox pointers: BLOCK_SIZE=%d, "
"hw=%d, hr=%d, cuw=%d, cur=%d, cdw=%d, cdr=%d\n",
__func__,
SDIO_DL_BLOCK_SIZE,
outgoing->offset_write_p,
outgoing->offset_read_p,
reg->up_wr_ptr.reg_val,
reg->up_rd_ptr.reg_val,
reg->dl_wr_ptr.reg_val,
reg->dl_rd_ptr.reg_val);
#ifdef CONFIG_DEBUG_FS
update_gd(SDIO_DLD_DEBUGFS_CASE_1_CODE);
#endif
/* update static variables */
offset_write_p = outgoing->offset_write_p;
offset_read_p = outgoing->offset_read_p;
up_wr_ptr = reg->up_wr_ptr.reg_val;
up_rd_ptr = reg->up_rd_ptr.reg_val;
dl_wr_ptr = reg->dl_wr_ptr.reg_val;
dl_rd_ptr = reg->dl_rd_ptr.reg_val;
} else {
counter++;
}
return status;
}
/**
* sdio_dld_init_func
* enables the sdio func, and sets the func block size.
*
* @str_func: a pointer to func struct.
* @return 0 on success or negative value on error.
*/
static int sdio_dld_init_func(struct sdio_func *str_func)
{
int status1 = 0;
int status2 = 0;
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
sdio_claim_host(str_func);
status1 = sdio_enable_func(str_func);
if (status1) {
sdio_release_host(str_func);
pr_err(MODULE_NAME ": %s - sdio_enable_func() failed. "
"status=%d\n", __func__, status1);
return status1;
}
status2 = sdio_set_block_size(str_func, SDIO_DL_BLOCK_SIZE);
if (status2) {
pr_err(MODULE_NAME ": %s - sdio_set_block_size() failed. "
"status=%d\n", __func__, status2);
status1 = sdio_disable_func(str_func);
if (status1) {
pr_err(MODULE_NAME ": %s - sdio_disable_func() "
"failed. status=%d\n", __func__, status1);
}
sdio_release_host(str_func);
return status2;
}
sdio_release_host(str_func);
str_func->max_blksize = SDIO_DL_BLOCK_SIZE;
return 0;
}
/**
* sdio_dld_allocate_buffers
* initializes the sdio func, and then reads the mailbox, in
* order to allocate incoming and outgoing buffers according to
* the size that was read from the mailbox.
*
* @str_func: a pointer to func struct.
* @return 0 on success or negative value on error.
*/
static int sdio_dld_allocate_buffers(struct sdio_func *str_func)
{
int status = 0;
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
status = mailbox_to_seq_chunk_read_cfg(str_func);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"mailbox_to_seq_chunk_read_cfg(). status=%d\n",
__func__, status);
return status;
}
status = sdio_dld_allocate_local_buffers();
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdio_dld_allocate_local_buffers(). status=%d\n",
__func__, status);
return status;
}
return 0;
}
/**
* sdio_dld_create_thread
* creates thread and wakes it up.
*
* @return 0 on success or negative value on error.
*/
static int sdio_dld_create_thread(void)
{
sdio_dld->dld_main_thread.task_name = SDIO_DL_MAIN_THREAD_NAME;
sdio_dld->dld_main_thread.dld_task =
kthread_create(sdio_dld_main_task,
(void *)(sdio_dld->card),
sdio_dld->dld_main_thread.task_name);
if (IS_ERR(sdio_dld->dld_main_thread.dld_task)) {
pr_err(MODULE_NAME ": %s - kthread_create() failed\n",
__func__);
return -ENOMEM;
}
wake_up_process(sdio_dld->dld_main_thread.dld_task);
return 0;
}
/**
* start_timer
* sets the timer and starts.
*
* @timer: the timer to configure and add
* @ms: the ms until it expires
* @return None.
*/
static void start_timer(struct timer_list *timer, unsigned int ms)
{
if ((ms == 0) || (timer == NULL)) {
pr_err(MODULE_NAME ": %s - invalid parameter", __func__);
} else {
timer->expires = jiffies +
msecs_to_jiffies(ms);
add_timer(timer);
}
}
/**
* sdio_dld_timer_handler
* this is the timer handler. whenever it is invoked, it wakes
* up the main loop task, and the write callback, and starts
* the timer again.
*
* @data: a pointer to the tty device driver structure.
* @return None.
*/
static void sdio_dld_timer_handler(unsigned long data)
{
pr_debug(MODULE_NAME " Timer Expired\n");
spin_lock_irqsave(&lock2, lock_flags2);
if (sdio_dld->main_loop_event.wake_up_signal == 0) {
sdio_dld->main_loop_event.wake_up_signal = 1;
wake_up(&sdio_dld->main_loop_event.wait_event);
}
spin_unlock_irqrestore(&lock2, lock_flags2);
sdio_dld->write_callback_event.wake_up_signal = 1;
wake_up(&sdio_dld->write_callback_event.wait_event);
start_timer(&sdio_dld->timer, sdio_dld->poll_ms);
}
/**
* sdio_dld_push_timer_handler
* this is a timer handler of the push_timer.
*
* @data: a pointer to the tty device driver structure.
* @return None.
*/
static void sdio_dld_push_timer_handler(unsigned long data)
{
pr_err(MODULE_NAME " %s - Push Timer Expired... Trying to "
"push data to TTY Core for over then %d ms.\n",
__func__, sdio_dld->push_timer_ms);
}
/**
* sdio_dld_open
* this is the open callback of the tty driver.
* it initializes the sdio func, allocates the buffers, and
* creates the main thread.
*
* @tty: a pointer to the tty struct.
* @file: file descriptor.
* @return 0 on success or negative value on error.
*/
static int sdio_dld_open(struct tty_struct *tty, struct file *file)
{
int status = 0;
int func_in_array =
REAL_FUNC_TO_FUNC_IN_ARRAY(sdio_dld->sdioc_boot_func);
struct sdio_func *str_func = sdio_dld->card->sdio_func[func_in_array];
if (atomic_read(&sdio_dld_in_use) == 1)
return -EBUSY;
atomic_set(&sdio_dld_in_use, 1);
sdio_dld->tty_str = tty;
sdio_dld->tty_str->low_latency = 1;
sdio_dld->tty_str->icanon = 0;
set_bit(TTY_NO_WRITE_SPLIT, &sdio_dld->tty_str->flags);
pr_info(MODULE_NAME ": %s, TTY DEVICE FOR FLASHLESS BOOT OPENED\n",
__func__);
sdio_dld_info.start_time = get_jiffies_64(); /* read the current time */
if (!tty) {
pr_err(MODULE_NAME ": %s - param ""tty"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
atomic_set(&sdio_dld->dld_main_thread.please_close, 0);
sdio_dld->dld_main_thread.exit_wait.wake_up_signal = 0;
status = sdio_dld_allocate_buffers(str_func);
if (status) {
pr_err(MODULE_NAME ": %s, failed in "
"sdio_dld_allocate_buffers(). status=%d\n",
__func__, status);
return status;
}
/* init waiting event of the write callback */
init_waitqueue_head(&sdio_dld->write_callback_event.wait_event);
/* init waiting event of the main loop */
init_waitqueue_head(&sdio_dld->main_loop_event.wait_event);
/* configure and init the timer */
sdio_dld->poll_ms = TIMER_DURATION;
init_timer(&sdio_dld->timer);
sdio_dld->timer.data = (unsigned long) sdio_dld;
sdio_dld->timer.function = sdio_dld_timer_handler;
sdio_dld->timer.expires = jiffies +
msecs_to_jiffies(sdio_dld->poll_ms);
add_timer(&sdio_dld->timer);
sdio_dld->push_timer_ms = PUSH_TIMER_DURATION;
init_timer(&sdio_dld->push_timer);
sdio_dld->push_timer.data = (unsigned long) sdio_dld;
sdio_dld->push_timer.function = sdio_dld_push_timer_handler;
status = sdio_dld_create_thread();
if (status) {
del_timer_sync(&sdio_dld->timer);
del_timer_sync(&sdio_dld->push_timer);
sdio_dld_dealloc_local_buffers();
pr_err(MODULE_NAME ": %s, failed in sdio_dld_create_thread()."
"status=%d\n", __func__, status);
return status;
}
return 0;
}
/**
* sdio_dld_close
* this is the close callback of the tty driver. it requests
* the main thread to exit, and waits for notification of it.
* it also de-allocates the buffers, and unregisters the tty
* driver and device.
*
* @tty: a pointer to the tty struct.
* @file: file descriptor.
* @return None.
*/
static void sdio_dld_close(struct tty_struct *tty, struct file *file)
{
struct sdioc_reg_chunk *reg = &sdio_dld->sdio_dloader_data.sdioc_reg;
/* informing the SDIOC that it can exit boot phase */
sdio_dld->sdio_dloader_data.sdioc_reg.good_to_exit_ptr.reg_val =
SDIOC_EXIT_CODE;
atomic_set(&sdio_dld->dld_main_thread.please_close, 1);
pr_debug(MODULE_NAME ": %s - CLOSING - WAITING...", __func__);
wait_event(sdio_dld->dld_main_thread.exit_wait.wait_event,
sdio_dld->dld_main_thread.exit_wait.wake_up_signal);
pr_debug(MODULE_NAME ": %s - CLOSING - WOKE UP...", __func__);
#ifdef CONFIG_DEBUG_FS
gd.curr_i = curr_index;
gd.duration_ms = sdio_dld_info.time_msec;
gd.global_bytes_sent = sdio_dld_info.global_bytes_write_toio;
gd.global_bytes_received = 0;
gd.throughput_Mbs = sdio_dld_info.throughput;
gd.host_outgoing_buffer_size_KB = sdio_dld->sdio_dloader_data.
outgoing_data.buffer_size/BYTES_IN_KB;
gd.client_up_buffer_size_KB = reg->ul_buff_size.reg_val/BYTES_IN_KB;
gd.client_dl_buffer_size_KB = reg->dl_buff_size.reg_val/BYTES_IN_KB;
gd.client_dl_buffer_address = reg->dl_buff_address.reg_val;
gd.client_up_buffer_address = reg->up_buff_address.reg_val;
gd.global_bytes_received = sdio_dld_info.global_bytes_read_fromio;
gd.global_bytes_pushed = sdio_dld_info.global_bytes_push_tty;
#endif
/* saving register values before deallocating sdio_dld
in order to use it in sdio_dld_print_info() through shell command */
sdio_dld_info.cl_dl_rd_ptr = reg->dl_rd_ptr.reg_val;
sdio_dld_info.cl_dl_wr_ptr = reg->dl_wr_ptr.reg_val;
sdio_dld_info.cl_up_rd_ptr = reg->up_rd_ptr.reg_val;
sdio_dld_info.cl_up_wr_ptr = reg->up_wr_ptr.reg_val;
sdio_dld_info.host_read_ptr =
sdio_dld->sdio_dloader_data.outgoing_data.offset_read_p;
sdio_dld_info.host_write_ptr =
sdio_dld->sdio_dloader_data.outgoing_data.offset_write_p;
sdio_dld_info.cl_dl_buffer_size =
sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_size.reg_val;
sdio_dld_info.cl_up_buffer_size =
sdio_dld->sdio_dloader_data.sdioc_reg.ul_buff_size.reg_val;
sdio_dld_info.host_outgoing_buffer_size =
sdio_dld->sdio_dloader_data.outgoing_data.buffer_size;
sdio_dld_info.cl_dl_buffer_address =
sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_address.reg_val;
sdio_dld_info.cl_up_buffer_address =
sdio_dld->sdio_dloader_data.sdioc_reg.up_buff_address.reg_val;
sdio_dld_print_info();
if (sdio_dld->done_callback)
sdio_dld->done_callback();
schedule_work(&cleanup);
pr_info(MODULE_NAME ": %s - Bootloader done, returning...", __func__);
}
/**
* writing_size_to_buf
* writes from src buffer into dest buffer. if dest buffer
* reaches its end, rollover happens.
*
* @dest: destination buffer.
* @src: source buffer.
* @dest_wr_ptr: writing pointer in destination buffer.
* @dest_size: destination buffer size.
* @dest_rd_ptr: reading pointer in destination buffer.
* @size_to_write: size of bytes to write.
* @return -how many bytes actually written to destination
* buffer.
*
* ONLY destination buffer is treated as cyclic buffer.
*/
static int writing_size_to_buf(char *dest,
const unsigned char *src,
int *dest_wr_ptr,
int dest_size,
int dest_rd_ptr,
int size_to_write)
{
int actually_written = 0;
int size_to_add = *dest_wr_ptr;
if (!dest) {
pr_err(MODULE_NAME ": %s - param ""dest"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!src) {
pr_err(MODULE_NAME ": %s - param ""src"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!dest_wr_ptr) {
pr_err(MODULE_NAME ": %s - param ""dest_wr_ptr"" is NULL.\n",
__func__);
return -EINVAL;
}
for (actually_written = 0 ;
actually_written < size_to_write ; ++actually_written) {
/* checking if buffer is full */
if (((size_to_add + 1) % dest_size) == dest_rd_ptr) {
*dest_wr_ptr = size_to_add;
return actually_written;
}
dest[size_to_add] = src[actually_written];
size_to_add = (size_to_add+1)%dest_size;
}
*dest_wr_ptr = size_to_add;
return actually_written;
}
/**
* sdioc_bytes_till_end_of_buffer - this routine calculates how many bytes are
* empty/in use. if calculation requires rap around - it will ignore the rap
* around and will do the calculation untill the end of the buffer
*
* @write_ptr: writing pointer.
* @read_ptr: reading pointer.
* @total_size: buffer size.
* @free_bytes: return value-how many free bytes.
* @bytes_in_use: return value-how many bytes in use.
* @return 0 on success or negative value on error.
*
* buffer is treated as a cyclic buffer.
*/
static int sdioc_bytes_till_end_of_buffer(int write_ptr,
int read_ptr,
int total_size,
int *free_bytes,
int *bytes_in_use)
{
if (!free_bytes) {
pr_err(MODULE_NAME ": %s - param ""free_bytes"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!bytes_in_use) {
pr_err(MODULE_NAME ": %s - param ""bytes_in_use"" is NULL.\n",
__func__);
return -EINVAL;
}
if (write_ptr >= read_ptr) {
if (read_ptr == 0)
*free_bytes = total_size - write_ptr - 1;
else
*free_bytes = total_size - write_ptr;
*bytes_in_use = write_ptr - read_ptr;
} else {
*bytes_in_use = total_size - read_ptr;
*free_bytes = read_ptr - write_ptr - 1;
}
return 0;
}
/**
* sdioc_bytes_free_in_buffer
* this routine calculates how many bytes are free in a buffer
* and how many are in use, according to its reading and
* writing pointer offsets.
*
* @write_ptr: writing pointer.
* @read_ptr: reading pointer.
* @total_size: buffer size.
* @free_bytes: return value-how many free bytes in buffer.
* @bytes_in_use: return value-how many bytes in use in buffer.
* @return 0 on success or negative value on error.
*
* buffer is treated as a cyclic buffer.
*/
static int sdioc_bytes_free_in_buffer(int write_ptr,
int read_ptr,
int total_size,
int *free_bytes,
int *bytes_in_use)
{
if (!free_bytes) {
pr_err(MODULE_NAME ": %s - param ""free_bytes"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!bytes_in_use) {
pr_err(MODULE_NAME ": %s - param ""bytes_in_use"" is NULL.\n",
__func__);
return -EINVAL;
}
/* if pointers equel - buffers are empty. nothing to read/write */
if (write_ptr >= read_ptr)
*bytes_in_use = write_ptr - read_ptr;
else
*bytes_in_use = total_size - (read_ptr - write_ptr);
*free_bytes = total_size - *bytes_in_use - 1;
return 0;
}
/*
* sdio_dld_write_room
*
* This is the write_room function of the tty driver.
*
* @tty: pointer to tty struct.
* @return free bytes for write.
*
*/
static int sdio_dld_write_room(struct tty_struct *tty)
{
return sdio_dld->sdio_dloader_data.outgoing_data.buffer_size;
}
/**
* sdio_dld_write_callback
* this is the write callback of the tty driver.
*
* @tty: pointer to tty struct.
* @buf: buffer to write from.
* @count: number of bytes to write.
* @return bytes written or negative value on error.
*
* if destination buffer has not enough room for the incoming
* data, returns an error.
*/
static int sdio_dld_write_callback(struct tty_struct *tty,
const unsigned char *buf, int count)
{
struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data;
int dst_free_bytes = 0;
int dummy = 0;
int status = 0;
int bytes_written = 0;
int total_written = 0;
static int write_retry;
int pending_to_write = count;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_count = count;
update_gd(SDIO_DLD_DEBUGFS_CASE_5_CODE);
#endif
pr_debug(MODULE_NAME ": %s - WRITING CALLBACK CALLED WITH %d bytes\n",
__func__, count);
if (!outgoing->data) {
pr_err(MODULE_NAME ": %s - param ""outgoing->data"" is NULL.\n",
__func__);
return -EINVAL;
}
pr_debug(MODULE_NAME ": %s - WRITE CALLBACK size to write to outgoing"
" buffer %d\n", __func__, count);
/* as long as there is something to write to outgoing buffer */
do {
int bytes_to_write = 0;
status = sdioc_bytes_free_in_buffer(
outgoing->offset_write_p,
outgoing->offset_read_p,
outgoing->buffer_size,
&dst_free_bytes,
&dummy);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdioc_bytes_free_in_buffer(). status=%d\n",
__func__, status);
return status;
}
/*
* if there is free room in outgoing buffer
* lock mutex and request trigger notification from the main
* task. unlock mutex, and wait for sinal
*/
if (dst_free_bytes > 0) {
write_retry = 0;
/*
* if there is more data to write to outgoing buffer
* than it can receive, wait for signal from main task
*/
if (pending_to_write > dst_free_bytes) {
/* sampling updated dst_free_bytes */
status = sdioc_bytes_free_in_buffer(
outgoing->offset_write_p,
outgoing->offset_read_p,
outgoing->buffer_size,
&dst_free_bytes,
&dummy);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in "
"Function "
"sdioc_bytes_free_in_buffer(). "
"status=%d\n", __func__, status);
return status;
}
}
bytes_to_write = min(pending_to_write, dst_free_bytes);
bytes_written =
writing_size_to_buf(outgoing->data,
buf+total_written,
&outgoing->offset_write_p,
outgoing->buffer_size,
outgoing->offset_read_p,
bytes_to_write);
/* keeping sdio_dld_info up to date */
sdio_dld_info.host_write_ptr =
sdio_dld->sdio_dloader_data.
outgoing_data.offset_write_p;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_write_tty = bytes_written;
update_gd(SDIO_DLD_DEBUGFS_CASE_3_CODE);
#endif
sdio_dld_info.global_bytes_write_tty += bytes_written;
spin_lock_irqsave(&lock2, lock_flags2);
if (sdio_dld->main_loop_event.wake_up_signal == 0) {
sdio_dld->main_loop_event.wake_up_signal = 1;
wake_up(&sdio_dld->main_loop_event.wait_event);
}
spin_unlock_irqrestore(&lock2, lock_flags2);
/*
* although outgoing buffer has enough room, writing
* failed
*/
if (bytes_written != bytes_to_write) {
pr_err(MODULE_NAME ": %s - couldn't write "
"%d bytes to " "outgoing buffer."
"bytes_written=%d\n",
__func__, bytes_to_write,
bytes_written);
return -EIO;
}
total_written += bytes_written;
pending_to_write -= bytes_written;
outgoing->num_of_bytes_in_use += bytes_written;
pr_debug(MODULE_NAME ": %s - WRITE CHUNK to outgoing "
"buffer. pending_to_write=%d, "
"outgoing_free_bytes=%d, "
"bytes_written=%d\n",
__func__,
pending_to_write,
dst_free_bytes,
bytes_written);
} else {
write_retry++;
pr_debug(MODULE_NAME ": %s - WRITE CALLBACK - NO ROOM."
" pending_to_write=%d, write_retry=%d\n",
__func__,
pending_to_write,
write_retry);
spin_lock_irqsave(&lock1, lock_flags1);
sdio_dld->write_callback_event.wake_up_signal = 0;
spin_unlock_irqrestore(&lock1, lock_flags1);
pr_debug(MODULE_NAME ": %s - WRITE CALLBACK - "
"WAITING...", __func__);
#ifdef CONFIG_DEBUG_FS
update_gd(SDIO_DLD_DEBUGFS_CASE_8_CODE);
#endif
wait_event(sdio_dld->write_callback_event.wait_event,
sdio_dld->write_callback_event.
wake_up_signal);
#ifdef CONFIG_DEBUG_FS
update_gd(SDIO_DLD_DEBUGFS_CASE_9_CODE);
#endif
pr_debug(MODULE_NAME ": %s - WRITE CALLBACK - "
"WOKE UP...", __func__);
}
} while (pending_to_write > 0 && write_retry < WRITE_RETRIES);
if (pending_to_write > 0) {
pr_err(MODULE_NAME ": %s - WRITE CALLBACK - pending data is "
"%d out of %d > 0. total written in this "
"callback = %d\n",
__func__, pending_to_write, count, total_written);
}
if (write_retry == WRITE_RETRIES) {
pr_err(MODULE_NAME ": %s, write_retry=%d= max\n",
__func__, write_retry);
}
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_bytes_cb_tty = total_written;
update_gd(SDIO_DLD_DEBUGFS_CASE_10_CODE);
#endif
return total_written;
}
/**
* sdio_memcpy_fromio_wrapper -
* reads from sdioc, and updats the sdioc registers according
* to how many bytes were actually read.
*
* @str_func: a pointer to func struct.
* @client_rd_ptr: sdioc value of downlink read ptr.
* @client_wr_ptr: sdioc value of downlink write ptr.
* @buffer_to_store: buffer to store incoming data.
* @address_to_read: address to start reading from in sdioc.
* @size_to_read: size of bytes to read.
* @client_buffer_size: sdioc downlink buffer size.
* @return 0 on success or negative value on error.
*/
static int sdio_memcpy_fromio_wrapper(struct sdio_func *str_func,
unsigned int client_rd_ptr,
unsigned int client_wr_ptr,
void *buffer_to_store,
unsigned int address_to_read_from,
int size_to_read,
int client_buffer_size)
{
int status = 0;
struct sdioc_reg_chunk *reg_str =
&sdio_dld->sdio_dloader_data.sdioc_reg;
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!buffer_to_store) {
pr_err(MODULE_NAME ": %s - param ""buffer_to_store"" is "
"NULL.\n",
__func__);
return -EINVAL;
}
if (size_to_read < 0) {
pr_err(MODULE_NAME ": %s - invalid size to read=%d\n",
__func__, size_to_read);
return -EINVAL;
}
sdio_claim_host(str_func);
pr_debug(MODULE_NAME ": %s, READING DATA - from add %d, "
"size_to_read=%d\n",
__func__, address_to_read_from, size_to_read);
status = sdio_memcpy_fromio(str_func,
(void *)buffer_to_store,
address_to_read_from,
size_to_read);
if (status) {
pr_err(MODULE_NAME ": %s - sdio_memcpy_fromio()"
" DATA failed. status=%d.\n",
__func__, status);
sdio_release_host(str_func);
return status;
}
/* updating an offset according to cyclic buffer size */
reg_str->dl_rd_ptr.reg_val =
(reg_str->dl_rd_ptr.reg_val + size_to_read) %
client_buffer_size;
/* keeping sdio_dld_info up to date */
sdio_dld_info.cl_dl_rd_ptr = reg_str->dl_rd_ptr.reg_val;
status = sdio_memcpy_toio(str_func,
reg_str->dl_rd_ptr.reg_offset,
(void *)&reg_str->dl_rd_ptr.reg_val,
sizeof(reg_str->dl_rd_ptr.reg_val));
if (status) {
pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() "
"UPDATE PTR failed. status=%d.\n",
__func__, status);
}
sdio_release_host(str_func);
return status;
}
/**
* sdio_memcpy_toio_wrapper
* writes to sdioc, and updats the sdioc registers according
* to how many bytes were actually read.
*
* @str_func: a pointer to func struct.
* @client_wr_ptr: sdioc downlink write ptr.
* @h_read_ptr: host incoming read ptrs
* @buf_write_from: buffer to write from.
* @bytes_to_write: number of bytes to write.
* @return 0 on success or negative value on error.
*/
static int sdio_memcpy_toio_wrapper(struct sdio_func *str_func,
unsigned int client_wr_ptr,
unsigned int h_read_ptr,
void *buf_write_from,
int bytes_to_write)
{
int status = 0;
struct sdioc_reg_chunk *reg_str =
&sdio_dld->sdio_dloader_data.sdioc_reg;
struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data;
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!buf_write_from) {
pr_err(MODULE_NAME ": %s - param ""buf_write_from"" is NULL.\n",
__func__);
return -EINVAL;
}
sdio_claim_host(str_func);
pr_debug(MODULE_NAME ": %s, WRITING DATA TOIO to address 0x%x, "
"bytes_to_write=%d\n",
__func__,
reg_str->up_buff_address.reg_val + reg_str->up_wr_ptr.reg_val,
bytes_to_write);
status = sdio_memcpy_toio(str_func,
reg_str->up_buff_address.reg_val +
reg_str->up_wr_ptr.reg_val,
(void *) (outgoing->data + h_read_ptr),
bytes_to_write);
if (status) {
pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() "
"DATA failed. status=%d.\n", __func__, status);
sdio_release_host(str_func);
return status;
}
sdio_dld_info.global_bytes_write_toio += bytes_to_write;
outgoing->num_of_bytes_in_use -= bytes_to_write;
/*
* if writing to client succeeded, then
* 1. update the client up_wr_ptr
* 2. update the host outgoing rd ptr
**/
reg_str->up_wr_ptr.reg_val =
((reg_str->up_wr_ptr.reg_val + bytes_to_write) %
reg_str->ul_buff_size.reg_val);
/* keeping sdio_dld_info up to date */
sdio_dld_info.cl_up_wr_ptr = reg_str->up_wr_ptr.reg_val;
outgoing->offset_read_p =
((outgoing->offset_read_p + bytes_to_write) %
outgoing->buffer_size);
/* keeping sdio_dld_info up to date*/
sdio_dld_info.host_read_ptr = outgoing->offset_read_p;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_write_toio = bytes_to_write;
update_gd(SDIO_DLD_DEBUGFS_CASE_4_CODE);
#endif
/* updating uplink write pointer according to size that was written */
status = sdio_memcpy_toio(str_func,
reg_str->up_wr_ptr.reg_offset,
(void *)(&reg_str->up_wr_ptr.reg_val),
sizeof(reg_str->up_wr_ptr.reg_val));
if (status) {
pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() "
"UPDATE PTR failed. status=%d.\n",
__func__, status);
}
sdio_release_host(str_func);
return status;
}
/**
* sdio_dld_read
* reads from sdioc
*
* @client_rd_ptr: sdioc downlink read ptr.
* @client_wr_ptr: sdioc downlink write ptr.
* @reg_str: sdioc register shadowing struct.
* @str_func: a pointer to func struct.
* @bytes_read:how many bytes read.
* @return 0 on success or negative value on error.
*/
static int sdio_dld_read(unsigned int client_rd_ptr,
unsigned int client_wr_ptr,
struct sdioc_reg_chunk *reg_str,
struct sdio_func *str_func,
int *bytes_read)
{
int status = 0;
struct sdio_data *incoming = &sdio_dld->sdio_dloader_data.incoming_data;
if (!reg_str) {
pr_err(MODULE_NAME ": %s - param ""reg_str"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!bytes_read) {
pr_err(MODULE_NAME ": %s - param ""bytes_read"" is NULL.\n",
__func__);
return -EINVAL;
}
/* there is data to read in ONE chunk */
if (client_wr_ptr > client_rd_ptr) {
status = sdio_memcpy_fromio_wrapper(
str_func,
client_rd_ptr,
client_wr_ptr,
(void *)incoming->data,
reg_str->dl_buff_address.reg_val + client_rd_ptr,
client_wr_ptr - client_rd_ptr,
reg_str->dl_buff_size.reg_val);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdio_memcpy_fromio_wrapper(). "
"SINGLE CHUNK READ. status=%d\n",
__func__, status);
return status;
}
incoming->num_of_bytes_in_use += client_wr_ptr - client_rd_ptr;
*bytes_read = client_wr_ptr - client_rd_ptr;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_to_read =
client_wr_ptr - client_rd_ptr;
update_gd(SDIO_DLD_DEBUGFS_CASE_11_CODE);
#endif
}
/* there is data to read in TWO chunks */
else {
int dl_buf_size = reg_str->dl_buff_size.reg_val;
int tail_size = dl_buf_size - client_rd_ptr;
/* reading chunk#1: from rd_ptr to the end of the buffer */
status = sdio_memcpy_fromio_wrapper(
str_func,
client_rd_ptr,
dl_buf_size,
(void *)incoming->data,
reg_str->dl_buff_address.reg_val + client_rd_ptr,
tail_size,
dl_buf_size);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdio_memcpy_fromio_wrapper(). "
"1 of 2 CHUNKS READ. status=%d\n",
__func__, status);
return status;
}
incoming->num_of_bytes_in_use += tail_size;
*bytes_read = tail_size;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_to_read = tail_size;
update_gd(SDIO_DLD_DEBUGFS_CASE_11_CODE);
#endif
/* reading chunk#2: reading from beginning buffer */
status = sdio_memcpy_fromio_wrapper(
str_func,
client_rd_ptr,
client_wr_ptr,
(void *)(incoming->data + tail_size),
reg_str->dl_buff_address.reg_val,
client_wr_ptr,
reg_str->dl_buff_size.reg_val);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdio_memcpy_fromio_wrapper(). "
"2 of 2 CHUNKS READ. status=%d\n",
__func__, status);
return status;
}
incoming->num_of_bytes_in_use += client_wr_ptr;
*bytes_read += client_wr_ptr;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_to_read = client_wr_ptr;
update_gd(SDIO_DLD_DEBUGFS_CASE_11_CODE);
#endif
}
return 0;
}
/**
* sdio_dld_main_task
* sdio downloader main task. reads mailboxf checks if there is
* anything to read, checks if host has anything to
* write.
*
* @card: a pointer to mmc_card.
* @return 0 on success or negative value on error.
*/
static int sdio_dld_main_task(void *card)
{
int status = 0;
struct tty_struct *tty = sdio_dld->tty_str;
struct sdioc_reg_chunk *reg_str =
&sdio_dld->sdio_dloader_data.sdioc_reg;
int func = sdio_dld->sdioc_boot_func;
struct sdio_func *str_func = NULL;
struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data;
struct sdio_data *incoming = &sdio_dld->sdio_dloader_data.incoming_data;
struct sdio_dld_task *task = &sdio_dld->dld_main_thread;
int retries = 0;
#ifdef PUSH_STRING
int bytes_pushed = 0;
#endif
msleep(SLEEP_MS);
if (!card) {
pr_err(MODULE_NAME ": %s - param ""card"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!tty) {
pr_err(MODULE_NAME ": %s - param ""tty"" is NULL.\n",
__func__);
return -EINVAL;
}
str_func = ((struct mmc_card *)card)->
sdio_func[REAL_FUNC_TO_FUNC_IN_ARRAY(func)];
if (!str_func) {
pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n",
__func__);
return -EINVAL;
}
while (true) {
/* client pointers for both buffers */
int client_ul_wr_ptr = 0;
int client_ul_rd_ptr = 0;
int client_dl_wr_ptr = 0;
int client_dl_rd_ptr = 0;
/* host pointer for outgoing buffer */
int h_out_wr_ptr = 0;
int h_out_rd_ptr = 0;
int h_bytes_rdy_wr = 0;
int c_bytes_rdy_rcve = 0;
int need_to_write = 0;
int need_to_read = 0;
/*
* forever, checking for signal to die, then read MailBox.
* if nothing to read or nothing to write to client, sleep,
* and again read MailBox
*/
do {
int dummy = 0;
/* checking if a signal to die was sent */
if (atomic_read(&task->please_close) == 1) {
pr_debug(MODULE_NAME ": %s - 0x%x was written "
"to 9K\n", __func__, SDIOC_EXIT_CODE);
sdio_claim_host(str_func);
/* returned value is not checked on purpose */
sdio_memcpy_toio(
str_func,
reg_str->good_to_exit_ptr.reg_offset,
(void *)&reg_str->good_to_exit_ptr.
reg_val,
sizeof(reg_str->good_to_exit_ptr.
reg_val));
sdio_release_host(str_func);
task->exit_wait.wake_up_signal = 1;
wake_up(&task->exit_wait.wait_event);
return 0;
}
status = mailbox_to_seq_chunk_read_ptrs(str_func);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"mailbox_to_seq_chunk_read_ptrs(). "
"status=%d\n", __func__, status);
return status;
}
/* calculate how many bytes the host has send */
h_out_wr_ptr = outgoing->offset_write_p;
h_out_rd_ptr = outgoing->offset_read_p;
status = sdioc_bytes_till_end_of_buffer(
h_out_wr_ptr,
h_out_rd_ptr,
outgoing->buffer_size,
&dummy,
&h_bytes_rdy_wr);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdioc_bytes_till_end_of_buffer(). "
"status=%d\n", __func__, status);
return status;
}
/* is there something to read from client */
client_dl_wr_ptr = reg_str->dl_wr_ptr.reg_val;
client_dl_rd_ptr = reg_str->dl_rd_ptr.reg_val;
if (client_dl_rd_ptr != client_dl_wr_ptr)
need_to_read = 1;
/*
* calculate how many bytes the client can receive
* from host
*/
client_ul_wr_ptr = reg_str->up_wr_ptr.reg_val;
client_ul_rd_ptr = reg_str->up_rd_ptr.reg_val;
status = sdioc_bytes_till_end_of_buffer(
client_ul_wr_ptr,
client_ul_rd_ptr,
reg_str->ul_buff_size.reg_val,
&c_bytes_rdy_rcve,
&dummy);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdioc_bytes_till_end_of_buffer(). "
"status=%d\n", __func__, status);
return status;
}
/* if host has anything to write */
if (h_bytes_rdy_wr > 0)
need_to_write = 1;
if (need_to_write || need_to_read)
break;
spin_lock_irqsave(&lock2, lock_flags2);
sdio_dld->main_loop_event.wake_up_signal = 0;
spin_unlock_irqrestore(&lock2, lock_flags2);
pr_debug(MODULE_NAME ": %s - MAIN LOOP - WAITING...\n",
__func__);
#ifdef CONFIG_DEBUG_FS
update_gd(SDIO_DLD_DEBUGFS_CASE_6_CODE);
#endif
wait_event(sdio_dld->main_loop_event.wait_event,
sdio_dld->main_loop_event.wake_up_signal);
#ifdef CONFIG_DEBUG_FS
update_gd(SDIO_DLD_DEBUGFS_CASE_7_CODE);
#endif
pr_debug(MODULE_NAME ": %s - MAIN LOOP - WOKE UP...\n",
__func__);
} while (1);
/* CHECK IF THERE IS ANYTHING TO READ IN CLIENT */
if (need_to_read) {
#ifdef PUSH_STRING
int num_push = 0;
int left = 0;
int bytes_read;
#else
int i;
#endif
need_to_read = 0;
status = sdio_dld_read(client_dl_rd_ptr,
client_dl_wr_ptr,
reg_str,
str_func,
&bytes_read);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdio_dld_read(). status=%d\n",
__func__, status);
return status;
}
sdio_dld_info.global_bytes_read_fromio +=
bytes_read;
bytes_pushed = 0;
#ifdef PUSH_STRING
left = incoming->num_of_bytes_in_use;
start_timer(&sdio_dld->push_timer,
sdio_dld->push_timer_ms);
do {
num_push = tty_insert_flip_string(
tty,
incoming->data+bytes_pushed,
left);
bytes_pushed += num_push;
left -= num_push;
tty_flip_buffer_push(tty);
} while (left != 0);
del_timer(&sdio_dld->push_timer);
if (bytes_pushed != incoming->num_of_bytes_in_use) {
pr_err(MODULE_NAME ": %s - failed\n",
__func__);
}
#else
pr_debug(MODULE_NAME ": %s - NEED TO READ %d\n",
__func__, incoming->num_of_bytes_in_use);
for (i = 0 ; i < incoming->num_of_bytes_in_use ; ++i) {
int err = 0;
err = tty_insert_flip_char(tty,
incoming->data[i],
TTY_NORMAL);
tty_flip_buffer_push(tty);
}
pr_debug(MODULE_NAME ": %s - JUST READ\n", __func__);
#endif /*PUSH_STRING*/
sdio_dld_info.global_bytes_push_tty +=
incoming->num_of_bytes_in_use;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_push_to_tty = bytes_read;
update_gd(SDIO_DLD_DEBUGFS_CASE_12_CODE);
#endif
incoming->num_of_bytes_in_use = 0;
tty_flip_buffer_push(tty);
}
/* CHECK IF THERE IS ANYTHING TO WRITE IN HOST AND HOW MUCH */
if (need_to_write) {
int dummy = 0;
do {
int bytes_to_write = min(c_bytes_rdy_rcve,
h_bytes_rdy_wr);
/*
* in case nothing to send or no room to
* receive
*/
if (bytes_to_write == 0)
break;
if (client_ul_rd_ptr == 0 &&
(client_ul_rd_ptr != client_ul_wr_ptr))
break;
/*
* if client_rd_ptr points to start, but there
* is data to read wait until WRITE_TILL_END
* before writing a chunk of data, to avoid
* writing until (BUF_SIZE - 1), because it will
* yield an extra write of "1" bytes
*/
if (client_ul_rd_ptr == 0 &&
(client_ul_rd_ptr != client_ul_wr_ptr) &&
retries < WRITE_TILL_END_RETRIES) {
retries++;
break;
}
retries = 0;
#ifdef CONFIG_DEBUG_FS
debugfs_glob.global_8k_has = h_bytes_rdy_wr;
debugfs_glob.global_9k_has = c_bytes_rdy_rcve;
debugfs_glob.global_min = bytes_to_write;
update_gd(SDIO_DLD_DEBUGFS_CASE_2_CODE);
#endif
need_to_write = 0;
pr_debug(MODULE_NAME ": %s - NEED TO WRITE "
"TOIO %d\n",
__func__, bytes_to_write);
status = sdio_memcpy_toio_wrapper(
str_func,
reg_str->up_wr_ptr.reg_val,
outgoing->offset_read_p,
(void *)((char *)outgoing->data +
outgoing->offset_read_p),
bytes_to_write);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in "
"Function "
"sdio_memcpy_toio_wrapper(). "
"SINGLE CHUNK WRITE. "
"status=%d\n",
__func__, status);
return status;
}
sdio_claim_host(str_func);
status = sdio_memcpy_fromio(
str_func,
(void *)&reg_str->up_rd_ptr.reg_val,
SDIOC_UL_RD_PTR,
sizeof(reg_str->up_rd_ptr.reg_val));
if (status) {
pr_err(MODULE_NAME ": %s - "
"sdio_memcpy_fromio() "
"failed. status=%d\n",
__func__, status);
sdio_release_host(str_func);
return status;
}
sdio_release_host(str_func);
spin_lock_irqsave(&lock1, lock_flags1);
if (sdio_dld->write_callback_event.
wake_up_signal == 0) {
sdio_dld->write_callback_event.
wake_up_signal = 1;
wake_up(&sdio_dld->
write_callback_event.
wait_event);
}
spin_unlock_irqrestore(&lock1, lock_flags1);
client_ul_wr_ptr = reg_str->up_wr_ptr.reg_val;
client_ul_rd_ptr = reg_str->up_rd_ptr.reg_val;
status = sdioc_bytes_till_end_of_buffer(
client_ul_wr_ptr,
client_ul_rd_ptr,
reg_str->ul_buff_size.reg_val,
&c_bytes_rdy_rcve,
&dummy);
/* calculate how many bytes host has to send */
h_out_wr_ptr = outgoing->offset_write_p;
h_out_rd_ptr = outgoing->offset_read_p;
status = sdioc_bytes_till_end_of_buffer(
h_out_wr_ptr,
h_out_rd_ptr,
outgoing->buffer_size,
&dummy,
&h_bytes_rdy_wr);
} while (h_out_wr_ptr != h_out_rd_ptr);
}
}
return 0;
}
/**
* sdio_dld_init_global
* initialization of sdio_dld global struct
*
* @card: a pointer to mmc_card.
* @return 0 on success or negative value on error.
*/
static int sdio_dld_init_global(struct mmc_card *card,
int(*done)(void))
{
if (!card) {
pr_err(MODULE_NAME ": %s - param ""card"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!done) {
pr_err(MODULE_NAME ": %s - param ""done"" is NULL.\n",
__func__);
return -EINVAL;
}
sdio_dld->done_callback = done;
sdio_dld->card = card;
init_waitqueue_head(&sdio_dld->dld_main_thread.exit_wait.wait_event);
sdio_dld->write_callback_event.wake_up_signal = 1;
sdio_dld->main_loop_event.wake_up_signal = 1;
sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_size.reg_offset =
SDIOC_DL_BUFF_SIZE_OFFSET;
sdio_dld->sdio_dloader_data.sdioc_reg.dl_rd_ptr.reg_offset =
SDIOC_DL_RD_PTR;
sdio_dld->sdio_dloader_data.sdioc_reg.dl_wr_ptr.reg_offset =
SDIOC_DL_WR_PTR;
sdio_dld->sdio_dloader_data.sdioc_reg.ul_buff_size.reg_offset =
SDIOC_UP_BUFF_SIZE_OFFSET;
sdio_dld->sdio_dloader_data.sdioc_reg.up_rd_ptr.reg_offset =
SDIOC_UL_RD_PTR;
sdio_dld->sdio_dloader_data.sdioc_reg.up_wr_ptr.reg_offset =
SDIOC_UL_WR_PTR;
sdio_dld->sdio_dloader_data.sdioc_reg.good_to_exit_ptr.reg_offset =
SDIOC_EXIT_PTR;
sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_address.reg_offset =
SDIOC_DL_BUFF_ADDRESS;
sdio_dld->sdio_dloader_data.sdioc_reg.up_buff_address.reg_offset =
SDIOC_UP_BUFF_ADDRESS;
sdio_dld_set_op_mode(SDIO_DLD_NORMAL_MODE);
return 0;
}
/**
* sdio_downloader_setup
* initializes the TTY driver
*
* @card: a pointer to mmc_card.
* @num_of_devices: number of devices.
* @channel_number: channel number.
* @return 0 on success or negative value on error.
*
* The TTY stack needs to know in advance how many devices it should
* plan to manage. Use this call to set up the ports that will
* be exported through SDIO.
*/
int sdio_downloader_setup(struct mmc_card *card,
unsigned int num_of_devices,
int channel_number,
int(*done)(void))
{
int status = 0;
int result = 0;
int func_in_array = 0;
struct sdio_func *str_func = NULL;
struct device *tty_dev;
if (atomic_read(&sdio_dld_in_use) == 1)
return -EBUSY;
/*
* If the setup is already complete tear down the existing
* one and reinitialize. This might happen during modem restarts
* in boot phase.
*/
if (atomic_read(&sdio_dld_setup_done) == 1)
sdio_dld_tear_down(NULL);
if (num_of_devices == 0 || num_of_devices > MAX_NUM_DEVICES) {
pr_err(MODULE_NAME ": %s - invalid number of devices\n",
__func__);
return -EINVAL;
}
if (!card) {
pr_err(MODULE_NAME ": %s - param ""card"" is NULL.\n",
__func__);
return -EINVAL;
}
if (!done) {
pr_err(MODULE_NAME ": %s - param ""done"" is NULL.\n",
__func__);
return -EINVAL;
}
sdio_dld = kzalloc(sizeof(struct sdio_downloader), GFP_KERNEL);
if (!sdio_dld) {
pr_err(MODULE_NAME ": %s - couldn't allocate sdio_dld data "
"structure.", __func__);
return -ENOMEM;
}
#ifdef CONFIG_DEBUG_FS
bootloader_debugfs_init();
#endif /* CONFIG_DEBUG_FS */
status = sdio_dld_init_global(card, done);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdio_dld_init_global(). status=%d\n",
__func__, status);
kfree(sdio_dld);
return status;
}
sdio_dld->tty_drv = alloc_tty_driver(num_of_devices);
if (!sdio_dld->tty_drv) {
pr_err(MODULE_NAME ": %s - param ""sdio_dld->tty_drv"" is "
"NULL.\n", __func__);
kfree(sdio_dld);
return -EINVAL;
}
sdio_dld_set_op_mode((enum sdio_dld_op_mode)sdio_op_mode);
/* according to op_mode, a different tty device is created */
if (sdio_dld->op_mode == SDIO_DLD_BOOT_TEST_MODE)
sdio_dld->tty_drv->name = TTY_SDIO_DEV_TEST;
else
sdio_dld->tty_drv->name = TTY_SDIO_DEV;
sdio_dld->tty_drv->owner = THIS_MODULE;
sdio_dld->tty_drv->driver_name = "SDIO_Dloader";
/* uses dynamically assigned dev_t values */
sdio_dld->tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
sdio_dld->tty_drv->subtype = SERIAL_TYPE_NORMAL;
sdio_dld->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV
| TTY_DRIVER_RESET_TERMIOS;
/* initializing the tty driver */
sdio_dld->tty_drv->init_termios = tty_std_termios;
sdio_dld->tty_drv->init_termios.c_cflag =
B4800 | CS8 | CREAD | HUPCL | CLOCAL;
sdio_dld->tty_drv->init_termios.c_ispeed = INPUT_SPEED;
sdio_dld->tty_drv->init_termios.c_ospeed = OUTPUT_SPEED;
tty_set_operations(sdio_dld->tty_drv, &sdio_dloader_tty_ops);
status = tty_register_driver(sdio_dld->tty_drv);
if (status) {
put_tty_driver(sdio_dld->tty_drv);
pr_err(MODULE_NAME ": %s - tty_register_driver() failed\n",
__func__);
sdio_dld->tty_drv = NULL;
kfree(sdio_dld);
return status;
}
tty_dev = tty_register_device(sdio_dld->tty_drv, 0, NULL);
if (IS_ERR(tty_dev)) {
pr_err(MODULE_NAME ": %s - tty_register_device() "
"failed\n", __func__);
tty_unregister_driver(sdio_dld->tty_drv);
put_tty_driver(sdio_dld->tty_drv);
kfree(sdio_dld);
return PTR_ERR(tty_dev);
}
sdio_dld->sdioc_boot_func = SDIOC_CHAN_TO_FUNC_NUM(channel_number);
func_in_array = REAL_FUNC_TO_FUNC_IN_ARRAY(sdio_dld->sdioc_boot_func);
str_func = sdio_dld->card->sdio_func[func_in_array];
status = sdio_dld_init_func(str_func);
if (status) {
pr_err(MODULE_NAME ": %s - Failure in Function "
"sdio_dld_init_func(). status=%d\n",
__func__, status);
goto exit_err;
}
#ifdef CONFIG_DEBUG_FS
sdio_dld_debug_init();
#endif
sdio_claim_host(str_func);
/*
* notifing the client by writing what mode we are by writing
* to a special register
*/
status = sdio_memcpy_toio(str_func,
SDIOC_OP_MODE_PTR,
(void *)&sdio_dld->op_mode,
sizeof(sdio_dld->op_mode));
sdio_release_host(str_func);
if (status) {
pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() "
"writing to OP_MODE_REGISTER failed. "
"status=%d.\n",
__func__, status);
goto exit_err;
}
atomic_set(&sdio_dld_setup_done, 1);
return 0;
exit_err:
tty_unregister_device(sdio_dld->tty_drv, 0);
result = tty_unregister_driver(sdio_dld->tty_drv);
if (result)
pr_err(MODULE_NAME ": %s - tty_unregister_driver() "
"failed. result=%d\n", __func__, -result);
put_tty_driver(sdio_dld->tty_drv);
kfree(sdio_dld);
atomic_set(&sdio_dld_setup_done, 0);
return status;
}
static void sdio_dld_tear_down(struct work_struct *work)
{
int status = 0;
if (atomic_read(&sdio_dld_in_use) == 1) {
del_timer_sync(&sdio_dld->timer);
del_timer_sync(&sdio_dld->push_timer);
sdio_dld_dealloc_local_buffers();
}
tty_unregister_device(sdio_dld->tty_drv, 0);
status = tty_unregister_driver(sdio_dld->tty_drv);
if (status) {
pr_err(MODULE_NAME ": %s - tty_unregister_driver() failed\n",
__func__);
}
put_tty_driver(sdio_dld->tty_drv);
kfree(sdio_dld);
atomic_set(&sdio_dld_in_use, 0);
atomic_set(&sdio_dld_setup_done, 0);
}
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("SDIO Downloader");
MODULE_AUTHOR("Yaniv Gardi <ygardi@codeaurora.org>");
MODULE_VERSION(DRV_VERSION);