Hi Daniel I've tried to import the SMD server pack into the kernel. But somehow it went wrong. Any suggestions? best regards leviathan --- arch/arm/mach-msm/Kconfig | 25 + arch/arm/mach-msm/Makefile | 4 +- arch/arm/mach-msm/board-trout-battery.c | 796 ++++++++++++ arch/arm/mach-msm/board-trout-hsusb.c | 352 ++++++ arch/arm/mach-msm/gpio_chip.h | 38 + arch/arm/mach-msm/include/mach/board-hsusb.h | 68 + arch/arm/mach-msm/include/mach/board.h | 2 + arch/arm/mach-msm/smd_rpcrouter.c | 1310 ++++++++++++++++++++ arch/arm/mach-msm/smd_rpcrouter.h | 195 +++ arch/arm/mach-msm/smd_rpcrouter_device.c | 377 ++++++ arch/arm/mach-msm/smd_rpcrouter_servers.c | 229 ++++ drivers/staging/dream/include/mach/msm_rpcrouter.h | 1 + 12 files changed, 3395 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-msm/board-trout-battery.c create mode 100644 arch/arm/mach-msm/board-trout-hsusb.c create mode 100644 arch/arm/mach-msm/gpio_chip.h create mode 100644 arch/arm/mach-msm/include/mach/board-hsusb.h create mode 100644 arch/arm/mach-msm/smd_rpcrouter.c create mode 100644 arch/arm/mach-msm/smd_rpcrouter.h create mode 100644 arch/arm/mach-msm/smd_rpcrouter_device.c create mode 100644 arch/arm/mach-msm/smd_rpcrouter_servers.c diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig index 737cf3e..7eb8722 100644 --- a/arch/arm/mach-msm/Kconfig +++ b/arch/arm/mach-msm/Kconfig @@ -1,5 +1,30 @@ if ARCH_MSM +config MSM_AMSS_VERSION + int + default 6210 if MSM_AMSS_VERSION_6210 + default 6220 if MSM_AMSS_VERSION_6220 + default 6225 if MSM_AMSS_VERSION_6225 + default 6350 if MSM_AMSS_VERSION_6350 + +choice + prompt "AMSS modem firmware version" + + default MSM_AMSS_VERSION_6225 + + config MSM_AMSS_VERSION_6210 + bool "6.2.10" + + config MSM_AMSS_VERSION_6220 + bool "6.2.20" + + config MSM_AMSS_VERSION_6225 + bool "6.2.20 + New ADSP" + + config MSM_AMSS_VERSION_6350 + bool "6.3.50" +endchoice + choice prompt "Qualcomm MSM SoC Type" default ARCH_MSM7X00A diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index cdd1caf..6f4da0d 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -13,10 +13,10 @@ endif obj-$(CONFIG_ARCH_QSD8X50) += sirc.o obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o +obj-$(CONFIG_MSM_SMD) += smd_rpcrouter.o smd_rpcrouter_device.o smd_rpcrouter_servers.o obj-$(CONFIG_MSM_SMD) += last_radio_log.o -obj-$(CONFIG_MACH_TROUT) += board-trout.o board-trout-gpio.o board-trout-mmc.o devices-msm7x00.o -obj-$(CONFIG_MACH_TROUT) += board-trout.o board-trout-gpio.o board-trout-mmc.o board-trout-panel.o devices-msm7x00.o +obj-$(CONFIG_MACH_TROUT) += board-trout.o board-trout-gpio.o board-trout-mmc.o board-trout-hsusb.o board-trout-battery.o board-trout-panel.o devices-msm7x00.o obj-$(CONFIG_MACH_HALIBUT) += board-halibut.o devices-msm7x00.o obj-$(CONFIG_ARCH_MSM7X30) += board-msm7x30.o devices-msm7x30.o obj-$(CONFIG_ARCH_QSD8X50) += board-qsd8x50.o devices-qsd8x50.o diff --git a/arch/arm/mach-msm/board-trout-battery.c b/arch/arm/mach-msm/board-trout-battery.c new file mode 100644 index 0000000..2ea57b8 --- /dev/null +++ b/arch/arm/mach-msm/board-trout-battery.c @@ -0,0 +1,796 @@ +/* arch/arm/mach-msm/htc_battery.c + * + * Copyright (C) 2008 HTC Corporation. + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/power_supply.h> +#include <linux/platform_device.h> +#include <linux/debugfs.h> +#include "../drivers/staging/dream/include/linux/wakelock.h" +#include <asm/gpio.h> +#include "../drivers/staging/dream/include/mach/msm_rpcrouter.h" +#include <mach/board.h> +#include <asm/mach-types.h> +#include "board-trout.h" + +static struct wake_lock vbus_wake_lock; + +#define TRACE_BATT 0 + +#if TRACE_BATT +#include <linux/rtc.h> + +#define BATT(x...) do { \ +struct timespec ts; \ +struct rtc_time tm; \ +getnstimeofday(&ts); \ +rtc_time_to_tm(ts.tv_sec, &tm); \ +printk(KERN_INFO "[BATT] " x); \ +printk(" at %lld (%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", \ +ktime_to_ns(ktime_get()), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, \ +tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec); \ +} while (0) +#else +#define BATT(x...) do {} while (0) +#endif + +/* rpc related */ +#define APP_BATT_PDEV_NAME "rs30100001:00000000" +#define APP_BATT_PROG 0x30100001 +#define APP_BATT_VER MSM_RPC_VERS(0,0) +#define HTC_PROCEDURE_BATTERY_NULL 0 +#define HTC_PROCEDURE_GET_BATT_LEVEL 1 +#define HTC_PROCEDURE_GET_BATT_INFO 2 +#define HTC_PROCEDURE_GET_CABLE_STATUS 3 +#define HTC_PROCEDURE_SET_BATT_DELTA 4 + +/* module debugger */ +#define HTC_BATTERY_DEBUG 1 +#define BATTERY_PREVENTION 1 + +/* Enable this will shut down if no battery */ +#define ENABLE_BATTERY_DETECTION 0 +/* Sapphire pin changes: + * USB_ID (GPIO 90) is renamed to AC_IN (GPIO 30) + * CHARGER_EN (CPLD MISC2 bit[0]) is move to PMIC (MPP_14). + * ISET (CPLD MISC2 bit[1]) is move to PMIC (MPP_13). */ +#define GPIO_SAPPHIRE_USB_ID 30 + +#define GPIO_BATTERY_DETECTION 21 +#define GPIO_BATTERY_CHARGER_EN 128 + +/* Charge current selection */ +#define GPIO_BATTERY_CHARGER_CURRENT 129 + +typedef enum { + DISABLE = 0, + ENABLE_SLOW_CHG, + ENABLE_FAST_CHG +} batt_ctl_t; + +/* This order is the same as htc_power_supplies[] + * And it's also the same as htc_cable_status_update() + */ +typedef enum { + CHARGER_BATTERY = 0, + CHARGER_USB, + CHARGER_AC +} charger_type_t; + +const char *charger_tags[] = {"none", "USB", "AC"}; + +struct battery_info_reply { + u32 batt_id; /* Battery ID from ADC */ + u32 batt_vol; /* Battery voltage from ADC */ + u32 batt_temp; /* Battery Temperature (C) from formula and ADC */ + u32 batt_current; /* Battery current from ADC */ + u32 level; /* formula */ + u32 charging_source; /* 0: no cable, 1:usb, 2:AC */ + u32 charging_enabled; /* 0: Disable, 1: Enable */ + u32 full_bat; /* Full capacity of battery (mAh) */ +}; + +struct htc_battery_info { + int present; + unsigned long update_time; + + /* lock to protect the battery info */ + struct mutex lock; + + /* lock held while calling the arm9 to query the battery info */ + struct mutex rpc_lock; + struct battery_info_reply rep; +}; + +static struct msm_rpc_endpoint *endpoint; + +static struct htc_battery_info htc_batt_info; + +static unsigned int cache_time = 1000; + +static int htc_battery_initial = 0; + +static enum power_supply_property htc_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static enum power_supply_property htc_power_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *supply_list[] = { + "battery", +}; + +/* HTC dedicated attributes */ +static ssize_t htc_battery_show_property(struct device *dev, + struct device_attribute *attr, + char *buf); + +static int htc_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); + +static int htc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); + +static struct power_supply htc_power_supplies[] = { + { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = htc_battery_properties, + .num_properties = ARRAY_SIZE(htc_battery_properties), + .get_property = htc_battery_get_property, + }, + { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = htc_power_properties, + .num_properties = ARRAY_SIZE(htc_power_properties), + .get_property = htc_power_get_property, + }, + { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = htc_power_properties, + .num_properties = ARRAY_SIZE(htc_power_properties), + .get_property = htc_power_get_property, + }, +}; + +static int g_usb_online; + +/* -------------------------------------------------------------------------- */ + +#if defined(CONFIG_DEBUG_FS) +int htc_battery_set_charging(batt_ctl_t ctl); +static int batt_debug_set(void *data, u64 val) +{ + return htc_battery_set_charging((batt_ctl_t) val); +} + +static int batt_debug_get(void *data, u64 *val) +{ + return -ENOSYS; +} + +DEFINE_SIMPLE_ATTRIBUTE(batt_debug_fops, batt_debug_get, batt_debug_set, "%llu\n"); +static int __init batt_debug_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("htc_battery", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("charger_state", 0644, dent, NULL, &batt_debug_fops); + + return 0; +} + +device_initcall(batt_debug_init); +#endif + +static int init_batt_gpio(void) +{ + if (!machine_is_trout()) + return 0; + + if (gpio_request(GPIO_BATTERY_DETECTION, "batt_detect") < 0) + goto gpio_failed; + if (gpio_request(GPIO_BATTERY_CHARGER_EN, "charger_en") < 0) + goto gpio_failed; + if (gpio_request(GPIO_BATTERY_CHARGER_CURRENT, "charge_current") < 0) + goto gpio_failed; + + return 0; + +gpio_failed: + return -EINVAL; + +} + +/* + * battery_charging_ctrl - battery charing control. + * @ctl: battery control command + * + */ +static int battery_charging_ctrl(batt_ctl_t ctl) +{ + int result = 0; + + /* The charing operations are move to A9 in Sapphire. */ + if (!machine_is_trout()) + return result; + + switch (ctl) { + case DISABLE: + BATT("charger OFF"); + /* 0 for enable; 1 disable */ + result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 1); + break; + case ENABLE_SLOW_CHG: + BATT("charger ON (SLOW)"); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 0); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0); + break; + case ENABLE_FAST_CHG: + BATT("charger ON (FAST)"); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 1); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0); + break; + default: + printk(KERN_ERR "Not supported battery ctr called.!\n"); + result = -EINVAL; + break; + } + + return result; +} + +int htc_battery_set_charging(batt_ctl_t ctl) +{ + int rc; + + if ((rc = battery_charging_ctrl(ctl)) < 0) + goto result; + + if (!htc_battery_initial) { + htc_batt_info.rep.charging_enabled = ctl & 0x3; + } else { + mutex_lock(&htc_batt_info.lock); + htc_batt_info.rep.charging_enabled = ctl & 0x3; + mutex_unlock(&htc_batt_info.lock); + } +result: + return rc; +} + +int htc_battery_status_update(u32 curr_level) +{ + int notify; + if (!htc_battery_initial) + return 0; + + mutex_lock(&htc_batt_info.lock); + notify = (htc_batt_info.rep.level != curr_level); + htc_batt_info.rep.level = curr_level; + mutex_unlock(&htc_batt_info.lock); + + if (notify) + power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]); + return 0; +} + +int htc_cable_status_update(int status) +{ + int rc = 0; + unsigned last_source; + + if (!htc_battery_initial) + return 0; + + if (status < CHARGER_BATTERY || status > CHARGER_AC) { + BATT("%s: Not supported cable status received!", __func__); + return -EINVAL; + } + mutex_lock(&htc_batt_info.lock); + /* A9 reports USB charging when helf AC cable in and China AC charger. */ + /* Work arround: notify userspace AC charging first, + and notify USB charging again when receiving usb connected notificaiton from usb driver. */ + last_source = htc_batt_info.rep.charging_source; + if (status == CHARGER_USB && g_usb_online == 0) + htc_batt_info.rep.charging_source = CHARGER_AC; + else { + htc_batt_info.rep.charging_source = status; + /* usb driver will not notify usb offline. */ + if (status == CHARGER_BATTERY && g_usb_online == 1) + g_usb_online = 0; + } + + /* TODO: Don't call usb driver again with the same cable status. */ + msm_hsusb_set_vbus_state(status == CHARGER_USB); + + if (htc_batt_info.rep.charging_source != last_source) { + if (htc_batt_info.rep.charging_source == CHARGER_USB || + htc_batt_info.rep.charging_source == CHARGER_AC) { + wake_lock(&vbus_wake_lock); + } else { + /* give userspace some time to see the uevent and update + * LED state or whatnot... + */ + wake_lock_timeout(&vbus_wake_lock, HZ / 2); + } + if (htc_batt_info.rep.charging_source == CHARGER_BATTERY || last_source == CHARGER_BATTERY) + power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]); + if (htc_batt_info.rep.charging_source == CHARGER_USB || last_source == CHARGER_USB) + power_supply_changed(&htc_power_supplies[CHARGER_USB]); + if (htc_batt_info.rep.charging_source == CHARGER_AC || last_source == CHARGER_AC) + power_supply_changed(&htc_power_supplies[CHARGER_AC]); + } + mutex_unlock(&htc_batt_info.lock); + + return rc; +} + +/* A9 reports USB charging when helf AC cable in and China AC charger. */ +/* Work arround: notify userspace AC charging first, +and notify USB charging again when receiving usb connected notification from usb driver. */ +void notify_usb_connected(int online) +{ + mutex_lock(&htc_batt_info.lock); + + BATT("%s: online=%d, g_usb_online=%d", __func__, online, g_usb_online); + + if (g_usb_online != online) { + g_usb_online = online; + if (online && htc_batt_info.rep.charging_source == CHARGER_AC) { + mutex_unlock(&htc_batt_info.lock); + htc_cable_status_update(CHARGER_USB); + mutex_lock(&htc_batt_info.lock); + } else if (online) { + BATT("warning: usb connected but charging source=%d", htc_batt_info.rep.charging_source); + } + } + mutex_unlock(&htc_batt_info.lock); +} + +static int htc_get_batt_info(struct battery_info_reply *buffer) +{ + struct rpc_request_hdr req; + + struct htc_get_batt_info_rep { + struct rpc_reply_hdr hdr; + struct battery_info_reply info; + } rep; + + int rc; + + if (buffer == NULL) + return -EINVAL; + + rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_BATT_INFO, + &req, sizeof(req), + &rep, sizeof(rep), + 5 * HZ); + if ( rc < 0 ) + return rc; + + mutex_lock(&htc_batt_info.lock); + buffer->batt_id = be32_to_cpu(rep.info.batt_id); + buffer->batt_vol = be32_to_cpu(rep.info.batt_vol); + buffer->batt_temp = be32_to_cpu(rep.info.batt_temp); + buffer->batt_current = be32_to_cpu(rep.info.batt_current); + buffer->level = be32_to_cpu(rep.info.level); + /* Move the rules of charging_source to cable_status_update. */ + /* buffer->charging_source = be32_to_cpu(rep.info.charging_source); */ + buffer->charging_enabled = be32_to_cpu(rep.info.charging_enabled); + buffer->full_bat = be32_to_cpu(rep.info.full_bat); + mutex_unlock(&htc_batt_info.lock); + + return 0; +} + +/* -------------------------------------------------------------------------- */ +static int htc_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + charger_type_t charger; + + mutex_lock(&htc_batt_info.lock); + charger = htc_batt_info.rep.charging_source; + mutex_unlock(&htc_batt_info.lock); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = (charger == CHARGER_AC ? 1 : 0); + else if (psy->type == POWER_SUPPLY_TYPE_USB) + val->intval = (charger == CHARGER_USB ? 1 : 0); + else + val->intval = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int htc_battery_get_charging_status(void) +{ + u32 level; + charger_type_t charger; + int ret; + + mutex_lock(&htc_batt_info.lock); + charger = htc_batt_info.rep.charging_source; + + switch (charger) { + case CHARGER_BATTERY: + ret = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case CHARGER_USB: + case CHARGER_AC: + level = htc_batt_info.rep.level; + if (level == 100) + ret = POWER_SUPPLY_STATUS_FULL; + else + ret = POWER_SUPPLY_STATUS_CHARGING; + break; + default: + ret = POWER_SUPPLY_STATUS_UNKNOWN; + } + mutex_unlock(&htc_batt_info.lock); + return ret; +} + +static int htc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = htc_battery_get_charging_status(); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = htc_batt_info.present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + mutex_lock(&htc_batt_info.lock); + val->intval = htc_batt_info.rep.level; + mutex_unlock(&htc_batt_info.lock); + break; + default: + return -EINVAL; + } + + return 0; +} + +#define HTC_BATTERY_ATTR(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IRUGO, .owner = THIS_MODULE }, \ + .show = htc_battery_show_property, \ + .store = NULL, \ +} + +static struct device_attribute htc_battery_attrs[] = { + HTC_BATTERY_ATTR(batt_id), + HTC_BATTERY_ATTR(batt_vol), + HTC_BATTERY_ATTR(batt_temp), + HTC_BATTERY_ATTR(batt_current), + HTC_BATTERY_ATTR(charging_source), + HTC_BATTERY_ATTR(charging_enabled), + HTC_BATTERY_ATTR(full_bat), +}; + +enum { + BATT_ID = 0, + BATT_VOL, + BATT_TEMP, + BATT_CURRENT, + CHARGING_SOURCE, + CHARGING_ENABLED, + FULL_BAT, +}; + +static int htc_rpc_set_delta(unsigned delta) +{ + struct set_batt_delta_req { + struct rpc_request_hdr hdr; + uint32_t data; + } req; + + req.data = cpu_to_be32(delta); + return msm_rpc_call(endpoint, HTC_PROCEDURE_SET_BATT_DELTA, + &req, sizeof(req), 5 * HZ); +} + + +static ssize_t htc_battery_set_delta(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int rc; + unsigned long delta = 0; + + delta = simple_strtoul(buf, NULL, 10); + + if (delta > 100) + return -EINVAL; + + mutex_lock(&htc_batt_info.rpc_lock); + rc = htc_rpc_set_delta(delta); + mutex_unlock(&htc_batt_info.rpc_lock); + if (rc < 0) + return rc; + return count; +} + +static struct device_attribute htc_set_delta_attrs[] = { + __ATTR(delta, S_IWUSR | S_IWGRP, NULL, htc_battery_set_delta), +}; + +static int htc_battery_create_attrs(struct device * dev) +{ + int i, j, rc; + + for (i = 0; i < ARRAY_SIZE(htc_battery_attrs); i++) { + rc = device_create_file(dev, &htc_battery_attrs[i]); + if (rc) + goto htc_attrs_failed; + } + + for (j = 0; j < ARRAY_SIZE(htc_set_delta_attrs); j++) { + rc = device_create_file(dev, &htc_set_delta_attrs[j]); + if (rc) + goto htc_delta_attrs_failed; + } + + goto succeed; + +htc_attrs_failed: + while (i--) + device_remove_file(dev, &htc_battery_attrs[i]); +htc_delta_attrs_failed: + while (j--) + device_remove_file(dev, &htc_set_delta_attrs[i]); +succeed: + return rc; +} + +static ssize_t htc_battery_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i = 0; + const ptrdiff_t off = attr - htc_battery_attrs; + + /* rpc lock is used to prevent two threads from calling + * into the get info rpc at the same time + */ + + mutex_lock(&htc_batt_info.rpc_lock); + /* check cache time to decide if we need to update */ + if (htc_batt_info.update_time && + time_before(jiffies, htc_batt_info.update_time + + msecs_to_jiffies(cache_time))) + goto dont_need_update; + + if (htc_get_batt_info(&htc_batt_info.rep) < 0) { + printk(KERN_ERR "%s: rpc failed!!!\n", __FUNCTION__); + } else { + htc_batt_info.update_time = jiffies; + } +dont_need_update: + mutex_unlock(&htc_batt_info.rpc_lock); + + mutex_lock(&htc_batt_info.lock); + switch (off) { + case BATT_ID: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_id); + break; + case BATT_VOL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_vol); + break; + case BATT_TEMP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_temp); + break; + case BATT_CURRENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_current); + break; + case CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.charging_source); + break; + case CHARGING_ENABLED: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.charging_enabled); + break; + case FULL_BAT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.full_bat); + break; + default: + i = -EINVAL; + } + mutex_unlock(&htc_batt_info.lock); + + return i; +} + +static int htc_battery_probe(struct platform_device *pdev) +{ + int i, rc; + + /* init battery gpio */ + if ((rc = init_batt_gpio()) < 0) { + printk(KERN_ERR "%s: init battery gpio failed!\n", __FUNCTION__); + return rc; + } + + /* init structure data member */ + htc_batt_info.update_time = jiffies; + /* A9 will shutdown the phone if battery is pluged out, so this value is always 1. + htc_batt_info.present = gpio_get_value(GPIO_TROUT_MBAT_IN); + */ + htc_batt_info.present = 1; + + /* init rpc */ + endpoint = msm_rpc_connect(APP_BATT_PROG, APP_BATT_VER, 0); + if (IS_ERR(endpoint)) { + printk(KERN_ERR "%s: init rpc failed! rc = %ld\n", + __FUNCTION__, PTR_ERR(endpoint)); + return rc; + } + + /* init power supplier framework */ + for (i = 0; i < ARRAY_SIZE(htc_power_supplies); i++) { + rc = power_supply_register(&pdev->dev, &htc_power_supplies[i]); + if (rc) + printk(KERN_ERR "Failed to register power supply (%d)\n", rc); + } + + /* create htc detail attributes */ + htc_battery_create_attrs(htc_power_supplies[CHARGER_BATTERY].dev); + + /* After battery driver gets initialized, send rpc request to inquiry + * the battery status in case of we lost some info + */ + htc_battery_initial = 1; + + mutex_lock(&htc_batt_info.rpc_lock); + htc_batt_info.rep.charging_source = CHARGER_BATTERY; + if (htc_get_batt_info(&htc_batt_info.rep) < 0) + printk(KERN_ERR "%s: get info failed\n", __FUNCTION__); + + if (htc_rpc_set_delta(1) < 0) + printk(KERN_ERR "%s: set delta failed\n", __FUNCTION__); + htc_batt_info.update_time = jiffies; + mutex_unlock(&htc_batt_info.rpc_lock); + + return 0; +} + +static struct platform_driver htc_battery_driver = { + .probe = htc_battery_probe, + .driver = { + .name = APP_BATT_PDEV_NAME, + .owner = THIS_MODULE, + }, +}; + +/* batt_mtoa server definitions */ +#define BATT_MTOA_PROG 0x30100000 +#define BATT_MTOA_VERS 0 +#define RPC_BATT_MTOA_NULL 0 +#define RPC_BATT_MTOA_SET_CHARGING_PROC 1 +#define RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC 2 +#define RPC_BATT_MTOA_LEVEL_UPDATE_PROC 3 + +struct rpc_batt_mtoa_set_charging_args { + int enable; +}; + +struct rpc_batt_mtoa_cable_status_update_args { + int status; +}; + +struct rpc_dem_battery_update_args { + uint32_t level; +}; + +static int handle_battery_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, unsigned len) +{ + switch (req->procedure) { + case RPC_BATT_MTOA_NULL: + return 0; + + case RPC_BATT_MTOA_SET_CHARGING_PROC: { + struct rpc_batt_mtoa_set_charging_args *args; + args = (struct rpc_batt_mtoa_set_charging_args *)(req + 1); + args->enable = be32_to_cpu(args->enable); + BATT("set_charging: enable=%d",args->enable); + htc_battery_set_charging(args->enable); + return 0; + } + case RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC: { + struct rpc_batt_mtoa_cable_status_update_args *args; + args = (struct rpc_batt_mtoa_cable_status_update_args *)(req + 1); + args->status = be32_to_cpu(args->status); + BATT("cable_status_update: status=%d",args->status); + htc_cable_status_update(args->status); + return 0; + } + case RPC_BATT_MTOA_LEVEL_UPDATE_PROC: { + struct rpc_dem_battery_update_args *args; + args = (struct rpc_dem_battery_update_args *)(req + 1); + args->level = be32_to_cpu(args->level); + BATT("dem_battery_update: level=%d",args->level); + htc_battery_status_update(args->level); + return 0; + } + default: + printk(KERN_ERR "%s: program 0x%08x:%d: unknown procedure %d\n", + __FUNCTION__, req->prog, req->vers, req->procedure); + return -ENODEV; + } +} + +static struct msm_rpc_server battery_server = { + .prog = BATT_MTOA_PROG, + .vers = BATT_MTOA_VERS, + .rpc_call = handle_battery_call, +}; + +static int __init htc_battery_init(void) +{ + wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present"); + mutex_init(&htc_batt_info.lock); + mutex_init(&htc_batt_info.rpc_lock); + msm_rpc_create_server(&battery_server); + platform_driver_register(&htc_battery_driver); + return 0; +} + +module_init(htc_battery_init); +MODULE_DESCRIPTION("HTC Battery Driver"); +MODULE_LICENSE("GPL"); + diff --git a/arch/arm/mach-msm/board-trout-hsusb.c b/arch/arm/mach-msm/board-trout-hsusb.c new file mode 100644 index 0000000..a4af380 --- /dev/null +++ b/arch/arm/mach-msm/board-trout-hsusb.c @@ -0,0 +1,352 @@ +/* linux/arch/arm/mach-msm/devices.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai <thomas_tsai@xxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/platform_device.h> + +#include <linux/dma-mapping.h> +#include <mach/msm_iomap.h> +#include <mach/dma.h> +#include "gpio_chip.h" +#include "devices.h" +#include <mach/board.h> +#include <mach/board-hsusb.h> +#include <mach/msm_hsusb.h> + +#ifdef CONFIG_USB_FUNCTION +#include <linux/usb/mass_storage_function.h> +#endif + +#include <asm/mach/flash.h> +#include <asm/setup.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/partitions.h> +#include <linux/delay.h> +#include "../drivers/staging/dream/include/mach/msm_rpcrouter.h" +#include <mach/msm_iomap.h> +#include <mach/mmc.h> + +static char *df_serialno = "000000000000"; + +#if 0 +struct platform_device *devices[] __initdata = { + &msm_device_nand, + &msm_device_smd, + &msm_device_i2c, +}; + +void __init msm_add_devices(void) +{ + platform_add_devices(devices, ARRAY_SIZE(devices)); +} +#endif + +#define HSUSB_API_INIT_PHY_PROC 2 +#define HSUSB_API_PROG 0x30000064 +#define HSUSB_API_VERS MSM_RPC_VERS(1,1) + +static void internal_phy_reset(void) +{ + struct msm_rpc_endpoint *usb_ep; + int rc; + struct hsusb_phy_start_req { + struct rpc_request_hdr hdr; + } req; + + printk(KERN_INFO "msm_hsusb_phy_reset\n"); + + usb_ep = msm_rpc_connect(HSUSB_API_PROG, HSUSB_API_VERS, 0); + if (IS_ERR(usb_ep)) { + printk(KERN_ERR "%s: init rpc failed! error: %ld\n", + __func__, PTR_ERR(usb_ep)); + goto close; + } + rc = msm_rpc_call(usb_ep, HSUSB_API_INIT_PHY_PROC, + &req, sizeof(req), 5 * HZ); + if (rc < 0) + printk(KERN_ERR "%s: rpc call failed! (%d)\n", __func__, rc); + +close: + msm_rpc_close(usb_ep); +} + +/* adjust eye diagram, disable vbusvalid interrupts */ +static int hsusb_phy_init_seq[] = { 0x40, 0x31, 0x1D, 0x0D, 0x1D, 0x10, -1 }; + +#ifdef CONFIG_USB_FUNCTION +static char *usb_functions[] = { +#if defined(CONFIG_USB_FUNCTION_MASS_STORAGE) || defined(CONFIG_USB_FUNCTION_UMS) + "usb_mass_storage", +#endif +#ifdef CONFIG_USB_FUNCTION_ADB + "adb", +#endif +}; + +static struct msm_hsusb_product usb_products[] = { + { + .product_id = 0x0c01, + .functions = 0x00000001, /* usb_mass_storage */ + }, + { + .product_id = 0x0c02, + .functions = 0x00000003, /* usb_mass_storage + adb */ + }, +}; +#endif + +struct msm_hsusb_platform_data msm_hsusb_pdata = { + .phy_reset = internal_phy_reset, + .phy_init_seq = hsusb_phy_init_seq, + .usb_connected = notify_usb_connected, +#ifdef CONFIG_USB_FUNCTION + .vendor_id = 0x0bb4, + .product_id = 0x0c02, + .version = 0x0100, + .product_name = "Android Phone", + .manufacturer_name = "HTC", + + .functions = usb_functions, + .num_functions = ARRAY_SIZE(usb_functions), + .products = usb_products, + .num_products = ARRAY_SIZE(usb_products), +#endif +}; + +#ifdef CONFIG_USB_FUNCTION +static struct usb_mass_storage_platform_data mass_storage_pdata = { + .nluns = 1, + .buf_size = 16384, + .vendor = "HTC ", + .product = "Android Phone ", + .release = 0x0100, +}; + +static struct platform_device usb_mass_storage_device = { + .name = "usb_mass_storage", + .id = -1, + .dev = { + .platform_data = &mass_storage_pdata, + }, +}; +#endif + +#ifdef CONFIG_USB_ANDROID +static struct android_usb_platform_data android_usb_pdata = { + .vendor_id = 0x0bb4, + .product_id = 0x0c01, + .adb_product_id = 0x0c02, + .version = 0x0100, + .product_name = "Android Phone", + .manufacturer_name = "HTC", + .nluns = 1, +}; + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; +#endif + +void __init msm_add_usb_devices(void (*phy_reset) (void)) +{ + /* setup */ + if (phy_reset) + msm_hsusb_pdata.phy_reset = phy_reset; + msm_device_hsusb.dev.platform_data = &msm_hsusb_pdata; + platform_device_register(&msm_device_hsusb); +#ifdef CONFIG_USB_FUNCTION_MASS_STORAGE + platform_device_register(&usb_mass_storage_device); +#endif +#ifdef CONFIG_USB_ANDROID + platform_device_register(&android_usb_device); +#endif +} + +static struct resource ram_console_resource[] = { + { + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device ram_console_device = { + .name = "ram_console", + .id = -1, + .num_resources = ARRAY_SIZE(ram_console_resource), + .resource = ram_console_resource, +}; + +#define PM_LIBPROG 0x30000061 +#if (CONFIG_MSM_AMSS_VERSION == 6220) || (CONFIG_MSM_AMSS_VERSION == 6225) +#define PM_LIBVERS 0xfb837d0b +#else +#define PM_LIBVERS 0x10001 +#endif + +#if 0 +static struct platform_device *msm_serial_devices[] __initdata = { + &msm_device_uart1, + &msm_device_uart2, + &msm_device_uart3, + #ifdef CONFIG_SERIAL_MSM_HS + &msm_device_uart_dm1, + &msm_device_uart_dm2, + #endif +}; + +int __init msm_add_serial_devices(unsigned num) +{ + if (num > MSM_SERIAL_NUM) + return -EINVAL; + + return platform_device_register(msm_serial_devices[num]); +} +#endif + +#define ATAG_SMI 0x4d534D71 +/* setup calls mach->fixup, then parse_tags, parse_cmdline + * We need to setup meminfo in mach->fixup, so this function + * will need to traverse each tag to find smi tag. + */ +int __init parse_tag_smi(const struct tag *tags) +{ + int smi_sz = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_SMI) { + printk(KERN_DEBUG "find the smi tag\n"); + find = 1; + break; + } + } + if (!find) + return -1; + + printk(KERN_DEBUG "parse_tag_smi: smi size = %d\n", t->u.mem.size); + smi_sz = t->u.mem.size; + return smi_sz; +} +__tagtable(ATAG_SMI, parse_tag_smi); + + +#define ATAG_HWID 0x4d534D72 +int __init parse_tag_hwid(const struct tag *tags) +{ + int hwid = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_HWID) { + printk(KERN_DEBUG "find the hwid tag\n"); + find = 1; + break; + } + } + + if (find) + hwid = t->u.revision.rev; + printk(KERN_DEBUG "parse_tag_hwid: hwid = 0x%x\n", hwid); + return hwid; +} +__tagtable(ATAG_HWID, parse_tag_hwid); + +#define ATAG_SKUID 0x4d534D73 +int __init parse_tag_skuid(const struct tag *tags) +{ + int skuid = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_SKUID) { + printk(KERN_DEBUG "find the skuid tag\n"); + find = 1; + break; + } + } + + if (find) + skuid = t->u.revision.rev; + printk(KERN_DEBUG "parse_tag_skuid: hwid = 0x%x\n", skuid); + return skuid; +} +__tagtable(ATAG_SKUID, parse_tag_skuid); + +#define ATAG_ENGINEERID 0x4d534D75 +int __init parse_tag_engineerid(const struct tag *tags) +{ + int engineerid = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_ENGINEERID) { + printk(KERN_DEBUG "find the engineer tag\n"); + find = 1; + break; + } + } + + if (find) + engineerid = t->u.revision.rev; + printk(KERN_DEBUG "parse_tag_engineerid: hwid = 0x%x\n", engineerid); + return engineerid; +} +__tagtable(ATAG_ENGINEERID, parse_tag_engineerid); + +static int mfg_mode; +int __init board_mfg_mode_init(char *s) +{ + if (!strcmp(s, "normal")) + mfg_mode = 0; + else if (!strcmp(s, "factory2")) + mfg_mode = 1; + else if (!strcmp(s, "recovery")) + mfg_mode = 2; + else if (!strcmp(s, "charge")) + mfg_mode = 3; + + return 1; +} +__setup("androidboot.mode=", board_mfg_mode_init); + + +int board_mfg_mode(void) +{ + return mfg_mode; +} + +static int __init board_serialno_setup(char *serialno) +{ + char *str; + + /* use default serial number when mode is factory2 */ + if (mfg_mode == 1 || !strlen(serialno)) + str = df_serialno; + else + str = serialno; +#ifdef CONFIG_USB_FUNCTION + msm_hsusb_pdata.serial_number = str; +#endif +#ifdef CONFIG_USB_ANDROID + android_usb_pdata.serial_number = str; +#endif + return 1; +} + +__setup("androidboot.serialno=", board_serialno_setup); diff --git a/arch/arm/mach-msm/gpio_chip.h b/arch/arm/mach-msm/gpio_chip.h new file mode 100644 index 0000000..eab9f09 --- /dev/null +++ b/arch/arm/mach-msm/gpio_chip.h @@ -0,0 +1,38 @@ +/* arch/arm/mach-msm/gpio_chip.h + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _LINUX_GPIO_CHIP_H +#define _LINUX_GPIO_CHIP_H + +#include <linux/list.h> + +struct gpio_chip { + struct list_head list; + struct gpio_state *state; + + unsigned int start; + unsigned int end; + + int (*configure)(struct gpio_chip *chip, unsigned int gpio, unsigned long flags); + int (*get_irq_num)(struct gpio_chip *chip, unsigned int gpio, unsigned int *irqp, unsigned long *irqnumflagsp); + int (*read)(struct gpio_chip *chip, unsigned int gpio); + int (*write)(struct gpio_chip *chip, unsigned int gpio, unsigned on); + int (*read_detect_status)(struct gpio_chip *chip, unsigned int gpio); + int (*clear_detect_status)(struct gpio_chip *chip, unsigned int gpio); +}; + +int register_gpio_chip(struct gpio_chip *gpio_chip); + +#endif diff --git a/arch/arm/mach-msm/include/mach/board-hsusb.h b/arch/arm/mach-msm/include/mach/board-hsusb.h new file mode 100644 index 0000000..994accd --- /dev/null +++ b/arch/arm/mach-msm/include/mach/board-hsusb.h @@ -0,0 +1,68 @@ +/* arch/arm/mach-msm/include/mach/BOARD_HTC.h + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai <thomas_tsai@xxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ +#ifndef __ASM_ARCH_MSM_BOARD_HTC_H +#define __ASM_ARCH_MSM_BOARD_HTC_H + +#include <linux/types.h> +#include <linux/list.h> +#include <asm/setup.h> + +struct msm_pmem_setting{ + resource_size_t pmem_start; + resource_size_t pmem_size; + resource_size_t pmem_adsp_start; + resource_size_t pmem_adsp_size; + resource_size_t pmem_gpu0_start; + resource_size_t pmem_gpu0_size; + resource_size_t pmem_gpu1_start; + resource_size_t pmem_gpu1_size; + resource_size_t pmem_camera_start; + resource_size_t pmem_camera_size; + resource_size_t ram_console_start; + resource_size_t ram_console_size; +}; + +enum { + MSM_SERIAL_UART1 = 0, + MSM_SERIAL_UART2, + MSM_SERIAL_UART3, +#ifdef CONFIG_SERIAL_MSM_HS + MSM_SERIAL_UART1DM, + MSM_SERIAL_UART2DM, +#endif + MSM_SERIAL_NUM, +}; + + +/* common init routines for use by arch/arm/mach-msm/board-*.c */ + +void __init msm_add_usb_devices(void (*phy_reset) (void)); +void __init msm_add_mem_devices(struct msm_pmem_setting *setting); +void __init msm_init_pmic_vibrator(void); + +struct mmc_platform_data; +int __init msm_add_sdcc_devices(unsigned int controller, struct mmc_platform_data *plat); +int __init msm_add_serial_devices(unsigned uart); + +int __init board_mfg_mode(void); +int __init parse_tag_smi(const struct tag *tags); +int __init parse_tag_hwid(const struct tag * tags); +int __init parse_tag_skuid(const struct tag * tags); +int parse_tag_engineerid(const struct tag * tags); + +void notify_usb_connected(int online); + +char *board_serialno(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/board.h b/arch/arm/mach-msm/include/mach/board.h index e302fbd..fd2c087 100644 --- a/arch/arm/mach-msm/include/mach/board.h +++ b/arch/arm/mach-msm/include/mach/board.h @@ -41,4 +41,6 @@ void __init msm_init_gpio(void); void __init msm_clock_init(struct clk *clock_tbl, unsigned num_clocks); void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *); +static void msm_hsusb_set_vbus_state(int online); + #endif diff --git a/arch/arm/mach-msm/smd_rpcrouter.c b/arch/arm/mach-msm/smd_rpcrouter.c new file mode 100644 index 0000000..739ac29 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter.c @@ -0,0 +1,1310 @@ +/* arch/arm/mach-msm/smd_rpcrouter.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2009 QUALCOMM Incorporated. + * Author: San Mehat <san@xxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +/* TODO: handle cases where smd_write() will tempfail due to full fifo */ +/* TODO: thread priority? schedule a work to bump it? */ +/* TODO: maybe make server_list_lock a mutex */ +/* TODO: pool fragments to avoid kmalloc/kfree churn */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/cdev.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include "../drivers/staging/dream/include/linux/wakelock.h" +#include <asm/uaccess.h> +#include <asm/byteorder.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> + +#include <asm/byteorder.h> + +#include <mach/msm_smd.h> +#include "smd_rpcrouter.h" + +#define TRACE_R2R_MSG 0 +#define TRACE_R2R_RAW 0 +#define TRACE_RPC_MSG 0 +#define TRACE_NOTIFY_MSG 0 + +#define MSM_RPCROUTER_DEBUG 0 +#define MSM_RPCROUTER_DEBUG_PKT 0 +#define MSM_RPCROUTER_R2R_DEBUG 0 +#define DUMP_ALL_RECEIVED_HEADERS 0 + +#define DIAG(x...) printk("[RR] ERROR " x) + +#if MSM_RPCROUTER_DEBUG +#define D(x...) printk(x) +#else +#define D(x...) do {} while (0) +#endif + +#if TRACE_R2R_MSG +#define RR(x...) printk("[RR] "x) +#else +#define RR(x...) do {} while (0) +#endif + +#if TRACE_RPC_MSG +#define IO(x...) printk("[RPC] "x) +#else +#define IO(x...) do {} while (0) +#endif + +#if TRACE_NOTIFY_MSG +#define NTFY(x...) printk(KERN_ERR "[NOTIFY] "x) +#else +#define NTFY(x...) do {} while (0) +#endif + +static LIST_HEAD(local_endpoints); +static LIST_HEAD(remote_endpoints); + +static LIST_HEAD(server_list); + +static smd_channel_t *smd_channel; +static int initialized; +static wait_queue_head_t newserver_wait; +static wait_queue_head_t smd_wait; +static int smd_wait_count; /* odd while waiting */ + +static DEFINE_SPINLOCK(local_endpoints_lock); +static DEFINE_SPINLOCK(remote_endpoints_lock); +static DEFINE_SPINLOCK(server_list_lock); +static DEFINE_SPINLOCK(smd_lock); + +static struct workqueue_struct *rpcrouter_workqueue; +static struct wake_lock rpcrouter_wake_lock; +static int rpcrouter_need_len; + +static atomic_t next_xid = ATOMIC_INIT(1); +static uint8_t next_pacmarkid; + +static void do_read_data(struct work_struct *work); +static void do_create_pdevs(struct work_struct *work); +static void do_create_rpcrouter_pdev(struct work_struct *work); + +static DECLARE_WORK(work_read_data, do_read_data); +static DECLARE_WORK(work_create_pdevs, do_create_pdevs); +static DECLARE_WORK(work_create_rpcrouter_pdev, do_create_rpcrouter_pdev); + +#define RR_STATE_IDLE 0 +#define RR_STATE_HEADER 1 +#define RR_STATE_BODY 2 +#define RR_STATE_ERROR 3 + +struct rr_context { + struct rr_packet *pkt; + uint8_t *ptr; + uint32_t state; /* current assembly state */ + uint32_t count; /* bytes needed in this state */ +}; + +struct rr_context the_rr_context; + +static struct platform_device rpcrouter_pdev = { + .name = "oncrpc_router", + .id = -1, +}; + + +static int rpcrouter_send_control_msg(union rr_control_msg *msg) +{ + struct rr_header hdr; + unsigned long flags; + int need; + + if (!(msg->cmd == RPCROUTER_CTRL_CMD_HELLO) && !initialized) { + printk(KERN_ERR "rpcrouter_send_control_msg(): Warning, " + "router not initialized\n"); + return -EINVAL; + } + + hdr.version = RPCROUTER_VERSION; + hdr.type = msg->cmd; + hdr.src_pid = RPCROUTER_PID_LOCAL; + hdr.src_cid = RPCROUTER_ROUTER_ADDRESS; + hdr.confirm_rx = 0; + hdr.size = sizeof(*msg); + hdr.dst_pid = 0; + hdr.dst_cid = RPCROUTER_ROUTER_ADDRESS; + + /* TODO: what if channel is full? */ + + need = sizeof(hdr) + hdr.size; + spin_lock_irqsave(&smd_lock, flags); + while (smd_write_avail(smd_channel) < need) { + spin_unlock_irqrestore(&smd_lock, flags); + msleep(250); + spin_lock_irqsave(&smd_lock, flags); + } + smd_write(smd_channel, &hdr, sizeof(hdr)); + smd_write(smd_channel, msg, hdr.size); + spin_unlock_irqrestore(&smd_lock, flags); + return 0; +} + +static struct rr_server *rpcrouter_create_server(uint32_t pid, + uint32_t cid, + uint32_t prog, + uint32_t ver) +{ + struct rr_server *server; + unsigned long flags; + int rc; + + server = kmalloc(sizeof(struct rr_server), GFP_KERNEL); + if (!server) + return ERR_PTR(-ENOMEM); + + memset(server, 0, sizeof(struct rr_server)); + server->pid = pid; + server->cid = cid; + server->prog = prog; + server->vers = ver; + + spin_lock_irqsave(&server_list_lock, flags); + list_add_tail(&server->list, &server_list); + spin_unlock_irqrestore(&server_list_lock, flags); + + if (pid == RPCROUTER_PID_REMOTE) { + rc = msm_rpcrouter_create_server_cdev(server); + if (rc < 0) + goto out_fail; + } + return server; +out_fail: + spin_lock_irqsave(&server_list_lock, flags); + list_del(&server->list); + spin_unlock_irqrestore(&server_list_lock, flags); + kfree(server); + return ERR_PTR(rc); +} + +static void rpcrouter_destroy_server(struct rr_server *server) +{ + unsigned long flags; + + spin_lock_irqsave(&server_list_lock, flags); + list_del(&server->list); + spin_unlock_irqrestore(&server_list_lock, flags); + device_destroy(msm_rpcrouter_class, server->device_number); + kfree(server); +} + +static struct rr_server *rpcrouter_lookup_server(uint32_t prog, uint32_t ver) +{ + struct rr_server *server; + unsigned long flags; + + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->prog == prog + && server->vers == ver) { + spin_unlock_irqrestore(&server_list_lock, flags); + return server; + } + } + spin_unlock_irqrestore(&server_list_lock, flags); + return NULL; +} + +static struct rr_server *rpcrouter_lookup_server_by_dev(dev_t dev) +{ + struct rr_server *server; + unsigned long flags; + + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->device_number == dev) { + spin_unlock_irqrestore(&server_list_lock, flags); + return server; + } + } + spin_unlock_irqrestore(&server_list_lock, flags); + return NULL; +} + +struct msm_rpc_endpoint *msm_rpcrouter_create_local_endpoint(dev_t dev) +{ + struct msm_rpc_endpoint *ept; + unsigned long flags; + + ept = kmalloc(sizeof(struct msm_rpc_endpoint), GFP_KERNEL); + if (!ept) + return NULL; + memset(ept, 0, sizeof(struct msm_rpc_endpoint)); + + /* mark no reply outstanding */ + ept->reply_pid = 0xffffffff; + + ept->cid = (uint32_t) ept; + ept->pid = RPCROUTER_PID_LOCAL; + ept->dev = dev; + + if ((dev != msm_rpcrouter_devno) && (dev != MKDEV(0, 0))) { + struct rr_server *srv; + /* + * This is a userspace client which opened + * a program/ver devicenode. Bind the client + * to that destination + */ + srv = rpcrouter_lookup_server_by_dev(dev); + /* TODO: bug? really? */ + BUG_ON(!srv); + + ept->dst_pid = srv->pid; + ept->dst_cid = srv->cid; + ept->dst_prog = cpu_to_be32(srv->prog); + ept->dst_vers = cpu_to_be32(srv->vers); + ept->flags |= MSM_RPC_ENABLE_RECEIVE; + + D("Creating local ept %p @ %08x:%08x\n", ept, srv->prog, srv->vers); + } else { + /* mark not connected */ + ept->dst_pid = 0xffffffff; + D("Creating a master local ept %p\n", ept); + } + + init_waitqueue_head(&ept->wait_q); + INIT_LIST_HEAD(&ept->read_q); + spin_lock_init(&ept->read_q_lock); + wake_lock_init(&ept->read_q_wake_lock, WAKE_LOCK_SUSPEND, "rpc_read"); + INIT_LIST_HEAD(&ept->incomplete); + + spin_lock_irqsave(&local_endpoints_lock, flags); + list_add_tail(&ept->list, &local_endpoints); + spin_unlock_irqrestore(&local_endpoints_lock, flags); + return ept; +} + +int msm_rpcrouter_destroy_local_endpoint(struct msm_rpc_endpoint *ept) +{ + int rc; + union rr_control_msg msg; + + msg.cmd = RPCROUTER_CTRL_CMD_REMOVE_CLIENT; + msg.cli.pid = ept->pid; + msg.cli.cid = ept->cid; + + RR("x REMOVE_CLIENT id=%d:%08x\n", ept->pid, ept->cid); + rc = rpcrouter_send_control_msg(&msg); + if (rc < 0) + return rc; + + wake_lock_destroy(&ept->read_q_wake_lock); + list_del(&ept->list); + kfree(ept); + return 0; +} + +static int rpcrouter_create_remote_endpoint(uint32_t cid) +{ + struct rr_remote_endpoint *new_c; + unsigned long flags; + + new_c = kmalloc(sizeof(struct rr_remote_endpoint), GFP_KERNEL); + if (!new_c) + return -ENOMEM; + memset(new_c, 0, sizeof(struct rr_remote_endpoint)); + + new_c->cid = cid; + new_c->pid = RPCROUTER_PID_REMOTE; + init_waitqueue_head(&new_c->quota_wait); + spin_lock_init(&new_c->quota_lock); + + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_add_tail(&new_c->list, &remote_endpoints); + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + return 0; +} + +static struct msm_rpc_endpoint *rpcrouter_lookup_local_endpoint(uint32_t cid) +{ + struct msm_rpc_endpoint *ept; + unsigned long flags; + + spin_lock_irqsave(&local_endpoints_lock, flags); + list_for_each_entry(ept, &local_endpoints, list) { + if (ept->cid == cid) { + spin_unlock_irqrestore(&local_endpoints_lock, flags); + return ept; + } + } + spin_unlock_irqrestore(&local_endpoints_lock, flags); + return NULL; +} + +static struct rr_remote_endpoint *rpcrouter_lookup_remote_endpoint(uint32_t cid) +{ + struct rr_remote_endpoint *ept; + unsigned long flags; + + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_for_each_entry(ept, &remote_endpoints, list) { + if (ept->cid == cid) { + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + return ept; + } + } + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + return NULL; +} + +static int process_control_msg(union rr_control_msg *msg, int len) +{ + union rr_control_msg ctl; + struct rr_server *server; + struct rr_remote_endpoint *r_ept; + int rc = 0; + unsigned long flags; + + if (len != sizeof(*msg)) { + printk(KERN_ERR "rpcrouter: r2r msg size %d != %d\n", + len, sizeof(*msg)); + return -EINVAL; + } + + switch (msg->cmd) { + case RPCROUTER_CTRL_CMD_HELLO: + RR("o HELLO\n"); + + RR("x HELLO\n"); + memset(&ctl, 0, sizeof(ctl)); + ctl.cmd = RPCROUTER_CTRL_CMD_HELLO; + rpcrouter_send_control_msg(&ctl); + + initialized = 1; + + /* Send list of servers one at a time */ + ctl.cmd = RPCROUTER_CTRL_CMD_NEW_SERVER; + + /* TODO: long time to hold a spinlock... */ + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + ctl.srv.pid = server->pid; + ctl.srv.cid = server->cid; + ctl.srv.prog = server->prog; + ctl.srv.vers = server->vers; + + RR("x NEW_SERVER id=%d:%08x prog=%08x:%08x\n", + server->pid, server->cid, + server->prog, server->vers); + + rpcrouter_send_control_msg(&ctl); + } + spin_unlock_irqrestore(&server_list_lock, flags); + + queue_work(rpcrouter_workqueue, &work_create_rpcrouter_pdev); + break; + + case RPCROUTER_CTRL_CMD_RESUME_TX: + RR("o RESUME_TX id=%d:%08x\n", msg->cli.pid, msg->cli.cid); + + r_ept = rpcrouter_lookup_remote_endpoint(msg->cli.cid); + if (!r_ept) { + printk(KERN_ERR + "rpcrouter: Unable to resume client\n"); + break; + } + spin_lock_irqsave(&r_ept->quota_lock, flags); + r_ept->tx_quota_cntr = 0; + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + wake_up(&r_ept->quota_wait); + break; + + case RPCROUTER_CTRL_CMD_NEW_SERVER: + RR("o NEW_SERVER id=%d:%08x prog=%08x:%08x\n", + msg->srv.pid, msg->srv.cid, msg->srv.prog, msg->srv.vers); + + server = rpcrouter_lookup_server(msg->srv.prog, msg->srv.vers); + + if (!server) { + server = rpcrouter_create_server( + msg->srv.pid, msg->srv.cid, + msg->srv.prog, msg->srv.vers); + if (!server) + return -ENOMEM; + /* + * XXX: Verify that its okay to add the + * client to our remote client list + * if we get a NEW_SERVER notification + */ + if (!rpcrouter_lookup_remote_endpoint(msg->srv.cid)) { + rc = rpcrouter_create_remote_endpoint( + msg->srv.cid); + if (rc < 0) + printk(KERN_ERR + "rpcrouter:Client create" + "error (%d)\n", rc); + } + schedule_work(&work_create_pdevs); + wake_up(&newserver_wait); + } else { + if ((server->pid == msg->srv.pid) && + (server->cid == msg->srv.cid)) { + printk(KERN_ERR "rpcrouter: Duplicate svr\n"); + } else { + server->pid = msg->srv.pid; + server->cid = msg->srv.cid; + } + } + break; + + case RPCROUTER_CTRL_CMD_REMOVE_SERVER: + RR("o REMOVE_SERVER prog=%08x:%d\n", + msg->srv.prog, msg->srv.vers); + server = rpcrouter_lookup_server(msg->srv.prog, msg->srv.vers); + if (server) + rpcrouter_destroy_server(server); + break; + + case RPCROUTER_CTRL_CMD_REMOVE_CLIENT: + RR("o REMOVE_CLIENT id=%d:%08x\n", msg->cli.pid, msg->cli.cid); + if (msg->cli.pid != RPCROUTER_PID_REMOTE) { + printk(KERN_ERR + "rpcrouter: Denying remote removal of " + "local client\n"); + break; + } + r_ept = rpcrouter_lookup_remote_endpoint(msg->cli.cid); + if (r_ept) { + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_del(&r_ept->list); + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + kfree(r_ept); + } + + /* Notify local clients of this event */ + printk(KERN_ERR "rpcrouter: LOCAL NOTIFICATION NOT IMP\n"); + rc = -ENOSYS; + + break; + default: + RR("o UNKNOWN(%08x)\n", msg->cmd); + rc = -ENOSYS; + } + + return rc; +} + +static void do_create_rpcrouter_pdev(struct work_struct *work) +{ + platform_device_register(&rpcrouter_pdev); +} + +static void do_create_pdevs(struct work_struct *work) +{ + unsigned long flags; + struct rr_server *server; + + /* TODO: race if destroyed while being registered */ + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->pid == RPCROUTER_PID_REMOTE) { + if (server->pdev_name[0] == 0) { + spin_unlock_irqrestore(&server_list_lock, + flags); + msm_rpcrouter_create_server_pdev(server); + schedule_work(&work_create_pdevs); + return; + } + } + } + spin_unlock_irqrestore(&server_list_lock, flags); +} + +static void rpcrouter_smdnotify(void *_dev, unsigned event) +{ + if (event != SMD_EVENT_DATA) + return; + + if (smd_read_avail(smd_channel) >= rpcrouter_need_len) + wake_lock(&rpcrouter_wake_lock); + wake_up(&smd_wait); +} + +static void *rr_malloc(unsigned sz) +{ + void *ptr = kmalloc(sz, GFP_KERNEL); + if (ptr) + return ptr; + + printk(KERN_ERR "rpcrouter: kmalloc of %d failed, retrying...\n", sz); + do { + ptr = kmalloc(sz, GFP_KERNEL); + } while (!ptr); + + return ptr; +} + +/* TODO: deal with channel teardown / restore */ +static int rr_read(void *data, int len) +{ + int rc; + unsigned long flags; +// printk("rr_read() %d\n", len); + for(;;) { + spin_lock_irqsave(&smd_lock, flags); + if (smd_read_avail(smd_channel) >= len) { + rc = smd_read(smd_channel, data, len); + spin_unlock_irqrestore(&smd_lock, flags); + if (rc == len) + return 0; + else + return -EIO; + } + rpcrouter_need_len = len; + wake_unlock(&rpcrouter_wake_lock); + spin_unlock_irqrestore(&smd_lock, flags); + +// printk("rr_read: waiting (%d)\n", len); + smd_wait_count++; + wake_up(&smd_wait); + wait_event(smd_wait, smd_read_avail(smd_channel) >= len); + smd_wait_count++; + } + return 0; +} + +static uint32_t r2r_buf[RPCROUTER_MSGSIZE_MAX]; + +static void do_read_data(struct work_struct *work) +{ + struct rr_header hdr; + struct rr_packet *pkt; + struct rr_fragment *frag; + struct msm_rpc_endpoint *ept; + uint32_t pm, mid; + unsigned long flags; + + if (rr_read(&hdr, sizeof(hdr))) + goto fail_io; + +#if TRACE_R2R_RAW + RR("- ver=%d type=%d src=%d:%08x crx=%d siz=%d dst=%d:%08x\n", + hdr.version, hdr.type, hdr.src_pid, hdr.src_cid, + hdr.confirm_rx, hdr.size, hdr.dst_pid, hdr.dst_cid); +#endif + + if (hdr.version != RPCROUTER_VERSION) { + DIAG("version %d != %d\n", hdr.version, RPCROUTER_VERSION); + goto fail_data; + } + if (hdr.size > RPCROUTER_MSGSIZE_MAX) { + DIAG("msg size %d > max %d\n", hdr.size, RPCROUTER_MSGSIZE_MAX); + goto fail_data; + } + + if (hdr.dst_cid == RPCROUTER_ROUTER_ADDRESS) { + if (rr_read(r2r_buf, hdr.size)) + goto fail_io; + process_control_msg((void*) r2r_buf, hdr.size); + goto done; + } + + if (hdr.size < sizeof(pm)) { + DIAG("runt packet (no pacmark)\n"); + goto fail_data; + } + if (rr_read(&pm, sizeof(pm))) + goto fail_io; + + hdr.size -= sizeof(pm); + + frag = rr_malloc(hdr.size + sizeof(*frag)); + frag->next = NULL; + frag->length = hdr.size; + if (rr_read(frag->data, hdr.size)) + goto fail_io; + + ept = rpcrouter_lookup_local_endpoint(hdr.dst_cid); + if (!ept) { + DIAG("no local ept for cid %08x\n", hdr.dst_cid); + kfree(frag); + goto done; + } + + /* See if there is already a partial packet that matches our mid + * and if so, append this fragment to that packet. + */ + mid = PACMARK_MID(pm); + list_for_each_entry(pkt, &ept->incomplete, list) { + if (pkt->mid == mid) { + pkt->last->next = frag; + pkt->last = frag; + pkt->length += frag->length; + if (PACMARK_LAST(pm)) { + list_del(&pkt->list); + goto packet_complete; + } + goto done; + } + } + /* This mid is new -- create a packet for it, and put it on + * the incomplete list if this fragment is not a last fragment, + * otherwise put it on the read queue. + */ + pkt = rr_malloc(sizeof(struct rr_packet)); + pkt->first = frag; + pkt->last = frag; + memcpy(&pkt->hdr, &hdr, sizeof(hdr)); + pkt->mid = mid; + pkt->length = frag->length; + if (!PACMARK_LAST(pm)) { + list_add_tail(&pkt->list, &ept->incomplete); + goto done; + } + +packet_complete: + spin_lock_irqsave(&ept->read_q_lock, flags); + if (ept->flags & MSM_RPC_ENABLE_RECEIVE) { + wake_lock(&ept->read_q_wake_lock); + list_add_tail(&pkt->list, &ept->read_q); + wake_up(&ept->wait_q); + } else { + pr_warning("smd_rpcrouter: Unexpected incoming data on %08x:%08x\n", + be32_to_cpu(ept->dst_prog), + be32_to_cpu(ept->dst_vers)); + } + spin_unlock_irqrestore(&ept->read_q_lock, flags); +done: + + if (hdr.confirm_rx) { + union rr_control_msg msg; + + msg.cmd = RPCROUTER_CTRL_CMD_RESUME_TX; + msg.cli.pid = hdr.dst_pid; + msg.cli.cid = hdr.dst_cid; + + RR("x RESUME_TX id=%d:%08x\n", msg.cli.pid, msg.cli.cid); + rpcrouter_send_control_msg(&msg); + } + + queue_work(rpcrouter_workqueue, &work_read_data); + return; + +fail_io: +fail_data: + printk(KERN_ERR "rpc_router has died\n"); + wake_unlock(&rpcrouter_wake_lock); +} + +void msm_rpc_setup_req(struct rpc_request_hdr *hdr, uint32_t prog, + uint32_t vers, uint32_t proc) +{ + memset(hdr, 0, sizeof(struct rpc_request_hdr)); + hdr->xid = cpu_to_be32(atomic_add_return(1, &next_xid)); + hdr->rpc_vers = cpu_to_be32(2); + hdr->prog = cpu_to_be32(prog); + hdr->vers = cpu_to_be32(vers); + hdr->procedure = cpu_to_be32(proc); +} + +struct msm_rpc_endpoint *msm_rpc_open(void) +{ + struct msm_rpc_endpoint *ept; + + ept = msm_rpcrouter_create_local_endpoint(MKDEV(0, 0)); + if (ept == NULL) + return ERR_PTR(-ENOMEM); + + return ept; +} + +int msm_rpc_close(struct msm_rpc_endpoint *ept) +{ + return msm_rpcrouter_destroy_local_endpoint(ept); +} +EXPORT_SYMBOL(msm_rpc_close); + +int msm_rpc_write(struct msm_rpc_endpoint *ept, void *buffer, int count) +{ + struct rr_header hdr; + uint32_t pacmark; + struct rpc_request_hdr *rq = buffer; + struct rr_remote_endpoint *r_ept; + unsigned long flags; + int needed; + DEFINE_WAIT(__wait); + + /* TODO: fragmentation for large outbound packets */ + if (count > (RPCROUTER_MSGSIZE_MAX - sizeof(uint32_t)) || !count) + return -EINVAL; + + /* snoop the RPC packet and enforce permissions */ + + /* has to have at least the xid and type fields */ + if (count < (sizeof(uint32_t) * 2)) { + printk(KERN_ERR "rr_write: rejecting runt packet\n"); + return -EINVAL; + } + + if (rq->type == 0) { + /* RPC CALL */ + if (count < (sizeof(uint32_t) * 6)) { + printk(KERN_ERR + "rr_write: rejecting runt call packet\n"); + return -EINVAL; + } + if (ept->dst_pid == 0xffffffff) { + printk(KERN_ERR "rr_write: not connected\n"); + return -ENOTCONN; + } + +#if CONFIG_MSM_AMSS_VERSION >= 6350 + if ((ept->dst_prog != rq->prog) || + !msm_rpc_is_compatible_version( + be32_to_cpu(ept->dst_vers), + be32_to_cpu(rq->vers))) { +#else + if (ept->dst_prog != rq->prog || ept->dst_vers != rq->vers) { +#endif + printk(KERN_ERR + "rr_write: cannot write to %08x:%d " + "(bound to %08x:%d)\n", + be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), + be32_to_cpu(ept->dst_prog), + be32_to_cpu(ept->dst_vers)); + return -EINVAL; + } + hdr.dst_pid = ept->dst_pid; + hdr.dst_cid = ept->dst_cid; + IO("CALL on ept %p to %08x:%08x @ %d:%08x (%d bytes) (xid %x proc %x)\n", + ept, + be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), + ept->dst_pid, ept->dst_cid, count, + be32_to_cpu(rq->xid), be32_to_cpu(rq->procedure)); + } else { + /* RPC REPLY */ + /* TODO: locking */ + if (ept->reply_pid == 0xffffffff) { + printk(KERN_ERR + "rr_write: rejecting unexpected reply\n"); + return -EINVAL; + } + if (ept->reply_xid != rq->xid) { + printk(KERN_ERR + "rr_write: rejecting packet w/ bad xid\n"); + return -EINVAL; + } + + hdr.dst_pid = ept->reply_pid; + hdr.dst_cid = ept->reply_cid; + + /* consume this reply */ + ept->reply_pid = 0xffffffff; + + IO("REPLY on ept %p to xid=%d @ %d:%08x (%d bytes)\n", + ept, + be32_to_cpu(rq->xid), hdr.dst_pid, hdr.dst_cid, count); + } + + r_ept = rpcrouter_lookup_remote_endpoint(hdr.dst_cid); + + if (!r_ept) { + printk(KERN_ERR + "msm_rpc_write(): No route to ept " + "[PID %x CID %x]\n", hdr.dst_pid, hdr.dst_cid); + return -EHOSTUNREACH; + } + + /* Create routing header */ + hdr.type = RPCROUTER_CTRL_CMD_DATA; + hdr.version = RPCROUTER_VERSION; + hdr.src_pid = ept->pid; + hdr.src_cid = ept->cid; + hdr.confirm_rx = 0; + hdr.size = count + sizeof(uint32_t); + + for (;;) { + prepare_to_wait(&r_ept->quota_wait, &__wait, + TASK_INTERRUPTIBLE); + spin_lock_irqsave(&r_ept->quota_lock, flags); + if (r_ept->tx_quota_cntr < RPCROUTER_DEFAULT_RX_QUOTA) + break; + if (signal_pending(current) && + (!(ept->flags & MSM_RPC_UNINTERRUPTIBLE))) + break; + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + schedule(); + } + finish_wait(&r_ept->quota_wait, &__wait); + + if (signal_pending(current) && + (!(ept->flags & MSM_RPC_UNINTERRUPTIBLE))) { + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + return -ERESTARTSYS; + } + r_ept->tx_quota_cntr++; + if (r_ept->tx_quota_cntr == RPCROUTER_DEFAULT_RX_QUOTA) + hdr.confirm_rx = 1; + + /* bump pacmark while interrupts disabled to avoid race + * probably should be atomic op instead + */ + pacmark = PACMARK(count, ++next_pacmarkid, 0, 1); + + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + + spin_lock_irqsave(&smd_lock, flags); + + needed = sizeof(hdr) + hdr.size; + while (smd_write_avail(smd_channel) < needed) { + spin_unlock_irqrestore(&smd_lock, flags); + msleep(250); + spin_lock_irqsave(&smd_lock, flags); + } + + /* TODO: deal with full fifo */ + smd_write(smd_channel, &hdr, sizeof(hdr)); + smd_write(smd_channel, &pacmark, sizeof(pacmark)); + smd_write(smd_channel, buffer, count); + + spin_unlock_irqrestore(&smd_lock, flags); + + return count; +} +EXPORT_SYMBOL(msm_rpc_write); + +/* + * NOTE: It is the responsibility of the caller to kfree buffer + */ +int msm_rpc_read(struct msm_rpc_endpoint *ept, void **buffer, + unsigned user_len, long timeout) +{ + struct rr_fragment *frag, *next; + char *buf; + int rc; + + rc = __msm_rpc_read(ept, &frag, user_len, timeout); + if (rc <= 0) + return rc; + + /* single-fragment messages conveniently can be + * returned as-is (the buffer is at the front) + */ + if (frag->next == 0) { + *buffer = (void*) frag; + return rc; + } + + /* multi-fragment messages, we have to do it the + * hard way, which is rather disgusting right now + */ + buf = rr_malloc(rc); + *buffer = buf; + + while (frag != NULL) { + memcpy(buf, frag->data, frag->length); + next = frag->next; + buf += frag->length; + kfree(frag); + frag = next; + } + + return rc; +} + +int msm_rpc_call(struct msm_rpc_endpoint *ept, uint32_t proc, + void *_request, int request_size, + long timeout) +{ + return msm_rpc_call_reply(ept, proc, + _request, request_size, + NULL, 0, timeout); +} +EXPORT_SYMBOL(msm_rpc_call); + +int msm_rpc_call_reply(struct msm_rpc_endpoint *ept, uint32_t proc, + void *_request, int request_size, + void *_reply, int reply_size, + long timeout) +{ + struct rpc_request_hdr *req = _request; + struct rpc_reply_hdr *reply; + int rc; + + if (request_size < sizeof(*req)) + return -ETOOSMALL; + + if (ept->dst_pid == 0xffffffff) + return -ENOTCONN; + + /* We can't use msm_rpc_setup_req() here, because dst_prog and + * dst_vers here are already in BE. + */ + memset(req, 0, sizeof(*req)); + req->xid = cpu_to_be32(atomic_add_return(1, &next_xid)); + req->rpc_vers = cpu_to_be32(2); + req->prog = ept->dst_prog; + req->vers = ept->dst_vers; + req->procedure = cpu_to_be32(proc); + + /* Allow replys to be added to the queue */ + ept->flags |= MSM_RPC_ENABLE_RECEIVE; + + rc = msm_rpc_write(ept, req, request_size); + if (rc < 0) + goto error; + + for (;;) { + rc = msm_rpc_read(ept, (void*) &reply, -1, timeout); + if (rc < 0) + goto error; + if (rc < (3 * sizeof(uint32_t))) { + rc = -EIO; + break; + } + /* we should not get CALL packets -- ignore them */ + if (reply->type == 0) { + kfree(reply); + continue; + } + /* If an earlier call timed out, we could get the (no + * longer wanted) reply for it. Ignore replies that + * we don't expect. + */ + if (reply->xid != req->xid) { + kfree(reply); + continue; + } + if (reply->reply_stat != 0) { + rc = -EPERM; + break; + } + if (reply->data.acc_hdr.accept_stat != 0) { + rc = -EINVAL; + break; + } + if (_reply == NULL) { + rc = 0; + break; + } + if (rc > reply_size) { + rc = -ENOMEM; + } else { + memcpy(_reply, reply, rc); + } + break; + } + kfree(reply); +error: + ept->flags &= ~MSM_RPC_ENABLE_RECEIVE; + wake_unlock(&ept->read_q_wake_lock); + + return rc; +} +EXPORT_SYMBOL(msm_rpc_call_reply); + + +static inline int ept_packet_available(struct msm_rpc_endpoint *ept) +{ + unsigned long flags; + int ret; + spin_lock_irqsave(&ept->read_q_lock, flags); + ret = !list_empty(&ept->read_q); + spin_unlock_irqrestore(&ept->read_q_lock, flags); + return ret; +} + +int __msm_rpc_read(struct msm_rpc_endpoint *ept, + struct rr_fragment **frag_ret, + unsigned len, long timeout) +{ + struct rr_packet *pkt; + struct rpc_request_hdr *rq; + DEFINE_WAIT(__wait); + unsigned long flags; + int rc; + + IO("READ on ept %p\n", ept); + + if (ept->flags & MSM_RPC_UNINTERRUPTIBLE) { + if (timeout < 0) { + wait_event(ept->wait_q, ept_packet_available(ept)); + } else { + rc = wait_event_timeout( + ept->wait_q, ept_packet_available(ept), + timeout); + if (rc == 0) + return -ETIMEDOUT; + } + } else { + if (timeout < 0) { + rc = wait_event_interruptible( + ept->wait_q, ept_packet_available(ept)); + if (rc < 0) + return rc; + } else { + rc = wait_event_interruptible_timeout( + ept->wait_q, ept_packet_available(ept), + timeout); + if (rc == 0) + return -ETIMEDOUT; + } + } + + spin_lock_irqsave(&ept->read_q_lock, flags); + if (list_empty(&ept->read_q)) { + spin_unlock_irqrestore(&ept->read_q_lock, flags); + return -EAGAIN; + } + pkt = list_first_entry(&ept->read_q, struct rr_packet, list); + if (pkt->length > len) { + spin_unlock_irqrestore(&ept->read_q_lock, flags); + return -ETOOSMALL; + } + list_del(&pkt->list); + if (list_empty(&ept->read_q)) + wake_unlock(&ept->read_q_wake_lock); + spin_unlock_irqrestore(&ept->read_q_lock, flags); + + rc = pkt->length; + + *frag_ret = pkt->first; + rq = (void*) pkt->first->data; + if ((rc >= (sizeof(uint32_t) * 3)) && (rq->type == 0)) { + IO("READ on ept %p is a CALL on %08x:%08x proc %d xid %d\n", + ept, be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), + be32_to_cpu(rq->procedure), + be32_to_cpu(rq->xid)); + /* RPC CALL */ + if (ept->reply_pid != 0xffffffff) { + printk(KERN_WARNING + "rr_read: lost previous reply xid...\n"); + } + /* TODO: locking? */ + ept->reply_pid = pkt->hdr.src_pid; + ept->reply_cid = pkt->hdr.src_cid; + ept->reply_xid = rq->xid; + } +#if TRACE_RPC_MSG + else if ((rc >= (sizeof(uint32_t) * 3)) && (rq->type == 1)) + IO("READ on ept %p is a REPLY\n", ept); + else IO("READ on ept %p (%d bytes)\n", ept, rc); +#endif + + kfree(pkt); + return rc; +} + +#if CONFIG_MSM_AMSS_VERSION >= 6350 +int msm_rpc_is_compatible_version(uint32_t server_version, + uint32_t client_version) +{ + if ((server_version & RPC_VERSION_MODE_MASK) != + (client_version & RPC_VERSION_MODE_MASK)) + return 0; + + if (server_version & RPC_VERSION_MODE_MASK) + return server_version == client_version; + + return ((server_version & RPC_VERSION_MAJOR_MASK) == + (client_version & RPC_VERSION_MAJOR_MASK)) && + ((server_version & RPC_VERSION_MINOR_MASK) >= + (client_version & RPC_VERSION_MINOR_MASK)); +} +EXPORT_SYMBOL(msm_rpc_is_compatible_version); + +static int msm_rpc_get_compatible_server(uint32_t prog, + uint32_t ver, + uint32_t *found_vers) +{ + struct rr_server *server; + unsigned long flags; + if (found_vers == NULL) + return 0; + + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if ((server->prog == prog) && + msm_rpc_is_compatible_version(server->vers, ver)) { + *found_vers = server->vers; + spin_unlock_irqrestore(&server_list_lock, flags); + return 0; + } + } + spin_unlock_irqrestore(&server_list_lock, flags); + return -1; +} +#endif + +struct msm_rpc_endpoint *msm_rpc_connect(uint32_t prog, uint32_t vers, unsigned flags) +{ + struct msm_rpc_endpoint *ept; + struct rr_server *server; + +#if CONFIG_MSM_AMSS_VERSION >= 6350 + if (!(vers & RPC_VERSION_MODE_MASK)) { + uint32_t found_vers; + if (msm_rpc_get_compatible_server(prog, vers, &found_vers) < 0) + return ERR_PTR(-EHOSTUNREACH); + if (found_vers != vers) { + D("RPC using new version %08x:{%08x --> %08x}\n", + prog, vers, found_vers); + vers = found_vers; + } + } +#endif + + server = rpcrouter_lookup_server(prog, vers); + if (!server) + return ERR_PTR(-EHOSTUNREACH); + + ept = msm_rpc_open(); + if (IS_ERR(ept)) + return ept; + + ept->flags = flags; + ept->dst_pid = server->pid; + ept->dst_cid = server->cid; + ept->dst_prog = cpu_to_be32(prog); + ept->dst_vers = cpu_to_be32(vers); + + return ept; +} +EXPORT_SYMBOL(msm_rpc_connect); + +uint32_t msm_rpc_get_vers(struct msm_rpc_endpoint *ept) +{ + return be32_to_cpu(ept->dst_vers); +} +EXPORT_SYMBOL(msm_rpc_get_vers); + +/* TODO: permission check? */ +int msm_rpc_register_server(struct msm_rpc_endpoint *ept, + uint32_t prog, uint32_t vers) +{ + int rc; + union rr_control_msg msg; + struct rr_server *server; + + server = rpcrouter_create_server(ept->pid, ept->cid, + prog, vers); + if (!server) + return -ENODEV; + + msg.srv.cmd = RPCROUTER_CTRL_CMD_NEW_SERVER; + msg.srv.pid = ept->pid; + msg.srv.cid = ept->cid; + msg.srv.prog = prog; + msg.srv.vers = vers; + + RR("x NEW_SERVER id=%d:%08x prog=%08x:%08x\n", + ept->pid, ept->cid, prog, vers); + + rc = rpcrouter_send_control_msg(&msg); + if (rc < 0) + return rc; + + ept->flags |= MSM_RPC_ENABLE_RECEIVE; + return 0; +} + +/* TODO: permission check -- disallow unreg of somebody else's server */ +int msm_rpc_unregister_server(struct msm_rpc_endpoint *ept, + uint32_t prog, uint32_t vers) +{ + struct rr_server *server; + server = rpcrouter_lookup_server(prog, vers); + + if (!server) + return -ENOENT; + + ept->flags &= ~MSM_RPC_ENABLE_RECEIVE; + wake_unlock(&ept->read_q_wake_lock); + rpcrouter_destroy_server(server); + return 0; +} + +static int msm_rpcrouter_probe(struct platform_device *pdev) +{ + int rc; + + /* Initialize what we need to start processing */ + INIT_LIST_HEAD(&local_endpoints); + INIT_LIST_HEAD(&remote_endpoints); + + init_waitqueue_head(&newserver_wait); + init_waitqueue_head(&smd_wait); + wake_lock_init(&rpcrouter_wake_lock, WAKE_LOCK_SUSPEND, "SMD_RPCCALL"); + + rpcrouter_workqueue = create_singlethread_workqueue("rpcrouter"); + if (!rpcrouter_workqueue) + return -ENOMEM; + + rc = msm_rpcrouter_init_devices(); + if (rc < 0) + goto fail_destroy_workqueue; + + /* Open up SMD channel 2 */ + initialized = 0; + rc = smd_open("SMD_RPCCALL", &smd_channel, NULL, rpcrouter_smdnotify); + if (rc < 0) + goto fail_remove_devices; + + queue_work(rpcrouter_workqueue, &work_read_data); + return 0; + + fail_remove_devices: + msm_rpcrouter_exit_devices(); + fail_destroy_workqueue: + destroy_workqueue(rpcrouter_workqueue); + return rc; +} + +static int msm_rpcrouter_suspend(struct platform_device *pdev, + pm_message_t state) +{ + /* Wait until the worker thread has waited at least once so that it + * gets a chance to release its wakelock. + */ + int wait_count = smd_wait_count; + if (!(smd_wait_count & 1)) + wait_event(smd_wait, smd_wait_count != wait_count); + return 0; +} + +static struct platform_driver msm_smd_channel2_driver = { + .probe = msm_rpcrouter_probe, + .driver = { + .name = "SMD_RPCCALL", + .owner = THIS_MODULE, + }, + .suspend = msm_rpcrouter_suspend, +}; + +static int __init rpcrouter_init(void) +{ + return platform_driver_register(&msm_smd_channel2_driver); +} + +module_init(rpcrouter_init); +MODULE_DESCRIPTION("MSM RPC Router"); +MODULE_AUTHOR("San Mehat <san@xxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/smd_rpcrouter.h b/arch/arm/mach-msm/smd_rpcrouter.h new file mode 100644 index 0000000..4279e6f --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter.h @@ -0,0 +1,195 @@ +/** arch/arm/mach-msm/smd_rpcrouter.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2008 QUALCOMM Incorporated. + * Author: San Mehat <san@xxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_SMD_RPCROUTER_H +#define _ARCH_ARM_MACH_MSM_SMD_RPCROUTER_H + +#include <linux/types.h> +#include <linux/list.h> +#include <linux/cdev.h> +#include <linux/platform_device.h> +#include "../drivers/staging/dream/include/linux/wakelock.h" + +#include <mach/msm_smd.h> +#include "../drivers/staging/dream/include/mach/msm_rpcrouter.h" + +/* definitions for the R2R wire protcol */ + +#define RPCROUTER_VERSION 1 +#define RPCROUTER_PROCESSORS_MAX 4 +#define RPCROUTER_MSGSIZE_MAX 512 + +#define RPCROUTER_CLIENT_BCAST_ID 0xffffffff +#define RPCROUTER_ROUTER_ADDRESS 0xfffffffe + +#define RPCROUTER_PID_LOCAL 1 +#define RPCROUTER_PID_REMOTE 0 + +#define RPCROUTER_CTRL_CMD_DATA 1 +#define RPCROUTER_CTRL_CMD_HELLO 2 +#define RPCROUTER_CTRL_CMD_BYE 3 +#define RPCROUTER_CTRL_CMD_NEW_SERVER 4 +#define RPCROUTER_CTRL_CMD_REMOVE_SERVER 5 +#define RPCROUTER_CTRL_CMD_REMOVE_CLIENT 6 +#define RPCROUTER_CTRL_CMD_RESUME_TX 7 +#define RPCROUTER_CTRL_CMD_EXIT 8 + +#define RPCROUTER_DEFAULT_RX_QUOTA 5 + +union rr_control_msg { + uint32_t cmd; + struct { + uint32_t cmd; + uint32_t prog; + uint32_t vers; + uint32_t pid; + uint32_t cid; + } srv; + struct { + uint32_t cmd; + uint32_t pid; + uint32_t cid; + } cli; +}; + +struct rr_header { + uint32_t version; + uint32_t type; + uint32_t src_pid; + uint32_t src_cid; + uint32_t confirm_rx; + uint32_t size; + uint32_t dst_pid; + uint32_t dst_cid; +}; + +/* internals */ + +#define RPCROUTER_MAX_REMOTE_SERVERS 100 + +struct rr_fragment { + unsigned char data[RPCROUTER_MSGSIZE_MAX]; + uint32_t length; + struct rr_fragment *next; +}; + +struct rr_packet { + struct list_head list; + struct rr_fragment *first; + struct rr_fragment *last; + struct rr_header hdr; + uint32_t mid; + uint32_t length; +}; + +#define PACMARK_LAST(n) ((n) & 0x80000000) +#define PACMARK_MID(n) (((n) >> 16) & 0xFF) +#define PACMARK_LEN(n) ((n) & 0xFFFF) + +static inline uint32_t PACMARK(uint32_t len, uint32_t mid, uint32_t first, + uint32_t last) +{ + return (len & 0xFFFF) | + ((mid & 0xFF) << 16) | + ((!!first) << 30) | + ((!!last) << 31); +} + +struct rr_server { + struct list_head list; + + uint32_t pid; + uint32_t cid; + uint32_t prog; + uint32_t vers; + + dev_t device_number; + struct cdev cdev; + struct device *device; + struct rpcsvr_platform_device p_device; + char pdev_name[32]; +}; + +struct rr_remote_endpoint { + uint32_t pid; + uint32_t cid; + + int tx_quota_cntr; + spinlock_t quota_lock; + wait_queue_head_t quota_wait; + + struct list_head list; +}; + +struct msm_rpc_endpoint { + struct list_head list; + + /* incomplete packets waiting for assembly */ + struct list_head incomplete; + + /* complete packets waiting to be read */ + struct list_head read_q; + spinlock_t read_q_lock; + struct wake_lock read_q_wake_lock; + wait_queue_head_t wait_q; + unsigned flags; + + /* endpoint address */ + uint32_t pid; + uint32_t cid; + + /* bound remote address + * if not connected (dst_pid == 0xffffffff) RPC_CALL writes fail + * RPC_CALLs must be to the prog/vers below or they will fail + */ + uint32_t dst_pid; + uint32_t dst_cid; + uint32_t dst_prog; /* be32 */ + uint32_t dst_vers; /* be32 */ + + /* reply remote address + * if reply_pid == 0xffffffff, none available + * RPC_REPLY writes may only go to the pid/cid/xid of the + * last RPC_CALL we received. + */ + uint32_t reply_pid; + uint32_t reply_cid; + uint32_t reply_xid; /* be32 */ + uint32_t next_pm; /* Pacmark sequence */ + + /* device node if this endpoint is accessed via userspace */ + dev_t dev; +}; + +/* shared between smd_rpcrouter*.c */ + +int __msm_rpc_read(struct msm_rpc_endpoint *ept, + struct rr_fragment **frag, + unsigned len, long timeout); + +struct msm_rpc_endpoint *msm_rpcrouter_create_local_endpoint(dev_t dev); +int msm_rpcrouter_destroy_local_endpoint(struct msm_rpc_endpoint *ept); + +int msm_rpcrouter_create_server_cdev(struct rr_server *server); +int msm_rpcrouter_create_server_pdev(struct rr_server *server); + +int msm_rpcrouter_init_devices(void); +void msm_rpcrouter_exit_devices(void); + +extern dev_t msm_rpcrouter_devno; +extern struct class *msm_rpcrouter_class; +#endif diff --git a/arch/arm/mach-msm/smd_rpcrouter_device.c b/arch/arm/mach-msm/smd_rpcrouter_device.c new file mode 100644 index 0000000..4ddbcff --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter_device.c @@ -0,0 +1,377 @@ +/* arch/arm/mach-msm/smd_rpcrouter_device.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2009 QUALCOMM Incorporated. + * Author: San Mehat <san@xxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/cdev.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include "../drivers/staging/dream/include/linux/msm_rpcrouter.h" + +#include <asm/uaccess.h> +#include <asm/byteorder.h> + +#include "smd_rpcrouter.h" + +#define SAFETY_MEM_SIZE 65536 + +/* Next minor # available for a remote server */ +static int next_minor = 1; + +struct class *msm_rpcrouter_class; +dev_t msm_rpcrouter_devno; + +static struct cdev rpcrouter_cdev; +static struct device *rpcrouter_device; + +static int rpcrouter_open(struct inode *inode, struct file *filp) +{ + int rc; + struct msm_rpc_endpoint *ept; + + rc = nonseekable_open(inode, filp); + if (rc < 0) + return rc; + + ept = msm_rpcrouter_create_local_endpoint(inode->i_rdev); + if (!ept) + return -ENOMEM; + + filp->private_data = ept; + return 0; +} + +static int rpcrouter_release(struct inode *inode, struct file *filp) +{ + struct msm_rpc_endpoint *ept; + ept = (struct msm_rpc_endpoint *) filp->private_data; + + return msm_rpcrouter_destroy_local_endpoint(ept); +} + +static ssize_t rpcrouter_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct msm_rpc_endpoint *ept; + struct rr_fragment *frag, *next; + int rc; + + ept = (struct msm_rpc_endpoint *) filp->private_data; + + rc = __msm_rpc_read(ept, &frag, count, -1); + if (rc < 0) + return rc; + + count = rc; + + while (frag != NULL) { + if (copy_to_user(buf, frag->data, frag->length)) { + printk(KERN_ERR + "rpcrouter: could not copy all read data to user!\n"); + rc = -EFAULT; + } + buf += frag->length; + next = frag->next; + kfree(frag); + frag = next; + } + + return rc; +} + +static ssize_t rpcrouter_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct msm_rpc_endpoint *ept; + int rc = 0; + void *k_buffer; + + ept = (struct msm_rpc_endpoint *) filp->private_data; + + /* A check for safety, this seems non-standard */ + if (count > SAFETY_MEM_SIZE) + return -EINVAL; + + k_buffer = kmalloc(count, GFP_KERNEL); + if (!k_buffer) + return -ENOMEM; + + if (copy_from_user(k_buffer, buf, count)) { + rc = -EFAULT; + goto write_out_free; + } + + rc = msm_rpc_write(ept, k_buffer, count); + if (rc < 0) + goto write_out_free; + + rc = count; +write_out_free: + kfree(k_buffer); + return rc; +} + +static unsigned int rpcrouter_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct msm_rpc_endpoint *ept; + unsigned mask = 0; + ept = (struct msm_rpc_endpoint *) filp->private_data; + + /* If there's data already in the read queue, return POLLIN. + * Else, wait for the requested amount of time, and check again. + */ + + if (!list_empty(&ept->read_q)) + mask |= POLLIN; + + if (!mask) { + poll_wait(filp, &ept->wait_q, wait); + if (!list_empty(&ept->read_q)) + mask |= POLLIN; + } + + return mask; +} + +static long rpcrouter_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct msm_rpc_endpoint *ept; + struct rpcrouter_ioctl_server_args server_args; + int rc = 0; + uint32_t n; + + ept = (struct msm_rpc_endpoint *) filp->private_data; + switch (cmd) { + + case RPC_ROUTER_IOCTL_GET_VERSION: + n = RPC_ROUTER_VERSION_V1; + rc = put_user(n, (unsigned int *) arg); + break; + + case RPC_ROUTER_IOCTL_GET_MTU: + /* the pacmark word reduces the actual payload + * possible per message + */ + n = RPCROUTER_MSGSIZE_MAX - sizeof(uint32_t); + rc = put_user(n, (unsigned int *) arg); + break; + + case RPC_ROUTER_IOCTL_REGISTER_SERVER: + rc = copy_from_user(&server_args, (void *) arg, + sizeof(server_args)); + if (rc < 0) + break; + msm_rpc_register_server(ept, + server_args.prog, + server_args.vers); + break; + + case RPC_ROUTER_IOCTL_UNREGISTER_SERVER: + rc = copy_from_user(&server_args, (void *) arg, + sizeof(server_args)); + if (rc < 0) + break; + + msm_rpc_unregister_server(ept, + server_args.prog, + server_args.vers); + break; + + case RPC_ROUTER_IOCTL_GET_MINOR_VERSION: + n = MSM_RPC_GET_MINOR(msm_rpc_get_vers(ept)); + rc = put_user(n, (unsigned int *)arg); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static struct file_operations rpcrouter_server_fops = { + .owner = THIS_MODULE, + .open = rpcrouter_open, + .release = rpcrouter_release, + .read = rpcrouter_read, + .write = rpcrouter_write, + .poll = rpcrouter_poll, + .unlocked_ioctl = rpcrouter_ioctl, +}; + +static struct file_operations rpcrouter_router_fops = { + .owner = THIS_MODULE, + .open = rpcrouter_open, + .release = rpcrouter_release, + .read = rpcrouter_read, + .write = rpcrouter_write, + .poll = rpcrouter_poll, + .unlocked_ioctl = rpcrouter_ioctl, +}; + +int msm_rpcrouter_create_server_cdev(struct rr_server *server) +{ + int rc; + uint32_t dev_vers; + + if (next_minor == RPCROUTER_MAX_REMOTE_SERVERS) { + printk(KERN_ERR + "rpcrouter: Minor numbers exhausted - Increase " + "RPCROUTER_MAX_REMOTE_SERVERS\n"); + return -ENOBUFS; + } + +#if CONFIG_MSM_AMSS_VERSION >= 6350 + /* Servers with bit 31 set are remote msm servers with hashkey version. + * Servers with bit 31 not set are remote msm servers with + * backwards compatible version type in which case the minor number + * (lower 16 bits) is set to zero. + * + */ + if ((server->vers & RPC_VERSION_MODE_MASK)) + dev_vers = server->vers; + else + dev_vers = server->vers & RPC_VERSION_MAJOR_MASK; +#else + dev_vers = server->vers; +#endif + + server->device_number = + MKDEV(MAJOR(msm_rpcrouter_devno), next_minor++); + + server->device = + device_create(msm_rpcrouter_class, rpcrouter_device, + server->device_number, NULL, "%.8x:%.8x", + server->prog, dev_vers); + if (IS_ERR(server->device)) { + printk(KERN_ERR + "rpcrouter: Unable to create device (%ld)\n", + PTR_ERR(server->device)); + return PTR_ERR(server->device);; + } + + cdev_init(&server->cdev, &rpcrouter_server_fops); + server->cdev.owner = THIS_MODULE; + + rc = cdev_add(&server->cdev, server->device_number, 1); + if (rc < 0) { + printk(KERN_ERR + "rpcrouter: Unable to add chrdev (%d)\n", rc); + device_destroy(msm_rpcrouter_class, server->device_number); + return rc; + } + return 0; +} + +/* for backward compatible version type (31st bit cleared) + * clearing minor number (lower 16 bits) in device name + * is neccessary for driver binding + */ +int msm_rpcrouter_create_server_pdev(struct rr_server *server) +{ + sprintf(server->pdev_name, "rs%.8x:%.8x", + server->prog, +#if CONFIG_MSM_AMSS_VERSION >= 6350 + (server->vers & RPC_VERSION_MODE_MASK) ? server->vers : + (server->vers & RPC_VERSION_MAJOR_MASK)); +#else + server->vers); +#endif + + server->p_device.base.id = -1; + server->p_device.base.name = server->pdev_name; + + server->p_device.prog = server->prog; + server->p_device.vers = server->vers; + + platform_device_register(&server->p_device.base); + return 0; +} + +int msm_rpcrouter_init_devices(void) +{ + int rc; + int major; + + /* Create the device nodes */ + msm_rpcrouter_class = class_create(THIS_MODULE, "oncrpc"); + if (IS_ERR(msm_rpcrouter_class)) { + rc = -ENOMEM; + printk(KERN_ERR + "rpcrouter: failed to create oncrpc class\n"); + goto fail; + } + + rc = alloc_chrdev_region(&msm_rpcrouter_devno, 0, + RPCROUTER_MAX_REMOTE_SERVERS + 1, + "oncrpc"); + if (rc < 0) { + printk(KERN_ERR + "rpcrouter: Failed to alloc chardev region (%d)\n", rc); + goto fail_destroy_class; + } + + major = MAJOR(msm_rpcrouter_devno); + rpcrouter_device = device_create(msm_rpcrouter_class, NULL, + msm_rpcrouter_devno, NULL, "%.8x:%d", + 0, 0); + if (IS_ERR(rpcrouter_device)) { + rc = -ENOMEM; + goto fail_unregister_cdev_region; + } + + cdev_init(&rpcrouter_cdev, &rpcrouter_router_fops); + rpcrouter_cdev.owner = THIS_MODULE; + + rc = cdev_add(&rpcrouter_cdev, msm_rpcrouter_devno, 1); + if (rc < 0) + goto fail_destroy_device; + + return 0; + +fail_destroy_device: + device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); +fail_unregister_cdev_region: + unregister_chrdev_region(msm_rpcrouter_devno, + RPCROUTER_MAX_REMOTE_SERVERS + 1); +fail_destroy_class: + class_destroy(msm_rpcrouter_class); +fail: + return rc; +} + +void msm_rpcrouter_exit_devices(void) +{ + cdev_del(&rpcrouter_cdev); + device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); + unregister_chrdev_region(msm_rpcrouter_devno, + RPCROUTER_MAX_REMOTE_SERVERS + 1); + class_destroy(msm_rpcrouter_class); +} + diff --git a/arch/arm/mach-msm/smd_rpcrouter_servers.c b/arch/arm/mach-msm/smd_rpcrouter_servers.c new file mode 100644 index 0000000..e7c1f1d --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter_servers.c @@ -0,0 +1,229 @@ +/* arch/arm/mach-msm/rpc_servers.c + * + * Copyright (C) 2007 Google, Inc. + * Author: Iliyan Malchev <ibm@xxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/cdev.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/kthread.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include "../drivers/staging/dream/include/linux/wakelock.h" + +#include <linux/uaccess.h> + +#include "../drivers/staging/dream/include/mach/msm_rpcrouter.h" +#include "../drivers/staging/dream/include/linux/msm_rpcrouter.h" + + +static struct msm_rpc_endpoint *endpoint; + +#define FLAG_REGISTERED 0x0001 + +static LIST_HEAD(rpc_server_list); +static DEFINE_MUTEX(rpc_server_list_lock); +static int rpc_servers_active; +static struct wake_lock rpc_servers_wake_lock; + +static void rpc_server_register(struct msm_rpc_server *server) +{ + int rc; + rc = msm_rpc_register_server(endpoint, server->prog, server->vers); + if (rc < 0) + printk(KERN_ERR "[rpcserver] error registering %p @ %08x:%d\n", + server, server->prog, server->vers); +} + +static struct msm_rpc_server *rpc_server_find(uint32_t prog, uint32_t vers) +{ + struct msm_rpc_server *server; + + mutex_lock(&rpc_server_list_lock); + list_for_each_entry(server, &rpc_server_list, list) { + if ((server->prog == prog) && +#if CONFIG_MSM_AMSS_VERSION >= 6350 + msm_rpc_is_compatible_version(server->vers, vers)) { +#else + server->vers == vers) { +#endif + mutex_unlock(&rpc_server_list_lock); + return server; + } + } + mutex_unlock(&rpc_server_list_lock); + return NULL; +} + +static void rpc_server_register_all(void) +{ + struct msm_rpc_server *server; + + mutex_lock(&rpc_server_list_lock); + list_for_each_entry(server, &rpc_server_list, list) { + if (!(server->flags & FLAG_REGISTERED)) { + rpc_server_register(server); + server->flags |= FLAG_REGISTERED; + } + } + mutex_unlock(&rpc_server_list_lock); +} + +int msm_rpc_create_server(struct msm_rpc_server *server) +{ + /* make sure we're in a sane state first */ + server->flags = 0; + INIT_LIST_HEAD(&server->list); + + mutex_lock(&rpc_server_list_lock); + list_add(&server->list, &rpc_server_list); + if (rpc_servers_active) { + rpc_server_register(server); + server->flags |= FLAG_REGISTERED; + } + mutex_unlock(&rpc_server_list_lock); + + return 0; +} + +static int rpc_send_accepted_void_reply(struct msm_rpc_endpoint *client, + uint32_t xid, uint32_t accept_status) +{ + int rc = 0; + uint8_t reply_buf[sizeof(struct rpc_reply_hdr)]; + struct rpc_reply_hdr *reply = (struct rpc_reply_hdr *)reply_buf; + + reply->xid = cpu_to_be32(xid); + reply->type = cpu_to_be32(1); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(client, reply_buf, sizeof(reply_buf)); + if (rc < 0) + printk(KERN_ERR + "%s: could not write response: %d\n", + __FUNCTION__, rc); + + return rc; +} + +static int rpc_servers_thread(void *data) +{ + void *buffer; + struct rpc_request_hdr *req; + struct msm_rpc_server *server; + int rc; + + for (;;) { + wake_unlock(&rpc_servers_wake_lock); + rc = wait_event_interruptible( endpoint->wait_q, !list_empty(&endpoint->read_q) ); + wake_lock(&rpc_servers_wake_lock); + rc = msm_rpc_read(endpoint, &buffer, -1, -1); + if (rc < 0) { + printk(KERN_ERR "%s: could not read: %d\n", + __FUNCTION__, rc); + break; + } + req = (struct rpc_request_hdr *)buffer; + + req->type = be32_to_cpu(req->type); + req->xid = be32_to_cpu(req->xid); + req->rpc_vers = be32_to_cpu(req->rpc_vers); + req->prog = be32_to_cpu(req->prog); + req->vers = be32_to_cpu(req->vers); + req->procedure = be32_to_cpu(req->procedure); + + server = rpc_server_find(req->prog, req->vers); + + if (req->rpc_vers != 2) + continue; + if (req->type != 0) + continue; + if (!server) { + rpc_send_accepted_void_reply( + endpoint, req->xid, + RPC_ACCEPTSTAT_PROG_UNAVAIL); + continue; + } + + rc = server->rpc_call(server, req, rc); + + switch (rc) { + case 0: + rpc_send_accepted_void_reply( + endpoint, req->xid, + RPC_ACCEPTSTAT_SUCCESS); + break; + default: + rpc_send_accepted_void_reply( + endpoint, req->xid, + RPC_ACCEPTSTAT_PROG_UNAVAIL); + break; + } + + kfree(buffer); + } + + do_exit(0); +} + +static int rpcservers_probe(struct platform_device *pdev) +{ + struct task_struct *server_thread; + + endpoint = msm_rpc_open(); + if (IS_ERR(endpoint)) + return PTR_ERR(endpoint); + + /* we're online -- register any servers installed beforehand */ + rpc_servers_active = 1; + rpc_server_register_all(); + + /* start the kernel thread */ + server_thread = kthread_run(rpc_servers_thread, NULL, "krpcserversd"); + if (IS_ERR(server_thread)) + return PTR_ERR(server_thread); + + return 0; +} + +static struct platform_driver rpcservers_driver = { + .probe = rpcservers_probe, + .driver = { + .name = "oncrpc_router", + .owner = THIS_MODULE, + }, +}; + +static int __init rpc_servers_init(void) +{ + wake_lock_init(&rpc_servers_wake_lock, WAKE_LOCK_SUSPEND, "rpc_server"); + return platform_driver_register(&rpcservers_driver); +} + +module_init(rpc_servers_init); + +MODULE_DESCRIPTION("MSM RPC Servers"); +MODULE_AUTHOR("Iliyan Malchev <ibm@xxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/dream/include/mach/msm_rpcrouter.h b/drivers/staging/dream/include/mach/msm_rpcrouter.h index 9724ece..5e51705 100644 --- a/drivers/staging/dream/include/mach/msm_rpcrouter.h +++ b/drivers/staging/dream/include/mach/msm_rpcrouter.h @@ -125,6 +125,7 @@ struct rpc_reply_hdr /* flags for msm_rpc_connect() */ #define MSM_RPC_UNINTERRUPTIBLE 0x0001 +#define MSM_RPC_ENABLE_RECEIVE (0x10000) /* use IS_ERR() to check for failure */ struct msm_rpc_endpoint *msm_rpc_open(void); -- 1.6.4.4 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html