From 1a960b402a51d80abf54e3f8e4972374ffe5f22d Mon Sep 17 00:00:00 2001 From: Jason Yeh Date: Wed, 23 Jul 2008 23:05:53 +0200 Subject: Oprofile Multiplexing Patch This patch introduces multiplexing support for the Oprofile kernel module. It basically adds a new function pointer in oprofile_operator allowing each architecture to supply its callback to switch between different sets of event when the timer expires. Userspace tools can modify the time slice through /dev/oprofile/time_slice. It also modifies the number of counters exposed to the userspace through /dev/oprofile. For example, the number of counters for AMD CPUs are changed to 32 and multiplexed in the sets of 4. Signed-off-by: Jason Yeh Signed-off-by: Robert Richter Cc: oprofile-list Signed-off-by: Ingo Molnar --- arch/x86/oprofile/nmi_int.c | 100 +++++++++++++++++++++++++++++++++++--- arch/x86/oprofile/op_counter.h | 3 +- arch/x86/oprofile/op_model_amd.c | 76 +++++++++++++++++------------ arch/x86/oprofile/op_model_p4.c | 4 ++ arch/x86/oprofile/op_model_ppro.c | 2 + arch/x86/oprofile/op_x86_model.h | 3 ++ drivers/oprofile/oprof.c | 58 ++++++++++++++++++++-- drivers/oprofile/oprof.h | 4 +- drivers/oprofile/oprofile_files.c | 39 ++++++++++++++- include/linux/oprofile.h | 3 ++ 10 files changed, 247 insertions(+), 45 deletions(-) diff --git a/arch/x86/oprofile/nmi_int.c b/arch/x86/oprofile/nmi_int.c index 287513a0981..2a65fe7680a 100644 --- a/arch/x86/oprofile/nmi_int.c +++ b/arch/x86/oprofile/nmi_int.c @@ -23,12 +23,18 @@ #include "op_counter.h" #include "op_x86_model.h" +DEFINE_PER_CPU(int, switch_index); + static struct op_x86_model_spec const *model; static DEFINE_PER_CPU(struct op_msrs, cpu_msrs); static DEFINE_PER_CPU(unsigned long, saved_lvtpc); static int nmi_start(void); static void nmi_stop(void); +static void nmi_cpu_save_mpx_registers(struct op_msrs *msrs); +static void nmi_cpu_restore_mpx_registers(struct op_msrs *msrs); +static void nmi_cpu_stop(void *dummy); +static void nmi_cpu_start(void *dummy); /* 0 == registered but off, 1 == registered and on */ static int nmi_enabled = 0; @@ -81,6 +87,47 @@ static void exit_sysfs(void) #define exit_sysfs() do { } while (0) #endif /* CONFIG_PM */ +static void nmi_cpu_switch(void *dummy) +{ + int cpu = smp_processor_id(); + int si = per_cpu(switch_index, cpu); + struct op_msrs *msrs = &per_cpu(cpu_msrs, cpu); + + nmi_cpu_stop(NULL); + nmi_cpu_save_mpx_registers(msrs); + + /* move to next set */ + si += model->num_hardware_counters; + if ((si > model->num_counters) || (counter_config[si].count == 0)) + per_cpu(switch_index, smp_processor_id()) = 0; + else + per_cpu(switch_index, smp_processor_id()) = si; + + nmi_cpu_restore_mpx_registers(msrs); + model->setup_ctrs(msrs); + nmi_cpu_start(NULL); +} + +/* + * Quick check to see if multiplexing is necessary. + * The check should be sufficient since counters are used + * in ordre. + */ +static int nmi_multiplex_on(void) +{ + return counter_config[model->num_hardware_counters].count ? 0 : -EINVAL; +} + +static int nmi_switch_event(void) +{ + if (nmi_multiplex_on() < 0) + return -EINVAL; + + on_each_cpu(nmi_cpu_switch, NULL, 0, 1); + + return 0; +} + static int profile_exceptions_notify(struct notifier_block *self, unsigned long val, void *data) { @@ -144,11 +191,10 @@ static void free_msrs(void) static int allocate_msrs(void) { - int success = 1; + int i, success = 1; size_t controls_size = sizeof(struct op_msr) * model->num_controls; size_t counters_size = sizeof(struct op_msr) * model->num_counters; - int i; for_each_possible_cpu(i) { per_cpu(cpu_msrs, i).counters = kmalloc(counters_size, GFP_KERNEL); @@ -156,8 +202,8 @@ static int allocate_msrs(void) success = 0; break; } - per_cpu(cpu_msrs, i).controls = kmalloc(controls_size, - GFP_KERNEL); + per_cpu(cpu_msrs, i).controls = + kmalloc(controls_size, GFP_KERNEL); if (!per_cpu(cpu_msrs, i).controls) { success = 0; break; @@ -201,7 +247,8 @@ static int nmi_setup(void) return err; } - /* We need to serialize save and setup for HT because the subset + /* + * We need to serialize save and setup for HT because the subset * of msrs are distinct for save and setup operations */ @@ -217,7 +264,6 @@ static int nmi_setup(void) per_cpu(cpu_msrs, 0).controls, sizeof(struct op_msr) * model->num_controls); } - } on_each_cpu(nmi_save_registers, NULL, 1); on_each_cpu(nmi_cpu_setup, NULL, 1); @@ -225,7 +271,41 @@ static int nmi_setup(void) return 0; } -static void nmi_restore_registers(struct op_msrs *msrs) +static void nmi_cpu_save_mpx_registers(struct op_msrs *msrs) +{ + unsigned int si = __get_cpu_var(switch_index); + unsigned int const nr_ctrs = model->num_hardware_counters; + struct op_msr *counters = &msrs->counters[si]; + unsigned int i; + + for (i = 0; i < nr_ctrs; ++i) { + int offset = i + si; + if (counters[offset].addr) { + rdmsr(counters[offset].addr, + counters[offset].multiplex.low, + counters[offset].multiplex.high); + } + } +} + +static void nmi_cpu_restore_mpx_registers(struct op_msrs *msrs) +{ + unsigned int si = __get_cpu_var(switch_index); + unsigned int const nr_ctrs = model->num_hardware_counters; + struct op_msr *counters = &msrs->counters[si]; + unsigned int i; + + for (i = 0; i < nr_ctrs; ++i) { + int offset = i + si; + if (counters[offset].addr) { + wrmsr(counters[offset].addr, + counters[offset].multiplex.low, + counters[offset].multiplex.high); + } + } +} + +static void nmi_cpu_restore_registers(struct op_msrs *msrs) { unsigned int const nr_ctrs = model->num_counters; unsigned int const nr_ctrls = model->num_controls; @@ -265,7 +345,8 @@ static void nmi_cpu_shutdown(void *dummy) apic_write(APIC_LVTERR, v | APIC_LVT_MASKED); apic_write(APIC_LVTPC, per_cpu(saved_lvtpc, cpu)); apic_write(APIC_LVTERR, v); - nmi_restore_registers(msrs); + nmi_cpu_restore_registers(msrs); + __get_cpu_var(switch_index) = 0; } static void nmi_shutdown(void) @@ -328,6 +409,7 @@ static int nmi_create_files(struct super_block *sb, struct dentry *root) oprofilefs_create_ulong(sb, dir, "unit_mask", &counter_config[i].unit_mask); oprofilefs_create_ulong(sb, dir, "kernel", &counter_config[i].kernel); oprofilefs_create_ulong(sb, dir, "user", &counter_config[i].user); + counter_config[i].save_count_low = 0; } return 0; @@ -469,12 +551,14 @@ int __init op_nmi_init(struct oprofile_operations *ops) } /* default values, can be overwritten by model */ + __get_cpu_var(switch_index) = 0; ops->create_files = nmi_create_files; ops->setup = nmi_setup; ops->shutdown = nmi_shutdown; ops->start = nmi_start; ops->stop = nmi_stop; ops->cpu_type = cpu_type; + ops->switch_events = nmi_switch_event; if (model->init) ret = model->init(ops); diff --git a/arch/x86/oprofile/op_counter.h b/arch/x86/oprofile/op_counter.h index 2880b15c467..786d6e01cf7 100644 --- a/arch/x86/oprofile/op_counter.h +++ b/arch/x86/oprofile/op_counter.h @@ -10,13 +10,14 @@ #ifndef OP_COUNTER_H #define OP_COUNTER_H -#define OP_MAX_COUNTER 8 +#define OP_MAX_COUNTER 32 /* Per-perfctr configuration as set via * oprofilefs. */ struct op_counter_config { unsigned long count; + unsigned long save_count_low; unsigned long enabled; unsigned long event; unsigned long kernel; diff --git a/arch/x86/oprofile/op_model_amd.c b/arch/x86/oprofile/op_model_amd.c index d9faf607b3a..bbf2b68bcc5 100644 --- a/arch/x86/oprofile/op_model_amd.c +++ b/arch/x86/oprofile/op_model_amd.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -23,8 +24,10 @@ #include "op_x86_model.h" #include "op_counter.h" -#define NUM_COUNTERS 4 -#define NUM_CONTROLS 4 +#define NUM_COUNTERS 32 +#define NUM_HARDWARE_COUNTERS 4 +#define NUM_CONTROLS 32 +#define NUM_HARDWARE_CONTROLS 4 #define CTR_IS_RESERVED(msrs, c) (msrs->counters[(c)].addr ? 1 : 0) #define CTR_READ(l, h, msrs, c) do {rdmsr(msrs->counters[(c)].addr, (l), (h)); } while (0) @@ -48,6 +51,7 @@ #define CTRL_SET_GUEST_ONLY(val, h) (val |= ((h & 1) << 8)) static unsigned long reset_value[NUM_COUNTERS]; +DECLARE_PER_CPU(int, switch_index); #ifdef CONFIG_OPROFILE_IBS @@ -130,15 +134,17 @@ static void op_amd_fill_in_addresses(struct op_msrs * const msrs) int i; for (i = 0; i < NUM_COUNTERS; i++) { - if (reserve_perfctr_nmi(MSR_K7_PERFCTR0 + i)) - msrs->counters[i].addr = MSR_K7_PERFCTR0 + i; + int hw_counter = i % NUM_HARDWARE_COUNTERS; + if (reserve_perfctr_nmi(MSR_K7_PERFCTR0 + hw_counter)) + msrs->counters[i].addr = MSR_K7_PERFCTR0 + hw_counter; else msrs->counters[i].addr = 0; } for (i = 0; i < NUM_CONTROLS; i++) { - if (reserve_evntsel_nmi(MSR_K7_EVNTSEL0 + i)) - msrs->controls[i].addr = MSR_K7_EVNTSEL0 + i; + int hw_control = i % NUM_HARDWARE_CONTROLS; + if (reserve_evntsel_nmi(MSR_K7_EVNTSEL0 + hw_control)) + msrs->controls[i].addr = MSR_K7_EVNTSEL0 + hw_control; else msrs->controls[i].addr = 0; } @@ -150,8 +156,16 @@ static void op_amd_setup_ctrs(struct op_msrs const * const msrs) unsigned int low, high; int i; + for (i = 0; i < NUM_HARDWARE_CONTROLS; ++i) { + int offset = i + __get_cpu_var(switch_index); + if (counter_config[offset].enabled) + reset_value[offset] = counter_config[offset].count; + else + reset_value[offset] = 0; + } + /* clear all counters */ - for (i = 0 ; i < NUM_CONTROLS; ++i) { + for (i = 0 ; i < NUM_HARDWARE_CONTROLS; ++i) { if (unlikely(!CTRL_IS_RESERVED(msrs, i))) continue; CTRL_READ(low, high, msrs, i); @@ -161,34 +175,31 @@ static void op_amd_setup_ctrs(struct op_msrs const * const msrs) } /* avoid a false detection of ctr overflows in NMI handler */ - for (i = 0; i < NUM_COUNTERS; ++i) { + for (i = 0; i < NUM_HARDWARE_COUNTERS; ++i) { if (unlikely(!CTR_IS_RESERVED(msrs, i))) continue; CTR_WRITE(1, msrs, i); } /* enable active counters */ - for (i = 0; i < NUM_COUNTERS; ++i) { - if ((counter_config[i].enabled) && (CTR_IS_RESERVED(msrs, i))) { - reset_value[i] = counter_config[i].count; - - CTR_WRITE(counter_config[i].count, msrs, i); + for (i = 0; i < NUM_HARDWARE_COUNTERS; ++i) { + int offset = i + __get_cpu_var(switch_index); + if ((counter_config[offset].enabled) && (CTR_IS_RESERVED(msrs, i))) { + CTR_WRITE(counter_config[offset].count, msrs, i); CTRL_READ(low, high, msrs, i); CTRL_CLEAR_LO(low); CTRL_CLEAR_HI(high); CTRL_SET_ENABLE(low); - CTRL_SET_USR(low, counter_config[i].user); - CTRL_SET_KERN(low, counter_config[i].kernel); - CTRL_SET_UM(low, counter_config[i].unit_mask); - CTRL_SET_EVENT_LOW(low, counter_config[i].event); - CTRL_SET_EVENT_HIGH(high, counter_config[i].event); + CTRL_SET_USR(low, counter_config[offset].user); + CTRL_SET_KERN(low, counter_config[offset].kernel); + CTRL_SET_UM(low, counter_config[offset].unit_mask); + CTRL_SET_EVENT_LOW(low, counter_config[offset].event); + CTRL_SET_EVENT_HIGH(high, counter_config[offset].event); CTRL_SET_HOST_ONLY(high, 0); CTRL_SET_GUEST_ONLY(high, 0); CTRL_WRITE(low, high, msrs, i); - } else { - reset_value[i] = 0; } } } @@ -276,13 +287,14 @@ static int op_amd_check_ctrs(struct pt_regs * const regs, unsigned int low, high; int i; - for (i = 0 ; i < NUM_COUNTERS; ++i) { - if (!reset_value[i]) + for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { + int offset = i + __get_cpu_var(switch_index); + if (!reset_value[offset]) continue; CTR_READ(low, high, msrs, i); if (CTR_OVERFLOWED(low)) { - oprofile_add_sample(regs, i); - CTR_WRITE(reset_value[i], msrs, i); + oprofile_add_sample(regs, offset); + CTR_WRITE(reset_value[offset], msrs, i); } } @@ -298,8 +310,10 @@ static void op_amd_start(struct op_msrs const * const msrs) { unsigned int low, high; int i; - for (i = 0 ; i < NUM_COUNTERS ; ++i) { - if (reset_value[i]) { + + for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { + int offset = i + __get_cpu_var(switch_index); + if (reset_value[offset]) { CTRL_READ(low, high, msrs, i); CTRL_SET_ACTIVE(low); CTRL_WRITE(low, high, msrs, i); @@ -329,8 +343,8 @@ static void op_amd_stop(struct op_msrs const * const msrs) /* Subtle: stop on all counters to avoid race with * setting our pm callback */ - for (i = 0 ; i < NUM_COUNTERS ; ++i) { - if (!reset_value[i]) + for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { + if (!reset_value[i + per_cpu(switch_index, smp_processor_id())]) continue; CTRL_READ(low, high, msrs, i); CTRL_SET_INACTIVE(low); @@ -356,11 +370,11 @@ static void op_amd_shutdown(struct op_msrs const * const msrs) { int i; - for (i = 0 ; i < NUM_COUNTERS ; ++i) { + for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { if (CTR_IS_RESERVED(msrs, i)) release_perfctr_nmi(MSR_K7_PERFCTR0 + i); } - for (i = 0 ; i < NUM_CONTROLS ; ++i) { + for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { if (CTRL_IS_RESERVED(msrs, i)) release_evntsel_nmi(MSR_K7_EVNTSEL0 + i); } @@ -534,6 +548,8 @@ struct op_x86_model_spec const op_amd_spec = { .exit = op_amd_exit, .num_counters = NUM_COUNTERS, .num_controls = NUM_CONTROLS, + .num_hardware_counters = NUM_HARDWARE_COUNTERS, + .num_hardware_controls = NUM_HARDWARE_CONTROLS, .fill_in_addresses = &op_amd_fill_in_addresses, .setup_ctrs = &op_amd_setup_ctrs, .check_ctrs = &op_amd_check_ctrs, diff --git a/arch/x86/oprofile/op_model_p4.c b/arch/x86/oprofile/op_model_p4.c index 56b4757a1f4..e641545d479 100644 --- a/arch/x86/oprofile/op_model_p4.c +++ b/arch/x86/oprofile/op_model_p4.c @@ -701,6 +701,8 @@ static void p4_shutdown(struct op_msrs const * const msrs) struct op_x86_model_spec const op_p4_ht2_spec = { .num_counters = NUM_COUNTERS_HT2, .num_controls = NUM_CONTROLS_HT2, + .num_hardware_counters = NUM_COUNTERS_HT2, + .num_hardware_controls = NUM_CONTROLS_HT2, .fill_in_addresses = &p4_fill_in_addresses, .setup_ctrs = &p4_setup_ctrs, .check_ctrs = &p4_check_ctrs, @@ -713,6 +715,8 @@ struct op_x86_model_spec const op_p4_ht2_spec = { struct op_x86_model_spec const op_p4_spec = { .num_counters = NUM_COUNTERS_NON_HT, .num_controls = NUM_CONTROLS_NON_HT, + .num_hardware_counters = NUM_COUNTERS_NON_HT, + .num_hardware_controls = NUM_CONTROLS_NON_HT, .fill_in_addresses = &p4_fill_in_addresses, .setup_ctrs = &p4_setup_ctrs, .check_ctrs = &p4_check_ctrs, diff --git a/arch/x86/oprofile/op_model_ppro.c b/arch/x86/oprofile/op_model_ppro.c index eff431f6c57..e5811aa480e 100644 --- a/arch/x86/oprofile/op_model_ppro.c +++ b/arch/x86/oprofile/op_model_ppro.c @@ -183,6 +183,8 @@ static void ppro_shutdown(struct op_msrs const * const msrs) struct op_x86_model_spec const op_ppro_spec = { .num_counters = NUM_COUNTERS, .num_controls = NUM_CONTROLS, + .num_hardware_counters = NUM_COUNTERS, + .num_hardware_controls = NUM_CONTROLS, .fill_in_addresses = &ppro_fill_in_addresses, .setup_ctrs = &ppro_setup_ctrs, .check_ctrs = &ppro_check_ctrs, diff --git a/arch/x86/oprofile/op_x86_model.h b/arch/x86/oprofile/op_x86_model.h index 05a0261ba0c..e07ba107637 100644 --- a/arch/x86/oprofile/op_x86_model.h +++ b/arch/x86/oprofile/op_x86_model.h @@ -19,6 +19,7 @@ struct op_saved_msr { struct op_msr { unsigned long addr; struct op_saved_msr saved; + struct op_saved_msr multiplex; }; struct op_msrs { @@ -34,6 +35,8 @@ struct pt_regs; struct op_x86_model_spec { int (*init)(struct oprofile_operations *ops); void (*exit)(void); + unsigned int const num_hardware_counters; + unsigned int const num_hardware_controls; unsigned int const num_counters; unsigned int const num_controls; void (*fill_in_addresses)(struct op_msrs * const msrs); diff --git a/drivers/oprofile/oprof.c b/drivers/oprofile/oprof.c index 2c645170f06..b2fa5df64a6 100644 --- a/drivers/oprofile/oprof.c +++ b/drivers/oprofile/oprof.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include "oprof.h" @@ -19,13 +21,18 @@ #include "cpu_buffer.h" #include "buffer_sync.h" #include "oprofile_stats.h" + +static unsigned long is_setup; +static void switch_worker(struct work_struct *work); +static DECLARE_DELAYED_WORK(switch_work, switch_worker); +static DEFINE_MUTEX(start_mutex); struct oprofile_operations oprofile_ops; +unsigned long timeout_jiffies; unsigned long oprofile_started; unsigned long backtrace_depth; -static unsigned long is_setup; -static DEFINE_MUTEX(start_mutex); +/* Multiplexing defaults at 1 msec*/ /* timer 0 - use performance monitoring hardware if available @@ -87,6 +94,16 @@ out: return err; } +static void start_switch_worker(void) +{ + schedule_delayed_work(&switch_work, timeout_jiffies); +} + +static void switch_worker(struct work_struct *work) +{ + if (!oprofile_ops.switch_events()) + start_switch_worker(); +} /* Actually start profiling (echo 1>/dev/oprofile/enable) */ int oprofile_start(void) @@ -94,7 +111,6 @@ int oprofile_start(void) int err = -EINVAL; mutex_lock(&start_mutex); - if (!is_setup) goto out; @@ -108,6 +124,9 @@ int oprofile_start(void) if ((err = oprofile_ops.start())) goto out; + if (oprofile_ops.switch_events) + start_switch_worker(); + oprofile_started = 1; out: mutex_unlock(&start_mutex); @@ -123,6 +142,7 @@ void oprofile_stop(void) goto out; oprofile_ops.stop(); oprofile_started = 0; + cancel_delayed_work_sync(&switch_work); /* wake up the daemon to read what remains */ wake_up_buffer_waiter(); out: @@ -155,6 +175,32 @@ post_sync: mutex_unlock(&start_mutex); } +/* User inputs in ms, converts to jiffies */ +int oprofile_set_timeout(unsigned long val_msec) +{ + int err = 0; + + mutex_lock(&start_mutex); + + if (oprofile_started) { + err = -EBUSY; + goto out; + } + + if (!oprofile_ops.switch_events) { + err = -EINVAL; + goto out; + } + + timeout_jiffies = msecs_to_jiffies(val_msec); + if (timeout_jiffies == MAX_JIFFY_OFFSET) + timeout_jiffies = msecs_to_jiffies(1); + +out: + mutex_unlock(&start_mutex); + return err; + +} int oprofile_set_backtrace(unsigned long val) { @@ -179,10 +225,16 @@ out: return err; } +static void __init oprofile_switch_timer_init(void) +{ + timeout_jiffies = msecs_to_jiffies(1); +} + static int __init oprofile_init(void) { int err; + oprofile_switch_timer_init(); err = oprofile_arch_init(&oprofile_ops); if (err < 0 || timer) { diff --git a/drivers/oprofile/oprof.h b/drivers/oprofile/oprof.h index 18323650806..c4406a7366b 100644 --- a/drivers/oprofile/oprof.h +++ b/drivers/oprofile/oprof.h @@ -27,7 +27,8 @@ extern unsigned long fs_buffer_watershed; extern struct oprofile_operations oprofile_ops; extern unsigned long oprofile_started; extern unsigned long backtrace_depth; - +extern unsigned long timeout_jiffies; + struct super_block; struct dentry; @@ -35,5 +36,6 @@ void oprofile_create_files(struct super_block * sb, struct dentry * root); void oprofile_timer_init(struct oprofile_operations * ops); int oprofile_set_backtrace(unsigned long depth); +int oprofile_set_timeout(unsigned long time); #endif /* OPROF_H */ diff --git a/drivers/oprofile/oprofile_files.c b/drivers/oprofile/oprofile_files.c index ef953ba5ab6..cc4f5a1f8ef 100644 --- a/drivers/oprofile/oprofile_files.c +++ b/drivers/oprofile/oprofile_files.c @@ -9,6 +9,7 @@ #include #include +#include #include "event_buffer.h" #include "oprofile_stats.h" @@ -18,6 +19,40 @@ unsigned long fs_buffer_size = 131072; unsigned long fs_cpu_buffer_size = 8192; unsigned long fs_buffer_watershed = 32768; /* FIXME: tune */ +static ssize_t timeout_read(struct file *file, char __user *buf, + size_t count, loff_t *offset) +{ + return oprofilefs_ulong_to_user(jiffies_to_msecs(timeout_jiffies), + buf, count, offset); +} + + +static ssize_t timeout_write(struct file *file, char const __user *buf, + size_t count, loff_t *offset) +{ + unsigned long val; + int retval; + + if (*offset) + return -EINVAL; + + retval = oprofilefs_ulong_from_user(&val, buf, count); + if (retval) + return retval; + + retval = oprofile_set_timeout(val); + + if (retval) + return retval; + return count; +} + +static const struct file_operations timeout_fops = { + .read = timeout_read, + .write = timeout_write, +}; + + static ssize_t depth_read(struct file * file, char __user * buf, size_t count, loff_t * offset) { return oprofilefs_ulong_to_user(backtrace_depth, buf, count, offset); @@ -85,11 +120,10 @@ static ssize_t enable_write(struct file * file, char const __user * buf, size_t if (*offset) return -EINVAL; - retval = oprofilefs_ulong_from_user(&val, buf, count); if (retval) return retval; - + if (val) retval = oprofile_start(); else @@ -129,6 +163,7 @@ void oprofile_create_files(struct super_block * sb, struct dentry * root) oprofilefs_create_file(sb, root, "cpu_type", &cpu_type_fops); oprofilefs_create_file(sb, root, "backtrace_depth", &depth_fops); oprofilefs_create_file(sb, root, "pointer_size", &pointer_size_fops); + oprofilefs_create_file(sb, root, "timeout_ms", &timeout_fops); oprofile_create_stats_files(sb, root); if (oprofile_ops.create_files) oprofile_ops.create_files(sb, root); diff --git a/include/linux/oprofile.h b/include/linux/oprofile.h index bcb8f725427..687f2f4c36a 100644 --- a/include/linux/oprofile.h +++ b/include/linux/oprofile.h @@ -67,6 +67,9 @@ struct oprofile_operations { /* Initiate a stack backtrace. Optional. */ void (*backtrace)(struct pt_regs * const regs, unsigned int depth); + + /* Multiplex between different events. Optional. */ + int (*switch_events)(void); /* CPU identification string. */ char * cpu_type; }; -- cgit v1.2.3 From 7e7b43892b87b6be259479ef4de14029dcb4012f Mon Sep 17 00:00:00 2001 From: Robert Richter Date: Thu, 24 Jul 2008 16:00:16 +0200 Subject: x86/oprofile: fix on_each_cpu build error Signed-off-by: Robert Richter Cc: oprofile-list Cc: Jason Yeh Signed-off-by: Ingo Molnar --- arch/x86/oprofile/nmi_int.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/oprofile/nmi_int.c b/arch/x86/oprofile/nmi_int.c index 2a65fe7680a..fb4902bc6f1 100644 --- a/arch/x86/oprofile/nmi_int.c +++ b/arch/x86/oprofile/nmi_int.c @@ -123,7 +123,7 @@ static int nmi_switch_event(void) if (nmi_multiplex_on() < 0) return -EINVAL; - on_each_cpu(nmi_cpu_switch, NULL, 0, 1); + on_each_cpu(nmi_cpu_switch, NULL, 1); return 0; } -- cgit v1.2.3 From 7bb67439bf6bd3782f07f1d7be1e63406453d5de Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sun, 31 Aug 2008 08:05:58 -0700 Subject: select: Introduce a hrtimeout function This patch adds a schedule_hrtimeout() function, to be used by select() and poll() in a later patch. This function works similar to schedule_timeout() in most ways, but takes a timespec rather than jiffies. With a lot of contributions/fixes from Thomas Signed-off-by: Arjan van de Ven Signed-off-by: Thomas Gleixner --- include/linux/hrtimer.h | 2 ++ kernel/hrtimer.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 6d93dce61cb..becd17db1a1 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -346,6 +346,8 @@ extern long hrtimer_nanosleep_restart(struct restart_block *restart_block); extern void hrtimer_init_sleeper(struct hrtimer_sleeper *sl, struct task_struct *tsk); +extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode); + /* Soft interrupt function to run the hrtimer queues: */ extern void hrtimer_run_queues(void); extern void hrtimer_run_pending(void); diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index b8e4dce80a7..782137dc755 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -1678,3 +1678,68 @@ void __init hrtimers_init(void) #endif } +/** + * schedule_hrtimeout - sleep until timeout + * @expires: timeout value (ktime_t) + * @mode: timer mode, HRTIMER_MODE_ABS or HRTIMER_MODE_REL + * + * Make the current task sleep until the given expiry time has + * elapsed. The routine will return immediately unless + * the current task state has been set (see set_current_state()). + * + * You can set the task state as follows - + * + * %TASK_UNINTERRUPTIBLE - at least @timeout time is guaranteed to + * pass before the routine returns. + * + * %TASK_INTERRUPTIBLE - the routine may return early if a signal is + * delivered to the current task. + * + * The current task state is guaranteed to be TASK_RUNNING when this + * routine returns. + * + * Returns 0 when the timer has expired otherwise -EINTR + */ +int __sched schedule_hrtimeout(ktime_t *expires, + const enum hrtimer_mode mode) +{ + struct hrtimer_sleeper t; + + /* + * Optimize when a zero timeout value is given. It does not + * matter whether this is an absolute or a relative time. + */ + if (expires && !expires->tv64) { + __set_current_state(TASK_RUNNING); + return 0; + } + + /* + * A NULL parameter means "inifinte" + */ + if (!expires) { + schedule(); + __set_current_state(TASK_RUNNING); + return -EINTR; + } + + hrtimer_init_on_stack(&t.timer, CLOCK_MONOTONIC, mode); + t.timer.expires = *expires; + + hrtimer_init_sleeper(&t, current); + + hrtimer_start(&t.timer, t.timer.expires, mode); + if (!hrtimer_active(&t.timer)) + t.task = NULL; + + if (likely(t.task)) + schedule(); + + hrtimer_cancel(&t.timer); + destroy_hrtimer_on_stack(&t.timer); + + __set_current_state(TASK_RUNNING); + + return !t.task ? 0 : -EINTR; +} +EXPORT_SYMBOL_GPL(schedule_hrtimeout); -- cgit v1.2.3 From df0cc0539b4127bd02f64de2c335b4af1fdb3845 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 31 Aug 2008 08:09:53 -0700 Subject: select: add a timespec_add_safe() function For the select() rework, it's important to be able to add timespec structures in an overflow-safe manner. This patch adds a timespec_add_safe() function for this which is similar in operation to ktime_add_safe(), but works on a struct timespec. Signed-off-by: Thomas Gleixner Signed-off-by: Arjan van de Ven --- include/linux/time.h | 4 ++++ kernel/time.c | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/linux/time.h b/include/linux/time.h index e15206a7e82..72697647848 100644 --- a/include/linux/time.h +++ b/include/linux/time.h @@ -38,6 +38,8 @@ struct timezone { #define NSEC_PER_SEC 1000000000L #define FSEC_PER_SEC 1000000000000000L +#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1) + static inline int timespec_equal(const struct timespec *a, const struct timespec *b) { @@ -72,6 +74,8 @@ extern unsigned long mktime(const unsigned int year, const unsigned int mon, const unsigned int min, const unsigned int sec); extern void set_normalized_timespec(struct timespec *ts, time_t sec, long nsec); +extern struct timespec timespec_add_safe(const struct timespec lhs, + const struct timespec rhs); /* * sub = lhs - rhs, in normalized form diff --git a/kernel/time.c b/kernel/time.c index 6a08660b4fa..d63a4336fad 100644 --- a/kernel/time.c +++ b/kernel/time.c @@ -669,3 +669,21 @@ EXPORT_SYMBOL(get_jiffies_64); #endif EXPORT_SYMBOL(jiffies); + +/* + * Add two timespec values and do a safety check for overflow. + * It's assumed that both values are valid (>= 0) + */ +struct timespec timespec_add_safe(const struct timespec lhs, + const struct timespec rhs) +{ + struct timespec res; + + set_normalized_timespec(&res, lhs.tv_sec + rhs.tv_sec, + lhs.tv_nsec + rhs.tv_nsec); + + if (res.tv_sec < lhs.tv_sec || res.tv_sec < rhs.tv_sec) + res.tv_sec = TIME_T_MAX; + + return res; +} -- cgit v1.2.3 From b773ad40aca5bd755ba886620842f16e8fef6d75 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 31 Aug 2008 08:16:57 -0700 Subject: select: add poll_select_set_timeout() and poll_select_copy_remaining() helpers This patch adds 2 helpers that will be used for the hrtimer based select/poll: poll_select_set_timeout() is a helper that takes a timeout (as a second, nanosecond pair) and turns that into a "struct timespec" that represents the absolute end time. This is a common operation in the many select() and poll() variants and needs various, common, sanity checks. poll_select_copy_remaining() is a helper that takes care of copying the remaining time to userspace, as select(), pselect() and ppoll() do. This function comes in both a natural and a compat implementation (due to datastructure differences). Signed-off-by: Thomas Gleixner Signed-off-by: Arjan van de Ven --- fs/compat.c | 51 +++++++++++++++++++++++++++++++++++ fs/select.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/poll.h | 2 ++ 3 files changed, 128 insertions(+) diff --git a/fs/compat.c b/fs/compat.c index 075d0509970..424767c954a 100644 --- a/fs/compat.c +++ b/fs/compat.c @@ -1436,6 +1436,57 @@ out_ret: #define __COMPAT_NFDBITS (8 * sizeof(compat_ulong_t)) +static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, + int timeval, int ret) +{ + struct timespec ts; + + if (!p) + return ret; + + if (current->personality & STICKY_TIMEOUTS) + goto sticky; + + /* No update for zero timeout */ + if (!end_time->tv_sec && !end_time->tv_nsec) + return ret; + + ktime_get_ts(&ts); + ts = timespec_sub(*end_time, ts); + if (ts.tv_sec < 0) + ts.tv_sec = ts.tv_nsec = 0; + + if (timeval) { + struct compat_timeval rtv; + + rtv.tv_sec = ts.tv_sec; + rtv.tv_usec = ts.tv_nsec / NSEC_PER_USEC; + + if (!copy_to_user(p, &rtv, sizeof(rtv))) + return ret; + } else { + struct compat_timespec rts; + + rts.tv_sec = ts.tv_sec; + rts.tv_nsec = ts.tv_nsec; + + if (!copy_to_user(p, &rts, sizeof(rts))) + return ret; + } + /* + * If an application puts its timeval in read-only memory, we + * don't want the Linux-specific update to the timeval to + * cause a fault after the select has completed + * successfully. However, because we're not updating the + * timeval, we can't restart the system call. + */ + +sticky: + if (ret == -ERESTARTNOHAND) + ret = -EINTR; + return ret; +} + /* * Ooo, nasty. We need here to frob 32-bit unsigned longs to * 64-bit unsigned longs. diff --git a/fs/select.c b/fs/select.c index da0e88201c3..1180a620778 100644 --- a/fs/select.c +++ b/fs/select.c @@ -130,6 +130,81 @@ static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, add_wait_queue(wait_address, &entry->wait); } +/** + * poll_select_set_timeout - helper function to setup the timeout value + * @to: pointer to timespec variable for the final timeout + * @sec: seconds (from user space) + * @nsec: nanoseconds (from user space) + * + * Note, we do not use a timespec for the user space value here, That + * way we can use the function for timeval and compat interfaces as well. + * + * Returns -EINVAL if sec/nsec are not normalized. Otherwise 0. + */ +int poll_select_set_timeout(struct timespec *to, long sec, long nsec) +{ + struct timespec ts = {.tv_sec = sec, .tv_nsec = nsec}; + + if (!timespec_valid(&ts)) + return -EINVAL; + + /* Optimize for the zero timeout value here */ + if (!sec && !nsec) { + to->tv_sec = to->tv_nsec = 0; + } else { + ktime_get_ts(to); + *to = timespec_add_safe(*to, ts); + } + return 0; +} + +static int poll_select_copy_remaining(struct timespec *end_time, void __user *p, + int timeval, int ret) +{ + struct timespec rts; + struct timeval rtv; + + if (!p) + return ret; + + if (current->personality & STICKY_TIMEOUTS) + goto sticky; + + /* No update for zero timeout */ + if (!end_time->tv_sec && !end_time->tv_nsec) + return ret; + + ktime_get_ts(&rts); + rts = timespec_sub(*end_time, rts); + if (rts.tv_sec < 0) + rts.tv_sec = rts.tv_nsec = 0; + + if (timeval) { + rtv.tv_sec = rts.tv_sec; + rtv.tv_usec = rts.tv_nsec / NSEC_PER_USEC; + + if (!copy_to_user(p, &rtv, sizeof(rtv))) + return ret; + + } else if (!copy_to_user(p, &rts, sizeof(rts))) + return ret; + + /* + * If an application puts its timeval in read-only memory, we + * don't want the Linux-specific update to the timeval to + * cause a fault after the select has completed + * successfully. However, because we're not updating the + * timeval, we can't restart the system call. + */ + +sticky: + if (ret == -ERESTARTNOHAND) + ret = -EINTR; + return ret; +} + + + #define FDS_IN(fds, n) (fds->in + n) #define FDS_OUT(fds, n) (fds->out + n) #define FDS_EX(fds, n) (fds->ex + n) diff --git a/include/linux/poll.h b/include/linux/poll.h index ef453828877..f65de5128a9 100644 --- a/include/linux/poll.h +++ b/include/linux/poll.h @@ -120,6 +120,8 @@ extern int do_sys_poll(struct pollfd __user * ufds, unsigned int nfds, extern int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s64 *timeout); +extern int poll_select_set_timeout(struct timespec *to, long sec, long nsec); + #endif /* KERNEL */ #endif /* _LINUX_POLL_H */ -- cgit v1.2.3 From be5dad20a55e054a35dac7f6f5f184dc72b379b4 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 31 Aug 2008 08:19:15 -0700 Subject: select: add a poll specific struct to the restart_block union with hrtimer poll/select, the signal restart data no longer is a single long representing a jiffies count, but it becomes a second/nanosecond pair that also needs to encode if there was a timeout at all or not. This patch adds a struct to the restart_block union for this purpose Signed-off-by: Thomas Gleixner Signed-off-by: Arjan van de Ven --- include/linux/thread_info.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/linux/thread_info.h b/include/linux/thread_info.h index 38a56477f27..e6b820f8b56 100644 --- a/include/linux/thread_info.h +++ b/include/linux/thread_info.h @@ -38,6 +38,14 @@ struct restart_block { #endif u64 expires; } nanosleep; + /* For poll */ + struct { + struct pollfd __user *ufds; + int nfds; + int has_timeout; + unsigned long tv_sec; + unsigned long tv_nsec; + } poll; }; }; -- cgit v1.2.3 From 8ff3e8e85fa6c312051134b3953e397feb639f51 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sun, 31 Aug 2008 08:26:40 -0700 Subject: select: switch select() and poll() over to hrtimers With lots of help, input and cleanups from Thomas Gleixner This patch switches select() and poll() over to hrtimers. The core of the patch is replacing the "s64 timeout" with a "struct timespec end_time" in all the plumbing. But most of the diffstat comes from using the just introduced helpers: poll_select_set_timeout poll_select_copy_remaining timespec_add_safe which make manipulating the timespec easier and less error-prone. Signed-off-by: Arjan van de Ven Signed-off-by: Thomas Gleixner --- fs/compat.c | 136 ++++---------------------- fs/select.c | 263 +++++++++++++++++---------------------------------- include/linux/poll.h | 6 +- 3 files changed, 111 insertions(+), 294 deletions(-) diff --git a/fs/compat.c b/fs/compat.c index 424767c954a..133ed7f5d68 100644 --- a/fs/compat.c +++ b/fs/compat.c @@ -1568,7 +1568,8 @@ int compat_set_fd_set(unsigned long nr, compat_ulong_t __user *ufdset, ((unsigned long) (MAX_SCHEDULE_TIMEOUT / HZ)-1) int compat_core_sys_select(int n, compat_ulong_t __user *inp, - compat_ulong_t __user *outp, compat_ulong_t __user *exp, s64 *timeout) + compat_ulong_t __user *outp, compat_ulong_t __user *exp, + struct timespec *end_time) { fd_set_bits fds; void *bits; @@ -1615,7 +1616,7 @@ int compat_core_sys_select(int n, compat_ulong_t __user *inp, zero_fd_set(n, fds.res_out); zero_fd_set(n, fds.res_ex); - ret = do_select(n, &fds, timeout); + ret = do_select(n, &fds, end_time); if (ret < 0) goto out; @@ -1641,7 +1642,7 @@ asmlinkage long compat_sys_select(int n, compat_ulong_t __user *inp, compat_ulong_t __user *outp, compat_ulong_t __user *exp, struct compat_timeval __user *tvp) { - s64 timeout = -1; + struct timespec end_time, *to = NULL; struct compat_timeval tv; int ret; @@ -1649,43 +1650,14 @@ asmlinkage long compat_sys_select(int n, compat_ulong_t __user *inp, if (copy_from_user(&tv, tvp, sizeof(tv))) return -EFAULT; - if (tv.tv_sec < 0 || tv.tv_usec < 0) + to = &end_time; + if (poll_select_set_timeout(to, tv.tv_sec, + tv.tv_usec * NSEC_PER_USEC)) return -EINVAL; - - /* Cast to u64 to make GCC stop complaining */ - if ((u64)tv.tv_sec >= (u64)MAX_INT64_SECONDS) - timeout = -1; /* infinite */ - else { - timeout = DIV_ROUND_UP(tv.tv_usec, 1000000/HZ); - timeout += tv.tv_sec * HZ; - } } - ret = compat_core_sys_select(n, inp, outp, exp, &timeout); - - if (tvp) { - struct compat_timeval rtv; - - if (current->personality & STICKY_TIMEOUTS) - goto sticky; - rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)); - rtv.tv_sec = timeout; - if (compat_timeval_compare(&rtv, &tv) >= 0) - rtv = tv; - if (copy_to_user(tvp, &rtv, sizeof(rtv))) { -sticky: - /* - * If an application puts its timeval in read-only - * memory, we don't want the Linux-specific update to - * the timeval to cause a fault after the select has - * completed successfully. However, because we're not - * updating the timeval, we can't restart the system - * call. - */ - if (ret == -ERESTARTNOHAND) - ret = -EINTR; - } - } + ret = compat_core_sys_select(n, inp, outp, exp, to); + ret = poll_select_copy_remaining(&end_time, tvp, 1, ret); return ret; } @@ -1698,15 +1670,16 @@ asmlinkage long compat_sys_pselect7(int n, compat_ulong_t __user *inp, { compat_sigset_t ss32; sigset_t ksigmask, sigsaved; - s64 timeout = MAX_SCHEDULE_TIMEOUT; struct compat_timespec ts; + struct timespec end_time, *to = NULL; int ret; if (tsp) { if (copy_from_user(&ts, tsp, sizeof(ts))) return -EFAULT; - if (ts.tv_sec < 0 || ts.tv_nsec < 0) + to = &end_time; + if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec)) return -EINVAL; } @@ -1721,51 +1694,8 @@ asmlinkage long compat_sys_pselect7(int n, compat_ulong_t __user *inp, sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved); } - do { - if (tsp) { - if ((unsigned long)ts.tv_sec < MAX_SELECT_SECONDS) { - timeout = DIV_ROUND_UP(ts.tv_nsec, 1000000000/HZ); - timeout += ts.tv_sec * (unsigned long)HZ; - ts.tv_sec = 0; - ts.tv_nsec = 0; - } else { - ts.tv_sec -= MAX_SELECT_SECONDS; - timeout = MAX_SELECT_SECONDS * HZ; - } - } - - ret = compat_core_sys_select(n, inp, outp, exp, &timeout); - - } while (!ret && !timeout && tsp && (ts.tv_sec || ts.tv_nsec)); - - if (tsp) { - struct compat_timespec rts; - - if (current->personality & STICKY_TIMEOUTS) - goto sticky; - - rts.tv_sec = timeout / HZ; - rts.tv_nsec = (timeout % HZ) * (NSEC_PER_SEC/HZ); - if (rts.tv_nsec >= NSEC_PER_SEC) { - rts.tv_sec++; - rts.tv_nsec -= NSEC_PER_SEC; - } - if (compat_timespec_compare(&rts, &ts) >= 0) - rts = ts; - if (copy_to_user(tsp, &rts, sizeof(rts))) { -sticky: - /* - * If an application puts its timeval in read-only - * memory, we don't want the Linux-specific update to - * the timeval to cause a fault after the select has - * completed successfully. However, because we're not - * updating the timeval, we can't restart the system - * call. - */ - if (ret == -ERESTARTNOHAND) - ret = -EINTR; - } - } + ret = compat_core_sys_select(n, inp, outp, exp, to); + ret = poll_select_copy_remaining(&end_time, tsp, 0, ret); if (ret == -ERESTARTNOHAND) { /* @@ -1810,18 +1740,16 @@ asmlinkage long compat_sys_ppoll(struct pollfd __user *ufds, compat_sigset_t ss32; sigset_t ksigmask, sigsaved; struct compat_timespec ts; - s64 timeout = -1; + struct timespec end_time, *to = NULL; int ret; if (tsp) { if (copy_from_user(&ts, tsp, sizeof(ts))) return -EFAULT; - /* We assume that ts.tv_sec is always lower than - the number of seconds that can be expressed in - an s64. Otherwise the compiler bitches at us */ - timeout = DIV_ROUND_UP(ts.tv_nsec, 1000000000/HZ); - timeout += ts.tv_sec * HZ; + to = &end_time; + if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec)) + return -EINVAL; } if (sigmask) { @@ -1835,7 +1763,7 @@ asmlinkage long compat_sys_ppoll(struct pollfd __user *ufds, sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved); } - ret = do_sys_poll(ufds, nfds, &timeout); + ret = do_sys_poll(ufds, nfds, to); /* We can restart this syscall, usually */ if (ret == -EINTR) { @@ -1853,31 +1781,7 @@ asmlinkage long compat_sys_ppoll(struct pollfd __user *ufds, } else if (sigmask) sigprocmask(SIG_SETMASK, &sigsaved, NULL); - if (tsp && timeout >= 0) { - struct compat_timespec rts; - - if (current->personality & STICKY_TIMEOUTS) - goto sticky; - /* Yes, we know it's actually an s64, but it's also positive. */ - rts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * - 1000; - rts.tv_sec = timeout; - if (compat_timespec_compare(&rts, &ts) >= 0) - rts = ts; - if (copy_to_user(tsp, &rts, sizeof(rts))) { -sticky: - /* - * If an application puts its timeval in read-only - * memory, we don't want the Linux-specific update to - * the timeval to cause a fault after the select has - * completed successfully. However, because we're not - * updating the timeval, we can't restart the system - * call. - */ - if (ret == -ERESTARTNOHAND && timeout >= 0) - ret = -EINTR; - } - } + ret = poll_select_copy_remaining(&end_time, tsp, 0, ret); return ret; } diff --git a/fs/select.c b/fs/select.c index 1180a620778..f6dceb56793 100644 --- a/fs/select.c +++ b/fs/select.c @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -203,8 +204,6 @@ sticky: return ret; } - - #define FDS_IN(fds, n) (fds->in + n) #define FDS_OUT(fds, n) (fds->out + n) #define FDS_EX(fds, n) (fds->ex + n) @@ -257,11 +256,12 @@ get_max: #define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR) #define POLLEX_SET (POLLPRI) -int do_select(int n, fd_set_bits *fds, s64 *timeout) +int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { + ktime_t expire, *to = NULL; struct poll_wqueues table; poll_table *wait; - int retval, i; + int retval, i, timed_out = 0; rcu_read_lock(); retval = max_select_fd(n, fds); @@ -273,12 +273,14 @@ int do_select(int n, fd_set_bits *fds, s64 *timeout) poll_initwait(&table); wait = &table.pt; - if (!*timeout) + if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { wait = NULL; + timed_out = 1; + } + retval = 0; for (;;) { unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; - long __timeout; set_current_state(TASK_INTERRUPTIBLE); @@ -334,27 +336,25 @@ int do_select(int n, fd_set_bits *fds, s64 *timeout) cond_resched(); } wait = NULL; - if (retval || !*timeout || signal_pending(current)) + if (retval || timed_out || signal_pending(current)) break; if (table.error) { retval = table.error; break; } - if (*timeout < 0) { - /* Wait indefinitely */ - __timeout = MAX_SCHEDULE_TIMEOUT; - } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) { - /* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in a loop */ - __timeout = MAX_SCHEDULE_TIMEOUT - 1; - *timeout -= __timeout; - } else { - __timeout = *timeout; - *timeout = 0; + /* + * If this is the first loop and we have a timeout + * given, then we convert to ktime_t and set the to + * pointer to the expiry value. + */ + if (end_time && !to) { + expire = timespec_to_ktime(*end_time); + to = &expire; } - __timeout = schedule_timeout(__timeout); - if (*timeout >= 0) - *timeout += __timeout; + + if (!schedule_hrtimeout(to, HRTIMER_MODE_ABS)) + timed_out = 1; } __set_current_state(TASK_RUNNING); @@ -375,7 +375,7 @@ int do_select(int n, fd_set_bits *fds, s64 *timeout) ((unsigned long) (MAX_SCHEDULE_TIMEOUT / HZ)-1) int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, - fd_set __user *exp, s64 *timeout) + fd_set __user *exp, struct timespec *end_time) { fd_set_bits fds; void *bits; @@ -426,7 +426,7 @@ int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, zero_fd_set(n, fds.res_out); zero_fd_set(n, fds.res_ex); - ret = do_select(n, &fds, timeout); + ret = do_select(n, &fds, end_time); if (ret < 0) goto out; @@ -452,7 +452,7 @@ out_nofds: asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp) { - s64 timeout = -1; + struct timespec end_time, *to = NULL; struct timeval tv; int ret; @@ -460,43 +460,14 @@ asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp, if (copy_from_user(&tv, tvp, sizeof(tv))) return -EFAULT; - if (tv.tv_sec < 0 || tv.tv_usec < 0) + to = &end_time; + if (poll_select_set_timeout(to, tv.tv_sec, + tv.tv_usec * NSEC_PER_USEC)) return -EINVAL; - - /* Cast to u64 to make GCC stop complaining */ - if ((u64)tv.tv_sec >= (u64)MAX_INT64_SECONDS) - timeout = -1; /* infinite */ - else { - timeout = DIV_ROUND_UP(tv.tv_usec, USEC_PER_SEC/HZ); - timeout += tv.tv_sec * HZ; - } } - ret = core_sys_select(n, inp, outp, exp, &timeout); - - if (tvp) { - struct timeval rtv; - - if (current->personality & STICKY_TIMEOUTS) - goto sticky; - rtv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)); - rtv.tv_sec = timeout; - if (timeval_compare(&rtv, &tv) >= 0) - rtv = tv; - if (copy_to_user(tvp, &rtv, sizeof(rtv))) { -sticky: - /* - * If an application puts its timeval in read-only - * memory, we don't want the Linux-specific update to - * the timeval to cause a fault after the select has - * completed successfully. However, because we're not - * updating the timeval, we can't restart the system - * call. - */ - if (ret == -ERESTARTNOHAND) - ret = -EINTR; - } - } + ret = core_sys_select(n, inp, outp, exp, to); + ret = poll_select_copy_remaining(&end_time, tvp, 1, ret); return ret; } @@ -506,25 +477,17 @@ asmlinkage long sys_pselect7(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timespec __user *tsp, const sigset_t __user *sigmask, size_t sigsetsize) { - s64 timeout = MAX_SCHEDULE_TIMEOUT; sigset_t ksigmask, sigsaved; - struct timespec ts; + struct timespec ts, end_time, *to = NULL; int ret; if (tsp) { if (copy_from_user(&ts, tsp, sizeof(ts))) return -EFAULT; - if (ts.tv_sec < 0 || ts.tv_nsec < 0) + to = &end_time; + if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec)) return -EINVAL; - - /* Cast to u64 to make GCC stop complaining */ - if ((u64)ts.tv_sec >= (u64)MAX_INT64_SECONDS) - timeout = -1; /* infinite */ - else { - timeout = DIV_ROUND_UP(ts.tv_nsec, NSEC_PER_SEC/HZ); - timeout += ts.tv_sec * HZ; - } } if (sigmask) { @@ -538,32 +501,8 @@ asmlinkage long sys_pselect7(int n, fd_set __user *inp, fd_set __user *outp, sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved); } - ret = core_sys_select(n, inp, outp, exp, &timeout); - - if (tsp) { - struct timespec rts; - - if (current->personality & STICKY_TIMEOUTS) - goto sticky; - rts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * - 1000; - rts.tv_sec = timeout; - if (timespec_compare(&rts, &ts) >= 0) - rts = ts; - if (copy_to_user(tsp, &rts, sizeof(rts))) { -sticky: - /* - * If an application puts its timeval in read-only - * memory, we don't want the Linux-specific update to - * the timeval to cause a fault after the select has - * completed successfully. However, because we're not - * updating the timeval, we can't restart the system - * call. - */ - if (ret == -ERESTARTNOHAND) - ret = -EINTR; - } - } + ret = core_sys_select(n, inp, outp, exp, &end_time); + ret = poll_select_copy_remaining(&end_time, tsp, 0, ret); if (ret == -ERESTARTNOHAND) { /* @@ -649,18 +588,20 @@ static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait) } static int do_poll(unsigned int nfds, struct poll_list *list, - struct poll_wqueues *wait, s64 *timeout) + struct poll_wqueues *wait, struct timespec *end_time) { - int count = 0; poll_table* pt = &wait->pt; + ktime_t expire, *to = NULL; + int timed_out = 0, count = 0; /* Optimise the no-wait case */ - if (!(*timeout)) + if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { pt = NULL; + timed_out = 1; + } for (;;) { struct poll_list *walk; - long __timeout; set_current_state(TASK_INTERRUPTIBLE); for (walk = list; walk != NULL; walk = walk->next) { @@ -692,27 +633,21 @@ static int do_poll(unsigned int nfds, struct poll_list *list, if (signal_pending(current)) count = -EINTR; } - if (count || !*timeout) + if (count || timed_out) break; - if (*timeout < 0) { - /* Wait indefinitely */ - __timeout = MAX_SCHEDULE_TIMEOUT; - } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) { - /* - * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in - * a loop - */ - __timeout = MAX_SCHEDULE_TIMEOUT - 1; - *timeout -= __timeout; - } else { - __timeout = *timeout; - *timeout = 0; + /* + * If this is the first loop and we have a timeout + * given, then we convert to ktime_t and set the to + * pointer to the expiry value. + */ + if (end_time && !to) { + expire = timespec_to_ktime(*end_time); + to = &expire; } - __timeout = schedule_timeout(__timeout); - if (*timeout >= 0) - *timeout += __timeout; + if (!schedule_hrtimeout(to, HRTIMER_MODE_ABS)) + timed_out = 1; } __set_current_state(TASK_RUNNING); return count; @@ -721,7 +656,8 @@ static int do_poll(unsigned int nfds, struct poll_list *list, #define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \ sizeof(struct pollfd)) -int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout) +int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, + struct timespec *end_time) { struct poll_wqueues table; int err = -EFAULT, fdcount, len, size; @@ -761,7 +697,7 @@ int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout) } poll_initwait(&table); - fdcount = do_poll(nfds, head, &table, timeout); + fdcount = do_poll(nfds, head, &table, end_time); poll_freewait(&table); for (walk = head; walk; walk = walk->next) { @@ -787,16 +723,21 @@ out_fds: static long do_restart_poll(struct restart_block *restart_block) { - struct pollfd __user *ufds = (struct pollfd __user*)restart_block->arg0; - int nfds = restart_block->arg1; - s64 timeout = ((s64)restart_block->arg3<<32) | (s64)restart_block->arg2; + struct pollfd __user *ufds = restart_block->poll.ufds; + int nfds = restart_block->poll.nfds; + struct timespec *to = NULL, end_time; int ret; - ret = do_sys_poll(ufds, nfds, &timeout); + if (restart_block->poll.has_timeout) { + end_time.tv_sec = restart_block->poll.tv_sec; + end_time.tv_nsec = restart_block->poll.tv_nsec; + to = &end_time; + } + + ret = do_sys_poll(ufds, nfds, to); + if (ret == -EINTR) { restart_block->fn = do_restart_poll; - restart_block->arg2 = timeout & 0xFFFFFFFF; - restart_block->arg3 = (u64)timeout >> 32; ret = -ERESTART_RESTARTBLOCK; } return ret; @@ -805,31 +746,32 @@ static long do_restart_poll(struct restart_block *restart_block) asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds, long timeout_msecs) { - s64 timeout_jiffies; + struct timespec end_time, *to = NULL; int ret; - if (timeout_msecs > 0) { -#if HZ > 1000 - /* We can only overflow if HZ > 1000 */ - if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ) - timeout_jiffies = -1; - else -#endif - timeout_jiffies = msecs_to_jiffies(timeout_msecs) + 1; - } else { - /* Infinite (< 0) or no (0) timeout */ - timeout_jiffies = timeout_msecs; + if (timeout_msecs >= 0) { + to = &end_time; + poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC, + NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC)); } - ret = do_sys_poll(ufds, nfds, &timeout_jiffies); + ret = do_sys_poll(ufds, nfds, to); + if (ret == -EINTR) { struct restart_block *restart_block; + restart_block = ¤t_thread_info()->restart_block; restart_block->fn = do_restart_poll; - restart_block->arg0 = (unsigned long)ufds; - restart_block->arg1 = nfds; - restart_block->arg2 = timeout_jiffies & 0xFFFFFFFF; - restart_block->arg3 = (u64)timeout_jiffies >> 32; + restart_block->poll.ufds = ufds; + restart_block->poll.nfds = nfds; + + if (timeout_msecs >= 0) { + restart_block->poll.tv_sec = end_time.tv_sec; + restart_block->poll.tv_nsec = end_time.tv_nsec; + restart_block->poll.has_timeout = 1; + } else + restart_block->poll.has_timeout = 0; + ret = -ERESTART_RESTARTBLOCK; } return ret; @@ -841,21 +783,16 @@ asmlinkage long sys_ppoll(struct pollfd __user *ufds, unsigned int nfds, size_t sigsetsize) { sigset_t ksigmask, sigsaved; - struct timespec ts; - s64 timeout = -1; + struct timespec ts, end_time, *to = NULL; int ret; if (tsp) { if (copy_from_user(&ts, tsp, sizeof(ts))) return -EFAULT; - /* Cast to u64 to make GCC stop complaining */ - if ((u64)ts.tv_sec >= (u64)MAX_INT64_SECONDS) - timeout = -1; /* infinite */ - else { - timeout = DIV_ROUND_UP(ts.tv_nsec, NSEC_PER_SEC/HZ); - timeout += ts.tv_sec * HZ; - } + to = &end_time; + if (poll_select_set_timeout(to, ts.tv_sec, ts.tv_nsec)) + return -EINVAL; } if (sigmask) { @@ -869,7 +806,7 @@ asmlinkage long sys_ppoll(struct pollfd __user *ufds, unsigned int nfds, sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved); } - ret = do_sys_poll(ufds, nfds, &timeout); + ret = do_sys_poll(ufds, nfds, to); /* We can restart this syscall, usually */ if (ret == -EINTR) { @@ -887,31 +824,7 @@ asmlinkage long sys_ppoll(struct pollfd __user *ufds, unsigned int nfds, } else if (sigmask) sigprocmask(SIG_SETMASK, &sigsaved, NULL); - if (tsp && timeout >= 0) { - struct timespec rts; - - if (current->personality & STICKY_TIMEOUTS) - goto sticky; - /* Yes, we know it's actually an s64, but it's also positive. */ - rts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * - 1000; - rts.tv_sec = timeout; - if (timespec_compare(&rts, &ts) >= 0) - rts = ts; - if (copy_to_user(tsp, &rts, sizeof(rts))) { - sticky: - /* - * If an application puts its timeval in read-only - * memory, we don't want the Linux-specific update to - * the timeval to cause a fault after the select has - * completed successfully. However, because we're not - * updating the timeval, we can't restart the system - * call. - */ - if (ret == -ERESTARTNOHAND && timeout >= 0) - ret = -EINTR; - } - } + ret = poll_select_copy_remaining(&end_time, tsp, 0, ret); return ret; } diff --git a/include/linux/poll.h b/include/linux/poll.h index f65de5128a9..badd98ab06f 100644 --- a/include/linux/poll.h +++ b/include/linux/poll.h @@ -114,11 +114,11 @@ void zero_fd_set(unsigned long nr, unsigned long *fdset) #define MAX_INT64_SECONDS (((s64)(~((u64)0)>>1)/HZ)-1) -extern int do_select(int n, fd_set_bits *fds, s64 *timeout); +extern int do_select(int n, fd_set_bits *fds, struct timespec *end_time); extern int do_sys_poll(struct pollfd __user * ufds, unsigned int nfds, - s64 *timeout); + struct timespec *end_time); extern int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp, - fd_set __user *exp, s64 *timeout); + fd_set __user *exp, struct timespec *end_time); extern int poll_select_set_timeout(struct timespec *to, long sec, long nsec); -- cgit v1.2.3 From 63ca243b271f5b44e0b1057003cf498b6d0fadf7 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 14:35:02 -0700 Subject: hrtimer: add abstraction functions for accessing the "expires" member In order to be able to turn hrtimers into range based, we need to provide accessor functions for getting to the "expires" ktime_t member of the struct hrtimer. This patch adds a set of accessors for this purpose: * hrtimer_set_expires * hrtimer_set_expires_tv64 * hrtimer_add_expires * hrtimer_add_expires_ns * hrtimer_get_expires * hrtimer_get_expires_tv64 * hrtimer_get_expires_ns * hrtimer_expires_remaining * hrtimer_start_expires No users of these new accessors are added yet; these follow in later patches. Hopefully this patch can even go into 2.6.27-rc so that the conversions will not have a bottleneck in -next Signed-off-by: Arjan van de Ven --- include/linux/hrtimer.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index becd17db1a1..9900e998ea8 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -217,6 +217,45 @@ static inline int hrtimer_is_hres_active(struct hrtimer *timer) return timer->base->cpu_base->hres_active; } +static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time) +{ + timer->expires = time; +} +static inline void hrtimer_set_expires_tv64(struct hrtimer *timer, s64 tv64) +{ + timer->expires.tv64 = tv64; +} + +static inline void hrtimer_add_expires(struct hrtimer *timer, ktime_t time) +{ + timer->expires = ktime_add_safe(timer->expires, time); +} + +static inline void hrtimer_add_expires_ns(struct hrtimer *timer, unsigned long ns) +{ + timer->expires = ktime_add_ns(timer->expires, ns); +} + +static inline ktime_t hrtimer_get_expires(const struct hrtimer *timer) +{ + return timer->expires; +} + +static inline s64 hrtimer_get_expires_tv64(const struct hrtimer *timer) +{ + return timer->expires.tv64; +} + +static inline s64 hrtimer_get_expires_ns(const struct hrtimer *timer) +{ + return ktime_to_ns(timer->expires); +} + +static inline ktime_t hrtimer_expires_remaining(const struct hrtimer *timer) +{ + return ktime_sub(timer->expires, timer->base->get_time()); +} + /* * The resolution of the clocks. The resolution value is returned in * the clock_getres() system call to give application programmers an @@ -287,6 +326,12 @@ extern int hrtimer_start(struct hrtimer *timer, ktime_t tim, extern int hrtimer_cancel(struct hrtimer *timer); extern int hrtimer_try_to_cancel(struct hrtimer *timer); +static inline int hrtimer_start_expires(struct hrtimer *timer, + enum hrtimer_mode mode) +{ + return hrtimer_start(timer, hrtimer_get_expires(timer), mode); +} + static inline int hrtimer_restart(struct hrtimer *timer) { return hrtimer_start(timer, timer->expires, HRTIMER_MODE_ABS); -- cgit v1.2.3 From beb20d52d03a51218827fb4a36a4b583debb03f9 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 14:55:57 -0700 Subject: hrtimer: convert kvm to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts KVM to these accessors. Signed-off-by: Arjan van de Ven --- arch/x86/kvm/i8254.c | 6 +++--- arch/x86/kvm/lapic.c | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/arch/x86/kvm/i8254.c b/arch/x86/kvm/i8254.c index c0f7872a912..1bf8f57a304 100644 --- a/arch/x86/kvm/i8254.c +++ b/arch/x86/kvm/i8254.c @@ -205,8 +205,8 @@ static int __pit_timer_fn(struct kvm_kpit_state *ps) wake_up_interruptible(&vcpu0->wq); } - pt->timer.expires = ktime_add_ns(pt->timer.expires, pt->period); - pt->scheduled = ktime_to_ns(pt->timer.expires); + hrtimer_add_expires_ns(&pt->timer, pt->period); + pt->scheduled = ktime_to_ns(hrtimer_get_expires(&pt->timer)); return (pt->period == 0 ? 0 : 1); } @@ -246,7 +246,7 @@ void __kvm_migrate_pit_timer(struct kvm_vcpu *vcpu) timer = &pit->pit_state.pit_timer.timer; if (hrtimer_cancel(timer)) - hrtimer_start(timer, timer->expires, HRTIMER_MODE_ABS); + hrtimer_start_expires(timer, HRTIMER_MODE_ABS); } static void destroy_pit_timer(struct kvm_kpit_timer *pt) diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c index 73f43de69f6..a5b61de6adf 100644 --- a/arch/x86/kvm/lapic.c +++ b/arch/x86/kvm/lapic.c @@ -953,9 +953,7 @@ static int __apic_timer_fn(struct kvm_lapic *apic) } if (apic_lvtt_period(apic)) { result = 1; - apic->timer.dev.expires = ktime_add_ns( - apic->timer.dev.expires, - apic->timer.period); + hrtimer_add_expires_ns(&apic->timer.dev, apic->timer.period); } return result; } @@ -1124,7 +1122,7 @@ void __kvm_migrate_apic_timer(struct kvm_vcpu *vcpu) timer = &apic->timer.dev; if (hrtimer_cancel(timer)) - hrtimer_start(timer, timer->expires, HRTIMER_MODE_ABS); + hrtimer_start_expires(timer, HRTIMER_MODE_ABS); } void kvm_lapic_sync_from_vapic(struct kvm_vcpu *vcpu) -- cgit v1.2.3 From 76369470b7e5f97fc1a8af83c45b9ff739b08cb6 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:00:14 -0700 Subject: hrtimer: convert timerfd to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts timerfd to these accessors. Signed-off-by: Arjan van de Ven --- fs/timerfd.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/timerfd.c b/fs/timerfd.c index c502c60e4f5..0862f0e49d0 100644 --- a/fs/timerfd.c +++ b/fs/timerfd.c @@ -52,11 +52,9 @@ static enum hrtimer_restart timerfd_tmrproc(struct hrtimer *htmr) static ktime_t timerfd_get_remaining(struct timerfd_ctx *ctx) { - ktime_t now, remaining; - - now = ctx->tmr.base->get_time(); - remaining = ktime_sub(ctx->tmr.expires, now); + ktime_t remaining; + remaining = hrtimer_expires_remaining(&ctx->tmr); return remaining.tv64 < 0 ? ktime_set(0, 0): remaining; } @@ -74,7 +72,7 @@ static void timerfd_setup(struct timerfd_ctx *ctx, int flags, ctx->ticks = 0; ctx->tintv = timespec_to_ktime(ktmr->it_interval); hrtimer_init(&ctx->tmr, ctx->clockid, htmode); - ctx->tmr.expires = texp; + hrtimer_set_expires(&ctx->tmr, texp); ctx->tmr.function = timerfd_tmrproc; if (texp.tv64 != 0) hrtimer_start(&ctx->tmr, texp, htmode); -- cgit v1.2.3 From 23dd7bb09bd8d7efd8a602aed97b93d52f85e675 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:00:54 -0700 Subject: hrtimer: convert net::sched_cbq to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts sched_cbq to these accessors. Signed-off-by: Arjan van de Ven --- net/sched/sch_cbq.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/sched/sch_cbq.c b/net/sched/sch_cbq.c index 8b06fa90048..03e389e8d94 100644 --- a/net/sched/sch_cbq.c +++ b/net/sched/sch_cbq.c @@ -545,9 +545,10 @@ static void cbq_ovl_delay(struct cbq_class *cl) expires = ktime_set(0, 0); expires = ktime_add_ns(expires, PSCHED_US2NS(sched)); if (hrtimer_try_to_cancel(&q->delay_timer) && - ktime_to_ns(ktime_sub(q->delay_timer.expires, - expires)) > 0) - q->delay_timer.expires = expires; + ktime_to_ns(ktime_sub( + hrtimer_get_expires(&q->delay_timer), + expires)) > 0) + hrtimer_set_expires(&q->delay_timer, expires); hrtimer_restart(&q->delay_timer); cl->delayed = 1; cl->xstats.overactions++; -- cgit v1.2.3 From cc584b213f252bf698849cf4be2377cd3ec7501a Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:02:30 -0700 Subject: hrtimer: convert kernel/* to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts kernel/* to these accessors. Signed-off-by: Arjan van de Ven --- kernel/futex.c | 7 +++---- kernel/hrtimer.c | 44 +++++++++++++++++++++++--------------------- kernel/posix-timers.c | 10 ++++------ kernel/rtmutex.c | 3 +-- kernel/sched.c | 7 +++---- kernel/time/ntp.c | 3 +-- kernel/time/tick-sched.c | 21 ++++++++++----------- kernel/time/timer_list.c | 4 ++-- 8 files changed, 47 insertions(+), 52 deletions(-) diff --git a/kernel/futex.c b/kernel/futex.c index 7d1136e97c1..4cd5b4319b0 100644 --- a/kernel/futex.c +++ b/kernel/futex.c @@ -1299,10 +1299,9 @@ static int futex_wait(u32 __user *uaddr, struct rw_semaphore *fshared, hrtimer_init_on_stack(&t.timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); hrtimer_init_sleeper(&t, current); - t.timer.expires = *abs_time; + hrtimer_set_expires(&t.timer, *abs_time); - hrtimer_start(&t.timer, t.timer.expires, - HRTIMER_MODE_ABS); + hrtimer_start_expires(&t.timer, HRTIMER_MODE_ABS); if (!hrtimer_active(&t.timer)) t.task = NULL; @@ -1404,7 +1403,7 @@ static int futex_lock_pi(u32 __user *uaddr, struct rw_semaphore *fshared, hrtimer_init_on_stack(&to->timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); hrtimer_init_sleeper(to, current); - to->timer.expires = *time; + hrtimer_set_expires(&to->timer, *time); } q.pi_state = NULL; diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index 782137dc755..ae307feec74 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -517,7 +517,7 @@ static void hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base) if (!base->first) continue; timer = rb_entry(base->first, struct hrtimer, node); - expires = ktime_sub(timer->expires, base->offset); + expires = ktime_sub(hrtimer_get_expires(timer), base->offset); if (expires.tv64 < cpu_base->expires_next.tv64) cpu_base->expires_next = expires; } @@ -539,10 +539,10 @@ static int hrtimer_reprogram(struct hrtimer *timer, struct hrtimer_clock_base *base) { ktime_t *expires_next = &__get_cpu_var(hrtimer_bases).expires_next; - ktime_t expires = ktime_sub(timer->expires, base->offset); + ktime_t expires = ktime_sub(hrtimer_get_expires(timer), base->offset); int res; - WARN_ON_ONCE(timer->expires.tv64 < 0); + WARN_ON_ONCE(hrtimer_get_expires_tv64(timer) < 0); /* * When the callback is running, we do not reprogram the clock event @@ -794,7 +794,7 @@ u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval) u64 orun = 1; ktime_t delta; - delta = ktime_sub(now, timer->expires); + delta = ktime_sub(now, hrtimer_get_expires(timer)); if (delta.tv64 < 0) return 0; @@ -806,8 +806,8 @@ u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval) s64 incr = ktime_to_ns(interval); orun = ktime_divns(delta, incr); - timer->expires = ktime_add_ns(timer->expires, incr * orun); - if (timer->expires.tv64 > now.tv64) + hrtimer_add_expires_ns(timer, incr * orun); + if (hrtimer_get_expires_tv64(timer) > now.tv64) return orun; /* * This (and the ktime_add() below) is the @@ -815,7 +815,7 @@ u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval) */ orun++; } - timer->expires = ktime_add_safe(timer->expires, interval); + hrtimer_add_expires(timer, interval); return orun; } @@ -847,7 +847,8 @@ static void enqueue_hrtimer(struct hrtimer *timer, * We dont care about collisions. Nodes with * the same expiry time stay together. */ - if (timer->expires.tv64 < entry->expires.tv64) { + if (hrtimer_get_expires_tv64(timer) < + hrtimer_get_expires_tv64(entry)) { link = &(*link)->rb_left; } else { link = &(*link)->rb_right; @@ -982,7 +983,7 @@ hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode) #endif } - timer->expires = tim; + hrtimer_set_expires(timer, tim); timer_stats_hrtimer_set_start_info(timer); @@ -1076,7 +1077,7 @@ ktime_t hrtimer_get_remaining(const struct hrtimer *timer) ktime_t rem; base = lock_hrtimer_base(timer, &flags); - rem = ktime_sub(timer->expires, base->get_time()); + rem = hrtimer_expires_remaining(timer); unlock_hrtimer_base(timer, &flags); return rem; @@ -1108,7 +1109,7 @@ ktime_t hrtimer_get_next_event(void) continue; timer = rb_entry(base->first, struct hrtimer, node); - delta.tv64 = timer->expires.tv64; + delta.tv64 = hrtimer_get_expires_tv64(timer); delta = ktime_sub(delta, base->get_time()); if (delta.tv64 < mindelta.tv64) mindelta.tv64 = delta.tv64; @@ -1308,10 +1309,10 @@ void hrtimer_interrupt(struct clock_event_device *dev) timer = rb_entry(node, struct hrtimer, node); - if (basenow.tv64 < timer->expires.tv64) { + if (basenow.tv64 < hrtimer_get_expires_tv64(timer)) { ktime_t expires; - expires = ktime_sub(timer->expires, + expires = ktime_sub(hrtimer_get_expires(timer), base->offset); if (expires.tv64 < expires_next.tv64) expires_next = expires; @@ -1414,7 +1415,8 @@ void hrtimer_run_queues(void) struct hrtimer *timer; timer = rb_entry(node, struct hrtimer, node); - if (base->softirq_time.tv64 <= timer->expires.tv64) + if (base->softirq_time.tv64 <= + hrtimer_get_expires_tv64(timer)) break; if (timer->cb_mode == HRTIMER_CB_SOFTIRQ) { @@ -1462,7 +1464,7 @@ static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mod do { set_current_state(TASK_INTERRUPTIBLE); - hrtimer_start(&t->timer, t->timer.expires, mode); + hrtimer_start_expires(&t->timer, mode); if (!hrtimer_active(&t->timer)) t->task = NULL; @@ -1484,7 +1486,7 @@ static int update_rmtp(struct hrtimer *timer, struct timespec __user *rmtp) struct timespec rmt; ktime_t rem; - rem = ktime_sub(timer->expires, timer->base->get_time()); + rem = hrtimer_expires_remaining(timer); if (rem.tv64 <= 0) return 0; rmt = ktime_to_timespec(rem); @@ -1503,7 +1505,7 @@ long __sched hrtimer_nanosleep_restart(struct restart_block *restart) hrtimer_init_on_stack(&t.timer, restart->nanosleep.index, HRTIMER_MODE_ABS); - t.timer.expires.tv64 = restart->nanosleep.expires; + hrtimer_set_expires_tv64(&t.timer, restart->nanosleep.expires); if (do_nanosleep(&t, HRTIMER_MODE_ABS)) goto out; @@ -1530,7 +1532,7 @@ long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp, int ret = 0; hrtimer_init_on_stack(&t.timer, clockid, mode); - t.timer.expires = timespec_to_ktime(*rqtp); + hrtimer_set_expires(&t.timer, timespec_to_ktime(*rqtp)); if (do_nanosleep(&t, mode)) goto out; @@ -1550,7 +1552,7 @@ long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp, restart->fn = hrtimer_nanosleep_restart; restart->nanosleep.index = t.timer.base->index; restart->nanosleep.rmtp = rmtp; - restart->nanosleep.expires = t.timer.expires.tv64; + restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer); ret = -ERESTART_RESTARTBLOCK; out: @@ -1724,11 +1726,11 @@ int __sched schedule_hrtimeout(ktime_t *expires, } hrtimer_init_on_stack(&t.timer, CLOCK_MONOTONIC, mode); - t.timer.expires = *expires; + hrtimer_set_expires(&t.timer, *expires); hrtimer_init_sleeper(&t, current); - hrtimer_start(&t.timer, t.timer.expires, mode); + hrtimer_start_expires(&t.timer, mode); if (!hrtimer_active(&t.timer)) t.task = NULL; diff --git a/kernel/posix-timers.c b/kernel/posix-timers.c index e36d5798cbf..f85efcdcab2 100644 --- a/kernel/posix-timers.c +++ b/kernel/posix-timers.c @@ -668,7 +668,7 @@ common_timer_get(struct k_itimer *timr, struct itimerspec *cur_setting) (timr->it_sigev_notify & ~SIGEV_THREAD_ID) == SIGEV_NONE)) timr->it_overrun += (unsigned int) hrtimer_forward(timer, now, iv); - remaining = ktime_sub(timer->expires, now); + remaining = ktime_sub(hrtimer_get_expires(timer), now); /* Return 0 only, when the timer is expired and not pending */ if (remaining.tv64 <= 0) { /* @@ -762,7 +762,7 @@ common_timer_set(struct k_itimer *timr, int flags, hrtimer_init(&timr->it.real.timer, timr->it_clock, mode); timr->it.real.timer.function = posix_timer_fn; - timer->expires = timespec_to_ktime(new_setting->it_value); + hrtimer_set_expires(timer, timespec_to_ktime(new_setting->it_value)); /* Convert interval */ timr->it.real.interval = timespec_to_ktime(new_setting->it_interval); @@ -771,14 +771,12 @@ common_timer_set(struct k_itimer *timr, int flags, if (((timr->it_sigev_notify & ~SIGEV_THREAD_ID) == SIGEV_NONE)) { /* Setup correct expiry time for relative timers */ if (mode == HRTIMER_MODE_REL) { - timer->expires = - ktime_add_safe(timer->expires, - timer->base->get_time()); + hrtimer_add_expires(timer, timer->base->get_time()); } return 0; } - hrtimer_start(timer, timer->expires, mode); + hrtimer_start_expires(timer, mode); return 0; } diff --git a/kernel/rtmutex.c b/kernel/rtmutex.c index 6522ae5b14a..69d9cb921ff 100644 --- a/kernel/rtmutex.c +++ b/kernel/rtmutex.c @@ -631,8 +631,7 @@ rt_mutex_slowlock(struct rt_mutex *lock, int state, /* Setup the timer, when timeout != NULL */ if (unlikely(timeout)) { - hrtimer_start(&timeout->timer, timeout->timer.expires, - HRTIMER_MODE_ABS); + hrtimer_start_expires(&timeout->timer, HRTIMER_MODE_ABS); if (!hrtimer_active(&timeout->timer)) timeout->task = NULL; } diff --git a/kernel/sched.c b/kernel/sched.c index 1a5f73c1fcd..e46b5afa200 100644 --- a/kernel/sched.c +++ b/kernel/sched.c @@ -221,9 +221,8 @@ static void start_rt_bandwidth(struct rt_bandwidth *rt_b) now = hrtimer_cb_get_time(&rt_b->rt_period_timer); hrtimer_forward(&rt_b->rt_period_timer, now, rt_b->rt_period); - hrtimer_start(&rt_b->rt_period_timer, - rt_b->rt_period_timer.expires, - HRTIMER_MODE_ABS); + hrtimer_start_expires(&rt_b->rt_period_timer, + HRTIMER_MODE_ABS); } spin_unlock(&rt_b->rt_runtime_lock); } @@ -1058,7 +1057,7 @@ static void hrtick_start(struct rq *rq, u64 delay) struct hrtimer *timer = &rq->hrtick_timer; ktime_t time = ktime_add_ns(timer->base->get_time(), delay); - timer->expires = time; + hrtimer_set_expires(timer, time); if (rq == this_rq()) { hrtimer_restart(timer); diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 5125ddd8196..4c8d85421d2 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -142,8 +142,7 @@ static enum hrtimer_restart ntp_leap_second(struct hrtimer *timer) time_state = TIME_OOP; printk(KERN_NOTICE "Clock: " "inserting leap second 23:59:60 UTC\n"); - leap_timer.expires = ktime_add_ns(leap_timer.expires, - NSEC_PER_SEC); + hrtimer_add_expires_ns(&leap_timer, NSEC_PER_SEC); res = HRTIMER_RESTART; break; case TIME_DEL: diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index a87b0468568..b33be61c0f6 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -288,7 +288,7 @@ void tick_nohz_stop_sched_tick(int inidle) goto out; } - ts->idle_tick = ts->sched_timer.expires; + ts->idle_tick = hrtimer_get_expires(&ts->sched_timer); ts->tick_stopped = 1; ts->idle_jiffies = last_jiffies; rcu_enter_nohz(); @@ -419,21 +419,21 @@ void tick_nohz_restart_sched_tick(void) ts->tick_stopped = 0; ts->idle_exittime = now; hrtimer_cancel(&ts->sched_timer); - ts->sched_timer.expires = ts->idle_tick; + hrtimer_set_expires(&ts->sched_timer, ts->idle_tick); while (1) { /* Forward the time to expire in the future */ hrtimer_forward(&ts->sched_timer, now, tick_period); if (ts->nohz_mode == NOHZ_MODE_HIGHRES) { - hrtimer_start(&ts->sched_timer, - ts->sched_timer.expires, + hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS); /* Check, if the timer was already in the past */ if (hrtimer_active(&ts->sched_timer)) break; } else { - if (!tick_program_event(ts->sched_timer.expires, 0)) + if (!tick_program_event( + hrtimer_get_expires(&ts->sched_timer), 0)) break; } /* Update jiffies and reread time */ @@ -446,7 +446,7 @@ void tick_nohz_restart_sched_tick(void) static int tick_nohz_reprogram(struct tick_sched *ts, ktime_t now) { hrtimer_forward(&ts->sched_timer, now, tick_period); - return tick_program_event(ts->sched_timer.expires, 0); + return tick_program_event(hrtimer_get_expires(&ts->sched_timer), 0); } /* @@ -529,7 +529,7 @@ static void tick_nohz_switch_to_nohz(void) next = tick_init_jiffy_update(); for (;;) { - ts->sched_timer.expires = next; + hrtimer_set_expires(&ts->sched_timer, next); if (!tick_program_event(next, 0)) break; next = ktime_add(next, tick_period); @@ -625,16 +625,15 @@ void tick_setup_sched_timer(void) ts->sched_timer.cb_mode = HRTIMER_CB_IRQSAFE_NO_SOFTIRQ; /* Get the next period (per cpu) */ - ts->sched_timer.expires = tick_init_jiffy_update(); + hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update()); offset = ktime_to_ns(tick_period) >> 1; do_div(offset, num_possible_cpus()); offset *= smp_processor_id(); - ts->sched_timer.expires = ktime_add_ns(ts->sched_timer.expires, offset); + hrtimer_add_expires_ns(&ts->sched_timer, offset); for (;;) { hrtimer_forward(&ts->sched_timer, now, tick_period); - hrtimer_start(&ts->sched_timer, ts->sched_timer.expires, - HRTIMER_MODE_ABS); + hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS); /* Check, if the timer was already in the past */ if (hrtimer_active(&ts->sched_timer)) break; diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index a40e20fd000..5224a3215fb 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -66,8 +66,8 @@ print_timer(struct seq_file *m, struct hrtimer *timer, int idx, u64 now) #endif SEQ_printf(m, "\n"); SEQ_printf(m, " # expires at %Lu nsecs [in %Ld nsecs]\n", - (unsigned long long)ktime_to_ns(timer->expires), - (long long)(ktime_to_ns(timer->expires) - now)); + (unsigned long long)ktime_to_ns(hrtimer_get_expires(timer)), + (long long)(ktime_to_ns(hrtimer_get_expires(timer)) - now)); } static void -- cgit v1.2.3 From 23446d1dc3d4f42a2b0fb82d4a098f9179ba486d Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:18:10 -0700 Subject: hrtimer: convert powerpc/oprofile to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts powerpc/oprofile to these accessors. Signed-off-by: Arjan van de Ven --- arch/powerpc/oprofile/cell/spu_profiler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/oprofile/cell/spu_profiler.c b/arch/powerpc/oprofile/cell/spu_profiler.c index 380d7e21753..02ffe060db5 100644 --- a/arch/powerpc/oprofile/cell/spu_profiler.c +++ b/arch/powerpc/oprofile/cell/spu_profiler.c @@ -196,7 +196,7 @@ int start_spu_profiling(unsigned int cycles_reset) pr_debug("timer resolution: %lu\n", TICK_NSEC); kt = ktime_set(0, profiling_interval); hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - timer.expires = kt; + hrtimer_set_expires(&timer, kt); timer.function = profile_spus; /* Allocate arrays for collecting SPU PC samples */ -- cgit v1.2.3 From 18dd36af0010dd70c8634cdca0f99b47b5036c60 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:19:11 -0700 Subject: hrtimer: convert kvm-ia64 to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts KVM-ia64 to these accessors. Signed-off-by: Arjan van de Ven --- arch/ia64/kvm/kvm-ia64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/ia64/kvm/kvm-ia64.c b/arch/ia64/kvm/kvm-ia64.c index 7a37d06376b..cf8eae1855e 100644 --- a/arch/ia64/kvm/kvm-ia64.c +++ b/arch/ia64/kvm/kvm-ia64.c @@ -1112,7 +1112,7 @@ static void kvm_migrate_hlt_timer(struct kvm_vcpu *vcpu) struct hrtimer *p_ht = &vcpu->arch.hlt_timer; if (hrtimer_cancel(p_ht)) - hrtimer_start(p_ht, p_ht->expires, HRTIMER_MODE_ABS); + hrtimer_start_expires(p_ht, HRTIMER_MODE_ABS); } static enum hrtimer_restart hlt_timer_fn(struct hrtimer *data) -- cgit v1.2.3 From 6c644eaeb2e000a08f0e20653b0835bb90a93e4a Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:20:30 -0700 Subject: hrtimer: convert s390 to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts s390 to these accessors. Signed-off-by: Arjan van de Ven --- drivers/s390/crypto/ap_bus.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c index 62b6b55230d..6f02f1e674d 100644 --- a/drivers/s390/crypto/ap_bus.c +++ b/drivers/s390/crypto/ap_bus.c @@ -659,9 +659,9 @@ static ssize_t poll_timeout_store(struct bus_type *bus, const char *buf, hr_time = ktime_set(0, poll_timeout); if (!hrtimer_is_queued(&ap_poll_timer) || - !hrtimer_forward(&ap_poll_timer, ap_poll_timer.expires, hr_time)) { - ap_poll_timer.expires = hr_time; - hrtimer_start(&ap_poll_timer, hr_time, HRTIMER_MODE_ABS); + !hrtimer_forward(&ap_poll_timer, hrtimer_get_expires(&ap_poll_timer), hr_time)) { + hrtimer_set_expires(&ap_poll_timer, hr_time); + hrtimer_start_expires(&ap_poll_timer, HRTIMER_MODE_ABS); } return count; } -- cgit v1.2.3 From 5c73a7d0411999e3cb3c6d64225450813738ae25 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:25:20 -0700 Subject: hrtimer: convert sound/ to the new hrtimer apis In order to be able to do range hrtimers we need to use accessor functions to the "expire" member of the hrtimer struct. This patch converts sound/ to these accessors. Signed-off-by: Arjan van de Ven --- sound/drivers/pcsp/pcsp_lib.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c index e341f3f83b6..1f42e406311 100644 --- a/sound/drivers/pcsp/pcsp_lib.c +++ b/sound/drivers/pcsp/pcsp_lib.c @@ -34,7 +34,7 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) chip->thalf = 0; if (!atomic_read(&chip->timer_active)) return HRTIMER_NORESTART; - hrtimer_forward(&chip->timer, chip->timer.expires, + hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer), ktime_set(0, chip->ns_rem)); return HRTIMER_RESTART; } @@ -118,7 +118,8 @@ enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) chip->ns_rem = PCSP_PERIOD_NS(); ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); chip->ns_rem -= ns; - hrtimer_forward(&chip->timer, chip->timer.expires, ktime_set(0, ns)); + hrtimer_forward(&chip->timer, hrtimer_get_expires(&chip->timer), + ktime_set(0, ns)); return HRTIMER_RESTART; exit_nr_unlock2: -- cgit v1.2.3 From 799b64de256ea68fbb5db63bb55f61c305870643 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:27:58 -0700 Subject: hrtimer: rename the "expires" struct member to avoid accidental usage To catch code that still touches the "expires" memory directly, rename it to have the compiler complain rather than get nasty, hard to explain, runtime behavior Signed-off-by: Arjan van de Ven --- include/linux/hrtimer.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 9900e998ea8..485a634fd6e 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -111,7 +111,7 @@ enum hrtimer_cb_mode { */ struct hrtimer { struct rb_node node; - ktime_t expires; + ktime_t _expires; enum hrtimer_restart (*function)(struct hrtimer *); struct hrtimer_clock_base *base; unsigned long state; @@ -219,41 +219,41 @@ static inline int hrtimer_is_hres_active(struct hrtimer *timer) static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time) { - timer->expires = time; + timer->_expires = time; } static inline void hrtimer_set_expires_tv64(struct hrtimer *timer, s64 tv64) { - timer->expires.tv64 = tv64; + timer->_expires.tv64 = tv64; } static inline void hrtimer_add_expires(struct hrtimer *timer, ktime_t time) { - timer->expires = ktime_add_safe(timer->expires, time); + timer->_expires = ktime_add_safe(timer->_expires, time); } static inline void hrtimer_add_expires_ns(struct hrtimer *timer, unsigned long ns) { - timer->expires = ktime_add_ns(timer->expires, ns); + timer->_expires = ktime_add_ns(timer->_expires, ns); } static inline ktime_t hrtimer_get_expires(const struct hrtimer *timer) { - return timer->expires; + return timer->_expires; } static inline s64 hrtimer_get_expires_tv64(const struct hrtimer *timer) { - return timer->expires.tv64; + return timer->_expires.tv64; } static inline s64 hrtimer_get_expires_ns(const struct hrtimer *timer) { - return ktime_to_ns(timer->expires); + return ktime_to_ns(timer->_expires); } static inline ktime_t hrtimer_expires_remaining(const struct hrtimer *timer) { - return ktime_sub(timer->expires, timer->base->get_time()); + return ktime_sub(timer->_expires, timer->base->get_time()); } /* @@ -334,7 +334,7 @@ static inline int hrtimer_start_expires(struct hrtimer *timer, static inline int hrtimer_restart(struct hrtimer *timer) { - return hrtimer_start(timer, timer->expires, HRTIMER_MODE_ABS); + return hrtimer_start(timer, timer->_expires, HRTIMER_MODE_ABS); } /* Query timers: */ -- cgit v1.2.3 From 654c8e0b1c623b156c5b92f28d914ab38c9c2c90 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:47:08 -0700 Subject: hrtimer: turn hrtimers into range timers this patch turns hrtimers into range timers; they have 2 expire points 1) the soft expire point 2) the hard expire point the kernel will do it's regular best effort attempt to get the timer run at the hard expire point. However, if some other time fires after the soft expire point, the kernel now has the freedom to fire this timer at this point, and thus grouping the events and preventing a power-expensive wakeup in the future. Signed-off-by: Arjan van de Ven --- include/linux/hrtimer.h | 31 ++++++++++++++++++++++++++- kernel/hrtimer.c | 56 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 485a634fd6e..28259c33667 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -112,6 +112,7 @@ enum hrtimer_cb_mode { struct hrtimer { struct rb_node node; ktime_t _expires; + ktime_t _softexpires; enum hrtimer_restart (*function)(struct hrtimer *); struct hrtimer_clock_base *base; unsigned long state; @@ -220,20 +221,37 @@ static inline int hrtimer_is_hres_active(struct hrtimer *timer) static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time) { timer->_expires = time; + timer->_softexpires = time; } + +static inline void hrtimer_set_expires_range(struct hrtimer *timer, ktime_t time, ktime_t delta) +{ + timer->_softexpires = time; + timer->_expires = ktime_add_safe(time, delta); +} + +static inline void hrtimer_set_expires_range_ns(struct hrtimer *timer, ktime_t time, unsigned long delta) +{ + timer->_softexpires = time; + timer->_expires = ktime_add_safe(time, ns_to_ktime(delta)); +} + static inline void hrtimer_set_expires_tv64(struct hrtimer *timer, s64 tv64) { timer->_expires.tv64 = tv64; + timer->_softexpires.tv64 = tv64; } static inline void hrtimer_add_expires(struct hrtimer *timer, ktime_t time) { timer->_expires = ktime_add_safe(timer->_expires, time); + timer->_softexpires = ktime_add_safe(timer->_softexpires, time); } static inline void hrtimer_add_expires_ns(struct hrtimer *timer, unsigned long ns) { timer->_expires = ktime_add_ns(timer->_expires, ns); + timer->_softexpires = ktime_add_ns(timer->_softexpires, ns); } static inline ktime_t hrtimer_get_expires(const struct hrtimer *timer) @@ -241,10 +259,19 @@ static inline ktime_t hrtimer_get_expires(const struct hrtimer *timer) return timer->_expires; } +static inline ktime_t hrtimer_get_softexpires(const struct hrtimer *timer) +{ + return timer->_softexpires; +} + static inline s64 hrtimer_get_expires_tv64(const struct hrtimer *timer) { return timer->_expires.tv64; } +static inline s64 hrtimer_get_softexpires_tv64(const struct hrtimer *timer) +{ + return timer->_softexpires.tv64; +} static inline s64 hrtimer_get_expires_ns(const struct hrtimer *timer) { @@ -334,7 +361,7 @@ static inline int hrtimer_start_expires(struct hrtimer *timer, static inline int hrtimer_restart(struct hrtimer *timer) { - return hrtimer_start(timer, timer->_expires, HRTIMER_MODE_ABS); + return hrtimer_start_expires(timer, HRTIMER_MODE_ABS); } /* Query timers: */ @@ -391,6 +418,8 @@ extern long hrtimer_nanosleep_restart(struct restart_block *restart_block); extern void hrtimer_init_sleeper(struct hrtimer_sleeper *sl, struct task_struct *tsk); +extern int schedule_hrtimeout_range(ktime_t *expires, unsigned long delta, + const enum hrtimer_mode mode); extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode); /* Soft interrupt function to run the hrtimer queues: */ diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index ae307feec74..01483004183 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -1309,7 +1309,20 @@ void hrtimer_interrupt(struct clock_event_device *dev) timer = rb_entry(node, struct hrtimer, node); - if (basenow.tv64 < hrtimer_get_expires_tv64(timer)) { + /* + * The immediate goal for using the softexpires is + * minimizing wakeups, not running timers at the + * earliest interrupt after their soft expiration. + * This allows us to avoid using a Priority Search + * Tree, which can answer a stabbing querry for + * overlapping intervals and instead use the simple + * BST we already have. + * We don't add extra wakeups by delaying timers that + * are right-of a not yet expired timer, because that + * timer will have to trigger a wakeup anyway. + */ + + if (basenow.tv64 < hrtimer_get_softexpires_tv64(timer)) { ktime_t expires; expires = ktime_sub(hrtimer_get_expires(timer), @@ -1681,14 +1694,20 @@ void __init hrtimers_init(void) } /** - * schedule_hrtimeout - sleep until timeout + * schedule_hrtimeout_range - sleep until timeout * @expires: timeout value (ktime_t) + * @delta: slack in expires timeout (ktime_t) * @mode: timer mode, HRTIMER_MODE_ABS or HRTIMER_MODE_REL * * Make the current task sleep until the given expiry time has * elapsed. The routine will return immediately unless * the current task state has been set (see set_current_state()). * + * The @delta argument gives the kernel the freedom to schedule the + * actual wakeup to a time that is both power and performance friendly. + * The kernel give the normal best effort behavior for "@expires+@delta", + * but may decide to fire the timer earlier, but no earlier than @expires. + * * You can set the task state as follows - * * %TASK_UNINTERRUPTIBLE - at least @timeout time is guaranteed to @@ -1702,7 +1721,7 @@ void __init hrtimers_init(void) * * Returns 0 when the timer has expired otherwise -EINTR */ -int __sched schedule_hrtimeout(ktime_t *expires, +int __sched schedule_hrtimeout_range(ktime_t *expires, unsigned long delta, const enum hrtimer_mode mode) { struct hrtimer_sleeper t; @@ -1726,7 +1745,7 @@ int __sched schedule_hrtimeout(ktime_t *expires, } hrtimer_init_on_stack(&t.timer, CLOCK_MONOTONIC, mode); - hrtimer_set_expires(&t.timer, *expires); + hrtimer_set_expires_range_ns(&t.timer, *expires, delta); hrtimer_init_sleeper(&t, current); @@ -1744,4 +1763,33 @@ int __sched schedule_hrtimeout(ktime_t *expires, return !t.task ? 0 : -EINTR; } +EXPORT_SYMBOL_GPL(schedule_hrtimeout_range); + +/** + * schedule_hrtimeout - sleep until timeout + * @expires: timeout value (ktime_t) + * @mode: timer mode, HRTIMER_MODE_ABS or HRTIMER_MODE_REL + * + * Make the current task sleep until the given expiry time has + * elapsed. The routine will return immediately unless + * the current task state has been set (see set_current_state()). + * + * You can set the task state as follows - + * + * %TASK_UNINTERRUPTIBLE - at least @timeout time is guaranteed to + * pass before the routine returns. + * + * %TASK_INTERRUPTIBLE - the routine may return early if a signal is + * delivered to the current task. + * + * The current task state is guaranteed to be TASK_RUNNING when this + * routine returns. + * + * Returns 0 when the timer has expired otherwise -EINTR + */ +int __sched schedule_hrtimeout(ktime_t *expires, + const enum hrtimer_mode mode) +{ + return schedule_hrtimeout_range(expires, 0, mode); +} EXPORT_SYMBOL_GPL(schedule_hrtimeout); -- cgit v1.2.3 From 6976675d94042fbd446231d1bd8b7de71a980ada Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:52:40 -0700 Subject: hrtimer: create a "timer_slack" field in the task struct We want to be able to control the default "rounding" that is used by select() and poll() and friends. This is a per process property (so that we can have a "nice" like program to start certain programs with a looser or stricter rounding) that can be set/get via a prctl(). For this purpose, a field called "timer_slack_ns" is added to the task struct. In addition, a field called "default_timer_slack"ns" is added so that tasks easily can temporarily to a more/less accurate slack and then back to the default. The default value of the slack is set to 50 usec; this is significantly less than 2.6.27's average select() and poll() timing error but still allows the kernel to group timers somewhat to preserve power behavior. Applications and admins can override this via the prctl() Signed-off-by: Arjan van de Ven --- include/linux/init_task.h | 1 + include/linux/prctl.h | 7 +++++++ include/linux/sched.h | 6 ++++++ kernel/fork.c | 2 ++ kernel/sys.c | 10 ++++++++++ 5 files changed, 26 insertions(+) diff --git a/include/linux/init_task.h b/include/linux/init_task.h index 021d8e720c7..23fd8909b9e 100644 --- a/include/linux/init_task.h +++ b/include/linux/init_task.h @@ -170,6 +170,7 @@ extern struct group_info init_groups; .cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers), \ .fs_excl = ATOMIC_INIT(0), \ .pi_lock = __SPIN_LOCK_UNLOCKED(tsk.pi_lock), \ + .timer_slack_ns = 50000, /* 50 usec default slack */ \ .pids = { \ [PIDTYPE_PID] = INIT_PID_LINK(PIDTYPE_PID), \ [PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID), \ diff --git a/include/linux/prctl.h b/include/linux/prctl.h index 5ad79198d6f..48d887e3c6e 100644 --- a/include/linux/prctl.h +++ b/include/linux/prctl.h @@ -78,4 +78,11 @@ #define PR_GET_SECUREBITS 27 #define PR_SET_SECUREBITS 28 +/* + * Get/set the timerslack as used by poll/select/nanosleep + * A value of 0 means "use default" + */ +#define PR_SET_TIMERSLACK 29 +#define PR_GET_TIMERSLACK 30 + #endif /* _LINUX_PRCTL_H */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 3d9120c5ad1..dcc03fd5a7f 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1301,6 +1301,12 @@ struct task_struct { int latency_record_count; struct latency_record latency_record[LT_SAVECOUNT]; #endif + /* + * time slack values; these are used to round up poll() and + * select() etc timeout values. These are in nanoseconds. + */ + unsigned long timer_slack_ns; + unsigned long default_timer_slack_ns; }; /* diff --git a/kernel/fork.c b/kernel/fork.c index 7ce2ebe8479..4308d75f0fa 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -987,6 +987,8 @@ static struct task_struct *copy_process(unsigned long clone_flags, p->prev_utime = cputime_zero; p->prev_stime = cputime_zero; + p->default_timer_slack_ns = current->timer_slack_ns; + #ifdef CONFIG_DETECT_SOFTLOCKUP p->last_switch_count = 0; p->last_switch_timestamp = 0; diff --git a/kernel/sys.c b/kernel/sys.c index 038a7bc0901..1b96401a057 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -1727,6 +1727,16 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, case PR_SET_TSC: error = SET_TSC_CTL(arg2); break; + case PR_GET_TIMERSLACK: + error = current->timer_slack_ns; + break; + case PR_SET_TIMERSLACK: + if (arg2 <= 0) + current->timer_slack_ns = + current->default_timer_slack_ns; + else + current->timer_slack_ns = arg2; + break; default: error = -EINVAL; break; -- cgit v1.2.3 From 90d6e24a3686325edea7748b966e138c9923017d Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 1 Sep 2008 15:55:35 -0700 Subject: hrtimer: make select() and poll() use the hrtimer range feature This patch makes the select() and poll() hrtimers use the new range feature and settings from the task struct. In addition, this includes the estimate_accuracy() function that Linus posted to lkml, but changed entirely based on other peoples lkml feedback. Signed-off-by: Arjan van de Ven --- fs/select.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/fs/select.c b/fs/select.c index f6dceb56793..5e61b43d076 100644 --- a/fs/select.c +++ b/fs/select.c @@ -28,6 +28,58 @@ #include + +/* + * Estimate expected accuracy in ns from a timeval. + * + * After quite a bit of churning around, we've settled on + * a simple thing of taking 0.1% of the timeout as the + * slack, with a cap of 100 msec. + * "nice" tasks get a 0.5% slack instead. + * + * Consider this comment an open invitation to come up with even + * better solutions.. + */ + +static unsigned long __estimate_accuracy(struct timespec *tv) +{ + unsigned long slack; + int divfactor = 1000; + + if (task_nice(current)) + divfactor = divfactor / 5; + + slack = tv->tv_nsec / divfactor; + slack += tv->tv_sec * (NSEC_PER_SEC/divfactor); + + if (slack > 100 * NSEC_PER_MSEC) + slack = 100 * NSEC_PER_MSEC; + return slack; +} + +static unsigned long estimate_accuracy(struct timespec *tv) +{ + unsigned long ret; + struct timespec now; + + /* + * Realtime tasks get a slack of 0 for obvious reasons. + */ + + if (current->policy == SCHED_FIFO || + current->policy == SCHED_RR) + return 0; + + ktime_get_ts(&now); + now = timespec_sub(*tv, now); + ret = __estimate_accuracy(&now); + if (ret < current->timer_slack_ns) + return current->timer_slack_ns; + return ret; +} + + + struct poll_table_page { struct poll_table_page * next; struct poll_table_entry * entry; @@ -262,6 +314,7 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time) struct poll_wqueues table; poll_table *wait; int retval, i, timed_out = 0; + unsigned long slack = 0; rcu_read_lock(); retval = max_select_fd(n, fds); @@ -278,6 +331,9 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time) timed_out = 1; } + if (end_time) + slack = estimate_accuracy(end_time); + retval = 0; for (;;) { unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; @@ -353,7 +409,7 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time) to = &expire; } - if (!schedule_hrtimeout(to, HRTIMER_MODE_ABS)) + if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) timed_out = 1; } __set_current_state(TASK_RUNNING); @@ -593,6 +649,7 @@ static int do_poll(unsigned int nfds, struct poll_list *list, poll_table* pt = &wait->pt; ktime_t expire, *to = NULL; int timed_out = 0, count = 0; + unsigned long slack = 0; /* Optimise the no-wait case */ if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { @@ -600,6 +657,9 @@ static int do_poll(unsigned int nfds, struct poll_list *list, timed_out = 1; } + if (end_time) + slack = estimate_accuracy(end_time); + for (;;) { struct poll_list *walk; @@ -646,7 +706,7 @@ static int do_poll(unsigned int nfds, struct poll_list *list, to = &expire; } - if (!schedule_hrtimeout(to, HRTIMER_MODE_ABS)) + if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) timed_out = 1; } __set_current_state(TASK_RUNNING); -- cgit v1.2.3 From 584fb4a76413ec9215741e075e0dfb69173b213f Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sat, 6 Sep 2008 08:32:57 -0700 Subject: hrtimer: fix build bug found by Ingo in some randconfig configurations, hrtimers are used even though the hrtimer config if off; and it broke the build due to some of the new functions being on the wrong side of the ifdef. This patch moves the functions to the other side of the ifdef, fixing the build bug. Signed-off-by: Arjan van de Ven --- include/linux/hrtimer.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 28259c33667..c407b33ef84 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -198,13 +198,6 @@ struct hrtimer_cpu_base { #endif }; -#ifdef CONFIG_HIGH_RES_TIMERS -struct clock_event_device; - -extern void clock_was_set(void); -extern void hres_timers_resume(void); -extern void hrtimer_interrupt(struct clock_event_device *dev); - /* * In high resolution mode the time reference must be read accurate */ @@ -283,6 +276,13 @@ static inline ktime_t hrtimer_expires_remaining(const struct hrtimer *timer) return ktime_sub(timer->_expires, timer->base->get_time()); } +#ifdef CONFIG_HIGH_RES_TIMERS +struct clock_event_device; + +extern void clock_was_set(void); +extern void hres_timers_resume(void); +extern void hrtimer_interrupt(struct clock_event_device *dev); + /* * The resolution of the clocks. The resolution value is returned in * the clock_getres() system call to give application programmers an -- cgit v1.2.3 From 2ec02270c00f94b08fddfb68c37510a9fb47ac7c Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sat, 6 Sep 2008 09:36:56 -0700 Subject: hrtimer: another build fix More randconfig testing Signed-off-by: Arjan van de Ven --- include/linux/hrtimer.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index c407b33ef84..4c1a834b984 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -198,19 +198,6 @@ struct hrtimer_cpu_base { #endif }; -/* - * In high resolution mode the time reference must be read accurate - */ -static inline ktime_t hrtimer_cb_get_time(struct hrtimer *timer) -{ - return timer->base->get_time(); -} - -static inline int hrtimer_is_hres_active(struct hrtimer *timer) -{ - return timer->base->cpu_base->hres_active; -} - static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time) { timer->_expires = time; @@ -283,6 +270,19 @@ extern void clock_was_set(void); extern void hres_timers_resume(void); extern void hrtimer_interrupt(struct clock_event_device *dev); +/* + * In high resolution mode the time reference must be read accurate + */ +static inline ktime_t hrtimer_cb_get_time(struct hrtimer *timer) +{ + return timer->base->get_time(); +} + +static inline int hrtimer_is_hres_active(struct hrtimer *timer) +{ + return timer->base->cpu_base->hres_active; +} + /* * The resolution of the clocks. The resolution value is returned in * the clock_getres() system call to give application programmers an -- cgit v1.2.3 From da8f2e170ea94cc20f8ebbc8ee8d127edb8f12f1 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sun, 7 Sep 2008 10:47:46 -0700 Subject: hrtimer: add a hrtimer_start_range() function this patch adds a _range version of hrtimer_start() so that range timers can be created; the hrtimer_start() function is just a wrapper around this. In addition, hrtimer_start_expires() will now preserve existing ranges. Signed-off-by: Arjan van de Ven --- include/linux/hrtimer.h | 9 ++++++++- kernel/hrtimer.c | 26 +++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 4c1a834b984..1c0473e8ecb 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -350,13 +350,20 @@ static inline void destroy_hrtimer_on_stack(struct hrtimer *timer) { } /* Basic timer operations: */ extern int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode); +extern int hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim, + unsigned long range_ns, const enum hrtimer_mode mode); extern int hrtimer_cancel(struct hrtimer *timer); extern int hrtimer_try_to_cancel(struct hrtimer *timer); static inline int hrtimer_start_expires(struct hrtimer *timer, enum hrtimer_mode mode) { - return hrtimer_start(timer, hrtimer_get_expires(timer), mode); + unsigned long delta; + ktime_t soft, hard; + soft = hrtimer_get_softexpires(timer); + hard = hrtimer_get_expires(timer); + delta = ktime_to_ns(ktime_sub(hard, soft)); + return hrtimer_start_range_ns(timer, hrtimer_get_expires(timer), delta, mode); } static inline int hrtimer_restart(struct hrtimer *timer) diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index 01483004183..a0222097c57 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -945,9 +945,10 @@ remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base) } /** - * hrtimer_start - (re)start an relative timer on the current CPU + * hrtimer_start_range_ns - (re)start an relative timer on the current CPU * @timer: the timer to be added * @tim: expiry time + * @delta_ns: "slack" range for the timer * @mode: expiry mode: absolute (HRTIMER_ABS) or relative (HRTIMER_REL) * * Returns: @@ -955,7 +956,8 @@ remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base) * 1 when the timer was active */ int -hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode) +hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim, unsigned long delta_ns, + const enum hrtimer_mode mode) { struct hrtimer_clock_base *base, *new_base; unsigned long flags; @@ -983,7 +985,7 @@ hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode) #endif } - hrtimer_set_expires(timer, tim); + hrtimer_set_expires_range_ns(timer, tim, delta_ns); timer_stats_hrtimer_set_start_info(timer); @@ -1016,8 +1018,26 @@ hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode) return ret; } +EXPORT_SYMBOL_GPL(hrtimer_start_range_ns); + +/** + * hrtimer_start - (re)start an relative timer on the current CPU + * @timer: the timer to be added + * @tim: expiry time + * @mode: expiry mode: absolute (HRTIMER_ABS) or relative (HRTIMER_REL) + * + * Returns: + * 0 on success + * 1 when the timer was active + */ +int +hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode) +{ + return hrtimer_start_range_ns(timer, tim, 0, mode); +} EXPORT_SYMBOL_GPL(hrtimer_start); + /** * hrtimer_try_to_cancel - try to deactivate a timer * @timer: hrtimer to stop -- cgit v1.2.3 From 4ce105d30e08fb8a1783c55a0e48aa3fa200c455 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sun, 7 Sep 2008 15:31:39 -0700 Subject: hrtimer: incorporate feedback from Peter Zijlstra (based on lkml review) * use rt_task() * task_nice() has a sign Signed-off-by: Arjan van de Ven --- fs/select.c | 5 ++--- include/linux/hrtimer.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/select.c b/fs/select.c index 5e61b43d076..fdd8584e536 100644 --- a/fs/select.c +++ b/fs/select.c @@ -46,7 +46,7 @@ static unsigned long __estimate_accuracy(struct timespec *tv) unsigned long slack; int divfactor = 1000; - if (task_nice(current)) + if (task_nice(current) > 0) divfactor = divfactor / 5; slack = tv->tv_nsec / divfactor; @@ -66,8 +66,7 @@ static unsigned long estimate_accuracy(struct timespec *tv) * Realtime tasks get a slack of 0 for obvious reasons. */ - if (current->policy == SCHED_FIFO || - current->policy == SCHED_RR) + if (rt_task(current)) return 0; ktime_get_ts(&now); diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 1c0473e8ecb..95db11f62ff 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -363,7 +363,7 @@ static inline int hrtimer_start_expires(struct hrtimer *timer, soft = hrtimer_get_softexpires(timer); hard = hrtimer_get_expires(timer); delta = ktime_to_ns(ktime_sub(hard, soft)); - return hrtimer_start_range_ns(timer, hrtimer_get_expires(timer), delta, mode); + return hrtimer_start_range_ns(timer, soft, delta, mode); } static inline int hrtimer_restart(struct hrtimer *timer) -- cgit v1.2.3 From 704af52bd13a5d9f3c60c496c68e752fafdfb434 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sun, 7 Sep 2008 16:10:20 -0700 Subject: hrtimer: show the timer ranges in /proc/timer_list to help debugging and visibility of timer ranges, show them in the existing timer list in /proc/timer_list Signed-off-by: Arjan van de Ven --- kernel/time/timer_list.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index 5224a3215fb..122ee751d2d 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -65,8 +65,10 @@ print_timer(struct seq_file *m, struct hrtimer *timer, int idx, u64 now) SEQ_printf(m, ", %s/%d", tmp, timer->start_pid); #endif SEQ_printf(m, "\n"); - SEQ_printf(m, " # expires at %Lu nsecs [in %Ld nsecs]\n", + SEQ_printf(m, " # expires at %Lu-%Lu nsecs [in %Ld to %Ld nsecs]\n", + (unsigned long long)ktime_to_ns(hrtimer_get_softexpires(timer)), (unsigned long long)ktime_to_ns(hrtimer_get_expires(timer)), + (long long)(ktime_to_ns(hrtimer_get_softexpires(timer)) - now), (long long)(ktime_to_ns(hrtimer_get_expires(timer)) - now)); } -- cgit v1.2.3 From 96d2ab484e7a9bafdab44b8c7d1ef5944319b18c Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sun, 7 Sep 2008 16:08:55 -0700 Subject: hrtimer: fix signed/unsigned bug in slack estimator the slack estimator used unsigned math; however for very short delay it's possible that by the time you calculate the timeout, it's already passed and you get a negative time/slack... in an unsigned variable... which then gets turned into a 100 msec delay rather than zero. This patch fixes this by using a signed typee in the right places. Signed-off-by: Arjan van de Ven --- fs/select.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/fs/select.c b/fs/select.c index fdd8584e536..448e4400128 100644 --- a/fs/select.c +++ b/fs/select.c @@ -41,9 +41,9 @@ * better solutions.. */ -static unsigned long __estimate_accuracy(struct timespec *tv) +static long __estimate_accuracy(struct timespec *tv) { - unsigned long slack; + long slack; int divfactor = 1000; if (task_nice(current) > 0) @@ -54,10 +54,13 @@ static unsigned long __estimate_accuracy(struct timespec *tv) if (slack > 100 * NSEC_PER_MSEC) slack = 100 * NSEC_PER_MSEC; + + if (slack < 0) + slack = 0; return slack; } -static unsigned long estimate_accuracy(struct timespec *tv) +static long estimate_accuracy(struct timespec *tv) { unsigned long ret; struct timespec now; @@ -330,7 +333,7 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time) timed_out = 1; } - if (end_time) + if (end_time && !timed_out) slack = estimate_accuracy(end_time); retval = 0; @@ -656,7 +659,7 @@ static int do_poll(unsigned int nfds, struct poll_list *list, timed_out = 1; } - if (end_time) + if (end_time && !timed_out) slack = estimate_accuracy(end_time); for (;;) { -- cgit v1.2.3 From 3bd012060f962567aadb52b27b2fc8fdc91102c7 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 8 Sep 2008 08:58:59 -0700 Subject: hrtimer: make the nanosleep() syscall use the per process slack This patch makes the nanosleep() system call use the per process slack value; with this users are able to externally control existing applications to reduce the wakeup rate. Signed-off-by: Arjan van de Ven --- kernel/hrtimer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index a0222097c57..9a4c9018556 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -1563,9 +1563,14 @@ long hrtimer_nanosleep(struct timespec *rqtp, struct timespec __user *rmtp, struct restart_block *restart; struct hrtimer_sleeper t; int ret = 0; + unsigned long slack; + + slack = current->timer_slack_ns; + if (rt_task(current)) + slack = 0; hrtimer_init_on_stack(&t.timer, clockid, mode); - hrtimer_set_expires(&t.timer, timespec_to_ktime(*rqtp)); + hrtimer_set_expires_range_ns(&t.timer, timespec_to_ktime(*rqtp), slack); if (do_nanosleep(&t, mode)) goto out; -- cgit v1.2.3 From ae4b748e81b7e366f04f55229d5e372e372c33af Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 8 Sep 2008 09:03:57 -0700 Subject: hrtimer: make the futex() system call use the per process slack value This patch makes the futex() system call use the per process slack value; with this users are able to externally control existing applications to reduce the wakeup rate. Signed-off-by: Arjan van de Ven --- kernel/futex.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/futex.c b/kernel/futex.c index 4cd5b4319b0..8af10027514 100644 --- a/kernel/futex.c +++ b/kernel/futex.c @@ -1296,10 +1296,14 @@ static int futex_wait(u32 __user *uaddr, struct rw_semaphore *fshared, if (!abs_time) schedule(); else { + unsigned long slack; + slack = current->timer_slack_ns; + if (rt_task(current)) + slack = 0; hrtimer_init_on_stack(&t.timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); hrtimer_init_sleeper(&t, current); - hrtimer_set_expires(&t.timer, *abs_time); + hrtimer_set_expires_range_ns(&t.timer, *abs_time, slack); hrtimer_start_expires(&t.timer, HRTIMER_MODE_ABS); if (!hrtimer_active(&t.timer)) -- cgit v1.2.3 From 2e94d1f71f7e4404d997e6fb4f1618aa147d76f9 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Wed, 10 Sep 2008 16:06:00 -0700 Subject: hrtimer: peek at the timer queue just before going idle As part of going idle, we already look at the time of the next timer event to determine which C-state to select etc. This patch adds functionality that causes the timers that are past their soft expire time, to fire at this time, before we calculate the next wakeup time. This functionality will thus avoid wakeups by running timers before going idle rather than specially waking up for it. Signed-off-by: Arjan van de Ven --- drivers/cpuidle/cpuidle.c | 7 +++++++ include/linux/hrtimer.h | 5 +++++ kernel/hrtimer.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index 5ce07b517c5..2e314849936 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "cpuidle.h" @@ -60,6 +61,12 @@ static void cpuidle_idle_call(void) return; } + /* + * run any timers that can be run now, at this point + * before calculating the idle duration etc. + */ + hrtimer_peek_ahead_timers(); + /* ask the governor for the next state */ next_state = cpuidle_curr_governor->select(dev); if (need_resched()) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 95db11f62ff..d93b1e1dc16 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -326,6 +326,11 @@ static inline int hrtimer_is_hres_active(struct hrtimer *timer) extern ktime_t ktime_get(void); extern ktime_t ktime_get_real(void); + +DECLARE_PER_CPU(struct tick_device, tick_cpu_device); +extern void hrtimer_peek_ahead_timers(void); + + /* Exported timer functions: */ /* Initialize timers: */ diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index 9a4c9018556..eb2cf984959 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -1381,6 +1381,36 @@ void hrtimer_interrupt(struct clock_event_device *dev) raise_softirq(HRTIMER_SOFTIRQ); } +/** + * hrtimer_peek_ahead_timers -- run soft-expired timers now + * + * hrtimer_peek_ahead_timers will peek at the timer queue of + * the current cpu and check if there are any timers for which + * the soft expires time has passed. If any such timers exist, + * they are run immediately and then removed from the timer queue. + * + */ +void hrtimer_peek_ahead_timers(void) +{ + unsigned long flags; + struct tick_device *td; + struct clock_event_device *dev; + + if (hrtimer_hres_active()) + return; + + local_irq_save(flags); + td = &__get_cpu_var(tick_cpu_device); + if (!td) + goto out; + dev = td->evtdev; + if (!dev) + goto out; + hrtimer_interrupt(dev); +out: + local_irq_restore(flags); +} + static void run_hrtimer_softirq(struct softirq_action *h) { run_hrtimer_pending(&__get_cpu_var(hrtimer_bases)); -- cgit v1.2.3 From ccbe329bcd87924baed96474ec0a6725e3957897 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:03 +0100 Subject: bitmap: add bitmap_copy_le() bitmap_copy_le() copies a bitmap, putting the bits into little-endian order (i.e., each unsigned long word in the bitmap is put into little-endian order). The UWB stack used bitmaps to manage Medium Access Slot availability, and these bitmaps need to be written to the hardware in LE order. Signed-off-by: David Vrabel --- include/linux/bitmap.h | 1 + lib/bitmap.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h index 89781fd4885..5379913aca5 100644 --- a/include/linux/bitmap.h +++ b/include/linux/bitmap.h @@ -130,6 +130,7 @@ extern void bitmap_fold(unsigned long *dst, const unsigned long *orig, extern int bitmap_find_free_region(unsigned long *bitmap, int bits, int order); extern void bitmap_release_region(unsigned long *bitmap, int pos, int order); extern int bitmap_allocate_region(unsigned long *bitmap, int pos, int order); +extern void bitmap_copy_le(void *dst, const unsigned long *src, int nbits); #define BITMAP_LAST_WORD_MASK(nbits) \ ( \ diff --git a/lib/bitmap.c b/lib/bitmap.c index 06fb57c86de..c2006bfeea4 100644 --- a/lib/bitmap.c +++ b/lib/bitmap.c @@ -1007,3 +1007,25 @@ int bitmap_allocate_region(unsigned long *bitmap, int pos, int order) return 0; } EXPORT_SYMBOL(bitmap_allocate_region); + +/** + * bitmap_copy_le - copy a bitmap, putting the bits into little-endian order. + * @dst: destination buffer + * @src: bitmap to copy + * @nbits: number of bits in the bitmap + * + * Require nbits % BITS_PER_LONG == 0. + */ +void bitmap_copy_le(void *dst, const unsigned long *src, int nbits) +{ + unsigned long *d = dst; + int i; + + for (i = 0; i < nbits/BITS_PER_LONG; i++) { + if (BITS_PER_LONG == 64) + d[i] = cpu_to_le64(src[i]); + else + d[i] = cpu_to_le32(src[i]); + } +} +EXPORT_SYMBOL(bitmap_copy_le); -- cgit v1.2.3 From 99d368bc9e279a2a5e56f3afe32166260e90caa7 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:04 +0100 Subject: uwb: add initial documentation Documentation (and example utilities) for the UWB (and WUSB) stacks. Some of the documentation may be out-of-date. Signed-off-by: David Vrabel --- Documentation/usb/WUSB-Design-overview.txt | 448 +++++++++++++++++++++++++++++ Documentation/usb/wusb-cbaf | 133 +++++++++ 2 files changed, 581 insertions(+) create mode 100644 Documentation/usb/WUSB-Design-overview.txt create mode 100644 Documentation/usb/wusb-cbaf diff --git a/Documentation/usb/WUSB-Design-overview.txt b/Documentation/usb/WUSB-Design-overview.txt new file mode 100644 index 00000000000..4c3d62c7843 --- /dev/null +++ b/Documentation/usb/WUSB-Design-overview.txt @@ -0,0 +1,448 @@ + +Linux UWB + Wireless USB + WiNET + + (C) 2005-2006 Intel Corporation + Inaky Perez-Gonzalez + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License version + 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + +Please visit http://bughost.org/thewiki/Design-overview.txt-1.8 for +updated content. + + * Design-overview.txt-1.8 + +This code implements a Ultra Wide Band stack for Linux, as well as +drivers for the the USB based UWB radio controllers defined in the +Wireless USB 1.0 specification (including Wireless USB host controller +and an Intel WiNET controller). + + 1. Introduction + 1. HWA: Host Wire adapters, your Wireless USB dongle + + 2. DWA: Device Wired Adaptor, a Wireless USB hub for wired + devices + 3. WHCI: Wireless Host Controller Interface, the PCI WUSB host + adapter + 2. The UWB stack + 1. Devices and hosts: the basic structure + + 2. Host Controller life cycle + + 3. On the air: beacons and enumerating the radio neighborhood + + 4. Device lists + 5. Bandwidth allocation + + 3. Wireless USB Host Controller drivers + + 4. Glossary + + + Introduction + +UWB is a wide-band communication protocol that is to serve also as the +low-level protocol for others (much like TCP sits on IP). Currently +these others are Wireless USB and TCP/IP, but seems Bluetooth and +Firewire/1394 are coming along. + +UWB uses a band from roughly 3 to 10 GHz, transmitting at a max of +~-41dB (or 0.074 uW/MHz--geography specific data is still being +negotiated w/ regulators, so watch for changes). That band is divided in +a bunch of ~1.5 GHz wide channels (or band groups) composed of three +subbands/subchannels (528 MHz each). Each channel is independent of each +other, so you could consider them different "busses". Initially this +driver considers them all a single one. + +Radio time is divided in 65536 us long /superframes/, each one divided +in 256 256us long /MASs/ (Media Allocation Slots), which are the basic +time/media allocation units for transferring data. At the beginning of +each superframe there is a Beacon Period (BP), where every device +transmit its beacon on a single MAS. The length of the BP depends on how +many devices are present and the length of their beacons. + +Devices have a MAC (fixed, 48 bit address) and a device (changeable, 16 +bit address) and send periodic beacons to advertise themselves and pass +info on what they are and do. They advertise their capabilities and a +bunch of other stuff. + +The different logical parts of this driver are: + + * + + *UWB*: the Ultra-Wide-Band stack -- manages the radio and + associated spectrum to allow for devices sharing it. Allows to + control bandwidth assingment, beaconing, scanning, etc + + * + + *WUSB*: the layer that sits on top of UWB to provide Wireless USB. + The Wireless USB spec defines means to control a UWB radio and to + do the actual WUSB. + + + HWA: Host Wire adapters, your Wireless USB dongle + +WUSB also defines a device called a Host Wire Adaptor (HWA), which in +mere terms is a USB dongle that enables your PC to have UWB and Wireless +USB. The Wireless USB Host Controller in a HWA looks to the host like a +[Wireless] USB controller connected via USB (!) + +The HWA itself is broken in two or three main interfaces: + + * + + *RC*: Radio control -- this implements an interface to the + Ultra-Wide-Band radio controller. The driver for this implements a + USB-based UWB Radio Controller to the UWB stack. + + * + + *HC*: the wireless USB host controller. It looks like a USB host + whose root port is the radio and the WUSB devices connect to it. + To the system it looks like a separate USB host. The driver (will) + implement a USB host controller (similar to UHCI, OHCI or EHCI) + for which the root hub is the radio...To reiterate: it is a USB + controller that is connected via USB instead of PCI. + + * + + *WINET*: some HW provide a WiNET interface (IP over UWB). This + package provides a driver for it (it looks like a network + interface, winetX). The driver detects when there is a link up for + their type and kick into gear. + + + DWA: Device Wired Adaptor, a Wireless USB hub for wired devices + +These are the complement to HWAs. They are a USB host for connecting +wired devices, but it is connected to your PC connected via Wireless +USB. To the system it looks like yet another USB host. To the untrained +eye, it looks like a hub that connects upstream wirelessly. + +We still offer no support for this; however, it should share a lot of +code with the HWA-RC driver; there is a bunch of factorization work that +has been done to support that in upcoming releases. + + + WHCI: Wireless Host Controller Interface, the PCI WUSB host adapter + +This is your usual PCI device that implements WHCI. Similar in concept +to EHCI, it allows your wireless USB devices (including DWAs) to connect +to your host via a PCI interface. As in the case of the HWA, it has a +Radio Control interface and the WUSB Host Controller interface per se. + +There is still no driver support for this, but will be in upcoming +releases. + + + The UWB stack + +The main mission of the UWB stack is to keep a tally of which devices +are in radio proximity to allow drivers to connect to them. As well, it +provides an API for controlling the local radio controllers (RCs from +now on), such as to start/stop beaconing, scan, allocate bandwidth, etc. + + + Devices and hosts: the basic structure + +The main building block here is the UWB device (struct uwb_dev). For +each device that pops up in radio presence (ie: the UWB host receives a +beacon from it) you get a struct uwb_dev that will show up in +/sys/class/uwb and in /sys/bus/uwb/devices. + +For each RC that is detected, a new struct uwb_rc is created. In turn, a +RC is also a device, so they also show in /sys/class/uwb and +/sys/bus/uwb/devices, but at the same time, only radio controllers show +up in /sys/class/uwb_rc. + + * + + [*] The reason for RCs being also devices is that not only we can + see them while enumerating the system device tree, but also on the + radio (their beacons and stuff), so the handling has to be + likewise to that of a device. + +Each RC driver is implemented by a separate driver that plugs into the +interface that the UWB stack provides through a struct uwb_rc_ops. The +spec creators have been nice enough to make the message format the same +for HWA and WHCI RCs, so the driver is really a very thin transport that +moves the requests from the UWB API to the device [/uwb_rc_ops->cmd()/] +and sends the replies and notifications back to the API +[/uwb_rc_neh_grok()/]. Notifications are handled to the UWB daemon, that +is chartered, among other things, to keep the tab of how the UWB radio +neighborhood looks, creating and destroying devices as they show up or +dissapear. + +Command execution is very simple: a command block is sent and a event +block or reply is expected back. For sending/receiving command/events, a +handle called /neh/ (Notification/Event Handle) is opened with +/uwb_rc_neh_open()/. + +The HWA-RC (USB dongle) driver (drivers/uwb/hwa-rc.c) does this job for +the USB connected HWA. Eventually, drivers/whci-rc.c will do the same +for the PCI connected WHCI controller. + + + Host Controller life cycle + +So let's say we connect a dongle to the system: it is detected and +firmware uploaded if needed [for Intel's i1480 +/drivers/uwb/ptc/usb.c:ptc_usb_probe()/] and then it is reenumerated. +Now we have a real HWA device connected and +/drivers/uwb/hwa-rc.c:hwarc_probe()/ picks it up, that will set up the +Wire-Adaptor environment and then suck it into the UWB stack's vision of +the world [/drivers/uwb/lc-rc.c:uwb_rc_add()/]. + + * + + [*] The stack should put a new RC to scan for devices + [/uwb_rc_scan()/] so it finds what's available around and tries to + connect to them, but this is policy stuff and should be driven + from user space. As of now, the operator is expected to do it + manually; see the release notes for documentation on the procedure. + +When a dongle is disconnected, /drivers/uwb/hwa-rc.c:hwarc_disconnect()/ +takes time of tearing everything down safely (or not...). + + + On the air: beacons and enumerating the radio neighborhood + +So assuming we have devices and we have agreed for a channel to connect +on (let's say 9), we put the new RC to beacon: + + * + + $ echo 9 0 > /sys/class/uwb_rc/uwb0/beacon + +Now it is visible. If there were other devices in the same radio channel +and beacon group (that's what the zero is for), the dongle's radio +control interface will send beacon notifications on its +notification/event endpoint (NEEP). The beacon notifications are part of +the event stream that is funneled into the API with +/drivers/uwb/neh.c:uwb_rc_neh_grok()/ and delivered to the UWBD, the UWB +daemon through a notification list. + +UWBD wakes up and scans the event list; finds a beacon and adds it to +the BEACON CACHE (/uwb_beca/). If he receives a number of beacons from +the same device, he considers it to be 'onair' and creates a new device +[/drivers/uwb/lc-dev.c:uwbd_dev_onair()/]. Similarly, when no beacons +are received in some time, the device is considered gone and wiped out +[uwbd calls periodically /uwb/beacon.c:uwb_beca_purge()/ that will purge +the beacon cache of dead devices]. + + + Device lists + +All UWB devices are kept in the list of the struct bus_type uwb_bus. + + + Bandwidth allocation + +The UWB stack maintains a local copy of DRP availability through +processing of incoming *DRP Availability Change* notifications. This +local copy is currently used to present the current bandwidth +availability to the user through the sysfs file +/sys/class/uwb_rc/uwbx/bw_avail. In the future the bandwidth +availability information will be used by the bandwidth reservation +routines. + +The bandwidth reservation routines are in progress and are thus not +present in the current release. When completed they will enable a user +to initiate DRP reservation requests through interaction with sysfs. DRP +reservation requests from remote UWB devices will also be handled. The +bandwidth management done by the UWB stack will include callbacks to the +higher layers will enable the higher layers to use the reservations upon +completion. [Note: The bandwidth reservation work is in progress and +subject to change.] + + + Wireless USB Host Controller drivers + +*WARNING* This section needs a lot of work! + +As explained above, there are three different types of HCs in the WUSB +world: HWA-HC, DWA-HC and WHCI-HC. + +HWA-HC and DWA-HC share that they are Wire-Adapters (USB or WUSB +connected controllers), and their transfer management system is almost +identical. So is their notification delivery system. + +HWA-HC and WHCI-HC share that they are both WUSB host controllers, so +they have to deal with WUSB device life cycle and maintenance, wireless +root-hub + +HWA exposes a Host Controller interface (HWA-HC 0xe0/02/02). This has +three endpoints (Notifications, Data Transfer In and Data Transfer +Out--known as NEP, DTI and DTO in the code). + +We reserve UWB bandwidth for our Wireless USB Cluster, create a Cluster +ID and tell the HC to use all that. Then we start it. This means the HC +starts sending MMCs. + + * + + The MMCs are blocks of data defined somewhere in the WUSB1.0 spec + that define a stream in the UWB channel time allocated for sending + WUSB IEs (host to device commands/notifications) and Device + Notifications (device initiated to host). Each host defines a + unique Wireless USB cluster through MMCs. Devices can connect to a + single cluster at the time. The IEs are Information Elements, and + among them are the bandwidth allocations that tell each device + when can they transmit or receive. + +Now it all depends on external stimuli. + +*New device connection* + +A new device pops up, it scans the radio looking for MMCs that give out +the existence of Wireless USB channels. Once one (or more) are found, +selects which one to connect to. Sends a /DN_Connect/ (device +notification connect) during the DNTS (Device Notification Time +Slot--announced in the MMCs + +HC picks the /DN_Connect/ out (nep module sends to notif.c for delivery +into /devconnect/). This process starts the authentication process for +the device. First we allocate a /fake port/ and assign an +unauthenticated address (128 to 255--what we really do is +0x80 | fake_port_idx). We fiddle with the fake port status and /khubd/ +sees a new connection, so he moves on to enable the fake port with a reset. + +So now we are in the reset path -- we know we have a non-yet enumerated +device with an unauthorized address; we ask user space to authenticate +(FIXME: not yet done, similar to bluetooth pairing), then we do the key +exchange (FIXME: not yet done) and issue a /set address 0/ to bring the +device to the default state. Device is authenticated. + +From here, the USB stack takes control through the usb_hcd ops. khubd +has seen the port status changes, as we have been toggling them. It will +start enumerating and doing transfers through usb_hcd->urb_enqueue() to +read descriptors and move our data. + +*Device life cycle and keep alives* + +Everytime there is a succesful transfer to/from a device, we update a +per-device activity timestamp. If not, every now and then we check and +if the activity timestamp gets old, we ping the device by sending it a +Keep Alive IE; it responds with a /DN_Alive/ pong during the DNTS (this +arrives to us as a notification through +devconnect.c:wusb_handle_dn_alive(). If a device times out, we +disconnect it from the system (cleaning up internal information and +toggling the bits in the fake hub port, which kicks khubd into removing +the rest of the stuff). + +This is done through devconnect:__wusb_check_devs(), which will scan the +device list looking for whom needs refreshing. + +If the device wants to disconnect, it will either die (ugly) or send a +/DN_Disconnect/ that will prompt a disconnection from the system. + +*Sending and receiving data* + +Data is sent and received through /Remote Pipes/ (rpipes). An rpipe is +/aimed/ at an endpoint in a WUSB device. This is the same for HWAs and +DWAs. + +Each HC has a number of rpipes and buffers that can be assigned to them; +when doing a data transfer (xfer), first the rpipe has to be aimed and +prepared (buffers assigned), then we can start queueing requests for +data in or out. + +Data buffers have to be segmented out before sending--so we send first a +header (segment request) and then if there is any data, a data buffer +immediately after to the DTI interface (yep, even the request). If our +buffer is bigger than the max segment size, then we just do multiple +requests. + +[This sucks, because doing USB scatter gatter in Linux is resource +intensive, if any...not that the current approach is not. It just has to +be cleaned up a lot :)]. + +If reading, we don't send data buffers, just the segment headers saying +we want to read segments. + +When the xfer is executed, we receive a notification that says data is +ready in the DTI endpoint (handled through +xfer.c:wa_handle_notif_xfer()). In there we read from the DTI endpoint a +descriptor that gives us the status of the transfer, its identification +(given when we issued it) and the segment number. If it was a data read, +we issue another URB to read into the destination buffer the chunk of +data coming out of the remote endpoint. Done, wait for the next guy. The +callbacks for the URBs issued from here are the ones that will declare +the xfer complete at some point and call it's callback. + +Seems simple, but the implementation is not trivial. + + * + + *WARNING* Old!! + +The main xfer descriptor, wa_xfer (equivalent to a URB) contains an +array of segments, tallys on segments and buffers and callback +information. Buried in there is a lot of URBs for executing the segments +and buffer transfers. + +For OUT xfers, there is an array of segments, one URB for each, another +one of buffer URB. When submitting, we submit URBs for segment request +1, buffer 1, segment 2, buffer 2...etc. Then we wait on the DTI for xfer +result data; when all the segments are complete, we call the callback to +finalize the transfer. + +For IN xfers, we only issue URBs for the segments we want to read and +then wait for the xfer result data. + +*URB mapping into xfers* + +This is done by hwahc_op_urb_[en|de]queue(). In enqueue() we aim an +rpipe to the endpoint where we have to transmit, create a transfer +context (wa_xfer) and submit it. When the xfer is done, our callback is +called and we assign the status bits and release the xfer resources. + +In dequeue() we are basically cancelling/aborting the transfer. We issue +a xfer abort request to the HC, cancell all the URBs we had submitted +and not yet done and when all that is done, the xfer callback will be +called--this will call the URB callback. + + + Glossary + +*DWA* -- Device Wire Adapter + +USB host, wired for downstream devices, upstream connects wirelessly +with Wireless USB. + +*EVENT* -- Response to a command on the NEEP + +*HWA* -- Host Wire Adapter / USB dongle for UWB and Wireless USB + +*NEH* -- Notification/Event Handle + +Handle/file descriptor for receiving notifications or events. The WA +code requires you to get one of this to listen for notifications or +events on the NEEP. + +*NEEP* -- Notification/Event EndPoint + +Stuff related to the management of the first endpoint of a HWA USB +dongle that is used to deliver an stream of events and notifications to +the host. + +*NOTIFICATION* -- Message coming in the NEEP as response to something. + +*RC* -- Radio Control + +Design-overview.txt-1.8 (last edited 2006-11-04 12:22:24 by +InakyPerezGonzalez) + diff --git a/Documentation/usb/wusb-cbaf b/Documentation/usb/wusb-cbaf new file mode 100644 index 00000000000..a385478ba12 --- /dev/null +++ b/Documentation/usb/wusb-cbaf @@ -0,0 +1,133 @@ +#! /bin/bash +# + +set -e + +progname=$(basename $0) +function help +{ + cat < $uwb_rc/uwb_rc/beacon + echo I: started beaconing on ch 13 in host $(basename $uwb_rc) + fi + echo $host_CHID | cat > $dev/wusb_chid + echo I: started host $(basename $dev) + done + ;; + set-chid) + shift + for dev in ${2:-$devs} + do + echo "${2:-$host_CHID}" "${3:-$host_band_group}" "${4:-$host_name}" \ + | cat > $dev/wusb_host_info + done + ;; + get-cdid) + for dev in ${2:-$devs} + do + cat $dev/wusb_cdid + done + ;; + set-cc) + for dev in ${2:-$devs} + do + shift + CDID="$(head --bytes=16 /dev/urandom | od -tx1 -An)" + CK="$(head --bytes=16 /dev/urandom | od -tx1 -An)" + cat > $dev/wusb_cc <&2 + help 1>&2 + result=1 +esac +exit $result -- cgit v1.2.3 From 34e95e41f1fd751e33a7eb3fa66594903b81f13d Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:05 +0100 Subject: uwb: add the uwb include files Signed-off-by: David Vrabel --- include/linux/uwb.h | 761 ++++++++++++++++++++++++++++++++++++++++++ include/linux/uwb/debug-cmd.h | 57 ++++ include/linux/uwb/debug.h | 82 +++++ include/linux/uwb/spec.h | 727 ++++++++++++++++++++++++++++++++++++++++ include/linux/wlp.h | 735 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2362 insertions(+) create mode 100644 include/linux/uwb.h create mode 100644 include/linux/uwb/debug-cmd.h create mode 100644 include/linux/uwb/debug.h create mode 100644 include/linux/uwb/spec.h create mode 100644 include/linux/wlp.h diff --git a/include/linux/uwb.h b/include/linux/uwb.h new file mode 100644 index 00000000000..0cd35937e12 --- /dev/null +++ b/include/linux/uwb.h @@ -0,0 +1,761 @@ +/* + * Ultra Wide Band + * UWB API + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: doc: overview of the API, different parts and pointers + */ + +#ifndef __LINUX__UWB_H__ +#define __LINUX__UWB_H__ + +#include +#include +#include +#include +#include +#include + +struct uwb_dev; +struct uwb_beca_e; +struct uwb_rc; +struct uwb_rsv; +struct uwb_dbg; + +/** + * struct uwb_dev - a UWB Device + * @rc: UWB Radio Controller that discovered the device (kind of its + * parent). + * @bce: a beacon cache entry for this device; or NULL if the device + * is a local radio controller. + * @mac_addr: the EUI-48 address of this device. + * @dev_addr: the current DevAddr used by this device. + * @beacon_slot: the slot number the beacon is using. + * @streams: bitmap of streams allocated to reservations targeted at + * this device. For an RC, this is the streams allocated for + * reservations targeted at DevAddrs. + * + * A UWB device may either by a neighbor or part of a local radio + * controller. + */ +struct uwb_dev { + struct mutex mutex; + struct list_head list_node; + struct device dev; + struct uwb_rc *rc; /* radio controller */ + struct uwb_beca_e *bce; /* Beacon Cache Entry */ + + struct uwb_mac_addr mac_addr; + struct uwb_dev_addr dev_addr; + int beacon_slot; + DECLARE_BITMAP(streams, UWB_NUM_STREAMS); +}; +#define to_uwb_dev(d) container_of(d, struct uwb_dev, dev) + +/** + * UWB HWA/WHCI Radio Control {Command|Event} Block context IDs + * + * RC[CE]Bs have a 'context ID' field that matches the command with + * the event received to confirm it. + * + * Maximum number of context IDs + */ +enum { UWB_RC_CTX_MAX = 256 }; + + +/** Notification chain head for UWB generated events to listeners */ +struct uwb_notifs_chain { + struct list_head list; + struct mutex mutex; +}; + +/** + * struct uwb_mas_bm - a bitmap of all MAS in a superframe + * @bm: a bitmap of length #UWB_NUM_MAS + */ +struct uwb_mas_bm { + DECLARE_BITMAP(bm, UWB_NUM_MAS); +}; + +/** + * uwb_rsv_state - UWB Reservation state. + * + * NONE - reservation is not active (no DRP IE being transmitted). + * + * Owner reservation states: + * + * INITIATED - owner has sent an initial DRP request. + * PENDING - target responded with pending Reason Code. + * MODIFIED - reservation manager is modifying an established + * reservation with a different MAS allocation. + * ESTABLISHED - the reservation has been successfully negotiated. + * + * Target reservation states: + * + * DENIED - request is denied. + * ACCEPTED - request is accepted. + * PENDING - PAL has yet to make a decision to whether to accept or + * deny. + * + * FIXME: further target states TBD. + */ +enum uwb_rsv_state { + UWB_RSV_STATE_NONE, + UWB_RSV_STATE_O_INITIATED, + UWB_RSV_STATE_O_PENDING, + UWB_RSV_STATE_O_MODIFIED, + UWB_RSV_STATE_O_ESTABLISHED, + UWB_RSV_STATE_T_ACCEPTED, + UWB_RSV_STATE_T_DENIED, + UWB_RSV_STATE_T_PENDING, + + UWB_RSV_STATE_LAST, +}; + +enum uwb_rsv_target_type { + UWB_RSV_TARGET_DEV, + UWB_RSV_TARGET_DEVADDR, +}; + +/** + * struct uwb_rsv_target - the target of a reservation. + * + * Reservations unicast and targeted at a single device + * (UWB_RSV_TARGET_DEV); or (e.g., in the case of WUSB) targeted at a + * specific (private) DevAddr (UWB_RSV_TARGET_DEVADDR). + */ +struct uwb_rsv_target { + enum uwb_rsv_target_type type; + union { + struct uwb_dev *dev; + struct uwb_dev_addr devaddr; + }; +}; + +/* + * Number of streams reserved for reservations targeted at DevAddrs. + */ +#define UWB_NUM_GLOBAL_STREAMS 1 + +typedef void (*uwb_rsv_cb_f)(struct uwb_rsv *rsv); + +/** + * struct uwb_rsv - a DRP reservation + * + * Data structure management: + * + * @rc: the radio controller this reservation is for + * (as target or owner) + * @rc_node: a list node for the RC + * @pal_node: a list node for the PAL + * + * Owner and target parameters: + * + * @owner: the UWB device owning this reservation + * @target: the target UWB device + * @type: reservation type + * + * Owner parameters: + * + * @max_mas: maxiumum number of MAS + * @min_mas: minimum number of MAS + * @sparsity: owner selected sparsity + * @is_multicast: true iff multicast + * + * @callback: callback function when the reservation completes + * @pal_priv: private data for the PAL making the reservation + * + * Reservation status: + * + * @status: negotiation status + * @stream: stream index allocated for this reservation + * @mas: reserved MAS + * @drp_ie: the DRP IE + * @ie_valid: true iff the DRP IE matches the reservation parameters + * + * DRP reservations are uniquely identified by the owner, target and + * stream index. However, when using a DevAddr as a target (e.g., for + * a WUSB cluster reservation) the responses may be received from + * devices with different DevAddrs. In this case, reservations are + * uniquely identified by just the stream index. A number of stream + * indexes (UWB_NUM_GLOBAL_STREAMS) are reserved for this. + */ +struct uwb_rsv { + struct uwb_rc *rc; + struct list_head rc_node; + struct list_head pal_node; + + struct uwb_dev *owner; + struct uwb_rsv_target target; + enum uwb_drp_type type; + int max_mas; + int min_mas; + int sparsity; + bool is_multicast; + + uwb_rsv_cb_f callback; + void *pal_priv; + + enum uwb_rsv_state state; + u8 stream; + struct uwb_mas_bm mas; + struct uwb_ie_drp *drp_ie; + bool ie_valid; + struct timer_list timer; + bool expired; +}; + +static const +struct uwb_mas_bm uwb_mas_bm_zero = { .bm = { 0 } }; + +static inline void uwb_mas_bm_copy_le(void *dst, const struct uwb_mas_bm *mas) +{ + bitmap_copy_le(dst, mas->bm, UWB_NUM_MAS); +} + +/** + * struct uwb_drp_avail - a radio controller's view of MAS usage + * @global: MAS unused by neighbors (excluding reservations targetted + * or owned by the local radio controller) or the beaon period + * @local: MAS unused by local established reservations + * @pending: MAS unused by local pending reservations + * @ie: DRP Availability IE to be included in the beacon + * @ie_valid: true iff @ie is valid and does not need to regenerated from + * @global and @local + * + * Each radio controller maintains a view of MAS usage or + * availability. MAS available for a new reservation are determined + * from the intersection of @global, @local, and @pending. + * + * The radio controller must transmit a DRP Availability IE that's the + * intersection of @global and @local. + * + * A set bit indicates the MAS is unused and available. + * + * rc->rsvs_mutex should be held before accessing this data structure. + * + * [ECMA-368] section 17.4.3. + */ +struct uwb_drp_avail { + DECLARE_BITMAP(global, UWB_NUM_MAS); + DECLARE_BITMAP(local, UWB_NUM_MAS); + DECLARE_BITMAP(pending, UWB_NUM_MAS); + struct uwb_ie_drp_avail ie; + bool ie_valid; +}; + + +const char *uwb_rsv_state_str(enum uwb_rsv_state state); +const char *uwb_rsv_type_str(enum uwb_drp_type type); + +struct uwb_rsv *uwb_rsv_create(struct uwb_rc *rc, uwb_rsv_cb_f cb, + void *pal_priv); +void uwb_rsv_destroy(struct uwb_rsv *rsv); + +int uwb_rsv_establish(struct uwb_rsv *rsv); +int uwb_rsv_modify(struct uwb_rsv *rsv, + int max_mas, int min_mas, int sparsity); +void uwb_rsv_terminate(struct uwb_rsv *rsv); + +void uwb_rsv_accept(struct uwb_rsv *rsv, uwb_rsv_cb_f cb, void *pal_priv); + +/** + * Radio Control Interface instance + * + * + * Life cycle rules: those of the UWB Device. + * + * @index: an index number for this radio controller, as used in the + * device name. + * @version: version of protocol supported by this device + * @priv: Backend implementation; rw with uwb_dev.dev.sem taken. + * @cmd: Backend implementation to execute commands; rw and call + * only with uwb_dev.dev.sem taken. + * @reset: Hardware reset of radio controller and any PAL controllers. + * @filter: Backend implementation to manipulate data to and from device + * to be compliant to specification assumed by driver (WHCI + * 0.95). + * + * uwb_dev.dev.mutex is used to execute commands and update + * the corresponding structures; can't use a spinlock + * because rc->cmd() can sleep. + * @ies: This is a dynamically allocated array cacheing the + * IEs (settable by the host) that the beacon of this + * radio controller is currently sending. + * + * In reality, we store here the full command we set to + * the radio controller (which is basically a command + * prefix followed by all the IEs the beacon currently + * contains). This way we don't have to realloc and + * memcpy when setting it. + * + * We set this up in uwb_rc_ie_setup(), where we alloc + * this struct, call get_ie() [so we know which IEs are + * currently being sent, if any]. + * + * @ies_capacity:Amount of space (in bytes) allocated in @ies. The + * amount used is given by sizeof(*ies) plus ies->wIELength + * (which is a little endian quantity all the time). + * @ies_mutex: protect the IE cache + * @dbg: information for the debug interface + */ +struct uwb_rc { + struct uwb_dev uwb_dev; + int index; + u16 version; + + struct module *owner; + void *priv; + int (*start)(struct uwb_rc *rc); + void (*stop)(struct uwb_rc *rc); + int (*cmd)(struct uwb_rc *, const struct uwb_rccb *, size_t); + int (*reset)(struct uwb_rc *rc); + int (*filter_cmd)(struct uwb_rc *, struct uwb_rccb **, size_t *); + int (*filter_event)(struct uwb_rc *, struct uwb_rceb **, const size_t, + size_t *, size_t *); + + spinlock_t neh_lock; /* protects neh_* and ctx_* */ + struct list_head neh_list; /* Open NE handles */ + unsigned long ctx_bm[UWB_RC_CTX_MAX / 8 / sizeof(unsigned long)]; + u8 ctx_roll; + + int beaconing; /* Beaconing state [channel number] */ + int scanning; + enum uwb_scan_type scan_type:3; + unsigned ready:1; + struct uwb_notifs_chain notifs_chain; + + struct uwb_drp_avail drp_avail; + struct list_head reservations; + struct mutex rsvs_mutex; + struct workqueue_struct *rsv_workq; + struct work_struct rsv_update_work; + + struct mutex ies_mutex; + struct uwb_rc_cmd_set_ie *ies; + size_t ies_capacity; + + spinlock_t pal_lock; + struct list_head pals; + + struct uwb_dbg *dbg; +}; + + +/** + * struct uwb_pal - a UWB PAL + * @new_rsv: called when a peer requests a reservation (may be NULL if + * the PAL cannot accept reservation requests). + * + * A Protocol Adaptation Layer (PAL) is a user of the WiMedia UWB + * radio platform (e.g., WUSB, WLP or Bluetooth UWB AMP). + * + * The PALs using a radio controller must register themselves to + * permit the UWB stack to coordinate usage of the radio between the + * various PALs or to allow PALs to response to certain requests from + * peers. + * + * A struct uwb_pal should be embedded in a containing structure + * belonging to the PAL and initialized with uwb_pal_init()). Fields + * should be set appropriately by the PAL before registering the PAL + * with uwb_pal_register(). + */ +struct uwb_pal { + struct list_head node; + + void (*new_rsv)(struct uwb_rsv *rsv); +}; + +void uwb_pal_init(struct uwb_pal *pal); +int uwb_pal_register(struct uwb_rc *rc, struct uwb_pal *pal); +void uwb_pal_unregister(struct uwb_rc *rc, struct uwb_pal *pal); + +/* + * General public API + * + * This API can be used by UWB device drivers or by those implementing + * UWB Radio Controllers + */ +struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc, + const struct uwb_dev_addr *devaddr); +struct uwb_dev *uwb_dev_get_by_rc(struct uwb_dev *, struct uwb_rc *); +static inline void uwb_dev_get(struct uwb_dev *uwb_dev) +{ + get_device(&uwb_dev->dev); +} +static inline void uwb_dev_put(struct uwb_dev *uwb_dev) +{ + put_device(&uwb_dev->dev); +} +struct uwb_dev *uwb_dev_try_get(struct uwb_rc *rc, struct uwb_dev *uwb_dev); + +/** + * Callback function for 'uwb_{dev,rc}_foreach()'. + * + * @dev: Linux device instance + * 'uwb_dev = container_of(dev, struct uwb_dev, dev)' + * @priv: Data passed by the caller to 'uwb_{dev,rc}_foreach()'. + * + * @returns: 0 to continue the iterations, any other val to stop + * iterating and return the value to the caller of + * _foreach(). + */ +typedef int (*uwb_dev_for_each_f)(struct device *dev, void *priv); +int uwb_dev_for_each(struct uwb_rc *rc, uwb_dev_for_each_f func, void *priv); + +struct uwb_rc *uwb_rc_alloc(void); +struct uwb_rc *uwb_rc_get_by_dev(const struct uwb_dev_addr *); +struct uwb_rc *uwb_rc_get_by_grandpa(const struct device *); +void uwb_rc_put(struct uwb_rc *rc); + +typedef void (*uwb_rc_cmd_cb_f)(struct uwb_rc *rc, void *arg, + struct uwb_rceb *reply, ssize_t reply_size); + +int uwb_rc_cmd_async(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + u8 expected_type, u16 expected_event, + uwb_rc_cmd_cb_f cb, void *arg); +ssize_t uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + struct uwb_rceb *reply, size_t reply_size); +ssize_t uwb_rc_vcmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + u8 expected_type, u16 expected_event, + struct uwb_rceb **preply); +ssize_t uwb_rc_get_ie(struct uwb_rc *, struct uwb_rc_evt_get_ie **); +int uwb_bg_joined(struct uwb_rc *rc); + +size_t __uwb_addr_print(char *, size_t, const unsigned char *, int); + +int uwb_rc_dev_addr_set(struct uwb_rc *, const struct uwb_dev_addr *); +int uwb_rc_dev_addr_get(struct uwb_rc *, struct uwb_dev_addr *); +int uwb_rc_mac_addr_set(struct uwb_rc *, const struct uwb_mac_addr *); +int uwb_rc_mac_addr_get(struct uwb_rc *, struct uwb_mac_addr *); +int __uwb_mac_addr_assigned_check(struct device *, void *); +int __uwb_dev_addr_assigned_check(struct device *, void *); + +/* Print in @buf a pretty repr of @addr */ +static inline size_t uwb_dev_addr_print(char *buf, size_t buf_size, + const struct uwb_dev_addr *addr) +{ + return __uwb_addr_print(buf, buf_size, addr->data, 0); +} + +/* Print in @buf a pretty repr of @addr */ +static inline size_t uwb_mac_addr_print(char *buf, size_t buf_size, + const struct uwb_mac_addr *addr) +{ + return __uwb_addr_print(buf, buf_size, addr->data, 1); +} + +/* @returns 0 if device addresses @addr2 and @addr1 are equal */ +static inline int uwb_dev_addr_cmp(const struct uwb_dev_addr *addr1, + const struct uwb_dev_addr *addr2) +{ + return memcmp(addr1, addr2, sizeof(*addr1)); +} + +/* @returns 0 if MAC addresses @addr2 and @addr1 are equal */ +static inline int uwb_mac_addr_cmp(const struct uwb_mac_addr *addr1, + const struct uwb_mac_addr *addr2) +{ + return memcmp(addr1, addr2, sizeof(*addr1)); +} + +/* @returns !0 if a MAC @addr is a broadcast address */ +static inline int uwb_mac_addr_bcast(const struct uwb_mac_addr *addr) +{ + struct uwb_mac_addr bcast = { + .data = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } + }; + return !uwb_mac_addr_cmp(addr, &bcast); +} + +/* @returns !0 if a MAC @addr is all zeroes*/ +static inline int uwb_mac_addr_unset(const struct uwb_mac_addr *addr) +{ + struct uwb_mac_addr unset = { + .data = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } + }; + return !uwb_mac_addr_cmp(addr, &unset); +} + +/* @returns !0 if the address is in use. */ +static inline unsigned __uwb_dev_addr_assigned(struct uwb_rc *rc, + struct uwb_dev_addr *addr) +{ + return uwb_dev_for_each(rc, __uwb_dev_addr_assigned_check, addr); +} + +/* + * UWB Radio Controller API + * + * This API is used (in addition to the general API) to implement UWB + * Radio Controllers. + */ +void uwb_rc_init(struct uwb_rc *); +int uwb_rc_add(struct uwb_rc *, struct device *dev, void *rc_priv); +void uwb_rc_rm(struct uwb_rc *); +void uwb_rc_neh_grok(struct uwb_rc *, void *, size_t); +void uwb_rc_neh_error(struct uwb_rc *, int); +void uwb_rc_reset_all(struct uwb_rc *rc); + +/** + * uwb_rsv_is_owner - is the owner of this reservation the RC? + * @rsv: the reservation + */ +static inline bool uwb_rsv_is_owner(struct uwb_rsv *rsv) +{ + return rsv->owner == &rsv->rc->uwb_dev; +} + +/** + * Events generated by UWB that can be passed to any listeners + * + * Higher layers can register callback functions with the radio + * controller using uwb_notifs_register(). The radio controller + * maintains a list of all registered handlers and will notify all + * nodes when an event occurs. + */ +enum uwb_notifs { + UWB_NOTIF_BG_JOIN = 0, /* radio controller joined a beacon group */ + UWB_NOTIF_BG_LEAVE = 1, /* radio controller left a beacon group */ + UWB_NOTIF_ONAIR, + UWB_NOTIF_OFFAIR, +}; + +/* Callback function registered with UWB */ +struct uwb_notifs_handler { + struct list_head list_node; + void (*cb)(void *, struct uwb_dev *, enum uwb_notifs); + void *data; +}; + +int uwb_notifs_register(struct uwb_rc *, struct uwb_notifs_handler *); +int uwb_notifs_deregister(struct uwb_rc *, struct uwb_notifs_handler *); + + +/** + * UWB radio controller Event Size Entry (for creating entry tables) + * + * WUSB and WHCI define events and notifications, and they might have + * fixed or variable size. + * + * Each event/notification has a size which is not necessarily known + * in advance based on the event code. As well, vendor specific + * events/notifications will have a size impossible to determine + * unless we know about the device's specific details. + * + * It was way too smart of the spec writers not to think that it would + * be impossible for a generic driver to skip over vendor specific + * events/notifications if there are no LENGTH fields in the HEADER of + * each message...the transaction size cannot be counted on as the + * spec does not forbid to pack more than one event in a single + * transaction. + * + * Thus, we guess sizes with tables (or for events, when you know the + * size ahead of time you can use uwb_rc_neh_extra_size*()). We + * register tables with the known events and their sizes, and then we + * traverse those tables. For those with variable length, we provide a + * way to lookup the size inside the event/notification's + * payload. This allows device-specific event size tables to be + * registered. + * + * @size: Size of the payload + * + * @offset: if != 0, at offset @offset-1 starts a field with a length + * that has to be added to @size. The format of the field is + * given by @type. + * + * @type: Type and length of the offset field. Most common is LE 16 + * bits (that's why that is zero); others are there mostly to + * cover for bugs and weirdos. + */ +struct uwb_est_entry { + size_t size; + unsigned offset; + enum { UWB_EST_16 = 0, UWB_EST_8 = 1 } type; +}; + +int uwb_est_register(u8 type, u8 code_high, u16 vendor, u16 product, + const struct uwb_est_entry *, size_t entries); +int uwb_est_unregister(u8 type, u8 code_high, u16 vendor, u16 product, + const struct uwb_est_entry *, size_t entries); +ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, + size_t len); + +/* -- Misc */ + +enum { + EDC_MAX_ERRORS = 10, + EDC_ERROR_TIMEFRAME = HZ, +}; + +/* error density counter */ +struct edc { + unsigned long timestart; + u16 errorcount; +}; + +static inline +void edc_init(struct edc *edc) +{ + edc->timestart = jiffies; +} + +/* Called when an error occured. + * This is way to determine if the number of acceptable errors per time + * period has been exceeded. It is not accurate as there are cases in which + * this scheme will not work, for example if there are periodic occurences + * of errors that straddle updates to the start time. This scheme is + * sufficient for our usage. + * + * @returns 1 if maximum acceptable errors per timeframe has been exceeded. + */ +static inline int edc_inc(struct edc *err_hist, u16 max_err, u16 timeframe) +{ + unsigned long now; + + now = jiffies; + if (now - err_hist->timestart > timeframe) { + err_hist->errorcount = 1; + err_hist->timestart = now; + } else if (++err_hist->errorcount > max_err) { + err_hist->errorcount = 0; + err_hist->timestart = now; + return 1; + } + return 0; +} + + +/* Information Element handling */ + +/* For representing the state of writing to a buffer when iterating */ +struct uwb_buf_ctx { + char *buf; + size_t bytes, size; +}; + +typedef int (*uwb_ie_f)(struct uwb_dev *, const struct uwb_ie_hdr *, + size_t, void *); +struct uwb_ie_hdr *uwb_ie_next(void **ptr, size_t *len); +ssize_t uwb_ie_for_each(struct uwb_dev *uwb_dev, uwb_ie_f fn, void *data, + const void *buf, size_t size); +int uwb_ie_dump_hex(struct uwb_dev *, const struct uwb_ie_hdr *, + size_t, void *); +int uwb_rc_set_ie(struct uwb_rc *, struct uwb_rc_cmd_set_ie *); +struct uwb_ie_hdr *uwb_ie_next(void **ptr, size_t *len); + + +/* + * Transmission statistics + * + * UWB uses LQI and RSSI (one byte values) for reporting radio signal + * strength and line quality indication. We do quick and dirty + * averages of those. They are signed values, btw. + * + * For 8 bit quantities, we keep the min, the max, an accumulator + * (@sigma) and a # of samples. When @samples gets to 255, we compute + * the average (@sigma / @samples), place it in @sigma and reset + * @samples to 1 (so we use it as the first sample). + * + * Now, statistically speaking, probably I am kicking the kidneys of + * some books I have in my shelves collecting dust, but I just want to + * get an approx, not the Nobel. + * + * LOCKING: there is no locking per se, but we try to keep a lockless + * schema. Only _add_samples() modifies the values--as long as you + * have other locking on top that makes sure that no two calls of + * _add_sample() happen at the same time, then we are fine. Now, for + * resetting the values we just set @samples to 0 and that makes the + * next _add_sample() to start with defaults. Reading the values in + * _show() currently can race, so you need to make sure the calls are + * under the same lock that protects calls to _add_sample(). FIXME: + * currently unlocked (It is not ultraprecise but does the trick. Bite + * me). + */ +struct stats { + s8 min, max; + s16 sigma; + atomic_t samples; +}; + +static inline +void stats_init(struct stats *stats) +{ + atomic_set(&stats->samples, 0); + wmb(); +} + +static inline +void stats_add_sample(struct stats *stats, s8 sample) +{ + s8 min, max; + s16 sigma; + unsigned samples = atomic_read(&stats->samples); + if (samples == 0) { /* it was zero before, so we initialize */ + min = 127; + max = -128; + sigma = 0; + } else { + min = stats->min; + max = stats->max; + sigma = stats->sigma; + } + + if (sample < min) /* compute new values */ + min = sample; + else if (sample > max) + max = sample; + sigma += sample; + + stats->min = min; /* commit */ + stats->max = max; + stats->sigma = sigma; + if (atomic_add_return(1, &stats->samples) > 255) { + /* wrapped around! reset */ + stats->sigma = sigma / 256; + atomic_set(&stats->samples, 1); + } +} + +static inline ssize_t stats_show(struct stats *stats, char *buf) +{ + int min, max, avg; + int samples = atomic_read(&stats->samples); + if (samples == 0) + min = max = avg = 0; + else { + min = stats->min; + max = stats->max; + avg = stats->sigma / samples; + } + return scnprintf(buf, PAGE_SIZE, "%d %d %d\n", min, max, avg); +} + +static inline ssize_t stats_store(struct stats *stats, const char *buf, + size_t size) +{ + stats_init(stats); + return size; +} + +#endif /* #ifndef __LINUX__UWB_H__ */ diff --git a/include/linux/uwb/debug-cmd.h b/include/linux/uwb/debug-cmd.h new file mode 100644 index 00000000000..1141f41bab5 --- /dev/null +++ b/include/linux/uwb/debug-cmd.h @@ -0,0 +1,57 @@ +/* + * Ultra Wide Band + * Debug interface commands + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __LINUX__UWB__DEBUG_CMD_H__ +#define __LINUX__UWB__DEBUG_CMD_H__ + +#include + +/* + * Debug interface commands + * + * UWB_DBG_CMD_RSV_ESTABLISH: Establish a new unicast reservation. + * + * UWB_DBG_CMD_RSV_TERMINATE: Terminate the Nth reservation. + */ + +enum uwb_dbg_cmd_type { + UWB_DBG_CMD_RSV_ESTABLISH = 1, + UWB_DBG_CMD_RSV_TERMINATE = 2, +}; + +struct uwb_dbg_cmd_rsv_establish { + __u8 target[6]; + __u8 type; + __u16 max_mas; + __u16 min_mas; + __u8 sparsity; +}; + +struct uwb_dbg_cmd_rsv_terminate { + int index; +}; + +struct uwb_dbg_cmd { + __u32 type; + union { + struct uwb_dbg_cmd_rsv_establish rsv_establish; + struct uwb_dbg_cmd_rsv_terminate rsv_terminate; + }; +}; + +#endif /* #ifndef __LINUX__UWB__DEBUG_CMD_H__ */ diff --git a/include/linux/uwb/debug.h b/include/linux/uwb/debug.h new file mode 100644 index 00000000000..a86a73fe303 --- /dev/null +++ b/include/linux/uwb/debug.h @@ -0,0 +1,82 @@ +/* + * Ultra Wide Band + * Debug Support + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: doc + * Invoke like: + * + * #define D_LOCAL 4 + * #include + * + * At the end of your include files. + */ +#include + +struct device; +extern void dump_bytes(struct device *dev, const void *_buf, size_t rsize); + +/* Master debug switch; !0 enables, 0 disables */ +#define D_MASTER (!0) + +/* Local (per-file) debug switch; #define before #including */ +#ifndef D_LOCAL +#define D_LOCAL 0 +#endif + +#undef __d_printf +#undef d_fnstart +#undef d_fnend +#undef d_printf +#undef d_dump + +#define __d_printf(l, _tag, _dev, f, a...) \ +do { \ + struct device *__dev = (_dev); \ + if (D_MASTER && D_LOCAL >= (l)) { \ + char __head[64] = ""; \ + if (_dev != NULL) { \ + if ((unsigned long)__dev < 4096) \ + printk(KERN_ERR "E: Corrupt dev %p\n", \ + __dev); \ + else \ + snprintf(__head, sizeof(__head), \ + "%s %s: ", \ + dev_driver_string(__dev), \ + __dev->bus_id); \ + } \ + printk(KERN_ERR "%s%s" _tag ": " f, __head, \ + __func__, ## a); \ + } \ +} while (0 && _dev) + +#define d_fnstart(l, _dev, f, a...) \ + __d_printf(l, " FNSTART", _dev, f, ## a) +#define d_fnend(l, _dev, f, a...) \ + __d_printf(l, " FNEND", _dev, f, ## a) +#define d_printf(l, _dev, f, a...) \ + __d_printf(l, "", _dev, f, ## a) +#define d_dump(l, _dev, ptr, size) \ +do { \ + struct device *__dev = _dev; \ + if (D_MASTER && D_LOCAL >= (l)) \ + dump_bytes(__dev, ptr, size); \ +} while (0 && _dev) +#define d_test(l) (D_MASTER && D_LOCAL >= (l)) diff --git a/include/linux/uwb/spec.h b/include/linux/uwb/spec.h new file mode 100644 index 00000000000..198c15f8e25 --- /dev/null +++ b/include/linux/uwb/spec.h @@ -0,0 +1,727 @@ +/* + * Ultra Wide Band + * UWB Standard definitions + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * All these definitions are based on the ECMA-368 standard. + * + * Note all definitions are Little Endian in the wire, and we will + * convert them to host order before operating on the bitfields (that + * yes, we use extensively). + */ + +#ifndef __LINUX__UWB_SPEC_H__ +#define __LINUX__UWB_SPEC_H__ + +#include +#include + +#define i1480_FW 0x00000303 +/* #define i1480_FW 0x00000302 */ + +/** + * Number of Medium Access Slots in a superframe. + * + * UWB divides time in SuperFrames, each one divided in 256 pieces, or + * Medium Access Slots. See MBOA MAC[5.4.5] for details. The MAS is the + * basic bandwidth allocation unit in UWB. + */ +enum { UWB_NUM_MAS = 256 }; + +/** + * Number of Zones in superframe. + * + * UWB divides the superframe into zones with numbering starting from BPST. + * See MBOA MAC[16.8.6] + */ +enum { UWB_NUM_ZONES = 16 }; + +/* + * Number of MAS in a zone. + */ +#define UWB_MAS_PER_ZONE (UWB_NUM_MAS / UWB_NUM_ZONES) + +/* + * Number of streams per DRP reservation between a pair of devices. + * + * [ECMA-368] section 16.8.6. + */ +enum { UWB_NUM_STREAMS = 8 }; + +/* + * mMasLength + * + * The length of a MAS in microseconds. + * + * [ECMA-368] section 17.16. + */ +enum { UWB_MAS_LENGTH_US = 256 }; + +/* + * mBeaconSlotLength + * + * The length of the beacon slot in microseconds. + * + * [ECMA-368] section 17.16 + */ +enum { UWB_BEACON_SLOT_LENGTH_US = 85 }; + +/* + * mMaxLostBeacons + * + * The number beacons missing in consecutive superframes before a + * device can be considered as unreachable. + * + * [ECMA-368] section 17.16 + */ +enum { UWB_MAX_LOST_BEACONS = 3 }; + +/* + * Length of a superframe in microseconds. + */ +#define UWB_SUPERFRAME_LENGTH_US (UWB_MAS_LENGTH_US * UWB_NUM_MAS) + +/** + * UWB MAC address + * + * It is *imperative* that this struct is exactly 6 packed bytes (as + * it is also used to define headers sent down and up the wire/radio). + */ +struct uwb_mac_addr { + u8 data[6]; +} __attribute__((packed)); + + +/** + * UWB device address + * + * It is *imperative* that this struct is exactly 6 packed bytes (as + * it is also used to define headers sent down and up the wire/radio). + */ +struct uwb_dev_addr { + u8 data[2]; +} __attribute__((packed)); + + +/** + * Types of UWB addresses + * + * Order matters (by size). + */ +enum uwb_addr_type { + UWB_ADDR_DEV = 0, + UWB_ADDR_MAC = 1, +}; + + +/** Size of a char buffer for printing a MAC/device address */ +enum { UWB_ADDR_STRSIZE = 32 }; + + +/** UWB WiMedia protocol IDs. */ +enum uwb_prid { + UWB_PRID_WLP_RESERVED = 0x0000, + UWB_PRID_WLP = 0x0001, + UWB_PRID_WUSB_BOT = 0x0010, + UWB_PRID_WUSB = 0x0010, + UWB_PRID_WUSB_TOP = 0x001F, +}; + + +/** PHY Rate (MBOA MAC[7.8.12, Table 61]) */ +enum uwb_phy_rate { + UWB_PHY_RATE_53 = 0, + UWB_PHY_RATE_80, + UWB_PHY_RATE_106, + UWB_PHY_RATE_160, + UWB_PHY_RATE_200, + UWB_PHY_RATE_320, + UWB_PHY_RATE_400, + UWB_PHY_RATE_480, + UWB_PHY_RATE_INVALID +}; + + +/** + * Different ways to scan (MBOA MAC[6.2.2, Table 8], WUSB[Table 8-78]) + */ +enum uwb_scan_type { + UWB_SCAN_ONLY = 0, + UWB_SCAN_OUTSIDE_BP, + UWB_SCAN_WHILE_INACTIVE, + UWB_SCAN_DISABLED, + UWB_SCAN_ONLY_STARTTIME, + UWB_SCAN_TOP +}; + + +/** ACK Policy types (MBOA MAC[7.2.1.3]) */ +enum uwb_ack_pol { + UWB_ACK_NO = 0, + UWB_ACK_INM = 1, + UWB_ACK_B = 2, + UWB_ACK_B_REQ = 3, +}; + + +/** DRP reservation types ([ECMA-368 table 106) */ +enum uwb_drp_type { + UWB_DRP_TYPE_ALIEN_BP = 0, + UWB_DRP_TYPE_HARD, + UWB_DRP_TYPE_SOFT, + UWB_DRP_TYPE_PRIVATE, + UWB_DRP_TYPE_PCA, +}; + + +/** DRP Reason Codes ([ECMA-368] table 107) */ +enum uwb_drp_reason { + UWB_DRP_REASON_ACCEPTED = 0, + UWB_DRP_REASON_CONFLICT, + UWB_DRP_REASON_PENDING, + UWB_DRP_REASON_DENIED, + UWB_DRP_REASON_MODIFIED, +}; + +/** + * DRP Notification Reason Codes (WHCI 0.95 [3.1.4.9]) + */ +enum uwb_drp_notif_reason { + UWB_DRP_NOTIF_DRP_IE_RCVD = 0, + UWB_DRP_NOTIF_CONFLICT, + UWB_DRP_NOTIF_TERMINATE, +}; + + +/** Allocation of MAS slots in a DRP request MBOA MAC[7.8.7] */ +struct uwb_drp_alloc { + __le16 zone_bm; + __le16 mas_bm; +} __attribute__((packed)); + + +/** General MAC Header format (ECMA-368[16.2]) */ +struct uwb_mac_frame_hdr { + __le16 Frame_Control; + struct uwb_dev_addr DestAddr; + struct uwb_dev_addr SrcAddr; + __le16 Sequence_Control; + __le16 Access_Information; +} __attribute__((packed)); + + +/** + * uwb_beacon_frame - a beacon frame including MAC headers + * + * [ECMA] section 16.3. + */ +struct uwb_beacon_frame { + struct uwb_mac_frame_hdr hdr; + struct uwb_mac_addr Device_Identifier; /* may be a NULL EUI-48 */ + u8 Beacon_Slot_Number; + u8 Device_Control; + u8 IEData[]; +} __attribute__((packed)); + + +/** Information Element codes (MBOA MAC[T54]) */ +enum uwb_ie { + UWB_PCA_AVAILABILITY = 2, + UWB_IE_DRP_AVAILABILITY = 8, + UWB_IE_DRP = 9, + UWB_BP_SWITCH_IE = 11, + UWB_MAC_CAPABILITIES_IE = 12, + UWB_PHY_CAPABILITIES_IE = 13, + UWB_APP_SPEC_PROBE_IE = 15, + UWB_IDENTIFICATION_IE = 19, + UWB_MASTER_KEY_ID_IE = 20, + UWB_IE_WLP = 250, /* WiMedia Logical Link Control Protocol WLP 0.99 */ + UWB_APP_SPEC_IE = 255, +}; + + +/** + * Header common to all Information Elements (IEs) + */ +struct uwb_ie_hdr { + u8 element_id; /* enum uwb_ie */ + u8 length; +} __attribute__((packed)); + + +/** Dynamic Reservation Protocol IE (MBOA MAC[7.8.6]) */ +struct uwb_ie_drp { + struct uwb_ie_hdr hdr; + __le16 drp_control; + struct uwb_dev_addr dev_addr; + struct uwb_drp_alloc allocs[]; +} __attribute__((packed)); + +static inline int uwb_ie_drp_type(struct uwb_ie_drp *ie) +{ + return (le16_to_cpu(ie->drp_control) >> 0) & 0x7; +} + +static inline int uwb_ie_drp_stream_index(struct uwb_ie_drp *ie) +{ + return (le16_to_cpu(ie->drp_control) >> 3) & 0x7; +} + +static inline int uwb_ie_drp_reason_code(struct uwb_ie_drp *ie) +{ + return (le16_to_cpu(ie->drp_control) >> 6) & 0x7; +} + +static inline int uwb_ie_drp_status(struct uwb_ie_drp *ie) +{ + return (le16_to_cpu(ie->drp_control) >> 9) & 0x1; +} + +static inline int uwb_ie_drp_owner(struct uwb_ie_drp *ie) +{ + return (le16_to_cpu(ie->drp_control) >> 10) & 0x1; +} + +static inline int uwb_ie_drp_tiebreaker(struct uwb_ie_drp *ie) +{ + return (le16_to_cpu(ie->drp_control) >> 11) & 0x1; +} + +static inline int uwb_ie_drp_unsafe(struct uwb_ie_drp *ie) +{ + return (le16_to_cpu(ie->drp_control) >> 12) & 0x1; +} + +static inline void uwb_ie_drp_set_type(struct uwb_ie_drp *ie, enum uwb_drp_type type) +{ + u16 drp_control = le16_to_cpu(ie->drp_control); + drp_control = (drp_control & ~(0x7 << 0)) | (type << 0); + ie->drp_control = cpu_to_le16(drp_control); +} + +static inline void uwb_ie_drp_set_stream_index(struct uwb_ie_drp *ie, int stream_index) +{ + u16 drp_control = le16_to_cpu(ie->drp_control); + drp_control = (drp_control & ~(0x7 << 3)) | (stream_index << 3); + ie->drp_control = cpu_to_le16(drp_control); +} + +static inline void uwb_ie_drp_set_reason_code(struct uwb_ie_drp *ie, + enum uwb_drp_reason reason_code) +{ + u16 drp_control = le16_to_cpu(ie->drp_control); + drp_control = (ie->drp_control & ~(0x7 << 6)) | (reason_code << 6); + ie->drp_control = cpu_to_le16(drp_control); +} + +static inline void uwb_ie_drp_set_status(struct uwb_ie_drp *ie, int status) +{ + u16 drp_control = le16_to_cpu(ie->drp_control); + drp_control = (drp_control & ~(0x1 << 9)) | (status << 9); + ie->drp_control = cpu_to_le16(drp_control); +} + +static inline void uwb_ie_drp_set_owner(struct uwb_ie_drp *ie, int owner) +{ + u16 drp_control = le16_to_cpu(ie->drp_control); + drp_control = (drp_control & ~(0x1 << 10)) | (owner << 10); + ie->drp_control = cpu_to_le16(drp_control); +} + +static inline void uwb_ie_drp_set_tiebreaker(struct uwb_ie_drp *ie, int tiebreaker) +{ + u16 drp_control = le16_to_cpu(ie->drp_control); + drp_control = (drp_control & ~(0x1 << 11)) | (tiebreaker << 11); + ie->drp_control = cpu_to_le16(drp_control); +} + +static inline void uwb_ie_drp_set_unsafe(struct uwb_ie_drp *ie, int unsafe) +{ + u16 drp_control = le16_to_cpu(ie->drp_control); + drp_control = (drp_control & ~(0x1 << 12)) | (unsafe << 12); + ie->drp_control = cpu_to_le16(drp_control); +} + +/** Dynamic Reservation Protocol IE (MBOA MAC[7.8.7]) */ +struct uwb_ie_drp_avail { + struct uwb_ie_hdr hdr; + DECLARE_BITMAP(bmp, UWB_NUM_MAS); +} __attribute__((packed)); + +/** + * The Vendor ID is set to an OUI that indicates the vendor of the device. + * ECMA-368 [16.8.10] + */ +struct uwb_vendor_id { + u8 data[3]; +} __attribute__((packed)); + +/** + * The device type ID + * FIXME: clarify what this means + * ECMA-368 [16.8.10] + */ +struct uwb_device_type_id { + u8 data[3]; +} __attribute__((packed)); + + +/** + * UWB device information types + * ECMA-368 [16.8.10] + */ +enum uwb_dev_info_type { + UWB_DEV_INFO_VENDOR_ID = 0, + UWB_DEV_INFO_VENDOR_TYPE, + UWB_DEV_INFO_NAME, +}; + +/** + * UWB device information found in Identification IE + * ECMA-368 [16.8.10] + */ +struct uwb_dev_info { + u8 type; /* enum uwb_dev_info_type */ + u8 length; + u8 data[]; +} __attribute__((packed)); + +/** + * UWB Identification IE + * ECMA-368 [16.8.10] + */ +struct uwb_identification_ie { + struct uwb_ie_hdr hdr; + struct uwb_dev_info info[]; +} __attribute__((packed)); + +/* + * UWB Radio Controller + * + * These definitions are common to the Radio Control layers as + * exported by the WUSB1.0 HWA and WHCI interfaces. + */ + +/** Radio Control Command Block (WUSB1.0[Table 8-65] and WHCI 0.95) */ +struct uwb_rccb { + u8 bCommandType; /* enum hwa_cet */ + __le16 wCommand; /* Command code */ + u8 bCommandContext; /* Context ID */ +} __attribute__((packed)); + + +/** Radio Control Event Block (WUSB[table 8-66], WHCI 0.95) */ +struct uwb_rceb { + u8 bEventType; /* enum hwa_cet */ + __le16 wEvent; /* Event code */ + u8 bEventContext; /* Context ID */ +} __attribute__((packed)); + + +enum { + UWB_RC_CET_GENERAL = 0, /* General Command/Event type */ + UWB_RC_CET_EX_TYPE_1 = 1, /* Extended Type 1 Command/Event type */ +}; + +/* Commands to the radio controller */ +enum uwb_rc_cmd { + UWB_RC_CMD_CHANNEL_CHANGE = 16, + UWB_RC_CMD_DEV_ADDR_MGMT = 17, /* Device Address Management */ + UWB_RC_CMD_GET_IE = 18, /* GET Information Elements */ + UWB_RC_CMD_RESET = 19, + UWB_RC_CMD_SCAN = 20, /* Scan management */ + UWB_RC_CMD_SET_BEACON_FILTER = 21, + UWB_RC_CMD_SET_DRP_IE = 22, /* Dynamic Reservation Protocol IEs */ + UWB_RC_CMD_SET_IE = 23, /* Information Element management */ + UWB_RC_CMD_SET_NOTIFICATION_FILTER = 24, + UWB_RC_CMD_SET_TX_POWER = 25, + UWB_RC_CMD_SLEEP = 26, + UWB_RC_CMD_START_BEACON = 27, + UWB_RC_CMD_STOP_BEACON = 28, + UWB_RC_CMD_BP_MERGE = 29, + UWB_RC_CMD_SEND_COMMAND_FRAME = 30, + UWB_RC_CMD_SET_ASIE_NOTIF = 31, +}; + +/* Notifications from the radio controller */ +enum uwb_rc_evt { + UWB_RC_EVT_IE_RCV = 0, + UWB_RC_EVT_BEACON = 1, + UWB_RC_EVT_BEACON_SIZE = 2, + UWB_RC_EVT_BPOIE_CHANGE = 3, + UWB_RC_EVT_BP_SLOT_CHANGE = 4, + UWB_RC_EVT_BP_SWITCH_IE_RCV = 5, + UWB_RC_EVT_DEV_ADDR_CONFLICT = 6, + UWB_RC_EVT_DRP_AVAIL = 7, + UWB_RC_EVT_DRP = 8, + UWB_RC_EVT_BP_SWITCH_STATUS = 9, + UWB_RC_EVT_CMD_FRAME_RCV = 10, + UWB_RC_EVT_CHANNEL_CHANGE_IE_RCV = 11, + /* Events (command responses) use the same code as the command */ + UWB_RC_EVT_UNKNOWN_CMD_RCV = 65535, +}; + +enum uwb_rc_extended_type_1_cmd { + UWB_RC_SET_DAA_ENERGY_MASK = 32, + UWB_RC_SET_NOTIFICATION_FILTER_EX = 33, +}; + +enum uwb_rc_extended_type_1_evt { + UWB_RC_DAA_ENERGY_DETECTED = 0, +}; + +/* Radio Control Result Code. [WHCI] table 3-3. */ +enum { + UWB_RC_RES_SUCCESS = 0, + UWB_RC_RES_FAIL, + UWB_RC_RES_FAIL_HARDWARE, + UWB_RC_RES_FAIL_NO_SLOTS, + UWB_RC_RES_FAIL_BEACON_TOO_LARGE, + UWB_RC_RES_FAIL_INVALID_PARAMETER, + UWB_RC_RES_FAIL_UNSUPPORTED_PWR_LEVEL, + UWB_RC_RES_FAIL_INVALID_IE_DATA, + UWB_RC_RES_FAIL_BEACON_SIZE_EXCEEDED, + UWB_RC_RES_FAIL_CANCELLED, + UWB_RC_RES_FAIL_INVALID_STATE, + UWB_RC_RES_FAIL_INVALID_SIZE, + UWB_RC_RES_FAIL_ACK_NOT_RECEIVED, + UWB_RC_RES_FAIL_NO_MORE_ASIE_NOTIF, + UWB_RC_RES_FAIL_TIME_OUT = 255, +}; + +/* Confirm event. [WHCI] section 3.1.3.1 etc. */ +struct uwb_rc_evt_confirm { + struct uwb_rceb rceb; + u8 bResultCode; +} __attribute__((packed)); + +/* Device Address Management event. [WHCI] section 3.1.3.2. */ +struct uwb_rc_evt_dev_addr_mgmt { + struct uwb_rceb rceb; + u8 baAddr[6]; + u8 bResultCode; +} __attribute__((packed)); + + +/* Get IE Event. [WHCI] section 3.1.3.3. */ +struct uwb_rc_evt_get_ie { + struct uwb_rceb rceb; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/* Set DRP IE Event. [WHCI] section 3.1.3.7. */ +struct uwb_rc_evt_set_drp_ie { + struct uwb_rceb rceb; + __le16 wRemainingSpace; + u8 bResultCode; +} __attribute__((packed)); + +/* Set IE Event. [WHCI] section 3.1.3.8. */ +struct uwb_rc_evt_set_ie { + struct uwb_rceb rceb; + __le16 RemainingSpace; + u8 bResultCode; +} __attribute__((packed)); + +/* Scan command. [WHCI] 3.1.3.5. */ +struct uwb_rc_cmd_scan { + struct uwb_rccb rccb; + u8 bChannelNumber; + u8 bScanState; + __le16 wStartTime; +} __attribute__((packed)); + +/* Set DRP IE command. [WHCI] section 3.1.3.7. */ +struct uwb_rc_cmd_set_drp_ie { + struct uwb_rccb rccb; + __le16 wIELength; + struct uwb_ie_drp IEData[]; +} __attribute__((packed)); + +/* Set IE command. [WHCI] section 3.1.3.8. */ +struct uwb_rc_cmd_set_ie { + struct uwb_rccb rccb; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/* Set DAA Energy Mask event. [WHCI 0.96] section 3.1.3.17. */ +struct uwb_rc_evt_set_daa_energy_mask { + struct uwb_rceb rceb; + __le16 wLength; + u8 result; +} __attribute__((packed)); + +/* Set Notification Filter Extended event. [WHCI 0.96] section 3.1.3.18. */ +struct uwb_rc_evt_set_notification_filter_ex { + struct uwb_rceb rceb; + __le16 wLength; + u8 result; +} __attribute__((packed)); + +/* IE Received notification. [WHCI] section 3.1.4.1. */ +struct uwb_rc_evt_ie_rcv { + struct uwb_rceb rceb; + struct uwb_dev_addr SrcAddr; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/* Type of the received beacon. [WHCI] section 3.1.4.2. */ +enum uwb_rc_beacon_type { + UWB_RC_BEACON_TYPE_SCAN = 0, + UWB_RC_BEACON_TYPE_NEIGHBOR, + UWB_RC_BEACON_TYPE_OL_ALIEN, + UWB_RC_BEACON_TYPE_NOL_ALIEN, +}; + +/* Beacon received notification. [WHCI] 3.1.4.2. */ +struct uwb_rc_evt_beacon { + struct uwb_rceb rceb; + u8 bChannelNumber; + u8 bBeaconType; + __le16 wBPSTOffset; + u8 bLQI; + u8 bRSSI; + __le16 wBeaconInfoLength; + u8 BeaconInfo[]; +} __attribute__((packed)); + + +/* Beacon Size Change notification. [WHCI] section 3.1.4.3 */ +struct uwb_rc_evt_beacon_size { + struct uwb_rceb rceb; + __le16 wNewBeaconSize; +} __attribute__((packed)); + + +/* BPOIE Change notification. [WHCI] section 3.1.4.4. */ +struct uwb_rc_evt_bpoie_change { + struct uwb_rceb rceb; + __le16 wBPOIELength; + u8 BPOIE[]; +} __attribute__((packed)); + + +/* Beacon Slot Change notification. [WHCI] section 3.1.4.5. */ +struct uwb_rc_evt_bp_slot_change { + struct uwb_rceb rceb; + u8 slot_info; +} __attribute__((packed)); + +static inline int uwb_rc_evt_bp_slot_change_slot_num( + const struct uwb_rc_evt_bp_slot_change *evt) +{ + return evt->slot_info & 0x7f; +} + +static inline int uwb_rc_evt_bp_slot_change_no_slot( + const struct uwb_rc_evt_bp_slot_change *evt) +{ + return (evt->slot_info & 0x80) >> 7; +} + +/* BP Switch IE Received notification. [WHCI] section 3.1.4.6. */ +struct uwb_rc_evt_bp_switch_ie_rcv { + struct uwb_rceb rceb; + struct uwb_dev_addr wSrcAddr; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/* DevAddr Conflict notification. [WHCI] section 3.1.4.7. */ +struct uwb_rc_evt_dev_addr_conflict { + struct uwb_rceb rceb; +} __attribute__((packed)); + +/* DRP notification. [WHCI] section 3.1.4.9. */ +struct uwb_rc_evt_drp { + struct uwb_rceb rceb; + struct uwb_dev_addr src_addr; + u8 reason; + u8 beacon_slot_number; + __le16 ie_length; + u8 ie_data[]; +} __attribute__((packed)); + +static inline enum uwb_drp_notif_reason uwb_rc_evt_drp_reason(struct uwb_rc_evt_drp *evt) +{ + return evt->reason & 0x0f; +} + + +/* DRP Availability Change notification. [WHCI] section 3.1.4.8. */ +struct uwb_rc_evt_drp_avail { + struct uwb_rceb rceb; + DECLARE_BITMAP(bmp, UWB_NUM_MAS); +} __attribute__((packed)); + +/* BP switch status notification. [WHCI] section 3.1.4.10. */ +struct uwb_rc_evt_bp_switch_status { + struct uwb_rceb rceb; + u8 status; + u8 slot_offset; + __le16 bpst_offset; + u8 move_countdown; +} __attribute__((packed)); + +/* Command Frame Received notification. [WHCI] section 3.1.4.11. */ +struct uwb_rc_evt_cmd_frame_rcv { + struct uwb_rceb rceb; + __le16 receive_time; + struct uwb_dev_addr wSrcAddr; + struct uwb_dev_addr wDstAddr; + __le16 control; + __le16 reserved; + __le16 dataLength; + u8 data[]; +} __attribute__((packed)); + +/* Channel Change IE Received notification. [WHCI] section 3.1.4.12. */ +struct uwb_rc_evt_channel_change_ie_rcv { + struct uwb_rceb rceb; + struct uwb_dev_addr wSrcAddr; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/* DAA Energy Detected notification. [WHCI 0.96] section 3.1.4.14. */ +struct uwb_rc_evt_daa_energy_detected { + struct uwb_rceb rceb; + __le16 wLength; + u8 bandID; + u8 reserved; + u8 toneBmp[16]; +} __attribute__((packed)); + + +/** + * Radio Control Interface Class Descriptor + * + * WUSB 1.0 [8.6.1.2] + */ +struct uwb_rc_control_intf_class_desc { + u8 bLength; + u8 bDescriptorType; + __le16 bcdRCIVersion; +} __attribute__((packed)); + +#endif /* #ifndef __LINUX__UWB_SPEC_H__ */ diff --git a/include/linux/wlp.h b/include/linux/wlp.h new file mode 100644 index 00000000000..033545e145c --- /dev/null +++ b/include/linux/wlp.h @@ -0,0 +1,735 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2005-2006 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * - Does not (yet) include support for WLP control frames + * WLP Draft 0.99 [6.5]. + * + * A visual representation of the data structures. + * + * wssidB wssidB + * ^ ^ + * | | + * wssidA wssidA + * wlp interface { ^ ^ + * ... | | + * ... ... wssid wssid ... + * wlp --- ... | | + * }; neighbors --> neighbA --> neighbB + * ... + * wss + * ... + * eda cache --> neighborA --> neighborB --> neighborC ... + */ + +#ifndef __LINUX__WLP_H_ +#define __LINUX__WLP_H_ + +#include +#include +#include +#include + +/** + * WLP Protocol ID + * WLP Draft 0.99 [6.2] + * + * The MUX header for all WLP frames + */ +#define WLP_PROTOCOL_ID 0x0100 + +/** + * WLP Version + * WLP version placed in the association frames (WLP 0.99 [6.6]) + */ +#define WLP_VERSION 0x10 + +/** + * Bytes needed to print UUID as string + */ +#define WLP_WSS_UUID_STRSIZE 48 + +/** + * Bytes needed to print nonce as string + */ +#define WLP_WSS_NONCE_STRSIZE 48 + + +/** + * Size used for WLP name size + * + * The WSS name is set to 65 bytes, 1 byte larger than the maximum + * allowed by the WLP spec. This is to have a null terminated string + * for display to the user. A maximum of 64 bytes will still be used + * when placing the WSS name field in association frames. + */ +#define WLP_WSS_NAME_SIZE 65 + +/** + * Number of bytes added by WLP to data frame + * + * A data frame transmitted from a host will be placed in a Standard or + * Abbreviated WLP frame. These have an extra 4 bytes of header (struct + * wlp_frame_std_abbrv_hdr). + * When the stack sends this data frame for transmission it needs to ensure + * there is enough headroom for this header. + */ +#define WLP_DATA_HLEN 4 + +/** + * State of device regarding WLP Service Set + * + * WLP_WSS_STATE_NONE: the host does not participate in any WSS + * WLP_WSS_STATE_PART_ENROLLED: used as part of the enrollment sequence + * ("Partial Enroll"). This state is used to + * indicate the first part of enrollment that is + * unsecure. If the WSS is unsecure then the + * state will promptly go to WLP_WSS_STATE_ENROLLED, + * if the WSS is not secure then the enrollment + * procedure is a few more steps before we are + * enrolled. + * WLP_WSS_STATE_ENROLLED: the host is enrolled in a WSS + * WLP_WSS_STATE_ACTIVE: WSS is activated + * WLP_WSS_STATE_CONNECTED: host is connected to neighbor in WSS + * + */ +enum wlp_wss_state { + WLP_WSS_STATE_NONE = 0, + WLP_WSS_STATE_PART_ENROLLED, + WLP_WSS_STATE_ENROLLED, + WLP_WSS_STATE_ACTIVE, + WLP_WSS_STATE_CONNECTED, +}; + +/** + * WSS Secure status + * WLP 0.99 Table 6 + * + * Set to one if the WSS is secure, zero if it is not secure + */ +enum wlp_wss_sec_status { + WLP_WSS_UNSECURE = 0, + WLP_WSS_SECURE, +}; + +/** + * WLP frame type + * WLP Draft 0.99 [6.2 Table 1] + */ +enum wlp_frame_type { + WLP_FRAME_STANDARD = 0, + WLP_FRAME_ABBREVIATED, + WLP_FRAME_CONTROL, + WLP_FRAME_ASSOCIATION, +}; + +/** + * WLP Association Message Type + * WLP Draft 0.99 [6.6.1.2 Table 8] + */ +enum wlp_assoc_type { + WLP_ASSOC_D1 = 2, + WLP_ASSOC_D2 = 3, + WLP_ASSOC_M1 = 4, + WLP_ASSOC_M2 = 5, + WLP_ASSOC_M3 = 7, + WLP_ASSOC_M4 = 8, + WLP_ASSOC_M5 = 9, + WLP_ASSOC_M6 = 10, + WLP_ASSOC_M7 = 11, + WLP_ASSOC_M8 = 12, + WLP_ASSOC_F0 = 14, + WLP_ASSOC_E1 = 32, + WLP_ASSOC_E2 = 33, + WLP_ASSOC_C1 = 34, + WLP_ASSOC_C2 = 35, + WLP_ASSOC_C3 = 36, + WLP_ASSOC_C4 = 37, +}; + +/** + * WLP Attribute Type + * WLP Draft 0.99 [6.6.1 Table 6] + */ +enum wlp_attr_type { + WLP_ATTR_AUTH = 0x1005, /* Authenticator */ + WLP_ATTR_DEV_NAME = 0x1011, /* Device Name */ + WLP_ATTR_DEV_PWD_ID = 0x1012, /* Device Password ID */ + WLP_ATTR_E_HASH1 = 0x1014, /* E-Hash1 */ + WLP_ATTR_E_HASH2 = 0x1015, /* E-Hash2 */ + WLP_ATTR_E_SNONCE1 = 0x1016, /* E-SNonce1 */ + WLP_ATTR_E_SNONCE2 = 0x1017, /* E-SNonce2 */ + WLP_ATTR_ENCR_SET = 0x1018, /* Encrypted Settings */ + WLP_ATTR_ENRL_NONCE = 0x101A, /* Enrollee Nonce */ + WLP_ATTR_KEYWRAP_AUTH = 0x101E, /* Key Wrap Authenticator */ + WLP_ATTR_MANUF = 0x1021, /* Manufacturer */ + WLP_ATTR_MSG_TYPE = 0x1022, /* Message Type */ + WLP_ATTR_MODEL_NAME = 0x1023, /* Model Name */ + WLP_ATTR_MODEL_NR = 0x1024, /* Model Number */ + WLP_ATTR_PUB_KEY = 0x1032, /* Public Key */ + WLP_ATTR_REG_NONCE = 0x1039, /* Registrar Nonce */ + WLP_ATTR_R_HASH1 = 0x103D, /* R-Hash1 */ + WLP_ATTR_R_HASH2 = 0x103E, /* R-Hash2 */ + WLP_ATTR_R_SNONCE1 = 0x103F, /* R-SNonce1 */ + WLP_ATTR_R_SNONCE2 = 0x1040, /* R-SNonce2 */ + WLP_ATTR_SERIAL = 0x1042, /* Serial number */ + WLP_ATTR_UUID_E = 0x1047, /* UUID-E */ + WLP_ATTR_UUID_R = 0x1048, /* UUID-R */ + WLP_ATTR_PRI_DEV_TYPE = 0x1054, /* Primary Device Type */ + WLP_ATTR_SEC_DEV_TYPE = 0x1055, /* Secondary Device Type */ + WLP_ATTR_PORT_DEV = 0x1056, /* Portable Device */ + WLP_ATTR_APP_EXT = 0x1058, /* Application Extension */ + WLP_ATTR_WLP_VER = 0x2000, /* WLP Version */ + WLP_ATTR_WSSID = 0x2001, /* WSSID */ + WLP_ATTR_WSS_NAME = 0x2002, /* WSS Name */ + WLP_ATTR_WSS_SEC_STAT = 0x2003, /* WSS Secure Status */ + WLP_ATTR_WSS_BCAST = 0x2004, /* WSS Broadcast Address */ + WLP_ATTR_WSS_M_KEY = 0x2005, /* WSS Master Key */ + WLP_ATTR_ACC_ENRL = 0x2006, /* Accepting Enrollment */ + WLP_ATTR_WSS_INFO = 0x2007, /* WSS Information */ + WLP_ATTR_WSS_SEL_MTHD = 0x2008, /* WSS Selection Method */ + WLP_ATTR_ASSC_MTHD_LIST = 0x2009, /* Association Methods List */ + WLP_ATTR_SEL_ASSC_MTHD = 0x200A, /* Selected Association Method */ + WLP_ATTR_ENRL_HASH_COMM = 0x200B, /* Enrollee Hash Commitment */ + WLP_ATTR_WSS_TAG = 0x200C, /* WSS Tag */ + WLP_ATTR_WSS_VIRT = 0x200D, /* WSS Virtual EUI-48 */ + WLP_ATTR_WLP_ASSC_ERR = 0x200E, /* WLP Association Error */ + WLP_ATTR_VNDR_EXT = 0x200F, /* Vendor Extension */ +}; + +/** + * WLP Category ID of primary/secondary device + * WLP Draft 0.99 [6.6.1.8 Table 12] + */ +enum wlp_dev_category_id { + WLP_DEV_CAT_COMPUTER = 1, + WLP_DEV_CAT_INPUT, + WLP_DEV_CAT_PRINT_SCAN_FAX_COPIER, + WLP_DEV_CAT_CAMERA, + WLP_DEV_CAT_STORAGE, + WLP_DEV_CAT_INFRASTRUCTURE, + WLP_DEV_CAT_DISPLAY, + WLP_DEV_CAT_MULTIM, + WLP_DEV_CAT_GAMING, + WLP_DEV_CAT_TELEPHONE, + WLP_DEV_CAT_OTHER = 65535, +}; + +/** + * WLP WSS selection method + * WLP Draft 0.99 [6.6.1.6 Table 10] + */ +enum wlp_wss_sel_mthd { + WLP_WSS_ENRL_SELECT = 1, /* Enrollee selects */ + WLP_WSS_REG_SELECT, /* Registrar selects */ +}; + +/** + * WLP association error values + * WLP Draft 0.99 [6.6.1.5 Table 9] + */ +enum wlp_assc_error { + WLP_ASSOC_ERROR_NONE, + WLP_ASSOC_ERROR_AUTH, /* Authenticator Failure */ + WLP_ASSOC_ERROR_ROGUE, /* Rogue activity suspected */ + WLP_ASSOC_ERROR_BUSY, /* Device busy */ + WLP_ASSOC_ERROR_LOCK, /* Setup Locked */ + WLP_ASSOC_ERROR_NOT_READY, /* Registrar not ready */ + WLP_ASSOC_ERROR_INV, /* Invalid WSS selection */ + WLP_ASSOC_ERROR_MSG_TIME, /* Message timeout */ + WLP_ASSOC_ERROR_ENR_TIME, /* Enrollment session timeout */ + WLP_ASSOC_ERROR_PW, /* Device password invalid */ + WLP_ASSOC_ERROR_VER, /* Unsupported version */ + WLP_ASSOC_ERROR_INT, /* Internal error */ + WLP_ASSOC_ERROR_UNDEF, /* Undefined error */ + WLP_ASSOC_ERROR_NUM, /* Numeric comparison failure */ + WLP_ASSOC_ERROR_WAIT, /* Waiting for user input */ +}; + +/** + * WLP Parameters + * WLP 0.99 [7.7] + */ +enum wlp_parameters { + WLP_PER_MSG_TIMEOUT = 15, /* Seconds to wait for response to + association message. */ +}; + +/** + * WLP IE + * + * The WLP IE should be included in beacons by all devices. + * + * The driver can set only a few of the fields in this information element, + * most fields are managed by the device self. When the driver needs to set + * a field it will only provide values for the fields of interest, the rest + * will be filled with zeroes. The fields of interest are: + * + * Element ID + * Length + * Capabilities (only to include WSSID Hash list length) + * WSSID Hash List fields + * + * WLP 0.99 [6.7] + * + * Only the fields that will be used are detailed in this structure, rest + * are not detailed or marked as "notused". + */ +struct wlp_ie { + struct uwb_ie_hdr hdr; + __le16 capabilities; + __le16 cycle_param; + __le16 acw_anchor_addr; + u8 wssid_hash_list[]; +} __attribute__((packed)); + +static inline int wlp_ie_hash_length(struct wlp_ie *ie) +{ + return (le16_to_cpu(ie->capabilities) >> 12) & 0xf; +} + +static inline void wlp_ie_set_hash_length(struct wlp_ie *ie, int hash_length) +{ + u16 caps = le16_to_cpu(ie->capabilities); + caps = (caps & ~(0xf << 12)) | (hash_length << 12); + ie->capabilities = cpu_to_le16(caps); +} + +/** + * WLP nonce + * WLP Draft 0.99 [6.6.1 Table 6] + * + * A 128-bit random number often used (E-SNonce1, E-SNonce2, Enrollee + * Nonce, Registrar Nonce, R-SNonce1, R-SNonce2). It is passed to HW so + * it is packed. + */ +struct wlp_nonce { + u8 data[16]; +} __attribute__((packed)); + +/** + * WLP UUID + * WLP Draft 0.99 [6.6.1 Table 6] + * + * Universally Unique Identifier (UUID) encoded as an octet string in the + * order the octets are shown in string representation in RFC4122. A UUID + * is often used (UUID-E, UUID-R, WSSID). It is passed to HW so it is packed. + */ +struct wlp_uuid { + u8 data[16]; +} __attribute__((packed)); + + +/** + * Primary and secondary device type attributes + * WLP Draft 0.99 [6.6.1.8] + */ +struct wlp_dev_type { + enum wlp_dev_category_id category:16; + u8 OUI[3]; + u8 OUIsubdiv; + __le16 subID; +} __attribute__((packed)); + +/** + * WLP frame header + * WLP Draft 0.99 [6.2] + */ +struct wlp_frame_hdr { + __le16 mux_hdr; /* WLP_PROTOCOL_ID */ + enum wlp_frame_type type:8; +} __attribute__((packed)); + +/** + * WLP attribute field header + * WLP Draft 0.99 [6.6.1] + * + * Header of each attribute found in an association frame + */ +struct wlp_attr_hdr { + __le16 type; + __le16 length; +} __attribute__((packed)); + +/** + * Device information commonly used together + * + * Each of these device information elements has a specified range in which it + * should fit (WLP 0.99 [Table 6]). This range provided in the spec does not + * include the termination null '\0' character (when used in the + * association protocol the attribute fields are accompanied + * with a "length" field so the full range from the spec can be used for + * the value). We thus allocate an extra byte to be able to store a string + * of max length with a terminating '\0'. + */ +struct wlp_device_info { + char name[33]; + char model_name[33]; + char manufacturer[65]; + char model_nr[33]; + char serial[33]; + struct wlp_dev_type prim_dev_type; +}; + +/** + * Macros for the WLP attributes + * + * There are quite a few attributes (total is 43). The attribute layout can be + * in one of three categories: one value, an array, an enum forced to 8 bits. + * These macros help with their definitions. + */ +#define wlp_attr(type, name) \ +struct wlp_attr_##name { \ + struct wlp_attr_hdr hdr; \ + type name; \ +} __attribute__((packed)); + +#define wlp_attr_array(type, name) \ +struct wlp_attr_##name { \ + struct wlp_attr_hdr hdr; \ + type name[]; \ +} __attribute__((packed)); + +/** + * WLP association attribute fields + * WLP Draft 0.99 [6.6.1 Table 6] + * + * Attributes appear in same order as the Table in the spec + * FIXME Does not define all attributes yet + */ + +/* Device name: Friendly name of sending device */ +wlp_attr_array(u8, dev_name) + +/* Enrollee Nonce: Random number generated by enrollee for an enrollment + * session */ +wlp_attr(struct wlp_nonce, enonce) + +/* Manufacturer name: Name of manufacturer of the sending device */ +wlp_attr_array(u8, manufacturer) + +/* WLP Message Type */ +wlp_attr(u8, msg_type) + +/* WLP Model name: Model name of sending device */ +wlp_attr_array(u8, model_name) + +/* WLP Model number: Model number of sending device */ +wlp_attr_array(u8, model_nr) + +/* Registrar Nonce: Random number generated by registrar for an enrollment + * session */ +wlp_attr(struct wlp_nonce, rnonce) + +/* Serial number of device */ +wlp_attr_array(u8, serial) + +/* UUID of enrollee */ +wlp_attr(struct wlp_uuid, uuid_e) + +/* UUID of registrar */ +wlp_attr(struct wlp_uuid, uuid_r) + +/* WLP Primary device type */ +wlp_attr(struct wlp_dev_type, prim_dev_type) + +/* WLP Secondary device type */ +wlp_attr(struct wlp_dev_type, sec_dev_type) + +/* WLP protocol version */ +wlp_attr(u8, version) + +/* WLP service set identifier */ +wlp_attr(struct wlp_uuid, wssid) + +/* WLP WSS name */ +wlp_attr_array(u8, wss_name) + +/* WLP WSS Secure Status */ +wlp_attr(u8, wss_sec_status) + +/* WSS Broadcast Address */ +wlp_attr(struct uwb_mac_addr, wss_bcast) + +/* WLP Accepting Enrollment */ +wlp_attr(u8, accept_enrl) + +/** + * WSS information attributes + * WLP Draft 0.99 [6.6.3 Table 15] + */ +struct wlp_wss_info { + struct wlp_attr_wssid wssid; + struct wlp_attr_wss_name name; + struct wlp_attr_accept_enrl accept; + struct wlp_attr_wss_sec_status sec_stat; + struct wlp_attr_wss_bcast bcast; +} __attribute__((packed)); + +/* WLP WSS Information */ +wlp_attr_array(struct wlp_wss_info, wss_info) + +/* WLP WSS Selection method */ +wlp_attr(u8, wss_sel_mthd) + +/* WLP WSS tag */ +wlp_attr(u8, wss_tag) + +/* WSS Virtual Address */ +wlp_attr(struct uwb_mac_addr, wss_virt) + +/* WLP association error */ +wlp_attr(u8, wlp_assc_err) + +/** + * WLP standard and abbreviated frames + * + * WLP Draft 0.99 [6.3] and [6.4] + * + * The difference between the WLP standard frame and the WLP + * abbreviated frame is that the standard frame includes the src + * and dest addresses from the Ethernet header, the abbreviated frame does + * not. + * The src/dest (as well as the type/length and client data) are already + * defined as part of the Ethernet header, we do not do this here. + * From this perspective the standard and abbreviated frames appear the + * same - they will be treated differently though. + * + * The size of this header is also captured in WLP_DATA_HLEN to enable + * interfaces to prepare their headroom. + */ +struct wlp_frame_std_abbrv_hdr { + struct wlp_frame_hdr hdr; + u8 tag; +} __attribute__((packed)); + +/** + * WLP association frames + * + * WLP Draft 0.99 [6.6] + */ +struct wlp_frame_assoc { + struct wlp_frame_hdr hdr; + enum wlp_assoc_type type:8; + struct wlp_attr_version version; + struct wlp_attr_msg_type msg_type; + u8 attr[]; +} __attribute__((packed)); + +/* Ethernet to dev address mapping */ +struct wlp_eda { + spinlock_t lock; + struct list_head cache; /* Eth<->Dev Addr cache */ +}; + +/** + * WSS information temporary storage + * + * This information is only stored temporarily during discovery. It should + * not be stored unless the device is enrolled in the advertised WSS. This + * is done mainly because we follow the letter of the spec in this regard. + * See WLP 0.99 [7.2.3]. + * When the device does become enrolled in a WSS the WSS information will + * be stored as part of the more comprehensive struct wlp_wss. + */ +struct wlp_wss_tmp_info { + char name[WLP_WSS_NAME_SIZE]; + u8 accept_enroll; + u8 sec_status; + struct uwb_mac_addr bcast; +}; + +struct wlp_wssid_e { + struct list_head node; + struct wlp_uuid wssid; + struct wlp_wss_tmp_info *info; +}; + +/** + * A cache entry of WLP neighborhood + * + * @node: head of list is wlp->neighbors + * @wssid: list of wssids of this neighbor, element is wlp_wssid_e + * @info: temporary storage for information learned during discovery. This + * storage is used together with the wssid_e temporary storage + * during discovery. + */ +struct wlp_neighbor_e { + struct list_head node; + struct wlp_uuid uuid; + struct uwb_dev *uwb_dev; + struct list_head wssid; /* Elements are wlp_wssid_e */ + struct wlp_device_info *info; +}; + +struct wlp; +/** + * Information for an association session in progress. + * + * @exp_message: The type of the expected message. Both this message and a + * F0 message (which can be sent in response to any + * association frame) will be accepted as a valid message for + * this session. + * @cb: The function that will be called upon receipt of this + * message. + * @cb_priv: Private data of callback + * @data: Data used in association process (always a sk_buff?) + * @neighbor: Address of neighbor with which association session is in + * progress. + */ +struct wlp_session { + enum wlp_assoc_type exp_message; + void (*cb)(struct wlp *); + void *cb_priv; + void *data; + struct uwb_dev_addr neighbor_addr; +}; + +/** + * WLP Service Set + * + * @mutex: used to protect entire WSS structure. + * + * @name: The WSS name is set to 65 bytes, 1 byte larger than the maximum + * allowed by the WLP spec. This is to have a null terminated string + * for display to the user. A maximum of 64 bytes will still be used + * when placing the WSS name field in association frames. + * + * @accept_enroll: Accepting enrollment: Set to one if registrar is + * accepting enrollment in WSS, or zero otherwise. + * + * Global and local information for each WSS in which we are enrolled. + * WLP 0.99 Section 7.2.1 and Section 7.2.2 + */ +struct wlp_wss { + struct mutex mutex; + struct kobject kobj; + /* Global properties. */ + struct wlp_uuid wssid; + u8 hash; + char name[WLP_WSS_NAME_SIZE]; + struct uwb_mac_addr bcast; + u8 secure_status:1; + u8 master_key[16]; + /* Local properties. */ + u8 tag; + struct uwb_mac_addr virtual_addr; + /* Extra */ + u8 accept_enroll:1; + enum wlp_wss_state state; +}; + +/** + * WLP main structure + * @mutex: protect changes to WLP structure. We only allow changes to the + * uuid, so currently this mutex only protects this field. + */ +struct wlp { + struct mutex mutex; + struct uwb_rc *rc; /* UWB radio controller */ + struct uwb_pal pal; + struct wlp_eda eda; + struct wlp_uuid uuid; + struct wlp_session *session; + struct wlp_wss wss; + struct mutex nbmutex; /* Neighbor mutex protects neighbors list */ + struct list_head neighbors; /* Elements are wlp_neighbor_e */ + struct uwb_notifs_handler uwb_notifs_handler; + struct wlp_device_info *dev_info; + void (*fill_device_info)(struct wlp *wlp, struct wlp_device_info *info); + int (*xmit_frame)(struct wlp *, struct sk_buff *, + struct uwb_dev_addr *); + void (*stop_queue)(struct wlp *); + void (*start_queue)(struct wlp *); +}; + +/* sysfs */ + + +struct wlp_wss_attribute { + struct attribute attr; + ssize_t (*show)(struct wlp_wss *wss, char *buf); + ssize_t (*store)(struct wlp_wss *wss, const char *buf, size_t count); +}; + +#define WSS_ATTR(_name, _mode, _show, _store) \ +static struct wlp_wss_attribute wss_attr_##_name = __ATTR(_name, _mode, \ + _show, _store) + +extern int wlp_setup(struct wlp *, struct uwb_rc *); +extern void wlp_remove(struct wlp *); +extern ssize_t wlp_neighborhood_show(struct wlp *, char *); +extern int wlp_wss_setup(struct net_device *, struct wlp_wss *); +extern void wlp_wss_remove(struct wlp_wss *); +extern ssize_t wlp_wss_activate_show(struct wlp_wss *, char *); +extern ssize_t wlp_wss_activate_store(struct wlp_wss *, const char *, size_t); +extern ssize_t wlp_eda_show(struct wlp *, char *); +extern ssize_t wlp_eda_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_uuid_show(struct wlp *, char *); +extern ssize_t wlp_uuid_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_dev_name_show(struct wlp *, char *); +extern ssize_t wlp_dev_name_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_dev_manufacturer_show(struct wlp *, char *); +extern ssize_t wlp_dev_manufacturer_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_dev_model_name_show(struct wlp *, char *); +extern ssize_t wlp_dev_model_name_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_dev_model_nr_show(struct wlp *, char *); +extern ssize_t wlp_dev_model_nr_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_dev_serial_show(struct wlp *, char *); +extern ssize_t wlp_dev_serial_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_dev_prim_category_show(struct wlp *, char *); +extern ssize_t wlp_dev_prim_category_store(struct wlp *, const char *, + size_t); +extern ssize_t wlp_dev_prim_OUI_show(struct wlp *, char *); +extern ssize_t wlp_dev_prim_OUI_store(struct wlp *, const char *, size_t); +extern ssize_t wlp_dev_prim_OUI_sub_show(struct wlp *, char *); +extern ssize_t wlp_dev_prim_OUI_sub_store(struct wlp *, const char *, + size_t); +extern ssize_t wlp_dev_prim_subcat_show(struct wlp *, char *); +extern ssize_t wlp_dev_prim_subcat_store(struct wlp *, const char *, + size_t); +extern int wlp_receive_frame(struct device *, struct wlp *, struct sk_buff *, + struct uwb_dev_addr *); +extern int wlp_prepare_tx_frame(struct device *, struct wlp *, + struct sk_buff *, struct uwb_dev_addr *); +void wlp_reset_all(struct wlp *wlp); + +/** + * Initialize WSS + */ +static inline +void wlp_wss_init(struct wlp_wss *wss) +{ + mutex_init(&wss->mutex); +} + +static inline +void wlp_init(struct wlp *wlp) +{ + INIT_LIST_HEAD(&wlp->neighbors); + mutex_init(&wlp->mutex); + mutex_init(&wlp->nbmutex); + wlp_wss_init(&wlp->wss); +} + + +#endif /* #ifndef __LINUX__WLP_H_ */ -- cgit v1.2.3 From 183b9b592a622a7719ee38e275fd7ff3aaf74d0d Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:06 +0100 Subject: uwb: add the UWB stack (core files) UWB device and radio controller device and event management. Signed-off-by: David Vrabel --- drivers/uwb/driver.c | 142 +++++++++++++ drivers/uwb/lc-dev.c | 492 ++++++++++++++++++++++++++++++++++++++++++++ drivers/uwb/lc-rc.c | 501 +++++++++++++++++++++++++++++++++++++++++++++ drivers/uwb/pal.c | 71 +++++++ drivers/uwb/uwb-internal.h | 306 +++++++++++++++++++++++++++ drivers/uwb/uwbd.c | 427 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1939 insertions(+) create mode 100644 drivers/uwb/driver.c create mode 100644 drivers/uwb/lc-dev.c create mode 100644 drivers/uwb/lc-rc.c create mode 100644 drivers/uwb/pal.c create mode 100644 drivers/uwb/uwb-internal.h create mode 100644 drivers/uwb/uwbd.c diff --git a/drivers/uwb/driver.c b/drivers/uwb/driver.c new file mode 100644 index 00000000000..7eee8e40830 --- /dev/null +++ b/drivers/uwb/driver.c @@ -0,0 +1,142 @@ +/* + * Ultra Wide Band + * Driver initialization, etc + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * Life cycle: FIXME: explain + * + * UWB radio controller: + * + * 1. alloc a uwb_rc, zero it + * 2. call uwb_rc_init() on it to set it up + ops (won't do any + * kind of allocation) + * 3. register (now it is owned by the UWB stack--deregister before + * freeing/destroying). + * 4. It lives on it's own now (UWB stack handles)--when it + * disconnects, call unregister() + * 5. free it. + * + * Make sure you have a reference to the uwb_rc before calling + * any of the UWB API functions. + * + * TODO: + * + * 1. Locking and life cycle management is crappy still. All entry + * points to the UWB HCD API assume you have a reference on the + * uwb_rc structure and that it won't go away. They mutex lock it + * before doing anything. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "uwb-internal.h" + + +/* UWB stack attributes (or 'global' constants) */ + + +/** + * If a beacon dissapears for longer than this, then we consider the + * device who was represented by that beacon to be gone. + * + * ECMA-368[17.2.3, last para] establishes that a device must not + * consider a device to be its neighbour if he doesn't receive a beacon + * for more than mMaxLostBeacons. mMaxLostBeacons is defined in + * ECMA-368[17.16] as 3; because we can get only one beacon per + * superframe, that'd be 3 * 65ms = 195 ~ 200 ms. Let's give it time + * for jitter and stuff and make it 500 ms. + */ +unsigned long beacon_timeout_ms = 500; + +static +ssize_t beacon_timeout_ms_show(struct class *class, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%lu\n", beacon_timeout_ms); +} + +static +ssize_t beacon_timeout_ms_store(struct class *class, + const char *buf, size_t size) +{ + unsigned long bt; + ssize_t result; + result = sscanf(buf, "%lu", &bt); + if (result != 1) + return -EINVAL; + beacon_timeout_ms = bt; + return size; +} + +static struct class_attribute uwb_class_attrs[] = { + __ATTR(beacon_timeout_ms, S_IWUSR | S_IRUGO, + beacon_timeout_ms_show, beacon_timeout_ms_store), + __ATTR_NULL, +}; + +/** Device model classes */ +struct class uwb_rc_class = { + .name = "uwb_rc", + .class_attrs = uwb_class_attrs, +}; + + +static int __init uwb_subsys_init(void) +{ + int result = 0; + + result = uwb_est_create(); + if (result < 0) { + printk(KERN_ERR "uwb: Can't initialize EST subsystem\n"); + goto error_est_init; + } + + result = class_register(&uwb_rc_class); + if (result < 0) + goto error_uwb_rc_class_register; + uwbd_start(); + return 0; + +error_uwb_rc_class_register: + uwb_est_destroy(); +error_est_init: + return result; +} +module_init(uwb_subsys_init); + +static void __exit uwb_subsys_exit(void) +{ + uwbd_stop(); + class_unregister(&uwb_rc_class); + uwb_est_destroy(); + return; +} +module_exit(uwb_subsys_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Ultra Wide Band core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/lc-dev.c b/drivers/uwb/lc-dev.c new file mode 100644 index 00000000000..a6cb8ad731a --- /dev/null +++ b/drivers/uwb/lc-dev.c @@ -0,0 +1,492 @@ +/* + * Ultra Wide Band + * Life cycle of devices + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include +#include +#include +#include +#include +#include "uwb-internal.h" + +#define D_LOCAL 1 +#include + + +/* We initialize addresses to 0xff (invalid, as it is bcast) */ +static inline void uwb_dev_addr_init(struct uwb_dev_addr *addr) +{ + memset(&addr->data, 0xff, sizeof(addr->data)); +} + +static inline void uwb_mac_addr_init(struct uwb_mac_addr *addr) +{ + memset(&addr->data, 0xff, sizeof(addr->data)); +} + +/* @returns !0 if a device @addr is a broadcast address */ +static inline int uwb_dev_addr_bcast(const struct uwb_dev_addr *addr) +{ + static const struct uwb_dev_addr bcast = { .data = { 0xff, 0xff } }; + return !uwb_dev_addr_cmp(addr, &bcast); +} + +/* + * Add callback @new to be called when an event occurs in @rc. + */ +int uwb_notifs_register(struct uwb_rc *rc, struct uwb_notifs_handler *new) +{ + if (mutex_lock_interruptible(&rc->notifs_chain.mutex)) + return -ERESTARTSYS; + list_add(&new->list_node, &rc->notifs_chain.list); + mutex_unlock(&rc->notifs_chain.mutex); + return 0; +} +EXPORT_SYMBOL_GPL(uwb_notifs_register); + +/* + * Remove event handler (callback) + */ +int uwb_notifs_deregister(struct uwb_rc *rc, struct uwb_notifs_handler *entry) +{ + if (mutex_lock_interruptible(&rc->notifs_chain.mutex)) + return -ERESTARTSYS; + list_del(&entry->list_node); + mutex_unlock(&rc->notifs_chain.mutex); + return 0; +} +EXPORT_SYMBOL_GPL(uwb_notifs_deregister); + +/* + * Notify all event handlers of a given event on @rc + * + * We are called with a valid reference to the device, or NULL if the + * event is not for a particular event (e.g., a BG join event). + */ +void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event) +{ + struct uwb_notifs_handler *handler; + if (mutex_lock_interruptible(&rc->notifs_chain.mutex)) + return; + if (!list_empty(&rc->notifs_chain.list)) { + list_for_each_entry(handler, &rc->notifs_chain.list, list_node) { + handler->cb(handler->data, uwb_dev, event); + } + } + mutex_unlock(&rc->notifs_chain.mutex); +} + +/* + * Release the backing device of a uwb_dev that has been dynamically allocated. + */ +static void uwb_dev_sys_release(struct device *dev) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + d_fnstart(4, NULL, "(dev %p uwb_dev %p)\n", dev, uwb_dev); + uwb_bce_put(uwb_dev->bce); + d_printf(0, &uwb_dev->dev, "uwb_dev %p freed\n", uwb_dev); + memset(uwb_dev, 0x69, sizeof(*uwb_dev)); + kfree(uwb_dev); + d_fnend(4, NULL, "(dev %p uwb_dev %p) = void\n", dev, uwb_dev); +} + +/* + * Initialize a UWB device instance + * + * Alloc, zero and call this function. + */ +void uwb_dev_init(struct uwb_dev *uwb_dev) +{ + mutex_init(&uwb_dev->mutex); + device_initialize(&uwb_dev->dev); + uwb_dev->dev.release = uwb_dev_sys_release; + uwb_dev_addr_init(&uwb_dev->dev_addr); + uwb_mac_addr_init(&uwb_dev->mac_addr); + bitmap_fill(uwb_dev->streams, UWB_NUM_GLOBAL_STREAMS); +} + +static ssize_t uwb_dev_EUI_48_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + char addr[UWB_ADDR_STRSIZE]; + + uwb_mac_addr_print(addr, sizeof(addr), &uwb_dev->mac_addr); + return sprintf(buf, "%s\n", addr); +} +static DEVICE_ATTR(EUI_48, S_IRUGO, uwb_dev_EUI_48_show, NULL); + +static ssize_t uwb_dev_DevAddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + char addr[UWB_ADDR_STRSIZE]; + + uwb_dev_addr_print(addr, sizeof(addr), &uwb_dev->dev_addr); + return sprintf(buf, "%s\n", addr); +} +static DEVICE_ATTR(DevAddr, S_IRUGO, uwb_dev_DevAddr_show, NULL); + +/* + * Show the BPST of this device. + * + * Calculated from the receive time of the device's beacon and it's + * slot number. + */ +static ssize_t uwb_dev_BPST_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce; + struct uwb_beacon_frame *bf; + u16 bpst; + + bce = uwb_dev->bce; + mutex_lock(&bce->mutex); + bf = (struct uwb_beacon_frame *)bce->be->BeaconInfo; + bpst = bce->be->wBPSTOffset + - (u16)(bf->Beacon_Slot_Number * UWB_BEACON_SLOT_LENGTH_US); + mutex_unlock(&bce->mutex); + + return sprintf(buf, "%d\n", bpst); +} +static DEVICE_ATTR(BPST, S_IRUGO, uwb_dev_BPST_show, NULL); + +/* + * Show the IEs a device is beaconing + * + * We need to access the beacon cache, so we just lock it really + * quick, print the IEs and unlock. + * + * We have a reference on the cache entry, so that should be + * quite safe. + */ +static ssize_t uwb_dev_IEs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + return uwb_bce_print_IEs(uwb_dev, uwb_dev->bce, buf, PAGE_SIZE); +} +static DEVICE_ATTR(IEs, S_IRUGO | S_IWUSR, uwb_dev_IEs_show, NULL); + +static ssize_t uwb_dev_LQE_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + size_t result; + + mutex_lock(&bce->mutex); + result = stats_show(&uwb_dev->bce->lqe_stats, buf); + mutex_unlock(&bce->mutex); + return result; +} + +static ssize_t uwb_dev_LQE_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + ssize_t result; + + mutex_lock(&bce->mutex); + result = stats_store(&uwb_dev->bce->lqe_stats, buf, size); + mutex_unlock(&bce->mutex); + return result; +} +static DEVICE_ATTR(LQE, S_IRUGO | S_IWUSR, uwb_dev_LQE_show, uwb_dev_LQE_store); + +static ssize_t uwb_dev_RSSI_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + size_t result; + + mutex_lock(&bce->mutex); + result = stats_show(&uwb_dev->bce->rssi_stats, buf); + mutex_unlock(&bce->mutex); + return result; +} + +static ssize_t uwb_dev_RSSI_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_beca_e *bce = uwb_dev->bce; + ssize_t result; + + mutex_lock(&bce->mutex); + result = stats_store(&uwb_dev->bce->rssi_stats, buf, size); + mutex_unlock(&bce->mutex); + return result; +} +static DEVICE_ATTR(RSSI, S_IRUGO | S_IWUSR, uwb_dev_RSSI_show, uwb_dev_RSSI_store); + + +static struct attribute *dev_attrs[] = { + &dev_attr_EUI_48.attr, + &dev_attr_DevAddr.attr, + &dev_attr_BPST.attr, + &dev_attr_IEs.attr, + &dev_attr_LQE.attr, + &dev_attr_RSSI.attr, + NULL, +}; + +static struct attribute_group dev_attr_group = { + .attrs = dev_attrs, +}; + +static struct attribute_group *groups[] = { + &dev_attr_group, + NULL, +}; + +/** + * Device SYSFS registration + * + * + */ +static int __uwb_dev_sys_add(struct uwb_dev *uwb_dev, struct device *parent_dev) +{ + int result; + struct device *dev; + + d_fnstart(4, NULL, "(uwb_dev %p parent_dev %p)\n", uwb_dev, parent_dev); + BUG_ON(parent_dev == NULL); + + dev = &uwb_dev->dev; + /* Device sysfs files are only useful for neighbor devices not + local radio controllers. */ + if (&uwb_dev->rc->uwb_dev != uwb_dev) + dev->groups = groups; + dev->parent = parent_dev; + dev_set_drvdata(dev, uwb_dev); + + result = device_add(dev); + d_fnend(4, NULL, "(uwb_dev %p parent_dev %p) = %d\n", uwb_dev, parent_dev, result); + return result; +} + + +static void __uwb_dev_sys_rm(struct uwb_dev *uwb_dev) +{ + d_fnstart(4, NULL, "(uwb_dev %p)\n", uwb_dev); + dev_set_drvdata(&uwb_dev->dev, NULL); + device_del(&uwb_dev->dev); + d_fnend(4, NULL, "(uwb_dev %p) = void\n", uwb_dev); +} + + +/** + * Register and initialize a new UWB device + * + * Did you call uwb_dev_init() on it? + * + * @parent_rc: is the parent radio controller who has the link to the + * device. When registering the UWB device that is a UWB + * Radio Controller, we point back to it. + * + * If registering the device that is part of a radio, caller has set + * rc->uwb_dev->dev. Otherwise it is to be left NULL--a new one will + * be allocated. + */ +int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev, + struct uwb_rc *parent_rc) +{ + int result; + struct device *dev; + + BUG_ON(uwb_dev == NULL); + BUG_ON(parent_dev == NULL); + BUG_ON(parent_rc == NULL); + + mutex_lock(&uwb_dev->mutex); + dev = &uwb_dev->dev; + uwb_dev->rc = parent_rc; + result = __uwb_dev_sys_add(uwb_dev, parent_dev); + if (result < 0) + printk(KERN_ERR "UWB: unable to register dev %s with sysfs: %d\n", + dev_name(dev), result); + mutex_unlock(&uwb_dev->mutex); + return result; +} + + +void uwb_dev_rm(struct uwb_dev *uwb_dev) +{ + mutex_lock(&uwb_dev->mutex); + __uwb_dev_sys_rm(uwb_dev); + mutex_unlock(&uwb_dev->mutex); +} + + +static +int __uwb_dev_try_get(struct device *dev, void *__target_uwb_dev) +{ + struct uwb_dev *target_uwb_dev = __target_uwb_dev; + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + if (uwb_dev == target_uwb_dev) { + uwb_dev_get(uwb_dev); + return 1; + } else + return 0; +} + + +/** + * Given a UWB device descriptor, validate and refcount it + * + * @returns NULL if the device does not exist or is quiescing; the ptr to + * it otherwise. + */ +struct uwb_dev *uwb_dev_try_get(struct uwb_rc *rc, struct uwb_dev *uwb_dev) +{ + if (uwb_dev_for_each(rc, __uwb_dev_try_get, uwb_dev)) + return uwb_dev; + else + return NULL; +} +EXPORT_SYMBOL_GPL(uwb_dev_try_get); + + +/** + * Remove a device from the system [grunt for other functions] + */ +int __uwb_dev_offair(struct uwb_dev *uwb_dev, struct uwb_rc *rc) +{ + struct device *dev = &uwb_dev->dev; + char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE]; + + d_fnstart(3, NULL, "(dev %p [uwb_dev %p], uwb_rc %p)\n", dev, uwb_dev, rc); + uwb_mac_addr_print(macbuf, sizeof(macbuf), &uwb_dev->mac_addr); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &uwb_dev->dev_addr); + dev_info(dev, "uwb device (mac %s dev %s) disconnected from %s %s\n", + macbuf, devbuf, + rc ? rc->uwb_dev.dev.parent->bus->name : "n/a", + rc ? dev_name(rc->uwb_dev.dev.parent) : ""); + uwb_dev_rm(uwb_dev); + uwb_dev_put(uwb_dev); /* for the creation in _onair() */ + d_fnend(3, NULL, "(dev %p [uwb_dev %p], uwb_rc %p) = 0\n", dev, uwb_dev, rc); + return 0; +} + + +/** + * A device went off the air, clean up after it! + * + * This is called by the UWB Daemon (through the beacon purge function + * uwb_bcn_cache_purge) when it is detected that a device has been in + * radio silence for a while. + * + * If this device is actually a local radio controller we don't need + * to go through the offair process, as it is not registered as that. + * + * NOTE: uwb_bcn_cache.mutex is held! + */ +void uwbd_dev_offair(struct uwb_beca_e *bce) +{ + struct uwb_dev *uwb_dev; + + uwb_dev = bce->uwb_dev; + if (uwb_dev) { + uwb_notify(uwb_dev->rc, uwb_dev, UWB_NOTIF_OFFAIR); + __uwb_dev_offair(uwb_dev, uwb_dev->rc); + } +} + + +/** + * A device went on the air, start it up! + * + * This is called by the UWB Daemon when it is detected that a device + * has popped up in the radio range of the radio controller. + * + * It will just create the freaking device, register the beacon and + * stuff and yatla, done. + * + * + * NOTE: uwb_beca.mutex is held, bce->mutex is held + */ +void uwbd_dev_onair(struct uwb_rc *rc, struct uwb_beca_e *bce) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_dev *uwb_dev; + char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE]; + + uwb_mac_addr_print(macbuf, sizeof(macbuf), bce->mac_addr); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &bce->dev_addr); + uwb_dev = kcalloc(1, sizeof(*uwb_dev), GFP_KERNEL); + if (uwb_dev == NULL) { + dev_err(dev, "new device %s: Cannot allocate memory\n", + macbuf); + return; + } + uwb_dev_init(uwb_dev); /* This sets refcnt to one, we own it */ + uwb_dev->mac_addr = *bce->mac_addr; + uwb_dev->dev_addr = bce->dev_addr; + dev_set_name(&uwb_dev->dev, macbuf); + result = uwb_dev_add(uwb_dev, &rc->uwb_dev.dev, rc); + if (result < 0) { + dev_err(dev, "new device %s: cannot instantiate device\n", + macbuf); + goto error_dev_add; + } + /* plug the beacon cache */ + bce->uwb_dev = uwb_dev; + uwb_dev->bce = bce; + uwb_bce_get(bce); /* released in uwb_dev_sys_release() */ + dev_info(dev, "uwb device (mac %s dev %s) connected to %s %s\n", + macbuf, devbuf, rc->uwb_dev.dev.parent->bus->name, + dev_name(rc->uwb_dev.dev.parent)); + uwb_notify(rc, uwb_dev, UWB_NOTIF_ONAIR); + return; + +error_dev_add: + kfree(uwb_dev); + return; +} + +/** + * Iterate over the list of UWB devices, calling a @function on each + * + * See docs for bus_for_each().... + * + * @rc: radio controller for the devices. + * @function: function to call. + * @priv: data to pass to @function. + * @returns: 0 if no invocation of function() returned a value + * different to zero. That value otherwise. + */ +int uwb_dev_for_each(struct uwb_rc *rc, uwb_dev_for_each_f function, void *priv) +{ + return device_for_each_child(&rc->uwb_dev.dev, priv, function); +} +EXPORT_SYMBOL_GPL(uwb_dev_for_each); diff --git a/drivers/uwb/lc-rc.c b/drivers/uwb/lc-rc.c new file mode 100644 index 00000000000..a21c96bff23 --- /dev/null +++ b/drivers/uwb/lc-rc.c @@ -0,0 +1,501 @@ +/* + * Ultra Wide Band + * Life cycle of radio controllers + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * A UWB radio controller is also a UWB device, so it embeds one... + * + * List of RCs comes from the 'struct class uwb_rc_class'. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define D_LOCAL 1 +#include +#include "uwb-internal.h" + +static int uwb_rc_index_match(struct device *dev, void *data) +{ + int *index = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc->index == *index) + return 1; + return 0; +} + +static struct uwb_rc *uwb_rc_find_by_index(int index) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, &index, uwb_rc_index_match); + if (dev) + rc = dev_get_drvdata(dev); + return rc; +} + +static int uwb_rc_new_index(void) +{ + int index = 0; + + for (;;) { + if (!uwb_rc_find_by_index(index)) + return index; + if (++index < 0) + index = 0; + } +} + +/** + * Release the backing device of a uwb_rc that has been dynamically allocated. + */ +static void uwb_rc_sys_release(struct device *dev) +{ + struct uwb_dev *uwb_dev = container_of(dev, struct uwb_dev, dev); + struct uwb_rc *rc = container_of(uwb_dev, struct uwb_rc, uwb_dev); + + uwb_rc_neh_destroy(rc); + uwb_rc_ie_release(rc); + d_printf(1, dev, "freed uwb_rc %p\n", rc); + kfree(rc); +} + + +void uwb_rc_init(struct uwb_rc *rc) +{ + struct uwb_dev *uwb_dev = &rc->uwb_dev; + + uwb_dev_init(uwb_dev); + rc->uwb_dev.dev.class = &uwb_rc_class; + rc->uwb_dev.dev.release = uwb_rc_sys_release; + uwb_rc_neh_create(rc); + rc->beaconing = -1; + rc->scan_type = UWB_SCAN_DISABLED; + INIT_LIST_HEAD(&rc->notifs_chain.list); + mutex_init(&rc->notifs_chain.mutex); + uwb_drp_avail_init(rc); + uwb_rc_ie_init(rc); + uwb_rsv_init(rc); + uwb_rc_pal_init(rc); +} +EXPORT_SYMBOL_GPL(uwb_rc_init); + + +struct uwb_rc *uwb_rc_alloc(void) +{ + struct uwb_rc *rc; + rc = kzalloc(sizeof(*rc), GFP_KERNEL); + if (rc == NULL) + return NULL; + uwb_rc_init(rc); + return rc; +} +EXPORT_SYMBOL_GPL(uwb_rc_alloc); + +static struct attribute *rc_attrs[] = { + &dev_attr_mac_address.attr, + &dev_attr_scan.attr, + &dev_attr_beacon.attr, + NULL, +}; + +static struct attribute_group rc_attr_group = { + .attrs = rc_attrs, +}; + +/* + * Registration of sysfs specific stuff + */ +static int uwb_rc_sys_add(struct uwb_rc *rc) +{ + return sysfs_create_group(&rc->uwb_dev.dev.kobj, &rc_attr_group); +} + + +static void __uwb_rc_sys_rm(struct uwb_rc *rc) +{ + sysfs_remove_group(&rc->uwb_dev.dev.kobj, &rc_attr_group); +} + +/** + * uwb_rc_mac_addr_setup - get an RC's EUI-48 address or set it + * @rc: the radio controller. + * + * If the EUI-48 address is 00:00:00:00:00:00 or FF:FF:FF:FF:FF:FF + * then a random locally administered EUI-48 is generated and set on + * the device. The probability of address collisions is sufficiently + * unlikely (1/2^40 = 9.1e-13) that they're not checked for. + */ +static +int uwb_rc_mac_addr_setup(struct uwb_rc *rc) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_dev *uwb_dev = &rc->uwb_dev; + char devname[UWB_ADDR_STRSIZE]; + struct uwb_mac_addr addr; + + result = uwb_rc_mac_addr_get(rc, &addr); + if (result < 0) { + dev_err(dev, "cannot retrieve UWB EUI-48 address: %d\n", result); + return result; + } + + if (uwb_mac_addr_unset(&addr) || uwb_mac_addr_bcast(&addr)) { + addr.data[0] = 0x02; /* locally adminstered and unicast */ + get_random_bytes(&addr.data[1], sizeof(addr.data)-1); + + result = uwb_rc_mac_addr_set(rc, &addr); + if (result < 0) { + uwb_mac_addr_print(devname, sizeof(devname), &addr); + dev_err(dev, "cannot set EUI-48 address %s: %d\n", + devname, result); + return result; + } + } + uwb_dev->mac_addr = addr; + return 0; +} + + + +static int uwb_rc_setup(struct uwb_rc *rc) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + + result = uwb_rc_reset(rc); + if (result < 0) { + dev_err(dev, "cannot reset UWB radio: %d\n", result); + goto error; + } + result = uwb_rc_mac_addr_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup UWB MAC address: %d\n", result); + goto error; + } + result = uwb_rc_dev_addr_assign(rc); + if (result < 0) { + dev_err(dev, "cannot assign UWB DevAddr: %d\n", result); + goto error; + } + result = uwb_rc_ie_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup IE subsystem: %d\n", result); + goto error_ie_setup; + } + result = uwb_rc_set_identification_ie(rc); + if (result < 0) { + dev_err(dev, "cannot set Identification IE: %d\n", + result); + goto error_set_id_ie; + } + result = uwb_rsv_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup reservation subsystem: %d\n", result); + goto error_rsv_setup; + } + uwb_dbg_add_rc(rc); + return 0; + +error_rsv_setup: + uwb_rc_ie_release(rc); +error_ie_setup: +error: + return result; +} + + +/** + * Register a new UWB radio controller + * + * Did you call uwb_rc_init() on your rc? + * + * We assume that this is being called with a > 0 refcount on + * it [through ops->{get|put}_device(). We'll take our own, though. + * + * @parent_dev is our real device, the one that provides the actual UWB device + */ +int uwb_rc_add(struct uwb_rc *rc, struct device *parent_dev, void *priv) +{ + int result; + struct device *dev; + char macbuf[UWB_ADDR_STRSIZE], devbuf[UWB_ADDR_STRSIZE]; + + rc->index = uwb_rc_new_index(); + + dev = &rc->uwb_dev.dev; + dev_set_name(dev, "uwb%d", rc->index); + + rc->priv = priv; + + result = rc->start(rc); + if (result < 0) + goto error_rc_start; + + result = uwb_rc_setup(rc); + if (result < 0) { + dev_err(dev, "cannot setup UWB radio controller: %d\n", result); + goto error_rc_setup; + } + + result = uwb_dev_add(&rc->uwb_dev, parent_dev, rc); + if (result < 0 && result != -EADDRNOTAVAIL) + goto error_dev_add; + + result = uwb_rc_sys_add(rc); + if (result < 0) { + dev_err(parent_dev, "cannot register UWB radio controller " + "dev attributes: %d\n", result); + goto error_sys_add; + } + + uwb_mac_addr_print(macbuf, sizeof(macbuf), &rc->uwb_dev.mac_addr); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &rc->uwb_dev.dev_addr); + dev_info(dev, + "new uwb radio controller (mac %s dev %s) on %s %s\n", + macbuf, devbuf, parent_dev->bus->name, dev_name(parent_dev)); + rc->ready = 1; + return 0; + +error_sys_add: + uwb_dev_rm(&rc->uwb_dev); +error_dev_add: +error_rc_setup: + rc->stop(rc); + uwbd_flush(rc); +error_rc_start: + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_add); + + +static int uwb_dev_offair_helper(struct device *dev, void *priv) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + return __uwb_dev_offair(uwb_dev, uwb_dev->rc); +} + +/* + * Remove a Radio Controller; stop beaconing/scanning, disconnect all children + */ +void uwb_rc_rm(struct uwb_rc *rc) +{ + rc->ready = 0; + + uwb_dbg_del_rc(rc); + uwb_rsv_cleanup(rc); + uwb_rc_ie_rm(rc, UWB_IDENTIFICATION_IE); + if (rc->beaconing >= 0) + uwb_rc_beacon(rc, -1, 0); + if (rc->scan_type != UWB_SCAN_DISABLED) + uwb_rc_scan(rc, rc->scanning, UWB_SCAN_DISABLED, 0); + uwb_rc_reset(rc); + + rc->stop(rc); + uwbd_flush(rc); + + uwb_dev_lock(&rc->uwb_dev); + rc->priv = NULL; + rc->cmd = NULL; + uwb_dev_unlock(&rc->uwb_dev); + mutex_lock(&uwb_beca.mutex); + uwb_dev_for_each(rc, uwb_dev_offair_helper, NULL); + __uwb_rc_sys_rm(rc); + mutex_unlock(&uwb_beca.mutex); + uwb_dev_rm(&rc->uwb_dev); +} +EXPORT_SYMBOL_GPL(uwb_rc_rm); + +static int find_rc_try_get(struct device *dev, void *data) +{ + struct uwb_rc *target_rc = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc == NULL) { + WARN_ON(1); + return 0; + } + if (rc == target_rc) { + if (rc->ready == 0) + return 0; + else + return 1; + } + return 0; +} + +/** + * Given a radio controller descriptor, validate and refcount it + * + * @returns NULL if the rc does not exist or is quiescing; the ptr to + * it otherwise. + */ +struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *target_rc) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, target_rc, + find_rc_try_get); + if (dev) { + rc = dev_get_drvdata(dev); + __uwb_rc_get(rc); + } + return rc; +} +EXPORT_SYMBOL_GPL(__uwb_rc_try_get); + +/* + * RC get for external refcount acquirers... + * + * Increments the refcount of the device and it's backend modules + */ +static inline struct uwb_rc *uwb_rc_get(struct uwb_rc *rc) +{ + if (rc->ready == 0) + return NULL; + uwb_dev_get(&rc->uwb_dev); + return rc; +} + +static int find_rc_grandpa(struct device *dev, void *data) +{ + struct device *grandpa_dev = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc->uwb_dev.dev.parent->parent == grandpa_dev) { + rc = uwb_rc_get(rc); + return 1; + } + return 0; +} + +/** + * Locate and refcount a radio controller given a common grand-parent + * + * @grandpa_dev Pointer to the 'grandparent' device structure. + * @returns NULL If the rc does not exist or is quiescing; the ptr to + * it otherwise, properly referenced. + * + * The Radio Control interface (or the UWB Radio Controller) is always + * an interface of a device. The parent is the interface, the + * grandparent is the device that encapsulates the interface. + * + * There is no need to lock around as the "grandpa" would be + * refcounted by the target, and to remove the referemes, the + * uwb_rc_class->sem would have to be taken--we hold it, ergo we + * should be safe. + */ +struct uwb_rc *uwb_rc_get_by_grandpa(const struct device *grandpa_dev) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, (void *)grandpa_dev, + find_rc_grandpa); + if (dev) + rc = dev_get_drvdata(dev); + return rc; +} +EXPORT_SYMBOL_GPL(uwb_rc_get_by_grandpa); + +/** + * Find a radio controller by device address + * + * @returns the pointer to the radio controller, properly referenced + */ +static int find_rc_dev(struct device *dev, void *data) +{ + struct uwb_dev_addr *addr = data; + struct uwb_rc *rc = dev_get_drvdata(dev); + + if (rc == NULL) { + WARN_ON(1); + return 0; + } + if (!uwb_dev_addr_cmp(&rc->uwb_dev.dev_addr, addr)) { + rc = uwb_rc_get(rc); + return 1; + } + return 0; +} + +struct uwb_rc *uwb_rc_get_by_dev(const struct uwb_dev_addr *addr) +{ + struct device *dev; + struct uwb_rc *rc = NULL; + + dev = class_find_device(&uwb_rc_class, NULL, (void *)addr, + find_rc_dev); + if (dev) + rc = dev_get_drvdata(dev); + + return rc; +} +EXPORT_SYMBOL_GPL(uwb_rc_get_by_dev); + +/** + * Drop a reference on a radio controller + * + * This is the version that should be done by entities external to the + * UWB Radio Control stack (ie: clients of the API). + */ +void uwb_rc_put(struct uwb_rc *rc) +{ + __uwb_rc_put(rc); +} +EXPORT_SYMBOL_GPL(uwb_rc_put); + +/* + * + * + */ +ssize_t uwb_rc_print_IEs(struct uwb_rc *uwb_rc, char *buf, size_t size) +{ + ssize_t result; + struct uwb_rc_evt_get_ie *ie_info; + struct uwb_buf_ctx ctx; + + result = uwb_rc_get_ie(uwb_rc, &ie_info); + if (result < 0) + goto error_get_ie; + ctx.buf = buf; + ctx.size = size; + ctx.bytes = 0; + uwb_ie_for_each(&uwb_rc->uwb_dev, uwb_ie_dump_hex, &ctx, + ie_info->IEData, result - sizeof(*ie_info)); + result = ctx.bytes; + kfree(ie_info); +error_get_ie: + return result; +} + diff --git a/drivers/uwb/pal.c b/drivers/uwb/pal.c new file mode 100644 index 00000000000..5508993a820 --- /dev/null +++ b/drivers/uwb/pal.c @@ -0,0 +1,71 @@ +/* + * UWB PAL support. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +#include "uwb-internal.h" + +/** + * uwb_pal_init - initialize a UWB PAL + * @pal: the PAL to initialize + */ +void uwb_pal_init(struct uwb_pal *pal) +{ + INIT_LIST_HEAD(&pal->node); +} +EXPORT_SYMBOL_GPL(uwb_pal_init); + +/** + * uwb_pal_register - register a UWB PAL + * @rc: the radio controller the PAL will be using + * @pal: the PAL + * + * The PAL must be initialized with uwb_pal_init(). + */ +int uwb_pal_register(struct uwb_rc *rc, struct uwb_pal *pal) +{ + spin_lock(&rc->pal_lock); + list_add(&pal->node, &rc->pals); + spin_unlock(&rc->pal_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(uwb_pal_register); + +/** + * uwb_pal_register - unregister a UWB PAL + * @rc: the radio controller the PAL was using + * @pal: the PAL + */ +void uwb_pal_unregister(struct uwb_rc *rc, struct uwb_pal *pal) +{ + spin_lock(&rc->pal_lock); + list_del(&pal->node); + spin_unlock(&rc->pal_lock); +} +EXPORT_SYMBOL_GPL(uwb_pal_unregister); + +/** + * uwb_rc_pal_init - initialize the PAL related parts of a radio controller + * @rc: the radio controller + */ +void uwb_rc_pal_init(struct uwb_rc *rc) +{ + spin_lock_init(&rc->pal_lock); + INIT_LIST_HEAD(&rc->pals); +} diff --git a/drivers/uwb/uwb-internal.h b/drivers/uwb/uwb-internal.h new file mode 100644 index 00000000000..4f525a88985 --- /dev/null +++ b/drivers/uwb/uwb-internal.h @@ -0,0 +1,306 @@ +/* + * Ultra Wide Band + * UWB internal API + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * This contains most of the internal API for UWB. This is stuff used + * across the stack that of course, is of no interest to the rest. + * + * Some parts might end up going public (like uwb_rc_*())... + */ + +#ifndef __UWB_INTERNAL_H__ +#define __UWB_INTERNAL_H__ + +#include +#include +#include +#include +#include + +struct uwb_beca_e; + +/* General device API */ +extern void uwb_dev_init(struct uwb_dev *uwb_dev); +extern int __uwb_dev_offair(struct uwb_dev *, struct uwb_rc *); +extern int uwb_dev_add(struct uwb_dev *uwb_dev, struct device *parent_dev, + struct uwb_rc *parent_rc); +extern void uwb_dev_rm(struct uwb_dev *uwb_dev); +extern void uwbd_dev_onair(struct uwb_rc *, struct uwb_beca_e *); +extern void uwbd_dev_offair(struct uwb_beca_e *); +void uwb_notify(struct uwb_rc *rc, struct uwb_dev *uwb_dev, enum uwb_notifs event); + +/* General UWB Radio Controller Internal API */ +extern struct uwb_rc *__uwb_rc_try_get(struct uwb_rc *); +static inline struct uwb_rc *__uwb_rc_get(struct uwb_rc *rc) +{ + uwb_dev_get(&rc->uwb_dev); + return rc; +} + +static inline void __uwb_rc_put(struct uwb_rc *rc) +{ + uwb_dev_put(&rc->uwb_dev); +} + +extern int uwb_rc_reset(struct uwb_rc *rc); +extern int uwb_rc_beacon(struct uwb_rc *rc, + int channel, unsigned bpst_offset); +extern int uwb_rc_scan(struct uwb_rc *rc, + unsigned channel, enum uwb_scan_type type, + unsigned bpst_offset); +extern int uwb_rc_send_all_drp_ie(struct uwb_rc *rc); +extern ssize_t uwb_rc_print_IEs(struct uwb_rc *rc, char *, size_t); +extern void uwb_rc_ie_init(struct uwb_rc *); +extern void uwb_rc_ie_init(struct uwb_rc *); +extern ssize_t uwb_rc_ie_setup(struct uwb_rc *); +extern void uwb_rc_ie_release(struct uwb_rc *); +extern int uwb_rc_ie_add(struct uwb_rc *, + const struct uwb_ie_hdr *, size_t); +extern int uwb_rc_ie_rm(struct uwb_rc *, enum uwb_ie); +extern int uwb_rc_set_identification_ie(struct uwb_rc *); + +extern const char *uwb_rc_strerror(unsigned code); + +/* + * Time to wait for a response to an RC command. + * + * Some commands can take a long time to response. e.g., START_BEACON + * may scan for several superframes before joining an existing beacon + * group and this can take around 600 ms. + */ +#define UWB_RC_CMD_TIMEOUT_MS 1000 /* ms */ + +/* + * Notification/Event Handlers + */ + +struct uwb_rc_neh; + +void uwb_rc_neh_create(struct uwb_rc *rc); +void uwb_rc_neh_destroy(struct uwb_rc *rc); + +struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd, + u8 expected_type, u16 expected_event, + uwb_rc_cmd_cb_f cb, void *arg); +void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh); +void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh); +void uwb_rc_neh_put(struct uwb_rc_neh *neh); + +/* Event size tables */ +extern int uwb_est_create(void); +extern void uwb_est_destroy(void); + + +/* + * UWB Events & management daemon + */ + +/** + * enum uwb_event_type - types of UWB management daemon events + * + * The UWB management daemon (uwbd) can receive two types of events: + * UWB_EVT_TYPE_NOTIF - notification from the radio controller. + * UWB_EVT_TYPE_MSG - a simple message. + */ +enum uwb_event_type { + UWB_EVT_TYPE_NOTIF, + UWB_EVT_TYPE_MSG, +}; + +/** + * struct uwb_event_notif - an event for a radio controller notification + * @size: Size of the buffer (ie: Guaranteed to contain at least + * a full 'struct uwb_rceb') + * @rceb: Pointer to a kmalloced() event payload + */ +struct uwb_event_notif { + size_t size; + struct uwb_rceb *rceb; +}; + +/** + * enum uwb_event_message - an event for a message for asynchronous processing + * + * UWB_EVT_MSG_RESET - reset the radio controller and all PAL hardware. + */ +enum uwb_event_message { + UWB_EVT_MSG_RESET, +}; + +/** + * UWB Event + * @rc: Radio controller that emitted the event (referenced) + * @ts_jiffies: Timestamp, when was it received + * @type: This event's type. + */ +struct uwb_event { + struct list_head list_node; + struct uwb_rc *rc; + unsigned long ts_jiffies; + enum uwb_event_type type; + union { + struct uwb_event_notif notif; + enum uwb_event_message message; + }; +}; + +extern void uwbd_start(void); +extern void uwbd_stop(void); +extern struct uwb_event *uwb_event_alloc(size_t, gfp_t gfp_mask); +extern void uwbd_event_queue(struct uwb_event *); +void uwbd_flush(struct uwb_rc *rc); + +/* UWB event handlers */ +extern int uwbd_evt_handle_rc_beacon(struct uwb_event *); +extern int uwbd_evt_handle_rc_beacon_size(struct uwb_event *); +extern int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *); +extern int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *); +extern int uwbd_evt_handle_rc_drp(struct uwb_event *); +extern int uwbd_evt_handle_rc_drp_avail(struct uwb_event *); + +int uwbd_msg_handle_reset(struct uwb_event *evt); + + +/* + * Address management + */ +int uwb_rc_dev_addr_assign(struct uwb_rc *rc); +int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt); + +/* + * UWB Beacon Cache + * + * Each beacon we received is kept in a cache--when we receive that + * beacon consistently, that means there is a new device that we have + * to add to the system. + */ + +extern unsigned long beacon_timeout_ms; + +/** Beacon cache list */ +struct uwb_beca { + struct list_head list; + size_t entries; + struct mutex mutex; +}; + +extern struct uwb_beca uwb_beca; + +/** + * Beacon cache entry + * + * @jiffies_refresh: last time a beacon was received that refreshed + * this cache entry. + * @uwb_dev: device connected to this beacon. This pointer is not + * safe, you need to get it with uwb_dev_try_get() + * + * @hits: how many time we have seen this beacon since last time we + * cleared it + */ +struct uwb_beca_e { + struct mutex mutex; + struct kref refcnt; + struct list_head node; + struct uwb_mac_addr *mac_addr; + struct uwb_dev_addr dev_addr; + u8 hits; + unsigned long ts_jiffies; + struct uwb_dev *uwb_dev; + struct uwb_rc_evt_beacon *be; + struct stats lqe_stats, rssi_stats; /* radio statistics */ +}; +struct uwb_beacon_frame; +extern ssize_t uwb_bce_print_IEs(struct uwb_dev *, struct uwb_beca_e *, + char *, size_t); +extern struct uwb_beca_e *__uwb_beca_add(struct uwb_rc_evt_beacon *, + struct uwb_beacon_frame *, + unsigned long); + +extern void uwb_bce_kfree(struct kref *_bce); +static inline void uwb_bce_get(struct uwb_beca_e *bce) +{ + kref_get(&bce->refcnt); +} +static inline void uwb_bce_put(struct uwb_beca_e *bce) +{ + kref_put(&bce->refcnt, uwb_bce_kfree); +} +extern void uwb_beca_purge(void); +extern void uwb_beca_release(void); + +struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc, + const struct uwb_dev_addr *devaddr); +struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc, + const struct uwb_mac_addr *macaddr); + +/* -- UWB Sysfs representation */ +extern struct class uwb_rc_class; +extern struct device_attribute dev_attr_mac_address; +extern struct device_attribute dev_attr_beacon; +extern struct device_attribute dev_attr_scan; + +/* -- DRP Bandwidth allocator: bandwidth allocations, reservations, DRP */ +void uwb_rsv_init(struct uwb_rc *rc); +int uwb_rsv_setup(struct uwb_rc *rc); +void uwb_rsv_cleanup(struct uwb_rc *rc); + +void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state); +void uwb_rsv_remove(struct uwb_rsv *rsv); +struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie); +void uwb_rsv_sched_update(struct uwb_rc *rc); + +void uwb_drp_handle_timeout(struct uwb_rsv *rsv); +int uwb_drp_ie_update(struct uwb_rsv *rsv); +void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie); + +void uwb_drp_avail_init(struct uwb_rc *rc); +int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas); +void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas); +void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas); +void uwb_drp_avail_ie_update(struct uwb_rc *rc); + +/* -- PAL support */ +void uwb_rc_pal_init(struct uwb_rc *rc); + +/* -- Misc */ + +extern ssize_t uwb_mac_frame_hdr_print(char *, size_t, + const struct uwb_mac_frame_hdr *); + +/* -- Debug interface */ +void uwb_dbg_init(void); +void uwb_dbg_exit(void); +void uwb_dbg_add_rc(struct uwb_rc *rc); +void uwb_dbg_del_rc(struct uwb_rc *rc); + +/* Workarounds for version specific stuff */ + +static inline void uwb_dev_lock(struct uwb_dev *uwb_dev) +{ + down(&uwb_dev->dev.sem); +} + +static inline void uwb_dev_unlock(struct uwb_dev *uwb_dev) +{ + up(&uwb_dev->dev.sem); +} + +#endif /* #ifndef __UWB_INTERNAL_H__ */ diff --git a/drivers/uwb/uwbd.c b/drivers/uwb/uwbd.c new file mode 100644 index 00000000000..b3673d614ad --- /dev/null +++ b/drivers/uwb/uwbd.c @@ -0,0 +1,427 @@ +/* + * Ultra Wide Band + * Neighborhood Management Daemon + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This daemon takes care of maintaing information that describes the + * UWB neighborhood that the radios in this machine can see. It also + * keeps a tab of which devices are visible, makes sure each HC sits + * on a different channel to avoid interfering, etc. + * + * Different drivers (radio controller, device, any API in general) + * communicate with this daemon through an event queue. Daemon wakes + * up, takes a list of events and handles them one by one; handling + * function is extracted from a table based on the event's type and + * subtype. Events are freed only if the handling function says so. + * + * . Lock protecting the event list has to be an spinlock and locked + * with IRQSAVE because it might be called from an interrupt + * context (ie: when events arrive and the notification drops + * down from the ISR). + * + * . UWB radio controller drivers queue events to the daemon using + * uwbd_event_queue(). They just get the event, chew it to make it + * look like UWBD likes it and pass it in a buffer allocated with + * uwb_event_alloc(). + * + * EVENTS + * + * Events have a type, a subtype, a lenght, some other stuff and the + * data blob, which depends on the event. The header is 'struct + * uwb_event'; for payloads, see 'struct uwbd_evt_*'. + * + * EVENT HANDLER TABLES + * + * To find a handling function for an event, the type is used to index + * a subtype-table in the type-table. The subtype-table is indexed + * with the subtype to get the function that handles the event. Start + * with the main type-table 'uwbd_evt_type_handler'. + * + * DEVICES + * + * Devices are created when a bunch of beacons have been received and + * it is stablished that the device has stable radio presence. CREATED + * only, not configured. Devices are ONLY configured when an + * Application-Specific IE Probe is receieved, in which the device + * declares which Protocol ID it groks. Then the device is CONFIGURED + * (and the driver->probe() stuff of the device model is invoked). + * + * Devices are considered disconnected when a certain number of + * beacons are not received in an amount of time. + * + * Handler functions are called normally uwbd_evt_handle_*(). + */ + +#include +#include +#include +#include "uwb-internal.h" + +#define D_LOCAL 1 +#include + + +/** + * UWBD Event handler function signature + * + * Return !0 if the event needs not to be freed (ie the handler + * takes/took care of it). 0 means the daemon code will free the + * event. + * + * @evt->rc is already referenced and guaranteed to exist. See + * uwb_evt_handle(). + */ +typedef int (*uwbd_evt_handler_f)(struct uwb_event *); + +/** + * Properties of a UWBD event + * + * @handler: the function that will handle this event + * @name: text name of event + */ +struct uwbd_event { + uwbd_evt_handler_f handler; + const char *name; +}; + +/** Table of handlers for and properties of the UWBD Radio Control Events */ +static +struct uwbd_event uwbd_events[] = { + [UWB_RC_EVT_BEACON] = { + .handler = uwbd_evt_handle_rc_beacon, + .name = "BEACON_RECEIVED" + }, + [UWB_RC_EVT_BEACON_SIZE] = { + .handler = uwbd_evt_handle_rc_beacon_size, + .name = "BEACON_SIZE_CHANGE" + }, + [UWB_RC_EVT_BPOIE_CHANGE] = { + .handler = uwbd_evt_handle_rc_bpoie_change, + .name = "BPOIE_CHANGE" + }, + [UWB_RC_EVT_BP_SLOT_CHANGE] = { + .handler = uwbd_evt_handle_rc_bp_slot_change, + .name = "BP_SLOT_CHANGE" + }, + [UWB_RC_EVT_DRP_AVAIL] = { + .handler = uwbd_evt_handle_rc_drp_avail, + .name = "DRP_AVAILABILITY_CHANGE" + }, + [UWB_RC_EVT_DRP] = { + .handler = uwbd_evt_handle_rc_drp, + .name = "DRP" + }, + [UWB_RC_EVT_DEV_ADDR_CONFLICT] = { + .handler = uwbd_evt_handle_rc_dev_addr_conflict, + .name = "DEV_ADDR_CONFLICT", + }, +}; + + + +struct uwbd_evt_type_handler { + const char *name; + struct uwbd_event *uwbd_events; + size_t size; +}; + +#define UWBD_EVT_TYPE_HANDLER(n,a) { \ + .name = (n), \ + .uwbd_events = (a), \ + .size = sizeof(a)/sizeof((a)[0]) \ +} + + +/** Table of handlers for each UWBD Event type. */ +static +struct uwbd_evt_type_handler uwbd_evt_type_handlers[] = { + [UWB_RC_CET_GENERAL] = UWBD_EVT_TYPE_HANDLER("RC", uwbd_events) +}; + +static const +size_t uwbd_evt_type_handlers_len = + sizeof(uwbd_evt_type_handlers) / sizeof(uwbd_evt_type_handlers[0]); + +static const struct uwbd_event uwbd_message_handlers[] = { + [UWB_EVT_MSG_RESET] = { + .handler = uwbd_msg_handle_reset, + .name = "reset", + }, +}; + +static DEFINE_MUTEX(uwbd_event_mutex); + +/** + * Handle an URC event passed to the UWB Daemon + * + * @evt: the event to handle + * @returns: 0 if the event can be kfreed, !0 on the contrary + * (somebody else took ownership) [coincidentally, returning + * a <0 errno code will free it :)]. + * + * Looks up the two indirection tables (one for the type, one for the + * subtype) to decide which function handles it and then calls the + * handler. + * + * The event structure passed to the event handler has the radio + * controller in @evt->rc referenced. The reference will be dropped + * once the handler returns, so if it needs it for longer (async), + * it'll need to take another one. + */ +static +int uwbd_event_handle_urc(struct uwb_event *evt) +{ + int result; + struct uwbd_evt_type_handler *type_table; + uwbd_evt_handler_f handler; + u8 type, context; + u16 event; + + type = evt->notif.rceb->bEventType; + event = le16_to_cpu(evt->notif.rceb->wEvent); + context = evt->notif.rceb->bEventContext; + + if (type > uwbd_evt_type_handlers_len) { + if (printk_ratelimit()) + printk(KERN_ERR "UWBD: event type %u: unknown " + "(too high)\n", type); + return -EINVAL; + } + type_table = &uwbd_evt_type_handlers[type]; + if (type_table->uwbd_events == NULL) { + if (printk_ratelimit()) + printk(KERN_ERR "UWBD: event type %u: unknown\n", type); + return -EINVAL; + } + if (event > type_table->size) { + if (printk_ratelimit()) + printk(KERN_ERR "UWBD: event %s[%u]: " + "unknown (too high)\n", type_table->name, event); + return -EINVAL; + } + handler = type_table->uwbd_events[event].handler; + if (handler == NULL) { + if (printk_ratelimit()) + printk(KERN_ERR "UWBD: event %s[%u]: unknown\n", + type_table->name, event); + return -EINVAL; + } + d_printf(3, NULL, "processing 0x%02x/%04x/%02x, %zu bytes\n", + type, event, context, evt->notif.size); + result = (*handler)(evt); + if (result < 0) { + if (printk_ratelimit()) + printk(KERN_ERR "UWBD: event 0x%02x/%04x/%02x, " + "table %s[%u]: handling failed: %d\n", + type, event, context, type_table->name, + event, result); + } + return result; +} + +static void uwbd_event_handle_message(struct uwb_event *evt) +{ + struct uwb_rc *rc; + int result; + + rc = evt->rc; + + if (evt->message < 0 || evt->message >= ARRAY_SIZE(uwbd_message_handlers)) { + dev_err(&rc->uwb_dev.dev, "UWBD: invalid message type %d\n", evt->message); + return; + } + + /* If this is a reset event we need to drop the + * uwbd_event_mutex or it deadlocks when the reset handler + * attempts to flush the uwbd events. */ + if (evt->message == UWB_EVT_MSG_RESET) + mutex_unlock(&uwbd_event_mutex); + + result = uwbd_message_handlers[evt->message].handler(evt); + if (result < 0) + dev_err(&rc->uwb_dev.dev, "UWBD: '%s' message failed: %d\n", + uwbd_message_handlers[evt->message].name, result); + + if (evt->message == UWB_EVT_MSG_RESET) + mutex_lock(&uwbd_event_mutex); +} + +static void uwbd_event_handle(struct uwb_event *evt) +{ + struct uwb_rc *rc; + int should_keep; + + rc = evt->rc; + + if (rc->ready) { + switch (evt->type) { + case UWB_EVT_TYPE_NOTIF: + should_keep = uwbd_event_handle_urc(evt); + if (should_keep <= 0) + kfree(evt->notif.rceb); + break; + case UWB_EVT_TYPE_MSG: + uwbd_event_handle_message(evt); + break; + default: + dev_err(&rc->uwb_dev.dev, "UWBD: invalid event type %d\n", evt->type); + break; + } + } + + __uwb_rc_put(rc); /* for the __uwb_rc_get() in uwb_rc_notif_cb() */ +} +/* The UWB Daemon */ + + +/** Daemon's PID: used to decide if we can queue or not */ +static int uwbd_pid; +/** Daemon's task struct for managing the kthread */ +static struct task_struct *uwbd_task; +/** Daemon's waitqueue for waiting for new events */ +static DECLARE_WAIT_QUEUE_HEAD(uwbd_wq); +/** Daemon's list of events; we queue/dequeue here */ +static struct list_head uwbd_event_list = LIST_HEAD_INIT(uwbd_event_list); +/** Daemon's list lock to protect concurent access */ +static DEFINE_SPINLOCK(uwbd_event_list_lock); + + +/** + * UWB Daemon + * + * Listens to all UWB notifications and takes care to track the state + * of the UWB neighboorhood for the kernel. When we do a run, we + * spinlock, move the list to a private copy and release the + * lock. Hold it as little as possible. Not a conflict: it is + * guaranteed we own the events in the private list. + * + * FIXME: should change so we don't have a 1HZ timer all the time, but + * only if there are devices. + */ +static int uwbd(void *unused) +{ + unsigned long flags; + struct list_head list = LIST_HEAD_INIT(list); + struct uwb_event *evt, *nxt; + int should_stop = 0; + while (1) { + wait_event_interruptible_timeout( + uwbd_wq, + !list_empty(&uwbd_event_list) + || (should_stop = kthread_should_stop()), + HZ); + if (should_stop) + break; + try_to_freeze(); + + mutex_lock(&uwbd_event_mutex); + spin_lock_irqsave(&uwbd_event_list_lock, flags); + list_splice_init(&uwbd_event_list, &list); + spin_unlock_irqrestore(&uwbd_event_list_lock, flags); + list_for_each_entry_safe(evt, nxt, &list, list_node) { + list_del(&evt->list_node); + uwbd_event_handle(evt); + kfree(evt); + } + mutex_unlock(&uwbd_event_mutex); + + uwb_beca_purge(); /* Purge devices that left */ + } + return 0; +} + + +/** Start the UWB daemon */ +void uwbd_start(void) +{ + uwbd_task = kthread_run(uwbd, NULL, "uwbd"); + if (uwbd_task == NULL) + printk(KERN_ERR "UWB: Cannot start management daemon; " + "UWB won't work\n"); + else + uwbd_pid = uwbd_task->pid; +} + +/* Stop the UWB daemon and free any unprocessed events */ +void uwbd_stop(void) +{ + unsigned long flags; + struct uwb_event *evt, *nxt; + kthread_stop(uwbd_task); + spin_lock_irqsave(&uwbd_event_list_lock, flags); + uwbd_pid = 0; + list_for_each_entry_safe(evt, nxt, &uwbd_event_list, list_node) { + if (evt->type == UWB_EVT_TYPE_NOTIF) + kfree(evt->notif.rceb); + kfree(evt); + } + spin_unlock_irqrestore(&uwbd_event_list_lock, flags); + uwb_beca_release(); +} + +/* + * Queue an event for the management daemon + * + * When some lower layer receives an event, it uses this function to + * push it forward to the UWB daemon. + * + * Once you pass the event, you don't own it any more, but the daemon + * does. It will uwb_event_free() it when done, so make sure you + * uwb_event_alloc()ed it or bad things will happen. + * + * If the daemon is not running, we just free the event. + */ +void uwbd_event_queue(struct uwb_event *evt) +{ + unsigned long flags; + spin_lock_irqsave(&uwbd_event_list_lock, flags); + if (uwbd_pid != 0) { + list_add(&evt->list_node, &uwbd_event_list); + wake_up_all(&uwbd_wq); + } else { + __uwb_rc_put(evt->rc); + if (evt->type == UWB_EVT_TYPE_NOTIF) + kfree(evt->notif.rceb); + kfree(evt); + } + spin_unlock_irqrestore(&uwbd_event_list_lock, flags); + return; +} + +void uwbd_flush(struct uwb_rc *rc) +{ + struct uwb_event *evt, *nxt; + + mutex_lock(&uwbd_event_mutex); + + spin_lock_irq(&uwbd_event_list_lock); + list_for_each_entry_safe(evt, nxt, &uwbd_event_list, list_node) { + if (evt->rc == rc) { + __uwb_rc_put(rc); + list_del(&evt->list_node); + if (evt->type == UWB_EVT_TYPE_NOTIF) + kfree(evt->notif.rceb); + kfree(evt); + } + } + spin_unlock_irq(&uwbd_event_list_lock); + + mutex_unlock(&uwbd_event_mutex); +} -- cgit v1.2.3 From 0612edfd95ffe92201a2267e9e1b0fc68becf76d Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:07 +0100 Subject: uwb: add the UWB stack (radio controller interface) Add the UWB radio controller interface (URCI) support. Signed-off-by: David Vrabel --- drivers/uwb/est.c | 485 +++++++++++++++++++++++++++++++++++++++++ drivers/uwb/neh.c | 616 ++++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/uwb/reset.c | 362 ++++++++++++++++++++++++++++++ 3 files changed, 1463 insertions(+) create mode 100644 drivers/uwb/est.c create mode 100644 drivers/uwb/neh.c create mode 100644 drivers/uwb/reset.c diff --git a/drivers/uwb/est.c b/drivers/uwb/est.c new file mode 100644 index 00000000000..1667afb704d --- /dev/null +++ b/drivers/uwb/est.c @@ -0,0 +1,485 @@ +/* + * Ultra Wide Band Radio Control + * Event Size Tables management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * Infrastructure, code and data tables for guessing the size of + * events received on the notification endpoints of UWB radio + * controllers. + * + * You define a table of events and for each, its size and how to get + * the extra size. + * + * ENTRY POINTS: + * + * uwb_est_{init/destroy}(): To initialize/release the EST subsystem. + * + * uwb_est_[u]register(): To un/register event size tables + * uwb_est_grow() + * + * uwb_est_find_size(): Get the size of an event + * uwb_est_get_size() + */ +#include +#define D_LOCAL 0 +#include +#include "uwb-internal.h" + + +struct uwb_est { + u16 type_event_high; + u16 vendor, product; + u8 entries; + const struct uwb_est_entry *entry; +}; + + +static struct uwb_est *uwb_est; +static u8 uwb_est_size; +static u8 uwb_est_used; +static DEFINE_RWLOCK(uwb_est_lock); + +/** + * WUSB Standard Event Size Table, HWA-RC interface + * + * Sizes for events and notifications type 0 (general), high nibble 0. + */ +static +struct uwb_est_entry uwb_est_00_00xx[] = { + [UWB_RC_EVT_IE_RCV] = { + .size = sizeof(struct uwb_rc_evt_ie_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_ie_rcv, wIELength), + }, + [UWB_RC_EVT_BEACON] = { + .size = sizeof(struct uwb_rc_evt_beacon), + .offset = 1 + offsetof(struct uwb_rc_evt_beacon, wBeaconInfoLength), + }, + [UWB_RC_EVT_BEACON_SIZE] = { + .size = sizeof(struct uwb_rc_evt_beacon_size), + }, + [UWB_RC_EVT_BPOIE_CHANGE] = { + .size = sizeof(struct uwb_rc_evt_bpoie_change), + .offset = 1 + offsetof(struct uwb_rc_evt_bpoie_change, + wBPOIELength), + }, + [UWB_RC_EVT_BP_SLOT_CHANGE] = { + .size = sizeof(struct uwb_rc_evt_bp_slot_change), + }, + [UWB_RC_EVT_BP_SWITCH_IE_RCV] = { + .size = sizeof(struct uwb_rc_evt_bp_switch_ie_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_bp_switch_ie_rcv, wIELength), + }, + [UWB_RC_EVT_DEV_ADDR_CONFLICT] = { + .size = sizeof(struct uwb_rc_evt_dev_addr_conflict), + }, + [UWB_RC_EVT_DRP_AVAIL] = { + .size = sizeof(struct uwb_rc_evt_drp_avail) + }, + [UWB_RC_EVT_DRP] = { + .size = sizeof(struct uwb_rc_evt_drp), + .offset = 1 + offsetof(struct uwb_rc_evt_drp, ie_length), + }, + [UWB_RC_EVT_BP_SWITCH_STATUS] = { + .size = sizeof(struct uwb_rc_evt_bp_switch_status), + }, + [UWB_RC_EVT_CMD_FRAME_RCV] = { + .size = sizeof(struct uwb_rc_evt_cmd_frame_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_cmd_frame_rcv, dataLength), + }, + [UWB_RC_EVT_CHANNEL_CHANGE_IE_RCV] = { + .size = sizeof(struct uwb_rc_evt_channel_change_ie_rcv), + .offset = 1 + offsetof(struct uwb_rc_evt_channel_change_ie_rcv, wIELength), + }, + [UWB_RC_CMD_CHANNEL_CHANGE] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_DEV_ADDR_MGMT] = { + .size = sizeof(struct uwb_rc_evt_dev_addr_mgmt) }, + [UWB_RC_CMD_GET_IE] = { + .size = sizeof(struct uwb_rc_evt_get_ie), + .offset = 1 + offsetof(struct uwb_rc_evt_get_ie, wIELength), + }, + [UWB_RC_CMD_RESET] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SCAN] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_BEACON_FILTER] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_DRP_IE] = { + .size = sizeof(struct uwb_rc_evt_set_drp_ie), + }, + [UWB_RC_CMD_SET_IE] = { + .size = sizeof(struct uwb_rc_evt_set_ie), + }, + [UWB_RC_CMD_SET_NOTIFICATION_FILTER] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_TX_POWER] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SLEEP] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_START_BEACON] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_STOP_BEACON] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_BP_MERGE] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SEND_COMMAND_FRAME] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, + [UWB_RC_CMD_SET_ASIE_NOTIF] = { + .size = sizeof(struct uwb_rc_evt_confirm), + }, +}; + +static +struct uwb_est_entry uwb_est_01_00xx[] = { + [UWB_RC_DAA_ENERGY_DETECTED] = { + .size = sizeof(struct uwb_rc_evt_daa_energy_detected), + }, + [UWB_RC_SET_DAA_ENERGY_MASK] = { + .size = sizeof(struct uwb_rc_evt_set_daa_energy_mask), + }, + [UWB_RC_SET_NOTIFICATION_FILTER_EX] = { + .size = sizeof(struct uwb_rc_evt_set_notification_filter_ex), + }, +}; + +/** + * Initialize the EST subsystem + * + * Register the standard tables also. + * + * FIXME: tag init + */ +int uwb_est_create(void) +{ + int result; + + uwb_est_size = 2; + uwb_est_used = 0; + uwb_est = kzalloc(uwb_est_size * sizeof(uwb_est[0]), GFP_KERNEL); + if (uwb_est == NULL) + return -ENOMEM; + + result = uwb_est_register(UWB_RC_CET_GENERAL, 0, 0xffff, 0xffff, + uwb_est_00_00xx, ARRAY_SIZE(uwb_est_00_00xx)); + if (result < 0) + goto out; + result = uwb_est_register(UWB_RC_CET_EX_TYPE_1, 0, 0xffff, 0xffff, + uwb_est_01_00xx, ARRAY_SIZE(uwb_est_01_00xx)); +out: + return result; +} + + +/** Clean it up */ +void uwb_est_destroy(void) +{ + kfree(uwb_est); + uwb_est = NULL; + uwb_est_size = uwb_est_used = 0; +} + + +/** + * Double the capacity of the EST table + * + * @returns 0 if ok, < 0 errno no error. + */ +static +int uwb_est_grow(void) +{ + size_t actual_size = uwb_est_size * sizeof(uwb_est[0]); + void *new = kmalloc(2 * actual_size, GFP_ATOMIC); + if (new == NULL) + return -ENOMEM; + memcpy(new, uwb_est, actual_size); + memset(new + actual_size, 0, actual_size); + kfree(uwb_est); + uwb_est = new; + uwb_est_size *= 2; + return 0; +} + + +/** + * Register an event size table + * + * Makes room for it if the table is full, and then inserts it in the + * right position (entries are sorted by type, event_high, vendor and + * then product). + * + * @vendor: vendor code for matching against the device (0x0000 and + * 0xffff mean any); use 0x0000 to force all to match without + * checking possible vendor specific ones, 0xfffff to match + * after checking vendor specific ones. + * + * @product: product code from that vendor; same matching rules, use + * 0x0000 for not allowing vendor specific matches, 0xffff + * for allowing. + * + * This arragement just makes the tables sort differenty. Because the + * table is sorted by growing type-event_high-vendor-product, a zero + * vendor will match before than a 0x456a vendor, that will match + * before a 0xfffff vendor. + * + * @returns 0 if ok, < 0 errno on error (-ENOENT if not found). + */ +/* FIXME: add bus type to vendor/product code */ +int uwb_est_register(u8 type, u8 event_high, u16 vendor, u16 product, + const struct uwb_est_entry *entry, size_t entries) +{ + unsigned long flags; + unsigned itr; + u16 type_event_high; + int result = 0; + + write_lock_irqsave(&uwb_est_lock, flags); + if (uwb_est_used == uwb_est_size) { + result = uwb_est_grow(); + if (result < 0) + goto out; + } + /* Find the right spot to insert it in */ + type_event_high = type << 8 | event_high; + for (itr = 0; itr < uwb_est_used; itr++) + if (uwb_est[itr].type_event_high < type + && uwb_est[itr].vendor < vendor + && uwb_est[itr].product < product) + break; + + /* Shift others to make room for the new one? */ + if (itr < uwb_est_used) + memmove(&uwb_est[itr+1], &uwb_est[itr], uwb_est_used - itr); + uwb_est[itr].type_event_high = type << 8 | event_high; + uwb_est[itr].vendor = vendor; + uwb_est[itr].product = product; + uwb_est[itr].entry = entry; + uwb_est[itr].entries = entries; + uwb_est_used++; +out: + write_unlock_irqrestore(&uwb_est_lock, flags); + return result; +} +EXPORT_SYMBOL_GPL(uwb_est_register); + + +/** + * Unregister an event size table + * + * This just removes the specified entry and moves the ones after it + * to fill in the gap. This is needed to keep the list sorted; no + * reallocation is done to reduce the size of the table. + * + * We unregister by all the data we used to register instead of by + * pointer to the @entry array because we might have used the same + * table for a bunch of IDs (for example). + * + * @returns 0 if ok, < 0 errno on error (-ENOENT if not found). + */ +int uwb_est_unregister(u8 type, u8 event_high, u16 vendor, u16 product, + const struct uwb_est_entry *entry, size_t entries) +{ + unsigned long flags; + unsigned itr; + struct uwb_est est_cmp = { + .type_event_high = type << 8 | event_high, + .vendor = vendor, + .product = product, + .entry = entry, + .entries = entries + }; + write_lock_irqsave(&uwb_est_lock, flags); + for (itr = 0; itr < uwb_est_used; itr++) + if (!memcmp(&uwb_est[itr], &est_cmp, sizeof(est_cmp))) + goto found; + write_unlock_irqrestore(&uwb_est_lock, flags); + return -ENOENT; + +found: + if (itr < uwb_est_used - 1) /* Not last one? move ones above */ + memmove(&uwb_est[itr], &uwb_est[itr+1], uwb_est_used - itr - 1); + uwb_est_used--; + write_unlock_irqrestore(&uwb_est_lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(uwb_est_unregister); + + +/** + * Get the size of an event from a table + * + * @rceb: pointer to the buffer with the event + * @rceb_size: size of the area pointed to by @rceb in bytes. + * @returns: > 0 Size of the event + * -ENOSPC An area big enough was not provided to look + * ahead into the event's guts and guess the size. + * -EINVAL Unknown event code (wEvent). + * + * This will look at the received RCEB and guess what is the total + * size. For variable sized events, it will look further ahead into + * their length field to see how much data should be read. + * + * Note this size is *not* final--the neh (Notification/Event Handle) + * might specificy an extra size to add. + */ +static +ssize_t uwb_est_get_size(struct uwb_rc *uwb_rc, struct uwb_est *est, + u8 event_low, const struct uwb_rceb *rceb, + size_t rceb_size) +{ + unsigned offset; + ssize_t size; + struct device *dev = &uwb_rc->uwb_dev.dev; + const struct uwb_est_entry *entry; + + size = -ENOENT; + if (event_low >= est->entries) { /* in range? */ + if (printk_ratelimit()) + dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " + "event %u out of range\n", + est, est->type_event_high, est->vendor, + est->product, est->entries, + event_low); + goto out; + } + size = -ENOENT; + entry = &est->entry[event_low]; + if (entry->size == 0 && entry->offset == 0) { /* unknown? */ + if (printk_ratelimit()) + dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " + "event %u unknown\n", + est, est->type_event_high, est->vendor, + est->product, est->entries, event_low); + goto out; + } + offset = entry->offset; /* extra fries with that? */ + if (offset == 0) + size = entry->size; + else { + /* Ops, got an extra size field at 'offset'--read it */ + const void *ptr = rceb; + size_t type_size = 0; + offset--; + size = -ENOSPC; /* enough data for more? */ + switch (entry->type) { + case UWB_EST_16: type_size = sizeof(__le16); break; + case UWB_EST_8: type_size = sizeof(u8); break; + default: BUG(); + } + if (offset + type_size >= rceb_size) { + if (printk_ratelimit()) + dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " + "not enough data to read extra size\n", + est, est->type_event_high, est->vendor, + est->product, est->entries); + goto out; + } + size = entry->size; + ptr += offset; + switch (entry->type) { + case UWB_EST_16: size += le16_to_cpu(*(__le16 *)ptr); break; + case UWB_EST_8: size += *(u8 *)ptr; break; + default: BUG(); + } + } +out: + return size; +} + + +/** + * Guesses the size of a WA event + * + * @rceb: pointer to the buffer with the event + * @rceb_size: size of the area pointed to by @rceb in bytes. + * @returns: > 0 Size of the event + * -ENOSPC An area big enough was not provided to look + * ahead into the event's guts and guess the size. + * -EINVAL Unknown event code (wEvent). + * + * This will look at the received RCEB and guess what is the total + * size by checking all the tables registered with + * uwb_est_register(). For variable sized events, it will look further + * ahead into their length field to see how much data should be read. + * + * Note this size is *not* final--the neh (Notification/Event Handle) + * might specificy an extra size to add or replace. + */ +ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, + size_t rceb_size) +{ + /* FIXME: add vendor/product data */ + ssize_t size; + struct device *dev = &rc->uwb_dev.dev; + unsigned long flags; + unsigned itr; + u16 type_event_high, event; + u8 *ptr = (u8 *) rceb; + + read_lock_irqsave(&uwb_est_lock, flags); + d_printf(2, dev, "Size query for event 0x%02x/%04x/%02x," + " buffer size %ld\n", + (unsigned) rceb->bEventType, + (unsigned) le16_to_cpu(rceb->wEvent), + (unsigned) rceb->bEventContext, + (long) rceb_size); + size = -ENOSPC; + if (rceb_size < sizeof(*rceb)) + goto out; + event = le16_to_cpu(rceb->wEvent); + type_event_high = rceb->bEventType << 8 | (event & 0xff00) >> 8; + for (itr = 0; itr < uwb_est_used; itr++) { + d_printf(3, dev, "Checking EST 0x%04x/%04x/%04x\n", + uwb_est[itr].type_event_high, uwb_est[itr].vendor, + uwb_est[itr].product); + if (uwb_est[itr].type_event_high != type_event_high) + continue; + size = uwb_est_get_size(rc, &uwb_est[itr], + event & 0x00ff, rceb, rceb_size); + /* try more tables that might handle the same type */ + if (size != -ENOENT) + goto out; + } + /* FIXME: downgrade to _dbg() */ + if (printk_ratelimit()) + dev_err(dev, "event 0x%02x/%04x/%02x: no handlers available; " + "RCEB %02x %02x %02x %02x\n", + (unsigned) rceb->bEventType, + (unsigned) le16_to_cpu(rceb->wEvent), + (unsigned) rceb->bEventContext, + ptr[0], ptr[1], ptr[2], ptr[3]); + size = -ENOENT; +out: + read_unlock_irqrestore(&uwb_est_lock, flags); + return size; +} +EXPORT_SYMBOL_GPL(uwb_est_find_size); diff --git a/drivers/uwb/neh.c b/drivers/uwb/neh.c new file mode 100644 index 00000000000..91b61480e1e --- /dev/null +++ b/drivers/uwb/neh.c @@ -0,0 +1,616 @@ +/* + * WUSB Wire Adapter: Radio Control Interface (WUSB[8]) + * Notification and Event Handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * The RC interface of the Host Wire Adapter (USB dongle) or WHCI PCI + * card delivers a stream of notifications and events to the + * notification end event endpoint or area. This code takes care of + * getting a buffer with that data, breaking it up in separate + * notifications and events and then deliver those. + * + * Events are answers to commands and they carry a context ID that + * associates them to the command. Notifications are that, + * notifications, they come out of the blue and have a context ID of + * zero. Think of the context ID kind of like a handler. The + * uwb_rc_neh_* code deals with managing context IDs. + * + * This is why you require a handle to operate on a UWB host. When you + * open a handle a context ID is assigned to you. + * + * So, as it is done is: + * + * 1. Add an event handler [uwb_rc_neh_add()] (assigns a ctx id) + * 2. Issue command [rc->cmd(rc, ...)] + * 3. Arm the timeout timer [uwb_rc_neh_arm()] + * 4, Release the reference to the neh [uwb_rc_neh_put()] + * 5. Wait for the callback + * 6. Command result (RCEB) is passed to the callback + * + * If (2) fails, you should remove the handle [uwb_rc_neh_rm()] + * instead of arming the timer. + * + * Handles are for using in *serialized* code, single thread. + * + * When the notification/event comes, the IRQ handler/endpoint + * callback passes the data read to uwb_rc_neh_grok() which will break + * it up in a discrete series of events, look up who is listening for + * them and execute the pertinent callbacks. + * + * If the reader detects an error while reading the data stream, call + * uwb_rc_neh_error(). + * + * CONSTRAINTS/ASSUMPTIONS: + * + * - Most notifications/events are small (less thank .5k), copying + * around is ok. + * + * - Notifications/events are ALWAYS smaller than PAGE_SIZE + * + * - Notifications/events always come in a single piece (ie: a buffer + * will always contain entire notifications/events). + * + * - we cannot know in advance how long each event is (because they + * lack a length field in their header--smart move by the standards + * body, btw). So we need a facility to get the event size given the + * header. This is what the EST code does (notif/Event Size + * Tables), check nest.c--as well, you can associate the size to + * the handle [w/ neh->extra_size()]. + * + * - Most notifications/events are fixed size; only a few are variable + * size (NEST takes care of that). + * + * - Listeners of events expect them, so they usually provide a + * buffer, as they know the size. Listeners to notifications don't, + * so we allocate their buffers dynamically. + */ +#include +#include +#include + +#include "uwb-internal.h" +#define D_LOCAL 0 +#include + +/* + * UWB Radio Controller Notification/Event Handle + * + * Represents an entity waiting for an event coming from the UWB Radio + * Controller with a given context id (context) and type (evt_type and + * evt). On reception of the notification/event, the callback (cb) is + * called with the event. + * + * If the timer expires before the event is received, the callback is + * called with -ETIMEDOUT as the event size. + */ +struct uwb_rc_neh { + struct kref kref; + + struct uwb_rc *rc; + u8 evt_type; + __le16 evt; + u8 context; + uwb_rc_cmd_cb_f cb; + void *arg; + + struct timer_list timer; + struct list_head list_node; +}; + +static void uwb_rc_neh_timer(unsigned long arg); + +static void uwb_rc_neh_release(struct kref *kref) +{ + struct uwb_rc_neh *neh = container_of(kref, struct uwb_rc_neh, kref); + + kfree(neh); +} + +static void uwb_rc_neh_get(struct uwb_rc_neh *neh) +{ + kref_get(&neh->kref); +} + +/** + * uwb_rc_neh_put - release reference to a neh + * @neh: the neh + */ +void uwb_rc_neh_put(struct uwb_rc_neh *neh) +{ + kref_put(&neh->kref, uwb_rc_neh_release); +} + + +/** + * Assigns @neh a context id from @rc's pool + * + * @rc: UWB Radio Controller descriptor; @rc->neh_lock taken + * @neh: Notification/Event Handle + * @returns 0 if context id was assigned ok; < 0 errno on error (if + * all the context IDs are taken). + * + * (assumes @wa is locked). + * + * NOTE: WUSB spec reserves context ids 0x00 for notifications and + * 0xff is invalid, so they must not be used. Initialization + * fills up those two in the bitmap so they are not allocated. + * + * We spread the allocation around to reduce the posiblity of two + * consecutive opened @neh's getting the same context ID assigned (to + * avoid surprises with late events that timed out long time ago). So + * first we search from where @rc->ctx_roll is, if not found, we + * search from zero. + */ +static +int __uwb_rc_ctx_get(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + int result; + result = find_next_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX, + rc->ctx_roll++); + if (result < UWB_RC_CTX_MAX) + goto found; + result = find_first_zero_bit(rc->ctx_bm, UWB_RC_CTX_MAX); + if (result < UWB_RC_CTX_MAX) + goto found; + return -ENFILE; +found: + set_bit(result, rc->ctx_bm); + neh->context = result; + return 0; +} + + +/** Releases @neh's context ID back to @rc (@rc->neh_lock is locked). */ +static +void __uwb_rc_ctx_put(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + struct device *dev = &rc->uwb_dev.dev; + if (neh->context == 0) + return; + if (test_bit(neh->context, rc->ctx_bm) == 0) { + dev_err(dev, "context %u not set in bitmap\n", + neh->context); + WARN_ON(1); + } + clear_bit(neh->context, rc->ctx_bm); + neh->context = 0; +} + +/** + * uwb_rc_neh_add - add a neh for a radio controller command + * @rc: the radio controller + * @cmd: the radio controller command + * @expected_type: the type of the expected response event + * @expected_event: the expected event ID + * @cb: callback for when the event is received + * @arg: argument for the callback + * + * Creates a neh and adds it to the list of those waiting for an + * event. A context ID will be assigned to the command. + */ +struct uwb_rc_neh *uwb_rc_neh_add(struct uwb_rc *rc, struct uwb_rccb *cmd, + u8 expected_type, u16 expected_event, + uwb_rc_cmd_cb_f cb, void *arg) +{ + int result; + unsigned long flags; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_neh *neh; + + neh = kzalloc(sizeof(*neh), GFP_KERNEL); + if (neh == NULL) { + result = -ENOMEM; + goto error_kzalloc; + } + + kref_init(&neh->kref); + INIT_LIST_HEAD(&neh->list_node); + init_timer(&neh->timer); + neh->timer.function = uwb_rc_neh_timer; + neh->timer.data = (unsigned long)neh; + + neh->rc = rc; + neh->evt_type = expected_type; + neh->evt = cpu_to_le16(expected_event); + neh->cb = cb; + neh->arg = arg; + + spin_lock_irqsave(&rc->neh_lock, flags); + result = __uwb_rc_ctx_get(rc, neh); + if (result >= 0) { + cmd->bCommandContext = neh->context; + list_add_tail(&neh->list_node, &rc->neh_list); + uwb_rc_neh_get(neh); + } + spin_unlock_irqrestore(&rc->neh_lock, flags); + if (result < 0) + goto error_ctx_get; + + return neh; + +error_ctx_get: + kfree(neh); +error_kzalloc: + dev_err(dev, "cannot open handle to radio controller: %d\n", result); + return ERR_PTR(result); +} + +static void __uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + del_timer(&neh->timer); + __uwb_rc_ctx_put(rc, neh); + list_del(&neh->list_node); +} + +/** + * uwb_rc_neh_rm - remove a neh. + * @rc: the radio controller + * @neh: the neh to remove + * + * Remove an active neh immediately instead of waiting for the event + * (or a time out). + */ +void uwb_rc_neh_rm(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + __uwb_rc_neh_rm(rc, neh); + spin_unlock_irqrestore(&rc->neh_lock, flags); + + uwb_rc_neh_put(neh); +} + +/** + * uwb_rc_neh_arm - arm an event handler timeout timer + * + * @rc: UWB Radio Controller + * @neh: Notification/event handler for @rc + * + * The timer is only armed if the neh is active. + */ +void uwb_rc_neh_arm(struct uwb_rc *rc, struct uwb_rc_neh *neh) +{ + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + if (neh->context) + mod_timer(&neh->timer, + jiffies + msecs_to_jiffies(UWB_RC_CMD_TIMEOUT_MS)); + spin_unlock_irqrestore(&rc->neh_lock, flags); +} + +static void uwb_rc_neh_cb(struct uwb_rc_neh *neh, struct uwb_rceb *rceb, size_t size) +{ + (*neh->cb)(neh->rc, neh->arg, rceb, size); + uwb_rc_neh_put(neh); +} + +static bool uwb_rc_neh_match(struct uwb_rc_neh *neh, const struct uwb_rceb *rceb) +{ + return neh->evt_type == rceb->bEventType + && neh->evt == rceb->wEvent + && neh->context == rceb->bEventContext; +} + +/** + * Find the handle waiting for a RC Radio Control Event + * + * @rc: UWB Radio Controller + * @rceb: Pointer to the RCEB buffer + * @event_size: Pointer to the size of the RCEB buffer. Might be + * adjusted to take into account the @neh->extra_size + * settings. + * + * If the listener has no buffer (NULL buffer), one is allocated for + * the right size (the amount of data received). @neh->ptr will point + * to the event payload, which always starts with a 'struct + * uwb_rceb'. kfree() it when done. + */ +static +struct uwb_rc_neh *uwb_rc_neh_lookup(struct uwb_rc *rc, + const struct uwb_rceb *rceb) +{ + struct uwb_rc_neh *neh = NULL, *h; + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + + list_for_each_entry(h, &rc->neh_list, list_node) { + if (uwb_rc_neh_match(h, rceb)) { + neh = h; + break; + } + } + + if (neh) + __uwb_rc_neh_rm(rc, neh); + + spin_unlock_irqrestore(&rc->neh_lock, flags); + + return neh; +} + + +/** + * Process notifications coming from the radio control interface + * + * @rc: UWB Radio Control Interface descriptor + * @neh: Notification/Event Handler @neh->ptr points to + * @uwb_evt->buffer. + * + * This function is called by the event/notif handling subsystem when + * notifications arrive (hwarc_probe() arms a notification/event handle + * that calls back this function for every received notification; this + * function then will rearm itself). + * + * Notification data buffers are dynamically allocated by the NEH + * handling code in neh.c [uwb_rc_neh_lookup()]. What is actually + * allocated is space to contain the notification data. + * + * Buffers are prefixed with a Radio Control Event Block (RCEB) as + * defined by the WUSB Wired-Adapter Radio Control interface. We + * just use it for the notification code. + * + * On each case statement we just transcode endianess of the different + * fields. We declare a pointer to a RCI definition of an event, and + * then to a UWB definition of the same event (which are the same, + * remember). Event if we use different pointers + */ +static +void uwb_rc_notif(struct uwb_rc *rc, struct uwb_rceb *rceb, ssize_t size) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_event *uwb_evt; + + if (size == -ESHUTDOWN) + return; + if (size < 0) { + dev_err(dev, "ignoring event with error code %zu\n", + size); + return; + } + + uwb_evt = kzalloc(sizeof(*uwb_evt), GFP_ATOMIC); + if (unlikely(uwb_evt == NULL)) { + dev_err(dev, "no memory to queue event 0x%02x/%04x/%02x\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext); + return; + } + uwb_evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */ + uwb_evt->ts_jiffies = jiffies; + uwb_evt->type = UWB_EVT_TYPE_NOTIF; + uwb_evt->notif.size = size; + uwb_evt->notif.rceb = rceb; + + switch (le16_to_cpu(rceb->wEvent)) { + /* Trap some vendor specific events + * + * FIXME: move this to handling in ptc-est, where we + * register a NULL event handler for these two guys + * using the Intel IDs. + */ + case 0x0103: + dev_info(dev, "FIXME: DEVICE ADD\n"); + return; + case 0x0104: + dev_info(dev, "FIXME: DEVICE RM\n"); + return; + default: + break; + } + + uwbd_event_queue(uwb_evt); +} + +static void uwb_rc_neh_grok_event(struct uwb_rc *rc, struct uwb_rceb *rceb, size_t size) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_neh *neh; + struct uwb_rceb *notif; + + if (rceb->bEventContext == 0) { + notif = kmalloc(size, GFP_ATOMIC); + if (notif) { + memcpy(notif, rceb, size); + uwb_rc_notif(rc, notif, size); + } else + dev_err(dev, "event 0x%02x/%04x/%02x (%zu bytes): no memory\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, size); + } else { + neh = uwb_rc_neh_lookup(rc, rceb); + if (neh) + uwb_rc_neh_cb(neh, rceb, size); + else if (printk_ratelimit()) + dev_warn(dev, "event 0x%02x/%04x/%02x (%zu bytes): nobody cared\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, size); + } +} + +/** + * Given a buffer with one or more UWB RC events/notifications, break + * them up and dispatch them. + * + * @rc: UWB Radio Controller + * @buf: Buffer with the stream of notifications/events + * @buf_size: Amount of data in the buffer + * + * Note each notification/event starts always with a 'struct + * uwb_rceb', so the minimum size if 4 bytes. + * + * The device may pass us events formatted differently than expected. + * These are first filtered, potentially creating a new event in a new + * memory location. If a new event is created by the filter it is also + * freed here. + * + * For each notif/event, tries to guess the size looking at the EST + * tables, then looks for a neh that is waiting for that event and if + * found, copies the payload to the neh's buffer and calls it back. If + * not, the data is ignored. + * + * Note that if we can't find a size description in the EST tables, we + * still might find a size in the 'neh' handle in uwb_rc_neh_lookup(). + * + * Assumptions: + * + * @rc->neh_lock is NOT taken + * + * We keep track of various sizes here: + * size: contains the size of the buffer that is processed for the + * incoming event. this buffer may contain events that are not + * formatted as WHCI. + * real_size: the actual space taken by this event in the buffer. + * We need to keep track of the real size of an event to be able to + * advance the buffer correctly. + * event_size: the size of the event as expected by the core layer + * [OR] the size of the event after filtering. if the filtering + * created a new event in a new memory location then this is + * effectively the size of a new event buffer + */ +void uwb_rc_neh_grok(struct uwb_rc *rc, void *buf, size_t buf_size) +{ + struct device *dev = &rc->uwb_dev.dev; + void *itr; + struct uwb_rceb *rceb; + size_t size, real_size, event_size; + int needtofree; + + d_fnstart(3, dev, "(rc %p buf %p %zu buf_size)\n", rc, buf, buf_size); + d_printf(2, dev, "groking event block: %zu bytes\n", buf_size); + itr = buf; + size = buf_size; + while (size > 0) { + if (size < sizeof(*rceb)) { + dev_err(dev, "not enough data in event buffer to " + "process incoming events (%zu left, minimum is " + "%zu)\n", size, sizeof(*rceb)); + break; + } + + rceb = itr; + if (rc->filter_event) { + needtofree = rc->filter_event(rc, &rceb, size, + &real_size, &event_size); + if (needtofree < 0 && needtofree != -ENOANO) { + dev_err(dev, "BUG: Unable to filter event " + "(0x%02x/%04x/%02x) from " + "device. \n", rceb->bEventType, + le16_to_cpu(rceb->wEvent), + rceb->bEventContext); + break; + } + } else + needtofree = -ENOANO; + /* do real processing if there was no filtering or the + * filtering didn't act */ + if (needtofree == -ENOANO) { + ssize_t ret = uwb_est_find_size(rc, rceb, size); + if (ret < 0) + break; + if (ret > size) { + dev_err(dev, "BUG: hw sent incomplete event " + "0x%02x/%04x/%02x (%zd bytes), only got " + "%zu bytes. We don't handle that.\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, ret, size); + break; + } + real_size = event_size = ret; + } + uwb_rc_neh_grok_event(rc, rceb, event_size); + + if (needtofree == 1) + kfree(rceb); + + itr += real_size; + size -= real_size; + d_printf(2, dev, "consumed %zd bytes, %zu left\n", + event_size, size); + } + d_fnend(3, dev, "(rc %p buf %p %zu buf_size) = void\n", rc, buf, buf_size); +} +EXPORT_SYMBOL_GPL(uwb_rc_neh_grok); + + +/** + * The entity that reads from the device notification/event channel has + * detected an error. + * + * @rc: UWB Radio Controller + * @error: Errno error code + * + */ +void uwb_rc_neh_error(struct uwb_rc *rc, int error) +{ + struct uwb_rc_neh *neh, *next; + unsigned long flags; + + BUG_ON(error >= 0); + spin_lock_irqsave(&rc->neh_lock, flags); + list_for_each_entry_safe(neh, next, &rc->neh_list, list_node) { + __uwb_rc_neh_rm(rc, neh); + uwb_rc_neh_cb(neh, NULL, error); + } + spin_unlock_irqrestore(&rc->neh_lock, flags); +} +EXPORT_SYMBOL_GPL(uwb_rc_neh_error); + + +static void uwb_rc_neh_timer(unsigned long arg) +{ + struct uwb_rc_neh *neh = (struct uwb_rc_neh *)arg; + struct uwb_rc *rc = neh->rc; + unsigned long flags; + + spin_lock_irqsave(&rc->neh_lock, flags); + __uwb_rc_neh_rm(rc, neh); + spin_unlock_irqrestore(&rc->neh_lock, flags); + + uwb_rc_neh_cb(neh, NULL, -ETIMEDOUT); +} + +/** Initializes the @rc's neh subsystem + */ +void uwb_rc_neh_create(struct uwb_rc *rc) +{ + spin_lock_init(&rc->neh_lock); + INIT_LIST_HEAD(&rc->neh_list); + set_bit(0, rc->ctx_bm); /* 0 is reserved (see [WUSB] table 8-65) */ + set_bit(0xff, rc->ctx_bm); /* and 0xff is invalid */ + rc->ctx_roll = 1; +} + + +/** Release's the @rc's neh subsystem */ +void uwb_rc_neh_destroy(struct uwb_rc *rc) +{ + unsigned long flags; + struct uwb_rc_neh *neh, *next; + + spin_lock_irqsave(&rc->neh_lock, flags); + list_for_each_entry_safe(neh, next, &rc->neh_list, list_node) { + __uwb_rc_neh_rm(rc, neh); + uwb_rc_neh_put(neh); + } + spin_unlock_irqrestore(&rc->neh_lock, flags); +} diff --git a/drivers/uwb/reset.c b/drivers/uwb/reset.c new file mode 100644 index 00000000000..8de856fa795 --- /dev/null +++ b/drivers/uwb/reset.c @@ -0,0 +1,362 @@ +/* + * Ultra Wide Band + * UWB basic command support and radio reset + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: + * + * - docs + * + * - Now we are serializing (using the uwb_dev->mutex) the command + * execution; it should be parallelized as much as possible some + * day. + */ +#include +#include + +#include "uwb-internal.h" +#define D_LOCAL 0 +#include + +/** + * Command result codes (WUSB1.0[T8-69]) + */ +static +const char *__strerror[] = { + "success", + "failure", + "hardware failure", + "no more slots", + "beacon is too large", + "invalid parameter", + "unsupported power level", + "time out (wa) or invalid ie data (whci)", + "beacon size exceeded", + "cancelled", + "invalid state", + "invalid size", + "ack not recieved", + "no more asie notification", +}; + + +/** Return a string matching the given error code */ +const char *uwb_rc_strerror(unsigned code) +{ + if (code == 255) + return "time out"; + if (code >= ARRAY_SIZE(__strerror)) + return "unknown error"; + return __strerror[code]; +} + +int uwb_rc_cmd_async(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + u8 expected_type, u16 expected_event, + uwb_rc_cmd_cb_f cb, void *arg) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_neh *neh; + int needtofree = 0; + int result; + + uwb_dev_lock(&rc->uwb_dev); /* Protect against rc->priv being removed */ + if (rc->priv == NULL) { + uwb_dev_unlock(&rc->uwb_dev); + return -ESHUTDOWN; + } + + if (rc->filter_cmd) { + needtofree = rc->filter_cmd(rc, &cmd, &cmd_size); + if (needtofree < 0 && needtofree != -ENOANO) { + dev_err(dev, "%s: filter error: %d\n", + cmd_name, needtofree); + uwb_dev_unlock(&rc->uwb_dev); + return needtofree; + } + } + + neh = uwb_rc_neh_add(rc, cmd, expected_type, expected_event, cb, arg); + if (IS_ERR(neh)) { + result = PTR_ERR(neh); + goto out; + } + + result = rc->cmd(rc, cmd, cmd_size); + uwb_dev_unlock(&rc->uwb_dev); + if (result < 0) + uwb_rc_neh_rm(rc, neh); + else + uwb_rc_neh_arm(rc, neh); + uwb_rc_neh_put(neh); +out: + if (needtofree == 1) + kfree(cmd); + return result < 0 ? result : 0; +} +EXPORT_SYMBOL_GPL(uwb_rc_cmd_async); + +struct uwb_rc_cmd_done_params { + struct completion completion; + struct uwb_rceb *reply; + ssize_t reply_size; +}; + +static void uwb_rc_cmd_done(struct uwb_rc *rc, void *arg, + struct uwb_rceb *reply, ssize_t reply_size) +{ + struct uwb_rc_cmd_done_params *p = (struct uwb_rc_cmd_done_params *)arg; + + if (reply_size > 0) { + if (p->reply) + reply_size = min(p->reply_size, reply_size); + else + p->reply = kmalloc(reply_size, GFP_ATOMIC); + + if (p->reply) + memcpy(p->reply, reply, reply_size); + else + reply_size = -ENOMEM; + } + p->reply_size = reply_size; + complete(&p->completion); +} + + +/** + * Generic function for issuing commands to the Radio Control Interface + * + * @rc: UWB Radio Control descriptor + * @cmd_name: Name of the command being issued (for error messages) + * @cmd: Pointer to rccb structure containing the command; + * normally you embed this structure as the first member of + * the full command structure. + * @cmd_size: Size of the whole command buffer pointed to by @cmd. + * @reply: Pointer to where to store the reply + * @reply_size: @reply's size + * @expected_type: Expected type in the return event + * @expected_event: Expected event code in the return event + * @preply: Here a pointer to where the event data is received will + * be stored. Once done with the data, free with kfree(). + * + * This function is generic; it works for commands that return a fixed + * and known size or for commands that return a variable amount of data. + * + * If a buffer is provided, that is used, although it could be chopped + * to the maximum size of the buffer. If the buffer is NULL, then one + * be allocated in *preply with the whole contents of the reply. + * + * @rc needs to be referenced + */ +static +ssize_t __uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + struct uwb_rceb *reply, size_t reply_size, + u8 expected_type, u16 expected_event, + struct uwb_rceb **preply) +{ + ssize_t result = 0; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_cmd_done_params params; + + init_completion(¶ms.completion); + params.reply = reply; + params.reply_size = reply_size; + + result = uwb_rc_cmd_async(rc, cmd_name, cmd, cmd_size, + expected_type, expected_event, + uwb_rc_cmd_done, ¶ms); + if (result) + return result; + + wait_for_completion(¶ms.completion); + + if (preply) + *preply = params.reply; + + if (params.reply_size < 0) + dev_err(dev, "%s: confirmation event 0x%02x/%04x/%02x " + "reception failed: %d\n", cmd_name, + expected_type, expected_event, cmd->bCommandContext, + (int)params.reply_size); + return params.reply_size; +} + + +/** + * Generic function for issuing commands to the Radio Control Interface + * + * @rc: UWB Radio Control descriptor + * @cmd_name: Name of the command being issued (for error messages) + * @cmd: Pointer to rccb structure containing the command; + * normally you embed this structure as the first member of + * the full command structure. + * @cmd_size: Size of the whole command buffer pointed to by @cmd. + * @reply: Pointer to the beginning of the confirmation event + * buffer. Normally bigger than an 'struct hwarc_rceb'. + * You need to fill out reply->bEventType and reply->wEvent (in + * cpu order) as the function will use them to verify the + * confirmation event. + * @reply_size: Size of the reply buffer + * + * The function checks that the length returned in the reply is at + * least as big as @reply_size; if not, it will be deemed an error and + * -EIO returned. + * + * @rc needs to be referenced + */ +ssize_t uwb_rc_cmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + struct uwb_rceb *reply, size_t reply_size) +{ + struct device *dev = &rc->uwb_dev.dev; + ssize_t result; + + result = __uwb_rc_cmd(rc, cmd_name, + cmd, cmd_size, reply, reply_size, + reply->bEventType, reply->wEvent, NULL); + + if (result > 0 && result < reply_size) { + dev_err(dev, "%s: not enough data returned for decoding reply " + "(%zu bytes received vs at least %zu needed)\n", + cmd_name, result, reply_size); + result = -EIO; + } + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_cmd); + + +/** + * Generic function for issuing commands to the Radio Control + * Interface that return an unknown amount of data + * + * @rc: UWB Radio Control descriptor + * @cmd_name: Name of the command being issued (for error messages) + * @cmd: Pointer to rccb structure containing the command; + * normally you embed this structure as the first member of + * the full command structure. + * @cmd_size: Size of the whole command buffer pointed to by @cmd. + * @expected_type: Expected type in the return event + * @expected_event: Expected event code in the return event + * @preply: Here a pointer to where the event data is received will + * be stored. Once done with the data, free with kfree(). + * + * The function checks that the length returned in the reply is at + * least as big as a 'struct uwb_rceb *'; if not, it will be deemed an + * error and -EIO returned. + * + * @rc needs to be referenced + */ +ssize_t uwb_rc_vcmd(struct uwb_rc *rc, const char *cmd_name, + struct uwb_rccb *cmd, size_t cmd_size, + u8 expected_type, u16 expected_event, + struct uwb_rceb **preply) +{ + return __uwb_rc_cmd(rc, cmd_name, cmd, cmd_size, NULL, 0, + expected_type, expected_event, preply); +} +EXPORT_SYMBOL_GPL(uwb_rc_vcmd); + + +/** + * Reset a UWB Host Controller (and all radio settings) + * + * @rc: Host Controller descriptor + * @returns: 0 if ok, < 0 errno code on error + * + * We put the command on kmalloc'ed memory as some arches cannot do + * USB from the stack. The reply event is copied from an stage buffer, + * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details. + */ +int uwb_rc_reset(struct uwb_rc *rc) +{ + int result = -ENOMEM; + struct uwb_rc_evt_confirm reply; + struct uwb_rccb *cmd; + size_t cmd_size = sizeof(*cmd); + + mutex_lock(&rc->uwb_dev.mutex); + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET); + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_RESET; + result = uwb_rc_cmd(rc, "RESET", cmd, cmd_size, + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "RESET: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); +error_kzalloc: + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +int uwbd_msg_handle_reset(struct uwb_event *evt) +{ + struct uwb_rc *rc = evt->rc; + int ret; + + /* Need to prevent the RC hardware module going away while in + the rc->reset() call. */ + if (!try_module_get(rc->owner)) + return 0; + + dev_info(&rc->uwb_dev.dev, "resetting radio controller\n"); + ret = rc->reset(rc); + if (ret) + dev_err(&rc->uwb_dev.dev, "failed to reset hardware: %d\n", ret); + + module_put(rc->owner); + return ret; +} + +/** + * uwb_rc_reset_all - request a reset of the radio controller and PALs + * @rc: the radio controller of the hardware device to be reset. + * + * The full hardware reset of the radio controller and all the PALs + * will be scheduled. + */ +void uwb_rc_reset_all(struct uwb_rc *rc) +{ + struct uwb_event *evt; + + evt = kzalloc(sizeof(struct uwb_event), GFP_ATOMIC); + if (unlikely(evt == NULL)) + return; + + evt->rc = __uwb_rc_get(rc); /* will be put by uwbd's uwbd_event_handle() */ + evt->ts_jiffies = jiffies; + evt->type = UWB_EVT_TYPE_MSG; + evt->message = UWB_EVT_MSG_RESET; + + uwbd_event_queue(evt); +} +EXPORT_SYMBOL_GPL(uwb_rc_reset_all); -- cgit v1.2.3 From 22d203ecef9b0cc1fa8d8f64c935b451ca7d1022 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:08 +0100 Subject: uwb: add the UWB stack (MLME) Most of the MAC Layer Management Entity (MLME) support: address, beacon, IE and scan management. Signed-off-by: David Vrabel --- drivers/uwb/address.c | 374 +++++++++++++++++++++++++++++ drivers/uwb/beacon.c | 644 ++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/uwb/ie.c | 570 ++++++++++++++++++++++++++++++++++++++++++++ drivers/uwb/scan.c | 133 +++++++++++ 4 files changed, 1721 insertions(+) create mode 100644 drivers/uwb/address.c create mode 100644 drivers/uwb/beacon.c create mode 100644 drivers/uwb/ie.c create mode 100644 drivers/uwb/scan.c diff --git a/drivers/uwb/address.c b/drivers/uwb/address.c new file mode 100644 index 00000000000..1664ae5f170 --- /dev/null +++ b/drivers/uwb/address.c @@ -0,0 +1,374 @@ +/* + * Ultra Wide Band + * Address management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include +#include +#include +#include +#include +#include +#include "uwb-internal.h" + + +/** Device Address Management command */ +struct uwb_rc_cmd_dev_addr_mgmt { + struct uwb_rccb rccb; + u8 bmOperationType; + u8 baAddr[6]; +} __attribute__((packed)); + + +/** + * Low level command for setting/getting UWB radio's addresses + * + * @hwarc: HWA Radio Control interface instance + * @bmOperationType: + * Set/get, MAC/DEV (see WUSB1.0[8.6.2.2]) + * @baAddr: address buffer--assumed to have enough data to hold + * the address type requested. + * @reply: Pointer to reply buffer (can be stack allocated) + * @returns: 0 if ok, < 0 errno code on error. + * + * @cmd has to be allocated because USB cannot grok USB or vmalloc + * buffers depending on your combination of host architecture. + */ +static +int uwb_rc_dev_addr_mgmt(struct uwb_rc *rc, + u8 bmOperationType, const u8 *baAddr, + struct uwb_rc_evt_dev_addr_mgmt *reply) +{ + int result; + struct uwb_rc_cmd_dev_addr_mgmt *cmd; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_DEV_ADDR_MGMT); + cmd->bmOperationType = bmOperationType; + if (baAddr) { + size_t size = 0; + switch (bmOperationType >> 1) { + case 0: size = 2; break; + case 1: size = 6; break; + default: BUG(); + } + memcpy(cmd->baAddr, baAddr, size); + } + reply->rceb.bEventType = UWB_RC_CET_GENERAL; + reply->rceb.wEvent = UWB_RC_CMD_DEV_ADDR_MGMT; + result = uwb_rc_cmd(rc, "DEV-ADDR-MGMT", + &cmd->rccb, sizeof(*cmd), + &reply->rceb, sizeof(*reply)); + if (result < 0) + goto error_cmd; + if (result < sizeof(*reply)) { + dev_err(&rc->uwb_dev.dev, + "DEV-ADDR-MGMT: not enough data replied: " + "%d vs %zu bytes needed\n", result, sizeof(*reply)); + result = -ENOMSG; + } else if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "DEV-ADDR-MGMT: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply->bResultCode), + reply->bResultCode); + result = -EIO; + } else + result = 0; +error_cmd: + kfree(cmd); +error_kzalloc: + return result; +} + + +/** + * Set the UWB RC MAC or device address. + * + * @rc: UWB Radio Controller + * @_addr: Pointer to address to write [assumed to be either a + * 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *']. + * @type: Type of address to set (UWB_ADDR_DEV or UWB_ADDR_MAC). + * @returns: 0 if ok, < 0 errno code on error. + * + * Some anal retentivity here: even if both 'struct + * uwb_{dev,mac}_addr' have the actual byte array in the same offset + * and I could just pass _addr to hwarc_cmd_dev_addr_mgmt(), I prefer + * to use some syntatic sugar in case someday we decide to change the + * format of the structs. The compiler will optimize it out anyway. + */ +static int uwb_rc_addr_set(struct uwb_rc *rc, + const void *_addr, enum uwb_addr_type type) +{ + int result; + u8 bmOperationType = 0x1; /* Set address */ + const struct uwb_dev_addr *dev_addr = _addr; + const struct uwb_mac_addr *mac_addr = _addr; + struct uwb_rc_evt_dev_addr_mgmt reply; + const u8 *baAddr; + + result = -EINVAL; + switch (type) { + case UWB_ADDR_DEV: + baAddr = dev_addr->data; + break; + case UWB_ADDR_MAC: + baAddr = mac_addr->data; + bmOperationType |= 0x2; + break; + default: + return result; + } + return uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &reply); +} + + +/** + * Get the UWB radio's MAC or device address. + * + * @rc: UWB Radio Controller + * @_addr: Where to write the address data [assumed to be either a + * 'struct uwb_mac_addr *' or a 'struct uwb_dev_addr *']. + * @type: Type of address to get (UWB_ADDR_DEV or UWB_ADDR_MAC). + * @returns: 0 if ok (and *_addr set), < 0 errno code on error. + * + * See comment in uwb_rc_addr_set() about anal retentivity in the + * type handling of the address variables. + */ +static int uwb_rc_addr_get(struct uwb_rc *rc, + void *_addr, enum uwb_addr_type type) +{ + int result; + u8 bmOperationType = 0x0; /* Get address */ + struct uwb_rc_evt_dev_addr_mgmt evt; + struct uwb_dev_addr *dev_addr = _addr; + struct uwb_mac_addr *mac_addr = _addr; + u8 *baAddr; + + result = -EINVAL; + switch (type) { + case UWB_ADDR_DEV: + baAddr = dev_addr->data; + break; + case UWB_ADDR_MAC: + bmOperationType |= 0x2; + baAddr = mac_addr->data; + break; + default: + return result; + } + result = uwb_rc_dev_addr_mgmt(rc, bmOperationType, baAddr, &evt); + if (result == 0) + switch (type) { + case UWB_ADDR_DEV: + memcpy(&dev_addr->data, evt.baAddr, + sizeof(dev_addr->data)); + break; + case UWB_ADDR_MAC: + memcpy(&mac_addr->data, evt.baAddr, + sizeof(mac_addr->data)); + break; + default: /* shut gcc up */ + BUG(); + } + return result; +} + + +/** Get @rc's MAC address to @addr */ +int uwb_rc_mac_addr_get(struct uwb_rc *rc, + struct uwb_mac_addr *addr) { + return uwb_rc_addr_get(rc, addr, UWB_ADDR_MAC); +} +EXPORT_SYMBOL_GPL(uwb_rc_mac_addr_get); + + +/** Get @rc's device address to @addr */ +int uwb_rc_dev_addr_get(struct uwb_rc *rc, + struct uwb_dev_addr *addr) { + return uwb_rc_addr_get(rc, addr, UWB_ADDR_DEV); +} +EXPORT_SYMBOL_GPL(uwb_rc_dev_addr_get); + + +/** Set @rc's address to @addr */ +int uwb_rc_mac_addr_set(struct uwb_rc *rc, + const struct uwb_mac_addr *addr) +{ + int result = -EINVAL; + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_addr_set(rc, addr, UWB_ADDR_MAC); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + + +/** Set @rc's address to @addr */ +int uwb_rc_dev_addr_set(struct uwb_rc *rc, + const struct uwb_dev_addr *addr) +{ + int result = -EINVAL; + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_addr_set(rc, addr, UWB_ADDR_DEV); + rc->uwb_dev.dev_addr = *addr; + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* Returns !0 if given address is already assigned to device. */ +int __uwb_mac_addr_assigned_check(struct device *dev, void *_addr) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_mac_addr *addr = _addr; + + if (!uwb_mac_addr_cmp(addr, &uwb_dev->mac_addr)) + return !0; + return 0; +} + +/* Returns !0 if given address is already assigned to device. */ +int __uwb_dev_addr_assigned_check(struct device *dev, void *_addr) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_dev_addr *addr = _addr; + if (!uwb_dev_addr_cmp(addr, &uwb_dev->dev_addr)) + return !0; + return 0; +} + +/** + * uwb_dev_addr_assign - assigned a generated DevAddr to a radio controller + * @rc: the (local) radio controller device requiring a new DevAddr + * + * A new DevAddr is required when: + * - first setting up a radio controller + * - if the hardware reports a DevAddr conflict + * + * The DevAddr is randomly generated in the generated DevAddr range + * [0x100, 0xfeff]. The number of devices in a beacon group is limited + * by mMaxBPLength (96) so this address space will never be exhausted. + * + * [ECMA-368] 17.1.1, 17.16. + */ +int uwb_rc_dev_addr_assign(struct uwb_rc *rc) +{ + struct uwb_dev_addr new_addr; + + do { + get_random_bytes(new_addr.data, sizeof(new_addr.data)); + } while (new_addr.data[0] == 0x00 || new_addr.data[0] == 0xff + || __uwb_dev_addr_assigned(rc, &new_addr)); + + return uwb_rc_dev_addr_set(rc, &new_addr); +} + +/** + * uwbd_evt_handle_rc_dev_addr_conflict - handle a DEV_ADDR_CONFLICT event + * @evt: the DEV_ADDR_CONFLICT notification from the radio controller + * + * A new (non-conflicting) DevAddr is assigned to the radio controller. + * + * [ECMA-368] 17.1.1.1. + */ +int uwbd_evt_handle_rc_dev_addr_conflict(struct uwb_event *evt) +{ + struct uwb_rc *rc = evt->rc; + + return uwb_rc_dev_addr_assign(rc); +} + +/* + * Print the 48-bit EUI MAC address of the radio controller when + * reading /sys/class/uwb_rc/XX/mac_address + */ +static ssize_t uwb_rc_mac_addr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + struct uwb_mac_addr addr; + ssize_t result; + + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_addr_get(rc, &addr, UWB_ADDR_MAC); + mutex_unlock(&rc->uwb_dev.mutex); + if (result >= 0) { + result = uwb_mac_addr_print(buf, UWB_ADDR_STRSIZE, &addr); + buf[result++] = '\n'; + } + return result; +} + +/* + * Parse a 48 bit address written to /sys/class/uwb_rc/XX/mac_address + * and if correct, set it. + */ +static ssize_t uwb_rc_mac_addr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + struct uwb_mac_addr addr; + ssize_t result; + + result = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\n", + &addr.data[0], &addr.data[1], &addr.data[2], + &addr.data[3], &addr.data[4], &addr.data[5]); + if (result != 6) { + result = -EINVAL; + goto out; + } + if (is_multicast_ether_addr(addr.data)) { + dev_err(&rc->uwb_dev.dev, "refusing to set multicast " + "MAC address %s\n", buf); + result = -EINVAL; + goto out; + } + result = uwb_rc_mac_addr_set(rc, &addr); + if (result == 0) + rc->uwb_dev.mac_addr = addr; +out: + return result < 0 ? result : size; +} +DEVICE_ATTR(mac_address, S_IRUGO | S_IWUSR, uwb_rc_mac_addr_show, uwb_rc_mac_addr_store); + +/** Print @addr to @buf, @return bytes written */ +size_t __uwb_addr_print(char *buf, size_t buf_size, const unsigned char *addr, + int type) +{ + size_t result; + if (type) + result = scnprintf(buf, buf_size, + "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], + addr[3], addr[4], addr[5]); + else + result = scnprintf(buf, buf_size, "%02x:%02x", + addr[1], addr[0]); + return result; +} +EXPORT_SYMBOL_GPL(__uwb_addr_print); diff --git a/drivers/uwb/beacon.c b/drivers/uwb/beacon.c new file mode 100644 index 00000000000..f65a52c0afa --- /dev/null +++ b/drivers/uwb/beacon.c @@ -0,0 +1,644 @@ +/* + * Ultra Wide Band + * Beacon management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include +#include +#include +#include +#include +#include +#include "uwb-internal.h" + +#define D_LOCAL 0 +#include + +/** Start Beaconing command structure */ +struct uwb_rc_cmd_start_beacon { + struct uwb_rccb rccb; + __le16 wBPSTOffset; + u8 bChannelNumber; +} __attribute__((packed)); + + +static int uwb_rc_start_beacon(struct uwb_rc *rc, u16 bpst_offset, u8 channel) +{ + int result; + struct uwb_rc_cmd_start_beacon *cmd; + struct uwb_rc_evt_confirm reply; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_START_BEACON); + cmd->wBPSTOffset = cpu_to_le16(bpst_offset); + cmd->bChannelNumber = channel; + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_START_BEACON; + result = uwb_rc_cmd(rc, "START-BEACON", &cmd->rccb, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "START-BEACON: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); + return result; +} + +static int uwb_rc_stop_beacon(struct uwb_rc *rc) +{ + int result; + struct uwb_rccb *cmd; + struct uwb_rc_evt_confirm reply; + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_STOP_BEACON); + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_STOP_BEACON; + result = uwb_rc_cmd(rc, "STOP-BEACON", cmd, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "STOP-BEACON: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); + return result; +} + +/* + * Start/stop beacons + * + * @rc: UWB Radio Controller to operate on + * @channel: UWB channel on which to beacon (WUSB[table + * 5-12]). If -1, stop beaconing. + * @bpst_offset: Beacon Period Start Time offset; FIXME-do zero + * + * According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB + * of a SET IE command after the device sent the first beacon that includes + * the IEs specified in the SET IE command. So, after we start beaconing we + * check if there is anything in the IE cache and call the SET IE command + * if needed. + */ +int uwb_rc_beacon(struct uwb_rc *rc, int channel, unsigned bpst_offset) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + + mutex_lock(&rc->uwb_dev.mutex); + if (channel < 0) + channel = -1; + if (channel == -1) + result = uwb_rc_stop_beacon(rc); + else { + /* channel >= 0...dah */ + result = uwb_rc_start_beacon(rc, bpst_offset, channel); + if (result < 0) + goto out_up; + if (le16_to_cpu(rc->ies->wIELength) > 0) { + result = uwb_rc_set_ie(rc, rc->ies); + if (result < 0) { + dev_err(dev, "Cannot set new IE on device: " + "%d\n", result); + result = uwb_rc_stop_beacon(rc); + channel = -1; + bpst_offset = 0; + } else + result = 0; + } + } + + if (result < 0) + goto out_up; + rc->beaconing = channel; + + uwb_notify(rc, NULL, uwb_bg_joined(rc) ? UWB_NOTIF_BG_JOIN : UWB_NOTIF_BG_LEAVE); + +out_up: + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* + * Beacon cache + * + * The purpose of this is to speed up the lookup of becon information + * when a new beacon arrives. The UWB Daemon uses it also to keep a + * tab of which devices are in radio distance and which not. When a + * device's beacon stays present for more than a certain amount of + * time, it is considered a new, usable device. When a beacon ceases + * to be received for a certain amount of time, it is considered that + * the device is gone. + * + * FIXME: use an allocator for the entries + * FIXME: use something faster for search than a list + */ + +struct uwb_beca uwb_beca = { + .list = LIST_HEAD_INIT(uwb_beca.list), + .mutex = __MUTEX_INITIALIZER(uwb_beca.mutex) +}; + + +void uwb_bce_kfree(struct kref *_bce) +{ + struct uwb_beca_e *bce = container_of(_bce, struct uwb_beca_e, refcnt); + + kfree(bce->be); + kfree(bce); +} + + +/* Find a beacon by dev addr in the cache */ +static +struct uwb_beca_e *__uwb_beca_find_bydev(const struct uwb_dev_addr *dev_addr) +{ + struct uwb_beca_e *bce, *next; + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + d_printf(6, NULL, "looking for addr %02x:%02x in %02x:%02x\n", + dev_addr->data[0], dev_addr->data[1], + bce->dev_addr.data[0], bce->dev_addr.data[1]); + if (!memcmp(&bce->dev_addr, dev_addr, sizeof(bce->dev_addr))) + goto out; + } + bce = NULL; +out: + return bce; +} + +/* Find a beacon by dev addr in the cache */ +static +struct uwb_beca_e *__uwb_beca_find_bymac(const struct uwb_mac_addr *mac_addr) +{ + struct uwb_beca_e *bce, *next; + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + if (!memcmp(bce->mac_addr, mac_addr->data, + sizeof(bce->mac_addr))) + goto out; + } + bce = NULL; +out: + return bce; +} + +/** + * uwb_dev_get_by_devaddr - get a UWB device with a specific DevAddr + * @rc: the radio controller that saw the device + * @devaddr: DevAddr of the UWB device to find + * + * There may be more than one matching device (in the case of a + * DevAddr conflict), but only the first one is returned. + */ +struct uwb_dev *uwb_dev_get_by_devaddr(struct uwb_rc *rc, + const struct uwb_dev_addr *devaddr) +{ + struct uwb_dev *found = NULL; + struct uwb_beca_e *bce; + + mutex_lock(&uwb_beca.mutex); + bce = __uwb_beca_find_bydev(devaddr); + if (bce) + found = uwb_dev_try_get(rc, bce->uwb_dev); + mutex_unlock(&uwb_beca.mutex); + + return found; +} + +/** + * uwb_dev_get_by_macaddr - get a UWB device with a specific EUI-48 + * @rc: the radio controller that saw the device + * @devaddr: EUI-48 of the UWB device to find + */ +struct uwb_dev *uwb_dev_get_by_macaddr(struct uwb_rc *rc, + const struct uwb_mac_addr *macaddr) +{ + struct uwb_dev *found = NULL; + struct uwb_beca_e *bce; + + mutex_lock(&uwb_beca.mutex); + bce = __uwb_beca_find_bymac(macaddr); + if (bce) + found = uwb_dev_try_get(rc, bce->uwb_dev); + mutex_unlock(&uwb_beca.mutex); + + return found; +} + +/* Initialize a beacon cache entry */ +static void uwb_beca_e_init(struct uwb_beca_e *bce) +{ + mutex_init(&bce->mutex); + kref_init(&bce->refcnt); + stats_init(&bce->lqe_stats); + stats_init(&bce->rssi_stats); +} + +/* + * Add a beacon to the cache + * + * @be: Beacon event information + * @bf: Beacon frame (part of b, really) + * @ts_jiffies: Timestamp (in jiffies) when the beacon was received + */ +struct uwb_beca_e *__uwb_beca_add(struct uwb_rc_evt_beacon *be, + struct uwb_beacon_frame *bf, + unsigned long ts_jiffies) +{ + struct uwb_beca_e *bce; + + bce = kzalloc(sizeof(*bce), GFP_KERNEL); + if (bce == NULL) + return NULL; + uwb_beca_e_init(bce); + bce->ts_jiffies = ts_jiffies; + bce->uwb_dev = NULL; + list_add(&bce->node, &uwb_beca.list); + return bce; +} + +/* + * Wipe out beacon entries that became stale + * + * Remove associated devicest too. + */ +void uwb_beca_purge(void) +{ + struct uwb_beca_e *bce, *next; + unsigned long now = jiffies; + mutex_lock(&uwb_beca.mutex); + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + if (now - bce->ts_jiffies + > msecs_to_jiffies(beacon_timeout_ms)) { + uwbd_dev_offair(bce); + list_del(&bce->node); + uwb_bce_put(bce); + } + } + mutex_unlock(&uwb_beca.mutex); +} + +/* Clean up the whole beacon cache. Called on shutdown */ +void uwb_beca_release(void) +{ + struct uwb_beca_e *bce, *next; + mutex_lock(&uwb_beca.mutex); + list_for_each_entry_safe(bce, next, &uwb_beca.list, node) { + list_del(&bce->node); + uwb_bce_put(bce); + } + mutex_unlock(&uwb_beca.mutex); +} + +static void uwb_beacon_print(struct uwb_rc *rc, struct uwb_rc_evt_beacon *be, + struct uwb_beacon_frame *bf) +{ + char macbuf[UWB_ADDR_STRSIZE]; + char devbuf[UWB_ADDR_STRSIZE]; + char dstbuf[UWB_ADDR_STRSIZE]; + + uwb_mac_addr_print(macbuf, sizeof(macbuf), &bf->Device_Identifier); + uwb_dev_addr_print(devbuf, sizeof(devbuf), &bf->hdr.SrcAddr); + uwb_dev_addr_print(dstbuf, sizeof(dstbuf), &bf->hdr.DestAddr); + dev_info(&rc->uwb_dev.dev, + "BEACON from %s to %s (ch%u offset %u slot %u MAC %s)\n", + devbuf, dstbuf, be->bChannelNumber, be->wBPSTOffset, + bf->Beacon_Slot_Number, macbuf); +} + +/* + * @bce: beacon cache entry, referenced + */ +ssize_t uwb_bce_print_IEs(struct uwb_dev *uwb_dev, struct uwb_beca_e *bce, + char *buf, size_t size) +{ + ssize_t result = 0; + struct uwb_rc_evt_beacon *be; + struct uwb_beacon_frame *bf; + struct uwb_buf_ctx ctx = { + .buf = buf, + .bytes = 0, + .size = size + }; + + mutex_lock(&bce->mutex); + be = bce->be; + if (be == NULL) + goto out; + bf = (void *) be->BeaconInfo; + uwb_ie_for_each(uwb_dev, uwb_ie_dump_hex, &ctx, + bf->IEData, be->wBeaconInfoLength - sizeof(*bf)); + result = ctx.bytes; +out: + mutex_unlock(&bce->mutex); + return result; +} + +/* + * Verify that the beacon event, frame and IEs are ok + */ +static int uwb_verify_beacon(struct uwb_rc *rc, struct uwb_event *evt, + struct uwb_rc_evt_beacon *be) +{ + int result = -EINVAL; + struct uwb_beacon_frame *bf; + struct device *dev = &rc->uwb_dev.dev; + + /* Is there enough data to decode a beacon frame? */ + if (evt->notif.size < sizeof(*be) + sizeof(*bf)) { + dev_err(dev, "BEACON event: Not enough data to decode " + "(%zu vs %zu bytes needed)\n", evt->notif.size, + sizeof(*be) + sizeof(*bf)); + goto error; + } + /* FIXME: make sure beacon frame IEs are fine and that the whole thing + * is consistent */ + result = 0; +error: + return result; +} + +/* + * Handle UWB_RC_EVT_BEACON events + * + * We check the beacon cache to see how the received beacon fares. If + * is there already we refresh the timestamp. If not we create a new + * entry. + * + * According to the WHCI and WUSB specs, only one beacon frame is + * allowed per notification block, so we don't bother about scanning + * for more. + */ +int uwbd_evt_handle_rc_beacon(struct uwb_event *evt) +{ + int result = -EINVAL; + struct uwb_rc *rc; + struct uwb_rc_evt_beacon *be; + struct uwb_beacon_frame *bf; + struct uwb_beca_e *bce; + struct device *dev = &evt->rc->uwb_dev.dev; + unsigned long last_ts; + + rc = evt->rc; + be = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon, rceb); + result = uwb_verify_beacon(rc, evt, be); + if (result < 0) + return result; + + /* Ignore beacon if it is from an alien. */ + if (be->bBeaconType == UWB_RC_BEACON_TYPE_OL_ALIEN || + be->bBeaconType == UWB_RC_BEACON_TYPE_NOL_ALIEN) { + if (printk_ratelimit()) + dev_err(dev, "BEACON received from ALIEN. Action? \n"); + result = -ENOSYS; + return 0; + } + bf = (struct uwb_beacon_frame *) be->BeaconInfo; + + /* + * Drop beacons from devices with a NULL EUI-48 -- they cannot + * be uniquely identified. + * + * It's expected that these will all be WUSB devices and they + * have a WUSB specific connection method so ignoring them + * here shouldn't be a problem. + */ + if (uwb_mac_addr_bcast(&bf->Device_Identifier)) + return 0; + + mutex_lock(&uwb_beca.mutex); + bce = __uwb_beca_find_bymac(&bf->Device_Identifier); + if (bce == NULL) { + /* Not in there, a new device is pinging */ + uwb_beacon_print(evt->rc, be, bf); + bce = __uwb_beca_add(be, bf, evt->ts_jiffies); + if (bce == NULL) { + mutex_unlock(&uwb_beca.mutex); + return -ENOMEM; + } + } + mutex_unlock(&uwb_beca.mutex); + + mutex_lock(&bce->mutex); + /* purge old beacon data */ + kfree(bce->be); + + last_ts = bce->ts_jiffies; + + /* Update commonly used fields */ + bce->ts_jiffies = evt->ts_jiffies; + bce->be = be; + bce->dev_addr = bf->hdr.SrcAddr; + bce->mac_addr = &bf->Device_Identifier; + be->wBPSTOffset = le16_to_cpu(be->wBPSTOffset); + be->wBeaconInfoLength = le16_to_cpu(be->wBeaconInfoLength); + stats_add_sample(&bce->lqe_stats, be->bLQI - 7); + stats_add_sample(&bce->rssi_stats, be->bRSSI + 18); + + /* + * This might be a beacon from a new device. + */ + if (bce->uwb_dev == NULL) + uwbd_dev_onair(evt->rc, bce); + + mutex_unlock(&bce->mutex); + + return 1; /* we keep the event data */ +} + +/* + * Handle UWB_RC_EVT_BEACON_SIZE events + * + * XXXXX + */ +int uwbd_evt_handle_rc_beacon_size(struct uwb_event *evt) +{ + int result = -EINVAL; + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc_evt_beacon_size *bs; + + /* Is there enough data to decode the event? */ + if (evt->notif.size < sizeof(*bs)) { + dev_err(dev, "BEACON SIZE notification: Not enough data to " + "decode (%zu vs %zu bytes needed)\n", + evt->notif.size, sizeof(*bs)); + goto error; + } + bs = container_of(evt->notif.rceb, struct uwb_rc_evt_beacon_size, rceb); + if (0) + dev_info(dev, "Beacon size changed to %u bytes " + "(FIXME: action?)\n", le16_to_cpu(bs->wNewBeaconSize)); + else { + /* temporary hack until we do something with this message... */ + static unsigned count; + if (++count % 1000 == 0) + dev_info(dev, "Beacon size changed %u times " + "(FIXME: action?)\n", count); + } + result = 0; +error: + return result; +} + +/** + * uwbd_evt_handle_rc_bp_slot_change - handle a BP_SLOT_CHANGE event + * @evt: the BP_SLOT_CHANGE notification from the radio controller + * + * If the event indicates that no beacon period slots were available + * then radio controller has transitioned to a non-beaconing state. + * Otherwise, simply save the current beacon slot. + */ +int uwbd_evt_handle_rc_bp_slot_change(struct uwb_event *evt) +{ + struct uwb_rc *rc = evt->rc; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_evt_bp_slot_change *bpsc; + + if (evt->notif.size < sizeof(*bpsc)) { + dev_err(dev, "BP SLOT CHANGE event: Not enough data\n"); + return -EINVAL; + } + bpsc = container_of(evt->notif.rceb, struct uwb_rc_evt_bp_slot_change, rceb); + + mutex_lock(&rc->uwb_dev.mutex); + if (uwb_rc_evt_bp_slot_change_no_slot(bpsc)) { + dev_info(dev, "stopped beaconing: No free slots in BP\n"); + rc->beaconing = -1; + } else + rc->uwb_dev.beacon_slot = uwb_rc_evt_bp_slot_change_slot_num(bpsc); + mutex_unlock(&rc->uwb_dev.mutex); + + return 0; +} + +/** + * Handle UWB_RC_EVT_BPOIE_CHANGE events + * + * XXXXX + */ +struct uwb_ie_bpo { + struct uwb_ie_hdr hdr; + u8 bp_length; + u8 data[]; +} __attribute__((packed)); + +int uwbd_evt_handle_rc_bpoie_change(struct uwb_event *evt) +{ + int result = -EINVAL; + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc_evt_bpoie_change *bpoiec; + struct uwb_ie_bpo *bpoie; + static unsigned count; /* FIXME: this is a temp hack */ + size_t iesize; + + /* Is there enough data to decode it? */ + if (evt->notif.size < sizeof(*bpoiec)) { + dev_err(dev, "BPOIEC notification: Not enough data to " + "decode (%zu vs %zu bytes needed)\n", + evt->notif.size, sizeof(*bpoiec)); + goto error; + } + bpoiec = container_of(evt->notif.rceb, struct uwb_rc_evt_bpoie_change, rceb); + iesize = le16_to_cpu(bpoiec->wBPOIELength); + if (iesize < sizeof(*bpoie)) { + dev_err(dev, "BPOIEC notification: Not enough IE data to " + "decode (%zu vs %zu bytes needed)\n", + iesize, sizeof(*bpoie)); + goto error; + } + if (++count % 1000 == 0) /* Lame placeholder */ + dev_info(dev, "BPOIE: %u changes received\n", count); + /* + * FIXME: At this point we should go over all the IEs in the + * bpoiec->BPOIE array and act on each. + */ + result = 0; +error: + return result; +} + +/** + * uwb_bg_joined - is the RC in a beacon group? + * @rc: the radio controller + * + * Returns true if the radio controller is in a beacon group (even if + * it's the sole member). + */ +int uwb_bg_joined(struct uwb_rc *rc) +{ + return rc->beaconing != -1; +} +EXPORT_SYMBOL_GPL(uwb_bg_joined); + +/* + * Print beaconing state. + */ +static ssize_t uwb_rc_beacon_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + ssize_t result; + + mutex_lock(&rc->uwb_dev.mutex); + result = sprintf(buf, "%d\n", rc->beaconing); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* + * Start beaconing on the specified channel, or stop beaconing. + * + * The BPST offset of when to start searching for a beacon group to + * join may be specified. + */ +static ssize_t uwb_rc_beacon_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + int channel; + unsigned bpst_offset = 0; + ssize_t result = -EINVAL; + + result = sscanf(buf, "%d %u\n", &channel, &bpst_offset); + if (result >= 1) + result = uwb_rc_beacon(rc, channel, bpst_offset); + + return result < 0 ? result : size; +} +DEVICE_ATTR(beacon, S_IRUGO | S_IWUSR, uwb_rc_beacon_show, uwb_rc_beacon_store); diff --git a/drivers/uwb/ie.c b/drivers/uwb/ie.c new file mode 100644 index 00000000000..d54fe09a986 --- /dev/null +++ b/drivers/uwb/ie.c @@ -0,0 +1,570 @@ +/* + * Ultra Wide Band + * Information Element Handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include "uwb-internal.h" +#define D_LOCAL 0 +#include + +/** + * uwb_ie_next - get the next IE in a buffer + * @ptr: start of the buffer containing the IE data + * @len: length of the buffer + * + * Both @ptr and @len are updated so subsequent calls to uwb_ie_next() + * will get the next IE. + * + * NULL is returned (and @ptr and @len will not be updated) if there + * are no more IEs in the buffer or the buffer is too short. + */ +struct uwb_ie_hdr *uwb_ie_next(void **ptr, size_t *len) +{ + struct uwb_ie_hdr *hdr; + size_t ie_len; + + if (*len < sizeof(struct uwb_ie_hdr)) + return NULL; + + hdr = *ptr; + ie_len = sizeof(struct uwb_ie_hdr) + hdr->length; + + if (*len < ie_len) + return NULL; + + *ptr += ie_len; + *len -= ie_len; + + return hdr; +} +EXPORT_SYMBOL_GPL(uwb_ie_next); + +/** + * Get the IEs that a radio controller is sending in its beacon + * + * @uwb_rc: UWB Radio Controller + * @returns: Size read from the system + * + * We don't need to lock the uwb_rc's mutex because we don't modify + * anything. Once done with the iedata buffer, call + * uwb_rc_ie_release(iedata). Don't call kfree on it. + */ +ssize_t uwb_rc_get_ie(struct uwb_rc *uwb_rc, struct uwb_rc_evt_get_ie **pget_ie) +{ + ssize_t result; + struct device *dev = &uwb_rc->uwb_dev.dev; + struct uwb_rccb *cmd = NULL; + struct uwb_rceb *reply = NULL; + struct uwb_rc_evt_get_ie *get_ie; + + d_fnstart(3, dev, "(%p, %p)\n", uwb_rc, pget_ie); + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_GET_IE); + result = uwb_rc_vcmd(uwb_rc, "GET_IE", cmd, sizeof(*cmd), + UWB_RC_CET_GENERAL, UWB_RC_CMD_GET_IE, + &reply); + if (result < 0) + goto error_cmd; + get_ie = container_of(reply, struct uwb_rc_evt_get_ie, rceb); + if (result < sizeof(*get_ie)) { + dev_err(dev, "not enough data returned for decoding GET IE " + "(%zu bytes received vs %zu needed)\n", + result, sizeof(*get_ie)); + result = -EINVAL; + } else if (result < sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength)) { + dev_err(dev, "not enough data returned for decoding GET IE " + "payload (%zu bytes received vs %zu needed)\n", result, + sizeof(*get_ie) + le16_to_cpu(get_ie->wIELength)); + result = -EINVAL; + } else + *pget_ie = get_ie; +error_cmd: + kfree(cmd); +error_kzalloc: + d_fnend(3, dev, "(%p, %p) = %d\n", uwb_rc, pget_ie, (int)result); + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_get_ie); + + +/* + * Given a pointer to an IE, print it in ASCII/hex followed by a new line + * + * @ie_hdr: pointer to the IE header. Length is in there, and it is + * guaranteed that the ie_hdr->length bytes following it are + * safely accesible. + * + * @_data: context data passed from uwb_ie_for_each(), an struct output_ctx + */ +int uwb_ie_dump_hex(struct uwb_dev *uwb_dev, const struct uwb_ie_hdr *ie_hdr, + size_t offset, void *_ctx) +{ + struct uwb_buf_ctx *ctx = _ctx; + const u8 *pl = (void *)(ie_hdr + 1); + u8 pl_itr; + + ctx->bytes += scnprintf(ctx->buf + ctx->bytes, ctx->size - ctx->bytes, + "%02x %02x ", (unsigned) ie_hdr->element_id, + (unsigned) ie_hdr->length); + pl_itr = 0; + while (pl_itr < ie_hdr->length && ctx->bytes < ctx->size) + ctx->bytes += scnprintf(ctx->buf + ctx->bytes, + ctx->size - ctx->bytes, + "%02x ", (unsigned) pl[pl_itr++]); + if (ctx->bytes < ctx->size) + ctx->buf[ctx->bytes++] = '\n'; + return 0; +} +EXPORT_SYMBOL_GPL(uwb_ie_dump_hex); + + +/** + * Verify that a pointer in a buffer points to valid IE + * + * @start: pointer to start of buffer in which IE appears + * @itr: pointer to IE inside buffer that will be verified + * @top: pointer to end of buffer + * + * @returns: 0 if IE is valid, <0 otherwise + * + * Verification involves checking that the buffer can contain a + * header and the amount of data reported in the IE header can be found in + * the buffer. + */ +static +int uwb_rc_ie_verify(struct uwb_dev *uwb_dev, const void *start, + const void *itr, const void *top) +{ + struct device *dev = &uwb_dev->dev; + const struct uwb_ie_hdr *ie_hdr; + + if (top - itr < sizeof(*ie_hdr)) { + dev_err(dev, "Bad IE: no data to decode header " + "(%zu bytes left vs %zu needed) at offset %zu\n", + top - itr, sizeof(*ie_hdr), itr - start); + return -EINVAL; + } + ie_hdr = itr; + itr += sizeof(*ie_hdr); + if (top - itr < ie_hdr->length) { + dev_err(dev, "Bad IE: not enough data for payload " + "(%zu bytes left vs %zu needed) at offset %zu\n", + top - itr, (size_t)ie_hdr->length, + (void *)ie_hdr - start); + return -EINVAL; + } + return 0; +} + + +/** + * Walk a buffer filled with consecutive IE's a buffer + * + * @uwb_dev: UWB device this IEs belong to (for err messages mainly) + * + * @fn: function to call with each IE; if it returns 0, we keep + * traversing the buffer. If it returns !0, we'll stop and return + * that value. + * + * @data: pointer passed to @fn + * + * @buf: buffer where the consecutive IEs are located + * + * @size: size of @buf + * + * Each IE is checked for basic correctness (there is space left for + * the header and the payload). If that test is failed, we stop + * processing. For every good IE, @fn is called. + */ +ssize_t uwb_ie_for_each(struct uwb_dev *uwb_dev, uwb_ie_f fn, void *data, + const void *buf, size_t size) +{ + ssize_t result = 0; + const struct uwb_ie_hdr *ie_hdr; + const void *itr = buf, *top = itr + size; + + while (itr < top) { + if (uwb_rc_ie_verify(uwb_dev, buf, itr, top) != 0) + break; + ie_hdr = itr; + itr += sizeof(*ie_hdr) + ie_hdr->length; + result = fn(uwb_dev, ie_hdr, itr - buf, data); + if (result != 0) + break; + } + return result; +} +EXPORT_SYMBOL_GPL(uwb_ie_for_each); + + +/** + * Replace all IEs currently being transmitted by a device + * + * @cmd: pointer to the SET-IE command with the IEs to set + * @size: size of @buf + */ +int uwb_rc_set_ie(struct uwb_rc *rc, struct uwb_rc_cmd_set_ie *cmd) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_evt_set_ie reply; + + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_SET_IE; + result = uwb_rc_cmd(rc, "SET-IE", &cmd->rccb, + sizeof(*cmd) + le16_to_cpu(cmd->wIELength), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + else if (result != sizeof(reply)) { + dev_err(dev, "SET-IE: not enough data to decode reply " + "(%d bytes received vs %zu needed)\n", + result, sizeof(reply)); + result = -EIO; + } else if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(dev, "SET-IE: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + } else + result = 0; +error_cmd: + return result; +} + +/** + * Determine by IE id if IE is host settable + * WUSB 1.0 [8.6.2.8 Table 8.85] + * + * EXCEPTION: + * All but UWB_IE_WLP appears in Table 8.85 from WUSB 1.0. Setting this IE + * is required for the WLP substack to perform association with its WSS so + * we hope that the WUSB spec will be changed to reflect this. + */ +static +int uwb_rc_ie_is_host_settable(enum uwb_ie element_id) +{ + if (element_id == UWB_PCA_AVAILABILITY || + element_id == UWB_BP_SWITCH_IE || + element_id == UWB_MAC_CAPABILITIES_IE || + element_id == UWB_PHY_CAPABILITIES_IE || + element_id == UWB_APP_SPEC_PROBE_IE || + element_id == UWB_IDENTIFICATION_IE || + element_id == UWB_MASTER_KEY_ID_IE || + element_id == UWB_IE_WLP || + element_id == UWB_APP_SPEC_IE) + return 1; + return 0; +} + + +/** + * Extract Host Settable IEs from IE + * + * @ie_data: pointer to buffer containing all IEs + * @size: size of buffer + * + * @returns: length of buffer that only includes host settable IEs + * + * Given a buffer of IEs we move all Host Settable IEs to front of buffer + * by overwriting the IEs that are not Host Settable. + * Buffer length is adjusted accordingly. + */ +static +ssize_t uwb_rc_parse_host_settable_ie(struct uwb_dev *uwb_dev, + void *ie_data, size_t size) +{ + size_t new_len = size; + struct uwb_ie_hdr *ie_hdr; + size_t ie_length; + void *itr = ie_data, *top = itr + size; + + while (itr < top) { + if (uwb_rc_ie_verify(uwb_dev, ie_data, itr, top) != 0) + break; + ie_hdr = itr; + ie_length = sizeof(*ie_hdr) + ie_hdr->length; + if (uwb_rc_ie_is_host_settable(ie_hdr->element_id)) { + itr += ie_length; + } else { + memmove(itr, itr + ie_length, top - (itr + ie_length)); + new_len -= ie_length; + top -= ie_length; + } + } + return new_len; +} + + +/* Cleanup the whole IE management subsystem */ +void uwb_rc_ie_init(struct uwb_rc *uwb_rc) +{ + mutex_init(&uwb_rc->ies_mutex); +} + + +/** + * Set up cache for host settable IEs currently being transmitted + * + * First we just call GET-IE to get the current IEs being transmitted + * (or we workaround and pretend we did) and (because the format is + * the same) reuse that as the IE cache (with the command prefix, as + * explained in 'struct uwb_rc'). + * + * @returns: size of cache created + */ +ssize_t uwb_rc_ie_setup(struct uwb_rc *uwb_rc) +{ + struct device *dev = &uwb_rc->uwb_dev.dev; + ssize_t result; + size_t capacity; + struct uwb_rc_evt_get_ie *ie_info; + + d_fnstart(3, dev, "(%p)\n", uwb_rc); + mutex_lock(&uwb_rc->ies_mutex); + result = uwb_rc_get_ie(uwb_rc, &ie_info); + if (result < 0) + goto error_get_ie; + capacity = result; + d_printf(5, dev, "Got IEs %zu bytes (%zu long at %p)\n", result, + (size_t)le16_to_cpu(ie_info->wIELength), ie_info); + + /* Remove IEs that host should not set. */ + result = uwb_rc_parse_host_settable_ie(&uwb_rc->uwb_dev, + ie_info->IEData, le16_to_cpu(ie_info->wIELength)); + if (result < 0) + goto error_parse; + d_printf(5, dev, "purged non-settable IEs to %zu bytes\n", result); + uwb_rc->ies = (void *) ie_info; + uwb_rc->ies->rccb.bCommandType = UWB_RC_CET_GENERAL; + uwb_rc->ies->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_IE); + uwb_rc->ies_capacity = capacity; + d_printf(5, dev, "IE cache at %p %zu bytes, %zu capacity\n", + ie_info, result, capacity); + result = 0; +error_parse: +error_get_ie: + mutex_unlock(&uwb_rc->ies_mutex); + d_fnend(3, dev, "(%p) = %zu\n", uwb_rc, result); + return result; +} + + +/* Cleanup the whole IE management subsystem */ +void uwb_rc_ie_release(struct uwb_rc *uwb_rc) +{ + kfree(uwb_rc->ies); + uwb_rc->ies = NULL; + uwb_rc->ies_capacity = 0; +} + + +static +int __acc_size(struct uwb_dev *uwb_dev, const struct uwb_ie_hdr *ie_hdr, + size_t offset, void *_ctx) +{ + size_t *acc_size = _ctx; + *acc_size += sizeof(*ie_hdr) + ie_hdr->length; + d_printf(6, &uwb_dev->dev, "new acc size %zu\n", *acc_size); + return 0; +} + + +/** + * Add a new IE to IEs currently being transmitted by device + * + * @ies: the buffer containing the new IE or IEs to be added to + * the device's beacon. The buffer will be verified for + * consistence (meaning the headers should be right) and + * consistent with the buffer size. + * @size: size of @ies (in bytes, total buffer size) + * @returns: 0 if ok, <0 errno code on error + * + * According to WHCI 0.95 [4.13.6] the driver will only receive the RCEB + * after the device sent the first beacon that includes the IEs specified + * in the SET IE command. We thus cannot send this command if the device is + * not beaconing. Instead, a SET IE command will be sent later right after + * we start beaconing. + * + * Setting an IE on the device will overwrite all current IEs in device. So + * we take the current IEs being transmitted by the device, append the + * new one, and call SET IE with all the IEs needed. + * + * The local IE cache will only be updated with the new IE if SET IE + * completed successfully. + */ +int uwb_rc_ie_add(struct uwb_rc *uwb_rc, + const struct uwb_ie_hdr *ies, size_t size) +{ + int result = 0; + struct device *dev = &uwb_rc->uwb_dev.dev; + struct uwb_rc_cmd_set_ie *new_ies; + size_t ies_size, total_size, acc_size = 0; + + if (uwb_rc->ies == NULL) + return -ESHUTDOWN; + uwb_ie_for_each(&uwb_rc->uwb_dev, __acc_size, &acc_size, ies, size); + if (acc_size != size) { + dev_err(dev, "BUG: bad IEs, misconstructed headers " + "[%zu bytes reported vs %zu calculated]\n", + size, acc_size); + WARN_ON(1); + return -EINVAL; + } + mutex_lock(&uwb_rc->ies_mutex); + ies_size = le16_to_cpu(uwb_rc->ies->wIELength); + total_size = sizeof(*uwb_rc->ies) + ies_size; + if (total_size + size > uwb_rc->ies_capacity) { + d_printf(4, dev, "Reallocating IE cache from %p capacity %zu " + "to capacity %zu\n", uwb_rc->ies, uwb_rc->ies_capacity, + total_size + size); + new_ies = kzalloc(total_size + size, GFP_KERNEL); + if (new_ies == NULL) { + dev_err(dev, "No memory for adding new IE\n"); + result = -ENOMEM; + goto error_alloc; + } + memcpy(new_ies, uwb_rc->ies, total_size); + uwb_rc->ies_capacity = total_size + size; + kfree(uwb_rc->ies); + uwb_rc->ies = new_ies; + d_printf(4, dev, "New IE cache at %p capacity %zu\n", + uwb_rc->ies, uwb_rc->ies_capacity); + } + memcpy((void *)uwb_rc->ies + total_size, ies, size); + uwb_rc->ies->wIELength = cpu_to_le16(ies_size + size); + if (uwb_rc->beaconing != -1) { + result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies); + if (result < 0) { + dev_err(dev, "Cannot set new IE on device: %d\n", + result); + uwb_rc->ies->wIELength = cpu_to_le16(ies_size); + } else + result = 0; + } + d_printf(4, dev, "IEs now occupy %hu bytes of %zu capacity at %p\n", + le16_to_cpu(uwb_rc->ies->wIELength), uwb_rc->ies_capacity, + uwb_rc->ies); +error_alloc: + mutex_unlock(&uwb_rc->ies_mutex); + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_ie_add); + + +/* + * Remove an IE from internal cache + * + * We are dealing with our internal IE cache so no need to verify that the + * IEs are valid (it has been done already). + * + * Should be called with ies_mutex held + * + * We do not break out once an IE is found in the cache. It is currently + * possible to have more than one IE with the same ID included in the + * beacon. We don't reallocate, we just mark the size smaller. + */ +static +int uwb_rc_ie_cache_rm(struct uwb_rc *uwb_rc, enum uwb_ie to_remove) +{ + struct uwb_ie_hdr *ie_hdr; + size_t new_len = le16_to_cpu(uwb_rc->ies->wIELength); + void *itr = uwb_rc->ies->IEData; + void *top = itr + new_len; + + while (itr < top) { + ie_hdr = itr; + if (ie_hdr->element_id != to_remove) { + itr += sizeof(*ie_hdr) + ie_hdr->length; + } else { + int ie_length; + ie_length = sizeof(*ie_hdr) + ie_hdr->length; + if (top - itr != ie_length) + memmove(itr, itr + ie_length, top - itr + ie_length); + top -= ie_length; + new_len -= ie_length; + } + } + uwb_rc->ies->wIELength = cpu_to_le16(new_len); + return 0; +} + + +/** + * Remove an IE currently being transmitted by device + * + * @element_id: id of IE to be removed from device's beacon + */ +int uwb_rc_ie_rm(struct uwb_rc *uwb_rc, enum uwb_ie element_id) +{ + struct device *dev = &uwb_rc->uwb_dev.dev; + int result; + + if (uwb_rc->ies == NULL) + return -ESHUTDOWN; + mutex_lock(&uwb_rc->ies_mutex); + result = uwb_rc_ie_cache_rm(uwb_rc, element_id); + if (result < 0) + dev_err(dev, "Cannot remove IE from cache.\n"); + if (uwb_rc->beaconing != -1) { + result = uwb_rc_set_ie(uwb_rc, uwb_rc->ies); + if (result < 0) + dev_err(dev, "Cannot set new IE on device.\n"); + } + mutex_unlock(&uwb_rc->ies_mutex); + return result; +} +EXPORT_SYMBOL_GPL(uwb_rc_ie_rm); + + +/** + * Create and set new Identification IE + * + * Currently only sets the Vendor ID. The Vendor ID is set from the OUI, + * which is obtained from the first three bytes from the MAC address. + */ +int uwb_rc_set_identification_ie(struct uwb_rc *uwb_rc) +{ + struct { + struct uwb_identification_ie id_ie; + struct uwb_dev_info dev_info; + struct uwb_vendor_id vendor_id; + } ie_data; + + ie_data.id_ie.hdr.element_id = UWB_IDENTIFICATION_IE; + ie_data.id_ie.hdr.length = sizeof(struct uwb_dev_info) + + sizeof(struct uwb_vendor_id); + + ie_data.dev_info.type = UWB_DEV_INFO_VENDOR_ID; + ie_data.dev_info.length = sizeof(struct uwb_vendor_id); + + ie_data.vendor_id.data[0] = uwb_rc->uwb_dev.mac_addr.data[0]; + ie_data.vendor_id.data[1] = uwb_rc->uwb_dev.mac_addr.data[1]; + ie_data.vendor_id.data[2] = uwb_rc->uwb_dev.mac_addr.data[2]; + + return uwb_rc_ie_add(uwb_rc, &ie_data.id_ie.hdr, sizeof(ie_data)); +} diff --git a/drivers/uwb/scan.c b/drivers/uwb/scan.c new file mode 100644 index 00000000000..2d270748f32 --- /dev/null +++ b/drivers/uwb/scan.c @@ -0,0 +1,133 @@ +/* + * Ultra Wide Band + * Scanning management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * + * FIXME: docs + * FIXME: there are issues here on how BEACON and SCAN on USB RCI deal + * with each other. Currently seems that START_BEACON while + * SCAN_ONLY will cancel the scan, so we need to update the + * state here. Clarification request sent by email on + * 10/05/2005. + * 10/28/2005 No clear answer heard--maybe we'll hack the API + * so that when we start beaconing, if the HC is + * scanning in a mode not compatible with beaconing + * we just fail. + */ + +#include +#include +#include "uwb-internal.h" + + +/** + * Start/stop scanning in a radio controller + * + * @rc: UWB Radio Controlller + * @channel: Channel to scan; encodings in WUSB1.0[Table 5.12] + * @type: Type of scanning to do. + * @bpst_offset: value at which to start scanning (if type == + * UWB_SCAN_ONLY_STARTTIME) + * @returns: 0 if ok, < 0 errno code on error + * + * We put the command on kmalloc'ed memory as some arches cannot do + * USB from the stack. The reply event is copied from an stage buffer, + * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details. + */ +int uwb_rc_scan(struct uwb_rc *rc, + unsigned channel, enum uwb_scan_type type, + unsigned bpst_offset) +{ + int result; + struct uwb_rc_cmd_scan *cmd; + struct uwb_rc_evt_confirm reply; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + mutex_lock(&rc->uwb_dev.mutex); + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SCAN); + cmd->bChannelNumber = channel; + cmd->bScanState = type; + cmd->wStartTime = cpu_to_le16(bpst_offset); + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_SCAN; + result = uwb_rc_cmd(rc, "SCAN", &cmd->rccb, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, + "SCAN: command execution failed: %s (%d)\n", + uwb_rc_strerror(reply.bResultCode), reply.bResultCode); + result = -EIO; + goto error_cmd; + } + rc->scanning = channel; + rc->scan_type = type; +error_cmd: + mutex_unlock(&rc->uwb_dev.mutex); + kfree(cmd); +error_kzalloc: + return result; +} + +/* + * Print scanning state + */ +static ssize_t uwb_rc_scan_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + ssize_t result; + + mutex_lock(&rc->uwb_dev.mutex); + result = sprintf(buf, "%d %d\n", rc->scanning, rc->scan_type); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +/* + * + */ +static ssize_t uwb_rc_scan_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + struct uwb_rc *rc = uwb_dev->rc; + unsigned channel; + unsigned type; + unsigned bpst_offset = 0; + ssize_t result = -EINVAL; + + result = sscanf(buf, "%u %u %u\n", &channel, &type, &bpst_offset); + if (result >= 2 && type < UWB_SCAN_TOP) + result = uwb_rc_scan(rc, channel, type, bpst_offset); + + return result < 0 ? result : size; +} + +/** Radio Control sysfs interface (declaration) */ +DEVICE_ATTR(scan, S_IRUGO | S_IWUSR, uwb_rc_scan_show, uwb_rc_scan_store); -- cgit v1.2.3 From 8cc13a09474bb30d15dbf449767bb6d0198a8bf8 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:09 +0100 Subject: uwb: add the UWB stack (reservation manager) DRP and reservation management. Signed-off-by: David Vrabel --- drivers/uwb/drp-avail.c | 288 ++++++++++++++++++++ drivers/uwb/drp-ie.c | 232 +++++++++++++++++ drivers/uwb/drp.c | 461 ++++++++++++++++++++++++++++++++ drivers/uwb/rsv.c | 680 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1661 insertions(+) create mode 100644 drivers/uwb/drp-avail.c create mode 100644 drivers/uwb/drp-ie.c create mode 100644 drivers/uwb/drp.c create mode 100644 drivers/uwb/rsv.c diff --git a/drivers/uwb/drp-avail.c b/drivers/uwb/drp-avail.c new file mode 100644 index 00000000000..3febd855280 --- /dev/null +++ b/drivers/uwb/drp-avail.c @@ -0,0 +1,288 @@ +/* + * Ultra Wide Band + * DRP availability management + * + * Copyright (C) 2005-2006 Intel Corporation + * Reinette Chatre + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + * Manage DRP Availability (the MAS available for DRP + * reservations). Thus: + * + * - Handle DRP Availability Change notifications + * + * - Allow the reservation manager to indicate MAS reserved/released + * by local (owned by/targeted at the radio controller) + * reservations. + * + * - Based on the two sources above, generate a DRP Availability IE to + * be included in the beacon. + * + * See also the documentation for struct uwb_drp_avail. + */ + +#include +#include +#include +#include +#include "uwb-internal.h" + +/** + * uwb_drp_avail_init - initialize an RC's MAS availability + * + * All MAS are available initially. The RC will inform use which + * slots are used for the BP (it may change in size). + */ +void uwb_drp_avail_init(struct uwb_rc *rc) +{ + bitmap_fill(rc->drp_avail.global, UWB_NUM_MAS); + bitmap_fill(rc->drp_avail.local, UWB_NUM_MAS); + bitmap_fill(rc->drp_avail.pending, UWB_NUM_MAS); +} + +/* + * Determine MAS available for new local reservations. + * + * avail = global & local & pending + */ +static void uwb_drp_available(struct uwb_rc *rc, struct uwb_mas_bm *avail) +{ + bitmap_and(avail->bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS); + bitmap_and(avail->bm, avail->bm, rc->drp_avail.pending, UWB_NUM_MAS); +} + +/** + * uwb_drp_avail_reserve_pending - reserve MAS for a new reservation + * @rc: the radio controller + * @mas: the MAS to reserve + * + * Returns 0 on success, or -EBUSY if the MAS requested aren't available. + */ +int uwb_drp_avail_reserve_pending(struct uwb_rc *rc, struct uwb_mas_bm *mas) +{ + struct uwb_mas_bm avail; + + uwb_drp_available(rc, &avail); + if (!bitmap_subset(mas->bm, avail.bm, UWB_NUM_MAS)) + return -EBUSY; + + bitmap_andnot(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS); + return 0; +} + +/** + * uwb_drp_avail_reserve - reserve MAS for an established reservation + * @rc: the radio controller + * @mas: the MAS to reserve + */ +void uwb_drp_avail_reserve(struct uwb_rc *rc, struct uwb_mas_bm *mas) +{ + bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS); + bitmap_andnot(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS); + rc->drp_avail.ie_valid = false; +} + +/** + * uwb_drp_avail_release - release MAS from a pending or established reservation + * @rc: the radio controller + * @mas: the MAS to release + */ +void uwb_drp_avail_release(struct uwb_rc *rc, struct uwb_mas_bm *mas) +{ + bitmap_or(rc->drp_avail.local, rc->drp_avail.local, mas->bm, UWB_NUM_MAS); + bitmap_or(rc->drp_avail.pending, rc->drp_avail.pending, mas->bm, UWB_NUM_MAS); + rc->drp_avail.ie_valid = false; +} + +/** + * uwb_drp_avail_ie_update - update the DRP Availability IE + * @rc: the radio controller + * + * avail = global & local + */ +void uwb_drp_avail_ie_update(struct uwb_rc *rc) +{ + struct uwb_mas_bm avail; + + bitmap_and(avail.bm, rc->drp_avail.global, rc->drp_avail.local, UWB_NUM_MAS); + + rc->drp_avail.ie.hdr.element_id = UWB_IE_DRP_AVAILABILITY; + rc->drp_avail.ie.hdr.length = UWB_NUM_MAS / 8; + uwb_mas_bm_copy_le(rc->drp_avail.ie.bmp, &avail); + rc->drp_avail.ie_valid = true; +} + +/** + * Create an unsigned long from a buffer containing a byte stream. + * + * @array: pointer to buffer + * @itr: index of buffer from where we start + * @len: the buffer's remaining size may not be exact multiple of + * sizeof(unsigned long), @len is the length of buffer that needs + * to be converted. This will be sizeof(unsigned long) or smaller + * (BUG if not). If it is smaller then we will pad the remaining + * space of the result with zeroes. + */ +static +unsigned long get_val(u8 *array, size_t itr, size_t len) +{ + unsigned long val = 0; + size_t top = itr + len; + + BUG_ON(len > sizeof(val)); + + while (itr < top) { + val <<= 8; + val |= array[top - 1]; + top--; + } + val <<= 8 * (sizeof(val) - len); /* padding */ + return val; +} + +/** + * Initialize bitmap from data buffer. + * + * The bitmap to be converted could come from a IE, for example a + * DRP Availability IE. + * From ECMA-368 1.0 [16.8.7]: " + * octets: 1 1 N * (0 to 32) + * Element ID Length (=N) DRP Availability Bitmap + * + * The DRP Availability Bitmap field is up to 256 bits long, one + * bit for each MAS in the superframe, where the least-significant + * bit of the field corresponds to the first MAS in the superframe + * and successive bits correspond to successive MASs." + * + * The DRP Availability bitmap is in octets from 0 to 32, so octet + * 32 contains bits for MAS 1-8, etc. If the bitmap is smaller than 32 + * octets, the bits in octets not included at the end of the bitmap are + * treated as zero. In this case (when the bitmap is smaller than 32 + * octets) the MAS represented range from MAS 1 to MAS (size of bitmap) + * with the last octet still containing bits for MAS 1-8, etc. + * + * For example: + * F00F0102 03040506 0708090A 0B0C0D0E 0F010203 + * ^^^^ + * |||| + * |||| + * |||\LSB of byte is MAS 9 + * ||\MSB of byte is MAS 16 + * |\LSB of first byte is MAS 1 + * \ MSB of byte is MAS 8 + * + * An example of this encoding can be found in ECMA-368 Annex-D [Table D.11] + * + * The resulting bitmap will have the following mapping: + * bit position 0 == MAS 1 + * bit position 1 == MAS 2 + * ... + * bit position (UWB_NUM_MAS - 1) == MAS UWB_NUM_MAS + * + * @bmp_itr: pointer to bitmap (can be declared with DECLARE_BITMAP) + * @buffer: pointer to buffer containing bitmap data in big endian + * format (MSB first) + * @buffer_size:number of bytes with which bitmap should be initialized + */ +static +void buffer_to_bmp(unsigned long *bmp_itr, void *_buffer, + size_t buffer_size) +{ + u8 *buffer = _buffer; + size_t itr, len; + unsigned long val; + + itr = 0; + while (itr < buffer_size) { + len = buffer_size - itr >= sizeof(val) ? + sizeof(val) : buffer_size - itr; + val = get_val(buffer, itr, len); + bmp_itr[itr / sizeof(val)] = val; + itr += sizeof(val); + } +} + + +/** + * Extract DRP Availability bitmap from the notification. + * + * The notification that comes in contains a bitmap of (UWB_NUM_MAS / 8) bytes + * We convert that to our internal representation. + */ +static +int uwbd_evt_get_drp_avail(struct uwb_event *evt, unsigned long *bmp) +{ + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc_evt_drp_avail *drp_evt; + int result = -EINVAL; + + /* Is there enough data to decode the event? */ + if (evt->notif.size < sizeof(*drp_evt)) { + dev_err(dev, "DRP Availability Change: Not enough " + "data to decode event [%zu bytes, %zu " + "needed]\n", evt->notif.size, sizeof(*drp_evt)); + goto error; + } + drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp_avail, rceb); + buffer_to_bmp(bmp, drp_evt->bmp, UWB_NUM_MAS/8); + result = 0; +error: + return result; +} + + +/** + * Process an incoming DRP Availability notification. + * + * @evt: Event information (packs the actual event data, which + * radio controller it came to, etc). + * + * @returns: 0 on success (so uwbd() frees the event buffer), < 0 + * on error. + * + * According to ECMA-368 1.0 [16.8.7], bits set to ONE indicate that + * the MAS slot is available, bits set to ZERO indicate that the slot + * is busy. + * + * So we clear available slots, we set used slots :) + * + * The notification only marks non-availability based on the BP and + * received DRP IEs that are not for this radio controller. A copy of + * this bitmap is needed to generate the real availability (which + * includes local and pending reservations). + * + * The DRP Availability IE that this radio controller emits will need + * to be updated. + */ +int uwbd_evt_handle_rc_drp_avail(struct uwb_event *evt) +{ + int result; + struct uwb_rc *rc = evt->rc; + DECLARE_BITMAP(bmp, UWB_NUM_MAS); + + result = uwbd_evt_get_drp_avail(evt, bmp); + if (result < 0) + return result; + + mutex_lock(&rc->rsvs_mutex); + bitmap_copy(rc->drp_avail.global, bmp, UWB_NUM_MAS); + rc->drp_avail.ie_valid = false; + mutex_unlock(&rc->rsvs_mutex); + + uwb_rsv_sched_update(rc); + + return 0; +} diff --git a/drivers/uwb/drp-ie.c b/drivers/uwb/drp-ie.c new file mode 100644 index 00000000000..882724c5f12 --- /dev/null +++ b/drivers/uwb/drp-ie.c @@ -0,0 +1,232 @@ +/* + * UWB DRP IE management. + * + * Copyright (C) 2005-2006 Intel Corporation + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "uwb-internal.h" + +/* + * Allocate a DRP IE. + * + * To save having to free/allocate a DRP IE when its MAS changes, + * enough memory is allocated for the maxiumum number of DRP + * allocation fields. This gives an overhead per reservation of up to + * (UWB_NUM_ZONES - 1) * 4 = 60 octets. + */ +static struct uwb_ie_drp *uwb_drp_ie_alloc(void) +{ + struct uwb_ie_drp *drp_ie; + unsigned tiebreaker; + + drp_ie = kzalloc(sizeof(struct uwb_ie_drp) + + UWB_NUM_ZONES * sizeof(struct uwb_drp_alloc), + GFP_KERNEL); + if (drp_ie) { + drp_ie->hdr.element_id = UWB_IE_DRP; + + get_random_bytes(&tiebreaker, sizeof(unsigned)); + uwb_ie_drp_set_tiebreaker(drp_ie, tiebreaker & 1); + } + return drp_ie; +} + + +/* + * Fill a DRP IE's allocation fields from a MAS bitmap. + */ +static void uwb_drp_ie_from_bm(struct uwb_ie_drp *drp_ie, + struct uwb_mas_bm *mas) +{ + int z, i, num_fields = 0, next = 0; + struct uwb_drp_alloc *zones; + __le16 current_bmp; + DECLARE_BITMAP(tmp_bmp, UWB_NUM_MAS); + DECLARE_BITMAP(tmp_mas_bm, UWB_MAS_PER_ZONE); + + zones = drp_ie->allocs; + + bitmap_copy(tmp_bmp, mas->bm, UWB_NUM_MAS); + + /* Determine unique MAS bitmaps in zones from bitmap. */ + for (z = 0; z < UWB_NUM_ZONES; z++) { + bitmap_copy(tmp_mas_bm, tmp_bmp, UWB_MAS_PER_ZONE); + if (bitmap_weight(tmp_mas_bm, UWB_MAS_PER_ZONE) > 0) { + bool found = false; + current_bmp = (__le16) *tmp_mas_bm; + for (i = 0; i < next; i++) { + if (current_bmp == zones[i].mas_bm) { + zones[i].zone_bm |= 1 << z; + found = true; + break; + } + } + if (!found) { + num_fields++; + zones[next].zone_bm = 1 << z; + zones[next].mas_bm = current_bmp; + next++; + } + } + bitmap_shift_right(tmp_bmp, tmp_bmp, UWB_MAS_PER_ZONE, UWB_NUM_MAS); + } + + /* Store in format ready for transmission (le16). */ + for (i = 0; i < num_fields; i++) { + drp_ie->allocs[i].zone_bm = cpu_to_le16(zones[i].zone_bm); + drp_ie->allocs[i].mas_bm = cpu_to_le16(zones[i].mas_bm); + } + + drp_ie->hdr.length = sizeof(struct uwb_ie_drp) - sizeof(struct uwb_ie_hdr) + + num_fields * sizeof(struct uwb_drp_alloc); +} + +/** + * uwb_drp_ie_update - update a reservation's DRP IE + * @rsv: the reservation + */ +int uwb_drp_ie_update(struct uwb_rsv *rsv) +{ + struct device *dev = &rsv->rc->uwb_dev.dev; + struct uwb_ie_drp *drp_ie; + int reason_code, status; + + switch (rsv->state) { + case UWB_RSV_STATE_NONE: + kfree(rsv->drp_ie); + rsv->drp_ie = NULL; + return 0; + case UWB_RSV_STATE_O_INITIATED: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 0; + break; + case UWB_RSV_STATE_O_PENDING: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 0; + break; + case UWB_RSV_STATE_O_MODIFIED: + reason_code = UWB_DRP_REASON_MODIFIED; + status = 1; + break; + case UWB_RSV_STATE_O_ESTABLISHED: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 1; + break; + case UWB_RSV_STATE_T_ACCEPTED: + reason_code = UWB_DRP_REASON_ACCEPTED; + status = 1; + break; + case UWB_RSV_STATE_T_DENIED: + reason_code = UWB_DRP_REASON_DENIED; + status = 0; + break; + default: + dev_dbg(dev, "rsv with unhandled state (%d)\n", rsv->state); + return -EINVAL; + } + + if (rsv->drp_ie == NULL) { + rsv->drp_ie = uwb_drp_ie_alloc(); + if (rsv->drp_ie == NULL) + return -ENOMEM; + } + drp_ie = rsv->drp_ie; + + uwb_ie_drp_set_owner(drp_ie, uwb_rsv_is_owner(rsv)); + uwb_ie_drp_set_status(drp_ie, status); + uwb_ie_drp_set_reason_code(drp_ie, reason_code); + uwb_ie_drp_set_stream_index(drp_ie, rsv->stream); + uwb_ie_drp_set_type(drp_ie, rsv->type); + + if (uwb_rsv_is_owner(rsv)) { + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEV: + drp_ie->dev_addr = rsv->target.dev->dev_addr; + break; + case UWB_RSV_TARGET_DEVADDR: + drp_ie->dev_addr = rsv->target.devaddr; + break; + } + } else + drp_ie->dev_addr = rsv->owner->dev_addr; + + uwb_drp_ie_from_bm(drp_ie, &rsv->mas); + + rsv->ie_valid = true; + return 0; +} + +/* + * Set MAS bits from given MAS bitmap in a single zone of large bitmap. + * + * We are given a zone id and the MAS bitmap of bits that need to be set in + * this zone. Note that this zone may already have bits set and this only + * adds settings - we cannot simply assign the MAS bitmap contents to the + * zone contents. We iterate over the the bits (MAS) in the zone and set the + * bits that are set in the given MAS bitmap. + */ +static +void uwb_drp_ie_single_zone_to_bm(struct uwb_mas_bm *bm, u8 zone, u16 mas_bm) +{ + int mas; + u16 mas_mask; + + for (mas = 0; mas < UWB_MAS_PER_ZONE; mas++) { + mas_mask = 1 << mas; + if (mas_bm & mas_mask) + set_bit(zone * UWB_NUM_ZONES + mas, bm->bm); + } +} + +/** + * uwb_drp_ie_zones_to_bm - convert DRP allocation fields to a bitmap + * @mas: MAS bitmap that will be populated to correspond to the + * allocation fields in the DRP IE + * @drp_ie: the DRP IE that contains the allocation fields. + * + * The input format is an array of MAS allocation fields (16 bit Zone + * bitmap, 16 bit MAS bitmap) as described in [ECMA-368] section + * 16.8.6. The output is a full 256 bit MAS bitmap. + * + * We go over all the allocation fields, for each allocation field we + * know which zones are impacted. We iterate over all the zones + * impacted and call a function that will set the correct MAS bits in + * each zone. + */ +void uwb_drp_ie_to_bm(struct uwb_mas_bm *bm, const struct uwb_ie_drp *drp_ie) +{ + int numallocs = (drp_ie->hdr.length - 4) / 4; + const struct uwb_drp_alloc *alloc; + int cnt; + u16 zone_bm, mas_bm; + u8 zone; + u16 zone_mask; + + for (cnt = 0; cnt < numallocs; cnt++) { + alloc = &drp_ie->allocs[cnt]; + zone_bm = le16_to_cpu(alloc->zone_bm); + mas_bm = le16_to_cpu(alloc->mas_bm); + for (zone = 0; zone < UWB_NUM_ZONES; zone++) { + zone_mask = 1 << zone; + if (zone_bm & zone_mask) + uwb_drp_ie_single_zone_to_bm(bm, zone, mas_bm); + } + } +} diff --git a/drivers/uwb/drp.c b/drivers/uwb/drp.c new file mode 100644 index 00000000000..c0b1e5e2bd0 --- /dev/null +++ b/drivers/uwb/drp.c @@ -0,0 +1,461 @@ +/* + * Ultra Wide Band + * Dynamic Reservation Protocol handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include "uwb-internal.h" + +/** + * Construct and send the SET DRP IE + * + * @rc: UWB Host controller + * @returns: >= 0 number of bytes still available in the beacon + * < 0 errno code on error. + * + * See WUSB[8.6.2.7]: The host must set all the DRP IEs that it wants the + * device to include in its beacon at the same time. We thus have to + * traverse all reservations and include the DRP IEs of all PENDING + * and NEGOTIATED reservations in a SET DRP command for transmission. + * + * A DRP Availability IE is appended. + * + * rc->uwb_dev.mutex is held + * + * FIXME We currently ignore the returned value indicating the remaining space + * in beacon. This could be used to deny reservation requests earlier if + * determined that they would cause the beacon space to be exceeded. + */ +static +int uwb_rc_gen_send_drp_ie(struct uwb_rc *rc) +{ + int result; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_rc_cmd_set_drp_ie *cmd; + struct uwb_rc_evt_set_drp_ie reply; + struct uwb_rsv *rsv; + int num_bytes = 0; + u8 *IEDataptr; + + result = -ENOMEM; + /* First traverse all reservations to determine memory needed. */ + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (rsv->drp_ie != NULL) + num_bytes += rsv->drp_ie->hdr.length + 2; + } + num_bytes += sizeof(rc->drp_avail.ie); + cmd = kzalloc(sizeof(*cmd) + num_bytes, GFP_KERNEL); + if (cmd == NULL) + goto error; + cmd->rccb.bCommandType = UWB_RC_CET_GENERAL; + cmd->rccb.wCommand = cpu_to_le16(UWB_RC_CMD_SET_DRP_IE); + cmd->wIELength = num_bytes; + IEDataptr = (u8 *)&cmd->IEData[0]; + + /* Next traverse all reservations to place IEs in allocated memory. */ + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (rsv->drp_ie != NULL) { + memcpy(IEDataptr, rsv->drp_ie, + rsv->drp_ie->hdr.length + 2); + IEDataptr += rsv->drp_ie->hdr.length + 2; + } + } + memcpy(IEDataptr, &rc->drp_avail.ie, sizeof(rc->drp_avail.ie)); + + reply.rceb.bEventType = UWB_RC_CET_GENERAL; + reply.rceb.wEvent = UWB_RC_CMD_SET_DRP_IE; + result = uwb_rc_cmd(rc, "SET-DRP-IE", &cmd->rccb, + sizeof(*cmd) + num_bytes, &reply.rceb, + sizeof(reply)); + if (result < 0) + goto error_cmd; + result = le16_to_cpu(reply.wRemainingSpace); + if (reply.bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(&rc->uwb_dev.dev, "SET-DRP-IE: command execution " + "failed: %s (%d). RemainingSpace in beacon " + "= %d\n", uwb_rc_strerror(reply.bResultCode), + reply.bResultCode, result); + result = -EIO; + } else { + dev_dbg(dev, "SET-DRP-IE sent. RemainingSpace in beacon " + "= %d.\n", result); + result = 0; + } +error_cmd: + kfree(cmd); +error: + return result; + +} +/** + * Send all DRP IEs associated with this host + * + * @returns: >= 0 number of bytes still available in the beacon + * < 0 errno code on error. + * + * As per the protocol we obtain the host controller device lock to access + * bandwidth structures. + */ +int uwb_rc_send_all_drp_ie(struct uwb_rc *rc) +{ + int result; + + mutex_lock(&rc->uwb_dev.mutex); + result = uwb_rc_gen_send_drp_ie(rc); + mutex_unlock(&rc->uwb_dev.mutex); + return result; +} + +void uwb_drp_handle_timeout(struct uwb_rsv *rsv) +{ + struct device *dev = &rsv->rc->uwb_dev.dev; + + dev_dbg(dev, "reservation timeout in state %s (%d)\n", + uwb_rsv_state_str(rsv->state), rsv->state); + + switch (rsv->state) { + case UWB_RSV_STATE_O_INITIATED: + if (rsv->is_multicast) { + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED); + return; + } + break; + case UWB_RSV_STATE_O_ESTABLISHED: + if (rsv->is_multicast) + return; + break; + default: + break; + } + uwb_rsv_remove(rsv); +} + +/* + * Based on the DRP IE, transition a target reservation to a new + * state. + */ +static void uwb_drp_process_target(struct uwb_rc *rc, struct uwb_rsv *rsv, + struct uwb_ie_drp *drp_ie) +{ + struct device *dev = &rc->uwb_dev.dev; + int status; + enum uwb_drp_reason reason_code; + + status = uwb_ie_drp_status(drp_ie); + reason_code = uwb_ie_drp_reason_code(drp_ie); + + if (status) { + switch (reason_code) { + case UWB_DRP_REASON_ACCEPTED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_T_ACCEPTED); + break; + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } else { + switch (reason_code) { + case UWB_DRP_REASON_ACCEPTED: + /* New reservations are handled in uwb_rsv_find(). */ + break; + case UWB_DRP_REASON_DENIED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + break; + case UWB_DRP_REASON_CONFLICT: + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } +} + +/* + * Based on the DRP IE, transition an owner reservation to a new + * state. + */ +static void uwb_drp_process_owner(struct uwb_rc *rc, struct uwb_rsv *rsv, + struct uwb_ie_drp *drp_ie) +{ + struct device *dev = &rc->uwb_dev.dev; + int status; + enum uwb_drp_reason reason_code; + + status = uwb_ie_drp_status(drp_ie); + reason_code = uwb_ie_drp_reason_code(drp_ie); + + if (status) { + switch (reason_code) { + case UWB_DRP_REASON_ACCEPTED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_ESTABLISHED); + break; + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } else { + switch (reason_code) { + case UWB_DRP_REASON_PENDING: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_PENDING); + break; + case UWB_DRP_REASON_DENIED: + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + break; + case UWB_DRP_REASON_CONFLICT: + case UWB_DRP_REASON_MODIFIED: + dev_err(dev, "FIXME: unhandled reason code (%d/%d)\n", + reason_code, status); + break; + default: + dev_warn(dev, "ignoring invalid DRP IE state (%d/%d)\n", + reason_code, status); + } + } +} + +/* + * Process a received DRP IE, it's either for a reservation owned by + * the RC or targeted at it (or it's for a WUSB cluster reservation). + */ +static void uwb_drp_process(struct uwb_rc *rc, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_rsv *rsv; + + rsv = uwb_rsv_find(rc, src, drp_ie); + if (!rsv) { + /* + * No reservation? It's either for a recently + * terminated reservation; or the DRP IE couldn't be + * processed (e.g., an invalid IE or out of memory). + */ + return; + } + + /* + * Do nothing with DRP IEs for reservations that have been + * terminated. + */ + if (rsv->state == UWB_RSV_STATE_NONE) { + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + return; + } + + if (uwb_ie_drp_owner(drp_ie)) + uwb_drp_process_target(rc, rsv, drp_ie); + else + uwb_drp_process_owner(rc, rsv, drp_ie); +} + + +/* + * Process all the DRP IEs (both DRP IEs and the DRP Availability IE) + * from a device. + */ +static +void uwb_drp_process_all(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt, + size_t ielen, struct uwb_dev *src_dev) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_ie_hdr *ie_hdr; + void *ptr; + + ptr = drp_evt->ie_data; + for (;;) { + ie_hdr = uwb_ie_next(&ptr, &ielen); + if (!ie_hdr) + break; + + switch (ie_hdr->element_id) { + case UWB_IE_DRP_AVAILABILITY: + /* FIXME: does something need to be done with this? */ + break; + case UWB_IE_DRP: + uwb_drp_process(rc, src_dev, (struct uwb_ie_drp *)ie_hdr); + break; + default: + dev_warn(dev, "unexpected IE in DRP notification\n"); + break; + } + } + + if (ielen > 0) + dev_warn(dev, "%d octets remaining in DRP notification\n", + (int)ielen); +} + + +/* + * Go through all the DRP IEs and find the ones that conflict with our + * reservations. + * + * FIXME: must resolve the conflict according the the rules in + * [ECMA-368]. + */ +static +void uwb_drp_process_conflict_all(struct uwb_rc *rc, struct uwb_rc_evt_drp *drp_evt, + size_t ielen, struct uwb_dev *src_dev) +{ + struct device *dev = &rc->uwb_dev.dev; + struct uwb_ie_hdr *ie_hdr; + struct uwb_ie_drp *drp_ie; + void *ptr; + + ptr = drp_evt->ie_data; + for (;;) { + ie_hdr = uwb_ie_next(&ptr, &ielen); + if (!ie_hdr) + break; + + drp_ie = container_of(ie_hdr, struct uwb_ie_drp, hdr); + + /* FIXME: check if this DRP IE conflicts. */ + } + + if (ielen > 0) + dev_warn(dev, "%d octets remaining in DRP notification\n", + (int)ielen); +} + + +/* + * Terminate all reservations owned by, or targeted at, 'uwb_dev'. + */ +static void uwb_drp_terminate_all(struct uwb_rc *rc, struct uwb_dev *uwb_dev) +{ + struct uwb_rsv *rsv; + + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (rsv->owner == uwb_dev + || (rsv->target.type == UWB_RSV_TARGET_DEV && rsv->target.dev == uwb_dev)) + uwb_rsv_remove(rsv); + } +} + + +/** + * uwbd_evt_handle_rc_drp - handle a DRP_IE event + * @evt: the DRP_IE event from the radio controller + * + * This processes DRP notifications from the radio controller, either + * initiating a new reservation or transitioning an existing + * reservation into a different state. + * + * DRP notifications can occur for three different reasons: + * + * - UWB_DRP_NOTIF_DRP_IE_RECVD: one or more DRP IEs with the RC as + * the target or source have been recieved. + * + * These DRP IEs could be new or for an existing reservation. + * + * If the DRP IE for an existing reservation ceases to be to + * recieved for at least mMaxLostBeacons, the reservation should be + * considered to be terminated. Note that the TERMINATE reason (see + * below) may not always be signalled (e.g., the remote device has + * two or more reservations established with the RC). + * + * - UWB_DRP_NOTIF_CONFLICT: DRP IEs from any device in the beacon + * group conflict with the RC's reservations. + * + * - UWB_DRP_NOTIF_TERMINATE: DRP IEs are no longer being received + * from a device (i.e., it's terminated all reservations). + * + * Only the software state of the reservations is changed; the setting + * of the radio controller's DRP IEs is done after all the events in + * an event buffer are processed. This saves waiting multiple times + * for the SET_DRP_IE command to complete. + */ +int uwbd_evt_handle_rc_drp(struct uwb_event *evt) +{ + struct device *dev = &evt->rc->uwb_dev.dev; + struct uwb_rc *rc = evt->rc; + struct uwb_rc_evt_drp *drp_evt; + size_t ielength, bytes_left; + struct uwb_dev_addr src_addr; + struct uwb_dev *src_dev; + int reason; + + /* Is there enough data to decode the event (and any IEs in + its payload)? */ + if (evt->notif.size < sizeof(*drp_evt)) { + dev_err(dev, "DRP event: Not enough data to decode event " + "[%zu bytes left, %zu needed]\n", + evt->notif.size, sizeof(*drp_evt)); + return 0; + } + bytes_left = evt->notif.size - sizeof(*drp_evt); + drp_evt = container_of(evt->notif.rceb, struct uwb_rc_evt_drp, rceb); + ielength = le16_to_cpu(drp_evt->ie_length); + if (bytes_left != ielength) { + dev_err(dev, "DRP event: Not enough data in payload [%zu" + "bytes left, %zu declared in the event]\n", + bytes_left, ielength); + return 0; + } + + memcpy(src_addr.data, &drp_evt->src_addr, sizeof(src_addr)); + src_dev = uwb_dev_get_by_devaddr(rc, &src_addr); + if (!src_dev) { + /* + * A DRP notification from an unrecognized device. + * + * This is probably from a WUSB device that doesn't + * have an EUI-48 and therefore doesn't show up in the + * UWB device database. It's safe to simply ignore + * these. + */ + return 0; + } + + mutex_lock(&rc->rsvs_mutex); + + reason = uwb_rc_evt_drp_reason(drp_evt); + + switch (reason) { + case UWB_DRP_NOTIF_DRP_IE_RCVD: + uwb_drp_process_all(rc, drp_evt, ielength, src_dev); + break; + case UWB_DRP_NOTIF_CONFLICT: + uwb_drp_process_conflict_all(rc, drp_evt, ielength, src_dev); + break; + case UWB_DRP_NOTIF_TERMINATE: + uwb_drp_terminate_all(rc, src_dev); + break; + default: + dev_warn(dev, "ignored DRP event with reason code: %d\n", reason); + break; + } + + mutex_unlock(&rc->rsvs_mutex); + + uwb_dev_put(src_dev); + return 0; +} diff --git a/drivers/uwb/rsv.c b/drivers/uwb/rsv.c new file mode 100644 index 00000000000..bae16204576 --- /dev/null +++ b/drivers/uwb/rsv.c @@ -0,0 +1,680 @@ +/* + * UWB reservation management. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "uwb-internal.h" + +static void uwb_rsv_timer(unsigned long arg); + +static const char *rsv_states[] = { + [UWB_RSV_STATE_NONE] = "none", + [UWB_RSV_STATE_O_INITIATED] = "initiated", + [UWB_RSV_STATE_O_PENDING] = "pending", + [UWB_RSV_STATE_O_MODIFIED] = "modified", + [UWB_RSV_STATE_O_ESTABLISHED] = "established", + [UWB_RSV_STATE_T_ACCEPTED] = "accepted", + [UWB_RSV_STATE_T_DENIED] = "denied", + [UWB_RSV_STATE_T_PENDING] = "pending", +}; + +static const char *rsv_types[] = { + [UWB_DRP_TYPE_ALIEN_BP] = "alien-bp", + [UWB_DRP_TYPE_HARD] = "hard", + [UWB_DRP_TYPE_SOFT] = "soft", + [UWB_DRP_TYPE_PRIVATE] = "private", + [UWB_DRP_TYPE_PCA] = "pca", +}; + +/** + * uwb_rsv_state_str - return a string for a reservation state + * @state: the reservation state. + */ +const char *uwb_rsv_state_str(enum uwb_rsv_state state) +{ + if (state < UWB_RSV_STATE_NONE || state >= UWB_RSV_STATE_LAST) + return "unknown"; + return rsv_states[state]; +} +EXPORT_SYMBOL_GPL(uwb_rsv_state_str); + +/** + * uwb_rsv_type_str - return a string for a reservation type + * @type: the reservation type + */ +const char *uwb_rsv_type_str(enum uwb_drp_type type) +{ + if (type < UWB_DRP_TYPE_ALIEN_BP || type > UWB_DRP_TYPE_PCA) + return "invalid"; + return rsv_types[type]; +} +EXPORT_SYMBOL_GPL(uwb_rsv_type_str); + +static void uwb_rsv_dump(struct uwb_rsv *rsv) +{ + struct device *dev = &rsv->rc->uwb_dev.dev; + struct uwb_dev_addr devaddr; + char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE]; + + uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr); + if (rsv->target.type == UWB_RSV_TARGET_DEV) + devaddr = rsv->target.dev->dev_addr; + else + devaddr = rsv->target.devaddr; + uwb_dev_addr_print(target, sizeof(target), &devaddr); + + dev_dbg(dev, "rsv %s -> %s: %s\n", owner, target, uwb_rsv_state_str(rsv->state)); +} + +/* + * Get a free stream index for a reservation. + * + * If the target is a DevAddr (e.g., a WUSB cluster reservation) then + * the stream is allocated from a pool of per-RC stream indexes, + * otherwise a unique stream index for the target is selected. + */ +static int uwb_rsv_get_stream(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + unsigned long *streams_bm; + int stream; + + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEV: + streams_bm = rsv->target.dev->streams; + break; + case UWB_RSV_TARGET_DEVADDR: + streams_bm = rc->uwb_dev.streams; + break; + default: + return -EINVAL; + } + + stream = find_first_zero_bit(streams_bm, UWB_NUM_STREAMS); + if (stream >= UWB_NUM_STREAMS) + return -EBUSY; + + rsv->stream = stream; + set_bit(stream, streams_bm); + + return 0; +} + +static void uwb_rsv_put_stream(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + unsigned long *streams_bm; + + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEV: + streams_bm = rsv->target.dev->streams; + break; + case UWB_RSV_TARGET_DEVADDR: + streams_bm = rc->uwb_dev.streams; + break; + default: + return; + } + + clear_bit(rsv->stream, streams_bm); +} + +/* + * Generate a MAS allocation with a single row component. + */ +static void uwb_rsv_gen_alloc_row(struct uwb_mas_bm *mas, + int first_mas, int mas_per_zone, + int zs, int ze) +{ + struct uwb_mas_bm col; + int z; + + bitmap_zero(mas->bm, UWB_NUM_MAS); + bitmap_zero(col.bm, UWB_NUM_MAS); + bitmap_fill(col.bm, mas_per_zone); + bitmap_shift_left(col.bm, col.bm, first_mas + zs * UWB_MAS_PER_ZONE, UWB_NUM_MAS); + + for (z = zs; z <= ze; z++) { + bitmap_or(mas->bm, mas->bm, col.bm, UWB_NUM_MAS); + bitmap_shift_left(col.bm, col.bm, UWB_MAS_PER_ZONE, UWB_NUM_MAS); + } +} + +/* + * Allocate some MAS for this reservation based on current local + * availability, the reservation parameters (max_mas, min_mas, + * sparsity), and the WiMedia rules for MAS allocations. + * + * Returns -EBUSY is insufficient free MAS are available. + * + * FIXME: to simplify this, only safe reservations with a single row + * component in zones 1 to 15 are tried (zone 0 is skipped to avoid + * problems with the MAS reserved for the BP). + * + * [ECMA-368] section B.2. + */ +static int uwb_rsv_alloc_mas(struct uwb_rsv *rsv) +{ + static const int safe_mas_in_row[UWB_NUM_ZONES] = { + 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 2, 1, + }; + int n, r; + struct uwb_mas_bm mas; + bool found = false; + + /* + * Search all valid safe allocations until either: too few MAS + * are available; or the smallest allocation with sufficient + * MAS is found. + * + * The top of the zones are preferred, so space for larger + * allocations is available in the bottom of the zone (e.g., a + * 15 MAS allocation should start in row 14 leaving space for + * a 120 MAS allocation at row 0). + */ + for (n = safe_mas_in_row[0]; n >= 1; n--) { + int num_mas; + + num_mas = n * (UWB_NUM_ZONES - 1); + if (num_mas < rsv->min_mas) + break; + if (found && num_mas < rsv->max_mas) + break; + + for (r = UWB_MAS_PER_ZONE-1; r >= 0; r--) { + if (safe_mas_in_row[r] < n) + continue; + uwb_rsv_gen_alloc_row(&mas, r, n, 1, UWB_NUM_ZONES); + if (uwb_drp_avail_reserve_pending(rsv->rc, &mas) == 0) { + found = true; + break; + } + } + } + + if (!found) + return -EBUSY; + + bitmap_copy(rsv->mas.bm, mas.bm, UWB_NUM_MAS); + return 0; +} + +static void uwb_rsv_stroke_timer(struct uwb_rsv *rsv) +{ + int sframes = UWB_MAX_LOST_BEACONS; + + /* + * Multicast reservations can become established within 1 + * super frame and should not be terminated if no response is + * received. + */ + if (rsv->is_multicast) { + if (rsv->state == UWB_RSV_STATE_O_INITIATED) + sframes = 1; + if (rsv->state == UWB_RSV_STATE_O_ESTABLISHED) + sframes = 0; + } + + rsv->expired = false; + if (sframes > 0) { + /* + * Add an additional 2 superframes to account for the + * time to send the SET DRP IE command. + */ + unsigned timeout_us = (sframes + 2) * UWB_SUPERFRAME_LENGTH_US; + mod_timer(&rsv->timer, jiffies + usecs_to_jiffies(timeout_us)); + } else + del_timer(&rsv->timer); +} + +/* + * Update a reservations state, and schedule an update of the + * transmitted DRP IEs. + */ +static void uwb_rsv_state_update(struct uwb_rsv *rsv, + enum uwb_rsv_state new_state) +{ + rsv->state = new_state; + rsv->ie_valid = false; + + uwb_rsv_dump(rsv); + + uwb_rsv_stroke_timer(rsv); + uwb_rsv_sched_update(rsv->rc); +} + +static void uwb_rsv_callback(struct uwb_rsv *rsv) +{ + if (rsv->callback) + rsv->callback(rsv); +} + +void uwb_rsv_set_state(struct uwb_rsv *rsv, enum uwb_rsv_state new_state) +{ + if (rsv->state == new_state) { + switch (rsv->state) { + case UWB_RSV_STATE_O_ESTABLISHED: + case UWB_RSV_STATE_T_ACCEPTED: + case UWB_RSV_STATE_NONE: + uwb_rsv_stroke_timer(rsv); + break; + default: + /* Expecting a state transition so leave timer + as-is. */ + break; + } + return; + } + + switch (new_state) { + case UWB_RSV_STATE_NONE: + uwb_drp_avail_release(rsv->rc, &rsv->mas); + uwb_rsv_put_stream(rsv); + uwb_rsv_state_update(rsv, UWB_RSV_STATE_NONE); + uwb_rsv_callback(rsv); + break; + case UWB_RSV_STATE_O_INITIATED: + uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_INITIATED); + break; + case UWB_RSV_STATE_O_PENDING: + uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_PENDING); + break; + case UWB_RSV_STATE_O_ESTABLISHED: + uwb_drp_avail_reserve(rsv->rc, &rsv->mas); + uwb_rsv_state_update(rsv, UWB_RSV_STATE_O_ESTABLISHED); + uwb_rsv_callback(rsv); + break; + case UWB_RSV_STATE_T_ACCEPTED: + uwb_drp_avail_reserve(rsv->rc, &rsv->mas); + uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_ACCEPTED); + uwb_rsv_callback(rsv); + break; + case UWB_RSV_STATE_T_DENIED: + uwb_rsv_state_update(rsv, UWB_RSV_STATE_T_DENIED); + break; + default: + dev_err(&rsv->rc->uwb_dev.dev, "unhandled state: %s (%d)\n", + uwb_rsv_state_str(new_state), new_state); + } +} + +static struct uwb_rsv *uwb_rsv_alloc(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv; + + rsv = kzalloc(sizeof(struct uwb_rsv), GFP_KERNEL); + if (!rsv) + return NULL; + + INIT_LIST_HEAD(&rsv->rc_node); + INIT_LIST_HEAD(&rsv->pal_node); + init_timer(&rsv->timer); + rsv->timer.function = uwb_rsv_timer; + rsv->timer.data = (unsigned long)rsv; + + rsv->rc = rc; + + return rsv; +} + +static void uwb_rsv_free(struct uwb_rsv *rsv) +{ + uwb_dev_put(rsv->owner); + if (rsv->target.type == UWB_RSV_TARGET_DEV) + uwb_dev_put(rsv->target.dev); + kfree(rsv); +} + +/** + * uwb_rsv_create - allocate and initialize a UWB reservation structure + * @rc: the radio controller + * @cb: callback to use when the reservation completes or terminates + * @pal_priv: data private to the PAL to be passed in the callback + * + * The callback is called when the state of the reservation changes from: + * + * - pending to accepted + * - pending to denined + * - accepted to terminated + * - pending to terminated + */ +struct uwb_rsv *uwb_rsv_create(struct uwb_rc *rc, uwb_rsv_cb_f cb, void *pal_priv) +{ + struct uwb_rsv *rsv; + + rsv = uwb_rsv_alloc(rc); + if (!rsv) + return NULL; + + rsv->callback = cb; + rsv->pal_priv = pal_priv; + + return rsv; +} +EXPORT_SYMBOL_GPL(uwb_rsv_create); + +void uwb_rsv_remove(struct uwb_rsv *rsv) +{ + if (rsv->state != UWB_RSV_STATE_NONE) + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + del_timer_sync(&rsv->timer); + list_del(&rsv->rc_node); + uwb_rsv_free(rsv); +} + +/** + * uwb_rsv_destroy - free a UWB reservation structure + * @rsv: the reservation to free + * + * The reservation will be terminated if it is pending or established. + */ +void uwb_rsv_destroy(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + + mutex_lock(&rc->rsvs_mutex); + uwb_rsv_remove(rsv); + mutex_unlock(&rc->rsvs_mutex); +} +EXPORT_SYMBOL_GPL(uwb_rsv_destroy); + +/** + * usb_rsv_establish - start a reservation establishment + * @rsv: the reservation + * + * The PAL should fill in @rsv's owner, target, type, max_mas, + * min_mas, sparsity and is_multicast fields. If the target is a + * uwb_dev it must be referenced. + * + * The reservation's callback will be called when the reservation is + * accepted, denied or times out. + */ +int uwb_rsv_establish(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + int ret; + + mutex_lock(&rc->rsvs_mutex); + + ret = uwb_rsv_get_stream(rsv); + if (ret) + goto out; + + ret = uwb_rsv_alloc_mas(rsv); + if (ret) { + uwb_rsv_put_stream(rsv); + goto out; + } + + list_add_tail(&rsv->rc_node, &rc->reservations); + rsv->owner = &rc->uwb_dev; + uwb_dev_get(rsv->owner); + uwb_rsv_set_state(rsv, UWB_RSV_STATE_O_INITIATED); +out: + mutex_unlock(&rc->rsvs_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(uwb_rsv_establish); + +/** + * uwb_rsv_modify - modify an already established reservation + * @rsv: the reservation to modify + * @max_mas: new maximum MAS to reserve + * @min_mas: new minimum MAS to reserve + * @sparsity: new sparsity to use + * + * FIXME: implement this once there are PALs that use it. + */ +int uwb_rsv_modify(struct uwb_rsv *rsv, int max_mas, int min_mas, int sparsity) +{ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(uwb_rsv_modify); + +/** + * uwb_rsv_terminate - terminate an established reservation + * @rsv: the reservation to terminate + * + * A reservation is terminated by removing the DRP IE from the beacon, + * the other end will consider the reservation to be terminated when + * it does not see the DRP IE for at least mMaxLostBeacons. + * + * If applicable, the reference to the target uwb_dev will be released. + */ +void uwb_rsv_terminate(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + + mutex_lock(&rc->rsvs_mutex); + + uwb_rsv_set_state(rsv, UWB_RSV_STATE_NONE); + + mutex_unlock(&rc->rsvs_mutex); +} +EXPORT_SYMBOL_GPL(uwb_rsv_terminate); + +/** + * uwb_rsv_accept - accept a new reservation from a peer + * @rsv: the reservation + * @cb: call back for reservation changes + * @pal_priv: data to be passed in the above call back + * + * Reservation requests from peers are denied unless a PAL accepts it + * by calling this function. + */ +void uwb_rsv_accept(struct uwb_rsv *rsv, uwb_rsv_cb_f cb, void *pal_priv) +{ + rsv->callback = cb; + rsv->pal_priv = pal_priv; + rsv->state = UWB_RSV_STATE_T_ACCEPTED; +} +EXPORT_SYMBOL_GPL(uwb_rsv_accept); + +/* + * Is a received DRP IE for this reservation? + */ +static bool uwb_rsv_match(struct uwb_rsv *rsv, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_dev_addr *rsv_src; + int stream; + + stream = uwb_ie_drp_stream_index(drp_ie); + + if (rsv->stream != stream) + return false; + + switch (rsv->target.type) { + case UWB_RSV_TARGET_DEVADDR: + return rsv->stream == stream; + case UWB_RSV_TARGET_DEV: + if (uwb_ie_drp_owner(drp_ie)) + rsv_src = &rsv->owner->dev_addr; + else + rsv_src = &rsv->target.dev->dev_addr; + return uwb_dev_addr_cmp(&src->dev_addr, rsv_src) == 0; + } + return false; +} + +static struct uwb_rsv *uwb_rsv_new_target(struct uwb_rc *rc, + struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_rsv *rsv; + struct uwb_pal *pal; + enum uwb_rsv_state state; + + rsv = uwb_rsv_alloc(rc); + if (!rsv) + return NULL; + + rsv->rc = rc; + rsv->owner = src; + uwb_dev_get(rsv->owner); + rsv->target.type = UWB_RSV_TARGET_DEV; + rsv->target.dev = &rc->uwb_dev; + rsv->type = uwb_ie_drp_type(drp_ie); + rsv->stream = uwb_ie_drp_stream_index(drp_ie); + set_bit(rsv->stream, rsv->owner->streams); + uwb_drp_ie_to_bm(&rsv->mas, drp_ie); + + /* + * See if any PALs are interested in this reservation. If not, + * deny the request. + */ + rsv->state = UWB_RSV_STATE_T_DENIED; + spin_lock(&rc->pal_lock); + list_for_each_entry(pal, &rc->pals, node) { + if (pal->new_rsv) + pal->new_rsv(rsv); + if (rsv->state == UWB_RSV_STATE_T_ACCEPTED) + break; + } + spin_unlock(&rc->pal_lock); + + list_add_tail(&rsv->rc_node, &rc->reservations); + state = rsv->state; + rsv->state = UWB_RSV_STATE_NONE; + uwb_rsv_set_state(rsv, state); + + return rsv; +} + +/** + * uwb_rsv_find - find a reservation for a received DRP IE. + * @rc: the radio controller + * @src: source of the DRP IE + * @drp_ie: the DRP IE + * + * If the reservation cannot be found and the DRP IE is from a peer + * attempting to establish a new reservation, create a new reservation + * and add it to the list. + */ +struct uwb_rsv *uwb_rsv_find(struct uwb_rc *rc, struct uwb_dev *src, + struct uwb_ie_drp *drp_ie) +{ + struct uwb_rsv *rsv; + + list_for_each_entry(rsv, &rc->reservations, rc_node) { + if (uwb_rsv_match(rsv, src, drp_ie)) + return rsv; + } + + if (uwb_ie_drp_owner(drp_ie)) + return uwb_rsv_new_target(rc, src, drp_ie); + + return NULL; +} + +/* + * Go through all the reservations and check for timeouts and (if + * necessary) update their DRP IEs. + * + * FIXME: look at building the SET_DRP_IE command here rather than + * having to rescan the list in uwb_rc_send_all_drp_ie(). + */ +static bool uwb_rsv_update_all(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv, *t; + bool ie_updated = false; + + list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) { + if (rsv->expired) + uwb_drp_handle_timeout(rsv); + if (!rsv->ie_valid) { + uwb_drp_ie_update(rsv); + ie_updated = true; + } + } + + return ie_updated; +} + +void uwb_rsv_sched_update(struct uwb_rc *rc) +{ + queue_work(rc->rsv_workq, &rc->rsv_update_work); +} + +/* + * Update DRP IEs and, if necessary, the DRP Availability IE and send + * the updated IEs to the radio controller. + */ +static void uwb_rsv_update_work(struct work_struct *work) +{ + struct uwb_rc *rc = container_of(work, struct uwb_rc, rsv_update_work); + bool ie_updated; + + mutex_lock(&rc->rsvs_mutex); + + ie_updated = uwb_rsv_update_all(rc); + + if (!rc->drp_avail.ie_valid) { + uwb_drp_avail_ie_update(rc); + ie_updated = true; + } + + if (ie_updated) + uwb_rc_send_all_drp_ie(rc); + + mutex_unlock(&rc->rsvs_mutex); +} + +static void uwb_rsv_timer(unsigned long arg) +{ + struct uwb_rsv *rsv = (struct uwb_rsv *)arg; + + rsv->expired = true; + uwb_rsv_sched_update(rsv->rc); +} + +void uwb_rsv_init(struct uwb_rc *rc) +{ + INIT_LIST_HEAD(&rc->reservations); + mutex_init(&rc->rsvs_mutex); + INIT_WORK(&rc->rsv_update_work, uwb_rsv_update_work); + + bitmap_complement(rc->uwb_dev.streams, rc->uwb_dev.streams, UWB_NUM_STREAMS); +} + +int uwb_rsv_setup(struct uwb_rc *rc) +{ + char name[16]; + + snprintf(name, sizeof(name), "%s_rsvd", dev_name(&rc->uwb_dev.dev)); + rc->rsv_workq = create_singlethread_workqueue(name); + if (rc->rsv_workq == NULL) + return -ENOMEM; + + return 0; +} + +void uwb_rsv_cleanup(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv, *t; + + mutex_lock(&rc->rsvs_mutex); + list_for_each_entry_safe(rsv, t, &rc->reservations, rc_node) { + uwb_rsv_remove(rsv); + } + mutex_unlock(&rc->rsvs_mutex); + + cancel_work_sync(&rc->rsv_update_work); + destroy_workqueue(rc->rsv_workq); +} -- cgit v1.2.3 From 599e8d80a2cdf4f65fc49b31b27a49235c78acfe Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:10 +0100 Subject: uwb: add the UWB stack (debug support) Add various debugfs files, principaly for the reservation manager. Signed-off-by: David Vrabel --- drivers/uwb/uwb-debug.c | 367 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 drivers/uwb/uwb-debug.c diff --git a/drivers/uwb/uwb-debug.c b/drivers/uwb/uwb-debug.c new file mode 100644 index 00000000000..6d232c35d07 --- /dev/null +++ b/drivers/uwb/uwb-debug.c @@ -0,0 +1,367 @@ +/* + * Ultra Wide Band + * Debug support + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: doc + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#define D_LOCAL 0 +#include + +#include "uwb-internal.h" + +void dump_bytes(struct device *dev, const void *_buf, size_t rsize) +{ + const char *buf = _buf; + char line[32]; + size_t offset = 0; + int cnt, cnt2; + for (cnt = 0; cnt < rsize; cnt += 8) { + size_t rtop = rsize - cnt < 8 ? rsize - cnt : 8; + for (offset = cnt2 = 0; cnt2 < rtop; cnt2++) { + offset += scnprintf(line + offset, sizeof(line) - offset, + "%02x ", buf[cnt + cnt2] & 0xff); + } + if (dev) + dev_info(dev, "%s\n", line); + else + printk(KERN_INFO "%s\n", line); + } +} +EXPORT_SYMBOL_GPL(dump_bytes); + +/* + * Debug interface + * + * Per radio controller debugfs files (in uwb/uwbN/): + * + * command: Flexible command interface (see ). + * + * reservations: information on reservations. + * + * accept: Set to true (Y or 1) to accept reservation requests from + * peers. + * + * drp_avail: DRP availability information. + */ + +struct uwb_dbg { + struct uwb_pal pal; + + u32 accept; + struct list_head rsvs; + + struct dentry *root_d; + struct dentry *command_f; + struct dentry *reservations_f; + struct dentry *accept_f; + struct dentry *drp_avail_f; +}; + +static struct dentry *root_dir; + +static void uwb_dbg_rsv_cb(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + struct device *dev = &rc->uwb_dev.dev; + struct uwb_dev_addr devaddr; + char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE]; + + uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr); + if (rsv->target.type == UWB_RSV_TARGET_DEV) + devaddr = rsv->target.dev->dev_addr; + else + devaddr = rsv->target.devaddr; + uwb_dev_addr_print(target, sizeof(target), &devaddr); + + dev_dbg(dev, "debug: rsv %s -> %s: %s\n", + owner, target, uwb_rsv_state_str(rsv->state)); +} + +static int cmd_rsv_establish(struct uwb_rc *rc, + struct uwb_dbg_cmd_rsv_establish *cmd) +{ + struct uwb_mac_addr macaddr; + struct uwb_rsv *rsv; + struct uwb_dev *target; + int ret; + + memcpy(&macaddr, cmd->target, sizeof(macaddr)); + target = uwb_dev_get_by_macaddr(rc, &macaddr); + if (target == NULL) + return -ENODEV; + + rsv = uwb_rsv_create(rc, uwb_dbg_rsv_cb, NULL); + if (rsv == NULL) { + uwb_dev_put(target); + return -ENOMEM; + } + + rsv->owner = &rc->uwb_dev; + rsv->target.type = UWB_RSV_TARGET_DEV; + rsv->target.dev = target; + rsv->type = cmd->type; + rsv->max_mas = cmd->max_mas; + rsv->min_mas = cmd->min_mas; + rsv->sparsity = cmd->sparsity; + + ret = uwb_rsv_establish(rsv); + if (ret) + uwb_rsv_destroy(rsv); + else + list_add_tail(&rsv->pal_node, &rc->dbg->rsvs); + + return ret; +} + +static int cmd_rsv_terminate(struct uwb_rc *rc, + struct uwb_dbg_cmd_rsv_terminate *cmd) +{ + struct uwb_rsv *rsv, *found = NULL; + int i = 0; + + list_for_each_entry(rsv, &rc->dbg->rsvs, pal_node) { + if (i == cmd->index) { + found = rsv; + break; + } + } + if (!found) + return -EINVAL; + + list_del(&found->pal_node); + uwb_rsv_terminate(found); + + return 0; +} + +static int command_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +static ssize_t command_write(struct file *file, const char __user *buf, + size_t len, loff_t *off) +{ + struct uwb_rc *rc = file->private_data; + struct uwb_dbg_cmd cmd; + int ret; + + if (len != sizeof(struct uwb_dbg_cmd)) + return -EINVAL; + + if (copy_from_user(&cmd, buf, len) != 0) + return -EFAULT; + + switch (cmd.type) { + case UWB_DBG_CMD_RSV_ESTABLISH: + ret = cmd_rsv_establish(rc, &cmd.rsv_establish); + break; + case UWB_DBG_CMD_RSV_TERMINATE: + ret = cmd_rsv_terminate(rc, &cmd.rsv_terminate); + break; + default: + return -EINVAL; + } + + return ret < 0 ? ret : len; +} + +static struct file_operations command_fops = { + .open = command_open, + .write = command_write, + .read = NULL, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +static int reservations_print(struct seq_file *s, void *p) +{ + struct uwb_rc *rc = s->private; + struct uwb_rsv *rsv; + + mutex_lock(&rc->rsvs_mutex); + + list_for_each_entry(rsv, &rc->reservations, rc_node) { + struct uwb_dev_addr devaddr; + char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE]; + bool is_owner; + char buf[72]; + + uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr); + if (rsv->target.type == UWB_RSV_TARGET_DEV) { + devaddr = rsv->target.dev->dev_addr; + is_owner = &rc->uwb_dev == rsv->owner; + } else { + devaddr = rsv->target.devaddr; + is_owner = true; + } + uwb_dev_addr_print(target, sizeof(target), &devaddr); + + seq_printf(s, "%c %s -> %s: %s\n", + is_owner ? 'O' : 'T', + owner, target, uwb_rsv_state_str(rsv->state)); + seq_printf(s, " stream: %d type: %s\n", + rsv->stream, uwb_rsv_type_str(rsv->type)); + bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS); + seq_printf(s, " %s\n", buf); + } + + mutex_unlock(&rc->rsvs_mutex); + + return 0; +} + +static int reservations_open(struct inode *inode, struct file *file) +{ + return single_open(file, reservations_print, inode->i_private); +} + +static struct file_operations reservations_fops = { + .open = reservations_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int drp_avail_print(struct seq_file *s, void *p) +{ + struct uwb_rc *rc = s->private; + char buf[72]; + + bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.global, UWB_NUM_MAS); + seq_printf(s, "global: %s\n", buf); + bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.local, UWB_NUM_MAS); + seq_printf(s, "local: %s\n", buf); + bitmap_scnprintf(buf, sizeof(buf), rc->drp_avail.pending, UWB_NUM_MAS); + seq_printf(s, "pending: %s\n", buf); + + return 0; +} + +static int drp_avail_open(struct inode *inode, struct file *file) +{ + return single_open(file, drp_avail_print, inode->i_private); +} + +static struct file_operations drp_avail_fops = { + .open = drp_avail_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static void uwb_dbg_new_rsv(struct uwb_rsv *rsv) +{ + struct uwb_rc *rc = rsv->rc; + + if (rc->dbg->accept) + uwb_rsv_accept(rsv, uwb_dbg_rsv_cb, NULL); +} + +/** + * uwb_dbg_add_rc - add a debug interface for a radio controller + * @rc: the radio controller + */ +void uwb_dbg_add_rc(struct uwb_rc *rc) +{ + rc->dbg = kzalloc(sizeof(struct uwb_dbg), GFP_KERNEL); + if (rc->dbg == NULL) + return; + + INIT_LIST_HEAD(&rc->dbg->rsvs); + + uwb_pal_init(&rc->dbg->pal); + rc->dbg->pal.new_rsv = uwb_dbg_new_rsv; + uwb_pal_register(rc, &rc->dbg->pal); + if (root_dir) { + rc->dbg->root_d = debugfs_create_dir(dev_name(&rc->uwb_dev.dev), + root_dir); + rc->dbg->command_f = debugfs_create_file("command", 0200, + rc->dbg->root_d, rc, + &command_fops); + rc->dbg->reservations_f = debugfs_create_file("reservations", 0444, + rc->dbg->root_d, rc, + &reservations_fops); + rc->dbg->accept_f = debugfs_create_bool("accept", 0644, + rc->dbg->root_d, + &rc->dbg->accept); + rc->dbg->drp_avail_f = debugfs_create_file("drp_avail", 0444, + rc->dbg->root_d, rc, + &drp_avail_fops); + } +} + +/** + * uwb_dbg_add_rc - remove a radio controller's debug interface + * @rc: the radio controller + */ +void uwb_dbg_del_rc(struct uwb_rc *rc) +{ + struct uwb_rsv *rsv, *t; + + if (rc->dbg == NULL) + return; + + list_for_each_entry_safe(rsv, t, &rc->dbg->rsvs, pal_node) { + uwb_rsv_destroy(rsv); + } + + uwb_pal_unregister(rc, &rc->dbg->pal); + + if (root_dir) { + debugfs_remove(rc->dbg->drp_avail_f); + debugfs_remove(rc->dbg->accept_f); + debugfs_remove(rc->dbg->reservations_f); + debugfs_remove(rc->dbg->command_f); + debugfs_remove(rc->dbg->root_d); + } +} + +/** + * uwb_dbg_exit - initialize the debug interface sub-module + */ +void uwb_dbg_init(void) +{ + root_dir = debugfs_create_dir("uwb", NULL); +} + +/** + * uwb_dbg_exit - clean-up the debug interface sub-module + */ +void uwb_dbg_exit(void) +{ + debugfs_remove(root_dir); +} -- cgit v1.2.3 From 2f86c3e67d6423d6d23ee2f737ad4f0730435742 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 17 Sep 2008 16:34:11 +0100 Subject: uwb: add the UWB stack (build system) The Kbuild and Kconfig files. Signed-off-by: Greg Kroah-Hartman Signed-off-by: David Vrabel --- arch/arm/Kconfig | 2 ++ arch/cris/Kconfig | 2 ++ arch/h8300/Kconfig | 2 ++ drivers/Kconfig | 2 ++ drivers/Makefile | 1 + drivers/uwb/Kconfig | 28 ++++++++++++++++++++++++++++ drivers/uwb/Makefile | 20 ++++++++++++++++++++ 7 files changed, 57 insertions(+) create mode 100644 drivers/uwb/Kconfig create mode 100644 drivers/uwb/Makefile diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 70dba166890..8eedbfa52f0 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1220,6 +1220,8 @@ source "drivers/hid/Kconfig" source "drivers/usb/Kconfig" +source "drivers/uwb/Kconfig" + source "drivers/mmc/Kconfig" source "drivers/leds/Kconfig" diff --git a/arch/cris/Kconfig b/arch/cris/Kconfig index 9389d38f222..cb66c4da25c 100644 --- a/arch/cris/Kconfig +++ b/arch/cris/Kconfig @@ -677,6 +677,8 @@ source "fs/Kconfig" source "drivers/usb/Kconfig" +source "drivers/uwb/Kconfig" + source "arch/cris/Kconfig.debug" source "security/Kconfig" diff --git a/arch/h8300/Kconfig b/arch/h8300/Kconfig index 396ab059efa..2b413325e88 100644 --- a/arch/h8300/Kconfig +++ b/arch/h8300/Kconfig @@ -213,6 +213,8 @@ source "drivers/hwmon/Kconfig" source "drivers/usb/Kconfig" +source "drivers/uwb/Kconfig" + endmenu source "fs/Kconfig" diff --git a/drivers/Kconfig b/drivers/Kconfig index 59f33fa6af3..9b399a1bae8 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -78,6 +78,8 @@ source "drivers/hid/Kconfig" source "drivers/usb/Kconfig" +source "drivers/uwb/Kconfig" + source "drivers/mmc/Kconfig" source "drivers/memstick/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 2735bde7347..1a6930f9726 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -99,3 +99,4 @@ obj-$(CONFIG_OF) += of/ obj-$(CONFIG_SSB) += ssb/ obj-$(CONFIG_VIRTIO) += virtio/ obj-$(CONFIG_REGULATOR) += regulator/ +obj-$(CONFIG_UWB) += uwb/ diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig new file mode 100644 index 00000000000..3f01a69daf8 --- /dev/null +++ b/drivers/uwb/Kconfig @@ -0,0 +1,28 @@ +# +# UWB device configuration +# + +menuconfig UWB + tristate "Ultra Wide Band devices" + depends on PCI + default n + help + UWB is a high-bandwidth, low-power, point-to-point radio + technology using a wide spectrum (3.1-10.6GHz). It is + optimized for in-room use (480Mbps at 2 meters, 110Mbps at + 10m). It serves as the transport layer for other protocols, + such as Wireless USB (WUSB), IP (WLP) and upcoming + Bluetooth and 1394 + + The topology is peer to peer; however, higher level + protocols (such as WUSB) might impose a master/slave + relationship. + + Say Y here if your computer has UWB radio controllers (USB or PCI) + based. You will need to enable the radio controllers + below. It is ok to select all of them, no harm done. + + For more help check the UWB and WUSB related files in + . + + To compile the UWB stack as a module, choose M here. diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile new file mode 100644 index 00000000000..9a67be5ac5c --- /dev/null +++ b/drivers/uwb/Makefile @@ -0,0 +1,20 @@ +obj-$(CONFIG_UWB) += uwb.o + +uwb-objs := \ + address.o \ + beacon.o \ + driver.o \ + drp.o \ + drp-avail.o \ + drp-ie.o \ + est.o \ + ie.o \ + lc-dev.o \ + lc-rc.o \ + neh.o \ + pal.o \ + reset.o \ + rsv.o \ + scan.o \ + uwb-debug.o \ + uwbd.o -- cgit v1.2.3 From da389eac31be24556a71dd59ea6539ae4cba5c15 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:12 +0100 Subject: uwb: add the umc bus The UMC bus is used for the capabilities exposed by a UWB Multi-interface Controller as described in the WHCI specification. Signed-off-by: David Vrabel --- drivers/uwb/Makefile | 6 ++ drivers/uwb/umc-bus.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/uwb/umc-dev.c | 104 +++++++++++++++++++++++ drivers/uwb/umc-drv.c | 31 +++++++ include/linux/uwb/umc.h | 194 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 553 insertions(+) create mode 100644 drivers/uwb/umc-bus.c create mode 100644 drivers/uwb/umc-dev.c create mode 100644 drivers/uwb/umc-drv.c create mode 100644 include/linux/uwb/umc.h diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile index 9a67be5ac5c..41c9fca5f87 100644 --- a/drivers/uwb/Makefile +++ b/drivers/uwb/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_UWB) += uwb.o +obj-$(CONFIG_UWB_WHCI) += umc.o uwb-objs := \ address.o \ @@ -18,3 +19,8 @@ uwb-objs := \ scan.o \ uwb-debug.o \ uwbd.o + +umc-objs := \ + umc-bus.o \ + umc-dev.o \ + umc-drv.o diff --git a/drivers/uwb/umc-bus.c b/drivers/uwb/umc-bus.c new file mode 100644 index 00000000000..2d8d62d9f53 --- /dev/null +++ b/drivers/uwb/umc-bus.c @@ -0,0 +1,218 @@ +/* + * Bus for UWB Multi-interface Controller capabilities. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include +#include +#include +#include +#include + +static int umc_bus_unbind_helper(struct device *dev, void *data) +{ + struct device *parent = data; + + if (dev->parent == parent && dev->driver) + device_release_driver(dev); + return 0; +} + +/** + * umc_controller_reset - reset the whole UMC controller + * @umc: the UMC device for the radio controller. + * + * Drivers will be unbound from all UMC devices belonging to the + * controller and then the radio controller will be rebound. The + * radio controller is expected to do a full hardware reset when it is + * probed. + * + * If this is called while a probe() or remove() is in progress it + * will return -EAGAIN and not perform the reset. + */ +int umc_controller_reset(struct umc_dev *umc) +{ + struct device *parent = umc->dev.parent; + int ret; + + if (down_trylock(&parent->sem)) + return -EAGAIN; + bus_for_each_dev(&umc_bus_type, NULL, parent, umc_bus_unbind_helper); + ret = device_attach(&umc->dev); + if (ret == 1) + ret = 0; + up(&parent->sem); + + return ret; +} +EXPORT_SYMBOL_GPL(umc_controller_reset); + +/** + * umc_match_pci_id - match a UMC driver to a UMC device's parent PCI device. + * @umc_drv: umc driver with match_data pointing to a zero-terminated + * table of pci_device_id's. + * @umc: umc device whose parent is to be matched. + */ +int umc_match_pci_id(struct umc_driver *umc_drv, struct umc_dev *umc) +{ + const struct pci_device_id *id_table = umc_drv->match_data; + struct pci_dev *pci; + + if (umc->dev.parent->bus != &pci_bus_type) + return 0; + + pci = to_pci_dev(umc->dev.parent); + return pci_match_id(id_table, pci) != NULL; +} +EXPORT_SYMBOL_GPL(umc_match_pci_id); + +static int umc_bus_rescan_helper(struct device *dev, void *data) +{ + int ret = 0; + + if (!dev->driver) + ret = device_attach(dev); + + return ret < 0 ? ret : 0; +} + +static void umc_bus_rescan(void) +{ + int err; + + /* + * We can't use bus_rescan_devices() here as it deadlocks when + * it tries to retake the dev->parent semaphore. + */ + err = bus_for_each_dev(&umc_bus_type, NULL, NULL, umc_bus_rescan_helper); + if (err < 0) + printk(KERN_WARNING "%s: rescan of bus failed: %d\n", + KBUILD_MODNAME, err); +} + +static int umc_bus_match(struct device *dev, struct device_driver *drv) +{ + struct umc_dev *umc = to_umc_dev(dev); + struct umc_driver *umc_driver = to_umc_driver(drv); + + if (umc->cap_id == umc_driver->cap_id) { + if (umc_driver->match) + return umc_driver->match(umc_driver, umc); + else + return 1; + } + return 0; +} + +static int umc_device_probe(struct device *dev) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + int err; + + umc_driver = to_umc_driver(dev->driver); + umc = to_umc_dev(dev); + + get_device(dev); + err = umc_driver->probe(umc); + if (err) + put_device(dev); + else + umc_bus_rescan(); + + return err; +} + +static int umc_device_remove(struct device *dev) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + + umc_driver = to_umc_driver(dev->driver); + umc = to_umc_dev(dev); + + umc_driver->remove(umc); + put_device(dev); + return 0; +} + +static int umc_device_suspend(struct device *dev, pm_message_t state) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + int err = 0; + + umc = to_umc_dev(dev); + + if (dev->driver) { + umc_driver = to_umc_driver(dev->driver); + if (umc_driver->suspend) + err = umc_driver->suspend(umc, state); + } + return err; +} + +static int umc_device_resume(struct device *dev) +{ + struct umc_dev *umc; + struct umc_driver *umc_driver; + int err = 0; + + umc = to_umc_dev(dev); + + if (dev->driver) { + umc_driver = to_umc_driver(dev->driver); + if (umc_driver->resume) + err = umc_driver->resume(umc); + } + return err; +} + +static ssize_t capability_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct umc_dev *umc = to_umc_dev(dev); + + return sprintf(buf, "0x%02x\n", umc->cap_id); +} + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct umc_dev *umc = to_umc_dev(dev); + + return sprintf(buf, "0x%04x\n", umc->version); +} + +static struct device_attribute umc_dev_attrs[] = { + __ATTR_RO(capability_id), + __ATTR_RO(version), + __ATTR_NULL, +}; + +struct bus_type umc_bus_type = { + .name = "umc", + .match = umc_bus_match, + .probe = umc_device_probe, + .remove = umc_device_remove, + .suspend = umc_device_suspend, + .resume = umc_device_resume, + .dev_attrs = umc_dev_attrs, +}; +EXPORT_SYMBOL_GPL(umc_bus_type); + +static int __init umc_bus_init(void) +{ + return bus_register(&umc_bus_type); +} +module_init(umc_bus_init); + +static void __exit umc_bus_exit(void) +{ + bus_unregister(&umc_bus_type); +} +module_exit(umc_bus_exit); + +MODULE_DESCRIPTION("UWB Multi-interface Controller capability bus"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/umc-dev.c b/drivers/uwb/umc-dev.c new file mode 100644 index 00000000000..aa44e1c1a10 --- /dev/null +++ b/drivers/uwb/umc-dev.c @@ -0,0 +1,104 @@ +/* + * UWB Multi-interface Controller device management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include +#include +#define D_LOCAL 0 +#include + +static void umc_device_release(struct device *dev) +{ + struct umc_dev *umc = to_umc_dev(dev); + + kfree(umc); +} + +/** + * umc_device_create - allocate a child UMC device + * @parent: parent of the new UMC device. + * @n: index of the new device. + * + * The new UMC device will have a bus ID of the parent with '-n' + * appended. + */ +struct umc_dev *umc_device_create(struct device *parent, int n) +{ + struct umc_dev *umc; + + umc = kzalloc(sizeof(struct umc_dev), GFP_KERNEL); + if (umc) { + snprintf(umc->dev.bus_id, sizeof(umc->dev.bus_id), "%s-%d", + parent->bus_id, n); + umc->dev.parent = parent; + umc->dev.bus = &umc_bus_type; + umc->dev.release = umc_device_release; + + umc->dev.dma_mask = parent->dma_mask; + } + return umc; +} +EXPORT_SYMBOL_GPL(umc_device_create); + +/** + * umc_device_register - register a UMC device + * @umc: pointer to the UMC device + * + * The memory resource for the UMC device is acquired and the device + * registered with the system. + */ +int umc_device_register(struct umc_dev *umc) +{ + int err; + + d_fnstart(3, &umc->dev, "(umc_dev %p)\n", umc); + + err = request_resource(umc->resource.parent, &umc->resource); + if (err < 0) { + dev_err(&umc->dev, "can't allocate resource range " + "%016Lx to %016Lx: %d\n", + (unsigned long long)umc->resource.start, + (unsigned long long)umc->resource.end, + err); + goto error_request_resource; + } + + err = device_register(&umc->dev); + if (err < 0) + goto error_device_register; + d_fnend(3, &umc->dev, "(umc_dev %p) = 0\n", umc); + return 0; + +error_device_register: + release_resource(&umc->resource); +error_request_resource: + d_fnend(3, &umc->dev, "(umc_dev %p) = %d\n", umc, err); + return err; +} +EXPORT_SYMBOL_GPL(umc_device_register); + +/** + * umc_device_unregister - unregister a UMC device + * @umc: pointer to the UMC device + * + * First we unregister the device, make sure the driver can do it's + * resource release thing and then we try to release any left over + * resources. We take a ref to the device, to make sure it doesn't + * dissapear under our feet. + */ +void umc_device_unregister(struct umc_dev *umc) +{ + struct device *dev; + if (!umc) + return; + dev = get_device(&umc->dev); + d_fnstart(3, dev, "(umc_dev %p)\n", umc); + device_unregister(&umc->dev); + release_resource(&umc->resource); + d_fnend(3, dev, "(umc_dev %p) = void\n", umc); + put_device(dev); +} +EXPORT_SYMBOL_GPL(umc_device_unregister); diff --git a/drivers/uwb/umc-drv.c b/drivers/uwb/umc-drv.c new file mode 100644 index 00000000000..367b5eb85d6 --- /dev/null +++ b/drivers/uwb/umc-drv.c @@ -0,0 +1,31 @@ +/* + * UWB Multi-interface Controller driver management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include +#include + +int __umc_driver_register(struct umc_driver *umc_drv, struct module *module, + const char *mod_name) +{ + umc_drv->driver.name = umc_drv->name; + umc_drv->driver.owner = module; + umc_drv->driver.mod_name = mod_name; + umc_drv->driver.bus = &umc_bus_type; + + return driver_register(&umc_drv->driver); +} +EXPORT_SYMBOL_GPL(__umc_driver_register); + +/** + * umc_driver_register - unregister a UMC capabiltity driver. + * @umc_drv: pointer to the driver. + */ +void umc_driver_unregister(struct umc_driver *umc_drv) +{ + driver_unregister(&umc_drv->driver); +} +EXPORT_SYMBOL_GPL(umc_driver_unregister); diff --git a/include/linux/uwb/umc.h b/include/linux/uwb/umc.h new file mode 100644 index 00000000000..36a39e34f8d --- /dev/null +++ b/include/linux/uwb/umc.h @@ -0,0 +1,194 @@ +/* + * UWB Multi-interface Controller support. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GPLv2 + * + * UMC (UWB Multi-interface Controller) capabilities (e.g., radio + * controller, host controller) are presented as devices on the "umc" + * bus. + * + * The radio controller is not strictly a UMC capability but it's + * useful to present it as such. + * + * References: + * + * [WHCI] Wireless Host Controller Interface Specification for + * Certified Wireless Universal Serial Bus, revision 0.95. + * + * How this works is kind of convoluted but simple. The whci.ko driver + * loads when WHCI devices are detected. These WHCI devices expose + * many devices in the same PCI function (they couldn't have reused + * functions, no), so for each PCI function that exposes these many + * devices, whci ceates a umc_dev [whci_probe() -> whci_add_cap()] + * with umc_device_create() and adds it to the bus with + * umc_device_register(). + * + * umc_device_register() calls device_register() which will push the + * bus management code to load your UMC driver's somehting_probe() + * that you have registered for that capability code. + * + * Now when the WHCI device is removed, whci_remove() will go over + * each umc_dev assigned to each of the PCI function's capabilities + * and through whci_del_cap() call umc_device_unregister() each + * created umc_dev. Of course, if you are bound to the device, your + * driver's something_remove() will be called. + */ + +#ifndef _LINUX_UWB_UMC_H_ +#define _LINUX_UWB_UMC_H_ + +#include +#include + +/* + * UMC capability IDs. + * + * 0x00 is reserved so use it for the radio controller device. + * + * [WHCI] table 2-8 + */ +#define UMC_CAP_ID_WHCI_RC 0x00 /* radio controller */ +#define UMC_CAP_ID_WHCI_WUSB_HC 0x01 /* WUSB host controller */ + +/** + * struct umc_dev - UMC capability device + * + * @version: version of the specification this capability conforms to. + * @cap_id: capability ID. + * @bar: PCI Bar (64 bit) where the resource lies + * @resource: register space resource. + * @irq: interrupt line. + */ +struct umc_dev { + u16 version; + u8 cap_id; + u8 bar; + struct resource resource; + unsigned irq; + struct device dev; +}; + +#define to_umc_dev(d) container_of(d, struct umc_dev, dev) + +/** + * struct umc_driver - UMC capability driver + * @cap_id: supported capability ID. + * @match: driver specific capability matching function. + * @match_data: driver specific data for match() (e.g., a + * table of pci_device_id's if umc_match_pci_id() is used). + */ +struct umc_driver { + char *name; + u8 cap_id; + int (*match)(struct umc_driver *, struct umc_dev *); + const void *match_data; + + int (*probe)(struct umc_dev *); + void (*remove)(struct umc_dev *); + int (*suspend)(struct umc_dev *, pm_message_t state); + int (*resume)(struct umc_dev *); + + struct device_driver driver; +}; + +#define to_umc_driver(d) container_of(d, struct umc_driver, driver) + +extern struct bus_type umc_bus_type; + +struct umc_dev *umc_device_create(struct device *parent, int n); +int __must_check umc_device_register(struct umc_dev *umc); +void umc_device_unregister(struct umc_dev *umc); + +int __must_check __umc_driver_register(struct umc_driver *umc_drv, + struct module *mod, + const char *mod_name); + +/** + * umc_driver_register - register a UMC capabiltity driver. + * @umc_drv: pointer to the driver. + */ +static inline int __must_check umc_driver_register(struct umc_driver *umc_drv) +{ + return __umc_driver_register(umc_drv, THIS_MODULE, KBUILD_MODNAME); +} +void umc_driver_unregister(struct umc_driver *umc_drv); + +/* + * Utility function you can use to match (umc_driver->match) against a + * null-terminated array of 'struct pci_device_id' in + * umc_driver->match_data. + */ +int umc_match_pci_id(struct umc_driver *umc_drv, struct umc_dev *umc); + +/** + * umc_parent_pci_dev - return the UMC's parent PCI device or NULL if none + * @umc_dev: UMC device whose parent PCI device we are looking for + * + * DIRTY!!! DON'T RELY ON THIS + * + * FIXME: This is as dirty as it gets, but we need some way to check + * the correct type of umc_dev->parent (so that for example, we can + * cast to pci_dev). Casting to pci_dev is necesary because at some + * point we need to request resources from the device. Mapping is + * easily over come (ioremap and stuff are bus agnostic), but hooking + * up to some error handlers (such as pci error handlers) might need + * this. + * + * THIS might (probably will) be removed in the future, so don't count + * on it. + */ +static inline struct pci_dev *umc_parent_pci_dev(struct umc_dev *umc_dev) +{ + struct pci_dev *pci_dev = NULL; + if (umc_dev->dev.parent->bus == &pci_bus_type) + pci_dev = to_pci_dev(umc_dev->dev.parent); + return pci_dev; +} + +/** + * umc_dev_get() - reference a UMC device. + * @umc_dev: Pointer to UMC device. + * + * NOTE: we are assuming in this whole scheme that the parent device + * is referenced at _probe() time and unreferenced at _remove() + * time by the parent's subsystem. + */ +static inline struct umc_dev *umc_dev_get(struct umc_dev *umc_dev) +{ + get_device(&umc_dev->dev); + return umc_dev; +} + +/** + * umc_dev_put() - unreference a UMC device. + * @umc_dev: Pointer to UMC device. + */ +static inline void umc_dev_put(struct umc_dev *umc_dev) +{ + put_device(&umc_dev->dev); +} + +/** + * umc_set_drvdata - set UMC device's driver data. + * @umc_dev: Pointer to UMC device. + * @data: Data to set. + */ +static inline void umc_set_drvdata(struct umc_dev *umc_dev, void *data) +{ + dev_set_drvdata(&umc_dev->dev, data); +} + +/** + * umc_get_drvdata - recover UMC device's driver data. + * @umc_dev: Pointer to UMC device. + */ +static inline void *umc_get_drvdata(struct umc_dev *umc_dev) +{ + return dev_get_drvdata(&umc_dev->dev); +} + +int umc_controller_reset(struct umc_dev *umc); + +#endif /* #ifndef _LINUX_UWB_UMC_H_ */ -- cgit v1.2.3 From 8f1b678ab900c2bda1620dfb6e1f1f02604fc3a2 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:13 +0100 Subject: uwb: add the driver to enumerate WHCI capabilities This enumerates the capabilties of a WHCI device, adding a umc device for each one. Signed-off-by: David Vrabel --- drivers/uwb/Makefile | 2 +- drivers/uwb/whci.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/uwb/whci.h | 117 +++++++++++++++++++++ 3 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 drivers/uwb/whci.c create mode 100644 include/linux/uwb/whci.h diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile index 41c9fca5f87..b054471af28 100644 --- a/drivers/uwb/Makefile +++ b/drivers/uwb/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_UWB) += uwb.o -obj-$(CONFIG_UWB_WHCI) += umc.o +obj-$(CONFIG_UWB_WHCI) += umc.o whci.o uwb-objs := \ address.o \ diff --git a/drivers/uwb/whci.c b/drivers/uwb/whci.c new file mode 100644 index 00000000000..3df2388f908 --- /dev/null +++ b/drivers/uwb/whci.c @@ -0,0 +1,269 @@ +/* + * WHCI UWB Multi-interface Controller enumerator. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This file is released under the GNU GPL v2. + */ +#include +#include +#include +#include +#include +#include + +struct whci_card { + struct pci_dev *pci; + void __iomem *uwbbase; + u8 n_caps; + struct umc_dev *devs[0]; +}; + + +/* Fix faulty HW :( */ +static +u64 whci_capdata_quirks(struct whci_card *card, u64 capdata) +{ + u64 capdata_orig = capdata; + struct pci_dev *pci_dev = card->pci; + if (pci_dev->vendor == PCI_VENDOR_ID_INTEL + && (pci_dev->device == 0x0c3b || pci_dev->device == 0004) + && pci_dev->class == 0x0d1010) { + switch (UWBCAPDATA_TO_CAP_ID(capdata)) { + /* WLP capability has 0x100 bytes of aperture */ + case 0x80: + capdata |= 0x40 << 8; break; + /* WUSB capability has 0x80 bytes of aperture + * and ID is 1 */ + case 0x02: + capdata &= ~0xffff; + capdata |= 0x2001; + break; + } + } + if (capdata_orig != capdata) + dev_warn(&pci_dev->dev, + "PCI v%04x d%04x c%06x#%02x: " + "corrected capdata from %016Lx to %016Lx\n", + pci_dev->vendor, pci_dev->device, pci_dev->class, + (unsigned)UWBCAPDATA_TO_CAP_ID(capdata), + (unsigned long long)capdata_orig, + (unsigned long long)capdata); + return capdata; +} + + +/** + * whci_wait_for - wait for a WHCI register to be set + * + * Polls (for at most @max_ms ms) until '*@reg & @mask == @result'. + */ +int whci_wait_for(struct device *dev, u32 __iomem *reg, u32 mask, u32 result, + unsigned long max_ms, const char *tag) +{ + unsigned t = 0; + u32 val; + for (;;) { + val = le_readl(reg); + if ((val & mask) == result) + break; + msleep(10); + if (t >= max_ms) { + dev_err(dev, "timed out waiting for %s ", tag); + return -ETIMEDOUT; + } + t += 10; + } + return 0; +} +EXPORT_SYMBOL_GPL(whci_wait_for); + + +/* + * NOTE: the capinfo and capdata registers are slightly different + * (size and cap-id fields). So for cap #0, we need to fill + * in. Size comes from the size of the register block + * (statically calculated); cap_id comes from nowhere, we use + * zero, that is reserved, for the radio controller, because + * none was defined at the spec level. + */ +static int whci_add_cap(struct whci_card *card, int n) +{ + struct umc_dev *umc; + u64 capdata; + int bar, err; + + umc = umc_device_create(&card->pci->dev, n); + if (umc == NULL) + return -ENOMEM; + + capdata = le_readq(card->uwbbase + UWBCAPDATA(n)); + + bar = UWBCAPDATA_TO_BAR(capdata) << 1; + + capdata = whci_capdata_quirks(card, capdata); + /* Capability 0 is the radio controller. It's size is 32 + * bytes (WHCI0.95[2.3, T2-9]). */ + umc->version = UWBCAPDATA_TO_VERSION(capdata); + umc->cap_id = n == 0 ? 0 : UWBCAPDATA_TO_CAP_ID(capdata); + umc->bar = bar; + umc->resource.start = pci_resource_start(card->pci, bar) + + UWBCAPDATA_TO_OFFSET(capdata); + umc->resource.end = umc->resource.start + + (n == 0 ? 0x20 : UWBCAPDATA_TO_SIZE(capdata)) - 1; + umc->resource.name = umc->dev.bus_id; + umc->resource.flags = card->pci->resource[bar].flags; + umc->resource.parent = &card->pci->resource[bar]; + umc->irq = card->pci->irq; + + err = umc_device_register(umc); + if (err < 0) + goto error; + card->devs[n] = umc; + return 0; + +error: + kfree(umc); + return err; +} + +static void whci_del_cap(struct whci_card *card, int n) +{ + struct umc_dev *umc = card->devs[n]; + + if (umc != NULL) + umc_device_unregister(umc); +} + +static int whci_n_caps(struct pci_dev *pci) +{ + void __iomem *uwbbase; + u64 capinfo; + + uwbbase = pci_iomap(pci, 0, 8); + if (!uwbbase) + return -ENOMEM; + capinfo = le_readq(uwbbase + UWBCAPINFO); + pci_iounmap(pci, uwbbase); + + return UWBCAPINFO_TO_N_CAPS(capinfo); +} + +static int whci_probe(struct pci_dev *pci, const struct pci_device_id *id) +{ + struct whci_card *card; + int err, n_caps, n; + + err = pci_enable_device(pci); + if (err < 0) + goto error; + pci_enable_msi(pci); + pci_set_master(pci); + err = -ENXIO; + if (!pci_set_dma_mask(pci, DMA_64BIT_MASK)) + pci_set_consistent_dma_mask(pci, DMA_64BIT_MASK); + else if (!pci_set_dma_mask(pci, DMA_32BIT_MASK)) + pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK); + else + goto error_dma; + + err = n_caps = whci_n_caps(pci); + if (n_caps < 0) + goto error_ncaps; + + err = -ENOMEM; + card = kzalloc(sizeof(struct whci_card) + + sizeof(struct whci_dev *) * (n_caps + 1), + GFP_KERNEL); + if (card == NULL) + goto error_kzalloc; + card->pci = pci; + card->n_caps = n_caps; + + err = -EBUSY; + if (!request_mem_region(pci_resource_start(pci, 0), + UWBCAPDATA_SIZE(card->n_caps), + "whci (capability data)")) + goto error_request_memregion; + err = -ENOMEM; + card->uwbbase = pci_iomap(pci, 0, UWBCAPDATA_SIZE(card->n_caps)); + if (!card->uwbbase) + goto error_iomap; + + /* Add each capability. */ + for (n = 0; n <= card->n_caps; n++) { + err = whci_add_cap(card, n); + if (err < 0 && n == 0) { + dev_err(&pci->dev, "cannot bind UWB radio controller:" + " %d\n", err); + goto error_bind; + } + if (err < 0) + dev_warn(&pci->dev, "warning: cannot bind capability " + "#%u: %d\n", n, err); + } + pci_set_drvdata(pci, card); + return 0; + +error_bind: + pci_iounmap(pci, card->uwbbase); +error_iomap: + release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps)); +error_request_memregion: + kfree(card); +error_kzalloc: +error_ncaps: +error_dma: + pci_disable_msi(pci); + pci_disable_device(pci); +error: + return err; +} + +static void whci_remove(struct pci_dev *pci) +{ + struct whci_card *card = pci_get_drvdata(pci); + int n; + + pci_set_drvdata(pci, NULL); + /* Unregister each capability in reverse (so the master device + * is unregistered last). */ + for (n = card->n_caps; n >= 0 ; n--) + whci_del_cap(card, n); + pci_iounmap(pci, card->uwbbase); + release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps)); + kfree(card); + pci_disable_msi(pci); + pci_disable_device(pci); +} + +static struct pci_device_id whci_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, whci_id_table); + + +static struct pci_driver whci_driver = { + .name = "whci", + .id_table = whci_id_table, + .probe = whci_probe, + .remove = whci_remove, +}; + +static int __init whci_init(void) +{ + return pci_register_driver(&whci_driver); +} + +static void __exit whci_exit(void) +{ + pci_unregister_driver(&whci_driver); +} + +module_init(whci_init); +module_exit(whci_exit); + +MODULE_DESCRIPTION("WHCI UWB Multi-interface Controller enumerator"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/include/linux/uwb/whci.h b/include/linux/uwb/whci.h new file mode 100644 index 00000000000..915ec23042d --- /dev/null +++ b/include/linux/uwb/whci.h @@ -0,0 +1,117 @@ +/* + * Wireless Host Controller Interface for Ultra-Wide-Band and Wireless USB + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * + * References: + * [WHCI] Wireless Host Controller Interface Specification for + * Certified Wireless Universal Serial Bus, revision 0.95. + */ +#ifndef _LINUX_UWB_WHCI_H_ +#define _LINUX_UWB_WHCI_H_ + +#include + +/* + * UWB interface capability registers (offsets from UWBBASE) + * + * [WHCI] section 2.2 + */ +#define UWBCAPINFO 0x00 /* == UWBCAPDATA(0) */ +# define UWBCAPINFO_TO_N_CAPS(c) (((c) >> 0) & 0xFull) +#define UWBCAPDATA(n) (8*(n)) +# define UWBCAPDATA_TO_VERSION(c) (((c) >> 32) & 0xFFFFull) +# define UWBCAPDATA_TO_OFFSET(c) (((c) >> 18) & 0x3FFFull) +# define UWBCAPDATA_TO_BAR(c) (((c) >> 16) & 0x3ull) +# define UWBCAPDATA_TO_SIZE(c) ((((c) >> 8) & 0xFFull) * sizeof(u32)) +# define UWBCAPDATA_TO_CAP_ID(c) (((c) >> 0) & 0xFFull) + +/* Size of the WHCI capability data (including the RC capability) for + a device with n capabilities. */ +#define UWBCAPDATA_SIZE(n) (8 + 8*(n)) + + +/* + * URC registers (offsets from URCBASE) + * + * [WHCI] section 2.3 + */ +#define URCCMD 0x00 +# define URCCMD_RESET (1 << 31) /* UMC Hardware reset */ +# define URCCMD_RS (1 << 30) /* Run/Stop */ +# define URCCMD_EARV (1 << 29) /* Event Address Register Valid */ +# define URCCMD_ACTIVE (1 << 15) /* Command is active */ +# define URCCMD_IWR (1 << 14) /* Interrupt When Ready */ +# define URCCMD_SIZE_MASK 0x00000fff /* Command size mask */ +#define URCSTS 0x04 +# define URCSTS_EPS (1 << 17) /* Event Processing Status */ +# define URCSTS_HALTED (1 << 16) /* RC halted */ +# define URCSTS_HSE (1 << 10) /* Host System Error...fried */ +# define URCSTS_ER (1 << 9) /* Event Ready */ +# define URCSTS_RCI (1 << 8) /* Ready for Command Interrupt */ +# define URCSTS_INT_MASK 0x00000700 /* URC interrupt sources */ +# define URCSTS_ISI 0x000000ff /* Interrupt Source Identification */ +#define URCINTR 0x08 +# define URCINTR_EN_ALL 0x000007ff /* Enable all interrupt sources */ +#define URCCMDADDR 0x10 +#define URCEVTADDR 0x18 +# define URCEVTADDR_OFFSET_MASK 0xfff /* Event pointer offset mask */ + + +/** Write 32 bit @value to little endian register at @addr */ +static inline +void le_writel(u32 value, void __iomem *addr) +{ + iowrite32(value, addr); +} + + +/** Read from 32 bit little endian register at @addr */ +static inline +u32 le_readl(void __iomem *addr) +{ + return ioread32(addr); +} + + +/** Write 64 bit @value to little endian register at @addr */ +static inline +void le_writeq(u64 value, void __iomem *addr) +{ + iowrite32(value, addr); + iowrite32(value >> 32, addr + 4); +} + + +/** Read from 64 bit little endian register at @addr */ +static inline +u64 le_readq(void __iomem *addr) +{ + u64 value; + value = ioread32(addr); + value |= (u64)ioread32(addr + 4) << 32; + return value; +} + +extern int whci_wait_for(struct device *dev, u32 __iomem *reg, + u32 mask, u32 result, + unsigned long max_ms, const char *tag); + +#endif /* #ifndef _LINUX_UWB_WHCI_H_ */ -- cgit v1.2.3 From b6e069830c5927fd4d5fce67cb6440fddd10d429 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:14 +0100 Subject: uwb: add whc-rc radio control driver Add the driver for WHCI radio controllers. Signed-off-by: David Vrabel --- drivers/uwb/Kconfig | 20 ++ drivers/uwb/Makefile | 2 +- drivers/uwb/whc-rc.c | 528 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 drivers/uwb/whc-rc.c diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig index 3f01a69daf8..c0eb973111e 100644 --- a/drivers/uwb/Kconfig +++ b/drivers/uwb/Kconfig @@ -26,3 +26,23 @@ menuconfig UWB . To compile the UWB stack as a module, choose M here. + +if UWB + +config UWB_WHCI + tristate "UWB Radio Control driver for WHCI-compliant cards" + depends on PCI + help + This driver enables the radio controller for WHCI cards. + + WHCI is an specification developed by Intel + (http://www.intel.com/technology/comms/wusb/whci.htm) much + in the spirit of USB's EHCI, but for UWB and Wireless USB + radio/host controllers connected via memmory mapping (eg: + PCI). Most of these cards come also with a Wireless USB host + controller. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you do not have the hardware. + +endif # UWB diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile index b054471af28..bdcb494b00e 100644 --- a/drivers/uwb/Makefile +++ b/drivers/uwb/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_UWB) += uwb.o -obj-$(CONFIG_UWB_WHCI) += umc.o whci.o +obj-$(CONFIG_UWB_WHCI) += umc.o whci.o whc-rc.o uwb-objs := \ address.o \ diff --git a/drivers/uwb/whc-rc.c b/drivers/uwb/whc-rc.c new file mode 100644 index 00000000000..5a93abea6d2 --- /dev/null +++ b/drivers/uwb/whc-rc.c @@ -0,0 +1,528 @@ +/* + * Wireless Host Controller: Radio Control Interface (WHCI v0.95[2.3]) + * Radio Control command/event transport to the UWB stack + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Initialize and hook up the Radio Control interface. + * + * For each device probed, creates an 'struct whcrc' which contains + * just the representation of the UWB Radio Controller, and the logic + * for reading notifications and passing them to the UWB Core. + * + * So we initialize all of those, register the UWB Radio Controller + * and setup the notification/event handle to pipe the notifications + * to the UWB management Daemon. + * + * Once uwb_rc_add() is called, the UWB stack takes control, resets + * the radio and readies the device to take commands the UWB + * API/user-space. + * + * Note this driver is just a transport driver; the commands are + * formed at the UWB stack and given to this driver who will deliver + * them to the hw and transfer the replies/notifications back to the + * UWB stack through the UWB daemon (UWBD). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "uwb-internal.h" + +#define D_LOCAL 0 +#include + +/** + * Descriptor for an instance of the UWB Radio Control Driver that + * attaches to the URC interface of the WHCI PCI card. + * + * Unless there is a lock specific to the 'data members', all access + * is protected by uwb_rc->mutex. + */ +struct whcrc { + struct umc_dev *umc_dev; + struct uwb_rc *uwb_rc; /* UWB host controller */ + + unsigned long area; + void __iomem *rc_base; + size_t rc_len; + spinlock_t irq_lock; + + void *evt_buf, *cmd_buf; + dma_addr_t evt_dma_buf, cmd_dma_buf; + wait_queue_head_t cmd_wq; + struct work_struct event_work; +}; + +/** + * Execute an UWB RC command on WHCI/RC + * + * @rc: Instance of a Radio Controller that is a whcrc + * @cmd: Buffer containing the RCCB and payload to execute + * @cmd_size: Size of the command buffer. + * + * We copy the command into whcrc->cmd_buf (as it is pretty and + * aligned`and physically contiguous) and then press the right keys in + * the controller's URCCMD register to get it to read it. We might + * have to wait for the cmd_sem to be open to us. + * + * NOTE: rc's mutex has to be locked + */ +static int whcrc_cmd(struct uwb_rc *uwb_rc, + const struct uwb_rccb *cmd, size_t cmd_size) +{ + int result = 0; + struct whcrc *whcrc = uwb_rc->priv; + struct device *dev = &whcrc->umc_dev->dev; + u32 urccmd; + + d_fnstart(3, dev, "(%p, %p, %zu)\n", uwb_rc, cmd, cmd_size); + might_sleep(); + + if (cmd_size >= 4096) { + result = -E2BIG; + goto error; + } + + /* + * If the URC is halted, then the hardware has reset itself. + * Attempt to recover by restarting the device and then return + * an error as it's likely that the current command isn't + * valid for a newly started RC. + */ + if (le_readl(whcrc->rc_base + URCSTS) & URCSTS_HALTED) { + dev_err(dev, "requesting reset of halted radio controller\n"); + uwb_rc_reset_all(uwb_rc); + result = -EIO; + goto error; + } + + result = wait_event_timeout(whcrc->cmd_wq, + !(le_readl(whcrc->rc_base + URCCMD) & URCCMD_ACTIVE), HZ/2); + if (result == 0) { + dev_err(dev, "device is not ready to execute commands\n"); + result = -ETIMEDOUT; + goto error; + } + + memmove(whcrc->cmd_buf, cmd, cmd_size); + le_writeq(whcrc->cmd_dma_buf, whcrc->rc_base + URCCMDADDR); + + spin_lock(&whcrc->irq_lock); + urccmd = le_readl(whcrc->rc_base + URCCMD); + urccmd &= ~(URCCMD_EARV | URCCMD_SIZE_MASK); + le_writel(urccmd | URCCMD_ACTIVE | URCCMD_IWR | cmd_size, + whcrc->rc_base + URCCMD); + spin_unlock(&whcrc->irq_lock); + +error: + d_fnend(3, dev, "(%p, %p, %zu) = %d\n", + uwb_rc, cmd, cmd_size, result); + return result; +} + +static int whcrc_reset(struct uwb_rc *rc) +{ + struct whcrc *whcrc = rc->priv; + + return umc_controller_reset(whcrc->umc_dev); +} + +/** + * Reset event reception mechanism and tell hw we are ready to get more + * + * We have read all the events in the event buffer, so we are ready to + * reset it to the beginning. + * + * This is only called during initialization or after an event buffer + * has been retired. This means we can be sure that event processing + * is disabled and it's safe to update the URCEVTADDR register. + * + * There's no need to wait for the event processing to start as the + * URC will not clear URCCMD_ACTIVE until (internal) event buffer + * space is available. + */ +static +void whcrc_enable_events(struct whcrc *whcrc) +{ + struct device *dev = &whcrc->umc_dev->dev; + u32 urccmd; + + d_fnstart(4, dev, "(whcrc %p)\n", whcrc); + + le_writeq(whcrc->evt_dma_buf, whcrc->rc_base + URCEVTADDR); + + spin_lock(&whcrc->irq_lock); + urccmd = le_readl(whcrc->rc_base + URCCMD) & ~URCCMD_ACTIVE; + le_writel(urccmd | URCCMD_EARV, whcrc->rc_base + URCCMD); + spin_unlock(&whcrc->irq_lock); + + d_fnend(4, dev, "(whcrc %p) = void\n", whcrc); +} + +static void whcrc_event_work(struct work_struct *work) +{ + struct whcrc *whcrc = container_of(work, struct whcrc, event_work); + struct device *dev = &whcrc->umc_dev->dev; + size_t size; + u64 urcevtaddr; + + urcevtaddr = le_readq(whcrc->rc_base + URCEVTADDR); + size = urcevtaddr & URCEVTADDR_OFFSET_MASK; + + d_printf(3, dev, "received %zu octet event\n", size); + d_dump(4, dev, whcrc->evt_buf, size > 32 ? 32 : size); + + uwb_rc_neh_grok(whcrc->uwb_rc, whcrc->evt_buf, size); + whcrc_enable_events(whcrc); +} + +/** + * Catch interrupts? + * + * We ack inmediately (and expect the hw to do the right thing and + * raise another IRQ if things have changed :) + */ +static +irqreturn_t whcrc_irq_cb(int irq, void *_whcrc) +{ + struct whcrc *whcrc = _whcrc; + struct device *dev = &whcrc->umc_dev->dev; + u32 urcsts; + + d_fnstart(4, dev, "irq %d _whcrc %p)\n", irq, _whcrc); + urcsts = le_readl(whcrc->rc_base + URCSTS); + if (!(urcsts & URCSTS_INT_MASK)) + return IRQ_NONE; + le_writel(urcsts & URCSTS_INT_MASK, whcrc->rc_base + URCSTS); + + d_printf(4, dev, "acked 0x%08x, urcsts 0x%08x\n", + le_readl(whcrc->rc_base + URCSTS), urcsts); + + if (whcrc->uwb_rc == NULL) { + if (printk_ratelimit()) + dev_dbg(dev, "Received interrupt when not yet " + "ready!\n"); + goto out; + } + + if (urcsts & URCSTS_HSE) { + dev_err(dev, "host system error -- hardware halted\n"); + /* FIXME: do something sensible here */ + goto out; + } + if (urcsts & URCSTS_ER) { + d_printf(3, dev, "ER: event ready\n"); + schedule_work(&whcrc->event_work); + } + if (urcsts & URCSTS_RCI) { + d_printf(3, dev, "RCI: ready to execute another command\n"); + wake_up_all(&whcrc->cmd_wq); + } +out: + return IRQ_HANDLED; +} + + +/** + * Initialize a UMC RC interface: map regions, get (shared) IRQ + */ +static +int whcrc_setup_rc_umc(struct whcrc *whcrc) +{ + int result = 0; + struct device *dev = &whcrc->umc_dev->dev; + struct umc_dev *umc_dev = whcrc->umc_dev; + + whcrc->area = umc_dev->resource.start; + whcrc->rc_len = umc_dev->resource.end - umc_dev->resource.start + 1; + result = -EBUSY; + if (request_mem_region(whcrc->area, whcrc->rc_len, KBUILD_MODNAME) + == NULL) { + dev_err(dev, "can't request URC region (%zu bytes @ 0x%lx): %d\n", + whcrc->rc_len, whcrc->area, result); + goto error_request_region; + } + + whcrc->rc_base = ioremap_nocache(whcrc->area, whcrc->rc_len); + if (whcrc->rc_base == NULL) { + dev_err(dev, "can't ioremap registers (%zu bytes @ 0x%lx): %d\n", + whcrc->rc_len, whcrc->area, result); + goto error_ioremap_nocache; + } + + result = request_irq(umc_dev->irq, whcrc_irq_cb, IRQF_SHARED, + KBUILD_MODNAME, whcrc); + if (result < 0) { + dev_err(dev, "can't allocate IRQ %d: %d\n", + umc_dev->irq, result); + goto error_request_irq; + } + + result = -ENOMEM; + whcrc->cmd_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE, + &whcrc->cmd_dma_buf, GFP_KERNEL); + if (whcrc->cmd_buf == NULL) { + dev_err(dev, "Can't allocate cmd transfer buffer\n"); + goto error_cmd_buffer; + } + + whcrc->evt_buf = dma_alloc_coherent(&umc_dev->dev, PAGE_SIZE, + &whcrc->evt_dma_buf, GFP_KERNEL); + if (whcrc->evt_buf == NULL) { + dev_err(dev, "Can't allocate evt transfer buffer\n"); + goto error_evt_buffer; + } + d_printf(3, dev, "UWB RC Interface: %zu bytes at 0x%p, irq %u\n", + whcrc->rc_len, whcrc->rc_base, umc_dev->irq); + return 0; + +error_evt_buffer: + dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf, + whcrc->cmd_dma_buf); +error_cmd_buffer: + free_irq(umc_dev->irq, whcrc); +error_request_irq: + iounmap(whcrc->rc_base); +error_ioremap_nocache: + release_mem_region(whcrc->area, whcrc->rc_len); +error_request_region: + return result; +} + + +/** + * Release RC's UMC resources + */ +static +void whcrc_release_rc_umc(struct whcrc *whcrc) +{ + struct umc_dev *umc_dev = whcrc->umc_dev; + + dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->evt_buf, + whcrc->evt_dma_buf); + dma_free_coherent(&umc_dev->dev, PAGE_SIZE, whcrc->cmd_buf, + whcrc->cmd_dma_buf); + free_irq(umc_dev->irq, whcrc); + iounmap(whcrc->rc_base); + release_mem_region(whcrc->area, whcrc->rc_len); +} + + +/** + * whcrc_start_rc - start a WHCI radio controller + * @whcrc: the radio controller to start + * + * Reset the UMC device, start the radio controller, enable events and + * finally enable interrupts. + */ +static int whcrc_start_rc(struct uwb_rc *rc) +{ + struct whcrc *whcrc = rc->priv; + int result = 0; + struct device *dev = &whcrc->umc_dev->dev; + unsigned long start, duration; + + /* Reset the thing */ + le_writel(URCCMD_RESET, whcrc->rc_base + URCCMD); + if (d_test(3)) + start = jiffies; + if (whci_wait_for(dev, whcrc->rc_base + URCCMD, URCCMD_RESET, 0, + 5000, "device to reset at init") < 0) { + result = -EBUSY; + goto error; + } else if (d_test(3)) { + duration = jiffies - start; + if (duration > msecs_to_jiffies(40)) + dev_err(dev, "Device took %ums to " + "reset. MAX expected: 40ms\n", + jiffies_to_msecs(duration)); + } + + /* Set the event buffer, start the controller (enable IRQs later) */ + le_writel(0, whcrc->rc_base + URCINTR); + le_writel(URCCMD_RS, whcrc->rc_base + URCCMD); + result = -ETIMEDOUT; + if (d_test(3)) + start = jiffies; + if (whci_wait_for(dev, whcrc->rc_base + URCSTS, URCSTS_HALTED, 0, + 5000, "device to start") < 0) + goto error; + if (d_test(3)) { + duration = jiffies - start; + if (duration > msecs_to_jiffies(40)) + dev_err(dev, "Device took %ums to start. " + "MAX expected: 40ms\n", + jiffies_to_msecs(duration)); + } + whcrc_enable_events(whcrc); + result = 0; + le_writel(URCINTR_EN_ALL, whcrc->rc_base + URCINTR); +error: + return result; +} + + +/** + * whcrc_stop_rc - stop a WHCI radio controller + * @whcrc: the radio controller to stop + * + * Disable interrupts and cancel any pending event processing work + * before clearing the Run/Stop bit. + */ +static +void whcrc_stop_rc(struct uwb_rc *rc) +{ + struct whcrc *whcrc = rc->priv; + struct umc_dev *umc_dev = whcrc->umc_dev; + + le_writel(0, whcrc->rc_base + URCINTR); + cancel_work_sync(&whcrc->event_work); + + le_writel(0, whcrc->rc_base + URCCMD); + whci_wait_for(&umc_dev->dev, whcrc->rc_base + URCSTS, + URCSTS_HALTED, 0, 40, "URCSTS.HALTED"); +} + +static void whcrc_init(struct whcrc *whcrc) +{ + spin_lock_init(&whcrc->irq_lock); + init_waitqueue_head(&whcrc->cmd_wq); + INIT_WORK(&whcrc->event_work, whcrc_event_work); +} + +/** + * Initialize the radio controller. + * + * NOTE: we setup whcrc->uwb_rc before calling uwb_rc_add(); in the + * IRQ handler we use that to determine if the hw is ready to + * handle events. Looks like a race condition, but it really is + * not. + */ +static +int whcrc_probe(struct umc_dev *umc_dev) +{ + int result; + struct uwb_rc *uwb_rc; + struct whcrc *whcrc; + struct device *dev = &umc_dev->dev; + + d_fnstart(3, dev, "(umc_dev %p)\n", umc_dev); + result = -ENOMEM; + uwb_rc = uwb_rc_alloc(); + if (uwb_rc == NULL) { + dev_err(dev, "unable to allocate RC instance\n"); + goto error_rc_alloc; + } + whcrc = kzalloc(sizeof(*whcrc), GFP_KERNEL); + if (whcrc == NULL) { + dev_err(dev, "unable to allocate WHC-RC instance\n"); + goto error_alloc; + } + whcrc_init(whcrc); + whcrc->umc_dev = umc_dev; + + result = whcrc_setup_rc_umc(whcrc); + if (result < 0) { + dev_err(dev, "Can't setup RC UMC interface: %d\n", result); + goto error_setup_rc_umc; + } + whcrc->uwb_rc = uwb_rc; + + uwb_rc->owner = THIS_MODULE; + uwb_rc->cmd = whcrc_cmd; + uwb_rc->reset = whcrc_reset; + uwb_rc->start = whcrc_start_rc; + uwb_rc->stop = whcrc_stop_rc; + + result = uwb_rc_add(uwb_rc, dev, whcrc); + if (result < 0) + goto error_rc_add; + umc_set_drvdata(umc_dev, whcrc); + d_fnend(3, dev, "(umc_dev %p) = 0\n", umc_dev); + return 0; + +error_rc_add: + whcrc_release_rc_umc(whcrc); +error_setup_rc_umc: + kfree(whcrc); +error_alloc: + uwb_rc_put(uwb_rc); +error_rc_alloc: + d_fnend(3, dev, "(umc_dev %p) = %d\n", umc_dev, result); + return result; +} + +/** + * Clean up the radio control resources + * + * When we up the command semaphore, everybody possibly held trying to + * execute a command should be granted entry and then they'll see the + * host is quiescing and up it (so it will chain to the next waiter). + * This should not happen (in any case), as we can only remove when + * there are no handles open... + */ +static void whcrc_remove(struct umc_dev *umc_dev) +{ + struct whcrc *whcrc = umc_get_drvdata(umc_dev); + struct uwb_rc *uwb_rc = whcrc->uwb_rc; + + umc_set_drvdata(umc_dev, NULL); + uwb_rc_rm(uwb_rc); + whcrc_release_rc_umc(whcrc); + kfree(whcrc); + uwb_rc_put(uwb_rc); + d_printf(1, &umc_dev->dev, "freed whcrc %p\n", whcrc); +} + +/* PCI device ID's that we handle [so it gets loaded] */ +static struct pci_device_id whcrc_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { /* empty last entry */ } +}; +MODULE_DEVICE_TABLE(pci, whcrc_id_table); + +static struct umc_driver whcrc_driver = { + .name = "whc-rc", + .cap_id = UMC_CAP_ID_WHCI_RC, + .probe = whcrc_probe, + .remove = whcrc_remove, +}; + +static int __init whcrc_driver_init(void) +{ + return umc_driver_register(&whcrc_driver); +} +module_init(whcrc_driver_init); + +static void __exit whcrc_driver_exit(void) +{ + umc_driver_unregister(&whcrc_driver); +} +module_exit(whcrc_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless Host Controller Radio Control Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From de520b8bd5525d33e6a6f36b297836125736bd2a Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:15 +0100 Subject: uwb: add HWA radio controller driver Add a driver for USB-connected UWB radio controllers (HWAs). Signed-off-by: David Vrabel --- drivers/uwb/Kconfig | 12 + drivers/uwb/Makefile | 1 + drivers/uwb/hwa-rc.c | 911 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 924 insertions(+) create mode 100644 drivers/uwb/hwa-rc.c diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig index c0eb973111e..a442f39f354 100644 --- a/drivers/uwb/Kconfig +++ b/drivers/uwb/Kconfig @@ -29,6 +29,18 @@ menuconfig UWB if UWB +config UWB_HWA + tristate "UWB Radio Control driver for WUSB-compliant USB dongles (HWA)" + depends on USB + help + This driver enables the radio controller for HWA USB + devices. HWA stands for Host Wire Adapter, and it is a UWB + Radio Controller connected to your system via USB. Most of + them come with a Wireless USB host controller also. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you do not have the hardware. + config UWB_WHCI tristate "UWB Radio Control driver for WHCI-compliant cards" depends on PCI diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile index bdcb494b00e..6bdb8e7f91c 100644 --- a/drivers/uwb/Makefile +++ b/drivers/uwb/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_UWB) += uwb.o obj-$(CONFIG_UWB_WHCI) += umc.o whci.o whc-rc.o +obj-$(CONFIG_UWB_HWA) += hwa-rc.o uwb-objs := \ address.o \ diff --git a/drivers/uwb/hwa-rc.c b/drivers/uwb/hwa-rc.c new file mode 100644 index 00000000000..f822a1872e4 --- /dev/null +++ b/drivers/uwb/hwa-rc.c @@ -0,0 +1,911 @@ +/* + * WUSB Host Wire Adapter: Radio Control Interface (WUSB[8.6]) + * Radio Control command/event transport + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Initialize the Radio Control interface Driver. + * + * For each device probed, creates an 'struct hwarc' which contains + * just the representation of the UWB Radio Controller, and the logic + * for reading notifications and passing them to the UWB Core. + * + * So we initialize all of those, register the UWB Radio Controller + * and setup the notification/event handle to pipe the notifications + * to the UWB management Daemon. + * + * Command and event filtering. + * + * This is the driver for the Radio Control Interface described in WUSB + * 1.0. The core UWB module assumes that all drivers are compliant to the + * WHCI 0.95 specification. We thus create a filter that parses all + * incoming messages from the (WUSB 1.0) device and manipulate them to + * conform to the WHCI 0.95 specification. Similarly, outgoing messages + * are parsed and manipulated to conform to the WUSB 1.0 compliant messages + * that the device expects. Only a few messages are affected: + * Affected events: + * UWB_RC_EVT_BEACON + * UWB_RC_EVT_BP_SLOT_CHANGE + * UWB_RC_EVT_DRP_AVAIL + * UWB_RC_EVT_DRP + * Affected commands: + * UWB_RC_CMD_SCAN + * UWB_RC_CMD_SET_DRP_IE + * + * + * + */ +#include +#include +#include +#include +#include +#include +#include +#include "uwb-internal.h" +#define D_LOCAL 1 +#include + + +/** + * Descriptor for an instance of the UWB Radio Control Driver that + * attaches to the RCI interface of the Host Wired Adapter. + * + * Unless there is a lock specific to the 'data members', all access + * is protected by uwb_rc->mutex. + * + * The NEEP (Notification/Event EndPoint) URB (@neep_urb) writes to + * @rd_buffer. Note there is no locking because it is perfectly (heh!) + * serialized--probe() submits an URB, callback is called, processes + * the data (synchronously), submits another URB, and so on. There is + * no concurrent access to the buffer. + */ +struct hwarc { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + struct uwb_rc *uwb_rc; /* UWB host controller */ + struct urb *neep_urb; /* Notification endpoint handling */ + struct edc neep_edc; + void *rd_buffer; /* NEEP read buffer */ +}; + + +/* Beacon received notification (WUSB 1.0 [8.6.3.2]) */ +struct uwb_rc_evt_beacon_WUSB_0100 { + struct uwb_rceb rceb; + u8 bChannelNumber; + __le16 wBPSTOffset; + u8 bLQI; + u8 bRSSI; + __le16 wBeaconInfoLength; + u8 BeaconInfo[]; +} __attribute__((packed)); + +/** + * Filter WUSB 1.0 BEACON RCV notification to be WHCI 0.95 + * + * @header: the incoming event + * @buf_size: size of buffer containing incoming event + * @new_size: size of event after filtering completed + * + * The WHCI 0.95 spec has a "Beacon Type" field. This value is unknown at + * the time we receive the beacon from WUSB so we just set it to + * UWB_RC_BEACON_TYPE_NEIGHBOR as a default. + * The solution below allocates memory upon receipt of every beacon from a + * WUSB device. This will deteriorate performance. What is the right way to + * do this? + */ +static +int hwarc_filter_evt_beacon_WUSB_0100(struct uwb_rc *rc, + struct uwb_rceb **header, + const size_t buf_size, + size_t *new_size) +{ + struct uwb_rc_evt_beacon_WUSB_0100 *be; + struct uwb_rc_evt_beacon *newbe; + size_t bytes_left, ielength; + struct device *dev = &rc->uwb_dev.dev; + + be = container_of(*header, struct uwb_rc_evt_beacon_WUSB_0100, rceb); + bytes_left = buf_size; + if (bytes_left < sizeof(*be)) { + dev_err(dev, "Beacon Received Notification: Not enough data " + "to decode for filtering (%zu vs %zu bytes needed)\n", + bytes_left, sizeof(*be)); + return -EINVAL; + } + bytes_left -= sizeof(*be); + ielength = le16_to_cpu(be->wBeaconInfoLength); + if (bytes_left < ielength) { + dev_err(dev, "Beacon Received Notification: Not enough data " + "to decode IEs (%zu vs %zu bytes needed)\n", + bytes_left, ielength); + return -EINVAL; + } + newbe = kzalloc(sizeof(*newbe) + ielength, GFP_ATOMIC); + if (newbe == NULL) + return -ENOMEM; + newbe->rceb = be->rceb; + newbe->bChannelNumber = be->bChannelNumber; + newbe->bBeaconType = UWB_RC_BEACON_TYPE_NEIGHBOR; + newbe->wBPSTOffset = be->wBPSTOffset; + newbe->bLQI = be->bLQI; + newbe->bRSSI = be->bRSSI; + newbe->wBeaconInfoLength = be->wBeaconInfoLength; + memcpy(newbe->BeaconInfo, be->BeaconInfo, ielength); + *header = &newbe->rceb; + *new_size = sizeof(*newbe) + ielength; + return 1; /* calling function will free memory */ +} + + +/* DRP Availability change notification (WUSB 1.0 [8.6.3.8]) */ +struct uwb_rc_evt_drp_avail_WUSB_0100 { + struct uwb_rceb rceb; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/** + * Filter WUSB 1.0 DRP AVAILABILITY CHANGE notification to be WHCI 0.95 + * + * @header: the incoming event + * @buf_size: size of buffer containing incoming event + * @new_size: size of event after filtering completed + */ +static +int hwarc_filter_evt_drp_avail_WUSB_0100(struct uwb_rc *rc, + struct uwb_rceb **header, + const size_t buf_size, + size_t *new_size) +{ + struct uwb_rc_evt_drp_avail_WUSB_0100 *da; + struct uwb_rc_evt_drp_avail *newda; + struct uwb_ie_hdr *ie_hdr; + size_t bytes_left, ielength; + struct device *dev = &rc->uwb_dev.dev; + + + da = container_of(*header, struct uwb_rc_evt_drp_avail_WUSB_0100, rceb); + bytes_left = buf_size; + if (bytes_left < sizeof(*da)) { + dev_err(dev, "Not enough data to decode DRP Avail " + "Notification for filtering. Expected %zu, " + "received %zu.\n", (size_t)sizeof(*da), bytes_left); + return -EINVAL; + } + bytes_left -= sizeof(*da); + ielength = le16_to_cpu(da->wIELength); + if (bytes_left < ielength) { + dev_err(dev, "DRP Avail Notification filter: IE length " + "[%zu bytes] does not match actual length " + "[%zu bytes].\n", ielength, bytes_left); + return -EINVAL; + } + if (ielength < sizeof(*ie_hdr)) { + dev_err(dev, "DRP Avail Notification filter: Not enough " + "data to decode IE [%zu bytes, %zu needed]\n", + ielength, sizeof(*ie_hdr)); + return -EINVAL; + } + ie_hdr = (void *) da->IEData; + if (ie_hdr->length > 32) { + dev_err(dev, "DRP Availability Change event has unexpected " + "length for filtering. Expected < 32 bytes, " + "got %zu bytes.\n", (size_t)ie_hdr->length); + return -EINVAL; + } + newda = kzalloc(sizeof(*newda), GFP_ATOMIC); + if (newda == NULL) + return -ENOMEM; + newda->rceb = da->rceb; + memcpy(newda->bmp, (u8 *) ie_hdr + sizeof(*ie_hdr), ie_hdr->length); + *header = &newda->rceb; + *new_size = sizeof(*newda); + return 1; /* calling function will free memory */ +} + + +/* DRP notification (WUSB 1.0 [8.6.3.9]) */ +struct uwb_rc_evt_drp_WUSB_0100 { + struct uwb_rceb rceb; + struct uwb_dev_addr wSrcAddr; + u8 bExplicit; + __le16 wIELength; + u8 IEData[]; +} __attribute__((packed)); + +/** + * Filter WUSB 1.0 DRP Notification to be WHCI 0.95 + * + * @header: the incoming event + * @buf_size: size of buffer containing incoming event + * @new_size: size of event after filtering completed + * + * It is hard to manage DRP reservations without having a Reason code. + * Unfortunately there is none in the WUSB spec. We just set the default to + * DRP IE RECEIVED. + * We do not currently use the bBeaconSlotNumber value, so we set this to + * zero for now. + */ +static +int hwarc_filter_evt_drp_WUSB_0100(struct uwb_rc *rc, + struct uwb_rceb **header, + const size_t buf_size, + size_t *new_size) +{ + struct uwb_rc_evt_drp_WUSB_0100 *drpev; + struct uwb_rc_evt_drp *newdrpev; + size_t bytes_left, ielength; + struct device *dev = &rc->uwb_dev.dev; + + drpev = container_of(*header, struct uwb_rc_evt_drp_WUSB_0100, rceb); + bytes_left = buf_size; + if (bytes_left < sizeof(*drpev)) { + dev_err(dev, "Not enough data to decode DRP Notification " + "for filtering. Expected %zu, received %zu.\n", + (size_t)sizeof(*drpev), bytes_left); + return -EINVAL; + } + ielength = le16_to_cpu(drpev->wIELength); + bytes_left -= sizeof(*drpev); + if (bytes_left < ielength) { + dev_err(dev, "DRP Notification filter: header length [%zu " + "bytes] does not match actual length [%zu " + "bytes].\n", ielength, bytes_left); + return -EINVAL; + } + newdrpev = kzalloc(sizeof(*newdrpev) + ielength, GFP_ATOMIC); + if (newdrpev == NULL) + return -ENOMEM; + newdrpev->rceb = drpev->rceb; + newdrpev->src_addr = drpev->wSrcAddr; + newdrpev->reason = UWB_DRP_NOTIF_DRP_IE_RCVD; + newdrpev->beacon_slot_number = 0; + newdrpev->ie_length = drpev->wIELength; + memcpy(newdrpev->ie_data, drpev->IEData, ielength); + *header = &newdrpev->rceb; + *new_size = sizeof(*newdrpev) + ielength; + return 1; /* calling function will free memory */ +} + + +/* Scan Command (WUSB 1.0 [8.6.2.5]) */ +struct uwb_rc_cmd_scan_WUSB_0100 { + struct uwb_rccb rccb; + u8 bChannelNumber; + u8 bScanState; +} __attribute__((packed)); + +/** + * Filter WHCI 0.95 SCAN command to be WUSB 1.0 SCAN command + * + * @header: command sent to device (compliant to WHCI 0.95) + * @size: size of command sent to device + * + * We only reduce the size by two bytes because the WUSB 1.0 scan command + * does not have the last field (wStarttime). Also, make sure we don't send + * the device an unexpected scan type. + */ +static +int hwarc_filter_cmd_scan_WUSB_0100(struct uwb_rc *rc, + struct uwb_rccb **header, + size_t *size) +{ + struct uwb_rc_cmd_scan *sc; + + sc = container_of(*header, struct uwb_rc_cmd_scan, rccb); + + if (sc->bScanState == UWB_SCAN_ONLY_STARTTIME) + sc->bScanState = UWB_SCAN_ONLY; + /* Don't send the last two bytes. */ + *size -= 2; + return 0; +} + + +/* SET DRP IE command (WUSB 1.0 [8.6.2.7]) */ +struct uwb_rc_cmd_set_drp_ie_WUSB_0100 { + struct uwb_rccb rccb; + u8 bExplicit; + __le16 wIELength; + struct uwb_ie_drp IEData[]; +} __attribute__((packed)); + +/** + * Filter WHCI 0.95 SET DRP IE command to be WUSB 1.0 SET DRP IE command + * + * @header: command sent to device (compliant to WHCI 0.95) + * @size: size of command sent to device + * + * WUSB has an extra bExplicit field - we assume always explicit + * negotiation so this field is set. The command expected by the device is + * thus larger than the one prepared by the driver so we need to + * reallocate memory to accommodate this. + * We trust the driver to send us the correct data so no checking is done + * on incoming data - evn though it is variable length. + */ +static +int hwarc_filter_cmd_set_drp_ie_WUSB_0100(struct uwb_rc *rc, + struct uwb_rccb **header, + size_t *size) +{ + struct uwb_rc_cmd_set_drp_ie *orgcmd; + struct uwb_rc_cmd_set_drp_ie_WUSB_0100 *cmd; + size_t ielength; + + orgcmd = container_of(*header, struct uwb_rc_cmd_set_drp_ie, rccb); + ielength = le16_to_cpu(orgcmd->wIELength); + cmd = kzalloc(sizeof(*cmd) + ielength, GFP_KERNEL); + if (cmd == NULL) + return -ENOMEM; + cmd->rccb = orgcmd->rccb; + cmd->bExplicit = 0; + cmd->wIELength = orgcmd->wIELength; + memcpy(cmd->IEData, orgcmd->IEData, ielength); + *header = &cmd->rccb; + *size = sizeof(*cmd) + ielength; + return 1; /* calling function will free memory */ +} + + +/** + * Filter data from WHCI driver to WUSB device + * + * @header: WHCI 0.95 compliant command from driver + * @size: length of command + * + * The routine managing commands to the device (uwb_rc_cmd()) will call the + * filtering function pointer (if it exists) before it passes any data to + * the device. At this time the command has been formatted according to + * WHCI 0.95 and is ready to be sent to the device. + * + * The filter function will be provided with the current command and its + * length. The function will manipulate the command if necessary and + * potentially reallocate memory for a command that needed more memory that + * the given command. If new memory was created the function will return 1 + * to indicate to the calling function that the memory need to be freed + * when not needed any more. The size will contain the new length of the + * command. + * If memory has not been allocated we rely on the original mechanisms to + * free the memory of the command - even when we reduce the value of size. + */ +static +int hwarc_filter_cmd_WUSB_0100(struct uwb_rc *rc, struct uwb_rccb **header, + size_t *size) +{ + int result; + struct uwb_rccb *rccb = *header; + int cmd = le16_to_cpu(rccb->wCommand); + switch (cmd) { + case UWB_RC_CMD_SCAN: + result = hwarc_filter_cmd_scan_WUSB_0100(rc, header, size); + break; + case UWB_RC_CMD_SET_DRP_IE: + result = hwarc_filter_cmd_set_drp_ie_WUSB_0100(rc, header, size); + break; + default: + result = -ENOANO; + break; + } + return result; +} + + +/** + * Filter data from WHCI driver to WUSB device + * + * @header: WHCI 0.95 compliant command from driver + * @size: length of command + * + * Filter commands based on which protocol the device supports. The WUSB + * errata should be the same as WHCI 0.95 so we do not filter that here - + * only WUSB 1.0. + */ +static +int hwarc_filter_cmd(struct uwb_rc *rc, struct uwb_rccb **header, + size_t *size) +{ + int result = -ENOANO; + if (rc->version == 0x0100) + result = hwarc_filter_cmd_WUSB_0100(rc, header, size); + return result; +} + + +/** + * Compute return value as sum of incoming value and value at given offset + * + * @rceb: event for which we compute the size, it contains a variable + * length field. + * @core_size: size of the "non variable" part of the event + * @offset: place in event where the length of the variable part is stored + * @buf_size: total length of buffer in which event arrived - we need to make + * sure we read the offset in memory that is still part of the event + */ +static +ssize_t hwarc_get_event_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, + size_t core_size, size_t offset, + const size_t buf_size) +{ + ssize_t size = -ENOSPC; + const void *ptr = rceb; + size_t type_size = sizeof(__le16); + struct device *dev = &rc->uwb_dev.dev; + + if (offset + type_size >= buf_size) { + dev_err(dev, "Not enough data to read extra size of event " + "0x%02x/%04x/%02x, only got %zu bytes.\n", + rceb->bEventType, le16_to_cpu(rceb->wEvent), + rceb->bEventContext, buf_size); + goto out; + } + ptr += offset; + size = core_size + le16_to_cpu(*(__le16 *)ptr); +out: + return size; +} + + +/* Beacon slot change notification (WUSB 1.0 [8.6.3.5]) */ +struct uwb_rc_evt_bp_slot_change_WUSB_0100 { + struct uwb_rceb rceb; + u8 bSlotNumber; +} __attribute__((packed)); + + +/** + * Filter data from WUSB device to WHCI driver + * + * @header: incoming event + * @buf_size: size of buffer in which event arrived + * @_event_size: actual size of event in the buffer + * @new_size: size of event after filtered + * + * We don't know how the buffer is constructed - there may be more than one + * event in it so buffer length does not determine event length. We first + * determine the expected size of the incoming event. This value is passed + * back only if the actual filtering succeeded (so we know the computed + * expected size is correct). This value will be zero if + * the event did not need any filtering. + * + * WHCI interprets the BP Slot Change event's data differently than + * WUSB. The event sizes are exactly the same. The data field + * indicates the new beacon slot in which a RC is transmitting its + * beacon. The maximum value of this is 96 (wMacBPLength ECMA-368 + * 17.16 (Table 117)). We thus know that the WUSB value will not set + * the bit bNoSlot, so we don't really do anything (placeholder). + */ +static +int hwarc_filter_event_WUSB_0100(struct uwb_rc *rc, struct uwb_rceb **header, + const size_t buf_size, size_t *_real_size, + size_t *_new_size) +{ + int result = -ENOANO; + struct uwb_rceb *rceb = *header; + int event = le16_to_cpu(rceb->wEvent); + size_t event_size; + size_t core_size, offset; + + if (rceb->bEventType != UWB_RC_CET_GENERAL) + goto out; + switch (event) { + case UWB_RC_EVT_BEACON: + core_size = sizeof(struct uwb_rc_evt_beacon_WUSB_0100); + offset = offsetof(struct uwb_rc_evt_beacon_WUSB_0100, + wBeaconInfoLength); + event_size = hwarc_get_event_size(rc, rceb, core_size, + offset, buf_size); + if (event_size < 0) + goto out; + *_real_size = event_size; + result = hwarc_filter_evt_beacon_WUSB_0100(rc, header, + buf_size, _new_size); + break; + case UWB_RC_EVT_BP_SLOT_CHANGE: + *_new_size = *_real_size = + sizeof(struct uwb_rc_evt_bp_slot_change_WUSB_0100); + result = 0; + break; + + case UWB_RC_EVT_DRP_AVAIL: + core_size = sizeof(struct uwb_rc_evt_drp_avail_WUSB_0100); + offset = offsetof(struct uwb_rc_evt_drp_avail_WUSB_0100, + wIELength); + event_size = hwarc_get_event_size(rc, rceb, core_size, + offset, buf_size); + if (event_size < 0) + goto out; + *_real_size = event_size; + result = hwarc_filter_evt_drp_avail_WUSB_0100( + rc, header, buf_size, _new_size); + break; + + case UWB_RC_EVT_DRP: + core_size = sizeof(struct uwb_rc_evt_drp_WUSB_0100); + offset = offsetof(struct uwb_rc_evt_drp_WUSB_0100, wIELength); + event_size = hwarc_get_event_size(rc, rceb, core_size, + offset, buf_size); + if (event_size < 0) + goto out; + *_real_size = event_size; + result = hwarc_filter_evt_drp_WUSB_0100(rc, header, + buf_size, _new_size); + break; + + default: + break; + } +out: + return result; +} + +/** + * Filter data from WUSB device to WHCI driver + * + * @header: incoming event + * @buf_size: size of buffer in which event arrived + * @_event_size: actual size of event in the buffer + * @_new_size: size of event after filtered + * + * Filter events based on which protocol the device supports. The WUSB + * errata should be the same as WHCI 0.95 so we do not filter that here - + * only WUSB 1.0. + * + * If we don't handle it, we return -ENOANO (why the weird error code? + * well, so if I get it, I can pinpoint in the code that raised + * it...after all, not too many places use the higher error codes). + */ +static +int hwarc_filter_event(struct uwb_rc *rc, struct uwb_rceb **header, + const size_t buf_size, size_t *_real_size, + size_t *_new_size) +{ + int result = -ENOANO; + if (rc->version == 0x0100) + result = hwarc_filter_event_WUSB_0100( + rc, header, buf_size, _real_size, _new_size); + return result; +} + + +/** + * Execute an UWB RC command on HWA + * + * @rc: Instance of a Radio Controller that is a HWA + * @cmd: Buffer containing the RCCB and payload to execute + * @cmd_size: Size of the command buffer. + * + * NOTE: rc's mutex has to be locked + */ +static +int hwarc_cmd(struct uwb_rc *uwb_rc, const struct uwb_rccb *cmd, size_t cmd_size) +{ + struct hwarc *hwarc = uwb_rc->priv; + return usb_control_msg( + hwarc->usb_dev, usb_sndctrlpipe(hwarc->usb_dev, 0), + WA_EXEC_RC_CMD, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, hwarc->usb_iface->cur_altsetting->desc.bInterfaceNumber, + (void *) cmd, cmd_size, 100 /* FIXME: this is totally arbitrary */); +} + +static +int hwarc_reset(struct uwb_rc *uwb_rc) +{ + struct hwarc *hwarc = uwb_rc->priv; + return usb_reset_device(hwarc->usb_dev); +} + +/** + * Callback for the notification and event endpoint + * + * Check's that everything is fine and then passes the read data to + * the notification/event handling mechanism (neh). + */ +static +void hwarc_neep_cb(struct urb *urb) +{ + struct hwarc *hwarc = urb->context; + struct usb_interface *usb_iface = hwarc->usb_iface; + struct device *dev = &usb_iface->dev; + int result; + + switch (result = urb->status) { + case 0: + d_printf(3, dev, "NEEP: receive stat %d, %zu bytes\n", + urb->status, (size_t)urb->actual_length); + uwb_rc_neh_grok(hwarc->uwb_rc, urb->transfer_buffer, + urb->actual_length); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + d_printf(2, dev, "NEEP: URB reset/noent %d\n", urb->status); + goto out; + case -ESHUTDOWN: /* going away! */ + d_printf(2, dev, "NEEP: URB down %d\n", urb->status); + goto out; + default: /* On general errors, retry unless it gets ugly */ + if (edc_inc(&hwarc->neep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) + goto error_exceeded; + dev_err(dev, "NEEP: URB error %d\n", urb->status); + } + result = usb_submit_urb(urb, GFP_ATOMIC); + d_printf(3, dev, "NEEP: submit %d\n", result); + if (result < 0) { + dev_err(dev, "NEEP: Can't resubmit URB (%d) resetting device\n", + result); + goto error; + } +out: + return; + +error_exceeded: + dev_err(dev, "NEEP: URB max acceptable errors " + "exceeded, resetting device\n"); +error: + uwb_rc_neh_error(hwarc->uwb_rc, result); + uwb_rc_reset_all(hwarc->uwb_rc); + return; +} + +static void hwarc_init(struct hwarc *hwarc) +{ + edc_init(&hwarc->neep_edc); +} + +/** + * Initialize the notification/event endpoint stuff + * + * Note this is effectively a parallel thread; it knows that + * hwarc->uwb_rc always exists because the existence of a 'hwarc' + * means that there is a reverence on the hwarc->uwb_rc (see + * _probe()), and thus _neep_cb() can execute safely. + */ +static int hwarc_neep_init(struct uwb_rc *rc) +{ + struct hwarc *hwarc = rc->priv; + struct usb_interface *iface = hwarc->usb_iface; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct device *dev = &iface->dev; + int result; + struct usb_endpoint_descriptor *epd; + + epd = &iface->cur_altsetting->endpoint[0].desc; + hwarc->rd_buffer = (void *) __get_free_page(GFP_KERNEL); + if (hwarc->rd_buffer == NULL) { + dev_err(dev, "Unable to allocate notification's read buffer\n"); + goto error_rd_buffer; + } + hwarc->neep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (hwarc->neep_urb == NULL) { + dev_err(dev, "Unable to allocate notification URB\n"); + goto error_urb_alloc; + } + usb_fill_int_urb(hwarc->neep_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + hwarc->rd_buffer, PAGE_SIZE, + hwarc_neep_cb, hwarc, epd->bInterval); + result = usb_submit_urb(hwarc->neep_urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "Cannot submit notification URB: %d\n", result); + goto error_neep_submit; + } + return 0; + +error_neep_submit: + usb_free_urb(hwarc->neep_urb); +error_urb_alloc: + free_page((unsigned long)hwarc->rd_buffer); +error_rd_buffer: + return -ENOMEM; +} + + +/** Clean up all the notification endpoint resources */ +static void hwarc_neep_release(struct uwb_rc *rc) +{ + struct hwarc *hwarc = rc->priv; + + usb_kill_urb(hwarc->neep_urb); + usb_free_urb(hwarc->neep_urb); + free_page((unsigned long)hwarc->rd_buffer); +} + +/** + * Get the version from class-specific descriptor + * + * NOTE: this descriptor comes with the big bundled configuration + * descriptor that includes the interfaces' and endpoints', so + * we just look for it in the cached copy kept by the USB stack. + * + * NOTE2: We convert LE fields to CPU order. + */ +static int hwarc_get_version(struct uwb_rc *rc) +{ + int result; + + struct hwarc *hwarc = rc->priv; + struct uwb_rc_control_intf_class_desc *descr; + struct device *dev = &rc->uwb_dev.dev; + struct usb_device *usb_dev = hwarc->usb_dev; + char *itr; + struct usb_descriptor_header *hdr; + size_t itr_size, actconfig_idx; + u16 version; + + actconfig_idx = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[actconfig_idx]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + while (itr_size >= sizeof(*hdr)) { + hdr = (struct usb_descriptor_header *) itr; + d_printf(3, dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); + if (hdr->bDescriptorType == USB_DT_CS_RADIO_CONTROL) + goto found; + itr += hdr->bLength; + itr_size -= hdr->bLength; + } + dev_err(dev, "cannot find Radio Control Interface Class descriptor\n"); + return -ENODEV; + +found: + result = -EINVAL; + if (hdr->bLength > itr_size) { /* is it available? */ + dev_err(dev, "incomplete Radio Control Interface Class " + "descriptor (%zu bytes left, %u needed)\n", + itr_size, hdr->bLength); + goto error; + } + if (hdr->bLength < sizeof(*descr)) { + dev_err(dev, "short Radio Control Interface Class " + "descriptor\n"); + goto error; + } + descr = (struct uwb_rc_control_intf_class_desc *) hdr; + /* Make LE fields CPU order */ + version = __le16_to_cpu(descr->bcdRCIVersion); + if (version != 0x0100) { + dev_err(dev, "Device reports protocol version 0x%04x. We " + "do not support that. \n", version); + result = -EINVAL; + goto error; + } + rc->version = version; + d_printf(3, dev, "Device supports WUSB protocol version 0x%04x \n", + rc->version); + result = 0; +error: + return result; +} + +/* + * By creating a 'uwb_rc', we have a reference on it -- that reference + * is the one we drop when we disconnect. + * + * No need to switch altsettings; according to WUSB1.0[8.6.1.1], there + * is only one altsetting allowed. + */ +static int hwarc_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + int result; + struct uwb_rc *uwb_rc; + struct hwarc *hwarc; + struct device *dev = &iface->dev; + + result = -ENOMEM; + uwb_rc = uwb_rc_alloc(); + if (uwb_rc == NULL) { + dev_err(dev, "unable to allocate RC instance\n"); + goto error_rc_alloc; + } + hwarc = kzalloc(sizeof(*hwarc), GFP_KERNEL); + if (hwarc == NULL) { + dev_err(dev, "unable to allocate HWA RC instance\n"); + goto error_alloc; + } + hwarc_init(hwarc); + hwarc->usb_dev = usb_get_dev(interface_to_usbdev(iface)); + hwarc->usb_iface = usb_get_intf(iface); + hwarc->uwb_rc = uwb_rc; + + uwb_rc->owner = THIS_MODULE; + uwb_rc->start = hwarc_neep_init; + uwb_rc->stop = hwarc_neep_release; + uwb_rc->cmd = hwarc_cmd; + uwb_rc->reset = hwarc_reset; + uwb_rc->filter_cmd = hwarc_filter_cmd; + uwb_rc->filter_event = hwarc_filter_event; + + result = uwb_rc_add(uwb_rc, dev, hwarc); + if (result < 0) + goto error_rc_add; + result = hwarc_get_version(uwb_rc); + if (result < 0) { + dev_err(dev, "cannot retrieve version of RC \n"); + goto error_get_version; + } + usb_set_intfdata(iface, hwarc); + return 0; + +error_get_version: + uwb_rc_rm(uwb_rc); +error_rc_add: + usb_put_intf(iface); + usb_put_dev(hwarc->usb_dev); +error_alloc: + uwb_rc_put(uwb_rc); +error_rc_alloc: + return result; +} + +static void hwarc_disconnect(struct usb_interface *iface) +{ + struct hwarc *hwarc = usb_get_intfdata(iface); + struct uwb_rc *uwb_rc = hwarc->uwb_rc; + + usb_set_intfdata(hwarc->usb_iface, NULL); + uwb_rc_rm(uwb_rc); + usb_put_intf(hwarc->usb_iface); + usb_put_dev(hwarc->usb_dev); + d_printf(1, &hwarc->usb_iface->dev, "freed hwarc %p\n", hwarc); + kfree(hwarc); + uwb_rc_put(uwb_rc); /* when creating the device, refcount = 1 */ +} + +/** USB device ID's that we handle */ +static struct usb_device_id hwarc_id_table[] = { + { USB_INTERFACE_INFO(0xe0, 0x01, 0x02), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, hwarc_id_table); + +static struct usb_driver hwarc_driver = { + .name = "hwa-rc", + .probe = hwarc_probe, + .disconnect = hwarc_disconnect, + .id_table = hwarc_id_table, +}; + +static int __init hwarc_driver_init(void) +{ + int result; + result = usb_register(&hwarc_driver); + if (result < 0) + printk(KERN_ERR "HWA-RC: Cannot register USB driver: %d\n", + result); + return result; + +} +module_init(hwarc_driver_init); + +static void __exit hwarc_driver_exit(void) +{ + usb_deregister(&hwarc_driver); +} +module_exit(hwarc_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Host Wireless Adapter Radio Control Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From f51448543f8e4871f0539435fce42a14044f5652 Mon Sep 17 00:00:00 2001 From: Reinette Chatre Date: Wed, 17 Sep 2008 16:34:16 +0100 Subject: uwb: add the WiMedia LLC Protocol stack Add the generic code for the WiMedia Logical Link Control Protocol (WLP). This has been split into several patches for easier review. core (this patch): - everything else messages: - WLP message construction/decode wss: - Wireless Service Set support build-system: - Kconfig and Kbuild files Signed-off-by: David Vrabel --- drivers/uwb/wlp/driver.c | 43 +++ drivers/uwb/wlp/eda.c | 449 ++++++++++++++++++++++++++ drivers/uwb/wlp/sysfs.c | 709 +++++++++++++++++++++++++++++++++++++++++ drivers/uwb/wlp/txrx.c | 374 ++++++++++++++++++++++ drivers/uwb/wlp/wlp-internal.h | 228 +++++++++++++ drivers/uwb/wlp/wlp-lc.c | 585 ++++++++++++++++++++++++++++++++++ 6 files changed, 2388 insertions(+) create mode 100644 drivers/uwb/wlp/driver.c create mode 100644 drivers/uwb/wlp/eda.c create mode 100644 drivers/uwb/wlp/sysfs.c create mode 100644 drivers/uwb/wlp/txrx.c create mode 100644 drivers/uwb/wlp/wlp-internal.h create mode 100644 drivers/uwb/wlp/wlp-lc.c diff --git a/drivers/uwb/wlp/driver.c b/drivers/uwb/wlp/driver.c new file mode 100644 index 00000000000..cb8d699b6a6 --- /dev/null +++ b/drivers/uwb/wlp/driver.c @@ -0,0 +1,43 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Life cycle of WLP substack + * + * FIXME: Docs + */ + +#include + +static int __init wlp_subsys_init(void) +{ + return 0; +} +module_init(wlp_subsys_init); + +static void __exit wlp_subsys_exit(void) +{ + return; +} +module_exit(wlp_subsys_exit); + +MODULE_AUTHOR("Reinette Chatre "); +MODULE_DESCRIPTION("WiMedia Logical Link Control Protocol (WLP)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/wlp/eda.c b/drivers/uwb/wlp/eda.c new file mode 100644 index 00000000000..cdfe8dfc434 --- /dev/null +++ b/drivers/uwb/wlp/eda.c @@ -0,0 +1,449 @@ +/* + * WUSB Wire Adapter: WLP interface + * Ethernet to device address cache + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We need to be able to map ethernet addresses to device addresses + * and back because there is not explicit relationship between the eth + * addresses used in the ETH frames and the device addresses (no, it + * would not have been simpler to force as ETH address the MBOA MAC + * address...no, not at all :). + * + * A device has one MBOA MAC address and one device address. It is possible + * for a device to have more than one virtual MAC address (although a + * virtual address can be the same as the MBOA MAC address). The device + * address is guaranteed to be unique among the devices in the extended + * beacon group (see ECMA 17.1.1). We thus use the device address as index + * to this cache. We do allow searching based on virtual address as this + * is how Ethernet frames will be addressed. + * + * We need to support virtual EUI-48. Although, right now the virtual + * EUI-48 will always be the same as the MAC SAP address. The EDA cache + * entry thus contains a MAC SAP address as well as the virtual address + * (used to map the network stack address to a neighbor). When we move + * to support more than one virtual MAC on a host then this organization + * will have to change. Perhaps a neighbor has a list of WSSs, each with a + * tag and virtual EUI-48. + * + * On data transmission + * it is used to determine if the neighbor is connected and what WSS it + * belongs to. With this we know what tag to add to the WLP frame. Storing + * the WSS in the EDA cache may be overkill because we only support one + * WSS. Hopefully we will support more than one WSS at some point. + * On data reception it is used to determine the WSS based on + * the tag and address of the transmitting neighbor. + */ + +#define D_LOCAL 5 +#include +#include +#include +#include +#include "wlp-internal.h" + + +/* FIXME: cache is not purged, only on device close */ + +/* FIXME: does not scale, change to dynamic array */ + +/* + * Initialize the EDA cache + * + * @returns 0 if ok, < 0 errno code on error + * + * Call when the interface is being brought up + * + * NOTE: Keep it as a separate function as the implementation will + * change and be more complex. + */ +void wlp_eda_init(struct wlp_eda *eda) +{ + INIT_LIST_HEAD(&eda->cache); + spin_lock_init(&eda->lock); +} + +/* + * Release the EDA cache + * + * @returns 0 if ok, < 0 errno code on error + * + * Called when the interface is brought down + */ +void wlp_eda_release(struct wlp_eda *eda) +{ + unsigned long flags; + struct wlp_eda_node *itr, *next; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry_safe(itr, next, &eda->cache, list_node) { + list_del(&itr->list_node); + kfree(itr); + } + spin_unlock_irqrestore(&eda->lock, flags); +} + +/* + * Add an address mapping + * + * @returns 0 if ok, < 0 errno code on error + * + * An address mapping is initially created when the neighbor device is seen + * for the first time (it is "onair"). At this time the neighbor is not + * connected or associated with a WSS so we only populate the Ethernet and + * Device address fields. + * + */ +int wlp_eda_create_node(struct wlp_eda *eda, + const unsigned char eth_addr[ETH_ALEN], + const struct uwb_dev_addr *dev_addr) +{ + int result = 0; + struct wlp_eda_node *itr; + unsigned long flags; + + BUG_ON(dev_addr == NULL || eth_addr == NULL); + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + printk(KERN_ERR "EDA cache already contains entry " + "for neighbor %02x:%02x\n", + dev_addr->data[1], dev_addr->data[0]); + result = -EEXIST; + goto out_unlock; + } + } + itr = kzalloc(sizeof(*itr), GFP_ATOMIC); + if (itr != NULL) { + memcpy(itr->eth_addr, eth_addr, sizeof(itr->eth_addr)); + itr->dev_addr = *dev_addr; + list_add(&itr->list_node, &eda->cache); + } else + result = -ENOMEM; +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Remove entry from EDA cache + * + * This is done when the device goes off air. + */ +void wlp_eda_rm_node(struct wlp_eda *eda, const struct uwb_dev_addr *dev_addr) +{ + struct wlp_eda_node *itr, *next; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry_safe(itr, next, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + list_del(&itr->list_node); + kfree(itr); + break; + } + } + spin_unlock_irqrestore(&eda->lock, flags); +} + +/* + * Update an address mapping + * + * @returns 0 if ok, < 0 errno code on error + */ +int wlp_eda_update_node(struct wlp_eda *eda, + const struct uwb_dev_addr *dev_addr, + struct wlp_wss *wss, + const unsigned char virt_addr[ETH_ALEN], + const u8 tag, const enum wlp_wss_connect state) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + /* Found it, update it */ + itr->wss = wss; + memcpy(itr->virt_addr, virt_addr, + sizeof(itr->virt_addr)); + itr->tag = tag; + itr->state = state; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Update only state field of an address mapping + * + * @returns 0 if ok, < 0 errno code on error + */ +int wlp_eda_update_node_state(struct wlp_eda *eda, + const struct uwb_dev_addr *dev_addr, + const enum wlp_wss_connect state) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + /* Found it, update it */ + itr->state = state; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Return contents of EDA cache entry + * + * @dev_addr: index to EDA cache + * @eda_entry: pointer to where contents of EDA cache will be copied + */ +int wlp_copy_eda_node(struct wlp_eda *eda, struct uwb_dev_addr *dev_addr, + struct wlp_eda_node *eda_entry) +{ + int result = -ENOENT; + struct wlp_eda_node *itr; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(&itr->dev_addr, dev_addr, sizeof(itr->dev_addr))) { + *eda_entry = *itr; + result = 0; + goto out_unlock; + } + } + /* Not found */ +out_unlock: + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Execute function for every element in the cache + * + * @function: function to execute on element of cache (must be atomic) + * @priv: private data of function + * @returns: result of first function that failed, or last function + * executed if no function failed. + * + * Stop executing when function returns error for any element in cache. + * + * IMPORTANT: We are using a spinlock here: the function executed on each + * element has to be atomic. + */ +int wlp_eda_for_each(struct wlp_eda *eda, wlp_eda_for_each_f function, + void *priv) +{ + int result = 0; + struct wlp *wlp = container_of(eda, struct wlp, eda); + struct wlp_eda_node *entry; + unsigned long flags; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(entry, &eda->cache, list_node) { + result = (*function)(wlp, entry, priv); + if (result < 0) + break; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +/* + * Execute function for single element in the cache (return dev addr) + * + * @virt_addr: index into EDA cache used to determine which element to + * execute the function on + * @dev_addr: device address of element in cache will be returned using + * @dev_addr + * @function: function to execute on element of cache (must be atomic) + * @priv: private data of function + * @returns: result of function + * + * IMPORTANT: We are using a spinlock here: the function executed on the + * element has to be atomic. + */ +int wlp_eda_for_virtual(struct wlp_eda *eda, + const unsigned char virt_addr[ETH_ALEN], + struct uwb_dev_addr *dev_addr, + wlp_eda_for_each_f function, + void *priv) +{ + int result = 0; + struct wlp *wlp = container_of(eda, struct wlp, eda); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_eda_node *itr; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&eda->lock, flags); + list_for_each_entry(itr, &eda->cache, list_node) { + if (!memcmp(itr->virt_addr, virt_addr, + sizeof(itr->virt_addr))) { + d_printf(6, dev, "EDA: looking for " + "%02x:%02x:%02x:%02x:%02x:%02x hit %02x:%02x " + "wss %p tag 0x%02x state %u\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5], + itr->dev_addr.data[1], + itr->dev_addr.data[0], itr->wss, + itr->tag, itr->state); + result = (*function)(wlp, itr, priv); + *dev_addr = itr->dev_addr; + found = 1; + break; + } else + d_printf(6, dev, "EDA: looking for " + "%02x:%02x:%02x:%02x:%02x:%02x " + "against " + "%02x:%02x:%02x:%02x:%02x:%02x miss\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5], + itr->virt_addr[0], itr->virt_addr[1], + itr->virt_addr[2], itr->virt_addr[3], + itr->virt_addr[4], itr->virt_addr[5]); + } + if (!found) { + if (printk_ratelimit()) + dev_err(dev, "EDA: Eth addr %02x:%02x:%02x" + ":%02x:%02x:%02x not found.\n", + virt_addr[0], virt_addr[1], + virt_addr[2], virt_addr[3], + virt_addr[4], virt_addr[5]); + result = -ENODEV; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} + +static const char *__wlp_wss_connect_state[] = { "WLP_WSS_UNCONNECTED", + "WLP_WSS_CONNECTED", + "WLP_WSS_CONNECT_FAILED", +}; + +static const char *wlp_wss_connect_state_str(unsigned id) +{ + if (id >= ARRAY_SIZE(__wlp_wss_connect_state)) + return "unknown WSS connection state"; + return __wlp_wss_connect_state[id]; +} + +/* + * View EDA cache from user space + * + * A debugging feature to give user visibility into the EDA cache. Also + * used to display members of WSS to user (called from wlp_wss_members_show()) + */ +ssize_t wlp_eda_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + struct wlp_eda_node *entry; + unsigned long flags; + struct wlp_eda *eda = &wlp->eda; + spin_lock_irqsave(&eda->lock, flags); + result = scnprintf(buf, PAGE_SIZE, "#eth_addr dev_addr wss_ptr " + "tag state virt_addr\n"); + list_for_each_entry(entry, &eda->cache, list_node) { + result += scnprintf(buf + result, PAGE_SIZE - result, + "%02x:%02x:%02x:%02x:%02x:%02x %02x:%02x " + "%p 0x%02x %s " + "%02x:%02x:%02x:%02x:%02x:%02x\n", + entry->eth_addr[0], entry->eth_addr[1], + entry->eth_addr[2], entry->eth_addr[3], + entry->eth_addr[4], entry->eth_addr[5], + entry->dev_addr.data[1], + entry->dev_addr.data[0], entry->wss, + entry->tag, + wlp_wss_connect_state_str(entry->state), + entry->virt_addr[0], entry->virt_addr[1], + entry->virt_addr[2], entry->virt_addr[3], + entry->virt_addr[4], entry->virt_addr[5]); + if (result >= PAGE_SIZE) + break; + } + spin_unlock_irqrestore(&eda->lock, flags); + return result; +} +EXPORT_SYMBOL_GPL(wlp_eda_show); + +/* + * Add new EDA cache entry based on user input in sysfs + * + * Should only be used for debugging. + * + * The WSS is assumed to be the only WSS supported. This needs to be + * redesigned when we support more than one WSS. + */ +ssize_t wlp_eda_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + struct wlp_eda *eda = &wlp->eda; + u8 eth_addr[6]; + struct uwb_dev_addr dev_addr; + u8 tag; + unsigned state; + + result = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx " + "%02hhx:%02hhx %02hhx %u\n", + ð_addr[0], ð_addr[1], + ð_addr[2], ð_addr[3], + ð_addr[4], ð_addr[5], + &dev_addr.data[1], &dev_addr.data[0], &tag, &state); + switch (result) { + case 6: /* no dev addr specified -- remove entry NOT IMPLEMENTED */ + /*result = wlp_eda_rm(eda, eth_addr, &dev_addr);*/ + result = -ENOSYS; + break; + case 10: + state = state >= 1 ? 1 : 0; + result = wlp_eda_create_node(eda, eth_addr, &dev_addr); + if (result < 0 && result != -EEXIST) + goto error; + /* Set virtual addr to be same as MAC */ + result = wlp_eda_update_node(eda, &dev_addr, &wlp->wss, + eth_addr, tag, state); + if (result < 0) + goto error; + break; + default: /* bad format */ + result = -EINVAL; + } +error: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_eda_store); diff --git a/drivers/uwb/wlp/sysfs.c b/drivers/uwb/wlp/sysfs.c new file mode 100644 index 00000000000..1bb9b1f97d4 --- /dev/null +++ b/drivers/uwb/wlp/sysfs.c @@ -0,0 +1,709 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * sysfs functions + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: Docs + * + */ + +#include +#include "wlp-internal.h" + +static +size_t wlp_wss_wssid_e_print(char *buf, size_t bufsize, + struct wlp_wssid_e *wssid_e) +{ + size_t used = 0; + used += scnprintf(buf, bufsize, " WSS: "); + used += wlp_wss_uuid_print(buf + used, bufsize - used, + &wssid_e->wssid); + + if (wssid_e->info != NULL) { + used += scnprintf(buf + used, bufsize - used, " "); + used += uwb_mac_addr_print(buf + used, bufsize - used, + &wssid_e->info->bcast); + used += scnprintf(buf + used, bufsize - used, " %u %u %s\n", + wssid_e->info->accept_enroll, + wssid_e->info->sec_status, + wssid_e->info->name); + } + return used; +} + +/** + * Print out information learned from neighbor discovery + * + * Some fields being printed may not be included in the device discovery + * information (it is not mandatory). We are thus careful how the + * information is printed to ensure it is clear to the user what field is + * being referenced. + * The information being printed is for one time use - temporary storage is + * cleaned after it is printed. + * + * Ideally sysfs output should be on one line. The information printed here + * contain a few strings so it will be hard to parse if they are all + * printed on the same line - without agreeing on a standard field + * separator. + */ +static +ssize_t wlp_wss_neighborhood_print_remove(struct wlp *wlp, char *buf, + size_t bufsize) +{ + size_t used = 0; + struct wlp_neighbor_e *neighb; + struct wlp_wssid_e *wssid_e; + + mutex_lock(&wlp->nbmutex); + used = scnprintf(buf, bufsize, "#Neighbor information\n" + "#uuid dev_addr\n" + "# Device Name:\n# Model Name:\n# Manufacturer:\n" + "# Model Nr:\n# Serial:\n" + "# Pri Dev type: CategoryID OUI OUISubdiv " + "SubcategoryID\n" + "# WSS: WSSID WSS_name accept_enroll sec_status " + "bcast\n" + "# WSS: WSSID WSS_name accept_enroll sec_status " + "bcast\n\n"); + list_for_each_entry(neighb, &wlp->neighbors, node) { + if (bufsize - used <= 0) + goto out; + used += wlp_wss_uuid_print(buf + used, bufsize - used, + &neighb->uuid); + buf[used++] = ' '; + used += uwb_dev_addr_print(buf + used, bufsize - used, + &neighb->uwb_dev->dev_addr); + if (neighb->info != NULL) + used += scnprintf(buf + used, bufsize - used, + "\n Device Name: %s\n" + " Model Name: %s\n" + " Manufacturer:%s \n" + " Model Nr: %s\n" + " Serial: %s\n" + " Pri Dev type: " + "%u %02x:%02x:%02x %u %u\n", + neighb->info->name, + neighb->info->model_name, + neighb->info->manufacturer, + neighb->info->model_nr, + neighb->info->serial, + neighb->info->prim_dev_type.category, + neighb->info->prim_dev_type.OUI[0], + neighb->info->prim_dev_type.OUI[1], + neighb->info->prim_dev_type.OUI[2], + neighb->info->prim_dev_type.OUIsubdiv, + neighb->info->prim_dev_type.subID); + list_for_each_entry(wssid_e, &neighb->wssid, node) { + used += wlp_wss_wssid_e_print(buf + used, + bufsize - used, + wssid_e); + } + buf[used++] = '\n'; + wlp_remove_neighbor_tmp_info(neighb); + } + + +out: + mutex_unlock(&wlp->nbmutex); + return used; +} + + +/** + * Show properties of all WSS in neighborhood. + * + * Will trigger a complete discovery of WSS activated by this device and + * its neighbors. + */ +ssize_t wlp_neighborhood_show(struct wlp *wlp, char *buf) +{ + wlp_discover(wlp); + return wlp_wss_neighborhood_print_remove(wlp, buf, PAGE_SIZE); +} +EXPORT_SYMBOL_GPL(wlp_neighborhood_show); + +static +ssize_t __wlp_wss_properties_show(struct wlp_wss *wss, char *buf, + size_t bufsize) +{ + ssize_t result; + + result = wlp_wss_uuid_print(buf, bufsize, &wss->wssid); + result += scnprintf(buf + result, bufsize - result, " "); + result += uwb_mac_addr_print(buf + result, bufsize - result, + &wss->bcast); + result += scnprintf(buf + result, bufsize - result, + " 0x%02x %u ", wss->hash, wss->secure_status); + result += wlp_wss_key_print(buf + result, bufsize - result, + wss->master_key); + result += scnprintf(buf + result, bufsize - result, " 0x%02x ", + wss->tag); + result += uwb_mac_addr_print(buf + result, bufsize - result, + &wss->virtual_addr); + result += scnprintf(buf + result, bufsize - result, " %s", wss->name); + result += scnprintf(buf + result, bufsize - result, + "\n\n#WSSID\n#WSS broadcast address\n" + "#WSS hash\n#WSS secure status\n" + "#WSS master key\n#WSS local tag\n" + "#WSS local virtual EUI-48\n#WSS name\n"); + return result; +} + +/** + * Show which WSS is activated. + */ +ssize_t wlp_wss_activate_show(struct wlp_wss *wss, char *buf) +{ + int result = 0; + + if (mutex_lock_interruptible(&wss->mutex)) + goto out; + if (wss->state >= WLP_WSS_STATE_ACTIVE) + result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE); + else + result = scnprintf(buf, PAGE_SIZE, "No local WSS active.\n"); + result += scnprintf(buf + result, PAGE_SIZE - result, + "\n\n" + "# echo WSSID SECURE_STATUS ACCEPT_ENROLLMENT " + "NAME #create new WSS\n" + "# echo WSSID [DEV ADDR] #enroll in and activate " + "existing WSS, can request registrar\n" + "#\n" + "# WSSID is a 16 byte hex array. Eg. 12 A3 3B ... \n" + "# SECURE_STATUS 0 - unsecure, 1 - secure (default)\n" + "# ACCEPT_ENROLLMENT 0 - no, 1 - yes (default)\n" + "# NAME is the text string identifying the WSS\n" + "# DEV ADDR is the device address of neighbor " + "that should be registrar. Eg. 32:AB\n"); + + mutex_unlock(&wss->mutex); +out: + return result; + +} +EXPORT_SYMBOL_GPL(wlp_wss_activate_show); + +/** + * Create/activate a new WSS or enroll/activate in neighboring WSS + * + * The user can provide the WSSID of a WSS in which it wants to enroll. + * Only the WSSID is necessary if the WSS have been discovered before. If + * the WSS has not been discovered before, or the user wants to use a + * particular neighbor as its registrar, then the user can also provide a + * device address or the neighbor that will be used as registrar. + * + * A new WSS is created when the user provides a WSSID, secure status, and + * WSS name. + */ +ssize_t wlp_wss_activate_store(struct wlp_wss *wss, + const char *buf, size_t size) +{ + ssize_t result = -EINVAL; + struct wlp_uuid wssid; + struct uwb_dev_addr dev; + struct uwb_dev_addr bcast = {.data = {0xff, 0xff} }; + char name[65]; + unsigned sec_status, accept; + memset(name, 0, sizeof(name)); + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx:%02hhx", + &wssid.data[0] , &wssid.data[1], + &wssid.data[2] , &wssid.data[3], + &wssid.data[4] , &wssid.data[5], + &wssid.data[6] , &wssid.data[7], + &wssid.data[8] , &wssid.data[9], + &wssid.data[10], &wssid.data[11], + &wssid.data[12], &wssid.data[13], + &wssid.data[14], &wssid.data[15], + &dev.data[1], &dev.data[0]); + if (result == 16 || result == 17) { + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%u %u %64c", + &wssid.data[0] , &wssid.data[1], + &wssid.data[2] , &wssid.data[3], + &wssid.data[4] , &wssid.data[5], + &wssid.data[6] , &wssid.data[7], + &wssid.data[8] , &wssid.data[9], + &wssid.data[10], &wssid.data[11], + &wssid.data[12], &wssid.data[13], + &wssid.data[14], &wssid.data[15], + &sec_status, &accept, name); + if (result == 16) + result = wlp_wss_enroll_activate(wss, &wssid, &bcast); + else if (result == 19) { + sec_status = sec_status == 0 ? 0 : 1; + accept = accept == 0 ? 0 : 1; + /* We read name using %c, so the newline needs to be + * removed */ + if (strlen(name) != sizeof(name) - 1) + name[strlen(name) - 1] = '\0'; + result = wlp_wss_create_activate(wss, &wssid, name, + sec_status, accept); + } else + result = -EINVAL; + } else if (result == 18) + result = wlp_wss_enroll_activate(wss, &wssid, &dev); + else + result = -EINVAL; + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_wss_activate_store); + +/** + * Show the UUID of this host + */ +ssize_t wlp_uuid_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + + mutex_lock(&wlp->mutex); + result = wlp_wss_uuid_print(buf, PAGE_SIZE, &wlp->uuid); + buf[result++] = '\n'; + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_uuid_show); + +/** + * Store a new UUID for this host + * + * According to the spec this should be encoded as an octet string in the + * order the octets are shown in string representation in RFC 4122 (WLP + * 0.99 [Table 6]) + * + * We do not check value provided by user. + */ +ssize_t wlp_uuid_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + struct wlp_uuid uuid; + + mutex_lock(&wlp->mutex); + result = sscanf(buf, "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx ", + &uuid.data[0] , &uuid.data[1], + &uuid.data[2] , &uuid.data[3], + &uuid.data[4] , &uuid.data[5], + &uuid.data[6] , &uuid.data[7], + &uuid.data[8] , &uuid.data[9], + &uuid.data[10], &uuid.data[11], + &uuid.data[12], &uuid.data[13], + &uuid.data[14], &uuid.data[15]); + if (result != 16) { + result = -EINVAL; + goto error; + } + wlp->uuid = uuid; +error: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_uuid_store); + +/** + * Show contents of members of device information structure + */ +#define wlp_dev_info_show(type) \ +ssize_t wlp_dev_##type##_show(struct wlp *wlp, char *buf) \ +{ \ + ssize_t result = 0; \ + mutex_lock(&wlp->mutex); \ + if (wlp->dev_info == NULL) { \ + result = __wlp_setup_device_info(wlp); \ + if (result < 0) \ + goto out; \ + } \ + result = scnprintf(buf, PAGE_SIZE, "%s\n", wlp->dev_info->type);\ +out: \ + mutex_unlock(&wlp->mutex); \ + return result; \ +} \ +EXPORT_SYMBOL_GPL(wlp_dev_##type##_show); + +wlp_dev_info_show(name) +wlp_dev_info_show(model_name) +wlp_dev_info_show(model_nr) +wlp_dev_info_show(manufacturer) +wlp_dev_info_show(serial) + +/** + * Store contents of members of device information structure + */ +#define wlp_dev_info_store(type, len) \ +ssize_t wlp_dev_##type##_store(struct wlp *wlp, const char *buf, size_t size)\ +{ \ + ssize_t result; \ + char format[10]; \ + mutex_lock(&wlp->mutex); \ + if (wlp->dev_info == NULL) { \ + result = __wlp_alloc_device_info(wlp); \ + if (result < 0) \ + goto out; \ + } \ + memset(wlp->dev_info->type, 0, sizeof(wlp->dev_info->type)); \ + sprintf(format, "%%%uc", len); \ + result = sscanf(buf, format, wlp->dev_info->type); \ +out: \ + mutex_unlock(&wlp->mutex); \ + return result < 0 ? result : size; \ +} \ +EXPORT_SYMBOL_GPL(wlp_dev_##type##_store); + +wlp_dev_info_store(name, 32) +wlp_dev_info_store(manufacturer, 64) +wlp_dev_info_store(model_name, 32) +wlp_dev_info_store(model_nr, 32) +wlp_dev_info_store(serial, 32) + +static +const char *__wlp_dev_category[] = { + [WLP_DEV_CAT_COMPUTER] = "Computer", + [WLP_DEV_CAT_INPUT] = "Input device", + [WLP_DEV_CAT_PRINT_SCAN_FAX_COPIER] = "Printer, scanner, FAX, or " + "Copier", + [WLP_DEV_CAT_CAMERA] = "Camera", + [WLP_DEV_CAT_STORAGE] = "Storage Network", + [WLP_DEV_CAT_INFRASTRUCTURE] = "Infrastructure", + [WLP_DEV_CAT_DISPLAY] = "Display", + [WLP_DEV_CAT_MULTIM] = "Multimedia device", + [WLP_DEV_CAT_GAMING] = "Gaming device", + [WLP_DEV_CAT_TELEPHONE] = "Telephone", + [WLP_DEV_CAT_OTHER] = "Other", +}; + +static +const char *wlp_dev_category_str(unsigned cat) +{ + if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) + || cat == WLP_DEV_CAT_OTHER) + return __wlp_dev_category[cat]; + return "unknown category"; +} + +ssize_t wlp_dev_prim_category_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%s\n", + wlp_dev_category_str(wlp->dev_info->prim_dev_type.category)); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_category_show); + +ssize_t wlp_dev_prim_category_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + u16 cat; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%hu", &cat); + if ((cat >= WLP_DEV_CAT_COMPUTER && cat <= WLP_DEV_CAT_TELEPHONE) + || cat == WLP_DEV_CAT_OTHER) + wlp->dev_info->prim_dev_type.category = cat; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_category_store); + +ssize_t wlp_dev_prim_OUI_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%02x:%02x:%02x\n", + wlp->dev_info->prim_dev_type.OUI[0], + wlp->dev_info->prim_dev_type.OUI[1], + wlp->dev_info->prim_dev_type.OUI[2]); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_show); + +ssize_t wlp_dev_prim_OUI_store(struct wlp *wlp, const char *buf, size_t size) +{ + ssize_t result; + u8 OUI[3]; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%hhx:%hhx:%hhx", + &OUI[0], &OUI[1], &OUI[2]); + if (result != 3) { + result = -EINVAL; + goto out; + } else + memcpy(wlp->dev_info->prim_dev_type.OUI, OUI, sizeof(OUI)); +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_store); + + +ssize_t wlp_dev_prim_OUI_sub_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%u\n", + wlp->dev_info->prim_dev_type.OUIsubdiv); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_show); + +ssize_t wlp_dev_prim_OUI_sub_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + unsigned sub; + u8 max_sub = ~0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%u", &sub); + if (sub <= max_sub) + wlp->dev_info->prim_dev_type.OUIsubdiv = sub; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_OUI_sub_store); + +ssize_t wlp_dev_prim_subcat_show(struct wlp *wlp, char *buf) +{ + ssize_t result = 0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) + goto out; + } + result = scnprintf(buf, PAGE_SIZE, "%u\n", + wlp->dev_info->prim_dev_type.subID); +out: + mutex_unlock(&wlp->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_show); + +ssize_t wlp_dev_prim_subcat_store(struct wlp *wlp, const char *buf, + size_t size) +{ + ssize_t result; + unsigned sub; + __le16 max_sub = ~0; + mutex_lock(&wlp->mutex); + if (wlp->dev_info == NULL) { + result = __wlp_alloc_device_info(wlp); + if (result < 0) + goto out; + } + result = sscanf(buf, "%u", &sub); + if (sub <= max_sub) + wlp->dev_info->prim_dev_type.subID = sub; + else + result = -EINVAL; +out: + mutex_unlock(&wlp->mutex); + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(wlp_dev_prim_subcat_store); + +/** + * Subsystem implementation for interaction with individual WSS via sysfs + * + * Followed instructions for subsystem in Documentation/filesystems/sysfs.txt + */ + +#define kobj_to_wlp_wss(obj) container_of(obj, struct wlp_wss, kobj) +#define attr_to_wlp_wss_attr(_attr) \ + container_of(_attr, struct wlp_wss_attribute, attr) + +/** + * Sysfs subsystem: forward read calls + * + * Sysfs operation for forwarding read call to the show method of the + * attribute owner + */ +static +ssize_t wlp_wss_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr); + struct wlp_wss *wss = kobj_to_wlp_wss(kobj); + ssize_t ret = -EIO; + + if (wss_attr->show) + ret = wss_attr->show(wss, buf); + return ret; +} +/** + * Sysfs subsystem: forward write calls + * + * Sysfs operation for forwarding write call to the store method of the + * attribute owner + */ +static +ssize_t wlp_wss_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct wlp_wss_attribute *wss_attr = attr_to_wlp_wss_attr(attr); + struct wlp_wss *wss = kobj_to_wlp_wss(kobj); + ssize_t ret = -EIO; + + if (wss_attr->store) + ret = wss_attr->store(wss, buf, count); + return ret; +} + +static +struct sysfs_ops wss_sysfs_ops = { + .show = wlp_wss_attr_show, + .store = wlp_wss_attr_store, +}; + +struct kobj_type wss_ktype = { + .release = wlp_wss_release, + .sysfs_ops = &wss_sysfs_ops, +}; + + +/** + * Sysfs files for individual WSS + */ + +/** + * Print static properties of this WSS + * + * The name of a WSS may not be null teminated. It's max size is 64 bytes + * so we copy it to a larger array just to make sure we print sane data. + */ +static ssize_t wlp_wss_properties_show(struct wlp_wss *wss, char *buf) +{ + int result = 0; + + if (mutex_lock_interruptible(&wss->mutex)) + goto out; + result = __wlp_wss_properties_show(wss, buf, PAGE_SIZE); + mutex_unlock(&wss->mutex); +out: + return result; +} +WSS_ATTR(properties, S_IRUGO, wlp_wss_properties_show, NULL); + +/** + * Print all connected members of this WSS + * The EDA cache contains all members of WSS neighborhood. + */ +static ssize_t wlp_wss_members_show(struct wlp_wss *wss, char *buf) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + return wlp_eda_show(wlp, buf); +} +WSS_ATTR(members, S_IRUGO, wlp_wss_members_show, NULL); + +static +const char *__wlp_strstate[] = { + "none", + "partially enrolled", + "enrolled", + "active", + "connected", +}; + +static const char *wlp_wss_strstate(unsigned state) +{ + if (state >= ARRAY_SIZE(__wlp_strstate)) + return "unknown state"; + return __wlp_strstate[state]; +} + +/* + * Print current state of this WSS + */ +static ssize_t wlp_wss_state_show(struct wlp_wss *wss, char *buf) +{ + int result = 0; + + if (mutex_lock_interruptible(&wss->mutex)) + goto out; + result = scnprintf(buf, PAGE_SIZE, "%s\n", + wlp_wss_strstate(wss->state)); + mutex_unlock(&wss->mutex); +out: + return result; +} +WSS_ATTR(state, S_IRUGO, wlp_wss_state_show, NULL); + + +static +struct attribute *wss_attrs[] = { + &wss_attr_properties.attr, + &wss_attr_members.attr, + &wss_attr_state.attr, + NULL, +}; + +struct attribute_group wss_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wss_attrs, +}; diff --git a/drivers/uwb/wlp/txrx.c b/drivers/uwb/wlp/txrx.c new file mode 100644 index 00000000000..c701bd1a288 --- /dev/null +++ b/drivers/uwb/wlp/txrx.c @@ -0,0 +1,374 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * Message exchange infrastructure + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: Docs + * + */ + +#include +#include +#define D_LOCAL 5 +#include +#include "wlp-internal.h" + + +/** + * Direct incoming association msg to correct parsing routine + * + * We only expect D1, E1, C1, C3 messages as new. All other incoming + * association messages should form part of an established session that is + * handled elsewhere. + * The handling of these messages often require calling sleeping functions + * - this cannot be done in interrupt context. We use the kernel's + * workqueue to handle these messages. + */ +static +void wlp_direct_assoc_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *assoc = (void *) skb->data; + struct wlp_assoc_frame_ctx *frame_ctx; + d_fnstart(5, dev, "wlp %p, skb %p\n", wlp, skb); + frame_ctx = kmalloc(sizeof(*frame_ctx), GFP_ATOMIC); + if (frame_ctx == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for association " + "frame handling.\n"); + kfree_skb(skb); + goto out; + } + frame_ctx->wlp = wlp; + frame_ctx->skb = skb; + frame_ctx->src = *src; + switch (assoc->type) { + case WLP_ASSOC_D1: + d_printf(5, dev, "Received a D1 frame.\n"); + INIT_WORK(&frame_ctx->ws, wlp_handle_d1_frame); + schedule_work(&frame_ctx->ws); + break; + case WLP_ASSOC_E1: + d_printf(5, dev, "Received a E1 frame. FIXME?\n"); + kfree_skb(skb); /* Temporary until we handle it */ + kfree(frame_ctx); /* Temporary until we handle it */ + break; + case WLP_ASSOC_C1: + d_printf(5, dev, "Received a C1 frame.\n"); + INIT_WORK(&frame_ctx->ws, wlp_handle_c1_frame); + schedule_work(&frame_ctx->ws); + break; + case WLP_ASSOC_C3: + d_printf(5, dev, "Received a C3 frame.\n"); + INIT_WORK(&frame_ctx->ws, wlp_handle_c3_frame); + schedule_work(&frame_ctx->ws); + break; + default: + dev_err(dev, "Received unexpected association frame. " + "Type = %d \n", assoc->type); + kfree_skb(skb); + kfree(frame_ctx); + break; + } +out: + d_fnend(5, dev, "wlp %p\n", wlp); +} + +/** + * Process incoming association frame + * + * Although it could be possible to deal with some incoming association + * messages without creating a new session we are keeping things simple. We + * do not accept new association messages if there is a session in progress + * and the messages do not belong to that session. + * + * If an association message arrives that causes the creation of a session + * (WLP_ASSOC_E1) while we are in the process of creating a session then we + * rely on the neighbor mutex to protect the data. That is, the new session + * will not be started until the previous is completed. + */ +static +void wlp_receive_assoc_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *assoc = (void *) skb->data; + struct wlp_session *session = wlp->session; + u8 version; + d_fnstart(5, dev, "wlp %p, skb %p\n", wlp, skb); + + if (wlp_get_version(wlp, &assoc->version, &version, + sizeof(assoc->version)) < 0) + goto error; + if (version != WLP_VERSION) { + dev_err(dev, "Unsupported WLP version in association " + "message.\n"); + goto error; + } + if (session != NULL) { + /* Function that created this session is still holding the + * &wlp->mutex to protect this session. */ + if (assoc->type == session->exp_message || + assoc->type == WLP_ASSOC_F0) { + if (!memcmp(&session->neighbor_addr, src, + sizeof(*src))) { + session->data = skb; + (session->cb)(wlp); + } else { + dev_err(dev, "Received expected message from " + "unexpected source. Expected message " + "%d or F0 from %02x:%02x, but received " + "it from %02x:%02x. Dropping.\n", + session->exp_message, + session->neighbor_addr.data[1], + session->neighbor_addr.data[0], + src->data[1], src->data[0]); + goto error; + } + } else { + dev_err(dev, "Association already in progress. " + "Dropping.\n"); + goto error; + } + } else { + wlp_direct_assoc_frame(wlp, skb, src); + } + d_fnend(5, dev, "wlp %p\n", wlp); + return; +error: + kfree_skb(skb); + d_fnend(5, dev, "wlp %p\n", wlp); +} + +/** + * Verify incoming frame is from connected neighbor, prep to pass to WLP client + * + * Verification proceeds according to WLP 0.99 [7.3.1]. The source address + * is used to determine which neighbor is sending the frame and the WSS tag + * is used to know to which WSS the frame belongs (we only support one WSS + * so this test is straight forward). + * With the WSS found we need to ensure that we are connected before + * allowing the exchange of data frames. + */ +static +int wlp_verify_prep_rx_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -EINVAL; + struct wlp_eda_node eda_entry; + struct wlp_frame_std_abbrv_hdr *hdr = (void *) skb->data; + + d_fnstart(6, dev, "wlp %p, skb %p \n", wlp, skb); + /*verify*/ + result = wlp_copy_eda_node(&wlp->eda, src, &eda_entry); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Incoming frame is from unknown " + "neighbor %02x:%02x.\n", src->data[1], + src->data[0]); + goto out; + } + if (hdr->tag != eda_entry.tag) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Tag of incoming frame from " + "%02x:%02x does not match expected tag. " + "Received 0x%02x, expected 0x%02x. \n", + src->data[1], src->data[0], hdr->tag, + eda_entry.tag); + result = -EINVAL; + goto out; + } + if (eda_entry.state != WLP_WSS_CONNECTED) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Incoming frame from " + "%02x:%02x does is not from connected WSS.\n", + src->data[1], src->data[0]); + result = -EINVAL; + goto out; + } + /*prep*/ + skb_pull(skb, sizeof(*hdr)); +out: + d_fnend(6, dev, "wlp %p, skb %p, result = %d \n", wlp, skb, result); + return result; +} + +/** + * Receive a WLP frame from device + * + * @returns: 1 if calling function should free the skb + * 0 if it successfully handled skb and freed it + * 0 if error occured, will free skb in this case + */ +int wlp_receive_frame(struct device *dev, struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *src) +{ + unsigned len = skb->len; + void *ptr = skb->data; + struct wlp_frame_hdr *hdr; + int result = 0; + + d_fnstart(6, dev, "skb (%p), len (%u)\n", skb, len); + if (len < sizeof(*hdr)) { + dev_err(dev, "Not enough data to parse WLP header.\n"); + result = -EINVAL; + goto out; + } + hdr = ptr; + d_dump(6, dev, hdr, sizeof(*hdr)); + if (le16_to_cpu(hdr->mux_hdr) != WLP_PROTOCOL_ID) { + dev_err(dev, "Not a WLP frame type.\n"); + result = -EINVAL; + goto out; + } + switch (hdr->type) { + case WLP_FRAME_STANDARD: + if (len < sizeof(struct wlp_frame_std_abbrv_hdr)) { + dev_err(dev, "Not enough data to parse Standard " + "WLP header.\n"); + goto out; + } + result = wlp_verify_prep_rx_frame(wlp, skb, src); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Verification of frame " + "from neighbor %02x:%02x failed.\n", + src->data[1], src->data[0]); + goto out; + } + result = 1; + break; + case WLP_FRAME_ABBREVIATED: + dev_err(dev, "Abbreviated frame received. FIXME?\n"); + kfree_skb(skb); + break; + case WLP_FRAME_CONTROL: + dev_err(dev, "Control frame received. FIXME?\n"); + kfree_skb(skb); + break; + case WLP_FRAME_ASSOCIATION: + if (len < sizeof(struct wlp_frame_assoc)) { + dev_err(dev, "Not enough data to parse Association " + "WLP header.\n"); + goto out; + } + d_printf(5, dev, "Association frame received.\n"); + wlp_receive_assoc_frame(wlp, skb, src); + break; + default: + dev_err(dev, "Invalid frame received.\n"); + result = -EINVAL; + break; + } +out: + if (result < 0) { + kfree_skb(skb); + result = 0; + } + d_fnend(6, dev, "skb (%p)\n", skb); + return result; +} +EXPORT_SYMBOL_GPL(wlp_receive_frame); + + +/** + * Verify frame from network stack, prepare for further transmission + * + * @skb: the socket buffer that needs to be prepared for transmission (it + * is in need of a WLP header). If this is a broadcast frame we take + * over the entire transmission. + * If it is a unicast the WSS connection should already be established + * and transmission will be done by the calling function. + * @dst: On return this will contain the device address to which the + * frame is destined. + * @returns: 0 on success no tx : WLP header sucessfully applied to skb buffer, + * calling function can proceed with tx + * 1 on success with tx : WLP will take over transmission of this + * frame + * <0 on error + * + * The network stack (WLP client) is attempting to transmit a frame. We can + * only transmit data if a local WSS is at least active (connection will be + * done here if this is a broadcast frame and neighbor also has the WSS + * active). + * + * The frame can be either broadcast or unicast. Broadcast in a WSS is + * supported via multicast, but we don't support multicast yet (until + * devices start to support MAB IEs). If a broadcast frame needs to be + * transmitted it is treated as a unicast frame to each neighbor. In this + * case the WLP takes over transmission of the skb and returns 1 + * to the caller to indicate so. Also, in this case, if a neighbor has the + * same WSS activated but is not connected then the WSS connection will be + * done at this time. The neighbor's virtual address will be learned at + * this time. + * + * The destination address in a unicast frame is the virtual address of the + * neighbor. This address only becomes known when a WSS connection is + * established. We thus rely on a broadcast frame to trigger the setup of + * WSS connections to all neighbors before we are able to send unicast + * frames to them. This seems reasonable as IP would usually use ARP first + * before any unicast frames are sent. + * + * If we are already connected to the neighbor (neighbor's virtual address + * is known) we just prepare the WLP header and the caller will continue to + * send the frame. + * + * A failure in this function usually indicates something that cannot be + * fixed automatically. So, if this function fails (@return < 0) the calling + * function should not retry to send the frame as it will very likely keep + * failing. + * + */ +int wlp_prepare_tx_frame(struct device *dev, struct wlp *wlp, + struct sk_buff *skb, struct uwb_dev_addr *dst) +{ + int result = -EINVAL; + struct ethhdr *eth_hdr = (void *) skb->data; + + d_fnstart(6, dev, "wlp (%p), skb (%p) \n", wlp, skb); + if (is_broadcast_ether_addr(eth_hdr->h_dest)) { + d_printf(6, dev, "WLP: handling broadcast frame. \n"); + result = wlp_eda_for_each(&wlp->eda, wlp_wss_send_copy, skb); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "Unable to handle broadcast " + "frame from WLP client.\n"); + goto out; + } + dev_kfree_skb_irq(skb); + result = 1; + /* Frame will be transmitted by WLP. */ + } else { + d_printf(6, dev, "WLP: handling unicast frame. \n"); + result = wlp_eda_for_virtual(&wlp->eda, eth_hdr->h_dest, dst, + wlp_wss_prep_hdr, skb); + if (unlikely(result < 0)) { + if (printk_ratelimit()) + dev_err(dev, "Unable to prepare " + "skb for transmission. \n"); + goto out; + } + } +out: + d_fnend(6, dev, "wlp (%p), skb (%p). result = %d \n", wlp, skb, result); + return result; +} +EXPORT_SYMBOL_GPL(wlp_prepare_tx_frame); diff --git a/drivers/uwb/wlp/wlp-internal.h b/drivers/uwb/wlp/wlp-internal.h new file mode 100644 index 00000000000..1c94fabfb1a --- /dev/null +++ b/drivers/uwb/wlp/wlp-internal.h @@ -0,0 +1,228 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * Internal API + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + */ + +#ifndef __WLP_INTERNAL_H__ +#define __WLP_INTERNAL_H__ + +/** + * State of WSS connection + * + * A device needs to connect to a neighbor in an activated WSS before data + * can be transmitted. The spec also distinguishes between a new connection + * attempt and a connection attempt after previous connection attempts. The + * state WLP_WSS_CONNECT_FAILED is used for this scenario. See WLP 0.99 + * [7.2.6] + */ +enum wlp_wss_connect { + WLP_WSS_UNCONNECTED = 0, + WLP_WSS_CONNECTED, + WLP_WSS_CONNECT_FAILED, +}; + +extern struct kobj_type wss_ktype; +extern struct attribute_group wss_attr_group; + +extern int uwb_rc_ie_add(struct uwb_rc *, const struct uwb_ie_hdr *, size_t); +extern int uwb_rc_ie_rm(struct uwb_rc *, enum uwb_ie); + + +/* This should be changed to a dynamic array where entries are sorted + * by eth_addr and search is done in a binary form + * + * Although thinking twice about it: this technologie's maximum reach + * is 10 meters...unless you want to pack too much stuff in around + * your radio controller/WLP device, the list will probably not be + * too big. + * + * In any case, there is probably some data structure in the kernel + * than we could reused for that already. + * + * The below structure is really just good while we support one WSS per + * host. + */ +struct wlp_eda_node { + struct list_head list_node; + unsigned char eth_addr[ETH_ALEN]; + struct uwb_dev_addr dev_addr; + struct wlp_wss *wss; + unsigned char virt_addr[ETH_ALEN]; + u8 tag; + enum wlp_wss_connect state; +}; + +typedef int (*wlp_eda_for_each_f)(struct wlp *, struct wlp_eda_node *, void *); + +extern void wlp_eda_init(struct wlp_eda *); +extern void wlp_eda_release(struct wlp_eda *); +extern int wlp_eda_create_node(struct wlp_eda *, + const unsigned char eth_addr[ETH_ALEN], + const struct uwb_dev_addr *); +extern void wlp_eda_rm_node(struct wlp_eda *, const struct uwb_dev_addr *); +extern int wlp_eda_update_node(struct wlp_eda *, + const struct uwb_dev_addr *, + struct wlp_wss *, + const unsigned char virt_addr[ETH_ALEN], + const u8, const enum wlp_wss_connect); +extern int wlp_eda_update_node_state(struct wlp_eda *, + const struct uwb_dev_addr *, + const enum wlp_wss_connect); + +extern int wlp_copy_eda_node(struct wlp_eda *, struct uwb_dev_addr *, + struct wlp_eda_node *); +extern int wlp_eda_for_each(struct wlp_eda *, wlp_eda_for_each_f , void *); +extern int wlp_eda_for_virtual(struct wlp_eda *, + const unsigned char eth_addr[ETH_ALEN], + struct uwb_dev_addr *, + wlp_eda_for_each_f , void *); + + +extern void wlp_remove_neighbor_tmp_info(struct wlp_neighbor_e *); + +extern size_t wlp_wss_key_print(char *, size_t, u8 *); + +/* Function called when no more references to WSS exists */ +extern void wlp_wss_release(struct kobject *); + +extern void wlp_wss_reset(struct wlp_wss *); +extern int wlp_wss_create_activate(struct wlp_wss *, struct wlp_uuid *, + char *, unsigned, unsigned); +extern int wlp_wss_enroll_activate(struct wlp_wss *, struct wlp_uuid *, + struct uwb_dev_addr *); +extern ssize_t wlp_discover(struct wlp *); + +extern int wlp_enroll_neighbor(struct wlp *, struct wlp_neighbor_e *, + struct wlp_wss *, struct wlp_uuid *); +extern int wlp_wss_is_active(struct wlp *, struct wlp_wss *, + struct uwb_dev_addr *); + +struct wlp_assoc_conn_ctx { + struct work_struct ws; + struct wlp *wlp; + struct sk_buff *skb; + struct wlp_eda_node eda_entry; +}; + + +extern int wlp_wss_connect_prep(struct wlp *, struct wlp_eda_node *, void *); +extern int wlp_wss_send_copy(struct wlp *, struct wlp_eda_node *, void *); + + +/* Message handling */ +struct wlp_assoc_frame_ctx { + struct work_struct ws; + struct wlp *wlp; + struct sk_buff *skb; + struct uwb_dev_addr src; +}; + +extern int wlp_wss_prep_hdr(struct wlp *, struct wlp_eda_node *, void *); +extern void wlp_handle_d1_frame(struct work_struct *); +extern int wlp_parse_d2_frame_to_cache(struct wlp *, struct sk_buff *, + struct wlp_neighbor_e *); +extern int wlp_parse_d2_frame_to_enroll(struct wlp_wss *, struct sk_buff *, + struct wlp_neighbor_e *, + struct wlp_uuid *); +extern void wlp_handle_c1_frame(struct work_struct *); +extern void wlp_handle_c3_frame(struct work_struct *); +extern int wlp_parse_c3c4_frame(struct wlp *, struct sk_buff *, + struct wlp_uuid *, u8 *, + struct uwb_mac_addr *); +extern int wlp_parse_f0(struct wlp *, struct sk_buff *); +extern int wlp_send_assoc_frame(struct wlp *, struct wlp_wss *, + struct uwb_dev_addr *, enum wlp_assoc_type); +extern ssize_t wlp_get_version(struct wlp *, struct wlp_attr_version *, + u8 *, ssize_t); +extern ssize_t wlp_get_wssid(struct wlp *, struct wlp_attr_wssid *, + struct wlp_uuid *, ssize_t); +extern int __wlp_alloc_device_info(struct wlp *); +extern int __wlp_setup_device_info(struct wlp *); + +extern struct wlp_wss_attribute wss_attribute_properties; +extern struct wlp_wss_attribute wss_attribute_members; +extern struct wlp_wss_attribute wss_attribute_state; + +static inline +size_t wlp_wss_uuid_print(char *buf, size_t bufsize, struct wlp_uuid *uuid) +{ + size_t result; + + result = scnprintf(buf, bufsize, + "%02x:%02x:%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x:%02x:%02x:" + "%02x:%02x:%02x:%02x", + uuid->data[0], uuid->data[1], + uuid->data[2], uuid->data[3], + uuid->data[4], uuid->data[5], + uuid->data[6], uuid->data[7], + uuid->data[8], uuid->data[9], + uuid->data[10], uuid->data[11], + uuid->data[12], uuid->data[13], + uuid->data[14], uuid->data[15]); + return result; +} + +/** + * FIXME: How should a nonce be displayed? + */ +static inline +size_t wlp_wss_nonce_print(char *buf, size_t bufsize, struct wlp_nonce *nonce) +{ + size_t result; + + result = scnprintf(buf, bufsize, + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x", + nonce->data[0], nonce->data[1], + nonce->data[2], nonce->data[3], + nonce->data[4], nonce->data[5], + nonce->data[6], nonce->data[7], + nonce->data[8], nonce->data[9], + nonce->data[10], nonce->data[11], + nonce->data[12], nonce->data[13], + nonce->data[14], nonce->data[15]); + return result; +} + + +static inline +void wlp_session_cb(struct wlp *wlp) +{ + struct completion *completion = wlp->session->cb_priv; + complete(completion); +} + +static inline +int wlp_uuid_is_set(struct wlp_uuid *uuid) +{ + struct wlp_uuid zero_uuid = { .data = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00} }; + + if (!memcmp(uuid, &zero_uuid, sizeof(*uuid))) + return 0; + return 1; +} + +#endif /* __WLP_INTERNAL_H__ */ diff --git a/drivers/uwb/wlp/wlp-lc.c b/drivers/uwb/wlp/wlp-lc.c new file mode 100644 index 00000000000..0799402e73f --- /dev/null +++ b/drivers/uwb/wlp/wlp-lc.c @@ -0,0 +1,585 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2005-2006 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include +#define D_LOCAL 6 +#include +#include "wlp-internal.h" + + +static +void wlp_neighbor_init(struct wlp_neighbor_e *neighbor) +{ + INIT_LIST_HEAD(&neighbor->wssid); +} + +/** + * Create area for device information storage + * + * wlp->mutex must be held + */ +int __wlp_alloc_device_info(struct wlp *wlp) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + BUG_ON(wlp->dev_info != NULL); + wlp->dev_info = kzalloc(sizeof(struct wlp_device_info), GFP_KERNEL); + if (wlp->dev_info == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for " + "device information.\n"); + return -ENOMEM; + } + return 0; +} + + +/** + * Fill in device information using function provided by driver + * + * wlp->mutex must be held + */ +static +void __wlp_fill_device_info(struct wlp *wlp) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + + BUG_ON(wlp->fill_device_info == NULL); + d_printf(6, dev, "Retrieving device information " + "from device driver.\n"); + wlp->fill_device_info(wlp, wlp->dev_info); +} + +/** + * Setup device information + * + * Allocate area for device information and populate it. + * + * wlp->mutex must be held + */ +int __wlp_setup_device_info(struct wlp *wlp) +{ + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + + result = __wlp_alloc_device_info(wlp); + if (result < 0) { + dev_err(dev, "WLP: Unable to allocate area for " + "device information.\n"); + return result; + } + __wlp_fill_device_info(wlp); + return 0; +} + +/** + * Remove information about neighbor stored temporarily + * + * Information learned during discovey should only be stored when the + * device enrolls in the neighbor's WSS. We do need to store this + * information temporarily in order to present it to the user. + * + * We are only interested in keeping neighbor WSS information if that + * neighbor is accepting enrollment. + * + * should be called with wlp->nbmutex held + */ +void wlp_remove_neighbor_tmp_info(struct wlp_neighbor_e *neighbor) +{ + struct wlp_wssid_e *wssid_e, *next; + u8 keep; + if (!list_empty(&neighbor->wssid)) { + list_for_each_entry_safe(wssid_e, next, &neighbor->wssid, + node) { + if (wssid_e->info != NULL) { + keep = wssid_e->info->accept_enroll; + kfree(wssid_e->info); + wssid_e->info = NULL; + if (!keep) { + list_del(&wssid_e->node); + kfree(wssid_e); + } + } + } + } + if (neighbor->info != NULL) { + kfree(neighbor->info); + neighbor->info = NULL; + } +} + +/** + * Populate WLP neighborhood cache with neighbor information + * + * A new neighbor is found. If it is discoverable then we add it to the + * neighborhood cache. + * + */ +static +int wlp_add_neighbor(struct wlp *wlp, struct uwb_dev *dev) +{ + int result = 0; + int discoverable; + struct wlp_neighbor_e *neighbor; + + d_fnstart(6, &dev->dev, "uwb %p \n", dev); + d_printf(6, &dev->dev, "Found neighbor device %02x:%02x \n", + dev->dev_addr.data[1], dev->dev_addr.data[0]); + /** + * FIXME: + * Use contents of WLP IE found in beacon cache to determine if + * neighbor is discoverable. + * The device does not support WLP IE yet so this still needs to be + * done. Until then we assume all devices are discoverable. + */ + discoverable = 1; /* will be changed when FIXME disappears */ + if (discoverable) { + /* Add neighbor to cache for discovery */ + neighbor = kzalloc(sizeof(*neighbor), GFP_KERNEL); + if (neighbor == NULL) { + dev_err(&dev->dev, "Unable to create memory for " + "new neighbor. \n"); + result = -ENOMEM; + goto error_no_mem; + } + wlp_neighbor_init(neighbor); + uwb_dev_get(dev); + neighbor->uwb_dev = dev; + list_add(&neighbor->node, &wlp->neighbors); + } +error_no_mem: + d_fnend(6, &dev->dev, "uwb %p, result = %d \n", dev, result); + return result; +} + +/** + * Remove one neighbor from cache + */ +static +void __wlp_neighbor_release(struct wlp_neighbor_e *neighbor) +{ + struct wlp_wssid_e *wssid_e, *next_wssid_e; + + list_for_each_entry_safe(wssid_e, next_wssid_e, + &neighbor->wssid, node) { + list_del(&wssid_e->node); + kfree(wssid_e); + } + uwb_dev_put(neighbor->uwb_dev); + list_del(&neighbor->node); + kfree(neighbor); +} + +/** + * Clear entire neighborhood cache. + */ +static +void __wlp_neighbors_release(struct wlp *wlp) +{ + struct wlp_neighbor_e *neighbor, *next; + if (list_empty(&wlp->neighbors)) + return; + list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) { + __wlp_neighbor_release(neighbor); + } +} + +static +void wlp_neighbors_release(struct wlp *wlp) +{ + mutex_lock(&wlp->nbmutex); + __wlp_neighbors_release(wlp); + mutex_unlock(&wlp->nbmutex); +} + + + +/** + * Send D1 message to neighbor, receive D2 message + * + * @neighbor: neighbor to which D1 message will be sent + * @wss: if not NULL, it is an enrollment request for this WSS + * @wssid: if wss not NULL, this is the wssid of the WSS in which we + * want to enroll + * + * A D1/D2 exchange is done for one of two reasons: discovery or + * enrollment. If done for discovery the D1 message is sent to the neighbor + * and the contents of the D2 response is stored in a temporary cache. + * If done for enrollment the @wss and @wssid are provided also. In this + * case the D1 message is sent to the neighbor, the D2 response is parsed + * for enrollment of the WSS with wssid. + * + * &wss->mutex is held + */ +static +int wlp_d1d2_exchange(struct wlp *wlp, struct wlp_neighbor_e *neighbor, + struct wlp_wss *wss, struct wlp_uuid *wssid) +{ + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + DECLARE_COMPLETION_ONSTACK(completion); + struct wlp_session session; + struct sk_buff *skb; + struct wlp_frame_assoc *resp; + struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr; + + mutex_lock(&wlp->mutex); + if (!wlp_uuid_is_set(&wlp->uuid)) { + dev_err(dev, "WLP: UUID is not set. Set via sysfs to " + "proceed.\n"); + result = -ENXIO; + goto out; + } + /* Send D1 association frame */ + result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_D1); + if (result < 0) { + dev_err(dev, "Unable to send D1 frame to neighbor " + "%02x:%02x (%d)\n", dev_addr->data[1], + dev_addr->data[0], result); + d_printf(6, dev, "Add placeholders into buffer next to " + "neighbor information we have (dev address).\n"); + goto out; + } + /* Create session, wait for response */ + session.exp_message = WLP_ASSOC_D2; + session.cb = wlp_session_cb; + session.cb_priv = &completion; + session.neighbor_addr = *dev_addr; + BUG_ON(wlp->session != NULL); + wlp->session = &session; + /* Wait for D2/F0 frame */ + result = wait_for_completion_interruptible_timeout(&completion, + WLP_PER_MSG_TIMEOUT * HZ); + if (result == 0) { + result = -ETIMEDOUT; + dev_err(dev, "Timeout while sending D1 to neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + goto error_session; + } + if (result < 0) { + dev_err(dev, "Unable to discover/enroll neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + goto error_session; + } + /* Parse message in session->data: it will be either D2 or F0 */ + skb = session.data; + resp = (void *) skb->data; + d_printf(6, dev, "Received response to D1 frame. \n"); + d_dump(6, dev, skb->data, skb->len > 72 ? 72 : skb->len); + + if (resp->type == WLP_ASSOC_F0) { + result = wlp_parse_f0(wlp, skb); + if (result < 0) + dev_err(dev, "WLP: Unable to parse F0 from neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + result = -EINVAL; + goto error_resp_parse; + } + if (wss == NULL) { + /* Discovery */ + result = wlp_parse_d2_frame_to_cache(wlp, skb, neighbor); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse D2 message from " + "neighbor %02x:%02x for discovery.\n", + dev_addr->data[1], dev_addr->data[0]); + goto error_resp_parse; + } + } else { + /* Enrollment */ + result = wlp_parse_d2_frame_to_enroll(wss, skb, neighbor, + wssid); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse D2 message from " + "neighbor %02x:%02x for enrollment.\n", + dev_addr->data[1], dev_addr->data[0]); + goto error_resp_parse; + } + } +error_resp_parse: + kfree_skb(skb); +error_session: + wlp->session = NULL; +out: + mutex_unlock(&wlp->mutex); + return result; +} + +/** + * Enroll into WSS of provided WSSID by using neighbor as registrar + * + * &wss->mutex is held + */ +int wlp_enroll_neighbor(struct wlp *wlp, struct wlp_neighbor_e *neighbor, + struct wlp_wss *wss, struct wlp_uuid *wssid) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + struct uwb_dev_addr *dev_addr = &neighbor->uwb_dev->dev_addr; + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n", + wlp, neighbor, wss, wssid, buf); + d_printf(6, dev, "Complete me.\n"); + result = wlp_d1d2_exchange(wlp, neighbor, wss, wssid); + if (result < 0) { + dev_err(dev, "WLP: D1/D2 message exchange for enrollment " + "failed. result = %d \n", result); + goto out; + } + if (wss->state != WLP_WSS_STATE_PART_ENROLLED) { + dev_err(dev, "WLP: Unable to enroll into WSS %s using " + "neighbor %02x:%02x. \n", buf, + dev_addr->data[1], dev_addr->data[0]); + result = -EINVAL; + goto out; + } + if (wss->secure_status == WLP_WSS_SECURE) { + dev_err(dev, "FIXME: need to complete secure enrollment.\n"); + result = -EINVAL; + goto error; + } else { + wss->state = WLP_WSS_STATE_ENROLLED; + d_printf(2, dev, "WLP: Success Enrollment into unsecure WSS " + "%s using neighbor %02x:%02x. \n", buf, + dev_addr->data[1], dev_addr->data[0]); + } + + d_fnend(6, dev, "wlp %p, neighbor %p, wss %p, wssid %p (%s)\n", + wlp, neighbor, wss, wssid, buf); +out: + return result; +error: + wlp_wss_reset(wss); + return result; +} + +/** + * Discover WSS information of neighbor's active WSS + */ +static +int wlp_discover_neighbor(struct wlp *wlp, + struct wlp_neighbor_e *neighbor) +{ + return wlp_d1d2_exchange(wlp, neighbor, NULL, NULL); +} + + +/** + * Each neighbor in the neighborhood cache is discoverable. Discover it. + * + * Discovery is done through sending of D1 association frame and parsing + * the D2 association frame response. Only wssid from D2 will be included + * in neighbor cache, rest is just displayed to user and forgotten. + * + * The discovery is not done in parallel. This is simple and enables us to + * maintain only one association context. + * + * The discovery of one neighbor does not affect the other, but if the + * discovery of a neighbor fails it is removed from the neighborhood cache. + */ +static +int wlp_discover_all_neighbors(struct wlp *wlp) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor, *next; + + list_for_each_entry_safe(neighbor, next, &wlp->neighbors, node) { + result = wlp_discover_neighbor(wlp, neighbor); + if (result < 0) { + dev_err(dev, "WLP: Unable to discover neighbor " + "%02x:%02x, removing from neighborhood. \n", + neighbor->uwb_dev->dev_addr.data[1], + neighbor->uwb_dev->dev_addr.data[0]); + __wlp_neighbor_release(neighbor); + } + } + return result; +} + +static int wlp_add_neighbor_helper(struct device *dev, void *priv) +{ + struct wlp *wlp = priv; + struct uwb_dev *uwb_dev = to_uwb_dev(dev); + + return wlp_add_neighbor(wlp, uwb_dev); +} + +/** + * Discover WLP neighborhood + * + * Will send D1 association frame to all devices in beacon group that have + * discoverable bit set in WLP IE. D2 frames will be received, information + * displayed to user in @buf. Partial information (from D2 association + * frame) will be cached to assist with future association + * requests. + * + * The discovery of the WLP neighborhood is triggered by the user. This + * should occur infrequently and we thus free current cache and re-allocate + * memory if needed. + * + * If one neighbor fails during initial discovery (determining if it is a + * neighbor or not), we fail all - note that interaction with neighbor has + * not occured at this point so if a failure occurs we know something went wrong + * locally. We thus undo everything. + */ +ssize_t wlp_discover(struct wlp *wlp) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + + d_fnstart(6, dev, "wlp %p \n", wlp); + mutex_lock(&wlp->nbmutex); + /* Clear current neighborhood cache. */ + __wlp_neighbors_release(wlp); + /* Determine which devices in neighborhood. Repopulate cache. */ + result = uwb_dev_for_each(wlp->rc, wlp_add_neighbor_helper, wlp); + if (result < 0) { + /* May have partial neighbor information, release all. */ + __wlp_neighbors_release(wlp); + goto error_dev_for_each; + } + /* Discover the properties of devices in neighborhood. */ + result = wlp_discover_all_neighbors(wlp); + /* In case of failure we still print our partial results. */ + if (result < 0) { + dev_err(dev, "Unable to fully discover neighborhood. \n"); + result = 0; + } +error_dev_for_each: + mutex_unlock(&wlp->nbmutex); + d_fnend(6, dev, "wlp %p \n", wlp); + return result; +} + +/** + * Handle events from UWB stack + * + * We handle events conservatively. If a neighbor goes off the air we + * remove it from the neighborhood. If an association process is in + * progress this function will block waiting for the nbmutex to become + * free. The association process will thus be allowed to complete before it + * is removed. + */ +static +void wlp_uwb_notifs_cb(void *_wlp, struct uwb_dev *uwb_dev, + enum uwb_notifs event) +{ + struct wlp *wlp = _wlp; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor, *next; + int result; + switch (event) { + case UWB_NOTIF_ONAIR: + d_printf(6, dev, "UWB device %02x:%02x is onair\n", + uwb_dev->dev_addr.data[1], + uwb_dev->dev_addr.data[0]); + result = wlp_eda_create_node(&wlp->eda, + uwb_dev->mac_addr.data, + &uwb_dev->dev_addr); + if (result < 0) + dev_err(dev, "WLP: Unable to add new neighbor " + "%02x:%02x to EDA cache.\n", + uwb_dev->dev_addr.data[1], + uwb_dev->dev_addr.data[0]); + break; + case UWB_NOTIF_OFFAIR: + d_printf(6, dev, "UWB device %02x:%02x is offair\n", + uwb_dev->dev_addr.data[1], + uwb_dev->dev_addr.data[0]); + wlp_eda_rm_node(&wlp->eda, &uwb_dev->dev_addr); + mutex_lock(&wlp->nbmutex); + list_for_each_entry_safe(neighbor, next, &wlp->neighbors, + node) { + if (neighbor->uwb_dev == uwb_dev) { + d_printf(6, dev, "Removing device from " + "neighborhood.\n"); + __wlp_neighbor_release(neighbor); + } + } + mutex_unlock(&wlp->nbmutex); + break; + default: + dev_err(dev, "don't know how to handle event %d from uwb\n", + event); + } +} + +int wlp_setup(struct wlp *wlp, struct uwb_rc *rc) +{ + struct device *dev = &rc->uwb_dev.dev; + int result; + + d_fnstart(6, dev, "wlp %p\n", wlp); + BUG_ON(wlp->fill_device_info == NULL); + BUG_ON(wlp->xmit_frame == NULL); + BUG_ON(wlp->stop_queue == NULL); + BUG_ON(wlp->start_queue == NULL); + wlp->rc = rc; + wlp_eda_init(&wlp->eda);/* Set up address cache */ + wlp->uwb_notifs_handler.cb = wlp_uwb_notifs_cb; + wlp->uwb_notifs_handler.data = wlp; + uwb_notifs_register(rc, &wlp->uwb_notifs_handler); + + uwb_pal_init(&wlp->pal); + result = uwb_pal_register(rc, &wlp->pal); + if (result < 0) + uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler); + + d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result); + return result; +} +EXPORT_SYMBOL_GPL(wlp_setup); + +void wlp_remove(struct wlp *wlp) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + d_fnstart(6, dev, "wlp %p\n", wlp); + wlp_neighbors_release(wlp); + uwb_pal_unregister(wlp->rc, &wlp->pal); + uwb_notifs_deregister(wlp->rc, &wlp->uwb_notifs_handler); + wlp_eda_release(&wlp->eda); + mutex_lock(&wlp->mutex); + if (wlp->dev_info != NULL) + kfree(wlp->dev_info); + mutex_unlock(&wlp->mutex); + wlp->rc = NULL; + /* We have to use NULL here because this function can be called + * when the device disappeared. */ + d_fnend(6, NULL, "wlp %p\n", wlp); +} +EXPORT_SYMBOL_GPL(wlp_remove); + +/** + * wlp_reset_all - reset the WLP hardware + * @wlp: the WLP device to reset. + * + * This schedules a full hardware reset of the WLP device. The radio + * controller and any other PALs will also be reset. + */ +void wlp_reset_all(struct wlp *wlp) +{ + uwb_rc_reset_all(wlp->rc); +} +EXPORT_SYMBOL_GPL(wlp_reset_all); -- cgit v1.2.3 From e377e9d32d4945fe6a14775b3a4d9ecd1462e36a Mon Sep 17 00:00:00 2001 From: Reinette Chatre Date: Wed, 17 Sep 2008 16:34:17 +0100 Subject: uwb: add WiMedia LLC Protocol stack (messages) Add the WLP message formatting/decoding code. Signed-off-by: David Vrabel --- drivers/uwb/wlp/messages.c | 1946 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1946 insertions(+) create mode 100644 drivers/uwb/wlp/messages.c diff --git a/drivers/uwb/wlp/messages.c b/drivers/uwb/wlp/messages.c new file mode 100644 index 00000000000..a64cb824171 --- /dev/null +++ b/drivers/uwb/wlp/messages.c @@ -0,0 +1,1946 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * Message construction and parsing + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include +#define D_LOCAL 6 +#include +#include "wlp-internal.h" + +static +const char *__wlp_assoc_frame[] = { + [WLP_ASSOC_D1] = "WLP_ASSOC_D1", + [WLP_ASSOC_D2] = "WLP_ASSOC_D2", + [WLP_ASSOC_M1] = "WLP_ASSOC_M1", + [WLP_ASSOC_M2] = "WLP_ASSOC_M2", + [WLP_ASSOC_M3] = "WLP_ASSOC_M3", + [WLP_ASSOC_M4] = "WLP_ASSOC_M4", + [WLP_ASSOC_M5] = "WLP_ASSOC_M5", + [WLP_ASSOC_M6] = "WLP_ASSOC_M6", + [WLP_ASSOC_M7] = "WLP_ASSOC_M7", + [WLP_ASSOC_M8] = "WLP_ASSOC_M8", + [WLP_ASSOC_F0] = "WLP_ASSOC_F0", + [WLP_ASSOC_E1] = "WLP_ASSOC_E1", + [WLP_ASSOC_E2] = "WLP_ASSOC_E2", + [WLP_ASSOC_C1] = "WLP_ASSOC_C1", + [WLP_ASSOC_C2] = "WLP_ASSOC_C2", + [WLP_ASSOC_C3] = "WLP_ASSOC_C3", + [WLP_ASSOC_C4] = "WLP_ASSOC_C4", +}; + +static const char *wlp_assoc_frame_str(unsigned id) +{ + if (id >= ARRAY_SIZE(__wlp_assoc_frame)) + return "unknown association frame"; + return __wlp_assoc_frame[id]; +} + +static const char *__wlp_assc_error[] = { + "none", + "Authenticator Failure", + "Rogue activity suspected", + "Device busy", + "Setup Locked", + "Registrar not ready", + "Invalid WSS selection", + "Message timeout", + "Enrollment session timeout", + "Device password invalid", + "Unsupported version", + "Internal error", + "Undefined error", + "Numeric comparison failure", + "Waiting for user input", +}; + +static const char *wlp_assc_error_str(unsigned id) +{ + if (id >= ARRAY_SIZE(__wlp_assc_error)) + return "unknown WLP association error"; + return __wlp_assc_error[id]; +} + +static inline void wlp_set_attr_hdr(struct wlp_attr_hdr *hdr, unsigned type, + size_t len) +{ + hdr->type = cpu_to_le16(type); + hdr->length = cpu_to_le16(len); +} + +/* + * Populate fields of a constant sized attribute + * + * @returns: total size of attribute including size of new value + * + * We have two instances of this function (wlp_pset and wlp_set): one takes + * the value as a parameter, the other takes a pointer to the value as + * parameter. They thus only differ in how the value is assigned to the + * attribute. + * + * We use sizeof(*attr) - sizeof(struct wlp_attr_hdr) instead of + * sizeof(type) to be able to use this same code for the structures that + * contain 8bit enum values and be able to deal with pointer types. + */ +#define wlp_set(type, type_code, name) \ +static size_t wlp_set_##name(struct wlp_attr_##name *attr, type value) \ +{ \ + d_fnstart(6, NULL, "(attribute %p)\n", attr); \ + wlp_set_attr_hdr(&attr->hdr, type_code, \ + sizeof(*attr) - sizeof(struct wlp_attr_hdr)); \ + attr->name = value; \ + d_dump(6, NULL, attr, sizeof(*attr)); \ + d_fnend(6, NULL, "(attribute %p)\n", attr); \ + return sizeof(*attr); \ +} + +#define wlp_pset(type, type_code, name) \ +static size_t wlp_set_##name(struct wlp_attr_##name *attr, type value) \ +{ \ + d_fnstart(6, NULL, "(attribute %p)\n", attr); \ + wlp_set_attr_hdr(&attr->hdr, type_code, \ + sizeof(*attr) - sizeof(struct wlp_attr_hdr)); \ + attr->name = *value; \ + d_dump(6, NULL, attr, sizeof(*attr)); \ + d_fnend(6, NULL, "(attribute %p)\n", attr); \ + return sizeof(*attr); \ +} + +/** + * Populate fields of a variable attribute + * + * @returns: total size of attribute including size of new value + * + * Provided with a pointer to the memory area reserved for the + * attribute structure, the field is populated with the value. The + * reserved memory has to contain enough space for the value. + */ +#define wlp_vset(type, type_code, name) \ +static size_t wlp_set_##name(struct wlp_attr_##name *attr, type value, \ + size_t len) \ +{ \ + d_fnstart(6, NULL, "(attribute %p)\n", attr); \ + wlp_set_attr_hdr(&attr->hdr, type_code, len); \ + memcpy(attr->name, value, len); \ + d_dump(6, NULL, attr, sizeof(*attr) + len); \ + d_fnend(6, NULL, "(attribute %p)\n", attr); \ + return sizeof(*attr) + len; \ +} + +wlp_vset(char *, WLP_ATTR_DEV_NAME, dev_name) +wlp_vset(char *, WLP_ATTR_MANUF, manufacturer) +wlp_set(enum wlp_assoc_type, WLP_ATTR_MSG_TYPE, msg_type) +wlp_vset(char *, WLP_ATTR_MODEL_NAME, model_name) +wlp_vset(char *, WLP_ATTR_MODEL_NR, model_nr) +wlp_vset(char *, WLP_ATTR_SERIAL, serial) +wlp_vset(char *, WLP_ATTR_WSS_NAME, wss_name) +wlp_pset(struct wlp_uuid *, WLP_ATTR_UUID_E, uuid_e) +wlp_pset(struct wlp_uuid *, WLP_ATTR_UUID_R, uuid_r) +wlp_pset(struct wlp_uuid *, WLP_ATTR_WSSID, wssid) +wlp_pset(struct wlp_dev_type *, WLP_ATTR_PRI_DEV_TYPE, prim_dev_type) +/*wlp_pset(struct wlp_dev_type *, WLP_ATTR_SEC_DEV_TYPE, sec_dev_type)*/ +wlp_set(u8, WLP_ATTR_WLP_VER, version) +wlp_set(enum wlp_assc_error, WLP_ATTR_WLP_ASSC_ERR, wlp_assc_err) +wlp_set(enum wlp_wss_sel_mthd, WLP_ATTR_WSS_SEL_MTHD, wss_sel_mthd) +wlp_set(u8, WLP_ATTR_ACC_ENRL, accept_enrl) +wlp_set(u8, WLP_ATTR_WSS_SEC_STAT, wss_sec_status) +wlp_pset(struct uwb_mac_addr *, WLP_ATTR_WSS_BCAST, wss_bcast) +wlp_pset(struct wlp_nonce *, WLP_ATTR_ENRL_NONCE, enonce) +wlp_pset(struct wlp_nonce *, WLP_ATTR_REG_NONCE, rnonce) +wlp_set(u8, WLP_ATTR_WSS_TAG, wss_tag) +wlp_pset(struct uwb_mac_addr *, WLP_ATTR_WSS_VIRT, wss_virt) + +/** + * Fill in the WSS information attributes + * + * We currently only support one WSS, and this is assumed in this function + * that can populate only one WSS information attribute. + */ +static size_t wlp_set_wss_info(struct wlp_attr_wss_info *attr, + struct wlp_wss *wss) +{ + size_t datalen; + void *ptr = attr->wss_info; + size_t used = sizeof(*attr); + d_fnstart(6, NULL, "(attribute %p)\n", attr); + datalen = sizeof(struct wlp_wss_info) + strlen(wss->name); + wlp_set_attr_hdr(&attr->hdr, WLP_ATTR_WSS_INFO, datalen); + used = wlp_set_wssid(ptr, &wss->wssid); + used += wlp_set_wss_name(ptr + used, wss->name, strlen(wss->name)); + used += wlp_set_accept_enrl(ptr + used, wss->accept_enroll); + used += wlp_set_wss_sec_status(ptr + used, wss->secure_status); + used += wlp_set_wss_bcast(ptr + used, &wss->bcast); + d_dump(6, NULL, attr, sizeof(*attr) + datalen); + d_fnend(6, NULL, "(attribute %p, used %d)\n", + attr, (int)(sizeof(*attr) + used)); + return sizeof(*attr) + used; +} + +/** + * Verify attribute header + * + * @hdr: Pointer to attribute header that will be verified. + * @type: Expected attribute type. + * @len: Expected length of attribute value (excluding header). + * + * Most attribute values have a known length even when they do have a + * length field. This knowledge can be used via this function to verify + * that the length field matches the expected value. + */ +static int wlp_check_attr_hdr(struct wlp *wlp, struct wlp_attr_hdr *hdr, + enum wlp_attr_type type, unsigned len) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + + if (le16_to_cpu(hdr->type) != type) { + dev_err(dev, "WLP: unexpected header type. Expected " + "%u, got %u.\n", type, le16_to_cpu(hdr->type)); + return -EINVAL; + } + if (le16_to_cpu(hdr->length) != len) { + dev_err(dev, "WLP: unexpected length in header. Expected " + "%u, got %u.\n", len, le16_to_cpu(hdr->length)); + return -EINVAL; + } + return 0; +} + +/** + * Check if header of WSS information attribute valid + * + * @returns: length of WSS attributes (value of length attribute field) if + * valid WSS information attribute found + * -ENODATA if no WSS information attribute found + * -EIO other error occured + * + * The WSS information attribute is optional. The function will be provided + * with a pointer to data that could _potentially_ be a WSS information + * attribute. If a valid WSS information attribute is found it will return + * 0, if no WSS information attribute is found it will return -ENODATA, and + * another error will be returned if it is a WSS information attribute, but + * some parsing failure occured. + */ +static int wlp_check_wss_info_attr_hdr(struct wlp *wlp, + struct wlp_attr_hdr *hdr, size_t buflen) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + size_t len; + int result = 0; + + if (buflen < sizeof(*hdr)) { + dev_err(dev, "WLP: Not enough space in buffer to parse" + " WSS information attribute header.\n"); + result = -EIO; + goto out; + } + if (le16_to_cpu(hdr->type) != WLP_ATTR_WSS_INFO) { + /* WSS information is optional */ + result = -ENODATA; + goto out; + } + len = le16_to_cpu(hdr->length); + if (buflen < sizeof(*hdr) + len) { + dev_err(dev, "WLP: Not enough space in buffer to parse " + "variable data. Got %d, expected %d.\n", + (int)buflen, (int)(sizeof(*hdr) + len)); + result = -EIO; + goto out; + } + result = len; +out: + return result; +} + + +/** + * Get value of attribute from fixed size attribute field. + * + * @attr: Pointer to attribute field. + * @value: Pointer to variable in which attribute value will be placed. + * @buflen: Size of buffer in which attribute field (including header) + * can be found. + * @returns: Amount of given buffer consumed by parsing for this attribute. + * + * The size and type of the value is known by the type of the attribute. + */ +#define wlp_get(type, type_code, name) \ +ssize_t wlp_get_##name(struct wlp *wlp, struct wlp_attr_##name *attr, \ + type *value, ssize_t buflen) \ +{ \ + struct device *dev = &wlp->rc->uwb_dev.dev; \ + if (buflen < 0) \ + return -EINVAL; \ + if (buflen < sizeof(*attr)) { \ + dev_err(dev, "WLP: Not enough space in buffer to parse" \ + " attribute field. Need %d, received %zu\n", \ + (int)sizeof(*attr), buflen); \ + return -EIO; \ + } \ + if (wlp_check_attr_hdr(wlp, &attr->hdr, type_code, \ + sizeof(attr->name)) < 0) { \ + dev_err(dev, "WLP: Header verification failed. \n"); \ + return -EINVAL; \ + } \ + *value = attr->name; \ + return sizeof(*attr); \ +} + +#define wlp_get_sparse(type, type_code, name) \ + static wlp_get(type, type_code, name) + +/** + * Get value of attribute from variable sized attribute field. + * + * @max: The maximum size of this attribute. This value is dictated by + * the maximum value from the WLP specification. + * + * @attr: Pointer to attribute field. + * @value: Pointer to variable that will contain the value. The memory + * must already have been allocated for this value. + * @buflen: Size of buffer in which attribute field (including header) + * can be found. + * @returns: Amount of given bufferconsumed by parsing for this attribute. + */ +#define wlp_vget(type_val, type_code, name, max) \ +static ssize_t wlp_get_##name(struct wlp *wlp, \ + struct wlp_attr_##name *attr, \ + type_val *value, ssize_t buflen) \ +{ \ + struct device *dev = &wlp->rc->uwb_dev.dev; \ + size_t len; \ + if (buflen < 0) \ + return -EINVAL; \ + if (buflen < sizeof(*attr)) { \ + dev_err(dev, "WLP: Not enough space in buffer to parse" \ + " header.\n"); \ + return -EIO; \ + } \ + if (le16_to_cpu(attr->hdr.type) != type_code) { \ + dev_err(dev, "WLP: Unexpected attribute type. Got %u, " \ + "expected %u.\n", le16_to_cpu(attr->hdr.type), \ + type_code); \ + return -EINVAL; \ + } \ + len = le16_to_cpu(attr->hdr.length); \ + if (len > max) { \ + dev_err(dev, "WLP: Attribute larger than maximum " \ + "allowed. Received %zu, max is %d.\n", len, \ + (int)max); \ + return -EFBIG; \ + } \ + if (buflen < sizeof(*attr) + len) { \ + dev_err(dev, "WLP: Not enough space in buffer to parse "\ + "variable data.\n"); \ + return -EIO; \ + } \ + memcpy(value, (void *) attr + sizeof(*attr), len); \ + return sizeof(*attr) + len; \ +} + +wlp_get(u8, WLP_ATTR_WLP_VER, version) +wlp_get_sparse(enum wlp_wss_sel_mthd, WLP_ATTR_WSS_SEL_MTHD, wss_sel_mthd) +wlp_get_sparse(struct wlp_dev_type, WLP_ATTR_PRI_DEV_TYPE, prim_dev_type) +wlp_get_sparse(enum wlp_assc_error, WLP_ATTR_WLP_ASSC_ERR, wlp_assc_err) +wlp_get_sparse(struct wlp_uuid, WLP_ATTR_UUID_E, uuid_e) +wlp_get_sparse(struct wlp_uuid, WLP_ATTR_UUID_R, uuid_r) +wlp_get(struct wlp_uuid, WLP_ATTR_WSSID, wssid) +wlp_get_sparse(u8, WLP_ATTR_ACC_ENRL, accept_enrl) +wlp_get_sparse(u8, WLP_ATTR_WSS_SEC_STAT, wss_sec_status) +wlp_get_sparse(struct uwb_mac_addr, WLP_ATTR_WSS_BCAST, wss_bcast) +wlp_get_sparse(u8, WLP_ATTR_WSS_TAG, wss_tag) +wlp_get_sparse(struct uwb_mac_addr, WLP_ATTR_WSS_VIRT, wss_virt) +wlp_get_sparse(struct wlp_nonce, WLP_ATTR_ENRL_NONCE, enonce) +wlp_get_sparse(struct wlp_nonce, WLP_ATTR_REG_NONCE, rnonce) + +/* The buffers for the device info attributes can be found in the + * wlp_device_info struct. These buffers contain one byte more than the + * max allowed by the spec - this is done to be able to add the + * terminating \0 for user display. This terminating byte is not required + * in the actual attribute field (because it has a length field) so the + * maximum allowed for this value is one less than its size in the + * structure. + */ +wlp_vget(char, WLP_ATTR_WSS_NAME, wss_name, + FIELD_SIZEOF(struct wlp_wss, name) - 1) +wlp_vget(char, WLP_ATTR_DEV_NAME, dev_name, + FIELD_SIZEOF(struct wlp_device_info, name) - 1) +wlp_vget(char, WLP_ATTR_MANUF, manufacturer, + FIELD_SIZEOF(struct wlp_device_info, manufacturer) - 1) +wlp_vget(char, WLP_ATTR_MODEL_NAME, model_name, + FIELD_SIZEOF(struct wlp_device_info, model_name) - 1) +wlp_vget(char, WLP_ATTR_MODEL_NR, model_nr, + FIELD_SIZEOF(struct wlp_device_info, model_nr) - 1) +wlp_vget(char, WLP_ATTR_SERIAL, serial, + FIELD_SIZEOF(struct wlp_device_info, serial) - 1) + +/** + * Retrieve WSS Name, Accept enroll, Secure status, Broadcast from WSS info + * + * @attr: pointer to WSS name attribute in WSS information attribute field + * @info: structure that will be populated with data from WSS information + * field (WSS name, Accept enroll, secure status, broadcast address) + * @buflen: size of buffer + * + * Although the WSSID attribute forms part of the WSS info attribute it is + * retrieved separately and stored in a different location. + */ +static ssize_t wlp_get_wss_info_attrs(struct wlp *wlp, + struct wlp_attr_hdr *attr, + struct wlp_wss_tmp_info *info, + ssize_t buflen) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + void *ptr = attr; + size_t used = 0; + ssize_t result = -EINVAL; + + d_printf(6, dev, "WLP: WSS info: Retrieving WSS name\n"); + result = wlp_get_wss_name(wlp, ptr, info->name, buflen); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS name from " + "WSS info in D2 message.\n"); + goto error_parse; + } + used += result; + d_printf(6, dev, "WLP: WSS info: Retrieving accept enroll\n"); + result = wlp_get_accept_enrl(wlp, ptr + used, &info->accept_enroll, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain accepting " + "enrollment from WSS info in D2 message.\n"); + goto error_parse; + } + if (info->accept_enroll != 0 && info->accept_enroll != 1) { + dev_err(dev, "WLP: invalid value for accepting " + "enrollment in D2 message.\n"); + result = -EINVAL; + goto error_parse; + } + used += result; + d_printf(6, dev, "WLP: WSS info: Retrieving secure status\n"); + result = wlp_get_wss_sec_status(wlp, ptr + used, &info->sec_status, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain secure " + "status from WSS info in D2 message.\n"); + goto error_parse; + } + if (info->sec_status != 0 && info->sec_status != 1) { + dev_err(dev, "WLP: invalid value for secure " + "status in D2 message.\n"); + result = -EINVAL; + goto error_parse; + } + used += result; + d_printf(6, dev, "WLP: WSS info: Retrieving broadcast\n"); + result = wlp_get_wss_bcast(wlp, ptr + used, &info->bcast, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain broadcast " + "address from WSS info in D2 message.\n"); + goto error_parse; + } + used += result; + result = used; +error_parse: + return result; +} + +/** + * Create a new WSSID entry for the neighbor, allocate temporary storage + * + * Each neighbor can have many WSS active. We maintain a list of WSSIDs + * advertised by neighbor. During discovery we also cache information about + * these WSS in temporary storage. + * + * The temporary storage will be removed after it has been used (eg. + * displayed to user), the wssid element will be removed from the list when + * the neighbor is rediscovered or when it disappears. + */ +static struct wlp_wssid_e *wlp_create_wssid_e(struct wlp *wlp, + struct wlp_neighbor_e *neighbor) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_wssid_e *wssid_e; + + wssid_e = kzalloc(sizeof(*wssid_e), GFP_KERNEL); + if (wssid_e == NULL) { + dev_err(dev, "WLP: unable to allocate memory " + "for WSS information.\n"); + goto error_alloc; + } + wssid_e->info = kzalloc(sizeof(struct wlp_wss_tmp_info), GFP_KERNEL); + if (wssid_e->info == NULL) { + dev_err(dev, "WLP: unable to allocate memory " + "for temporary WSS information.\n"); + kfree(wssid_e); + wssid_e = NULL; + goto error_alloc; + } + list_add(&wssid_e->node, &neighbor->wssid); +error_alloc: + return wssid_e; +} + +/** + * Parse WSS information attribute + * + * @attr: pointer to WSS information attribute header + * @buflen: size of buffer in which WSS information attribute appears + * @wssid: will place wssid from WSS info attribute in this location + * @wss_info: will place other information from WSS information attribute + * in this location + * + * memory for @wssid and @wss_info must be allocated when calling this + */ +static ssize_t wlp_get_wss_info(struct wlp *wlp, struct wlp_attr_wss_info *attr, + size_t buflen, struct wlp_uuid *wssid, + struct wlp_wss_tmp_info *wss_info) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + ssize_t result; + size_t len; + size_t used = 0; + void *ptr; + + result = wlp_check_wss_info_attr_hdr(wlp, (struct wlp_attr_hdr *)attr, + buflen); + if (result < 0) + goto out; + len = result; + used = sizeof(*attr); + ptr = attr; + d_printf(6, dev, "WLP: WSS info: Retrieving WSSID\n"); + result = wlp_get_wssid(wlp, ptr + used, wssid, buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID from WSS info.\n"); + goto out; + } + used += result; + result = wlp_get_wss_info_attrs(wlp, ptr + used, wss_info, + buflen - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS information " + "from WSS information attributes. \n"); + goto out; + } + used += result; + if (len + sizeof(*attr) != used) { + dev_err(dev, "WLP: Amount of data parsed does not " + "match length field. Parsed %zu, length " + "field %zu. \n", used, len); + result = -EINVAL; + goto out; + } + result = used; + d_printf(6, dev, "WLP: Successfully parsed WLP information " + "attribute. used %zu bytes\n", used); +out: + return result; +} + +/** + * Retrieve WSS info from association frame + * + * @attr: pointer to WSS information attribute + * @neighbor: ptr to neighbor being discovered, NULL if enrollment in + * progress + * @wss: ptr to WSS being enrolled in, NULL if discovery in progress + * @buflen: size of buffer in which WSS information appears + * + * The WSS information attribute appears in the D2 association message. + * This message is used in two ways: to discover all neighbors or to enroll + * into a WSS activated by a neighbor. During discovery we only want to + * store the WSS info in a cache, to be deleted right after it has been + * used (eg. displayed to the user). During enrollment we store the WSS + * information for the lifetime of enrollment. + * + * During discovery we are interested in all WSS information, during + * enrollment we are only interested in the WSS being enrolled in. Even so, + * when in enrollment we keep parsing the message after finding the WSS of + * interest, this simplifies the calling routine in that it can be sure + * that all WSS information attributes have been parsed out of the message. + * + * Association frame is process with nbmutex held. The list access is safe. + */ +static ssize_t wlp_get_all_wss_info(struct wlp *wlp, + struct wlp_attr_wss_info *attr, + struct wlp_neighbor_e *neighbor, + struct wlp_wss *wss, ssize_t buflen) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + size_t used = 0; + ssize_t result = -EINVAL; + struct wlp_attr_wss_info *cur; + struct wlp_uuid wssid; + struct wlp_wss_tmp_info wss_info; + unsigned enroll; /* 0 - discovery to cache, 1 - enrollment */ + struct wlp_wssid_e *wssid_e; + char buf[WLP_WSS_UUID_STRSIZE]; + + d_fnstart(6, dev, "wlp %p, attr %p, neighbor %p, wss %p, buflen %d \n", + wlp, attr, neighbor, wss, (int)buflen); + if (buflen < 0) + goto out; + + if (neighbor != NULL && wss == NULL) + enroll = 0; /* discovery */ + else if (wss != NULL && neighbor == NULL) + enroll = 1; /* enrollment */ + else + goto out; + + cur = attr; + while (buflen - used > 0) { + memset(&wss_info, 0, sizeof(wss_info)); + cur = (void *)cur + used; + result = wlp_get_wss_info(wlp, cur, buflen - used, &wssid, + &wss_info); + if (result == -ENODATA) { + result = used; + goto out; + } else if (result < 0) { + dev_err(dev, "WLP: Unable to parse WSS information " + "from WSS information attribute. \n"); + result = -EINVAL; + goto error_parse; + } + if (enroll && !memcmp(&wssid, &wss->wssid, sizeof(wssid))) { + if (wss_info.accept_enroll != 1) { + dev_err(dev, "WLP: Requested WSS does " + "not accept enrollment.\n"); + result = -EINVAL; + goto out; + } + memcpy(wss->name, wss_info.name, sizeof(wss->name)); + wss->bcast = wss_info.bcast; + wss->secure_status = wss_info.sec_status; + wss->accept_enroll = wss_info.accept_enroll; + wss->state = WLP_WSS_STATE_PART_ENROLLED; + wlp_wss_uuid_print(buf, sizeof(buf), &wssid); + d_printf(2, dev, "WLP: Found WSS %s. Enrolling.\n", + buf); + } else { + wssid_e = wlp_create_wssid_e(wlp, neighbor); + if (wssid_e == NULL) { + dev_err(dev, "WLP: Cannot create new WSSID " + "entry for neighbor %02x:%02x.\n", + neighbor->uwb_dev->dev_addr.data[1], + neighbor->uwb_dev->dev_addr.data[0]); + result = -ENOMEM; + goto out; + } + wssid_e->wssid = wssid; + *wssid_e->info = wss_info; + } + used += result; + } + result = used; +error_parse: + if (result < 0 && !enroll) /* this was a discovery */ + wlp_remove_neighbor_tmp_info(neighbor); +out: + d_fnend(6, dev, "wlp %p, attr %p, neighbor %p, wss %p, buflen %d, " + "result %d \n", wlp, attr, neighbor, wss, (int)buflen, + (int)result); + return result; + +} + +/** + * Parse WSS information attributes into cache for discovery + * + * @attr: the first WSS information attribute in message + * @neighbor: the neighbor whose cache will be populated + * @buflen: size of the input buffer + */ +static ssize_t wlp_get_wss_info_to_cache(struct wlp *wlp, + struct wlp_attr_wss_info *attr, + struct wlp_neighbor_e *neighbor, + ssize_t buflen) +{ + return wlp_get_all_wss_info(wlp, attr, neighbor, NULL, buflen); +} + +/** + * Parse WSS information attributes into WSS struct for enrollment + * + * @attr: the first WSS information attribute in message + * @wss: the WSS that will be enrolled + * @buflen: size of the input buffer + */ +static ssize_t wlp_get_wss_info_to_enroll(struct wlp *wlp, + struct wlp_attr_wss_info *attr, + struct wlp_wss *wss, ssize_t buflen) +{ + return wlp_get_all_wss_info(wlp, attr, NULL, wss, buflen); +} + +/** + * Construct a D1 association frame + * + * We use the radio control functions to determine the values of the device + * properties. These are of variable length and the total space needed is + * tallied first before we start constructing the message. The radio + * control functions return strings that are terminated with \0. This + * character should not be included in the message (there is a length field + * accompanying it in the attribute). + */ +static int wlp_build_assoc_d1(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + struct wlp_device_info *info; + size_t used = 0; + struct wlp_frame_assoc *_d1; + struct sk_buff *_skb; + void *d1_itr; + + d_fnstart(6, dev, "wlp %p\n", wlp); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) { + dev_err(dev, "WLP: Unable to setup device " + "information for D1 message.\n"); + goto error; + } + } + info = wlp->dev_info; + d_printf(6, dev, "Local properties:\n" + "Device name (%d bytes): %s\n" + "Model name (%d bytes): %s\n" + "Manufacturer (%d bytes): %s\n" + "Model number (%d bytes): %s\n" + "Serial number (%d bytes): %s\n" + "Primary device type: \n" + " Category: %d \n" + " OUI: %02x:%02x:%02x \n" + " OUI Subdivision: %u \n", + (int)strlen(info->name), info->name, + (int)strlen(info->model_name), info->model_name, + (int)strlen(info->manufacturer), info->manufacturer, + (int)strlen(info->model_nr), info->model_nr, + (int)strlen(info->serial), info->serial, + info->prim_dev_type.category, + info->prim_dev_type.OUI[0], info->prim_dev_type.OUI[1], + info->prim_dev_type.OUI[2], info->prim_dev_type.OUIsubdiv); + _skb = dev_alloc_skb(sizeof(*_d1) + + sizeof(struct wlp_attr_uuid_e) + + sizeof(struct wlp_attr_wss_sel_mthd) + + sizeof(struct wlp_attr_dev_name) + + strlen(info->name) + + sizeof(struct wlp_attr_manufacturer) + + strlen(info->manufacturer) + + sizeof(struct wlp_attr_model_name) + + strlen(info->model_name) + + sizeof(struct wlp_attr_model_nr) + + strlen(info->model_nr) + + sizeof(struct wlp_attr_serial) + + strlen(info->serial) + + sizeof(struct wlp_attr_prim_dev_type) + + sizeof(struct wlp_attr_wlp_assc_err)); + if (_skb == NULL) { + dev_err(dev, "WLP: Cannot allocate memory for association " + "message.\n"); + result = -ENOMEM; + goto error; + } + _d1 = (void *) _skb->data; + d_printf(6, dev, "D1 starts at %p \n", _d1); + _d1->hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + _d1->hdr.type = WLP_FRAME_ASSOCIATION; + _d1->type = WLP_ASSOC_D1; + + wlp_set_version(&_d1->version, WLP_VERSION); + wlp_set_msg_type(&_d1->msg_type, WLP_ASSOC_D1); + d1_itr = _d1->attr; + used = wlp_set_uuid_e(d1_itr, &wlp->uuid); + used += wlp_set_wss_sel_mthd(d1_itr + used, WLP_WSS_REG_SELECT); + used += wlp_set_dev_name(d1_itr + used, info->name, + strlen(info->name)); + used += wlp_set_manufacturer(d1_itr + used, info->manufacturer, + strlen(info->manufacturer)); + used += wlp_set_model_name(d1_itr + used, info->model_name, + strlen(info->model_name)); + used += wlp_set_model_nr(d1_itr + used, info->model_nr, + strlen(info->model_nr)); + used += wlp_set_serial(d1_itr + used, info->serial, + strlen(info->serial)); + used += wlp_set_prim_dev_type(d1_itr + used, &info->prim_dev_type); + used += wlp_set_wlp_assc_err(d1_itr + used, WLP_ASSOC_ERROR_NONE); + skb_put(_skb, sizeof(*_d1) + used); + d_printf(6, dev, "D1 message:\n"); + d_dump(6, dev, _d1, sizeof(*_d1) + + sizeof(struct wlp_attr_uuid_e) + + sizeof(struct wlp_attr_wss_sel_mthd) + + sizeof(struct wlp_attr_dev_name) + + strlen(info->name) + + sizeof(struct wlp_attr_manufacturer) + + strlen(info->manufacturer) + + sizeof(struct wlp_attr_model_name) + + strlen(info->model_name) + + sizeof(struct wlp_attr_model_nr) + + strlen(info->model_nr) + + sizeof(struct wlp_attr_serial) + + strlen(info->serial) + + sizeof(struct wlp_attr_prim_dev_type) + + sizeof(struct wlp_attr_wlp_assc_err)); + *skb = _skb; +error: + d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result); + return result; +} + +/** + * Construct a D2 association frame + * + * We use the radio control functions to determine the values of the device + * properties. These are of variable length and the total space needed is + * tallied first before we start constructing the message. The radio + * control functions return strings that are terminated with \0. This + * character should not be included in the message (there is a length field + * accompanying it in the attribute). + */ +static +int wlp_build_assoc_d2(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb, struct wlp_uuid *uuid_e) +{ + + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + struct wlp_device_info *info; + size_t used = 0; + struct wlp_frame_assoc *_d2; + struct sk_buff *_skb; + void *d2_itr; + size_t mem_needed; + + d_fnstart(6, dev, "wlp %p\n", wlp); + if (wlp->dev_info == NULL) { + result = __wlp_setup_device_info(wlp); + if (result < 0) { + dev_err(dev, "WLP: Unable to setup device " + "information for D2 message.\n"); + goto error; + } + } + info = wlp->dev_info; + d_printf(6, dev, "Local properties:\n" + "Device name (%d bytes): %s\n" + "Model name (%d bytes): %s\n" + "Manufacturer (%d bytes): %s\n" + "Model number (%d bytes): %s\n" + "Serial number (%d bytes): %s\n" + "Primary device type: \n" + " Category: %d \n" + " OUI: %02x:%02x:%02x \n" + " OUI Subdivision: %u \n", + (int)strlen(info->name), info->name, + (int)strlen(info->model_name), info->model_name, + (int)strlen(info->manufacturer), info->manufacturer, + (int)strlen(info->model_nr), info->model_nr, + (int)strlen(info->serial), info->serial, + info->prim_dev_type.category, + info->prim_dev_type.OUI[0], info->prim_dev_type.OUI[1], + info->prim_dev_type.OUI[2], info->prim_dev_type.OUIsubdiv); + mem_needed = sizeof(*_d2) + + sizeof(struct wlp_attr_uuid_e) + + sizeof(struct wlp_attr_uuid_r) + + sizeof(struct wlp_attr_dev_name) + + strlen(info->name) + + sizeof(struct wlp_attr_manufacturer) + + strlen(info->manufacturer) + + sizeof(struct wlp_attr_model_name) + + strlen(info->model_name) + + sizeof(struct wlp_attr_model_nr) + + strlen(info->model_nr) + + sizeof(struct wlp_attr_serial) + + strlen(info->serial) + + sizeof(struct wlp_attr_prim_dev_type) + + sizeof(struct wlp_attr_wlp_assc_err); + if (wlp->wss.state >= WLP_WSS_STATE_ACTIVE) + mem_needed += sizeof(struct wlp_attr_wss_info) + + sizeof(struct wlp_wss_info) + + strlen(wlp->wss.name); + _skb = dev_alloc_skb(mem_needed); + if (_skb == NULL) { + dev_err(dev, "WLP: Cannot allocate memory for association " + "message.\n"); + result = -ENOMEM; + goto error; + } + _d2 = (void *) _skb->data; + d_printf(6, dev, "D2 starts at %p \n", _d2); + _d2->hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + _d2->hdr.type = WLP_FRAME_ASSOCIATION; + _d2->type = WLP_ASSOC_D2; + + wlp_set_version(&_d2->version, WLP_VERSION); + wlp_set_msg_type(&_d2->msg_type, WLP_ASSOC_D2); + d2_itr = _d2->attr; + used = wlp_set_uuid_e(d2_itr, uuid_e); + used += wlp_set_uuid_r(d2_itr + used, &wlp->uuid); + if (wlp->wss.state >= WLP_WSS_STATE_ACTIVE) + used += wlp_set_wss_info(d2_itr + used, &wlp->wss); + used += wlp_set_dev_name(d2_itr + used, info->name, + strlen(info->name)); + used += wlp_set_manufacturer(d2_itr + used, info->manufacturer, + strlen(info->manufacturer)); + used += wlp_set_model_name(d2_itr + used, info->model_name, + strlen(info->model_name)); + used += wlp_set_model_nr(d2_itr + used, info->model_nr, + strlen(info->model_nr)); + used += wlp_set_serial(d2_itr + used, info->serial, + strlen(info->serial)); + used += wlp_set_prim_dev_type(d2_itr + used, &info->prim_dev_type); + used += wlp_set_wlp_assc_err(d2_itr + used, WLP_ASSOC_ERROR_NONE); + skb_put(_skb, sizeof(*_d2) + used); + d_printf(6, dev, "D2 message:\n"); + d_dump(6, dev, _d2, mem_needed); + *skb = _skb; +error: + d_fnend(6, dev, "wlp %p, result = %d\n", wlp, result); + return result; +} + +/** + * Allocate memory for and populate fields of F0 association frame + * + * Currently (while focusing on unsecure enrollment) we ignore the + * nonce's that could be placed in the message. Only the error field is + * populated by the value provided by the caller. + */ +static +int wlp_build_assoc_f0(struct wlp *wlp, struct sk_buff **skb, + enum wlp_assc_error error) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -ENOMEM; + struct { + struct wlp_frame_assoc f0_hdr; + struct wlp_attr_enonce enonce; + struct wlp_attr_rnonce rnonce; + struct wlp_attr_wlp_assc_err assc_err; + } *f0; + struct sk_buff *_skb; + struct wlp_nonce tmp; + + d_fnstart(6, dev, "wlp %p\n", wlp); + _skb = dev_alloc_skb(sizeof(*f0)); + if (_skb == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for F0 " + "association frame. \n"); + goto error_alloc; + } + f0 = (void *) _skb->data; + d_printf(6, dev, "F0 starts at %p \n", f0); + f0->f0_hdr.hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + f0->f0_hdr.hdr.type = WLP_FRAME_ASSOCIATION; + f0->f0_hdr.type = WLP_ASSOC_F0; + wlp_set_version(&f0->f0_hdr.version, WLP_VERSION); + wlp_set_msg_type(&f0->f0_hdr.msg_type, WLP_ASSOC_F0); + memset(&tmp, 0, sizeof(tmp)); + wlp_set_enonce(&f0->enonce, &tmp); + wlp_set_rnonce(&f0->rnonce, &tmp); + wlp_set_wlp_assc_err(&f0->assc_err, error); + skb_put(_skb, sizeof(*f0)); + *skb = _skb; + result = 0; +error_alloc: + d_fnend(6, dev, "wlp %p, result %d \n", wlp, result); + return result; +} + +/** + * Parse F0 frame + * + * We just retrieve the values and print it as an error to the user. + * Calling function already knows an error occured (F0 indicates error), so + * we just parse the content as debug for higher layers. + */ +int wlp_parse_f0(struct wlp *wlp, struct sk_buff *skb) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *f0 = (void *) skb->data; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + struct wlp_nonce enonce, rnonce; + enum wlp_assc_error assc_err; + char enonce_buf[WLP_WSS_NONCE_STRSIZE]; + char rnonce_buf[WLP_WSS_NONCE_STRSIZE]; + + used = sizeof(*f0); + result = wlp_get_enonce(wlp, ptr + used, &enonce, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Enrollee nonce " + "attribute from F0 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_rnonce(wlp, ptr + used, &rnonce, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Registrar nonce " + "attribute from F0 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, &assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association error " + "attribute from F0 message.\n"); + goto error_parse; + } + wlp_wss_nonce_print(enonce_buf, sizeof(enonce_buf), &enonce); + wlp_wss_nonce_print(rnonce_buf, sizeof(rnonce_buf), &rnonce); + dev_err(dev, "WLP: Received F0 error frame from neighbor. Enrollee " + "nonce: %s, Registrar nonce: %s, WLP Association error: %s.\n", + enonce_buf, rnonce_buf, wlp_assc_error_str(assc_err)); + result = 0; +error_parse: + return result; +} + +/** + * Retrieve variable device information from association message + * + * The device information parsed is not required in any message. This + * routine will thus not fail if an attribute is not present. + * The attributes are expected in a certain order, even if all are not + * present. The "attribute type" value is used to ensure the attributes + * are parsed in the correct order. + * + * If an error is encountered during parsing the function will return an + * error code, when this happens the given device_info structure may be + * partially filled. + */ +static +int wlp_get_variable_info(struct wlp *wlp, void *data, + struct wlp_device_info *dev_info, ssize_t len) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + size_t used = 0; + struct wlp_attr_hdr *hdr; + ssize_t result = 0; + unsigned last = 0; + + while (len - used > 0) { + if (len - used < sizeof(*hdr)) { + dev_err(dev, "WLP: Partial data in frame, cannot " + "parse. \n"); + goto error_parse; + } + hdr = data + used; + switch (le16_to_cpu(hdr->type)) { + case WLP_ATTR_MANUF: + if (last >= WLP_ATTR_MANUF) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_manufacturer(wlp, data + used, + dev_info->manufacturer, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain " + "Manufacturer attribute from D1 " + "message.\n"); + goto error_parse; + } + last = WLP_ATTR_MANUF; + used += result; + break; + case WLP_ATTR_MODEL_NAME: + if (last >= WLP_ATTR_MODEL_NAME) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_model_name(wlp, data + used, + dev_info->model_name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Model " + "name attribute from D1 message.\n"); + goto error_parse; + } + last = WLP_ATTR_MODEL_NAME; + used += result; + break; + case WLP_ATTR_MODEL_NR: + if (last >= WLP_ATTR_MODEL_NR) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_model_nr(wlp, data + used, + dev_info->model_nr, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Model " + "number attribute from D1 message.\n"); + goto error_parse; + } + last = WLP_ATTR_MODEL_NR; + used += result; + break; + case WLP_ATTR_SERIAL: + if (last >= WLP_ATTR_SERIAL) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_serial(wlp, data + used, + dev_info->serial, len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Serial " + "number attribute from D1 message.\n"); + goto error_parse; + } + last = WLP_ATTR_SERIAL; + used += result; + break; + case WLP_ATTR_PRI_DEV_TYPE: + if (last >= WLP_ATTR_PRI_DEV_TYPE) { + dev_err(dev, "WLP: Incorrect order of " + "attribute values in D1 msg.\n"); + goto error_parse; + } + result = wlp_get_prim_dev_type(wlp, data + used, + &dev_info->prim_dev_type, + len - used); + if (result < 0) { + dev_err(dev, "WLP: Unable to obtain Primary " + "device type attribute from D1 " + "message.\n"); + goto error_parse; + } + dev_info->prim_dev_type.category = + le16_to_cpu(dev_info->prim_dev_type.category); + dev_info->prim_dev_type.subID = + le16_to_cpu(dev_info->prim_dev_type.subID); + last = WLP_ATTR_PRI_DEV_TYPE; + used += result; + break; + default: + /* This is not variable device information. */ + goto out; + break; + } + } +out: + return used; +error_parse: + return -EINVAL; +} + +/** + * Parse incoming D1 frame, populate attribute values + * + * Caller provides pointers to memory already allocated for attributes + * expected in the D1 frame. These variables will be populated. + */ +static +int wlp_parse_d1_frame(struct wlp *wlp, struct sk_buff *skb, + struct wlp_uuid *uuid_e, + enum wlp_wss_sel_mthd *sel_mthd, + struct wlp_device_info *dev_info, + enum wlp_assc_error *assc_err) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *d1 = (void *) skb->data; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + + used = sizeof(*d1); + result = wlp_get_uuid_e(wlp, ptr + used, uuid_e, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-E attribute from D1 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wss_sel_mthd(wlp, ptr + used, sel_mthd, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS selection method " + "from D1 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_dev_name(wlp, ptr + used, dev_info->name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Name from D1 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_variable_info(wlp, ptr + used, dev_info, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Information from " + "D1 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association Error " + "Information from D1 message.\n"); + goto error_parse; + } + result = 0; +error_parse: + return result; +} +/** + * Handle incoming D1 frame + * + * The frame has already been verified to contain an Association header with + * the correct version number. Parse the incoming frame, construct and send + * a D2 frame in response. + * + * It is not clear what to do with most fields in the incoming D1 frame. We + * retrieve and discard the information here for now. + */ +void wlp_handle_d1_frame(struct work_struct *ws) +{ + struct wlp_assoc_frame_ctx *frame_ctx = container_of(ws, + struct wlp_assoc_frame_ctx, + ws); + struct wlp *wlp = frame_ctx->wlp; + struct wlp_wss *wss = &wlp->wss; + struct sk_buff *skb = frame_ctx->skb; + struct uwb_dev_addr *src = &frame_ctx->src; + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_uuid uuid_e; + enum wlp_wss_sel_mthd sel_mthd = 0; + struct wlp_device_info dev_info; + enum wlp_assc_error assc_err; + char uuid[WLP_WSS_UUID_STRSIZE]; + struct sk_buff *resp = NULL; + + /* Parse D1 frame */ + d_fnstart(6, dev, "WLP: handle D1 frame. wlp = %p, skb = %p\n", + wlp, skb); + mutex_lock(&wss->mutex); + mutex_lock(&wlp->mutex); /* to access wlp->uuid */ + memset(&dev_info, 0, sizeof(dev_info)); + result = wlp_parse_d1_frame(wlp, skb, &uuid_e, &sel_mthd, &dev_info, + &assc_err); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse incoming D1 frame.\n"); + kfree_skb(skb); + goto out; + } + wlp_wss_uuid_print(uuid, sizeof(uuid), &uuid_e); + d_printf(6, dev, "From D1 frame:\n" + "UUID-E: %s\n" + "Selection method: %d\n" + "Device name (%d bytes): %s\n" + "Model name (%d bytes): %s\n" + "Manufacturer (%d bytes): %s\n" + "Model number (%d bytes): %s\n" + "Serial number (%d bytes): %s\n" + "Primary device type: \n" + " Category: %d \n" + " OUI: %02x:%02x:%02x \n" + " OUI Subdivision: %u \n", + uuid, sel_mthd, + (int)strlen(dev_info.name), dev_info.name, + (int)strlen(dev_info.model_name), dev_info.model_name, + (int)strlen(dev_info.manufacturer), dev_info.manufacturer, + (int)strlen(dev_info.model_nr), dev_info.model_nr, + (int)strlen(dev_info.serial), dev_info.serial, + dev_info.prim_dev_type.category, + dev_info.prim_dev_type.OUI[0], + dev_info.prim_dev_type.OUI[1], + dev_info.prim_dev_type.OUI[2], + dev_info.prim_dev_type.OUIsubdiv); + + kfree_skb(skb); + if (!wlp_uuid_is_set(&wlp->uuid)) { + dev_err(dev, "WLP: UUID is not set. Set via sysfs to " + "proceed. Respong to D1 message with error F0.\n"); + result = wlp_build_assoc_f0(wlp, &resp, + WLP_ASSOC_ERROR_NOT_READY); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 message.\n"); + goto out; + } + } else { + /* Construct D2 frame */ + result = wlp_build_assoc_d2(wlp, wss, &resp, &uuid_e); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct D2 message.\n"); + goto out; + } + } + /* Send D2 frame */ + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, resp, src); + if (result < 0) { + dev_err(dev, "WLP: Unable to transmit D2 association " + "message: %d\n", result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_any(resp); /* we need to free if tx fails */ + } +out: + kfree(frame_ctx); + mutex_unlock(&wlp->mutex); + mutex_unlock(&wss->mutex); + d_fnend(6, dev, "WLP: handle D1 frame. wlp = %p\n", wlp); +} + +/** + * Parse incoming D2 frame, create and populate temporary cache + * + * @skb: socket buffer in which D2 frame can be found + * @neighbor: the neighbor that sent the D2 frame + * + * Will allocate memory for temporary storage of information learned during + * discovery. + */ +int wlp_parse_d2_frame_to_cache(struct wlp *wlp, struct sk_buff *skb, + struct wlp_neighbor_e *neighbor) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *d2 = (void *) skb->data; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + struct wlp_uuid uuid_e; + struct wlp_device_info *nb_info; + enum wlp_assc_error assc_err; + + used = sizeof(*d2); + result = wlp_get_uuid_e(wlp, ptr + used, &uuid_e, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-E attribute from D2 " + "message.\n"); + goto error_parse; + } + if (memcmp(&uuid_e, &wlp->uuid, sizeof(uuid_e))) { + dev_err(dev, "WLP: UUID-E in incoming D2 does not match " + "local UUID sent in D1. \n"); + goto error_parse; + } + used += result; + result = wlp_get_uuid_r(wlp, ptr + used, &neighbor->uuid, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-R attribute from D2 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wss_info_to_cache(wlp, ptr + used, neighbor, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS information " + "from D2 message.\n"); + goto error_parse; + } + used += result; + neighbor->info = kzalloc(sizeof(struct wlp_device_info), GFP_KERNEL); + if (neighbor->info == NULL) { + dev_err(dev, "WLP: cannot allocate memory to store device " + "info.\n"); + result = -ENOMEM; + goto error_parse; + } + nb_info = neighbor->info; + result = wlp_get_dev_name(wlp, ptr + used, nb_info->name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Name from D2 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_variable_info(wlp, ptr + used, nb_info, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Information from " + "D2 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, &assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association Error " + "Information from D2 message.\n"); + goto error_parse; + } + if (assc_err != WLP_ASSOC_ERROR_NONE) { + dev_err(dev, "WLP: neighbor device returned association " + "error %d\n", assc_err); + result = -EINVAL; + goto error_parse; + } + result = 0; +error_parse: + if (result < 0) + wlp_remove_neighbor_tmp_info(neighbor); + return result; +} + +/** + * Parse incoming D2 frame, populate attribute values of WSS bein enrolled in + * + * @wss: our WSS that will be enrolled + * @skb: socket buffer in which D2 frame can be found + * @neighbor: the neighbor that sent the D2 frame + * @wssid: the wssid of the WSS in which we want to enroll + * + * Forms part of enrollment sequence. We are trying to enroll in WSS with + * @wssid by using @neighbor as registrar. A D1 message was sent to + * @neighbor and now we need to parse the D2 response. The neighbor's + * response is searched for the requested WSS and if found (and it accepts + * enrollment), we store the information. + */ +int wlp_parse_d2_frame_to_enroll(struct wlp_wss *wss, struct sk_buff *skb, + struct wlp_neighbor_e *neighbor, + struct wlp_uuid *wssid) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + ssize_t result; + struct wlp_uuid uuid_e; + struct wlp_uuid uuid_r; + struct wlp_device_info nb_info; + enum wlp_assc_error assc_err; + char uuid_bufA[WLP_WSS_UUID_STRSIZE]; + char uuid_bufB[WLP_WSS_UUID_STRSIZE]; + + used = sizeof(struct wlp_frame_assoc); + result = wlp_get_uuid_e(wlp, ptr + used, &uuid_e, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-E attribute from D2 " + "message.\n"); + goto error_parse; + } + if (memcmp(&uuid_e, &wlp->uuid, sizeof(uuid_e))) { + dev_err(dev, "WLP: UUID-E in incoming D2 does not match " + "local UUID sent in D1. \n"); + goto error_parse; + } + used += result; + result = wlp_get_uuid_r(wlp, ptr + used, &uuid_r, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain UUID-R attribute from D2 " + "message.\n"); + goto error_parse; + } + if (memcmp(&uuid_r, &neighbor->uuid, sizeof(uuid_r))) { + wlp_wss_uuid_print(uuid_bufA, sizeof(uuid_bufA), + &neighbor->uuid); + wlp_wss_uuid_print(uuid_bufB, sizeof(uuid_bufB), &uuid_r); + dev_err(dev, "WLP: UUID of neighbor does not match UUID " + "learned during discovery. Originally discovered: %s, " + "now from D2 message: %s\n", uuid_bufA, uuid_bufB); + result = -EINVAL; + goto error_parse; + } + used += result; + wss->wssid = *wssid; + result = wlp_get_wss_info_to_enroll(wlp, ptr + used, wss, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS information " + "from D2 message.\n"); + goto error_parse; + } + if (wss->state != WLP_WSS_STATE_PART_ENROLLED) { + dev_err(dev, "WLP: D2 message did not contain information " + "for successful enrollment. \n"); + result = -EINVAL; + goto error_parse; + } + used += result; + /* Place device information on stack to continue parsing of message */ + result = wlp_get_dev_name(wlp, ptr + used, nb_info.name, + len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Name from D2 " + "message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_variable_info(wlp, ptr + used, &nb_info, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain Device Information from " + "D2 message.\n"); + goto error_parse; + } + used += result; + result = wlp_get_wlp_assc_err(wlp, ptr + used, &assc_err, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WLP Association Error " + "Information from D2 message.\n"); + goto error_parse; + } + if (assc_err != WLP_ASSOC_ERROR_NONE) { + dev_err(dev, "WLP: neighbor device returned association " + "error %d\n", assc_err); + if (wss->state == WLP_WSS_STATE_PART_ENROLLED) { + dev_err(dev, "WLP: Enrolled in WSS (should not " + "happen according to spec). Undoing. \n"); + wlp_wss_reset(wss); + } + result = -EINVAL; + goto error_parse; + } + result = 0; +error_parse: + return result; +} + +/** + * Parse C3/C4 frame into provided variables + * + * @wssid: will point to copy of wssid retrieved from C3/C4 frame + * @tag: will point to copy of tag retrieved from C3/C4 frame + * @virt_addr: will point to copy of virtual address retrieved from C3/C4 + * frame. + * + * Calling function has to allocate memory for these values. + * + * skb contains a valid C3/C4 frame, return the individual fields of this + * frame in the provided variables. + */ +int wlp_parse_c3c4_frame(struct wlp *wlp, struct sk_buff *skb, + struct wlp_uuid *wssid, u8 *tag, + struct uwb_mac_addr *virt_addr) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result; + void *ptr = skb->data; + size_t len = skb->len; + size_t used; + char buf[WLP_WSS_UUID_STRSIZE]; + struct wlp_frame_assoc *assoc = ptr; + + d_fnstart(6, dev, "wlp %p, skb %p \n", wlp, skb); + used = sizeof(*assoc); + result = wlp_get_wssid(wlp, ptr + used, wssid, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID attribute from " + "%s message.\n", wlp_assoc_frame_str(assoc->type)); + goto error_parse; + } + used += result; + result = wlp_get_wss_tag(wlp, ptr + used, tag, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS tag attribute from " + "%s message.\n", wlp_assoc_frame_str(assoc->type)); + goto error_parse; + } + used += result; + result = wlp_get_wss_virt(wlp, ptr + used, virt_addr, len - used); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSS virtual address " + "attribute from %s message.\n", + wlp_assoc_frame_str(assoc->type)); + goto error_parse; + } + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_printf(6, dev, "WLP: parsed: WSSID %s, tag 0x%02x, virt " + "%02x:%02x:%02x:%02x:%02x:%02x \n", buf, *tag, + virt_addr->data[0], virt_addr->data[1], virt_addr->data[2], + virt_addr->data[3], virt_addr->data[4], virt_addr->data[5]); + +error_parse: + d_fnend(6, dev, "wlp %p, skb %p, result = %d \n", wlp, skb, result); + return result; +} + +/** + * Allocate memory for and populate fields of C1 or C2 association frame + * + * The C1 and C2 association frames appear identical - except for the type. + */ +static +int wlp_build_assoc_c1c2(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb, enum wlp_assoc_type type) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -ENOMEM; + struct { + struct wlp_frame_assoc c_hdr; + struct wlp_attr_wssid wssid; + } *c; + struct sk_buff *_skb; + + d_fnstart(6, dev, "wlp %p, wss %p \n", wlp, wss); + _skb = dev_alloc_skb(sizeof(*c)); + if (_skb == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for C1/C2 " + "association frame. \n"); + goto error_alloc; + } + c = (void *) _skb->data; + d_printf(6, dev, "C1/C2 starts at %p \n", c); + c->c_hdr.hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + c->c_hdr.hdr.type = WLP_FRAME_ASSOCIATION; + c->c_hdr.type = type; + wlp_set_version(&c->c_hdr.version, WLP_VERSION); + wlp_set_msg_type(&c->c_hdr.msg_type, type); + wlp_set_wssid(&c->wssid, &wss->wssid); + skb_put(_skb, sizeof(*c)); + d_printf(6, dev, "C1/C2 message:\n"); + d_dump(6, dev, c, sizeof(*c)); + *skb = _skb; + result = 0; +error_alloc: + d_fnend(6, dev, "wlp %p, wss %p, result %d \n", wlp, wss, result); + return result; +} + + +static +int wlp_build_assoc_c1(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c1c2(wlp, wss, skb, WLP_ASSOC_C1); +} + +static +int wlp_build_assoc_c2(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c1c2(wlp, wss, skb, WLP_ASSOC_C2); +} + + +/** + * Allocate memory for and populate fields of C3 or C4 association frame + * + * The C3 and C4 association frames appear identical - except for the type. + */ +static +int wlp_build_assoc_c3c4(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb, enum wlp_assoc_type type) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = -ENOMEM; + struct { + struct wlp_frame_assoc c_hdr; + struct wlp_attr_wssid wssid; + struct wlp_attr_wss_tag wss_tag; + struct wlp_attr_wss_virt wss_virt; + } *c; + struct sk_buff *_skb; + + d_fnstart(6, dev, "wlp %p, wss %p \n", wlp, wss); + _skb = dev_alloc_skb(sizeof(*c)); + if (_skb == NULL) { + dev_err(dev, "WLP: Unable to allocate memory for C3/C4 " + "association frame. \n"); + goto error_alloc; + } + c = (void *) _skb->data; + d_printf(6, dev, "C3/C4 starts at %p \n", c); + c->c_hdr.hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + c->c_hdr.hdr.type = WLP_FRAME_ASSOCIATION; + c->c_hdr.type = type; + wlp_set_version(&c->c_hdr.version, WLP_VERSION); + wlp_set_msg_type(&c->c_hdr.msg_type, type); + wlp_set_wssid(&c->wssid, &wss->wssid); + wlp_set_wss_tag(&c->wss_tag, wss->tag); + wlp_set_wss_virt(&c->wss_virt, &wss->virtual_addr); + skb_put(_skb, sizeof(*c)); + d_printf(6, dev, "C3/C4 message:\n"); + d_dump(6, dev, c, sizeof(*c)); + *skb = _skb; + result = 0; +error_alloc: + d_fnend(6, dev, "wlp %p, wss %p, result %d \n", wlp, wss, result); + return result; +} + +static +int wlp_build_assoc_c3(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c3c4(wlp, wss, skb, WLP_ASSOC_C3); +} + +static +int wlp_build_assoc_c4(struct wlp *wlp, struct wlp_wss *wss, + struct sk_buff **skb) +{ + return wlp_build_assoc_c3c4(wlp, wss, skb, WLP_ASSOC_C4); +} + + +#define wlp_send_assoc(type, id) \ +static int wlp_send_assoc_##type(struct wlp *wlp, struct wlp_wss *wss, \ + struct uwb_dev_addr *dev_addr) \ +{ \ + struct device *dev = &wlp->rc->uwb_dev.dev; \ + int result; \ + struct sk_buff *skb = NULL; \ + d_fnstart(6, dev, "wlp %p, wss %p, neighbor: %02x:%02x\n", \ + wlp, wss, dev_addr->data[1], dev_addr->data[0]); \ + d_printf(6, dev, "WLP: Constructing %s frame. \n", \ + wlp_assoc_frame_str(id)); \ + /* Build the frame */ \ + result = wlp_build_assoc_##type(wlp, wss, &skb); \ + if (result < 0) { \ + dev_err(dev, "WLP: Unable to construct %s association " \ + "frame: %d\n", wlp_assoc_frame_str(id), result);\ + goto error_build_assoc; \ + } \ + /* Send the frame */ \ + d_printf(6, dev, "Transmitting %s frame to %02x:%02x \n", \ + wlp_assoc_frame_str(id), \ + dev_addr->data[1], dev_addr->data[0]); \ + BUG_ON(wlp->xmit_frame == NULL); \ + result = wlp->xmit_frame(wlp, skb, dev_addr); \ + if (result < 0) { \ + dev_err(dev, "WLP: Unable to transmit %s association " \ + "message: %d\n", wlp_assoc_frame_str(id), \ + result); \ + if (result == -ENXIO) \ + dev_err(dev, "WLP: Is network interface " \ + "up? \n"); \ + goto error_xmit; \ + } \ + return 0; \ +error_xmit: \ + /* We could try again ... */ \ + dev_kfree_skb_any(skb);/*we need to free if tx fails*/ \ +error_build_assoc: \ + d_fnend(6, dev, "wlp %p, wss %p, neighbor: %02x:%02x\n", \ + wlp, wss, dev_addr->data[1], dev_addr->data[0]); \ + return result; \ +} + +wlp_send_assoc(d1, WLP_ASSOC_D1) +wlp_send_assoc(c1, WLP_ASSOC_C1) +wlp_send_assoc(c3, WLP_ASSOC_C3) + +int wlp_send_assoc_frame(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr, + enum wlp_assoc_type type) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + switch (type) { + case WLP_ASSOC_D1: + result = wlp_send_assoc_d1(wlp, wss, dev_addr); + break; + case WLP_ASSOC_C1: + result = wlp_send_assoc_c1(wlp, wss, dev_addr); + break; + case WLP_ASSOC_C3: + result = wlp_send_assoc_c3(wlp, wss, dev_addr); + break; + default: + dev_err(dev, "WLP: Received request to send unknown " + "association message.\n"); + result = -EINVAL; + break; + } + return result; +} + +/** + * Handle incoming C1 frame + * + * The frame has already been verified to contain an Association header with + * the correct version number. Parse the incoming frame, construct and send + * a C2 frame in response. + */ +void wlp_handle_c1_frame(struct work_struct *ws) +{ + struct wlp_assoc_frame_ctx *frame_ctx = container_of(ws, + struct wlp_assoc_frame_ctx, + ws); + struct wlp *wlp = frame_ctx->wlp; + struct wlp_wss *wss = &wlp->wss; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_frame_assoc *c1 = (void *) frame_ctx->skb->data; + unsigned int len = frame_ctx->skb->len; + struct uwb_dev_addr *src = &frame_ctx->src; + int result; + struct wlp_uuid wssid; + char buf[WLP_WSS_UUID_STRSIZE]; + struct sk_buff *resp = NULL; + + /* Parse C1 frame */ + d_fnstart(6, dev, "WLP: handle C1 frame. wlp = %p, c1 = %p\n", + wlp, c1); + mutex_lock(&wss->mutex); + result = wlp_get_wssid(wlp, (void *)c1 + sizeof(*c1), &wssid, + len - sizeof(*c1)); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID from C1 frame.\n"); + goto out; + } + wlp_wss_uuid_print(buf, sizeof(buf), &wssid); + d_printf(6, dev, "Received C1 frame with WSSID %s \n", buf); + if (!memcmp(&wssid, &wss->wssid, sizeof(wssid)) + && wss->state == WLP_WSS_STATE_ACTIVE) { + d_printf(6, dev, "WSSID from C1 frame is known locally " + "and is active\n"); + /* Construct C2 frame */ + result = wlp_build_assoc_c2(wlp, wss, &resp); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct C2 message.\n"); + goto out; + } + } else { + d_printf(6, dev, "WSSID from C1 frame is not known locally " + "or is not active\n"); + /* Construct F0 frame */ + result = wlp_build_assoc_f0(wlp, &resp, WLP_ASSOC_ERROR_INV); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 message.\n"); + goto out; + } + } + /* Send C2 frame */ + d_printf(6, dev, "Transmitting response (C2/F0) frame to %02x:%02x \n", + src->data[1], src->data[0]); + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, resp, src); + if (result < 0) { + dev_err(dev, "WLP: Unable to transmit response association " + "message: %d\n", result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_any(resp); /* we need to free if tx fails */ + } +out: + kfree_skb(frame_ctx->skb); + kfree(frame_ctx); + mutex_unlock(&wss->mutex); + d_fnend(6, dev, "WLP: handle C1 frame. wlp = %p\n", wlp); +} + +/** + * Handle incoming C3 frame + * + * The frame has already been verified to contain an Association header with + * the correct version number. Parse the incoming frame, construct and send + * a C4 frame in response. If the C3 frame identifies a WSS that is locally + * active then we connect to this neighbor (add it to our EDA cache). + */ +void wlp_handle_c3_frame(struct work_struct *ws) +{ + struct wlp_assoc_frame_ctx *frame_ctx = container_of(ws, + struct wlp_assoc_frame_ctx, + ws); + struct wlp *wlp = frame_ctx->wlp; + struct wlp_wss *wss = &wlp->wss; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct sk_buff *skb = frame_ctx->skb; + struct uwb_dev_addr *src = &frame_ctx->src; + int result; + char buf[WLP_WSS_UUID_STRSIZE]; + struct sk_buff *resp = NULL; + struct wlp_uuid wssid; + u8 tag; + struct uwb_mac_addr virt_addr; + + /* Parse C3 frame */ + d_fnstart(6, dev, "WLP: handle C3 frame. wlp = %p, skb = %p\n", + wlp, skb); + mutex_lock(&wss->mutex); + result = wlp_parse_c3c4_frame(wlp, skb, &wssid, &tag, &virt_addr); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain values from C3 frame.\n"); + goto out; + } + wlp_wss_uuid_print(buf, sizeof(buf), &wssid); + d_printf(6, dev, "Received C3 frame with WSSID %s \n", buf); + if (!memcmp(&wssid, &wss->wssid, sizeof(wssid)) + && wss->state >= WLP_WSS_STATE_ACTIVE) { + d_printf(6, dev, "WSSID from C3 frame is known locally " + "and is active\n"); + result = wlp_eda_update_node(&wlp->eda, src, wss, + (void *) virt_addr.data, tag, + WLP_WSS_CONNECTED); + if (result < 0) { + dev_err(dev, "WLP: Unable to update EDA cache " + "with new connected neighbor information.\n"); + result = wlp_build_assoc_f0(wlp, &resp, + WLP_ASSOC_ERROR_INT); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 " + "message.\n"); + goto out; + } + } else { + wss->state = WLP_WSS_STATE_CONNECTED; + /* Construct C4 frame */ + result = wlp_build_assoc_c4(wlp, wss, &resp); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct C4 " + "message.\n"); + goto out; + } + } + } else { + d_printf(6, dev, "WSSID from C3 frame is not known locally " + "or is not active\n"); + /* Construct F0 frame */ + result = wlp_build_assoc_f0(wlp, &resp, WLP_ASSOC_ERROR_INV); + if (result < 0) { + dev_err(dev, "WLP: Unable to construct F0 message.\n"); + goto out; + } + } + /* Send C4 frame */ + d_printf(6, dev, "Transmitting response (C4/F0) frame to %02x:%02x \n", + src->data[1], src->data[0]); + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, resp, src); + if (result < 0) { + dev_err(dev, "WLP: Unable to transmit response association " + "message: %d\n", result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_any(resp); /* we need to free if tx fails */ + } +out: + kfree_skb(frame_ctx->skb); + kfree(frame_ctx); + mutex_unlock(&wss->mutex); + d_fnend(6, dev, "WLP: handle C3 frame. wlp = %p, skb = %p\n", + wlp, skb); +} + + -- cgit v1.2.3 From 2f19204480f16a20d8571a97c13f0cec2968607c Mon Sep 17 00:00:00 2001 From: Reinette Chatre Date: Wed, 17 Sep 2008 16:34:18 +0100 Subject: uwb: add WiMedia LLC Protocol stack (WSS) Add the Wireless Service Set (WSS) code. Signed-off-by: David Vrabel --- drivers/uwb/wlp/wss-lc.c | 1055 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1055 insertions(+) create mode 100644 drivers/uwb/wlp/wss-lc.c diff --git a/drivers/uwb/wlp/wss-lc.c b/drivers/uwb/wlp/wss-lc.c new file mode 100644 index 00000000000..96b18c9bd6e --- /dev/null +++ b/drivers/uwb/wlp/wss-lc.c @@ -0,0 +1,1055 @@ +/* + * WiMedia Logical Link Control Protocol (WLP) + * + * Copyright (C) 2007 Intel Corporation + * Reinette Chatre + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Implementation of the WLP association protocol. + * + * FIXME: Docs + * + * A UWB network interface will configure a WSS through wlp_wss_setup() after + * the interface has been assigned a MAC address, typically after + * "ifconfig" has been called. When the interface goes down it should call + * wlp_wss_remove(). + * + * When the WSS is ready for use the user interacts via sysfs to create, + * discover, and activate WSS. + * + * wlp_wss_enroll_activate() + * + * wlp_wss_create_activate() + * wlp_wss_set_wssid_hash() + * wlp_wss_comp_wssid_hash() + * wlp_wss_sel_bcast_addr() + * wlp_wss_sysfs_add() + * + * Called when no more references to WSS exist: + * wlp_wss_release() + * wlp_wss_reset() + */ + +#include /* for is_valid_ether_addr */ +#include +#include +#define D_LOCAL 5 +#include +#include "wlp-internal.h" + + +size_t wlp_wss_key_print(char *buf, size_t bufsize, u8 *key) +{ + size_t result; + + result = scnprintf(buf, bufsize, + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x", + key[0], key[1], key[2], key[3], + key[4], key[5], key[6], key[7], + key[8], key[9], key[10], key[11], + key[12], key[13], key[14], key[15]); + return result; +} + +/** + * Compute WSSID hash + * WLP Draft 0.99 [7.2.1] + * + * The WSSID hash for a WSSID is the result of an octet-wise exclusive-OR + * of all octets in the WSSID. + */ +static +u8 wlp_wss_comp_wssid_hash(struct wlp_uuid *wssid) +{ + return wssid->data[0] ^ wssid->data[1] ^ wssid->data[2] + ^ wssid->data[3] ^ wssid->data[4] ^ wssid->data[5] + ^ wssid->data[6] ^ wssid->data[7] ^ wssid->data[8] + ^ wssid->data[9] ^ wssid->data[10] ^ wssid->data[11] + ^ wssid->data[12] ^ wssid->data[13] ^ wssid->data[14] + ^ wssid->data[15]; +} + +/** + * Select a multicast EUI-48 for the WSS broadcast address. + * WLP Draft 0.99 [7.2.1] + * + * Selected based on the WiMedia Alliance OUI, 00-13-88, within the WLP + * range, [01-13-88-00-01-00, 01-13-88-00-01-FF] inclusive. + * + * This address is currently hardcoded. + * FIXME? + */ +static +struct uwb_mac_addr wlp_wss_sel_bcast_addr(struct wlp_wss *wss) +{ + struct uwb_mac_addr bcast = { + .data = { 0x01, 0x13, 0x88, 0x00, 0x01, 0x00 } + }; + return bcast; +} + +/** + * Clear the contents of the WSS structure - all except kobj, mutex, virtual + * + * We do not want to reinitialize - the internal kobj should not change as + * it still points to the parent received during setup. The mutex should + * remain also. We thus just reset values individually. + * The virutal address assigned to WSS will remain the same for the + * lifetime of the WSS. We only reset the fields that can change during its + * lifetime. + */ +void wlp_wss_reset(struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + d_fnstart(5, dev, "wss (%p) \n", wss); + memset(&wss->wssid, 0, sizeof(wss->wssid)); + wss->hash = 0; + memset(&wss->name[0], 0, sizeof(wss->name)); + memset(&wss->bcast, 0, sizeof(wss->bcast)); + wss->secure_status = WLP_WSS_UNSECURE; + memset(&wss->master_key[0], 0, sizeof(wss->master_key)); + wss->tag = 0; + wss->state = WLP_WSS_STATE_NONE; + d_fnend(5, dev, "wss (%p) \n", wss); +} + +/** + * Create sysfs infrastructure for WSS + * + * The WSS is configured to have the interface as parent (see wlp_wss_setup()) + * a new sysfs directory that includes wssid as its name is created in the + * interface's sysfs directory. The group of files interacting with WSS are + * created also. + */ +static +int wlp_wss_sysfs_add(struct wlp_wss *wss, char *wssid_str) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result; + + d_fnstart(5, dev, "wss (%p), wssid: %s\n", wss, wssid_str); + result = kobject_set_name(&wss->kobj, "wss-%s", wssid_str); + if (result < 0) + return result; + wss->kobj.ktype = &wss_ktype; + result = kobject_init_and_add(&wss->kobj, + &wss_ktype, wss->kobj.parent, "wlp"); + if (result < 0) { + dev_err(dev, "WLP: Cannot register WSS kobject.\n"); + goto error_kobject_register; + } + result = sysfs_create_group(&wss->kobj, &wss_attr_group); + if (result < 0) { + dev_err(dev, "WLP: Cannot register WSS attributes: %d\n", + result); + goto error_sysfs_create_group; + } + d_fnend(5, dev, "Completed. result = %d \n", result); + return 0; +error_sysfs_create_group: + + kobject_put(&wss->kobj); /* will free name if needed */ + return result; +error_kobject_register: + kfree(wss->kobj.name); + wss->kobj.name = NULL; + wss->kobj.ktype = NULL; + return result; +} + + +/** + * Release WSS + * + * No more references exist to this WSS. We should undo everything that was + * done in wlp_wss_create_activate() except removing the group. The group + * is not removed because an object can be unregistered before the group is + * created. We also undo any additional operations on the WSS after this + * (addition of members). + * + * If memory was allocated for the kobject's name then it will + * be freed by the kobject system during this time. + * + * The EDA cache is removed and reinitilized when the WSS is removed. We + * thus loose knowledge of members of this WSS at that time and need not do + * it here. + */ +void wlp_wss_release(struct kobject *kobj) +{ + struct wlp_wss *wss = container_of(kobj, struct wlp_wss, kobj); + + wlp_wss_reset(wss); +} + +/** + * Enroll into a WSS using provided neighbor as registrar + * + * First search the neighborhood information to learn which neighbor is + * referred to, next proceed with enrollment. + * + * &wss->mutex is held + */ +static +int wlp_wss_enroll_target(struct wlp_wss *wss, struct wlp_uuid *wssid, + struct uwb_dev_addr *dest) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor; + char buf[WLP_WSS_UUID_STRSIZE]; + int result = -ENXIO; + struct uwb_dev_addr *dev_addr; + + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(5, dev, "wss %p, wssid %s, registrar %02x:%02x \n", + wss, buf, dest->data[1], dest->data[0]); + mutex_lock(&wlp->nbmutex); + list_for_each_entry(neighbor, &wlp->neighbors, node) { + dev_addr = &neighbor->uwb_dev->dev_addr; + if (!memcmp(dest, dev_addr, sizeof(*dest))) { + d_printf(5, dev, "Neighbor %02x:%02x is valid, " + "enrolling. \n", + dev_addr->data[1], dev_addr->data[0]); + result = wlp_enroll_neighbor(wlp, neighbor, wss, + wssid); + break; + } + } + if (result == -ENXIO) + dev_err(dev, "WLP: Cannot find neighbor %02x:%02x. \n", + dest->data[1], dest->data[0]); + mutex_unlock(&wlp->nbmutex); + d_fnend(5, dev, "wss %p, wssid %s, registrar %02x:%02x, result %d \n", + wss, buf, dest->data[1], dest->data[0], result); + return result; +} + +/** + * Enroll into a WSS previously discovered + * + * User provides WSSID of WSS, search for neighbor that has this WSS + * activated and attempt to enroll. + * + * &wss->mutex is held + */ +static +int wlp_wss_enroll_discovered(struct wlp_wss *wss, struct wlp_uuid *wssid) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct wlp_neighbor_e *neighbor; + struct wlp_wssid_e *wssid_e; + char buf[WLP_WSS_UUID_STRSIZE]; + int result = -ENXIO; + + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(5, dev, "wss %p, wssid %s \n", wss, buf); + mutex_lock(&wlp->nbmutex); + list_for_each_entry(neighbor, &wlp->neighbors, node) { + list_for_each_entry(wssid_e, &neighbor->wssid, node) { + if (!memcmp(wssid, &wssid_e->wssid, sizeof(*wssid))) { + d_printf(5, dev, "Found WSSID %s in neighbor " + "%02x:%02x cache. \n", buf, + neighbor->uwb_dev->dev_addr.data[1], + neighbor->uwb_dev->dev_addr.data[0]); + result = wlp_enroll_neighbor(wlp, neighbor, + wss, wssid); + if (result == 0) /* enrollment success */ + goto out; + break; + } + } + } +out: + if (result == -ENXIO) + dev_err(dev, "WLP: Cannot find WSSID %s in cache. \n", buf); + mutex_unlock(&wlp->nbmutex); + d_fnend(5, dev, "wss %p, wssid %s, result %d \n", wss, buf, result); + return result; +} + +/** + * Enroll into WSS with provided WSSID, registrar may be provided + * + * @wss: out WSS that will be enrolled + * @wssid: wssid of neighboring WSS that we want to enroll in + * @devaddr: registrar can be specified, will be broadcast (ff:ff) if any + * neighbor can be used as registrar. + * + * &wss->mutex is held + */ +static +int wlp_wss_enroll(struct wlp_wss *wss, struct wlp_uuid *wssid, + struct uwb_dev_addr *devaddr) +{ + int result; + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + struct uwb_dev_addr bcast = {.data = {0xff, 0xff} }; + + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + if (wss->state != WLP_WSS_STATE_NONE) { + dev_err(dev, "WLP: Already enrolled in WSS %s.\n", buf); + result = -EEXIST; + goto error; + } + if (!memcmp(&bcast, devaddr, sizeof(bcast))) { + d_printf(5, dev, "Request to enroll in discovered WSS " + "with WSSID %s \n", buf); + result = wlp_wss_enroll_discovered(wss, wssid); + } else { + d_printf(5, dev, "Request to enroll in WSSID %s with " + "registrar %02x:%02x\n", buf, devaddr->data[1], + devaddr->data[0]); + result = wlp_wss_enroll_target(wss, wssid, devaddr); + } + if (result < 0) { + dev_err(dev, "WLP: Unable to enroll into WSS %s, result %d \n", + buf, result); + goto error; + } + d_printf(2, dev, "Successfully enrolled into WSS %s \n", buf); + result = wlp_wss_sysfs_add(wss, buf); + if (result < 0) { + dev_err(dev, "WLP: Unable to set up sysfs for WSS kobject.\n"); + wlp_wss_reset(wss); + } +error: + return result; + +} + +/** + * Activate given WSS + * + * Prior to activation a WSS must be enrolled. To activate a WSS a device + * includes the WSS hash in the WLP IE in its beacon in each superframe. + * WLP 0.99 [7.2.5]. + * + * The WSS tag is also computed at this time. We only support one activated + * WSS so we can use the hash as a tag - there will never be a conflict. + * + * We currently only support one activated WSS so only one WSS hash is + * included in the WLP IE. + */ +static +int wlp_wss_activate(struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + struct uwb_rc *uwb_rc = wlp->rc; + int result; + struct { + struct wlp_ie wlp_ie; + u8 hash; /* only include one hash */ + } ie_data; + + d_fnstart(5, dev, "Activating WSS %p. \n", wss); + BUG_ON(wss->state != WLP_WSS_STATE_ENROLLED); + wss->hash = wlp_wss_comp_wssid_hash(&wss->wssid); + wss->tag = wss->hash; + memset(&ie_data, 0, sizeof(ie_data)); + ie_data.wlp_ie.hdr.element_id = UWB_IE_WLP; + ie_data.wlp_ie.hdr.length = sizeof(ie_data) - sizeof(struct uwb_ie_hdr); + wlp_ie_set_hash_length(&ie_data.wlp_ie, sizeof(ie_data.hash)); + ie_data.hash = wss->hash; + result = uwb_rc_ie_add(uwb_rc, &ie_data.wlp_ie.hdr, + sizeof(ie_data)); + if (result < 0) { + dev_err(dev, "WLP: Unable to add WLP IE to beacon. " + "result = %d.\n", result); + goto error_wlp_ie; + } + wss->state = WLP_WSS_STATE_ACTIVE; + result = 0; +error_wlp_ie: + d_fnend(5, dev, "Activating WSS %p, result = %d \n", wss, result); + return result; +} + +/** + * Enroll in and activate WSS identified by provided WSSID + * + * The neighborhood cache should contain a list of all neighbors and the + * WSS they have activated. Based on that cache we search which neighbor we + * can perform the association process with. The user also has option to + * specify which neighbor it prefers as registrar. + * Successful enrollment is followed by activation. + * Successful activation will create the sysfs directory containing + * specific information regarding this WSS. + */ +int wlp_wss_enroll_activate(struct wlp_wss *wss, struct wlp_uuid *wssid, + struct uwb_dev_addr *devaddr) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + char buf[WLP_WSS_UUID_STRSIZE]; + + d_fnstart(5, dev, "Enrollment and activation requested. \n"); + mutex_lock(&wss->mutex); + result = wlp_wss_enroll(wss, wssid, devaddr); + if (result < 0) { + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + dev_err(dev, "WLP: Enrollment into WSS %s failed.\n", buf); + goto error_enroll; + } + result = wlp_wss_activate(wss); + if (result < 0) { + dev_err(dev, "WLP: Unable to activate WSS. Undoing enrollment " + "result = %d \n", result); + /* Undo enrollment */ + wlp_wss_reset(wss); + goto error_activate; + } +error_activate: +error_enroll: + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "Completed. result = %d \n", result); + return result; +} + +/** + * Create, enroll, and activate a new WSS + * + * @wssid: new wssid provided by user + * @name: WSS name requested by used. + * @sec_status: security status requested by user + * + * A user requested the creation of a new WSS. All operations are done + * locally. The new WSS will be stored locally, the hash will be included + * in the WLP IE, and the sysfs infrastructure for this WSS will be + * created. + */ +int wlp_wss_create_activate(struct wlp_wss *wss, struct wlp_uuid *wssid, + char *name, unsigned sec_status, unsigned accept) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + char buf[WLP_WSS_UUID_STRSIZE]; + d_fnstart(5, dev, "Request to create new WSS.\n"); + result = wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_printf(5, dev, "Request to create WSS: WSSID=%s, name=%s, " + "sec_status=%u, accepting enrollment=%u \n", + buf, name, sec_status, accept); + if (!mutex_trylock(&wss->mutex)) { + dev_err(dev, "WLP: WLP association session in progress.\n"); + return -EBUSY; + } + if (wss->state != WLP_WSS_STATE_NONE) { + dev_err(dev, "WLP: WSS already exists. Not creating new.\n"); + result = -EEXIST; + goto out; + } + if (wss->kobj.parent == NULL) { + dev_err(dev, "WLP: WSS parent not ready. Is network interface " + "up?\n"); + result = -ENXIO; + goto out; + } + if (sec_status == WLP_WSS_SECURE) { + dev_err(dev, "WLP: FIXME Creation of secure WSS not " + "supported yet.\n"); + result = -EINVAL; + goto out; + } + wss->wssid = *wssid; + memcpy(wss->name, name, sizeof(wss->name)); + wss->bcast = wlp_wss_sel_bcast_addr(wss); + wss->secure_status = sec_status; + wss->accept_enroll = accept; + /*wss->virtual_addr is initialized in call to wlp_wss_setup*/ + /* sysfs infrastructure */ + result = wlp_wss_sysfs_add(wss, buf); + if (result < 0) { + dev_err(dev, "Cannot set up sysfs for WSS kobject.\n"); + wlp_wss_reset(wss); + goto out; + } else + result = 0; + wss->state = WLP_WSS_STATE_ENROLLED; + result = wlp_wss_activate(wss); + if (result < 0) { + dev_err(dev, "WLP: Unable to activate WSS. Undoing " + "enrollment\n"); + wlp_wss_reset(wss); + goto out; + } + result = 0; +out: + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "Completed. result = %d \n", result); + return result; +} + +/** + * Determine if neighbor has WSS activated + * + * @returns: 1 if neighbor has WSS activated, zero otherwise + * + * This can be done in two ways: + * - send a C1 frame, parse C2/F0 response + * - examine the WLP IE sent by the neighbor + * + * The WLP IE is not fully supported in hardware so we use the C1/C2 frame + * exchange to determine if a WSS is activated. Using the WLP IE should be + * faster and should be used when it becomes possible. + */ +int wlp_wss_is_active(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + DECLARE_COMPLETION_ONSTACK(completion); + struct wlp_session session; + struct sk_buff *skb; + struct wlp_frame_assoc *resp; + struct wlp_uuid wssid; + + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + mutex_lock(&wlp->mutex); + /* Send C1 association frame */ + result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_C1); + if (result < 0) { + dev_err(dev, "Unable to send C1 frame to neighbor " + "%02x:%02x (%d)\n", dev_addr->data[1], + dev_addr->data[0], result); + result = 0; + goto out; + } + /* Create session, wait for response */ + session.exp_message = WLP_ASSOC_C2; + session.cb = wlp_session_cb; + session.cb_priv = &completion; + session.neighbor_addr = *dev_addr; + BUG_ON(wlp->session != NULL); + wlp->session = &session; + /* Wait for C2/F0 frame */ + result = wait_for_completion_interruptible_timeout(&completion, + WLP_PER_MSG_TIMEOUT * HZ); + if (result == 0) { + dev_err(dev, "Timeout while sending C1 to neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + goto out; + } + if (result < 0) { + dev_err(dev, "Unable to send C1 to neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + result = 0; + goto out; + } + /* Parse message in session->data: it will be either C2 or F0 */ + skb = session.data; + resp = (void *) skb->data; + d_printf(5, dev, "Received response to C1 frame. \n"); + d_dump(5, dev, skb->data, skb->len > 72 ? 72 : skb->len); + if (resp->type == WLP_ASSOC_F0) { + result = wlp_parse_f0(wlp, skb); + if (result < 0) + dev_err(dev, "WLP: unable to parse incoming F0 " + "frame from neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + result = 0; + goto error_resp_parse; + } + /* WLP version and message type fields have already been parsed */ + result = wlp_get_wssid(wlp, (void *)resp + sizeof(*resp), &wssid, + skb->len - sizeof(*resp)); + if (result < 0) { + dev_err(dev, "WLP: unable to obtain WSSID from C2 frame.\n"); + result = 0; + goto error_resp_parse; + } + if (!memcmp(&wssid, &wss->wssid, sizeof(wssid))) { + d_printf(5, dev, "WSSID in C2 frame matches local " + "active WSS.\n"); + result = 1; + } else { + dev_err(dev, "WLP: Received a C2 frame without matching " + "WSSID.\n"); + result = 0; + } +error_resp_parse: + kfree_skb(skb); +out: + wlp->session = NULL; + mutex_unlock(&wlp->mutex); + d_fnend(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + return result; +} + +/** + * Activate connection with neighbor by updating EDA cache + * + * @wss: local WSS to which neighbor wants to connect + * @dev_addr: neighbor's address + * @wssid: neighbor's WSSID - must be same as our WSS's WSSID + * @tag: neighbor's WSS tag used to identify frames transmitted by it + * @virt_addr: neighbor's virtual EUI-48 + */ +static +int wlp_wss_activate_connection(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr, + struct wlp_uuid *wssid, u8 *tag, + struct uwb_mac_addr *virt_addr) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + char buf[WLP_WSS_UUID_STRSIZE]; + wlp_wss_uuid_print(buf, sizeof(buf), wssid); + d_fnstart(5, dev, "wlp %p, wss %p, wssid %s, tag %u, virtual " + "%02x:%02x:%02x:%02x:%02x:%02x \n", wlp, wss, buf, *tag, + virt_addr->data[0], virt_addr->data[1], virt_addr->data[2], + virt_addr->data[3], virt_addr->data[4], virt_addr->data[5]); + + if (!memcmp(wssid, &wss->wssid, sizeof(*wssid))) { + d_printf(5, dev, "WSSID from neighbor frame matches local " + "active WSS.\n"); + /* Update EDA cache */ + result = wlp_eda_update_node(&wlp->eda, dev_addr, wss, + (void *) virt_addr->data, *tag, + WLP_WSS_CONNECTED); + if (result < 0) + dev_err(dev, "WLP: Unable to update EDA cache " + "with new connected neighbor information.\n"); + } else { + dev_err(dev, "WLP: Neighbor does not have matching " + "WSSID.\n"); + result = -EINVAL; + } + + d_fnend(5, dev, "wlp %p, wss %p, wssid %s, tag %u, virtual " + "%02x:%02x:%02x:%02x:%02x:%02x, result = %d \n", + wlp, wss, buf, *tag, + virt_addr->data[0], virt_addr->data[1], virt_addr->data[2], + virt_addr->data[3], virt_addr->data[4], virt_addr->data[5], + result); + + return result; +} + +/** + * Connect to WSS neighbor + * + * Use C3/C4 exchange to determine if neighbor has WSS activated and + * retrieve the WSS tag and virtual EUI-48 of the neighbor. + */ +static +int wlp_wss_connect_neighbor(struct wlp *wlp, struct wlp_wss *wss, + struct uwb_dev_addr *dev_addr) +{ + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + struct wlp_uuid wssid; + u8 tag; + struct uwb_mac_addr virt_addr; + DECLARE_COMPLETION_ONSTACK(completion); + struct wlp_session session; + struct wlp_frame_assoc *resp; + struct sk_buff *skb; + + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + mutex_lock(&wlp->mutex); + /* Send C3 association frame */ + result = wlp_send_assoc_frame(wlp, wss, dev_addr, WLP_ASSOC_C3); + if (result < 0) { + dev_err(dev, "Unable to send C3 frame to neighbor " + "%02x:%02x (%d)\n", dev_addr->data[1], + dev_addr->data[0], result); + goto out; + } + /* Create session, wait for response */ + session.exp_message = WLP_ASSOC_C4; + session.cb = wlp_session_cb; + session.cb_priv = &completion; + session.neighbor_addr = *dev_addr; + BUG_ON(wlp->session != NULL); + wlp->session = &session; + /* Wait for C4/F0 frame */ + result = wait_for_completion_interruptible_timeout(&completion, + WLP_PER_MSG_TIMEOUT * HZ); + if (result == 0) { + dev_err(dev, "Timeout while sending C3 to neighbor " + "%02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + result = -ETIMEDOUT; + goto out; + } + if (result < 0) { + dev_err(dev, "Unable to send C3 to neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + goto out; + } + /* Parse message in session->data: it will be either C4 or F0 */ + skb = session.data; + resp = (void *) skb->data; + d_printf(5, dev, "Received response to C3 frame. \n"); + d_dump(5, dev, skb->data, skb->len > 72 ? 72 : skb->len); + if (resp->type == WLP_ASSOC_F0) { + result = wlp_parse_f0(wlp, skb); + if (result < 0) + dev_err(dev, "WLP: unable to parse incoming F0 " + "frame from neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + result = -EINVAL; + goto error_resp_parse; + } + result = wlp_parse_c3c4_frame(wlp, skb, &wssid, &tag, &virt_addr); + if (result < 0) { + dev_err(dev, "WLP: Unable to parse C4 frame from neighbor.\n"); + goto error_resp_parse; + } + result = wlp_wss_activate_connection(wlp, wss, dev_addr, &wssid, &tag, + &virt_addr); + if (result < 0) { + dev_err(dev, "WLP: Unable to activate connection to " + "neighbor %02x:%02x.\n", dev_addr->data[1], + dev_addr->data[0]); + goto error_resp_parse; + } +error_resp_parse: + kfree_skb(skb); +out: + /* Record that we unsuccessfully tried to connect to this neighbor */ + if (result < 0) + wlp_eda_update_node_state(&wlp->eda, dev_addr, + WLP_WSS_CONNECT_FAILED); + wlp->session = NULL; + mutex_unlock(&wlp->mutex); + d_fnend(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + return result; +} + +/** + * Connect to neighbor with common WSS, send pending frame + * + * This function is scheduled when a frame is destined to a neighbor with + * which we do not have a connection. A copy of the EDA cache entry is + * provided - not the actual cache entry (because it is protected by a + * spinlock). + * + * First determine if neighbor has the same WSS activated, connect if it + * does. The C3/C4 exchange is dual purpose to determine if neighbor has + * WSS activated and proceed with the connection. + * + * The frame that triggered the connection setup is sent after connection + * setup. + * + * network queue is stopped - we need to restart when done + * + */ +static +void wlp_wss_connect_send(struct work_struct *ws) +{ + struct wlp_assoc_conn_ctx *conn_ctx = container_of(ws, + struct wlp_assoc_conn_ctx, + ws); + struct wlp *wlp = conn_ctx->wlp; + struct sk_buff *skb = conn_ctx->skb; + struct wlp_eda_node *eda_entry = &conn_ctx->eda_entry; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + struct wlp_wss *wss = &wlp->wss; + int result; + struct device *dev = &wlp->rc->uwb_dev.dev; + char buf[WLP_WSS_UUID_STRSIZE]; + + mutex_lock(&wss->mutex); + wlp_wss_uuid_print(buf, sizeof(buf), &wss->wssid); + d_fnstart(5, dev, "wlp %p, wss %p (wssid %s), neighbor %02x:%02x \n", + wlp, wss, buf, dev_addr->data[1], dev_addr->data[0]); + if (wss->state < WLP_WSS_STATE_ACTIVE) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Attempting to connect with " + "WSS that is not active or connected.\n"); + dev_kfree_skb(skb); + goto out; + } + /* Establish connection - send C3 rcv C4 */ + result = wlp_wss_connect_neighbor(wlp, wss, dev_addr); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to establish connection " + "with neighbor %02x:%02x.\n", + dev_addr->data[1], dev_addr->data[0]); + dev_kfree_skb(skb); + goto out; + } + /* EDA entry changed, update the local copy being used */ + result = wlp_copy_eda_node(&wlp->eda, dev_addr, eda_entry); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Cannot find EDA entry for " + "neighbor %02x:%02x \n", + dev_addr->data[1], dev_addr->data[0]); + } + result = wlp_wss_prep_hdr(wlp, eda_entry, skb); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to prepare frame header for " + "transmission (neighbor %02x:%02x). \n", + dev_addr->data[1], dev_addr->data[0]); + dev_kfree_skb(skb); + goto out; + } + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, skb, dev_addr); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to transmit frame: %d\n", + result); + if (result == -ENXIO) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb(skb);/*we need to free if tx fails */ + } +out: + kfree(conn_ctx); + BUG_ON(wlp->start_queue == NULL); + wlp->start_queue(wlp); + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "wlp %p, wss %p (wssid %s)\n", wlp, wss, buf); +} + +/** + * Add WLP header to outgoing skb + * + * @eda_entry: pointer to neighbor's entry in the EDA cache + * @_skb: skb containing data destined to the neighbor + */ +int wlp_wss_prep_hdr(struct wlp *wlp, struct wlp_eda_node *eda_entry, + void *_skb) +{ + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + unsigned char *eth_addr = eda_entry->eth_addr; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + struct sk_buff *skb = _skb; + struct wlp_frame_std_abbrv_hdr *std_hdr; + + d_fnstart(6, dev, "wlp %p \n", wlp); + if (eda_entry->state == WLP_WSS_CONNECTED) { + /* Add WLP header */ + BUG_ON(skb_headroom(skb) < sizeof(*std_hdr)); + std_hdr = (void *) __skb_push(skb, sizeof(*std_hdr)); + std_hdr->hdr.mux_hdr = cpu_to_le16(WLP_PROTOCOL_ID); + std_hdr->hdr.type = WLP_FRAME_STANDARD; + std_hdr->tag = eda_entry->wss->tag; + } else { + if (printk_ratelimit()) + dev_err(dev, "WLP: Destination neighbor (Ethernet: " + "%02x:%02x:%02x:%02x:%02x:%02x, Dev: " + "%02x:%02x) is not connected. \n", eth_addr[0], + eth_addr[1], eth_addr[2], eth_addr[3], + eth_addr[4], eth_addr[5], dev_addr->data[1], + dev_addr->data[0]); + result = -EINVAL; + } + d_fnend(6, dev, "wlp %p \n", wlp); + return result; +} + + +/** + * Prepare skb for neighbor: connect if not already and prep WLP header + * + * This function is called in interrupt context, but it needs to sleep. We + * temporarily stop the net queue to establish the WLP connection. + * Setup of the WLP connection and restart of queue is scheduled + * on the default work queue. + * + * run with eda->lock held (spinlock) + */ +int wlp_wss_connect_prep(struct wlp *wlp, struct wlp_eda_node *eda_entry, + void *_skb) +{ + int result = 0; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + unsigned char *eth_addr = eda_entry->eth_addr; + struct sk_buff *skb = _skb; + struct wlp_assoc_conn_ctx *conn_ctx; + + d_fnstart(5, dev, "wlp %p\n", wlp); + d_printf(5, dev, "To neighbor %02x:%02x with eth " + "%02x:%02x:%02x:%02x:%02x:%02x\n", dev_addr->data[1], + dev_addr->data[0], eth_addr[0], eth_addr[1], eth_addr[2], + eth_addr[3], eth_addr[4], eth_addr[5]); + if (eda_entry->state == WLP_WSS_UNCONNECTED) { + /* We don't want any more packets while we set up connection */ + BUG_ON(wlp->stop_queue == NULL); + wlp->stop_queue(wlp); + conn_ctx = kmalloc(sizeof(*conn_ctx), GFP_ATOMIC); + if (conn_ctx == NULL) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to allocate memory " + "for connection handling.\n"); + result = -ENOMEM; + goto out; + } + conn_ctx->wlp = wlp; + conn_ctx->skb = skb; + conn_ctx->eda_entry = *eda_entry; + INIT_WORK(&conn_ctx->ws, wlp_wss_connect_send); + schedule_work(&conn_ctx->ws); + result = 1; + } else if (eda_entry->state == WLP_WSS_CONNECT_FAILED) { + /* Previous connection attempts failed, don't retry - see + * conditions for connection in WLP 0.99 [7.6.2] */ + if (printk_ratelimit()) + dev_err(dev, "Could not connect to neighbor " + "previously. Not retrying. \n"); + result = -ENONET; + goto out; + } else { /* eda_entry->state == WLP_WSS_CONNECTED */ + d_printf(5, dev, "Neighbor is connected, preparing frame.\n"); + result = wlp_wss_prep_hdr(wlp, eda_entry, skb); + } +out: + d_fnend(5, dev, "wlp %p, result = %d \n", wlp, result); + return result; +} + +/** + * Emulate broadcast: copy skb, send copy to neighbor (connect if not already) + * + * We need to copy skbs in the case where we emulate broadcast through + * unicast. We copy instead of clone because we are modifying the data of + * the frame after copying ... clones share data so we cannot emulate + * broadcast using clones. + * + * run with eda->lock held (spinlock) + */ +int wlp_wss_send_copy(struct wlp *wlp, struct wlp_eda_node *eda_entry, + void *_skb) +{ + int result = -ENOMEM; + struct device *dev = &wlp->rc->uwb_dev.dev; + struct sk_buff *skb = _skb; + struct sk_buff *copy; + struct uwb_dev_addr *dev_addr = &eda_entry->dev_addr; + + d_fnstart(5, dev, "to neighbor %02x:%02x, skb (%p) \n", + dev_addr->data[1], dev_addr->data[0], skb); + copy = skb_copy(skb, GFP_ATOMIC); + if (copy == NULL) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to copy skb for " + "transmission.\n"); + goto out; + } + result = wlp_wss_connect_prep(wlp, eda_entry, copy); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to connect/send skb " + "to neighbor.\n"); + dev_kfree_skb_irq(copy); + goto out; + } else if (result == 1) + /* Frame will be transmitted separately */ + goto out; + BUG_ON(wlp->xmit_frame == NULL); + result = wlp->xmit_frame(wlp, copy, dev_addr); + if (result < 0) { + if (printk_ratelimit()) + dev_err(dev, "WLP: Unable to transmit frame: %d\n", + result); + if ((result == -ENXIO) && printk_ratelimit()) + dev_err(dev, "WLP: Is network interface up? \n"); + /* We could try again ... */ + dev_kfree_skb_irq(copy);/*we need to free if tx fails */ + } +out: + d_fnend(5, dev, "to neighbor %02x:%02x \n", dev_addr->data[1], + dev_addr->data[0]); + return result; +} + + +/** + * Setup WSS + * + * Should be called by network driver after the interface has been given a + * MAC address. + */ +int wlp_wss_setup(struct net_device *net_dev, struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + int result = 0; + d_fnstart(5, dev, "wss (%p) \n", wss); + mutex_lock(&wss->mutex); + wss->kobj.parent = &net_dev->dev.kobj; + if (!is_valid_ether_addr(net_dev->dev_addr)) { + dev_err(dev, "WLP: Invalid MAC address. Cannot use for" + "virtual.\n"); + result = -EINVAL; + goto out; + } + memcpy(wss->virtual_addr.data, net_dev->dev_addr, + sizeof(wss->virtual_addr.data)); +out: + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "wss (%p) \n", wss); + return result; +} +EXPORT_SYMBOL_GPL(wlp_wss_setup); + +/** + * Remove WSS + * + * Called by client that configured WSS through wlp_wss_setup(). This + * function is called when client no longer needs WSS, eg. client shuts + * down. + * + * We remove the WLP IE from the beacon before initiating local cleanup. + */ +void wlp_wss_remove(struct wlp_wss *wss) +{ + struct wlp *wlp = container_of(wss, struct wlp, wss); + struct device *dev = &wlp->rc->uwb_dev.dev; + d_fnstart(5, dev, "wss (%p) \n", wss); + mutex_lock(&wss->mutex); + if (wss->state == WLP_WSS_STATE_ACTIVE) + uwb_rc_ie_rm(wlp->rc, UWB_IE_WLP); + if (wss->state != WLP_WSS_STATE_NONE) { + sysfs_remove_group(&wss->kobj, &wss_attr_group); + kobject_put(&wss->kobj); + } + wss->kobj.parent = NULL; + memset(&wss->virtual_addr, 0, sizeof(wss->virtual_addr)); + /* Cleanup EDA cache */ + wlp_eda_release(&wlp->eda); + wlp_eda_init(&wlp->eda); + mutex_unlock(&wss->mutex); + d_fnend(5, dev, "wss (%p) \n", wss); +} +EXPORT_SYMBOL_GPL(wlp_wss_remove); -- cgit v1.2.3 From 3b0c5a3818555988b6235144e0174b1a512719b7 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 17 Sep 2008 16:34:19 +0100 Subject: uwb: add WiMedia LLC Protocol (build system) Add the WLP build system (Kconfig and Kbuild files). Signed-off-by: David Vrabel --- drivers/uwb/Kconfig | 7 +++++++ drivers/uwb/Makefile | 1 + drivers/uwb/wlp/Makefile | 10 ++++++++++ 3 files changed, 18 insertions(+) create mode 100644 drivers/uwb/wlp/Makefile diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig index a442f39f354..41f8984d198 100644 --- a/drivers/uwb/Kconfig +++ b/drivers/uwb/Kconfig @@ -57,4 +57,11 @@ config UWB_WHCI To compile this driver select Y (built in) or M (module). It is safe to select any even if you do not have the hardware. +config UWB_WLP + tristate "Support WiMedia Link Protocol (Ethernet/IP over UWB)" + depends on UWB && NET + help + This is a common library for drivers that implement + networking over UWB. + endif # UWB diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile index 6bdb8e7f91c..79f48e3d4ec 100644 --- a/drivers/uwb/Makefile +++ b/drivers/uwb/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_UWB) += uwb.o +obj-$(CONFIG_UWB_WLP) += wlp/ obj-$(CONFIG_UWB_WHCI) += umc.o whci.o whc-rc.o obj-$(CONFIG_UWB_HWA) += hwa-rc.o diff --git a/drivers/uwb/wlp/Makefile b/drivers/uwb/wlp/Makefile new file mode 100644 index 00000000000..c72c11db5b1 --- /dev/null +++ b/drivers/uwb/wlp/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_UWB_WLP) := wlp.o + +wlp-objs := \ + driver.o \ + eda.o \ + messages.o \ + sysfs.o \ + txrx.o \ + wlp-lc.o \ + wss-lc.o -- cgit v1.2.3 From 1ba47da527121ff704f4e9f27a12c9f32db05022 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:20 +0100 Subject: uwb: add the i1480 DFU driver Add the driver for downloading the firmware to an Intel i1480 device. Signed-off-by: David Vrabel --- drivers/uwb/Kconfig | 12 + drivers/uwb/Makefile | 1 + drivers/uwb/i1480/Makefile | 1 + drivers/uwb/i1480/dfu/Makefile | 9 + drivers/uwb/i1480/dfu/dfu.c | 281 ++++++++++++++++++++ drivers/uwb/i1480/dfu/i1480-dfu.h | 263 +++++++++++++++++++ drivers/uwb/i1480/dfu/mac.c | 529 ++++++++++++++++++++++++++++++++++++++ drivers/uwb/i1480/dfu/phy.c | 203 +++++++++++++++ drivers/uwb/i1480/dfu/usb.c | 500 +++++++++++++++++++++++++++++++++++ drivers/uwb/i1480/i1480-est.c | 99 +++++++ 10 files changed, 1898 insertions(+) create mode 100644 drivers/uwb/i1480/Makefile create mode 100644 drivers/uwb/i1480/dfu/Makefile create mode 100644 drivers/uwb/i1480/dfu/dfu.c create mode 100644 drivers/uwb/i1480/dfu/i1480-dfu.h create mode 100644 drivers/uwb/i1480/dfu/mac.c create mode 100644 drivers/uwb/i1480/dfu/phy.c create mode 100644 drivers/uwb/i1480/dfu/usb.c create mode 100644 drivers/uwb/i1480/i1480-est.c diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig index 41f8984d198..59acb5b001d 100644 --- a/drivers/uwb/Kconfig +++ b/drivers/uwb/Kconfig @@ -64,4 +64,16 @@ config UWB_WLP This is a common library for drivers that implement networking over UWB. +config UWB_I1480U + tristate "Support for Intel Wireless UWB Link 1480 HWA" + depends on UWB_HWA + select FW_LOADER + help + This driver enables support for the i1480 when connected via + USB. It consists of a firmware uploader that will enable it + to behave as an HWA device. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you do not have the hardware. + endif # UWB diff --git a/drivers/uwb/Makefile b/drivers/uwb/Makefile index 79f48e3d4ec..257e6908304 100644 --- a/drivers/uwb/Makefile +++ b/drivers/uwb/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_UWB) += uwb.o obj-$(CONFIG_UWB_WLP) += wlp/ obj-$(CONFIG_UWB_WHCI) += umc.o whci.o whc-rc.o obj-$(CONFIG_UWB_HWA) += hwa-rc.o +obj-$(CONFIG_UWB_I1480U) += i1480/ uwb-objs := \ address.o \ diff --git a/drivers/uwb/i1480/Makefile b/drivers/uwb/i1480/Makefile new file mode 100644 index 00000000000..d69da1684cf --- /dev/null +++ b/drivers/uwb/i1480/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_UWB_I1480U) += dfu/ i1480-est.o diff --git a/drivers/uwb/i1480/dfu/Makefile b/drivers/uwb/i1480/dfu/Makefile new file mode 100644 index 00000000000..bd1b9f25424 --- /dev/null +++ b/drivers/uwb/i1480/dfu/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_UWB_I1480U) += i1480-dfu-usb.o + +i1480-dfu-usb-objs := \ + dfu.o \ + mac.o \ + phy.o \ + usb.o + + diff --git a/drivers/uwb/i1480/dfu/dfu.c b/drivers/uwb/i1480/dfu/dfu.c new file mode 100644 index 00000000000..ebffaf54215 --- /dev/null +++ b/drivers/uwb/i1480/dfu/dfu.c @@ -0,0 +1,281 @@ +/* + * Intel Wireless UWB Link 1480 + * Main driver + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Common code for firmware upload used by the USB and PCI version; + * i1480_fw_upload() takes a device descriptor and uses the function + * pointers it provides to upload firmware and prepare the PHY. + * + * As well, provides common functions used by the rest of the code. + */ +#include "i1480-dfu.h" +#include +#include +#include +#include +#include +#include + +#define D_LOCAL 0 +#include + +/** @return 0 if If @evt is a valid reply event; otherwise complain */ +int i1480_rceb_check(const struct i1480 *i1480, const struct uwb_rceb *rceb, + const char *cmd, u8 context, + unsigned expected_type, unsigned expected_event) +{ + int result = 0; + struct device *dev = i1480->dev; + if (rceb->bEventContext != context) { + dev_err(dev, "%s: " + "unexpected context id 0x%02x (expected 0x%02x)\n", + cmd, rceb->bEventContext, context); + result = -EINVAL; + } + if (rceb->bEventType != expected_type) { + dev_err(dev, "%s: " + "unexpected event type 0x%02x (expected 0x%02x)\n", + cmd, rceb->bEventType, expected_type); + result = -EINVAL; + } + if (le16_to_cpu(rceb->wEvent) != expected_event) { + dev_err(dev, "%s: " + "unexpected event 0x%04x (expected 0x%04x)\n", + cmd, le16_to_cpu(rceb->wEvent), expected_event); + result = -EINVAL; + } + return result; +} +EXPORT_SYMBOL_GPL(i1480_rceb_check); + + +/** + * Execute a Radio Control Command + * + * Command data has to be in i1480->cmd_buf. + * + * @returns size of the reply data filled in i1480->evt_buf or < 0 errno + * code on error. + */ +ssize_t i1480_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size, + size_t reply_size) +{ + ssize_t result; + struct uwb_rceb *reply = i1480->evt_buf; + struct uwb_rccb *cmd = i1480->cmd_buf; + u16 expected_event = reply->wEvent; + u8 expected_type = reply->bEventType; + u8 context; + + d_fnstart(3, i1480->dev, "(%p, %s, %zu)\n", i1480, cmd_name, cmd_size); + init_completion(&i1480->evt_complete); + i1480->evt_result = -EINPROGRESS; + do { + get_random_bytes(&context, 1); + } while (context == 0x00 || context == 0xff); + cmd->bCommandContext = context; + result = i1480->cmd(i1480, cmd_name, cmd_size); + if (result < 0) + goto error; + /* wait for the callback to report a event was received */ + result = wait_for_completion_interruptible_timeout( + &i1480->evt_complete, HZ); + if (result == 0) { + result = -ETIMEDOUT; + goto error; + } + if (result < 0) + goto error; + result = i1480->evt_result; + if (result < 0) { + dev_err(i1480->dev, "%s: command reply reception failed: %zd\n", + cmd_name, result); + goto error; + } + if (result != reply_size) { + dev_err(i1480->dev, "%s returned only %zu bytes, %zu expected\n", + cmd_name, result, reply_size); + result = -EINVAL; + goto error; + } + /* Verify we got the right event in response */ + result = i1480_rceb_check(i1480, i1480->evt_buf, cmd_name, context, + expected_type, expected_event); +error: + d_fnend(3, i1480->dev, "(%p, %s, %zu) = %zd\n", + i1480, cmd_name, cmd_size, result); + return result; +} +EXPORT_SYMBOL_GPL(i1480_cmd); + + +/** + * Get information about the MAC and PHY + * + * @wa: Wired adaptor + * @neh: Notification/event handler + * @reply: Pointer to the reply event buffer + * @returns: 0 if ok, < 0 errno code on error. + */ +static +int i1480_cmd_get_mac_phy_info(struct i1480 *i1480) +{ + int result; + struct uwb_rccb *cmd = i1480->cmd_buf; + struct i1480_evt_confirm_GMPI *reply = i1480->evt_buf; + + cmd->bCommandType = i1480_CET_VS1; + cmd->wCommand = cpu_to_le16(i1480_CMD_GET_MAC_PHY_INFO); + reply->rceb.bEventType = i1480_CET_VS1; + reply->rceb.wEvent = i1480_EVT_GET_MAC_PHY_INFO; + result = i1480_cmd(i1480, "GET_MAC_PHY_INFO", sizeof(*cmd), + sizeof(*reply)); + if (result < 0) + goto out; + if (le16_to_cpu(reply->status) != 0x00) { + dev_err(i1480->dev, + "GET_MAC_PHY_INFO: command execution failed: %d\n", + reply->status); + result = -EIO; + } +out: + return result; +} + + +/** + * Get i1480's info and print it + * + * @wa: Wire Adapter + * @neh: Notification/event handler + * @returns: 0 if ok, < 0 errno code on error. + */ +static +int i1480_check_info(struct i1480 *i1480) +{ + struct i1480_evt_confirm_GMPI *reply = i1480->evt_buf; + int result; + unsigned mac_fw_rev; +#if i1480_FW <= 0x00000302 + unsigned phy_fw_rev; +#endif + if (i1480->quirk_no_check_info) { + dev_err(i1480->dev, "firmware info check disabled\n"); + return 0; + } + + result = i1480_cmd_get_mac_phy_info(i1480); + if (result < 0) { + dev_err(i1480->dev, "Cannot get MAC & PHY information: %d\n", + result); + goto out; + } + mac_fw_rev = le16_to_cpu(reply->mac_fw_rev); +#if i1480_FW > 0x00000302 + dev_info(i1480->dev, + "HW v%02hx " + "MAC FW v%02hx.%02hx caps %04hx " + "PHY type %02hx v%02hx caps %02hx %02hx %02hx\n", + reply->hw_rev, mac_fw_rev >> 8, mac_fw_rev & 0xff, + le16_to_cpu(reply->mac_caps), + reply->phy_vendor, reply->phy_rev, + reply->phy_caps[0], reply->phy_caps[1], reply->phy_caps[2]); +#else + phy_fw_rev = le16_to_cpu(reply->phy_fw_rev); + dev_info(i1480->dev, "MAC FW v%02hx.%02hx caps %04hx " + " PHY FW v%02hx.%02hx caps %04hx\n", + mac_fw_rev >> 8, mac_fw_rev & 0xff, + le16_to_cpu(reply->mac_caps), + phy_fw_rev >> 8, phy_fw_rev & 0xff, + le16_to_cpu(reply->phy_caps)); +#endif + dev_dbg(i1480->dev, + "key-stores:%hu mcast-addr-stores:%hu sec-modes:%hu\n", + (unsigned short) reply->key_stores, + le16_to_cpu(reply->mcast_addr_stores), + (unsigned short) reply->sec_mode_supported); + /* FIXME: complain if fw version too low -- pending for + * numbering to stabilize */ +out: + return result; +} + + +static +int i1480_print_state(struct i1480 *i1480) +{ + int result; + u32 *buf = (u32 *) i1480->cmd_buf; + + result = i1480->read(i1480, 0x80080000, 2 * sizeof(*buf)); + if (result < 0) { + dev_err(i1480->dev, "cannot read U & L states: %d\n", result); + goto error; + } + dev_info(i1480->dev, "state U 0x%08x, L 0x%08x\n", buf[0], buf[1]); +error: + return result; +} + + +/* + * PCI probe, firmware uploader + * + * _mac_fw_upload() will call rc_setup(), which needs an rc_release(). + */ +int i1480_fw_upload(struct i1480 *i1480) +{ + int result; + + result = i1480_pre_fw_upload(i1480); /* PHY pre fw */ + if (result < 0 && result != -ENOENT) { + i1480_print_state(i1480); + goto error; + } + result = i1480_mac_fw_upload(i1480); /* MAC fw */ + if (result < 0) { + if (result == -ENOENT) + dev_err(i1480->dev, "Cannot locate MAC FW file '%s'\n", + i1480->mac_fw_name); + else + i1480_print_state(i1480); + goto error; + } + result = i1480_phy_fw_upload(i1480); /* PHY fw */ + if (result < 0 && result != -ENOENT) { + i1480_print_state(i1480); + goto error_rc_release; + } + result = i1480_check_info(i1480); + if (result < 0) { + dev_warn(i1480->dev, "Warning! Cannot check firmware info: %d\n", + result); + result = 0; + } + dev_info(i1480->dev, "firmware uploaded successfully\n"); +error_rc_release: + if (i1480->rc_release) + i1480->rc_release(i1480); + result = 0; +error: + return result; +} +EXPORT_SYMBOL_GPL(i1480_fw_upload); diff --git a/drivers/uwb/i1480/dfu/i1480-dfu.h b/drivers/uwb/i1480/dfu/i1480-dfu.h new file mode 100644 index 00000000000..4103b287ac7 --- /dev/null +++ b/drivers/uwb/i1480/dfu/i1480-dfu.h @@ -0,0 +1,263 @@ +/* + * i1480 Device Firmware Upload + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver is the firmware uploader for the Intel Wireless UWB + * Link 1480 device (both in the USB and PCI incarnations). + * + * The process is quite simple: we stop the device, write the firmware + * to its memory and then restart it. Wait for the device to let us + * know it is done booting firmware. Ready. + * + * We might have to upload before or after a phy firmware (which might + * be done in two methods, using a normal firmware image or through + * the MPI port). + * + * Because USB and PCI use common methods, we just make ops out of the + * common operations (read, write, wait_init_done and cmd) and + * implement them in usb.c and pci.c. + * + * The flow is (some parts omitted): + * + * i1480_{usb,pci}_probe() On enumerate/discovery + * i1480_fw_upload() + * i1480_pre_fw_upload() + * __mac_fw_upload() + * fw_hdrs_load() + * mac_fw_hdrs_push() + * i1480->write() [i1480_{usb,pci}_write()] + * i1480_fw_cmp() + * i1480->read() [i1480_{usb,pci}_read()] + * i1480_mac_fw_upload() + * __mac_fw_upload() + * i1480->setup(() + * i1480->wait_init_done() + * i1480_cmd_reset() + * i1480->cmd() [i1480_{usb,pci}_cmd()] + * ... + * i1480_phy_fw_upload() + * request_firmware() + * i1480_mpi_write() + * i1480->cmd() [i1480_{usb,pci}_cmd()] + * i1480_check_info() + * + * Once the probe function enumerates the device and uploads the + * firmware, we just exit with -ENODEV, as we don't really want to + * attach to the device. + */ +#ifndef __i1480_DFU_H__ +#define __i1480_DFU_H__ + +#include +#include +#include + +#define i1480_FW_UPLOAD_MODE_MASK (cpu_to_le32(0x00000018)) + +#if i1480_FW > 0x00000302 +#define i1480_RCEB_EXTENDED +#endif + +struct uwb_rccb; +struct uwb_rceb; + +/* + * Common firmware upload handlers + * + * Normally you embed this struct in another one specific to your hw. + * + * @write Write to device's memory from buffer. + * @read Read from device's memory to i1480->evt_buf. + * @setup Setup device after basic firmware is uploaded + * @wait_init_done + * Wait for the device to send a notification saying init + * is done. + * @cmd FOP for issuing the command to the hardware. The + * command data is contained in i1480->cmd_buf and the size + * is supplied as an argument. The command replied is put + * in i1480->evt_buf and the size in i1480->evt_result (or if + * an error, a < 0 errno code). + * + * @cmd_buf Memory buffer used to send commands to the device. + * Allocated by the upper layers i1480_fw_upload(). + * Size has to be @buf_size. + * @evt_buf Memory buffer used to place the async notifications + * received by the hw. Allocated by the upper layers + * i1480_fw_upload(). + * Size has to be @buf_size. + * @cmd_complete + * Low level driver uses this to notify code waiting afor + * an event that the event has arrived and data is in + * i1480->evt_buf (and size/result in i1480->evt_result). + * @hw_rev + * Use this value to activate dfu code to support new revisions + * of hardware. i1480_init() sets this to a default value. + * It should be updated by the USB and PCI code. + */ +struct i1480 { + struct device *dev; + + int (*write)(struct i1480 *, u32 addr, const void *, size_t); + int (*read)(struct i1480 *, u32 addr, size_t); + int (*rc_setup)(struct i1480 *); + void (*rc_release)(struct i1480 *); + int (*wait_init_done)(struct i1480 *); + int (*cmd)(struct i1480 *, const char *cmd_name, size_t cmd_size); + const char *pre_fw_name; + const char *mac_fw_name; + const char *mac_fw_name_deprecate; /* FIXME: Will go away */ + const char *phy_fw_name; + u8 hw_rev; + + size_t buf_size; /* size of both evt_buf and cmd_buf */ + void *evt_buf, *cmd_buf; + ssize_t evt_result; + struct completion evt_complete; + + u8 quirk_no_check_info:1; +}; + +static inline +void i1480_init(struct i1480 *i1480) +{ + i1480->hw_rev = 1; + init_completion(&i1480->evt_complete); +} + +extern int i1480_fw_upload(struct i1480 *); +extern int i1480_pre_fw_upload(struct i1480 *); +extern int i1480_mac_fw_upload(struct i1480 *); +extern int i1480_phy_fw_upload(struct i1480 *); +extern ssize_t i1480_cmd(struct i1480 *, const char *, size_t, size_t); +extern int i1480_rceb_check(const struct i1480 *, + const struct uwb_rceb *, const char *, u8, + unsigned, unsigned); + +enum { + /* Vendor specific command type */ + i1480_CET_VS1 = 0xfd, + /* i1480 commands */ + i1480_CMD_SET_IP_MAS = 0x000e, + i1480_CMD_GET_MAC_PHY_INFO = 0x0003, + i1480_CMD_MPI_WRITE = 0x000f, + i1480_CMD_MPI_READ = 0x0010, + /* i1480 events */ +#if i1480_FW > 0x00000302 + i1480_EVT_CONFIRM = 0x0002, + i1480_EVT_RM_INIT_DONE = 0x0101, + i1480_EVT_DEV_ADD = 0x0103, + i1480_EVT_DEV_RM = 0x0104, + i1480_EVT_DEV_ID_CHANGE = 0x0105, + i1480_EVT_GET_MAC_PHY_INFO = i1480_CMD_GET_MAC_PHY_INFO, +#else + i1480_EVT_CONFIRM = 0x0002, + i1480_EVT_RM_INIT_DONE = 0x0101, + i1480_EVT_DEV_ADD = 0x0103, + i1480_EVT_DEV_RM = 0x0104, + i1480_EVT_DEV_ID_CHANGE = 0x0105, + i1480_EVT_GET_MAC_PHY_INFO = i1480_EVT_CONFIRM, +#endif +}; + + +struct i1480_evt_confirm { + struct uwb_rceb rceb; +#ifdef i1480_RCEB_EXTENDED + __le16 wParamLength; +#endif + u8 bResultCode; +} __attribute__((packed)); + + +struct i1480_rceb { + struct uwb_rceb rceb; +#ifdef i1480_RCEB_EXTENDED + __le16 wParamLength; +#endif +} __attribute__((packed)); + + +/** + * Get MAC & PHY Information confirm event structure + * + * Confirm event returned by the command. + */ +struct i1480_evt_confirm_GMPI { +#if i1480_FW > 0x00000302 + struct uwb_rceb rceb; + __le16 wParamLength; + __le16 status; + u8 mac_addr[6]; /* EUI-64 bit IEEE address [still 8 bytes?] */ + u8 dev_addr[2]; + __le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */ + u8 hw_rev; + u8 phy_vendor; + u8 phy_rev; /* major v = >> 8; minor = v & 0xff */ + __le16 mac_caps; + u8 phy_caps[3]; + u8 key_stores; + __le16 mcast_addr_stores; + u8 sec_mode_supported; +#else + struct uwb_rceb rceb; + u8 status; + u8 mac_addr[8]; /* EUI-64 bit IEEE address [still 8 bytes?] */ + u8 dev_addr[2]; + __le16 mac_fw_rev; /* major = v >> 8; minor = v & 0xff */ + __le16 phy_fw_rev; /* major v = >> 8; minor = v & 0xff */ + __le16 mac_caps; + u8 phy_caps; + u8 key_stores; + __le16 mcast_addr_stores; + u8 sec_mode_supported; +#endif +} __attribute__((packed)); + + +struct i1480_cmd_mpi_write { + struct uwb_rccb rccb; + __le16 size; + u8 data[]; +}; + + +struct i1480_cmd_mpi_read { + struct uwb_rccb rccb; + __le16 size; + struct { + u8 page, offset; + } __attribute__((packed)) data[]; +} __attribute__((packed)); + + +struct i1480_evt_mpi_read { + struct uwb_rceb rceb; +#ifdef i1480_RCEB_EXTENDED + __le16 wParamLength; +#endif + u8 bResultCode; + __le16 size; + struct { + u8 page, offset, value; + } __attribute__((packed)) data[]; +} __attribute__((packed)); + + +#endif /* #ifndef __i1480_DFU_H__ */ diff --git a/drivers/uwb/i1480/dfu/mac.c b/drivers/uwb/i1480/dfu/mac.c new file mode 100644 index 00000000000..3d445541e8e --- /dev/null +++ b/drivers/uwb/i1480/dfu/mac.c @@ -0,0 +1,529 @@ +/* + * Intel Wireless UWB Link 1480 + * MAC Firmware upload implementation + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Implementation of the code for parsing the firmware file (extract + * the headers and binary code chunks) in the fw_*() functions. The + * code to upload pre and mac firmwares is the same, so it uses a + * common entry point in __mac_fw_upload(), which uses the i1480 + * function pointers to push the firmware to the device. + */ +#include +#include +#include +#include "i1480-dfu.h" + +#define D_LOCAL 0 +#include + +/* + * Descriptor for a continuous segment of MAC fw data + */ +struct fw_hdr { + unsigned long address; + size_t length; + const u32 *bin; + struct fw_hdr *next; +}; + + +/* Free a chain of firmware headers */ +static +void fw_hdrs_free(struct fw_hdr *hdr) +{ + struct fw_hdr *next; + + while (hdr) { + next = hdr->next; + kfree(hdr); + hdr = next; + } +} + + +/* Fill a firmware header descriptor from a memory buffer */ +static +int fw_hdr_load(struct i1480 *i1480, struct fw_hdr *hdr, unsigned hdr_cnt, + const char *_data, const u32 *data_itr, const u32 *data_top) +{ + size_t hdr_offset = (const char *) data_itr - _data; + size_t remaining_size = (void *) data_top - (void *) data_itr; + if (data_itr + 2 > data_top) { + dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in header at " + "offset %zu, limit %zu\n", + hdr_cnt, hdr_offset, + (const char *) data_itr + 2 - _data, + (const char *) data_top - _data); + return -EINVAL; + } + hdr->next = NULL; + hdr->address = le32_to_cpu(*data_itr++); + hdr->length = le32_to_cpu(*data_itr++); + hdr->bin = data_itr; + if (hdr->length > remaining_size) { + dev_err(i1480->dev, "fw hdr #%u/%zu: EOF reached in data; " + "chunk too long (%zu bytes), only %zu left\n", + hdr_cnt, hdr_offset, hdr->length, remaining_size); + return -EINVAL; + } + return 0; +} + + +/** + * Get a buffer where the firmware is supposed to be and create a + * chain of headers linking them together. + * + * @phdr: where to place the pointer to the first header (headers link + * to the next via the @hdr->next ptr); need to free the whole + * chain when done. + * + * @_data: Pointer to the data buffer. + * + * @_data_size: Size of the data buffer (bytes); data size has to be a + * multiple of 4. Function will fail if not. + * + * Goes over the whole binary blob; reads the first chunk and creates + * a fw hdr from it (which points to where the data is in @_data and + * the length of the chunk); then goes on to the next chunk until + * done. Each header is linked to the next. + */ +static +int fw_hdrs_load(struct i1480 *i1480, struct fw_hdr **phdr, + const char *_data, size_t data_size) +{ + int result; + unsigned hdr_cnt = 0; + u32 *data = (u32 *) _data, *data_itr, *data_top; + struct fw_hdr *hdr, **prev_hdr = phdr; + + result = -EINVAL; + /* Check size is ok and pointer is aligned */ + if (data_size % sizeof(u32) != 0) + goto error; + if ((unsigned long) _data % sizeof(u16) != 0) + goto error; + *phdr = NULL; + data_itr = data; + data_top = (u32 *) (_data + data_size); + while (data_itr < data_top) { + result = -ENOMEM; + hdr = kmalloc(sizeof(*hdr), GFP_KERNEL); + if (hdr == NULL) { + dev_err(i1480->dev, "Cannot allocate fw header " + "for chunk #%u\n", hdr_cnt); + goto error_alloc; + } + result = fw_hdr_load(i1480, hdr, hdr_cnt, + _data, data_itr, data_top); + if (result < 0) + goto error_load; + data_itr += 2 + hdr->length; + *prev_hdr = hdr; + prev_hdr = &hdr->next; + hdr_cnt++; + }; + *prev_hdr = NULL; + return 0; + +error_load: + kfree(hdr); +error_alloc: + fw_hdrs_free(*phdr); +error: + return result; +} + + +/** + * Compares a chunk of fw with one in the devices's memory + * + * @i1480: Device instance + * @hdr: Pointer to the firmware chunk + * @returns: 0 if equal, < 0 errno on error. If > 0, it is the offset + * where the difference was found (plus one). + * + * Kind of dirty and simplistic, but does the trick in both the PCI + * and USB version. We do a quick[er] memcmp(), and if it fails, we do + * a byte-by-byte to find the offset. + */ +static +ssize_t i1480_fw_cmp(struct i1480 *i1480, struct fw_hdr *hdr) +{ + ssize_t result = 0; + u32 src_itr = 0, cnt; + size_t size = hdr->length*sizeof(hdr->bin[0]); + size_t chunk_size; + u8 *bin = (u8 *) hdr->bin; + + while (size > 0) { + chunk_size = size < i1480->buf_size ? size : i1480->buf_size; + result = i1480->read(i1480, hdr->address + src_itr, chunk_size); + if (result < 0) { + dev_err(i1480->dev, "error reading for verification: " + "%zd\n", result); + goto error; + } + if (memcmp(i1480->cmd_buf, bin + src_itr, result)) { + u8 *buf = i1480->cmd_buf; + d_printf(2, i1480->dev, + "original data @ %p + %u, %zu bytes\n", + bin, src_itr, result); + d_dump(4, i1480->dev, bin + src_itr, result); + for (cnt = 0; cnt < result; cnt++) + if (bin[src_itr + cnt] != buf[cnt]) { + dev_err(i1480->dev, "byte failed at " + "src_itr %u cnt %u [0x%02x " + "vs 0x%02x]\n", src_itr, cnt, + bin[src_itr + cnt], buf[cnt]); + result = src_itr + cnt + 1; + goto cmp_failed; + } + } + src_itr += result; + size -= result; + } + result = 0; +error: +cmp_failed: + return result; +} + + +/** + * Writes firmware headers to the device. + * + * @prd: PRD instance + * @hdr: Processed firmware + * @returns: 0 if ok, < 0 errno on error. + */ +static +int mac_fw_hdrs_push(struct i1480 *i1480, struct fw_hdr *hdr, + const char *fw_name, const char *fw_tag) +{ + struct device *dev = i1480->dev; + ssize_t result = 0; + struct fw_hdr *hdr_itr; + int verif_retry_count; + + d_fnstart(3, dev, "(%p, %p)\n", i1480, hdr); + /* Now, header by header, push them to the hw */ + for (hdr_itr = hdr; hdr_itr != NULL; hdr_itr = hdr_itr->next) { + verif_retry_count = 0; +retry: + dev_dbg(dev, "fw chunk (%zu @ 0x%08lx)\n", + hdr_itr->length * sizeof(hdr_itr->bin[0]), + hdr_itr->address); + result = i1480->write(i1480, hdr_itr->address, hdr_itr->bin, + hdr_itr->length*sizeof(hdr_itr->bin[0])); + if (result < 0) { + dev_err(dev, "%s fw '%s': write failed (%zuB @ 0x%lx):" + " %zd\n", fw_tag, fw_name, + hdr_itr->length * sizeof(hdr_itr->bin[0]), + hdr_itr->address, result); + break; + } + result = i1480_fw_cmp(i1480, hdr_itr); + if (result < 0) { + dev_err(dev, "%s fw '%s': verification read " + "failed (%zuB @ 0x%lx): %zd\n", + fw_tag, fw_name, + hdr_itr->length * sizeof(hdr_itr->bin[0]), + hdr_itr->address, result); + break; + } + if (result > 0) { /* Offset where it failed + 1 */ + result--; + dev_err(dev, "%s fw '%s': WARNING: verification " + "failed at 0x%lx: retrying\n", + fw_tag, fw_name, hdr_itr->address + result); + if (++verif_retry_count < 3) + goto retry; /* write this block again! */ + dev_err(dev, "%s fw '%s': verification failed at 0x%lx: " + "tried %d times\n", fw_tag, fw_name, + hdr_itr->address + result, verif_retry_count); + result = -EINVAL; + break; + } + } + d_fnend(3, dev, "(%zd)\n", result); + return result; +} + + +/** Puts the device in firmware upload mode.*/ +static +int mac_fw_upload_enable(struct i1480 *i1480) +{ + int result; + u32 reg = 0x800000c0; + u32 *buffer = (u32 *)i1480->cmd_buf; + + if (i1480->hw_rev > 1) + reg = 0x8000d0d4; + result = i1480->read(i1480, reg, sizeof(u32)); + if (result < 0) + goto error_cmd; + *buffer &= ~i1480_FW_UPLOAD_MODE_MASK; + result = i1480->write(i1480, reg, buffer, sizeof(u32)); + if (result < 0) + goto error_cmd; + return 0; +error_cmd: + dev_err(i1480->dev, "can't enable fw upload mode: %d\n", result); + return result; +} + + +/** Gets the device out of firmware upload mode. */ +static +int mac_fw_upload_disable(struct i1480 *i1480) +{ + int result; + u32 reg = 0x800000c0; + u32 *buffer = (u32 *)i1480->cmd_buf; + + if (i1480->hw_rev > 1) + reg = 0x8000d0d4; + result = i1480->read(i1480, reg, sizeof(u32)); + if (result < 0) + goto error_cmd; + *buffer |= i1480_FW_UPLOAD_MODE_MASK; + result = i1480->write(i1480, reg, buffer, sizeof(u32)); + if (result < 0) + goto error_cmd; + return 0; +error_cmd: + dev_err(i1480->dev, "can't disable fw upload mode: %d\n", result); + return result; +} + + + +/** + * Generic function for uploading a MAC firmware. + * + * @i1480: Device instance + * @fw_name: Name of firmware file to upload. + * @fw_tag: Name of the firmware type (for messages) + * [eg: MAC, PRE] + * @do_wait: Wait for device to emit initialization done message (0 + * for PRE fws, 1 for MAC fws). + * @returns: 0 if ok, < 0 errno on error. + */ +static +int __mac_fw_upload(struct i1480 *i1480, const char *fw_name, + const char *fw_tag) +{ + int result; + const struct firmware *fw; + struct fw_hdr *fw_hdrs; + + d_fnstart(3, i1480->dev, "(%p, %s, %s)\n", i1480, fw_name, fw_tag); + result = request_firmware(&fw, fw_name, i1480->dev); + if (result < 0) /* Up to caller to complain on -ENOENT */ + goto out; + d_printf(3, i1480->dev, "%s fw '%s': uploading\n", fw_tag, fw_name); + result = fw_hdrs_load(i1480, &fw_hdrs, fw->data, fw->size); + if (result < 0) { + dev_err(i1480->dev, "%s fw '%s': failed to parse firmware " + "file: %d\n", fw_tag, fw_name, result); + goto out_release; + } + result = mac_fw_upload_enable(i1480); + if (result < 0) + goto out_hdrs_release; + result = mac_fw_hdrs_push(i1480, fw_hdrs, fw_name, fw_tag); + mac_fw_upload_disable(i1480); +out_hdrs_release: + if (result >= 0) + dev_info(i1480->dev, "%s fw '%s': uploaded\n", fw_tag, fw_name); + else + dev_err(i1480->dev, "%s fw '%s': failed to upload (%d), " + "power cycle device\n", fw_tag, fw_name, result); + fw_hdrs_free(fw_hdrs); +out_release: + release_firmware(fw); +out: + d_fnend(3, i1480->dev, "(%p, %s, %s) = %d\n", i1480, fw_name, fw_tag, + result); + return result; +} + + +/** + * Upload a pre-PHY firmware + * + */ +int i1480_pre_fw_upload(struct i1480 *i1480) +{ + int result; + result = __mac_fw_upload(i1480, i1480->pre_fw_name, "PRE"); + if (result == 0) + msleep(400); + return result; +} + + +/** + * Reset a the MAC and PHY + * + * @i1480: Device's instance + * @returns: 0 if ok, < 0 errno code on error + * + * We put the command on kmalloc'ed memory as some arches cannot do + * USB from the stack. The reply event is copied from an stage buffer, + * so it can be in the stack. See WUSB1.0[8.6.2.4] for more details. + * + * We issue the reset to make sure the UWB controller reinits the PHY; + * this way we can now if the PHY init went ok. + */ +static +int i1480_cmd_reset(struct i1480 *i1480) +{ + int result; + struct uwb_rccb *cmd = (void *) i1480->cmd_buf; + struct i1480_evt_reset { + struct uwb_rceb rceb; + u8 bResultCode; + } __attribute__((packed)) *reply = (void *) i1480->evt_buf; + + result = -ENOMEM; + cmd->bCommandType = UWB_RC_CET_GENERAL; + cmd->wCommand = cpu_to_le16(UWB_RC_CMD_RESET); + reply->rceb.bEventType = UWB_RC_CET_GENERAL; + reply->rceb.wEvent = UWB_RC_CMD_RESET; + result = i1480_cmd(i1480, "RESET", sizeof(*cmd), sizeof(*reply)); + if (result < 0) + goto out; + if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(i1480->dev, "RESET: command execution failed: %u\n", + reply->bResultCode); + result = -EIO; + } +out: + return result; + +} + + +/** Wait for the MAC FW to start running */ +static +int i1480_fw_is_running_q(struct i1480 *i1480) +{ + int cnt = 0; + int result; + u32 *val = (u32 *) i1480->cmd_buf; + + d_fnstart(3, i1480->dev, "(i1480 %p)\n", i1480); + for (cnt = 0; cnt < 10; cnt++) { + msleep(100); + result = i1480->read(i1480, 0x80080000, 4); + if (result < 0) { + dev_err(i1480->dev, "Can't read 0x8008000: %d\n", result); + goto out; + } + if (*val == 0x55555555UL) /* fw running? cool */ + goto out; + if (printk_ratelimit()) + d_printf(5, i1480->dev, "read #%d: 0x%08x\n", cnt, *val); + } + dev_err(i1480->dev, "Timed out waiting for fw to start\n"); + result = -ETIMEDOUT; +out: + d_fnend(3, i1480->dev, "(i1480 %p) = %d\n", i1480, result); + return result; + +} + + +/** + * Upload MAC firmware, wait for it to start + * + * @i1480: Device instance + * @fw_name: Name of the file that contains the firmware + * + * This has to be called after the pre fw has been uploaded (if + * there is any). + */ +int i1480_mac_fw_upload(struct i1480 *i1480) +{ + int result = 0, deprecated_name = 0; + struct i1480_rceb *rcebe = (void *) i1480->evt_buf; + + d_fnstart(3, i1480->dev, "(%p)\n", i1480); + result = __mac_fw_upload(i1480, i1480->mac_fw_name, "MAC"); + if (result == -ENOENT) { + result = __mac_fw_upload(i1480, i1480->mac_fw_name_deprecate, + "MAC"); + deprecated_name = 1; + } + if (result < 0) + return result; + if (deprecated_name == 1) + dev_warn(i1480->dev, + "WARNING: firmware file name %s is deprecated, " + "please rename to %s\n", + i1480->mac_fw_name_deprecate, i1480->mac_fw_name); + result = i1480_fw_is_running_q(i1480); + if (result < 0) + goto error_fw_not_running; + result = i1480->rc_setup ? i1480->rc_setup(i1480) : 0; + if (result < 0) { + dev_err(i1480->dev, "Cannot setup after MAC fw upload: %d\n", + result); + goto error_setup; + } + result = i1480->wait_init_done(i1480); /* wait init'on */ + if (result < 0) { + dev_err(i1480->dev, "MAC fw '%s': Initialization timed out " + "(%d)\n", i1480->mac_fw_name, result); + goto error_init_timeout; + } + /* verify we got the right initialization done event */ + if (i1480->evt_result != sizeof(*rcebe)) { + dev_err(i1480->dev, "MAC fw '%s': initialization event returns " + "wrong size (%zu bytes vs %zu needed)\n", + i1480->mac_fw_name, i1480->evt_result, sizeof(*rcebe)); + dump_bytes(i1480->dev, rcebe, min(i1480->evt_result, (ssize_t)32)); + goto error_size; + } + result = -EIO; + if (rcebe->rceb.bEventType != i1480_CET_VS1 + || le16_to_cpu(rcebe->rceb.wEvent) != i1480_EVT_RM_INIT_DONE) { + dev_err(i1480->dev, "wrong initialization event 0x%02x/%04x/%02x " + "received; expected 0x%02x/%04x/00\n", + rcebe->rceb.bEventType, le16_to_cpu(rcebe->rceb.wEvent), + rcebe->rceb.bEventContext, i1480_CET_VS1, + i1480_EVT_RM_INIT_DONE); + goto error_init_timeout; + } + result = i1480_cmd_reset(i1480); + if (result < 0) + dev_err(i1480->dev, "MAC fw '%s': MBOA reset failed (%d)\n", + i1480->mac_fw_name, result); +error_fw_not_running: +error_init_timeout: +error_size: +error_setup: + d_fnend(3, i1480->dev, "(i1480 %p) = %d\n", i1480, result); + return result; +} diff --git a/drivers/uwb/i1480/dfu/phy.c b/drivers/uwb/i1480/dfu/phy.c new file mode 100644 index 00000000000..3b1a87de8e6 --- /dev/null +++ b/drivers/uwb/i1480/dfu/phy.c @@ -0,0 +1,203 @@ +/* + * Intel Wireless UWB Link 1480 + * PHY parameters upload + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Code for uploading the PHY parameters to the PHY through the UWB + * Radio Control interface. + * + * We just send the data through the MPI interface using HWA-like + * commands and then reset the PHY to make sure it is ok. + */ +#include +#include +#include +#include +#include "i1480-dfu.h" + + +/** + * Write a value array to an address of the MPI interface + * + * @i1480: Device descriptor + * @data: Data array to write + * @size: Size of the data array + * @returns: 0 if ok, < 0 errno code on error. + * + * The data array is organized into pairs: + * + * ADDRESS VALUE + * + * ADDRESS is BE 16 bit unsigned, VALUE 8 bit unsigned. Size thus has + * to be a multiple of three. + */ +static +int i1480_mpi_write(struct i1480 *i1480, const void *data, size_t size) +{ + int result; + struct i1480_cmd_mpi_write *cmd = i1480->cmd_buf; + struct i1480_evt_confirm *reply = i1480->evt_buf; + + BUG_ON(size > 480); + result = -ENOMEM; + cmd->rccb.bCommandType = i1480_CET_VS1; + cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_WRITE); + cmd->size = cpu_to_le16(size); + memcpy(cmd->data, data, size); + reply->rceb.bEventType = i1480_CET_VS1; + reply->rceb.wEvent = i1480_CMD_MPI_WRITE; + result = i1480_cmd(i1480, "MPI-WRITE", sizeof(*cmd) + size, sizeof(*reply)); + if (result < 0) + goto out; + if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(i1480->dev, "MPI-WRITE: command execution failed: %d\n", + reply->bResultCode); + result = -EIO; + } +out: + return result; +} + + +/** + * Read a value array to from an address of the MPI interface + * + * @i1480: Device descriptor + * @data: where to place the read array + * @srcaddr: Where to read from + * @size: Size of the data read array + * @returns: 0 if ok, < 0 errno code on error. + * + * The command data array is organized into pairs ADDR0 ADDR1..., and + * the returned data in ADDR0 VALUE0 ADDR1 VALUE1... + * + * We generate the command array to be a sequential read and then + * rearrange the result. + * + * We use the i1480->cmd_buf for the command, i1480->evt_buf for the reply. + * + * As the reply has to fit in 512 bytes (i1480->evt_buffer), the max amount + * of values we can read is (512 - sizeof(*reply)) / 3 + */ +static +int i1480_mpi_read(struct i1480 *i1480, u8 *data, u16 srcaddr, size_t size) +{ + int result; + struct i1480_cmd_mpi_read *cmd = i1480->cmd_buf; + struct i1480_evt_mpi_read *reply = i1480->evt_buf; + unsigned cnt; + + memset(i1480->cmd_buf, 0x69, 512); + memset(i1480->evt_buf, 0x69, 512); + + BUG_ON(size > (i1480->buf_size - sizeof(*reply)) / 3); + result = -ENOMEM; + cmd->rccb.bCommandType = i1480_CET_VS1; + cmd->rccb.wCommand = cpu_to_le16(i1480_CMD_MPI_READ); + cmd->size = cpu_to_le16(3*size); + for (cnt = 0; cnt < size; cnt++) { + cmd->data[cnt].page = (srcaddr + cnt) >> 8; + cmd->data[cnt].offset = (srcaddr + cnt) & 0xff; + } + reply->rceb.bEventType = i1480_CET_VS1; + reply->rceb.wEvent = i1480_CMD_MPI_READ; + result = i1480_cmd(i1480, "MPI-READ", sizeof(*cmd) + 2*size, + sizeof(*reply) + 3*size); + if (result < 0) + goto out; + if (reply->bResultCode != UWB_RC_RES_SUCCESS) { + dev_err(i1480->dev, "MPI-READ: command execution failed: %d\n", + reply->bResultCode); + result = -EIO; + } + for (cnt = 0; cnt < size; cnt++) { + if (reply->data[cnt].page != (srcaddr + cnt) >> 8) + dev_err(i1480->dev, "MPI-READ: page inconsistency at " + "index %u: expected 0x%02x, got 0x%02x\n", cnt, + (srcaddr + cnt) >> 8, reply->data[cnt].page); + if (reply->data[cnt].offset != ((srcaddr + cnt) & 0x00ff)) + dev_err(i1480->dev, "MPI-READ: offset inconsistency at " + "index %u: expected 0x%02x, got 0x%02x\n", cnt, + (srcaddr + cnt) & 0x00ff, + reply->data[cnt].offset); + data[cnt] = reply->data[cnt].value; + } + result = 0; +out: + return result; +} + + +/** + * Upload a PHY firmware, wait for it to start + * + * @i1480: Device instance + * @fw_name: Name of the file that contains the firmware + * + * We assume the MAC fw is up and running. This means we can use the + * MPI interface to write the PHY firmware. Once done, we issue an + * MBOA Reset, which will force the MAC to reset and reinitialize the + * PHY. If that works, we are ready to go. + * + * Max packet size for the MPI write is 512, so the max buffer is 480 + * (which gives us 160 byte triads of MSB, LSB and VAL for the data). + */ +int i1480_phy_fw_upload(struct i1480 *i1480) +{ + int result; + const struct firmware *fw; + const char *data_itr, *data_top; + const size_t MAX_BLK_SIZE = 480; /* 160 triads */ + size_t data_size; + u8 phy_stat; + + result = request_firmware(&fw, i1480->phy_fw_name, i1480->dev); + if (result < 0) + goto out; + /* Loop writing data in chunks as big as possible until done. */ + for (data_itr = fw->data, data_top = data_itr + fw->size; + data_itr < data_top; data_itr += MAX_BLK_SIZE) { + data_size = min(MAX_BLK_SIZE, (size_t) (data_top - data_itr)); + result = i1480_mpi_write(i1480, data_itr, data_size); + if (result < 0) + goto error_mpi_write; + } + /* Read MPI page 0, offset 6; if 0, PHY was initialized correctly. */ + result = i1480_mpi_read(i1480, &phy_stat, 0x0006, 1); + if (result < 0) { + dev_err(i1480->dev, "PHY: can't get status: %d\n", result); + goto error_mpi_status; + } + if (phy_stat != 0) { + result = -ENODEV; + dev_info(i1480->dev, "error, PHY not ready: %u\n", phy_stat); + goto error_phy_status; + } + dev_info(i1480->dev, "PHY fw '%s': uploaded\n", i1480->phy_fw_name); +error_phy_status: +error_mpi_status: +error_mpi_write: + release_firmware(fw); + if (result < 0) + dev_err(i1480->dev, "PHY fw '%s': failed to upload (%d), " + "power cycle device\n", i1480->phy_fw_name, result); +out: + return result; +} diff --git a/drivers/uwb/i1480/dfu/usb.c b/drivers/uwb/i1480/dfu/usb.c new file mode 100644 index 00000000000..98eeeff051a --- /dev/null +++ b/drivers/uwb/i1480/dfu/usb.c @@ -0,0 +1,500 @@ +/* + * Intel Wireless UWB Link 1480 + * USB SKU firmware upload implementation + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver will prepare the i1480 device to behave as a real + * Wireless USB HWA adaptor by uploading the firmware. + * + * When the device is connected or driver is loaded, i1480_usb_probe() + * is called--this will allocate and initialize the device structure, + * fill in the pointers to the common functions (read, write, + * wait_init_done and cmd for HWA command execution) and once that is + * done, call the common firmware uploading routine. Then clean up and + * return -ENODEV, as we don't attach to the device. + * + * The rest are the basic ops we implement that the fw upload code + * uses to do its job. All the ops in the common code are i1480->NAME, + * the functions are i1480_usb_NAME(). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "i1480-dfu.h" + +#define D_LOCAL 0 +#include + + +struct i1480_usb { + struct i1480 i1480; + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + struct urb *neep_urb; /* URB for reading from EP1 */ +}; + + +static +void i1480_usb_init(struct i1480_usb *i1480_usb) +{ + i1480_init(&i1480_usb->i1480); +} + + +static +int i1480_usb_create(struct i1480_usb *i1480_usb, struct usb_interface *iface) +{ + struct usb_device *usb_dev = interface_to_usbdev(iface); + int result = -ENOMEM; + + i1480_usb->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */ + i1480_usb->usb_iface = usb_get_intf(iface); + usb_set_intfdata(iface, i1480_usb); /* Bind the driver to iface0 */ + i1480_usb->neep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (i1480_usb->neep_urb == NULL) + goto error; + return 0; + +error: + usb_set_intfdata(iface, NULL); + usb_put_intf(iface); + usb_put_dev(usb_dev); + return result; +} + + +static +void i1480_usb_destroy(struct i1480_usb *i1480_usb) +{ + usb_kill_urb(i1480_usb->neep_urb); + usb_free_urb(i1480_usb->neep_urb); + usb_set_intfdata(i1480_usb->usb_iface, NULL); + usb_put_intf(i1480_usb->usb_iface); + usb_put_dev(i1480_usb->usb_dev); +} + + +/** + * Write a buffer to a memory address in the i1480 device + * + * @i1480: i1480 instance + * @memory_address: + * Address where to write the data buffer to. + * @buffer: Buffer to the data + * @size: Size of the buffer [has to be < 512]. + * @returns: 0 if ok, < 0 errno code on error. + * + * Data buffers to USB cannot be on the stack or in vmalloc'ed areas, + * so we copy it to the local i1480 buffer before proceeding. In any + * case, we have a max size we can send, soooo. + */ +static +int i1480_usb_write(struct i1480 *i1480, u32 memory_address, + const void *buffer, size_t size) +{ + int result = 0; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + size_t buffer_size, itr = 0; + + d_fnstart(3, i1480->dev, "(%p, 0x%08x, %p, %zu)\n", + i1480, memory_address, buffer, size); + BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */ + while (size > 0) { + buffer_size = size < i1480->buf_size ? size : i1480->buf_size; + memcpy(i1480->cmd_buf, buffer + itr, buffer_size); + result = usb_control_msg( + i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0), + 0xf0, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + cpu_to_le16(memory_address & 0xffff), + cpu_to_le16((memory_address >> 16) & 0xffff), + i1480->cmd_buf, buffer_size, 100 /* FIXME: arbitrary */); + if (result < 0) + break; + d_printf(3, i1480->dev, + "wrote @ 0x%08x %u bytes (of %zu bytes requested)\n", + memory_address, result, buffer_size); + d_dump(4, i1480->dev, i1480->cmd_buf, result); + itr += result; + memory_address += result; + size -= result; + } + d_fnend(3, i1480->dev, "(%p, 0x%08x, %p, %zu) = %d\n", + i1480, memory_address, buffer, size, result); + return result; +} + + +/** + * Read a block [max size 512] of the device's memory to @i1480's buffer. + * + * @i1480: i1480 instance + * @memory_address: + * Address where to read from. + * @size: Size to read. Smaller than or equal to 512. + * @returns: >= 0 number of bytes written if ok, < 0 errno code on error. + * + * NOTE: if the memory address or block is incorrect, you might get a + * stall or a different memory read. Caller has to verify the + * memory address and size passed back in the @neh structure. + */ +static +int i1480_usb_read(struct i1480 *i1480, u32 addr, size_t size) +{ + ssize_t result = 0, bytes = 0; + size_t itr, read_size = i1480->buf_size; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + + d_fnstart(3, i1480->dev, "(%p, 0x%08x, %zu)\n", + i1480, addr, size); + BUG_ON(size > i1480->buf_size); + BUG_ON(size & 0x3); /* Needs to be a multiple of 4 */ + BUG_ON(read_size > 512); + + if (addr >= 0x8000d200 && addr < 0x8000d400) /* Yeah, HW quirk */ + read_size = 4; + + for (itr = 0; itr < size; itr += read_size) { + size_t itr_addr = addr + itr; + size_t itr_size = min(read_size, size - itr); + result = usb_control_msg( + i1480_usb->usb_dev, usb_rcvctrlpipe(i1480_usb->usb_dev, 0), + 0xf0, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + cpu_to_le16(itr_addr & 0xffff), + cpu_to_le16((itr_addr >> 16) & 0xffff), + i1480->cmd_buf + itr, itr_size, + 100 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(i1480->dev, "%s: USB read error: %zd\n", + __func__, result); + goto out; + } + if (result != itr_size) { + result = -EIO; + dev_err(i1480->dev, + "%s: partial read got only %zu bytes vs %zu expected\n", + __func__, result, itr_size); + goto out; + } + bytes += result; + } + result = bytes; +out: + d_fnend(3, i1480->dev, "(%p, 0x%08x, %zu) = %zd\n", + i1480, addr, size, result); + if (result > 0) + d_dump(4, i1480->dev, i1480->cmd_buf, result); + return result; +} + + +/** + * Callback for reads on the notification/event endpoint + * + * Just enables the completion read handler. + */ +static +void i1480_usb_neep_cb(struct urb *urb) +{ + struct i1480 *i1480 = urb->context; + struct device *dev = i1480->dev; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + dev_dbg(dev, "NEEP: reset/noent %d\n", urb->status); + break; + case -ESHUTDOWN: /* going away! */ + dev_dbg(dev, "NEEP: down %d\n", urb->status); + break; + default: + dev_err(dev, "NEEP: unknown status %d\n", urb->status); + break; + } + i1480->evt_result = urb->actual_length; + complete(&i1480->evt_complete); + return; +} + + +/** + * Wait for the MAC FW to initialize + * + * MAC FW sends a 0xfd/0101/00 notification to EP1 when done + * initializing. Get that notification into i1480->evt_buf; upper layer + * will verify it. + * + * Set i1480->evt_result with the result of getting the event or its + * size (if succesful). + * + * Delivers the data directly to i1480->evt_buf + */ +static +int i1480_usb_wait_init_done(struct i1480 *i1480) +{ + int result; + struct device *dev = i1480->dev; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + struct usb_endpoint_descriptor *epd; + + d_fnstart(3, dev, "(%p)\n", i1480); + init_completion(&i1480->evt_complete); + i1480->evt_result = -EINPROGRESS; + epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb(i1480_usb->neep_urb, i1480_usb->usb_dev, + usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress), + i1480->evt_buf, i1480->buf_size, + i1480_usb_neep_cb, i1480, epd->bInterval); + result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "init done: cannot submit NEEP read: %d\n", + result); + goto error_submit; + } + /* Wait for the USB callback to get the data */ + result = wait_for_completion_interruptible_timeout( + &i1480->evt_complete, HZ); + if (result <= 0) { + result = result == 0 ? -ETIMEDOUT : result; + goto error_wait; + } + usb_kill_urb(i1480_usb->neep_urb); + d_fnend(3, dev, "(%p) = 0\n", i1480); + return 0; + +error_wait: + usb_kill_urb(i1480_usb->neep_urb); +error_submit: + i1480->evt_result = result; + d_fnend(3, dev, "(%p) = %d\n", i1480, result); + return result; +} + + +/** + * Generic function for issuing commands to the i1480 + * + * @i1480: i1480 instance + * @cmd_name: Name of the command (for error messages) + * @cmd: Pointer to command buffer + * @cmd_size: Size of the command buffer + * @reply: Buffer for the reply event + * @reply_size: Expected size back (including RCEB); the reply buffer + * is assumed to be as big as this. + * @returns: >= 0 size of the returned event data if ok, + * < 0 errno code on error. + * + * Arms the NE handle, issues the command to the device and checks the + * basics of the reply event. + */ +static +int i1480_usb_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size) +{ + int result; + struct device *dev = i1480->dev; + struct i1480_usb *i1480_usb = container_of(i1480, struct i1480_usb, i1480); + struct usb_endpoint_descriptor *epd; + struct uwb_rccb *cmd = i1480->cmd_buf; + u8 iface_no; + + d_fnstart(3, dev, "(%p, %s, %zu)\n", i1480, cmd_name, cmd_size); + /* Post a read on the notification & event endpoint */ + iface_no = i1480_usb->usb_iface->cur_altsetting->desc.bInterfaceNumber; + epd = &i1480_usb->usb_iface->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb( + i1480_usb->neep_urb, i1480_usb->usb_dev, + usb_rcvintpipe(i1480_usb->usb_dev, epd->bEndpointAddress), + i1480->evt_buf, i1480->buf_size, + i1480_usb_neep_cb, i1480, epd->bInterval); + result = usb_submit_urb(i1480_usb->neep_urb, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "%s: cannot submit NEEP read: %d\n", + cmd_name, result); + goto error_submit_ep1; + } + /* Now post the command on EP0 */ + result = usb_control_msg( + i1480_usb->usb_dev, usb_sndctrlpipe(i1480_usb->usb_dev, 0), + WA_EXEC_RC_CMD, + USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_CLASS, + 0, iface_no, + cmd, cmd_size, + 100 /* FIXME: this is totally arbitrary */); + if (result < 0) { + dev_err(dev, "%s: control request failed: %d\n", + cmd_name, result); + goto error_submit_ep0; + } + d_fnend(3, dev, "(%p, %s, %zu) = %d\n", + i1480, cmd_name, cmd_size, result); + return result; + +error_submit_ep0: + usb_kill_urb(i1480_usb->neep_urb); +error_submit_ep1: + d_fnend(3, dev, "(%p, %s, %zu) = %d\n", + i1480, cmd_name, cmd_size, result); + return result; +} + + +/* + * Probe a i1480 device for uploading firmware. + * + * We attach only to interface #0, which is the radio control interface. + */ +static +int i1480_usb_probe(struct usb_interface *iface, const struct usb_device_id *id) +{ + struct i1480_usb *i1480_usb; + struct i1480 *i1480; + struct device *dev = &iface->dev; + int result; + + result = -ENODEV; + if (iface->cur_altsetting->desc.bInterfaceNumber != 0) { + dev_dbg(dev, "not attaching to iface %d\n", + iface->cur_altsetting->desc.bInterfaceNumber); + goto error; + } + if (iface->num_altsetting > 1 + && interface_to_usbdev(iface)->descriptor.idProduct == 0xbabe) { + /* Need altsetting #1 [HW QUIRK] or EP1 won't work */ + result = usb_set_interface(interface_to_usbdev(iface), 0, 1); + if (result < 0) + dev_warn(dev, + "can't set altsetting 1 on iface 0: %d\n", + result); + } + + result = -ENOMEM; + i1480_usb = kzalloc(sizeof(*i1480_usb), GFP_KERNEL); + if (i1480_usb == NULL) { + dev_err(dev, "Unable to allocate instance\n"); + goto error; + } + i1480_usb_init(i1480_usb); + + i1480 = &i1480_usb->i1480; + i1480->buf_size = 512; + i1480->cmd_buf = kmalloc(2 * i1480->buf_size, GFP_KERNEL); + if (i1480->cmd_buf == NULL) { + dev_err(dev, "Cannot allocate transfer buffers\n"); + result = -ENOMEM; + goto error_buf_alloc; + } + i1480->evt_buf = i1480->cmd_buf + i1480->buf_size; + + result = i1480_usb_create(i1480_usb, iface); + if (result < 0) { + dev_err(dev, "Cannot create instance: %d\n", result); + goto error_create; + } + + /* setup the fops and upload the firmare */ + i1480->pre_fw_name = "i1480-pre-phy-0.0.bin"; + i1480->mac_fw_name = "i1480-usb-0.0.bin"; + i1480->mac_fw_name_deprecate = "ptc-0.0.bin"; + i1480->phy_fw_name = "i1480-phy-0.0.bin"; + i1480->dev = &iface->dev; + i1480->write = i1480_usb_write; + i1480->read = i1480_usb_read; + i1480->rc_setup = NULL; + i1480->wait_init_done = i1480_usb_wait_init_done; + i1480->cmd = i1480_usb_cmd; + + result = i1480_fw_upload(&i1480_usb->i1480); /* the real thing */ + if (result >= 0) { + usb_reset_device(i1480_usb->usb_dev); + result = -ENODEV; /* we don't want to bind to the iface */ + } + i1480_usb_destroy(i1480_usb); +error_create: + kfree(i1480->cmd_buf); +error_buf_alloc: + kfree(i1480_usb); +error: + return result; +} + +#define i1480_USB_DEV(v, p) \ +{ \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE \ + | USB_DEVICE_ID_MATCH_DEV_INFO \ + | USB_DEVICE_ID_MATCH_INT_INFO, \ + .idVendor = (v), \ + .idProduct = (p), \ + .bDeviceClass = 0xff, \ + .bDeviceSubClass = 0xff, \ + .bDeviceProtocol = 0xff, \ + .bInterfaceClass = 0xff, \ + .bInterfaceSubClass = 0xff, \ + .bInterfaceProtocol = 0xff, \ +} + + +/** USB device ID's that we handle */ +static struct usb_device_id i1480_usb_id_table[] = { + i1480_USB_DEV(0x8086, 0xdf3b), + i1480_USB_DEV(0x15a9, 0x0005), + i1480_USB_DEV(0x07d1, 0x3802), + i1480_USB_DEV(0x050d, 0x305a), + i1480_USB_DEV(0x3495, 0x3007), + {}, +}; +MODULE_DEVICE_TABLE(usb, i1480_usb_id_table); + + +static struct usb_driver i1480_dfu_driver = { + .name = "i1480-dfu-usb", + .id_table = i1480_usb_id_table, + .probe = i1480_usb_probe, + .disconnect = NULL, +}; + + +/* + * Initialize the i1480 DFU driver. + * + * We also need to register our function for guessing event sizes. + */ +static int __init i1480_dfu_driver_init(void) +{ + return usb_register(&i1480_dfu_driver); +} +module_init(i1480_dfu_driver_init); + + +static void __exit i1480_dfu_driver_exit(void) +{ + usb_deregister(&i1480_dfu_driver); +} +module_exit(i1480_dfu_driver_exit); + + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Intel Wireless UWB Link 1480 firmware uploader for USB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/i1480/i1480-est.c b/drivers/uwb/i1480/i1480-est.c new file mode 100644 index 00000000000..7bf8c6febae --- /dev/null +++ b/drivers/uwb/i1480/i1480-est.c @@ -0,0 +1,99 @@ +/* + * Intel Wireless UWB Link 1480 + * Event Size tables for Wired Adaptors + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include +#include +#include +#include +#include "dfu/i1480-dfu.h" + + +/** Event size table for wEvents 0x00XX */ +static struct uwb_est_entry i1480_est_fd00[] = { + /* Anybody expecting this response has to use + * neh->extra_size to specify the real size that will + * come back. */ + [i1480_EVT_CONFIRM] = { .size = sizeof(struct i1480_evt_confirm) }, + [i1480_CMD_SET_IP_MAS] = { .size = sizeof(struct i1480_evt_confirm) }, +#ifdef i1480_RCEB_EXTENDED + [0x09] = { + .size = sizeof(struct i1480_rceb), + .offset = 1 + offsetof(struct i1480_rceb, wParamLength), + }, +#endif +}; + +/** Event size table for wEvents 0x01XX */ +static struct uwb_est_entry i1480_est_fd01[] = { + [0xff & i1480_EVT_RM_INIT_DONE] = { .size = sizeof(struct i1480_rceb) }, + [0xff & i1480_EVT_DEV_ADD] = { .size = sizeof(struct i1480_rceb) + 9 }, + [0xff & i1480_EVT_DEV_RM] = { .size = sizeof(struct i1480_rceb) + 9 }, + [0xff & i1480_EVT_DEV_ID_CHANGE] = { + .size = sizeof(struct i1480_rceb) + 2 }, +}; + +static int i1480_est_init(void) +{ + int result = uwb_est_register(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b, + i1480_est_fd00, + ARRAY_SIZE(i1480_est_fd00)); + if (result < 0) { + printk(KERN_ERR "Can't register EST table fd00: %d\n", result); + return result; + } + result = uwb_est_register(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b, + i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01)); + if (result < 0) { + printk(KERN_ERR "Can't register EST table fd01: %d\n", result); + return result; + } + return 0; +} +module_init(i1480_est_init); + +static void i1480_est_exit(void) +{ + uwb_est_unregister(i1480_CET_VS1, 0x00, 0x8086, 0x0c3b, + i1480_est_fd00, ARRAY_SIZE(i1480_est_fd00)); + uwb_est_unregister(i1480_CET_VS1, 0x01, 0x8086, 0x0c3b, + i1480_est_fd01, ARRAY_SIZE(i1480_est_fd01)); +} +module_exit(i1480_est_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("i1480's Vendor Specific Event Size Tables"); +MODULE_LICENSE("GPL"); + +/** + * USB device ID's that we handle + * + * [so we are loaded when this kind device is connected] + */ +static struct usb_device_id i1480_est_id_table[] = { + { USB_DEVICE(0x8086, 0xdf3b), }, + { USB_DEVICE(0x8086, 0x0c3b), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, i1480_est_id_table); -- cgit v1.2.3 From a21b963aa4a98c645b1fa3799f2e4a6ebb6c974a Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:21 +0100 Subject: uwb: add the i1480 WLP driver Add the driver for the WLP capability of the Intel i1480 device. Signed-off-by: David Vrabel --- drivers/uwb/Kconfig | 10 + drivers/uwb/i1480/Makefile | 1 + drivers/uwb/i1480/i1480-wlp.h | 200 ++++++++++ drivers/uwb/i1480/i1480u-wlp/Makefile | 8 + drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h | 284 ++++++++++++++ drivers/uwb/i1480/i1480u-wlp/lc.c | 421 ++++++++++++++++++++ drivers/uwb/i1480/i1480u-wlp/netdev.c | 368 +++++++++++++++++ drivers/uwb/i1480/i1480u-wlp/rx.c | 486 +++++++++++++++++++++++ drivers/uwb/i1480/i1480u-wlp/sysfs.c | 408 +++++++++++++++++++ drivers/uwb/i1480/i1480u-wlp/tx.c | 632 ++++++++++++++++++++++++++++++ 10 files changed, 2818 insertions(+) create mode 100644 drivers/uwb/i1480/i1480-wlp.h create mode 100644 drivers/uwb/i1480/i1480u-wlp/Makefile create mode 100644 drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h create mode 100644 drivers/uwb/i1480/i1480u-wlp/lc.c create mode 100644 drivers/uwb/i1480/i1480u-wlp/netdev.c create mode 100644 drivers/uwb/i1480/i1480u-wlp/rx.c create mode 100644 drivers/uwb/i1480/i1480u-wlp/sysfs.c create mode 100644 drivers/uwb/i1480/i1480u-wlp/tx.c diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig index 59acb5b001d..317e9fefbc0 100644 --- a/drivers/uwb/Kconfig +++ b/drivers/uwb/Kconfig @@ -76,4 +76,14 @@ config UWB_I1480U To compile this driver select Y (built in) or M (module). It is safe to select any even if you do not have the hardware. +config UWB_I1480U_WLP + tristate "Support for Intel Wireless UWB Link 1480 HWA's WLP interface" + depends on UWB_I1480U && UWB_WLP && NET + help + This driver enables WLP support for the i1480 when connected via + USB. WLP is the WiMedia Link Protocol, or IP over UWB. + + To compile this driver select Y (built in) or M (module). It + is safe to select any even if you don't have the hardware. + endif # UWB diff --git a/drivers/uwb/i1480/Makefile b/drivers/uwb/i1480/Makefile index d69da1684cf..212bbc7d4c3 100644 --- a/drivers/uwb/i1480/Makefile +++ b/drivers/uwb/i1480/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_UWB_I1480U) += dfu/ i1480-est.o +obj-$(CONFIG_UWB_I1480U_WLP) += i1480u-wlp/ diff --git a/drivers/uwb/i1480/i1480-wlp.h b/drivers/uwb/i1480/i1480-wlp.h new file mode 100644 index 00000000000..18a8b0e4567 --- /dev/null +++ b/drivers/uwb/i1480/i1480-wlp.h @@ -0,0 +1,200 @@ +/* + * Intel 1480 Wireless UWB Link + * WLP specific definitions + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#ifndef __i1480_wlp_h__ +#define __i1480_wlp_h__ + +#include +#include +#include +#include +#include + +/* New simplified header format? */ +#undef WLP_HDR_FMT_2 /* FIXME: rename */ + +/** + * Values of the Delivery ID & Type field when PCA or DRP + * + * The Delivery ID & Type field in the WLP TX header indicates whether + * the frame is PCA or DRP. This is done based on the high level bit of + * this field. + * We use this constant to test if the traffic is PCA or DRP as follows: + * if (wlp_tx_hdr_delivery_id_type(wlp_tx_hdr) & WLP_DRP) + * this is DRP traffic + * else + * this is PCA traffic + */ +enum deliver_id_type_bit { + WLP_DRP = 8, +}; + +/** + * WLP TX header + * + * Indicates UWB/WLP-specific transmission parameters for a network + * packet. + */ +struct wlp_tx_hdr { + /* dword 0 */ + struct uwb_dev_addr dstaddr; + u8 key_index; + u8 mac_params; + /* dword 1 */ + u8 phy_params; +#ifndef WLP_HDR_FMT_2 + u8 reserved; + __le16 oui01; /* FIXME: not so sure if __le16 or u8[2] */ + /* dword 2 */ + u8 oui2; /* if all LE, it could be merged */ + __le16 prid; +#endif +} __attribute__((packed)); + +static inline int wlp_tx_hdr_delivery_id_type(const struct wlp_tx_hdr *hdr) +{ + return hdr->mac_params & 0x0f; +} + +static inline int wlp_tx_hdr_ack_policy(const struct wlp_tx_hdr *hdr) +{ + return (hdr->mac_params >> 4) & 0x07; +} + +static inline int wlp_tx_hdr_rts_cts(const struct wlp_tx_hdr *hdr) +{ + return (hdr->mac_params >> 7) & 0x01; +} + +static inline void wlp_tx_hdr_set_delivery_id_type(struct wlp_tx_hdr *hdr, int id) +{ + hdr->mac_params = (hdr->mac_params & ~0x0f) | id; +} + +static inline void wlp_tx_hdr_set_ack_policy(struct wlp_tx_hdr *hdr, + enum uwb_ack_pol policy) +{ + hdr->mac_params = (hdr->mac_params & ~0x70) | (policy << 4); +} + +static inline void wlp_tx_hdr_set_rts_cts(struct wlp_tx_hdr *hdr, int rts_cts) +{ + hdr->mac_params = (hdr->mac_params & ~0x80) | (rts_cts << 7); +} + +static inline enum uwb_phy_rate wlp_tx_hdr_phy_rate(const struct wlp_tx_hdr *hdr) +{ + return hdr->phy_params & 0x0f; +} + +static inline int wlp_tx_hdr_tx_power(const struct wlp_tx_hdr *hdr) +{ + return (hdr->phy_params >> 4) & 0x0f; +} + +static inline void wlp_tx_hdr_set_phy_rate(struct wlp_tx_hdr *hdr, enum uwb_phy_rate rate) +{ + hdr->phy_params = (hdr->phy_params & ~0x0f) | rate; +} + +static inline void wlp_tx_hdr_set_tx_power(struct wlp_tx_hdr *hdr, int pwr) +{ + hdr->phy_params = (hdr->phy_params & ~0xf0) | (pwr << 4); +} + + +/** + * WLP RX header + * + * Provides UWB/WLP-specific transmission data for a received + * network packet. + */ +struct wlp_rx_hdr { + /* dword 0 */ + struct uwb_dev_addr dstaddr; + struct uwb_dev_addr srcaddr; + /* dword 1 */ + u8 LQI; + s8 RSSI; + u8 reserved3; +#ifndef WLP_HDR_FMT_2 + u8 oui0; + /* dword 2 */ + __le16 oui12; + __le16 prid; +#endif +} __attribute__((packed)); + + +/** User configurable options for WLP */ +struct wlp_options { + struct mutex mutex; /* access to user configurable options*/ + struct wlp_tx_hdr def_tx_hdr; /* default tx hdr */ + u8 pca_base_priority; + u8 bw_alloc; /*index into bw_allocs[] for PCA/DRP reservations*/ +}; + + +static inline +void wlp_options_init(struct wlp_options *options) +{ + mutex_init(&options->mutex); + wlp_tx_hdr_set_ack_policy(&options->def_tx_hdr, UWB_ACK_INM); + wlp_tx_hdr_set_rts_cts(&options->def_tx_hdr, 1); + /* FIXME: default to phy caps */ + wlp_tx_hdr_set_phy_rate(&options->def_tx_hdr, UWB_PHY_RATE_480); +#ifndef WLP_HDR_FMT_2 + options->def_tx_hdr.prid = cpu_to_le16(0x0000); +#endif +} + + +/* sysfs helpers */ + +extern ssize_t uwb_pca_base_priority_store(struct wlp_options *, + const char *, size_t); +extern ssize_t uwb_pca_base_priority_show(const struct wlp_options *, char *); +extern ssize_t uwb_bw_alloc_store(struct wlp_options *, const char *, size_t); +extern ssize_t uwb_bw_alloc_show(const struct wlp_options *, char *); +extern ssize_t uwb_ack_policy_store(struct wlp_options *, + const char *, size_t); +extern ssize_t uwb_ack_policy_show(const struct wlp_options *, char *); +extern ssize_t uwb_rts_cts_store(struct wlp_options *, const char *, size_t); +extern ssize_t uwb_rts_cts_show(const struct wlp_options *, char *); +extern ssize_t uwb_phy_rate_store(struct wlp_options *, const char *, size_t); +extern ssize_t uwb_phy_rate_show(const struct wlp_options *, char *); + + +/** Simple bandwidth allocation (temporary and too simple) */ +struct wlp_bw_allocs { + const char *name; + struct { + u8 mask, stream; + } tx, rx; +}; + + +#endif /* #ifndef __i1480_wlp_h__ */ diff --git a/drivers/uwb/i1480/i1480u-wlp/Makefile b/drivers/uwb/i1480/i1480u-wlp/Makefile new file mode 100644 index 00000000000..fe6709b8e68 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_UWB_I1480U_WLP) += i1480u-wlp.o + +i1480u-wlp-objs := \ + lc.o \ + netdev.o \ + rx.o \ + sysfs.o \ + tx.o diff --git a/drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h b/drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h new file mode 100644 index 00000000000..5f1b2951bb8 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/i1480u-wlp.h @@ -0,0 +1,284 @@ +/* + * Intel 1480 Wireless UWB Link USB + * Header formats, constants, general internal interfaces + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This is not an standard interface. + * + * FIXME: docs + * + * i1480u-wlp is pretty simple: two endpoints, one for tx, one for + * rx. rx is polled. Network packets (ethernet, whatever) are wrapped + * in i1480 TX or RX headers (for sending over the air), and these + * packets are wrapped in UNTD headers (for sending to the WLP UWB + * controller). + * + * UNTD packets (UNTD hdr + i1480 hdr + network packet) packets + * cannot be bigger than i1480u_MAX_FRG_SIZE. When this happens, the + * i1480 packet is broken in chunks/packets: + * + * UNTD-1st.hdr + i1480.hdr + payload + * UNTD-next.hdr + payload + * ... + * UNTD-last.hdr + payload + * + * so that each packet is smaller or equal than i1480u_MAX_FRG_SIZE. + * + * All HW structures and bitmaps are little endian, so we need to play + * ugly tricks when defining bitfields. Hoping for the day GCC + * implements __attribute__((endian(1234))). + * + * FIXME: ROADMAP to the whole implementation + */ + +#ifndef __i1480u_wlp_h__ +#define __i1480u_wlp_h__ + +#include +#include +#include /* struct uwb_rc, struct uwb_notifs_handler */ +#include +#include "../i1480-wlp.h" + +#undef i1480u_FLOW_CONTROL /* Enable flow control code */ + +/** + * Basic flow control + */ +enum { + i1480u_TX_INFLIGHT_MAX = 1000, + i1480u_TX_INFLIGHT_THRESHOLD = 100, +}; + +/** Maximum size of a transaction that we can tx/rx */ +enum { + /* Maximum packet size computed as follows: max UNTD header (8) + + * i1480 RX header (8) + max Ethernet header and payload (4096) + + * Padding added by skb_reserve (2) to make post Ethernet payload + * start on 16 byte boundary*/ + i1480u_MAX_RX_PKT_SIZE = 4114, + i1480u_MAX_FRG_SIZE = 512, + i1480u_RX_BUFS = 9, +}; + + +/** + * UNTD packet type + * + * We need to fragment any payload whose UNTD packet is going to be + * bigger than i1480u_MAX_FRG_SIZE. + */ +enum i1480u_pkt_type { + i1480u_PKT_FRAG_1ST = 0x1, + i1480u_PKT_FRAG_NXT = 0x0, + i1480u_PKT_FRAG_LST = 0x2, + i1480u_PKT_FRAG_CMP = 0x3 +}; +enum { + i1480u_PKT_NONE = 0x4, +}; + +/** USB Network Transfer Descriptor - common */ +struct untd_hdr { + u8 type; + __le16 len; +} __attribute__((packed)); + +static inline enum i1480u_pkt_type untd_hdr_type(const struct untd_hdr *hdr) +{ + return hdr->type & 0x03; +} + +static inline int untd_hdr_rx_tx(const struct untd_hdr *hdr) +{ + return (hdr->type >> 2) & 0x01; +} + +static inline void untd_hdr_set_type(struct untd_hdr *hdr, enum i1480u_pkt_type type) +{ + hdr->type = (hdr->type & ~0x03) | type; +} + +static inline void untd_hdr_set_rx_tx(struct untd_hdr *hdr, int rx_tx) +{ + hdr->type = (hdr->type & ~0x04) | (rx_tx << 2); +} + + +/** + * USB Network Transfer Descriptor - Complete Packet + * + * This is for a packet that is smaller (header + payload) than + * i1480u_MAX_FRG_SIZE. + * + * @hdr.total_len is the size of the payload; the payload doesn't + * count this header nor the padding, but includes the size of i1480 + * header. + */ +struct untd_hdr_cmp { + struct untd_hdr hdr; + u8 padding; +} __attribute__((packed)); + + +/** + * USB Network Transfer Descriptor - First fragment + * + * @hdr.len is the size of the *whole packet* (excluding UNTD + * headers); @fragment_len is the size of the payload (excluding UNTD + * headers, but including i1480 headers). + */ +struct untd_hdr_1st { + struct untd_hdr hdr; + __le16 fragment_len; + u8 padding[3]; +} __attribute__((packed)); + + +/** + * USB Network Transfer Descriptor - Next / Last [Rest] + * + * @hdr.len is the size of the payload, not including headrs. + */ +struct untd_hdr_rst { + struct untd_hdr hdr; + u8 padding; +} __attribute__((packed)); + + +/** + * Transmission context + * + * Wraps all the stuff needed to track a pending/active tx + * operation. + */ +struct i1480u_tx { + struct list_head list_node; + struct i1480u *i1480u; + struct urb *urb; + + struct sk_buff *skb; + struct wlp_tx_hdr *wlp_tx_hdr; + + void *buf; /* if NULL, no new buf was used */ + size_t buf_size; +}; + +/** + * Basic flow control + * + * We maintain a basic flow control counter. "count" how many TX URBs are + * outstanding. Only allow "max" + * TX URBs to be outstanding. If this value is reached the queue will be + * stopped. The queue will be restarted when there are + * "threshold" URBs outstanding. + * Maintain a counter of how many time the TX queue needed to be restarted + * due to the "max" being exceeded and the "threshold" reached again. The + * timestamp "restart_ts" is to keep track from when the counter was last + * queried (see sysfs handling of file wlp_tx_inflight). + */ +struct i1480u_tx_inflight { + atomic_t count; + unsigned long max; + unsigned long threshold; + unsigned long restart_ts; + atomic_t restart_count; +}; + +/** + * Instance of a i1480u WLP interface + * + * Keeps references to the USB device that wraps it, as well as it's + * interface and associated UWB host controller. As well, it also + * keeps a link to the netdevice for integration into the networking + * stack. + * We maintian separate error history for the tx and rx endpoints because + * the implementation does not rely on locking - having one shared + * structure between endpoints may cause problems. Adding locking to the + * implementation will have higher cost than adding a separate structure. + */ +struct i1480u { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + struct net_device *net_dev; + + spinlock_t lock; + struct net_device_stats stats; + + /* RX context handling */ + struct sk_buff *rx_skb; + struct uwb_dev_addr rx_srcaddr; + size_t rx_untd_pkt_size; + struct i1480u_rx_buf { + struct i1480u *i1480u; /* back pointer */ + struct urb *urb; + struct sk_buff *data; /* i1480u_MAX_RX_PKT_SIZE each */ + } rx_buf[i1480u_RX_BUFS]; /* N bufs */ + + spinlock_t tx_list_lock; /* TX context */ + struct list_head tx_list; + u8 tx_stream; + + struct stats lqe_stats, rssi_stats; /* radio statistics */ + + /* Options we can set from sysfs */ + struct wlp_options options; + struct uwb_notifs_handler uwb_notifs_handler; + struct edc tx_errors; + struct edc rx_errors; + struct wlp wlp; +#ifdef i1480u_FLOW_CONTROL + struct urb *notif_urb; + struct edc notif_edc; /* error density counter */ + u8 notif_buffer[1]; +#endif + struct i1480u_tx_inflight tx_inflight; +}; + +/* Internal interfaces */ +extern void i1480u_rx_cb(struct urb *urb); +extern int i1480u_rx_setup(struct i1480u *); +extern void i1480u_rx_release(struct i1480u *); +extern void i1480u_tx_release(struct i1480u *); +extern int i1480u_xmit_frame(struct wlp *, struct sk_buff *, + struct uwb_dev_addr *); +extern void i1480u_stop_queue(struct wlp *); +extern void i1480u_start_queue(struct wlp *); +extern int i1480u_sysfs_setup(struct i1480u *); +extern void i1480u_sysfs_release(struct i1480u *); + +/* netdev interface */ +extern int i1480u_open(struct net_device *); +extern int i1480u_stop(struct net_device *); +extern int i1480u_hard_start_xmit(struct sk_buff *, struct net_device *); +extern void i1480u_tx_timeout(struct net_device *); +extern int i1480u_set_config(struct net_device *, struct ifmap *); +extern struct net_device_stats *i1480u_get_stats(struct net_device *); +extern int i1480u_change_mtu(struct net_device *, int); +extern void i1480u_uwb_notifs_cb(void *, struct uwb_dev *, enum uwb_notifs); + +/* bandwidth allocation callback */ +extern void i1480u_bw_alloc_cb(struct uwb_rsv *); + +/* Sys FS */ +extern struct attribute_group i1480u_wlp_attr_group; + +#endif /* #ifndef __i1480u_wlp_h__ */ diff --git a/drivers/uwb/i1480/i1480u-wlp/lc.c b/drivers/uwb/i1480/i1480u-wlp/lc.c new file mode 100644 index 00000000000..737d60cd5b7 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/lc.c @@ -0,0 +1,421 @@ +/* + * WUSB Wire Adapter: WLP interface + * Driver for the Linux Network stack. + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * This implements a very simple network driver for the WLP USB + * device that is associated to a UWB (Ultra Wide Band) host. + * + * This is seen as an interface of a composite device. Once the UWB + * host has an association to another WLP capable device, the + * networking interface (aka WLP) can start to send packets back and + * forth. + * + * Limitations: + * + * - Hand cranked; can't ifup the interface until there is an association + * + * - BW allocation very simplistic [see i1480u_mas_set() and callees]. + * + * + * ROADMAP: + * + * ENTRY POINTS (driver model): + * + * i1480u_driver_{exit,init}(): initialization of the driver. + * + * i1480u_probe(): called by the driver code when a device + * matching 'i1480u_id_table' is connected. + * + * This allocs a netdev instance, inits with + * i1480u_add(), then registers_netdev(). + * i1480u_init() + * i1480u_add() + * + * i1480u_disconnect(): device has been disconnected/module + * is being removed. + * i1480u_rm() + */ +#include +#include +#include +#include +#include "i1480u-wlp.h" + + + +static inline +void i1480u_init(struct i1480u *i1480u) +{ + /* nothing so far... doesn't it suck? */ + spin_lock_init(&i1480u->lock); + INIT_LIST_HEAD(&i1480u->tx_list); + spin_lock_init(&i1480u->tx_list_lock); + wlp_options_init(&i1480u->options); + edc_init(&i1480u->tx_errors); + edc_init(&i1480u->rx_errors); +#ifdef i1480u_FLOW_CONTROL + edc_init(&i1480u->notif_edc); +#endif + stats_init(&i1480u->lqe_stats); + stats_init(&i1480u->rssi_stats); + wlp_init(&i1480u->wlp); +} + +/** + * Fill WLP device information structure + * + * The structure will contain a few character arrays, each ending with a + * null terminated string. Each string has to fit (excluding terminating + * character) into a specified range obtained from the WLP substack. + * + * It is still not clear exactly how this device information should be + * obtained. Until we find out we use the USB device descriptor as backup, some + * information elements have intuitive mappings, other not. + */ +static +void i1480u_fill_device_info(struct wlp *wlp, struct wlp_device_info *dev_info) +{ + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct usb_device *usb_dev = i1480u->usb_dev; + /* Treat device name and model name the same */ + if (usb_dev->descriptor.iProduct) { + usb_string(usb_dev, usb_dev->descriptor.iProduct, + dev_info->name, sizeof(dev_info->name)); + usb_string(usb_dev, usb_dev->descriptor.iProduct, + dev_info->model_name, sizeof(dev_info->model_name)); + } + if (usb_dev->descriptor.iManufacturer) + usb_string(usb_dev, usb_dev->descriptor.iManufacturer, + dev_info->manufacturer, + sizeof(dev_info->manufacturer)); + scnprintf(dev_info->model_nr, sizeof(dev_info->model_nr), "%04x", + __le16_to_cpu(usb_dev->descriptor.bcdDevice)); + if (usb_dev->descriptor.iSerialNumber) + usb_string(usb_dev, usb_dev->descriptor.iSerialNumber, + dev_info->serial, sizeof(dev_info->serial)); + /* FIXME: where should we obtain category? */ + dev_info->prim_dev_type.category = cpu_to_le16(WLP_DEV_CAT_OTHER); + /* FIXME: Complete OUI and OUIsubdiv attributes */ +} + +#ifdef i1480u_FLOW_CONTROL +/** + * Callback for the notification endpoint + * + * This mostly controls the xon/xoff protocol. In case of hard error, + * we stop the queue. If not, we always retry. + */ +static +void i1480u_notif_cb(struct urb *urb, struct pt_regs *regs) +{ + struct i1480u *i1480u = urb->context; + struct usb_interface *usb_iface = i1480u->usb_iface; + struct device *dev = &usb_iface->dev; + int result; + + switch (urb->status) { + case 0: /* Got valid data, do xon/xoff */ + switch (i1480u->notif_buffer[0]) { + case 'N': + dev_err(dev, "XOFF STOPPING queue at %lu\n", jiffies); + netif_stop_queue(i1480u->net_dev); + break; + case 'A': + dev_err(dev, "XON STARTING queue at %lu\n", jiffies); + netif_start_queue(i1480u->net_dev); + break; + default: + dev_err(dev, "NEP: unknown data 0x%02hhx\n", + i1480u->notif_buffer[0]); + } + break; + case -ECONNRESET: /* Controlled situation ... */ + case -ENOENT: /* we killed the URB... */ + dev_err(dev, "NEP: URB reset/noent %d\n", urb->status); + goto error; + case -ESHUTDOWN: /* going away! */ + dev_err(dev, "NEP: URB down %d\n", urb->status); + goto error; + default: /* Retry unless it gets ugly */ + if (edc_inc(&i1480u->notif_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "NEP: URB max acceptable errors " + "exceeded; resetting device\n"); + goto error_reset; + } + dev_err(dev, "NEP: URB error %d\n", urb->status); + break; + } + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "NEP: Can't resubmit URB: %d; resetting device\n", + result); + goto error_reset; + } + return; + +error_reset: + wlp_reset_all(&i1480-wlp); +error: + netif_stop_queue(i1480u->net_dev); + return; +} +#endif + +static +int i1480u_add(struct i1480u *i1480u, struct usb_interface *iface) +{ + int result = -ENODEV; + struct wlp *wlp = &i1480u->wlp; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct net_device *net_dev = i1480u->net_dev; + struct uwb_rc *rc; + struct uwb_dev *uwb_dev; +#ifdef i1480u_FLOW_CONTROL + struct usb_endpoint_descriptor *epd; +#endif + + i1480u->usb_dev = usb_get_dev(usb_dev); + i1480u->usb_iface = iface; + rc = uwb_rc_get_by_grandpa(&i1480u->usb_dev->dev); + if (rc == NULL) { + dev_err(&iface->dev, "Cannot get associated UWB Radio " + "Controller\n"); + goto out; + } + wlp->xmit_frame = i1480u_xmit_frame; + wlp->fill_device_info = i1480u_fill_device_info; + wlp->stop_queue = i1480u_stop_queue; + wlp->start_queue = i1480u_start_queue; + result = wlp_setup(wlp, rc); + if (result < 0) { + dev_err(&iface->dev, "Cannot setup WLP\n"); + goto error_wlp_setup; + } + result = 0; + ether_setup(net_dev); /* make it an etherdevice */ + uwb_dev = &rc->uwb_dev; + /* FIXME: hookup address change notifications? */ + + memcpy(net_dev->dev_addr, uwb_dev->mac_addr.data, + sizeof(net_dev->dev_addr)); + + net_dev->hard_header_len = sizeof(struct untd_hdr_cmp) + + sizeof(struct wlp_tx_hdr) + + WLP_DATA_HLEN + + ETH_HLEN; + net_dev->mtu = 3500; + net_dev->tx_queue_len = 20; /* FIXME: maybe use 1000? */ + +/* net_dev->flags &= ~IFF_BROADCAST; FIXME: BUG in firmware */ + /* FIXME: multicast disabled */ + net_dev->flags &= ~IFF_MULTICAST; + net_dev->features &= ~NETIF_F_SG; + net_dev->features &= ~NETIF_F_FRAGLIST; + /* All NETIF_F_*_CSUM disabled */ + net_dev->features |= NETIF_F_HIGHDMA; + net_dev->watchdog_timeo = 5*HZ; /* FIXME: a better default? */ + + net_dev->open = i1480u_open; + net_dev->stop = i1480u_stop; + net_dev->hard_start_xmit = i1480u_hard_start_xmit; + net_dev->tx_timeout = i1480u_tx_timeout; + net_dev->get_stats = i1480u_get_stats; + net_dev->set_config = i1480u_set_config; + net_dev->change_mtu = i1480u_change_mtu; + +#ifdef i1480u_FLOW_CONTROL + /* Notification endpoint setup (submitted when we open the device) */ + i1480u->notif_urb = usb_alloc_urb(0, GFP_KERNEL); + if (i1480u->notif_urb == NULL) { + dev_err(&iface->dev, "Unable to allocate notification URB\n"); + result = -ENOMEM; + goto error_urb_alloc; + } + epd = &iface->cur_altsetting->endpoint[0].desc; + usb_fill_int_urb(i1480u->notif_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + i1480u->notif_buffer, sizeof(i1480u->notif_buffer), + i1480u_notif_cb, i1480u, epd->bInterval); + +#endif + + i1480u->tx_inflight.max = i1480u_TX_INFLIGHT_MAX; + i1480u->tx_inflight.threshold = i1480u_TX_INFLIGHT_THRESHOLD; + i1480u->tx_inflight.restart_ts = jiffies; + usb_set_intfdata(iface, i1480u); + return result; + +#ifdef i1480u_FLOW_CONTROL +error_urb_alloc: +#endif + wlp_remove(wlp); +error_wlp_setup: + uwb_rc_put(rc); +out: + usb_put_dev(i1480u->usb_dev); + return result; +} + +static void i1480u_rm(struct i1480u *i1480u) +{ + struct uwb_rc *rc = i1480u->wlp.rc; + usb_set_intfdata(i1480u->usb_iface, NULL); +#ifdef i1480u_FLOW_CONTROL + usb_kill_urb(i1480u->notif_urb); + usb_free_urb(i1480u->notif_urb); +#endif + wlp_remove(&i1480u->wlp); + uwb_rc_put(rc); + usb_put_dev(i1480u->usb_dev); +} + +/** Just setup @net_dev's i1480u private data */ +static void i1480u_netdev_setup(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + /* Initialize @i1480u */ + memset(i1480u, 0, sizeof(*i1480u)); + i1480u_init(i1480u); +} + +/** + * Probe a i1480u interface and register it + * + * @iface: USB interface to link to + * @id: USB class/subclass/protocol id + * @returns: 0 if ok, < 0 errno code on error. + * + * Does basic housekeeping stuff and then allocs a netdev with space + * for the i1480u data. Initializes, registers in i1480u, registers in + * netdev, ready to go. + */ +static int i1480u_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + int result; + struct net_device *net_dev; + struct device *dev = &iface->dev; + struct i1480u *i1480u; + + /* Allocate instance [calls i1480u_netdev_setup() on it] */ + result = -ENOMEM; + net_dev = alloc_netdev(sizeof(*i1480u), "wlp%d", i1480u_netdev_setup); + if (net_dev == NULL) { + dev_err(dev, "no memory for network device instance\n"); + goto error_alloc_netdev; + } + SET_NETDEV_DEV(net_dev, dev); + i1480u = netdev_priv(net_dev); + i1480u->net_dev = net_dev; + result = i1480u_add(i1480u, iface); /* Now setup all the wlp stuff */ + if (result < 0) { + dev_err(dev, "cannot add i1480u device: %d\n", result); + goto error_i1480u_add; + } + result = register_netdev(net_dev); /* Okey dokey, bring it up */ + if (result < 0) { + dev_err(dev, "cannot register network device: %d\n", result); + goto error_register_netdev; + } + i1480u_sysfs_setup(i1480u); + if (result < 0) + goto error_sysfs_init; + return 0; + +error_sysfs_init: + unregister_netdev(net_dev); +error_register_netdev: + i1480u_rm(i1480u); +error_i1480u_add: + free_netdev(net_dev); +error_alloc_netdev: + return result; +} + + +/** + * Disconect a i1480u from the system. + * + * i1480u_stop() has been called before, so al the rx and tx contexts + * have been taken down already. Make sure the queue is stopped, + * unregister netdev and i1480u, free and kill. + */ +static void i1480u_disconnect(struct usb_interface *iface) +{ + struct i1480u *i1480u; + struct net_device *net_dev; + + i1480u = usb_get_intfdata(iface); + net_dev = i1480u->net_dev; + netif_stop_queue(net_dev); +#ifdef i1480u_FLOW_CONTROL + usb_kill_urb(i1480u->notif_urb); +#endif + i1480u_sysfs_release(i1480u); + unregister_netdev(net_dev); + i1480u_rm(i1480u); + free_netdev(net_dev); +} + +static struct usb_device_id i1480u_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE \ + | USB_DEVICE_ID_MATCH_DEV_INFO \ + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x8086, + .idProduct = 0x0c3b, + .bDeviceClass = 0xef, + .bDeviceSubClass = 0x02, + .bDeviceProtocol = 0x02, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + }, + {}, +}; +MODULE_DEVICE_TABLE(usb, i1480u_id_table); + +static struct usb_driver i1480u_driver = { + .name = KBUILD_MODNAME, + .probe = i1480u_probe, + .disconnect = i1480u_disconnect, + .id_table = i1480u_id_table, +}; + +static int __init i1480u_driver_init(void) +{ + return usb_register(&i1480u_driver); +} +module_init(i1480u_driver_init); + + +static void __exit i1480u_driver_exit(void) +{ + usb_deregister(&i1480u_driver); +} +module_exit(i1480u_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("i1480 Wireless UWB Link WLP networking for USB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uwb/i1480/i1480u-wlp/netdev.c b/drivers/uwb/i1480/i1480u-wlp/netdev.c new file mode 100644 index 00000000000..8802ac43d87 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/netdev.c @@ -0,0 +1,368 @@ +/* + * WUSB Wire Adapter: WLP interface + * Driver for the Linux Network stack. + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * Implementation of the netdevice linkage (except tx and rx related stuff). + * + * ROADMAP: + * + * ENTRY POINTS (Net device): + * + * i1480u_open(): Called when we ifconfig up the interface; + * associates to a UWB host controller, reserves + * bandwidth (MAS), sets up RX USB URB and starts + * the queue. + * + * i1480u_stop(): Called when we ifconfig down a interface; + * reverses _open(). + * + * i1480u_set_config(): + */ + +#include +#include +#include +#include "i1480u-wlp.h" + +struct i1480u_cmd_set_ip_mas { + struct uwb_rccb rccb; + struct uwb_dev_addr addr; + u8 stream; + u8 owner; + u8 type; /* enum uwb_drp_type */ + u8 baMAS[32]; +} __attribute__((packed)); + + +static +int i1480u_set_ip_mas( + struct uwb_rc *rc, + const struct uwb_dev_addr *dstaddr, + u8 stream, u8 owner, u8 type, unsigned long *mas) +{ + + int result; + struct i1480u_cmd_set_ip_mas *cmd; + struct uwb_rc_evt_confirm reply; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_kzalloc; + cmd->rccb.bCommandType = 0xfd; + cmd->rccb.wCommand = cpu_to_le16(0x000e); + cmd->addr = *dstaddr; + cmd->stream = stream; + cmd->owner = owner; + cmd->type = type; + if (mas == NULL) + memset(cmd->baMAS, 0x00, sizeof(cmd->baMAS)); + else + memcpy(cmd->baMAS, mas, sizeof(cmd->baMAS)); + reply.rceb.bEventType = 0xfd; + reply.rceb.wEvent = cpu_to_le16(0x000e); + result = uwb_rc_cmd(rc, "SET-IP-MAS", &cmd->rccb, sizeof(*cmd), + &reply.rceb, sizeof(reply)); + if (result < 0) + goto error_cmd; + if (reply.bResultCode != UWB_RC_RES_FAIL) { + dev_err(&rc->uwb_dev.dev, + "SET-IP-MAS: command execution failed: %d\n", + reply.bResultCode); + result = -EIO; + } +error_cmd: + kfree(cmd); +error_kzalloc: + return result; +} + +/* + * Inform a WLP interface of a MAS reservation + * + * @rc is assumed refcnted. + */ +/* FIXME: detect if remote device is WLP capable? */ +static int i1480u_mas_set_dev(struct uwb_dev *uwb_dev, struct uwb_rc *rc, + u8 stream, u8 owner, u8 type, unsigned long *mas) +{ + int result = 0; + struct device *dev = &rc->uwb_dev.dev; + + result = i1480u_set_ip_mas(rc, &uwb_dev->dev_addr, stream, owner, + type, mas); + if (result < 0) { + char rcaddrbuf[UWB_ADDR_STRSIZE], devaddrbuf[UWB_ADDR_STRSIZE]; + uwb_dev_addr_print(rcaddrbuf, sizeof(rcaddrbuf), + &rc->uwb_dev.dev_addr); + uwb_dev_addr_print(devaddrbuf, sizeof(devaddrbuf), + &uwb_dev->dev_addr); + dev_err(dev, "Set IP MAS (%s to %s) failed: %d\n", + rcaddrbuf, devaddrbuf, result); + } + return result; +} + +/** + * Called by bandwidth allocator when change occurs in reservation. + * + * @rsv: The reservation that is being established, modified, or + * terminated. + * + * When a reservation is established, modified, or terminated the upper layer + * (WLP here) needs set/update the currently available Media Access Slots + * that can be use for IP traffic. + * + * Our action taken during failure depends on how the reservation is being + * changed: + * - if reservation is being established we do nothing if we cannot set the + * new MAS to be used + * - if reservation is being terminated we revert back to PCA whether the + * SET IP MAS command succeeds or not. + */ +void i1480u_bw_alloc_cb(struct uwb_rsv *rsv) +{ + int result = 0; + struct i1480u *i1480u = rsv->pal_priv; + struct device *dev = &i1480u->usb_iface->dev; + struct uwb_dev *target_dev = rsv->target.dev; + struct uwb_rc *rc = i1480u->wlp.rc; + u8 stream = rsv->stream; + int type = rsv->type; + int is_owner = rsv->owner == &rc->uwb_dev; + unsigned long *bmp = rsv->mas.bm; + + dev_err(dev, "WLP callback called - sending set ip mas\n"); + /*user cannot change options while setting configuration*/ + mutex_lock(&i1480u->options.mutex); + switch (rsv->state) { + case UWB_RSV_STATE_T_ACCEPTED: + case UWB_RSV_STATE_O_ESTABLISHED: + result = i1480u_mas_set_dev(target_dev, rc, stream, is_owner, + type, bmp); + if (result < 0) { + dev_err(dev, "MAS reservation failed: %d\n", result); + goto out; + } + if (is_owner) { + wlp_tx_hdr_set_delivery_id_type(&i1480u->options.def_tx_hdr, + WLP_DRP | stream); + wlp_tx_hdr_set_rts_cts(&i1480u->options.def_tx_hdr, 0); + } + break; + case UWB_RSV_STATE_NONE: + /* revert back to PCA */ + result = i1480u_mas_set_dev(target_dev, rc, stream, is_owner, + type, bmp); + if (result < 0) + dev_err(dev, "MAS reservation failed: %d\n", result); + /* Revert to PCA even though SET IP MAS failed. */ + wlp_tx_hdr_set_delivery_id_type(&i1480u->options.def_tx_hdr, + i1480u->options.pca_base_priority); + wlp_tx_hdr_set_rts_cts(&i1480u->options.def_tx_hdr, 1); + break; + default: + dev_err(dev, "unexpected WLP reservation state: %s (%d).\n", + uwb_rsv_state_str(rsv->state), rsv->state); + break; + } +out: + mutex_unlock(&i1480u->options.mutex); + return; +} + +/** + * + * Called on 'ifconfig up' + */ +int i1480u_open(struct net_device *net_dev) +{ + int result; + struct i1480u *i1480u = netdev_priv(net_dev); + struct wlp *wlp = &i1480u->wlp; + struct uwb_rc *rc; + struct device *dev = &i1480u->usb_iface->dev; + + rc = wlp->rc; + result = i1480u_rx_setup(i1480u); /* Alloc RX stuff */ + if (result < 0) + goto error_rx_setup; + netif_wake_queue(net_dev); +#ifdef i1480u_FLOW_CONTROL + result = usb_submit_urb(i1480u->notif_urb, GFP_KERNEL);; + if (result < 0) { + dev_err(dev, "Can't submit notification URB: %d\n", result); + goto error_notif_urb_submit; + } +#endif + i1480u->uwb_notifs_handler.cb = i1480u_uwb_notifs_cb; + i1480u->uwb_notifs_handler.data = i1480u; + if (uwb_bg_joined(rc)) + netif_carrier_on(net_dev); + else + netif_carrier_off(net_dev); + uwb_notifs_register(rc, &i1480u->uwb_notifs_handler); + /* Interface is up with an address, now we can create WSS */ + result = wlp_wss_setup(net_dev, &wlp->wss); + if (result < 0) { + dev_err(dev, "Can't create WSS: %d. \n", result); + goto error_notif_deregister; + } + return 0; +error_notif_deregister: + uwb_notifs_deregister(rc, &i1480u->uwb_notifs_handler); +#ifdef i1480u_FLOW_CONTROL +error_notif_urb_submit: +#endif + netif_stop_queue(net_dev); + i1480u_rx_release(i1480u); +error_rx_setup: + return result; +} + + +/** + * Called on 'ifconfig down' + */ +int i1480u_stop(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + struct wlp *wlp = &i1480u->wlp; + struct uwb_rc *rc = wlp->rc; + + BUG_ON(wlp->rc == NULL); + wlp_wss_remove(&wlp->wss); + uwb_notifs_deregister(rc, &i1480u->uwb_notifs_handler); + netif_carrier_off(net_dev); +#ifdef i1480u_FLOW_CONTROL + usb_kill_urb(i1480u->notif_urb); +#endif + netif_stop_queue(net_dev); + i1480u_rx_release(i1480u); + i1480u_tx_release(i1480u); + return 0; +} + + +/** Report statistics */ +struct net_device_stats *i1480u_get_stats(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + return &i1480u->stats; +} + + +/** + * + * Change the interface config--we probably don't have to do anything. + */ +int i1480u_set_config(struct net_device *net_dev, struct ifmap *map) +{ + int result; + struct i1480u *i1480u = netdev_priv(net_dev); + BUG_ON(i1480u->wlp.rc == NULL); + result = 0; + return result; +} + +/** + * Change the MTU of the interface + */ +int i1480u_change_mtu(struct net_device *net_dev, int mtu) +{ + static union { + struct wlp_tx_hdr tx; + struct wlp_rx_hdr rx; + } i1480u_all_hdrs; + + if (mtu < ETH_HLEN) /* We encap eth frames */ + return -ERANGE; + if (mtu > 4000 - sizeof(i1480u_all_hdrs)) + return -ERANGE; + net_dev->mtu = mtu; + return 0; +} + + +/** + * Callback function to handle events from UWB + * When we see other devices we know the carrier is ok, + * if we are the only device in the beacon group we set the carrier + * state to off. + * */ +void i1480u_uwb_notifs_cb(void *data, struct uwb_dev *uwb_dev, + enum uwb_notifs event) +{ + struct i1480u *i1480u = data; + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + switch (event) { + case UWB_NOTIF_BG_JOIN: + netif_carrier_on(net_dev); + dev_info(dev, "Link is up\n"); + break; + case UWB_NOTIF_BG_LEAVE: + netif_carrier_off(net_dev); + dev_info(dev, "Link is down\n"); + break; + default: + dev_err(dev, "don't know how to handle event %d from uwb\n", + event); + } +} + +/** + * Stop the network queue + * + * Enable WLP substack to stop network queue. We also set the flow control + * threshold at this time to prevent the flow control from restarting the + * queue. + * + * we are loosing the current threshold value here ... FIXME? + */ +void i1480u_stop_queue(struct wlp *wlp) +{ + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct net_device *net_dev = i1480u->net_dev; + i1480u->tx_inflight.threshold = 0; + netif_stop_queue(net_dev); +} + +/** + * Start the network queue + * + * Enable WLP substack to start network queue. Also re-enable the flow + * control to manage the queue again. + * + * We re-enable the flow control by storing the default threshold in the + * flow control threshold. This means that if the user modified the + * threshold before the queue was stopped and restarted that information + * will be lost. FIXME? + */ +void i1480u_start_queue(struct wlp *wlp) +{ + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct net_device *net_dev = i1480u->net_dev; + i1480u->tx_inflight.threshold = i1480u_TX_INFLIGHT_THRESHOLD; + netif_start_queue(net_dev); +} diff --git a/drivers/uwb/i1480/i1480u-wlp/rx.c b/drivers/uwb/i1480/i1480u-wlp/rx.c new file mode 100644 index 00000000000..9fc035354a7 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/rx.c @@ -0,0 +1,486 @@ +/* + * WUSB Wire Adapter: WLP interface + * Driver for the Linux Network stack. + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * i1480u's RX handling is simple. i1480u will send the received + * network packets broken up in fragments; 1 to N fragments make a + * packet, we assemble them together and deliver the packet with netif_rx(). + * + * Beacuse each USB transfer is a *single* fragment (except when the + * transfer contains a first fragment), each URB called thus + * back contains one or two fragments. So we queue N URBs, each with its own + * fragment buffer. When a URB is done, we process it (adding to the + * current skb from the fragment buffer until complete). Once + * processed, we requeue the URB. There is always a bunch of URBs + * ready to take data, so the intergap should be minimal. + * + * An URB's transfer buffer is the data field of a socket buffer. This + * reduces copying as data can be passed directly to network layer. If a + * complete packet or 1st fragment is received the URB's transfer buffer is + * taken away from it and used to send data to the network layer. In this + * case a new transfer buffer is allocated to the URB before being requeued. + * If a "NEXT" or "LAST" fragment is received, the fragment contents is + * appended to the RX packet under construction and the transfer buffer + * is reused. To be able to use this buffer to assemble complete packets + * we set each buffer's size to that of the MAX ethernet packet that can + * be received. There is thus room for improvement in memory usage. + * + * When the max tx fragment size increases, we should be able to read + * data into the skbs directly with very simple code. + * + * ROADMAP: + * + * ENTRY POINTS: + * + * i1480u_rx_setup(): setup RX context [from i1480u_open()] + * + * i1480u_rx_release(): release RX context [from i1480u_stop()] + * + * i1480u_rx_cb(): called when the RX USB URB receives a + * packet. It removes the header and pushes it up + * the Linux netdev stack with netif_rx(). + * + * i1480u_rx_buffer() + * i1480u_drop() and i1480u_fix() + * i1480u_skb_deliver + * + */ + +#include +#include +#include "i1480u-wlp.h" + +#define D_LOCAL 0 +#include + + +/** + * Setup the RX context + * + * Each URB is provided with a transfer_buffer that is the data field + * of a new socket buffer. + */ +int i1480u_rx_setup(struct i1480u *i1480u) +{ + int result, cnt; + struct device *dev = &i1480u->usb_iface->dev; + struct net_device *net_dev = i1480u->net_dev; + struct usb_endpoint_descriptor *epd; + struct sk_buff *skb; + + /* Alloc RX stuff */ + i1480u->rx_skb = NULL; /* not in process of receiving packet */ + result = -ENOMEM; + epd = &i1480u->usb_iface->cur_altsetting->endpoint[1].desc; + for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) { + struct i1480u_rx_buf *rx_buf = &i1480u->rx_buf[cnt]; + rx_buf->i1480u = i1480u; + skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE); + if (!skb) { + dev_err(dev, + "RX: cannot allocate RX buffer %d\n", cnt); + result = -ENOMEM; + goto error; + } + skb->dev = net_dev; + skb->ip_summed = CHECKSUM_NONE; + skb_reserve(skb, 2); + rx_buf->data = skb; + rx_buf->urb = usb_alloc_urb(0, GFP_KERNEL); + if (unlikely(rx_buf->urb == NULL)) { + dev_err(dev, "RX: cannot allocate URB %d\n", cnt); + result = -ENOMEM; + goto error; + } + usb_fill_bulk_urb(rx_buf->urb, i1480u->usb_dev, + usb_rcvbulkpipe(i1480u->usb_dev, epd->bEndpointAddress), + rx_buf->data->data, i1480u_MAX_RX_PKT_SIZE - 2, + i1480u_rx_cb, rx_buf); + result = usb_submit_urb(rx_buf->urb, GFP_NOIO); + if (unlikely(result < 0)) { + dev_err(dev, "RX: cannot submit URB %d: %d\n", + cnt, result); + goto error; + } + } + return 0; + +error: + i1480u_rx_release(i1480u); + return result; +} + + +/** Release resources associated to the rx context */ +void i1480u_rx_release(struct i1480u *i1480u) +{ + int cnt; + for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) { + if (i1480u->rx_buf[cnt].data) + dev_kfree_skb(i1480u->rx_buf[cnt].data); + if (i1480u->rx_buf[cnt].urb) { + usb_kill_urb(i1480u->rx_buf[cnt].urb); + usb_free_urb(i1480u->rx_buf[cnt].urb); + } + } + if (i1480u->rx_skb != NULL) + dev_kfree_skb(i1480u->rx_skb); +} + +static +void i1480u_rx_unlink_urbs(struct i1480u *i1480u) +{ + int cnt; + for (cnt = 0; cnt < i1480u_RX_BUFS; cnt++) { + if (i1480u->rx_buf[cnt].urb) + usb_unlink_urb(i1480u->rx_buf[cnt].urb); + } +} + +/** Fix an out-of-sequence packet */ +#define i1480u_fix(i1480u, msg...) \ +do { \ + if (printk_ratelimit()) \ + dev_err(&i1480u->usb_iface->dev, msg); \ + dev_kfree_skb_irq(i1480u->rx_skb); \ + i1480u->rx_skb = NULL; \ + i1480u->rx_untd_pkt_size = 0; \ +} while (0) + + +/** Drop an out-of-sequence packet */ +#define i1480u_drop(i1480u, msg...) \ +do { \ + if (printk_ratelimit()) \ + dev_err(&i1480u->usb_iface->dev, msg); \ + i1480u->stats.rx_dropped++; \ +} while (0) + + + + +/** Finalizes setting up the SKB and delivers it + * + * We first pass the incoming frame to WLP substack for verification. It + * may also be a WLP association frame in which case WLP will take over the + * processing. If WLP does not take it over it will still verify it, if the + * frame is invalid the skb will be freed by WLP and we will not continue + * parsing. + * */ +static +void i1480u_skb_deliver(struct i1480u *i1480u) +{ + int should_parse; + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + + d_printf(6, dev, "RX delivered pre skb(%p), %u bytes\n", + i1480u->rx_skb, i1480u->rx_skb->len); + d_dump(7, dev, i1480u->rx_skb->data, i1480u->rx_skb->len); + should_parse = wlp_receive_frame(dev, &i1480u->wlp, i1480u->rx_skb, + &i1480u->rx_srcaddr); + if (!should_parse) + goto out; + i1480u->rx_skb->protocol = eth_type_trans(i1480u->rx_skb, net_dev); + d_printf(5, dev, "RX delivered skb(%p), %u bytes\n", + i1480u->rx_skb, i1480u->rx_skb->len); + d_dump(7, dev, i1480u->rx_skb->data, + i1480u->rx_skb->len > 72 ? 72 : i1480u->rx_skb->len); + i1480u->stats.rx_packets++; + i1480u->stats.rx_bytes += i1480u->rx_untd_pkt_size; + net_dev->last_rx = jiffies; + /* FIXME: flow control: check netif_rx() retval */ + + netif_rx(i1480u->rx_skb); /* deliver */ +out: + i1480u->rx_skb = NULL; + i1480u->rx_untd_pkt_size = 0; +} + + +/** + * Process a buffer of data received from the USB RX endpoint + * + * First fragment arrives with next or last fragment. All other fragments + * arrive alone. + * + * /me hates long functions. + */ +static +void i1480u_rx_buffer(struct i1480u_rx_buf *rx_buf) +{ + unsigned pkt_completed = 0; /* !0 when we got all pkt fragments */ + size_t untd_hdr_size, untd_frg_size; + size_t i1480u_hdr_size; + struct wlp_rx_hdr *i1480u_hdr = NULL; + + struct i1480u *i1480u = rx_buf->i1480u; + struct sk_buff *skb = rx_buf->data; + int size_left = rx_buf->urb->actual_length; + void *ptr = rx_buf->urb->transfer_buffer; /* also rx_buf->data->data */ + struct untd_hdr *untd_hdr; + + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + struct sk_buff *new_skb; + +#if 0 + dev_fnstart(dev, + "(i1480u %p ptr %p size_left %zu)\n", i1480u, ptr, size_left); + dev_err(dev, "RX packet, %zu bytes\n", size_left); + dump_bytes(dev, ptr, size_left); +#endif + i1480u_hdr_size = sizeof(struct wlp_rx_hdr); + + while (size_left > 0) { + if (pkt_completed) { + i1480u_drop(i1480u, "RX: fragment follows completed" + "packet in same buffer. Dropping\n"); + break; + } + untd_hdr = ptr; + if (size_left < sizeof(*untd_hdr)) { /* Check the UNTD header */ + i1480u_drop(i1480u, "RX: short UNTD header! Dropping\n"); + goto out; + } + if (unlikely(untd_hdr_rx_tx(untd_hdr) == 0)) { /* Paranoia: TX set? */ + i1480u_drop(i1480u, "RX: TX bit set! Dropping\n"); + goto out; + } + switch (untd_hdr_type(untd_hdr)) { /* Check the UNTD header type */ + case i1480u_PKT_FRAG_1ST: { + struct untd_hdr_1st *untd_hdr_1st = (void *) untd_hdr; + dev_dbg(dev, "1st fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_1st); + if (i1480u->rx_skb != NULL) + i1480u_fix(i1480u, "RX: 1st fragment out of " + "sequence! Fixing\n"); + if (size_left < untd_hdr_size + i1480u_hdr_size) { + i1480u_drop(i1480u, "RX: short 1st fragment! " + "Dropping\n"); + goto out; + } + i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len) + - i1480u_hdr_size; + untd_frg_size = le16_to_cpu(untd_hdr_1st->fragment_len); + if (size_left < untd_hdr_size + untd_frg_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + i1480u->rx_skb = skb; + i1480u_hdr = (void *) untd_hdr_1st + untd_hdr_size; + i1480u->rx_srcaddr = i1480u_hdr->srcaddr; + skb_put(i1480u->rx_skb, untd_hdr_size + untd_frg_size); + skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size); + stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7); + stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18); + rx_buf->data = NULL; /* need to create new buffer */ + break; + } + case i1480u_PKT_FRAG_NXT: { + dev_dbg(dev, "nxt fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_rst); + if (i1480u->rx_skb == NULL) { + i1480u_drop(i1480u, "RX: next fragment out of " + "sequence! Dropping\n"); + goto out; + } + if (size_left < untd_hdr_size) { + i1480u_drop(i1480u, "RX: short NXT fragment! " + "Dropping\n"); + goto out; + } + untd_frg_size = le16_to_cpu(untd_hdr->len); + if (size_left < untd_hdr_size + untd_frg_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + memmove(skb_put(i1480u->rx_skb, untd_frg_size), + ptr + untd_hdr_size, untd_frg_size); + break; + } + case i1480u_PKT_FRAG_LST: { + dev_dbg(dev, "Lst fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_rst); + if (i1480u->rx_skb == NULL) { + i1480u_drop(i1480u, "RX: last fragment out of " + "sequence! Dropping\n"); + goto out; + } + if (size_left < untd_hdr_size) { + i1480u_drop(i1480u, "RX: short LST fragment! " + "Dropping\n"); + goto out; + } + untd_frg_size = le16_to_cpu(untd_hdr->len); + if (size_left < untd_frg_size + untd_hdr_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + memmove(skb_put(i1480u->rx_skb, untd_frg_size), + ptr + untd_hdr_size, untd_frg_size); + pkt_completed = 1; + break; + } + case i1480u_PKT_FRAG_CMP: { + dev_dbg(dev, "cmp fragment\n"); + untd_hdr_size = sizeof(struct untd_hdr_cmp); + if (i1480u->rx_skb != NULL) + i1480u_fix(i1480u, "RX: fix out-of-sequence CMP" + " fragment!\n"); + if (size_left < untd_hdr_size + i1480u_hdr_size) { + i1480u_drop(i1480u, "RX: short CMP fragment! " + "Dropping\n"); + goto out; + } + i1480u->rx_untd_pkt_size = le16_to_cpu(untd_hdr->len); + untd_frg_size = i1480u->rx_untd_pkt_size; + if (size_left < i1480u->rx_untd_pkt_size + untd_hdr_size) { + i1480u_drop(i1480u, + "RX: short payload! Dropping\n"); + goto out; + } + i1480u->rx_skb = skb; + i1480u_hdr = (void *) untd_hdr + untd_hdr_size; + i1480u->rx_srcaddr = i1480u_hdr->srcaddr; + stats_add_sample(&i1480u->lqe_stats, (s8) i1480u_hdr->LQI - 7); + stats_add_sample(&i1480u->rssi_stats, i1480u_hdr->RSSI + 18); + skb_put(i1480u->rx_skb, untd_hdr_size + i1480u->rx_untd_pkt_size); + skb_pull(i1480u->rx_skb, untd_hdr_size + i1480u_hdr_size); + rx_buf->data = NULL; /* for hand off skb to network stack */ + pkt_completed = 1; + i1480u->rx_untd_pkt_size -= i1480u_hdr_size; /* accurate stat */ + break; + } + default: + i1480u_drop(i1480u, "RX: unknown packet type %u! " + "Dropping\n", untd_hdr_type(untd_hdr)); + goto out; + } + size_left -= untd_hdr_size + untd_frg_size; + if (size_left > 0) + ptr += untd_hdr_size + untd_frg_size; + } + if (pkt_completed) + i1480u_skb_deliver(i1480u); +out: + /* recreate needed RX buffers*/ + if (rx_buf->data == NULL) { + /* buffer is being used to receive packet, create new */ + new_skb = dev_alloc_skb(i1480u_MAX_RX_PKT_SIZE); + if (!new_skb) { + if (printk_ratelimit()) + dev_err(dev, + "RX: cannot allocate RX buffer\n"); + } else { + new_skb->dev = net_dev; + new_skb->ip_summed = CHECKSUM_NONE; + skb_reserve(new_skb, 2); + rx_buf->data = new_skb; + } + } + return; +} + + +/** + * Called when an RX URB has finished receiving or has found some kind + * of error condition. + * + * LIMITATIONS: + * + * - We read USB-transfers, each transfer contains a SINGLE fragment + * (can contain a complete packet, or a 1st, next, or last fragment + * of a packet). + * Looks like a transfer can contain more than one fragment (07/18/06) + * + * - Each transfer buffer is the size of the maximum packet size (minus + * headroom), i1480u_MAX_PKT_SIZE - 2 + * + * - We always read the full USB-transfer, no partials. + * + * - Each transfer is read directly into a skb. This skb will be used to + * send data to the upper layers if it is the first fragment or a complete + * packet. In the other cases the data will be copied from the skb to + * another skb that is being prepared for the upper layers from a prev + * first fragment. + * + * It is simply too much of a pain. Gosh, there should be a unified + * SG infrastructure for *everything* [so that I could declare a SG + * buffer, pass it to USB for receiving, append some space to it if + * I wish, receive more until I have the whole chunk, adapt + * pointers on each fragment to remove hardware headers and then + * attach that to an skbuff and netif_rx()]. + */ +void i1480u_rx_cb(struct urb *urb) +{ + int result; + int do_parse_buffer = 1; + struct i1480u_rx_buf *rx_buf = urb->context; + struct i1480u *i1480u = rx_buf->i1480u; + struct device *dev = &i1480u->usb_iface->dev; + unsigned long flags; + u8 rx_buf_idx = rx_buf - i1480u->rx_buf; + + switch (urb->status) { + case 0: + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: /* going away! */ + dev_err(dev, "RX URB[%u]: goind down %d\n", + rx_buf_idx, urb->status); + goto error; + default: + dev_err(dev, "RX URB[%u]: unknown status %d\n", + rx_buf_idx, urb->status); + if (edc_inc(&i1480u->rx_errors, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "RX: max acceptable errors exceeded," + " resetting device.\n"); + i1480u_rx_unlink_urbs(i1480u); + wlp_reset_all(&i1480u->wlp); + goto error; + } + do_parse_buffer = 0; + break; + } + spin_lock_irqsave(&i1480u->lock, flags); + /* chew the data fragments, extract network packets */ + if (do_parse_buffer) { + i1480u_rx_buffer(rx_buf); + if (rx_buf->data) { + rx_buf->urb->transfer_buffer = rx_buf->data->data; + result = usb_submit_urb(rx_buf->urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "RX URB[%u]: cannot submit %d\n", + rx_buf_idx, result); + } + } + } + spin_unlock_irqrestore(&i1480u->lock, flags); +error: + return; +} + diff --git a/drivers/uwb/i1480/i1480u-wlp/sysfs.c b/drivers/uwb/i1480/i1480u-wlp/sysfs.c new file mode 100644 index 00000000000..a1d8ca6ac93 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/sysfs.c @@ -0,0 +1,408 @@ +/* + * WUSB Wire Adapter: WLP interface + * Sysfs interfaces + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ + +#include +#include +#include +#include +#include "i1480u-wlp.h" + + +/** + * + * @dev: Class device from the net_device; assumed refcnted. + * + * Yes, I don't lock--we assume it is refcounted and I am getting a + * single byte value that is kind of atomic to read. + */ +ssize_t uwb_phy_rate_show(const struct wlp_options *options, char *buf) +{ + return sprintf(buf, "%u\n", + wlp_tx_hdr_phy_rate(&options->def_tx_hdr)); +} +EXPORT_SYMBOL_GPL(uwb_phy_rate_show); + + +ssize_t uwb_phy_rate_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result; + unsigned rate; + + result = sscanf(buf, "%u\n", &rate); + if (result != 1) { + result = -EINVAL; + goto out; + } + result = -EINVAL; + if (rate >= UWB_PHY_RATE_INVALID) + goto out; + wlp_tx_hdr_set_phy_rate(&options->def_tx_hdr, rate); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_phy_rate_store); + + +ssize_t uwb_rts_cts_show(const struct wlp_options *options, char *buf) +{ + return sprintf(buf, "%u\n", + wlp_tx_hdr_rts_cts(&options->def_tx_hdr)); +} +EXPORT_SYMBOL_GPL(uwb_rts_cts_show); + + +ssize_t uwb_rts_cts_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result; + unsigned value; + + result = sscanf(buf, "%u\n", &value); + if (result != 1) { + result = -EINVAL; + goto out; + } + result = -EINVAL; + wlp_tx_hdr_set_rts_cts(&options->def_tx_hdr, !!value); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_rts_cts_store); + + +ssize_t uwb_ack_policy_show(const struct wlp_options *options, char *buf) +{ + return sprintf(buf, "%u\n", + wlp_tx_hdr_ack_policy(&options->def_tx_hdr)); +} +EXPORT_SYMBOL_GPL(uwb_ack_policy_show); + + +ssize_t uwb_ack_policy_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result; + unsigned value; + + result = sscanf(buf, "%u\n", &value); + if (result != 1 || value > UWB_ACK_B_REQ) { + result = -EINVAL; + goto out; + } + wlp_tx_hdr_set_ack_policy(&options->def_tx_hdr, value); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_ack_policy_store); + + +/** + * Show the PCA base priority. + * + * We can access without locking, as the value is (for now) orthogonal + * to other values. + */ +ssize_t uwb_pca_base_priority_show(const struct wlp_options *options, + char *buf) +{ + return sprintf(buf, "%u\n", + options->pca_base_priority); +} +EXPORT_SYMBOL_GPL(uwb_pca_base_priority_show); + + +/** + * Set the PCA base priority. + * + * We can access without locking, as the value is (for now) orthogonal + * to other values. + */ +ssize_t uwb_pca_base_priority_store(struct wlp_options *options, + const char *buf, size_t size) +{ + ssize_t result = -EINVAL; + u8 pca_base_priority; + + result = sscanf(buf, "%hhu\n", &pca_base_priority); + if (result != 1) { + result = -EINVAL; + goto out; + } + result = -EINVAL; + if (pca_base_priority >= 8) + goto out; + options->pca_base_priority = pca_base_priority; + /* Update TX header if we are currently using PCA. */ + if (result >= 0 && (wlp_tx_hdr_delivery_id_type(&options->def_tx_hdr) & WLP_DRP) == 0) + wlp_tx_hdr_set_delivery_id_type(&options->def_tx_hdr, options->pca_base_priority); + result = 0; +out: + return result < 0 ? result : size; +} +EXPORT_SYMBOL_GPL(uwb_pca_base_priority_store); + +/** + * Show current inflight values + * + * Will print the current MAX and THRESHOLD values for the basic flow + * control. In addition it will report how many times the TX queue needed + * to be restarted since the last time this query was made. + */ +static ssize_t wlp_tx_inflight_show(struct i1480u_tx_inflight *inflight, + char *buf) +{ + ssize_t result; + unsigned long sec_elapsed = (jiffies - inflight->restart_ts)/HZ; + unsigned long restart_count = atomic_read(&inflight->restart_count); + + result = scnprintf(buf, PAGE_SIZE, "%lu %lu %d %lu %lu %lu\n" + "#read: threshold max inflight_count restarts " + "seconds restarts/sec\n" + "#write: threshold max\n", + inflight->threshold, inflight->max, + atomic_read(&inflight->count), + restart_count, sec_elapsed, + sec_elapsed == 0 ? 0 : restart_count/sec_elapsed); + inflight->restart_ts = jiffies; + atomic_set(&inflight->restart_count, 0); + return result; +} + +static +ssize_t wlp_tx_inflight_store(struct i1480u_tx_inflight *inflight, + const char *buf, size_t size) +{ + unsigned long in_threshold, in_max; + ssize_t result; + result = sscanf(buf, "%lu %lu", &in_threshold, &in_max); + if (result != 2) + return -EINVAL; + if (in_max <= in_threshold) + return -EINVAL; + inflight->max = in_max; + inflight->threshold = in_threshold; + return size; +} +/* + * Glue (or function adaptors) for accesing info on sysfs + * + * [we need this indirection because the PCI driver does almost the + * same] + * + * Linux 2.6.21 changed how 'struct netdevice' does attributes (from + * having a 'struct class_dev' to having a 'struct device'). That is + * quite of a pain. + * + * So we try to abstract that here. i1480u_SHOW() and i1480u_STORE() + * create adaptors for extracting the 'struct i1480u' from a 'struct + * dev' and calling a function for doing a sysfs operation (as we have + * them factorized already). i1480u_ATTR creates the attribute file + * (CLASS_DEVICE_ATTR or DEVICE_ATTR) and i1480u_ATTR_NAME produces a + * class_device_attr_NAME or device_attr_NAME (for group registration). + */ +#include + +#define i1480u_SHOW(name, fn, param) \ +static ssize_t i1480u_show_##name(struct device *dev, \ + struct device_attribute *attr,\ + char *buf) \ +{ \ + struct i1480u *i1480u = netdev_priv(to_net_dev(dev)); \ + return fn(&i1480u->param, buf); \ +} + +#define i1480u_STORE(name, fn, param) \ +static ssize_t i1480u_store_##name(struct device *dev, \ + struct device_attribute *attr,\ + const char *buf, size_t size)\ +{ \ + struct i1480u *i1480u = netdev_priv(to_net_dev(dev)); \ + return fn(&i1480u->param, buf, size); \ +} + +#define i1480u_ATTR(name, perm) static DEVICE_ATTR(name, perm, \ + i1480u_show_##name,\ + i1480u_store_##name) + +#define i1480u_ATTR_SHOW(name) static DEVICE_ATTR(name, \ + S_IRUGO, \ + i1480u_show_##name, NULL) + +#define i1480u_ATTR_NAME(a) (dev_attr_##a) + + +/* + * Sysfs adaptors + */ +i1480u_SHOW(uwb_phy_rate, uwb_phy_rate_show, options); +i1480u_STORE(uwb_phy_rate, uwb_phy_rate_store, options); +i1480u_ATTR(uwb_phy_rate, S_IRUGO | S_IWUSR); + +i1480u_SHOW(uwb_rts_cts, uwb_rts_cts_show, options); +i1480u_STORE(uwb_rts_cts, uwb_rts_cts_store, options); +i1480u_ATTR(uwb_rts_cts, S_IRUGO | S_IWUSR); + +i1480u_SHOW(uwb_ack_policy, uwb_ack_policy_show, options); +i1480u_STORE(uwb_ack_policy, uwb_ack_policy_store, options); +i1480u_ATTR(uwb_ack_policy, S_IRUGO | S_IWUSR); + +i1480u_SHOW(uwb_pca_base_priority, uwb_pca_base_priority_show, options); +i1480u_STORE(uwb_pca_base_priority, uwb_pca_base_priority_store, options); +i1480u_ATTR(uwb_pca_base_priority, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_eda, wlp_eda_show, wlp); +i1480u_STORE(wlp_eda, wlp_eda_store, wlp); +i1480u_ATTR(wlp_eda, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_uuid, wlp_uuid_show, wlp); +i1480u_STORE(wlp_uuid, wlp_uuid_store, wlp); +i1480u_ATTR(wlp_uuid, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_name, wlp_dev_name_show, wlp); +i1480u_STORE(wlp_dev_name, wlp_dev_name_store, wlp); +i1480u_ATTR(wlp_dev_name, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_manufacturer, wlp_dev_manufacturer_show, wlp); +i1480u_STORE(wlp_dev_manufacturer, wlp_dev_manufacturer_store, wlp); +i1480u_ATTR(wlp_dev_manufacturer, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_model_name, wlp_dev_model_name_show, wlp); +i1480u_STORE(wlp_dev_model_name, wlp_dev_model_name_store, wlp); +i1480u_ATTR(wlp_dev_model_name, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_model_nr, wlp_dev_model_nr_show, wlp); +i1480u_STORE(wlp_dev_model_nr, wlp_dev_model_nr_store, wlp); +i1480u_ATTR(wlp_dev_model_nr, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_serial, wlp_dev_serial_show, wlp); +i1480u_STORE(wlp_dev_serial, wlp_dev_serial_store, wlp); +i1480u_ATTR(wlp_dev_serial, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_category, wlp_dev_prim_category_show, wlp); +i1480u_STORE(wlp_dev_prim_category, wlp_dev_prim_category_store, wlp); +i1480u_ATTR(wlp_dev_prim_category, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_OUI, wlp_dev_prim_OUI_show, wlp); +i1480u_STORE(wlp_dev_prim_OUI, wlp_dev_prim_OUI_store, wlp); +i1480u_ATTR(wlp_dev_prim_OUI, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_OUI_sub, wlp_dev_prim_OUI_sub_show, wlp); +i1480u_STORE(wlp_dev_prim_OUI_sub, wlp_dev_prim_OUI_sub_store, wlp); +i1480u_ATTR(wlp_dev_prim_OUI_sub, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_dev_prim_subcat, wlp_dev_prim_subcat_show, wlp); +i1480u_STORE(wlp_dev_prim_subcat, wlp_dev_prim_subcat_store, wlp); +i1480u_ATTR(wlp_dev_prim_subcat, S_IRUGO | S_IWUSR); + +i1480u_SHOW(wlp_neighborhood, wlp_neighborhood_show, wlp); +i1480u_ATTR_SHOW(wlp_neighborhood); + +i1480u_SHOW(wss_activate, wlp_wss_activate_show, wlp.wss); +i1480u_STORE(wss_activate, wlp_wss_activate_store, wlp.wss); +i1480u_ATTR(wss_activate, S_IRUGO | S_IWUSR); + +/* + * Show the (min, max, avg) Line Quality Estimate (LQE, in dB) as over + * the last 256 received WLP frames (ECMA-368 13.3). + * + * [the -7dB that have to be substracted from the LQI to make the LQE + * are already taken into account]. + */ +i1480u_SHOW(wlp_lqe, stats_show, lqe_stats); +i1480u_STORE(wlp_lqe, stats_store, lqe_stats); +i1480u_ATTR(wlp_lqe, S_IRUGO | S_IWUSR); + +/* + * Show the Receive Signal Strength Indicator averaged over all the + * received WLP frames (ECMA-368 13.3). Still is not clear what + * this value is, but is kind of a percentage of the signal strength + * at the antenna. + */ +i1480u_SHOW(wlp_rssi, stats_show, rssi_stats); +i1480u_STORE(wlp_rssi, stats_store, rssi_stats); +i1480u_ATTR(wlp_rssi, S_IRUGO | S_IWUSR); + +/** + * We maintain a basic flow control counter. "count" how many TX URBs are + * outstanding. Only allow "max" + * TX URBs to be outstanding. If this value is reached the queue will be + * stopped. The queue will be restarted when there are + * "threshold" URBs outstanding. + */ +i1480u_SHOW(wlp_tx_inflight, wlp_tx_inflight_show, tx_inflight); +i1480u_STORE(wlp_tx_inflight, wlp_tx_inflight_store, tx_inflight); +i1480u_ATTR(wlp_tx_inflight, S_IRUGO | S_IWUSR); + +static struct attribute *i1480u_attrs[] = { + &i1480u_ATTR_NAME(uwb_phy_rate).attr, + &i1480u_ATTR_NAME(uwb_rts_cts).attr, + &i1480u_ATTR_NAME(uwb_ack_policy).attr, + &i1480u_ATTR_NAME(uwb_pca_base_priority).attr, + &i1480u_ATTR_NAME(wlp_lqe).attr, + &i1480u_ATTR_NAME(wlp_rssi).attr, + &i1480u_ATTR_NAME(wlp_eda).attr, + &i1480u_ATTR_NAME(wlp_uuid).attr, + &i1480u_ATTR_NAME(wlp_dev_name).attr, + &i1480u_ATTR_NAME(wlp_dev_manufacturer).attr, + &i1480u_ATTR_NAME(wlp_dev_model_name).attr, + &i1480u_ATTR_NAME(wlp_dev_model_nr).attr, + &i1480u_ATTR_NAME(wlp_dev_serial).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_category).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_OUI).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_OUI_sub).attr, + &i1480u_ATTR_NAME(wlp_dev_prim_subcat).attr, + &i1480u_ATTR_NAME(wlp_neighborhood).attr, + &i1480u_ATTR_NAME(wss_activate).attr, + &i1480u_ATTR_NAME(wlp_tx_inflight).attr, + NULL, +}; + +static struct attribute_group i1480u_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = i1480u_attrs, +}; + +int i1480u_sysfs_setup(struct i1480u *i1480u) +{ + int result; + struct device *dev = &i1480u->usb_iface->dev; + result = sysfs_create_group(&i1480u->net_dev->dev.kobj, + &i1480u_attr_group); + if (result < 0) + dev_err(dev, "cannot initialize sysfs attributes: %d\n", + result); + return result; +} + + +void i1480u_sysfs_release(struct i1480u *i1480u) +{ + sysfs_remove_group(&i1480u->net_dev->dev.kobj, + &i1480u_attr_group); +} diff --git a/drivers/uwb/i1480/i1480u-wlp/tx.c b/drivers/uwb/i1480/i1480u-wlp/tx.c new file mode 100644 index 00000000000..3426bfb6824 --- /dev/null +++ b/drivers/uwb/i1480/i1480u-wlp/tx.c @@ -0,0 +1,632 @@ +/* + * WUSB Wire Adapter: WLP interface + * Deal with TX (massaging data to transmit, handling it) + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Transmission engine. Get an skb, create from that a WLP transmit + * context, add a WLP TX header (which we keep prefilled in the + * device's instance), fill out the target-specific fields and + * fire it. + * + * ROADMAP: + * + * Entry points: + * + * i1480u_tx_release(): called by i1480u_disconnect() to release + * pending tx contexts. + * + * i1480u_tx_cb(): callback for TX contexts (USB URBs) + * i1480u_tx_destroy(): + * + * i1480u_tx_timeout(): called for timeout handling from the + * network stack. + * + * i1480u_hard_start_xmit(): called for transmitting an skb from + * the network stack. Will interact with WLP + * substack to verify and prepare frame. + * i1480u_xmit_frame(): actual transmission on hardware + * + * i1480u_tx_create() Creates TX context + * i1480u_tx_create_1() For packets in 1 fragment + * i1480u_tx_create_n() For packets in >1 fragments + * + * TODO: + * + * - FIXME: rewrite using usb_sg_*(), add asynch support to + * usb_sg_*(). It might not make too much sense as most of + * the times the MTU will be smaller than one page... + */ + +#include "i1480u-wlp.h" +#define D_LOCAL 5 +#include + +enum { + /* This is only for Next and Last TX packets */ + i1480u_MAX_PL_SIZE = i1480u_MAX_FRG_SIZE + - sizeof(struct untd_hdr_rst), +}; + +/** Free resources allocated to a i1480u tx context. */ +static +void i1480u_tx_free(struct i1480u_tx *wtx) +{ + kfree(wtx->buf); + if (wtx->skb) + dev_kfree_skb_irq(wtx->skb); + usb_free_urb(wtx->urb); + kfree(wtx); +} + +static +void i1480u_tx_destroy(struct i1480u *i1480u, struct i1480u_tx *wtx) +{ + unsigned long flags; + spin_lock_irqsave(&i1480u->tx_list_lock, flags); /* not active any more */ + list_del(&wtx->list_node); + i1480u_tx_free(wtx); + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); +} + +static +void i1480u_tx_unlink_urbs(struct i1480u *i1480u) +{ + unsigned long flags; + struct i1480u_tx *wtx, *next; + + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) { + usb_unlink_urb(wtx->urb); + } + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); +} + + +/** + * Callback for a completed tx USB URB. + * + * TODO: + * + * - FIXME: recover errors more gracefully + * - FIXME: handle NAKs (I dont think they come here) for flow ctl + */ +static +void i1480u_tx_cb(struct urb *urb) +{ + struct i1480u_tx *wtx = urb->context; + struct i1480u *i1480u = wtx->i1480u; + struct net_device *net_dev = i1480u->net_dev; + struct device *dev = &i1480u->usb_iface->dev; + unsigned long flags; + + switch (urb->status) { + case 0: + spin_lock_irqsave(&i1480u->lock, flags); + i1480u->stats.tx_packets++; + i1480u->stats.tx_bytes += urb->actual_length; + spin_unlock_irqrestore(&i1480u->lock, flags); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + dev_dbg(dev, "notif endp: reset/noent %d\n", urb->status); + netif_stop_queue(net_dev); + break; + case -ESHUTDOWN: /* going away! */ + dev_dbg(dev, "notif endp: down %d\n", urb->status); + netif_stop_queue(net_dev); + break; + default: + dev_err(dev, "TX: unknown URB status %d\n", urb->status); + if (edc_inc(&i1480u->tx_errors, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "TX: max acceptable errors exceeded." + "Reset device.\n"); + netif_stop_queue(net_dev); + i1480u_tx_unlink_urbs(i1480u); + wlp_reset_all(&i1480u->wlp); + } + break; + } + i1480u_tx_destroy(i1480u, wtx); + if (atomic_dec_return(&i1480u->tx_inflight.count) + <= i1480u->tx_inflight.threshold + && netif_queue_stopped(net_dev) + && i1480u->tx_inflight.threshold != 0) { + if (d_test(2) && printk_ratelimit()) + d_printf(2, dev, "Restart queue. \n"); + netif_start_queue(net_dev); + atomic_inc(&i1480u->tx_inflight.restart_count); + } + return; +} + + +/** + * Given a buffer that doesn't fit in a single fragment, create an + * scatter/gather structure for delivery to the USB pipe. + * + * Implements functionality of i1480u_tx_create(). + * + * @wtx: tx descriptor + * @skb: skb to send + * @gfp_mask: gfp allocation mask + * @returns: Pointer to @wtx if ok, NULL on error. + * + * Sorry, TOO LONG a function, but breaking it up is kind of hard + * + * This will break the buffer in chunks smaller than + * i1480u_MAX_FRG_SIZE (including the header) and add proper headers + * to each: + * + * 1st header \ + * i1480 tx header | fragment 1 + * fragment data / + * nxt header \ fragment 2 + * fragment data / + * .. + * .. + * last header \ fragment 3 + * last fragment data / + * + * This does not fill the i1480 TX header, it is left up to the + * caller to do that; you can get it from @wtx->wlp_tx_hdr. + * + * This function consumes the skb unless there is an error. + */ +static +int i1480u_tx_create_n(struct i1480u_tx *wtx, struct sk_buff *skb, + gfp_t gfp_mask) +{ + int result; + void *pl; + size_t pl_size; + + void *pl_itr, *buf_itr; + size_t pl_size_left, frgs, pl_size_1st, frg_pl_size = 0; + struct untd_hdr_1st *untd_hdr_1st; + struct wlp_tx_hdr *wlp_tx_hdr; + struct untd_hdr_rst *untd_hdr_rst; + + wtx->skb = NULL; + pl = skb->data; + pl_itr = pl; + pl_size = skb->len; + pl_size_left = pl_size; /* payload size */ + /* First fragment; fits as much as i1480u_MAX_FRG_SIZE minus + * the headers */ + pl_size_1st = i1480u_MAX_FRG_SIZE + - sizeof(struct untd_hdr_1st) - sizeof(struct wlp_tx_hdr); + BUG_ON(pl_size_1st > pl_size); + pl_size_left -= pl_size_1st; + /* The rest have an smaller header (no i1480 TX header). We + * need to break up the payload in blocks smaller than + * i1480u_MAX_PL_SIZE (payload excluding header). */ + frgs = (pl_size_left + i1480u_MAX_PL_SIZE - 1) / i1480u_MAX_PL_SIZE; + /* Allocate space for the new buffer. In this new buffer we'll + * place the headers followed by the data fragment, headers, + * data fragments, etc.. + */ + result = -ENOMEM; + wtx->buf_size = sizeof(*untd_hdr_1st) + + sizeof(*wlp_tx_hdr) + + frgs * sizeof(*untd_hdr_rst) + + pl_size; + wtx->buf = kmalloc(wtx->buf_size, gfp_mask); + if (wtx->buf == NULL) + goto error_buf_alloc; + + buf_itr = wtx->buf; /* We got the space, let's fill it up */ + /* Fill 1st fragment */ + untd_hdr_1st = buf_itr; + buf_itr += sizeof(*untd_hdr_1st); + untd_hdr_set_type(&untd_hdr_1st->hdr, i1480u_PKT_FRAG_1ST); + untd_hdr_set_rx_tx(&untd_hdr_1st->hdr, 0); + untd_hdr_1st->hdr.len = cpu_to_le16(pl_size + sizeof(*wlp_tx_hdr)); + untd_hdr_1st->fragment_len = + cpu_to_le16(pl_size_1st + sizeof(*wlp_tx_hdr)); + memset(untd_hdr_1st->padding, 0, sizeof(untd_hdr_1st->padding)); + /* Set up i1480 header info */ + wlp_tx_hdr = wtx->wlp_tx_hdr = buf_itr; + buf_itr += sizeof(*wlp_tx_hdr); + /* Copy the first fragment */ + memcpy(buf_itr, pl_itr, pl_size_1st); + pl_itr += pl_size_1st; + buf_itr += pl_size_1st; + + /* Now do each remaining fragment */ + result = -EINVAL; + while (pl_size_left > 0) { + d_printf(5, NULL, "ITR HDR: pl_size_left %zu buf_itr %zu\n", + pl_size_left, buf_itr - wtx->buf); + if (buf_itr + sizeof(*untd_hdr_rst) - wtx->buf + > wtx->buf_size) { + printk(KERN_ERR "BUG: no space for header\n"); + goto error_bug; + } + d_printf(5, NULL, "ITR HDR 2: pl_size_left %zu buf_itr %zu\n", + pl_size_left, buf_itr - wtx->buf); + untd_hdr_rst = buf_itr; + buf_itr += sizeof(*untd_hdr_rst); + if (pl_size_left > i1480u_MAX_PL_SIZE) { + frg_pl_size = i1480u_MAX_PL_SIZE; + untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_NXT); + } else { + frg_pl_size = pl_size_left; + untd_hdr_set_type(&untd_hdr_rst->hdr, i1480u_PKT_FRAG_LST); + } + d_printf(5, NULL, + "ITR PL: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n", + pl_size_left, buf_itr - wtx->buf, frg_pl_size); + untd_hdr_set_rx_tx(&untd_hdr_rst->hdr, 0); + untd_hdr_rst->hdr.len = cpu_to_le16(frg_pl_size); + untd_hdr_rst->padding = 0; + if (buf_itr + frg_pl_size - wtx->buf + > wtx->buf_size) { + printk(KERN_ERR "BUG: no space for payload\n"); + goto error_bug; + } + memcpy(buf_itr, pl_itr, frg_pl_size); + buf_itr += frg_pl_size; + pl_itr += frg_pl_size; + pl_size_left -= frg_pl_size; + d_printf(5, NULL, + "ITR PL 2: pl_size_left %zu buf_itr %zu frg_pl_size %zu\n", + pl_size_left, buf_itr - wtx->buf, frg_pl_size); + } + dev_kfree_skb_irq(skb); + return 0; + +error_bug: + printk(KERN_ERR + "BUG: skb %u bytes\n" + "BUG: frg_pl_size %zd i1480u_MAX_FRG_SIZE %u\n" + "BUG: buf_itr %zu buf_size %zu pl_size_left %zu\n", + skb->len, + frg_pl_size, i1480u_MAX_FRG_SIZE, + buf_itr - wtx->buf, wtx->buf_size, pl_size_left); + + kfree(wtx->buf); +error_buf_alloc: + return result; +} + + +/** + * Given a buffer that fits in a single fragment, fill out a @wtx + * struct for transmitting it down the USB pipe. + * + * Uses the fact that we have space reserved in front of the skbuff + * for hardware headers :] + * + * This does not fill the i1480 TX header, it is left up to the + * caller to do that; you can get it from @wtx->wlp_tx_hdr. + * + * @pl: pointer to payload data + * @pl_size: size of the payuload + * + * This function does not consume the @skb. + */ +static +int i1480u_tx_create_1(struct i1480u_tx *wtx, struct sk_buff *skb, + gfp_t gfp_mask) +{ + struct untd_hdr_cmp *untd_hdr_cmp; + struct wlp_tx_hdr *wlp_tx_hdr; + + wtx->buf = NULL; + wtx->skb = skb; + BUG_ON(skb_headroom(skb) < sizeof(*wlp_tx_hdr)); + wlp_tx_hdr = (void *) __skb_push(skb, sizeof(*wlp_tx_hdr)); + wtx->wlp_tx_hdr = wlp_tx_hdr; + BUG_ON(skb_headroom(skb) < sizeof(*untd_hdr_cmp)); + untd_hdr_cmp = (void *) __skb_push(skb, sizeof(*untd_hdr_cmp)); + + untd_hdr_set_type(&untd_hdr_cmp->hdr, i1480u_PKT_FRAG_CMP); + untd_hdr_set_rx_tx(&untd_hdr_cmp->hdr, 0); + untd_hdr_cmp->hdr.len = cpu_to_le16(skb->len - sizeof(*untd_hdr_cmp)); + untd_hdr_cmp->padding = 0; + return 0; +} + + +/** + * Given a skb to transmit, massage it to become palatable for the TX pipe + * + * This will break the buffer in chunks smaller than + * i1480u_MAX_FRG_SIZE and add proper headers to each. + * + * 1st header \ + * i1480 tx header | fragment 1 + * fragment data / + * nxt header \ fragment 2 + * fragment data / + * .. + * .. + * last header \ fragment 3 + * last fragment data / + * + * Each fragment will be always smaller or equal to i1480u_MAX_FRG_SIZE. + * + * If the first fragment is smaller than i1480u_MAX_FRG_SIZE, then the + * following is composed: + * + * complete header \ + * i1480 tx header | single fragment + * packet data / + * + * We were going to use s/g support, but because the interface is + * synch and at the end there is plenty of overhead to do it, it + * didn't seem that worth for data that is going to be smaller than + * one page. + */ +static +struct i1480u_tx *i1480u_tx_create(struct i1480u *i1480u, + struct sk_buff *skb, gfp_t gfp_mask) +{ + int result; + struct usb_endpoint_descriptor *epd; + int usb_pipe; + unsigned long flags; + + struct i1480u_tx *wtx; + const size_t pl_max_size = + i1480u_MAX_FRG_SIZE - sizeof(struct untd_hdr_cmp) + - sizeof(struct wlp_tx_hdr); + + wtx = kmalloc(sizeof(*wtx), gfp_mask); + if (wtx == NULL) + goto error_wtx_alloc; + wtx->urb = usb_alloc_urb(0, gfp_mask); + if (wtx->urb == NULL) + goto error_urb_alloc; + epd = &i1480u->usb_iface->cur_altsetting->endpoint[2].desc; + usb_pipe = usb_sndbulkpipe(i1480u->usb_dev, epd->bEndpointAddress); + /* Fits in a single complete packet or need to split? */ + if (skb->len > pl_max_size) { + result = i1480u_tx_create_n(wtx, skb, gfp_mask); + if (result < 0) + goto error_create; + usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe, + wtx->buf, wtx->buf_size, i1480u_tx_cb, wtx); + } else { + result = i1480u_tx_create_1(wtx, skb, gfp_mask); + if (result < 0) + goto error_create; + usb_fill_bulk_urb(wtx->urb, i1480u->usb_dev, usb_pipe, + skb->data, skb->len, i1480u_tx_cb, wtx); + } + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + list_add(&wtx->list_node, &i1480u->tx_list); + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); + return wtx; + +error_create: + kfree(wtx->urb); +error_urb_alloc: + kfree(wtx); +error_wtx_alloc: + return NULL; +} + +/** + * Actual fragmentation and transmission of frame + * + * @wlp: WLP substack data structure + * @skb: To be transmitted + * @dst: Device address of destination + * @returns: 0 on success, <0 on failure + * + * This function can also be called directly (not just from + * hard_start_xmit), so we also check here if the interface is up before + * taking sending anything. + */ +int i1480u_xmit_frame(struct wlp *wlp, struct sk_buff *skb, + struct uwb_dev_addr *dst) +{ + int result = -ENXIO; + struct i1480u *i1480u = container_of(wlp, struct i1480u, wlp); + struct device *dev = &i1480u->usb_iface->dev; + struct net_device *net_dev = i1480u->net_dev; + struct i1480u_tx *wtx; + struct wlp_tx_hdr *wlp_tx_hdr; + static unsigned char dev_bcast[2] = { 0xff, 0xff }; +#if 0 + int lockup = 50; +#endif + + d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len, + net_dev); + BUG_ON(i1480u->wlp.rc == NULL); + if ((net_dev->flags & IFF_UP) == 0) + goto out; + result = -EBUSY; + if (atomic_read(&i1480u->tx_inflight.count) >= i1480u->tx_inflight.max) { + if (d_test(2) && printk_ratelimit()) + d_printf(2, dev, "Max frames in flight " + "stopping queue.\n"); + netif_stop_queue(net_dev); + goto error_max_inflight; + } + result = -ENOMEM; + wtx = i1480u_tx_create(i1480u, skb, GFP_ATOMIC); + if (unlikely(wtx == NULL)) { + if (printk_ratelimit()) + dev_err(dev, "TX: no memory for WLP TX URB," + "dropping packet (in flight %d)\n", + atomic_read(&i1480u->tx_inflight.count)); + netif_stop_queue(net_dev); + goto error_wtx_alloc; + } + wtx->i1480u = i1480u; + /* Fill out the i1480 header; @i1480u->def_tx_hdr read without + * locking. We do so because they are kind of orthogonal to + * each other (and thus not changed in an atomic batch). + * The ETH header is right after the WLP TX header. */ + wlp_tx_hdr = wtx->wlp_tx_hdr; + *wlp_tx_hdr = i1480u->options.def_tx_hdr; + wlp_tx_hdr->dstaddr = *dst; + if (!memcmp(&wlp_tx_hdr->dstaddr, dev_bcast, sizeof(dev_bcast)) + && (wlp_tx_hdr_delivery_id_type(wlp_tx_hdr) & WLP_DRP)) { + /*Broadcast message directed to DRP host. Send as best effort + * on PCA. */ + wlp_tx_hdr_set_delivery_id_type(wlp_tx_hdr, i1480u->options.pca_base_priority); + } + +#if 0 + dev_info(dev, "TX delivering skb -> USB, %zu bytes\n", skb->len); + dump_bytes(dev, skb->data, skb->len > 72 ? 72 : skb->len); +#endif +#if 0 + /* simulates a device lockup after every lockup# packets */ + if (lockup && ((i1480u->stats.tx_packets + 1) % lockup) == 0) { + /* Simulate a dropped transmit interrupt */ + net_dev->trans_start = jiffies; + netif_stop_queue(net_dev); + dev_err(dev, "Simulate lockup at %ld\n", jiffies); + return result; + } +#endif + + result = usb_submit_urb(wtx->urb, GFP_ATOMIC); /* Go baby */ + if (result < 0) { + dev_err(dev, "TX: cannot submit URB: %d\n", result); + /* We leave the freeing of skb to calling function */ + wtx->skb = NULL; + goto error_tx_urb_submit; + } + atomic_inc(&i1480u->tx_inflight.count); + net_dev->trans_start = jiffies; + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return result; + +error_tx_urb_submit: + i1480u_tx_destroy(i1480u, wtx); +error_wtx_alloc: +error_max_inflight: +out: + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return result; +} + + +/** + * Transmit an skb Called when an skbuf has to be transmitted + * + * The skb is first passed to WLP substack to ensure this is a valid + * frame. If valid the device address of destination will be filled and + * the WLP header prepended to the skb. If this step fails we fake sending + * the frame, if we return an error the network stack will just keep trying. + * + * Broadcast frames inside a WSS needs to be treated special as multicast is + * not supported. A broadcast frame is sent as unicast to each member of the + * WSS - this is done by the WLP substack when it finds a broadcast frame. + * So, we test if the WLP substack took over the skb and only transmit it + * if it has not (been taken over). + * + * @net_dev->xmit_lock is held + */ +int i1480u_hard_start_xmit(struct sk_buff *skb, struct net_device *net_dev) +{ + int result; + struct i1480u *i1480u = netdev_priv(net_dev); + struct device *dev = &i1480u->usb_iface->dev; + struct uwb_dev_addr dst; + + d_fnstart(6, dev, "(skb %p (%u), net_dev %p)\n", skb, skb->len, + net_dev); + BUG_ON(i1480u->wlp.rc == NULL); + if ((net_dev->flags & IFF_UP) == 0) + goto error; + result = wlp_prepare_tx_frame(dev, &i1480u->wlp, skb, &dst); + if (result < 0) { + dev_err(dev, "WLP verification of TX frame failed (%d). " + "Dropping packet.\n", result); + goto error; + } else if (result == 1) { + d_printf(6, dev, "WLP will transmit frame. \n"); + /* trans_start time will be set when WLP actually transmits + * the frame */ + goto out; + } + d_printf(6, dev, "Transmitting frame. \n"); + result = i1480u_xmit_frame(&i1480u->wlp, skb, &dst); + if (result < 0) { + dev_err(dev, "Frame TX failed (%d).\n", result); + goto error; + } + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return NETDEV_TX_OK; +error: + dev_kfree_skb_any(skb); + i1480u->stats.tx_dropped++; +out: + d_fnend(6, dev, "(skb %p (%u), net_dev %p) = %d\n", skb, skb->len, + net_dev, result); + return NETDEV_TX_OK; +} + + +/** + * Called when a pkt transmission doesn't complete in a reasonable period + * Device reset may sleep - do it outside of interrupt context (delayed) + */ +void i1480u_tx_timeout(struct net_device *net_dev) +{ + struct i1480u *i1480u = netdev_priv(net_dev); + + wlp_reset_all(&i1480u->wlp); +} + + +void i1480u_tx_release(struct i1480u *i1480u) +{ + unsigned long flags; + struct i1480u_tx *wtx, *next; + int count = 0, empty; + + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + list_for_each_entry_safe(wtx, next, &i1480u->tx_list, list_node) { + count++; + usb_unlink_urb(wtx->urb); + } + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); + count = count*10; /* i1480ut 200ms per unlinked urb (intervals of 20ms) */ + /* + * We don't like this sollution too much (dirty as it is), but + * it is cheaper than putting a refcount on each i1480u_tx and + * i1480uting for all of them to go away... + * + * Called when no more packets can be added to tx_list + * so can i1480ut for it to be empty. + */ + while (1) { + spin_lock_irqsave(&i1480u->tx_list_lock, flags); + empty = list_empty(&i1480u->tx_list); + spin_unlock_irqrestore(&i1480u->tx_list_lock, flags); + if (empty) + break; + count--; + BUG_ON(count == 0); + msleep(20); + } +} -- cgit v1.2.3 From c7f736484f8ecde4dc1bc8459179c4d65f2ccbe4 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:22 +0100 Subject: wusb: add the Wireless USB include files. Common header files derived from the WUSB 1.0 specification. Signed-off-by: David Vrabel --- include/linux/usb/wusb-wa.h | 271 +++++++++++++++++++++++++++++++ include/linux/usb/wusb.h | 376 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 647 insertions(+) create mode 100644 include/linux/usb/wusb-wa.h create mode 100644 include/linux/usb/wusb.h diff --git a/include/linux/usb/wusb-wa.h b/include/linux/usb/wusb-wa.h new file mode 100644 index 00000000000..a102561e702 --- /dev/null +++ b/include/linux/usb/wusb-wa.h @@ -0,0 +1,271 @@ +/* + * Wireless USB Wire Adapter constants and structures. + * + * Copyright (C) 2005-2006 Intel Corporation. + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * FIXME: organize properly, group logically + * + * All the event structures are defined in uwb/spec.h, as they are + * common to the WHCI and WUSB radio control interfaces. + * + * References: + * [WUSB] Wireless Universal Serial Bus Specification, revision 1.0, ch8 + */ +#ifndef __LINUX_USB_WUSB_WA_H +#define __LINUX_USB_WUSB_WA_H + +/** + * Radio Command Request for the Radio Control Interface + * + * Radio Control Interface command and event codes are the same as + * WHCI, and listed in include/linux/uwb.h:UWB_RC_{CMD,EVT}_* + */ +enum { + WA_EXEC_RC_CMD = 40, /* Radio Control command Request */ +}; + +/* Wireless Adapter Requests ([WUSB] table 8-51) */ +enum { + WUSB_REQ_ADD_MMC_IE = 20, + WUSB_REQ_REMOVE_MMC_IE = 21, + WUSB_REQ_SET_NUM_DNTS = 22, + WUSB_REQ_SET_CLUSTER_ID = 23, + WUSB_REQ_SET_DEV_INFO = 24, + WUSB_REQ_GET_TIME = 25, + WUSB_REQ_SET_STREAM_IDX = 26, + WUSB_REQ_SET_WUSB_MAS = 27, +}; + + +/* Wireless Adapter WUSB Channel Time types ([WUSB] table 8-52) */ +enum { + WUSB_TIME_ADJ = 0, + WUSB_TIME_BPST = 1, + WUSB_TIME_WUSB = 2, +}; + +enum { + WA_ENABLE = 0x01, + WA_RESET = 0x02, + RPIPE_PAUSE = 0x1, +}; + +/* Responses from Get Status request ([WUSB] section 8.3.1.6) */ +enum { + WA_STATUS_ENABLED = 0x01, + WA_STATUS_RESETTING = 0x02 +}; + +enum rpipe_crs { + RPIPE_CRS_CTL = 0x01, + RPIPE_CRS_ISO = 0x02, + RPIPE_CRS_BULK = 0x04, + RPIPE_CRS_INTR = 0x08 +}; + +/** + * RPipe descriptor ([WUSB] section 8.5.2.11) + * + * FIXME: explain rpipes + */ +struct usb_rpipe_descriptor { + u8 bLength; + u8 bDescriptorType; + __le16 wRPipeIndex; + __le16 wRequests; + __le16 wBlocks; /* rw if 0 */ + __le16 wMaxPacketSize; /* rw? */ + u8 bHSHubAddress; /* reserved: 0 */ + u8 bHSHubPort; /* ??? FIXME ??? */ + u8 bSpeed; /* rw: xfer rate 'enum uwb_phy_rate' */ + u8 bDeviceAddress; /* rw: Target device address */ + u8 bEndpointAddress; /* rw: Target EP address */ + u8 bDataSequence; /* ro: Current Data sequence */ + __le32 dwCurrentWindow; /* ro */ + u8 bMaxDataSequence; /* ro?: max supported seq */ + u8 bInterval; /* rw: */ + u8 bOverTheAirInterval; /* rw: */ + u8 bmAttribute; /* ro? */ + u8 bmCharacteristics; /* ro? enum rpipe_attr, supported xsactions */ + u8 bmRetryOptions; /* rw? */ + __le16 wNumTransactionErrors; /* rw */ +} __attribute__ ((packed)); + +/** + * Wire Adapter Notification types ([WUSB] sections 8.4.5 & 8.5.4) + * + * These are the notifications coming on the notification endpoint of + * an HWA and a DWA. + */ +enum wa_notif_type { + DWA_NOTIF_RWAKE = 0x91, + DWA_NOTIF_PORTSTATUS = 0x92, + WA_NOTIF_TRANSFER = 0x93, + HWA_NOTIF_BPST_ADJ = 0x94, + HWA_NOTIF_DN = 0x95, +}; + +/** + * Wire Adapter notification header + * + * Notifications coming from a wire adapter use a common header + * defined in [WUSB] sections 8.4.5 & 8.5.4. + */ +struct wa_notif_hdr { + u8 bLength; + u8 bNotifyType; /* enum wa_notif_type */ +} __attribute__((packed)); + +/** + * HWA DN Received notification [(WUSB] section 8.5.4.2) + * + * The DNData is specified in WUSB1.0[7.6]. For each device + * notification we received, we just need to dispatch it. + * + * @dndata: this is really an array of notifications, but all start + * with the same header. + */ +struct hwa_notif_dn { + struct wa_notif_hdr hdr; + u8 bSourceDeviceAddr; /* from errata 2005/07 */ + u8 bmAttributes; + struct wusb_dn_hdr dndata[]; +} __attribute__((packed)); + +/* [WUSB] section 8.3.3 */ +enum wa_xfer_type { + WA_XFER_TYPE_CTL = 0x80, + WA_XFER_TYPE_BI = 0x81, /* bulk/interrupt */ + WA_XFER_TYPE_ISO = 0x82, + WA_XFER_RESULT = 0x83, + WA_XFER_ABORT = 0x84, +}; + +/* [WUSB] section 8.3.3 */ +struct wa_xfer_hdr { + u8 bLength; /* 0x18 */ + u8 bRequestType; /* 0x80 WA_REQUEST_TYPE_CTL */ + __le16 wRPipe; /* RPipe index */ + __le32 dwTransferID; /* Host-assigned ID */ + __le32 dwTransferLength; /* Length of data to xfer */ + u8 bTransferSegment; +} __attribute__((packed)); + +struct wa_xfer_ctl { + struct wa_xfer_hdr hdr; + u8 bmAttribute; + __le16 wReserved; + struct usb_ctrlrequest baSetupData; +} __attribute__((packed)); + +struct wa_xfer_bi { + struct wa_xfer_hdr hdr; + u8 bReserved; + __le16 wReserved; +} __attribute__((packed)); + +struct wa_xfer_hwaiso { + struct wa_xfer_hdr hdr; + u8 bReserved; + __le16 wPresentationTime; + __le32 dwNumOfPackets; + /* FIXME: u8 pktdata[]? */ +} __attribute__((packed)); + +/* [WUSB] section 8.3.3.5 */ +struct wa_xfer_abort { + u8 bLength; + u8 bRequestType; + __le16 wRPipe; /* RPipe index */ + __le32 dwTransferID; /* Host-assigned ID */ +} __attribute__((packed)); + +/** + * WA Transfer Complete notification ([WUSB] section 8.3.3.3) + * + */ +struct wa_notif_xfer { + struct wa_notif_hdr hdr; + u8 bEndpoint; + u8 Reserved; +} __attribute__((packed)); + +/** Transfer result basic codes [WUSB] table 8-15 */ +enum { + WA_XFER_STATUS_SUCCESS, + WA_XFER_STATUS_HALTED, + WA_XFER_STATUS_DATA_BUFFER_ERROR, + WA_XFER_STATUS_BABBLE, + WA_XFER_RESERVED, + WA_XFER_STATUS_NOT_FOUND, + WA_XFER_STATUS_INSUFFICIENT_RESOURCE, + WA_XFER_STATUS_TRANSACTION_ERROR, + WA_XFER_STATUS_ABORTED, + WA_XFER_STATUS_RPIPE_NOT_READY, + WA_XFER_INVALID_FORMAT, + WA_XFER_UNEXPECTED_SEGMENT_NUMBER, + WA_XFER_STATUS_RPIPE_TYPE_MISMATCH, +}; + +/** [WUSB] section 8.3.3.4 */ +struct wa_xfer_result { + struct wa_notif_hdr hdr; + __le32 dwTransferID; + __le32 dwTransferLength; + u8 bTransferSegment; + u8 bTransferStatus; + __le32 dwNumOfPackets; +} __attribute__((packed)); + +/** + * Wire Adapter Class Descriptor ([WUSB] section 8.5.2.7). + * + * NOTE: u16 fields are read Little Endian from the hardware. + * + * @bNumPorts is the original max number of devices that the host can + * connect; we might chop this so the stack can handle + * it. In case you need to access it, use wusbhc->ports_max + * if it is a Wireless USB WA. + */ +struct usb_wa_descriptor { + u8 bLength; + u8 bDescriptorType; + u16 bcdWAVersion; + u8 bNumPorts; /* don't use!! */ + u8 bmAttributes; /* Reserved == 0 */ + u16 wNumRPipes; + u16 wRPipeMaxBlock; + u8 bRPipeBlockSize; + u8 bPwrOn2PwrGood; + u8 bNumMMCIEs; + u8 DeviceRemovable; /* FIXME: in DWA this is up to 16 bytes */ +} __attribute__((packed)); + +/** + * HWA Device Information Buffer (WUSB1.0[T8.54]) + */ +struct hwa_dev_info { + u8 bmDeviceAvailability[32]; /* FIXME: ignored for now */ + u8 bDeviceAddress; + __le16 wPHYRates; + u8 bmDeviceAttribute; +} __attribute__((packed)); + +#endif /* #ifndef __LINUX_USB_WUSB_WA_H */ diff --git a/include/linux/usb/wusb.h b/include/linux/usb/wusb.h new file mode 100644 index 00000000000..5f401b644ed --- /dev/null +++ b/include/linux/usb/wusb.h @@ -0,0 +1,376 @@ +/* + * Wireless USB Standard Definitions + * Event Size Tables + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * FIXME: organize properly, group logically + * + * All the event structures are defined in uwb/spec.h, as they are + * common to the WHCI and WUSB radio control interfaces. + */ + +#ifndef __WUSB_H__ +#define __WUSB_H__ + +#include +#include +#include +#include +#include + +/** + * WUSB Information Element header + * + * I don't know why, they decided to make it different to the MBOA MAC + * IE Header; beats me. + */ +struct wuie_hdr { + u8 bLength; + u8 bIEIdentifier; +} __attribute__((packed)); + +enum { + WUIE_ID_WCTA = 0x80, + WUIE_ID_CONNECTACK, + WUIE_ID_HOST_INFO, + WUIE_ID_CHANGE_ANNOUNCE, + WUIE_ID_DEVICE_DISCONNECT, + WUIE_ID_HOST_DISCONNECT, + WUIE_ID_KEEP_ALIVE = 0x89, + WUIE_ID_ISOCH_DISCARD, + WUIE_ID_RESET_DEVICE, +}; + +/** + * Maximum number of array elements in a WUSB IE. + * + * WUSB1.0[7.5 before table 7-38] says that in WUSB IEs that + * are "arrays" have to limited to 4 elements. So we define it + * like that to ease up and submit only the neeed size. + */ +#define WUIE_ELT_MAX 4 + +/** + * Wrapper for the data that defines a CHID, a CDID or a CK + * + * WUSB defines that CHIDs, CDIDs and CKs are a 16 byte string of + * data. In order to avoid confusion and enforce types, we wrap it. + * + * Make it packed, as we use it in some hw defintions. + */ +struct wusb_ckhdid { + u8 data[16]; +} __attribute__((packed)); + +const static +struct wusb_ckhdid wusb_ckhdid_zero = { .data = { 0 } }; + +#define WUSB_CKHDID_STRSIZE (3 * sizeof(struct wusb_ckhdid) + 1) + +/** + * WUSB IE: Host Information (WUSB1.0[7.5.2]) + * + * Used to provide information about the host to the Wireless USB + * devices in range (CHID can be used as an ASCII string). + */ +struct wuie_host_info { + struct wuie_hdr hdr; + __le16 attributes; + struct wusb_ckhdid CHID; +} __attribute__((packed)); + +/** + * WUSB IE: Connect Ack (WUSB1.0[7.5.1]) + * + * Used to acknowledge device connect requests. See note for + * WUIE_ELT_MAX. + */ +struct wuie_connect_ack { + struct wuie_hdr hdr; + struct { + struct wusb_ckhdid CDID; + u8 bDeviceAddress; /* 0 means unused */ + u8 bReserved; + } blk[WUIE_ELT_MAX]; +} __attribute__((packed)); + +/** + * WUSB IE Host Information Element, Connect Availability + * + * WUSB1.0[7.5.2], bmAttributes description + */ +enum { + WUIE_HI_CAP_RECONNECT = 0, + WUIE_HI_CAP_LIMITED, + WUIE_HI_CAP_RESERVED, + WUIE_HI_CAP_ALL, +}; + +/** + * WUSB IE: Channel Stop (WUSB1.0[7.5.8]) + * + * Tells devices the host is going to stop sending MMCs and will dissapear. + */ +struct wuie_channel_stop { + struct wuie_hdr hdr; + u8 attributes; + u8 timestamp[3]; +} __attribute__((packed)); + +/** + * WUSB IE: Keepalive (WUSB1.0[7.5.9]) + * + * Ask device(s) to send keepalives. + */ +struct wuie_keep_alive { + struct wuie_hdr hdr; + u8 bDeviceAddress[WUIE_ELT_MAX]; +} __attribute__((packed)); + +/** + * WUSB IE: Reset device (WUSB1.0[7.5.11]) + * + * Tell device to reset; in all truth, we can fit 4 CDIDs, but we only + * use it for one at the time... + * + * In any case, this request is a wee bit silly: why don't they target + * by address?? + */ +struct wuie_reset { + struct wuie_hdr hdr; + struct wusb_ckhdid CDID; +} __attribute__((packed)); + +/** + * WUSB IE: Disconnect device (WUSB1.0[7.5.11]) + * + * Tell device to disconnect; we can fit 4 addresses, but we only use + * it for one at the time... + */ +struct wuie_disconnect { + struct wuie_hdr hdr; + u8 bDeviceAddress; + u8 padding; +} __attribute__((packed)); + +/** + * WUSB IE: Host disconnect ([WUSB] section 7.5.5) + * + * Tells all connected devices to disconnect. + */ +struct wuie_host_disconnect { + struct wuie_hdr hdr; +} __attribute__((packed)); + +/** + * WUSB Device Notification header (WUSB1.0[7.6]) + */ +struct wusb_dn_hdr { + u8 bType; + u8 notifdata[]; +} __attribute__((packed)); + +/** Device Notification codes (WUSB1.0[Table 7-54]) */ +enum WUSB_DN { + WUSB_DN_CONNECT = 0x01, + WUSB_DN_DISCONNECT = 0x02, + WUSB_DN_EPRDY = 0x03, + WUSB_DN_MASAVAILCHANGED = 0x04, + WUSB_DN_RWAKE = 0x05, + WUSB_DN_SLEEP = 0x06, + WUSB_DN_ALIVE = 0x07, +}; + +/** WUSB Device Notification Connect */ +struct wusb_dn_connect { + struct wusb_dn_hdr hdr; + __le16 attributes; + struct wusb_ckhdid CDID; +} __attribute__((packed)); + +static inline int wusb_dn_connect_prev_dev_addr(const struct wusb_dn_connect *dn) +{ + return le16_to_cpu(dn->attributes) & 0xff; +} + +static inline int wusb_dn_connect_new_connection(const struct wusb_dn_connect *dn) +{ + return (le16_to_cpu(dn->attributes) >> 8) & 0x1; +} + +static inline int wusb_dn_connect_beacon_behavior(const struct wusb_dn_connect *dn) +{ + return (le16_to_cpu(dn->attributes) >> 9) & 0x03; +} + +/** Device is alive (aka: pong) (WUSB1.0[7.6.7]) */ +struct wusb_dn_alive { + struct wusb_dn_hdr hdr; +} __attribute__((packed)); + +/** Device is disconnecting (WUSB1.0[7.6.2]) */ +struct wusb_dn_disconnect { + struct wusb_dn_hdr hdr; +} __attribute__((packed)); + +/* General constants */ +enum { + WUSB_TRUST_TIMEOUT_MS = 4000, /* [WUSB] section 4.15.1 */ +}; + +static inline size_t ckhdid_printf(char *pr_ckhdid, size_t size, + const struct wusb_ckhdid *ckhdid) +{ + return scnprintf(pr_ckhdid, size, + "%02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx " + "%02hx %02hx %02hx %02hx %02hx %02hx %02hx %02hx", + ckhdid->data[0], ckhdid->data[1], + ckhdid->data[2], ckhdid->data[3], + ckhdid->data[4], ckhdid->data[5], + ckhdid->data[6], ckhdid->data[7], + ckhdid->data[8], ckhdid->data[9], + ckhdid->data[10], ckhdid->data[11], + ckhdid->data[12], ckhdid->data[13], + ckhdid->data[14], ckhdid->data[15]); +} + +/* + * WUSB Crypto stuff (WUSB1.0[6]) + */ + +extern const char *wusb_et_name(u8); + +/** + * WUSB key index WUSB1.0[7.3.2.4], for usage when setting keys for + * the host or the device. + */ +static inline u8 wusb_key_index(int index, int type, int originator) +{ + return (originator << 6) | (type << 4) | index; +} + +#define WUSB_KEY_INDEX_TYPE_PTK 0 /* for HWA only */ +#define WUSB_KEY_INDEX_TYPE_ASSOC 1 +#define WUSB_KEY_INDEX_TYPE_GTK 2 +#define WUSB_KEY_INDEX_ORIGINATOR_HOST 0 +#define WUSB_KEY_INDEX_ORIGINATOR_DEVICE 1 + +/* A CCM Nonce, defined in WUSB1.0[6.4.1] */ +struct aes_ccm_nonce { + u8 sfn[6]; /* Little Endian */ + u8 tkid[3]; /* LE */ + struct uwb_dev_addr dest_addr; + struct uwb_dev_addr src_addr; +} __attribute__((packed)); + +/* A CCM operation label, defined on WUSB1.0[6.5.x] */ +struct aes_ccm_label { + u8 data[14]; +} __attribute__((packed)); + +/* + * Input to the key derivation sequence defined in + * WUSB1.0[6.5.1]. Rest of the data is in the CCM Nonce passed to the + * PRF function. + */ +struct wusb_keydvt_in { + u8 hnonce[16]; + u8 dnonce[16]; +} __attribute__((packed)); + +/* + * Output from the key derivation sequence defined in + * WUSB1.0[6.5.1]. + */ +struct wusb_keydvt_out { + u8 kck[16]; + u8 ptk[16]; +} __attribute__((packed)); + +/* Pseudo Random Function WUSB1.0[6.5] */ +extern int wusb_crypto_init(void); +extern void wusb_crypto_exit(void); +extern ssize_t wusb_prf(void *out, size_t out_size, + const u8 key[16], const struct aes_ccm_nonce *_n, + const struct aes_ccm_label *a, + const void *b, size_t blen, size_t len); + +static inline int wusb_prf_64(void *out, size_t out_size, const u8 key[16], + const struct aes_ccm_nonce *n, + const struct aes_ccm_label *a, + const void *b, size_t blen) +{ + return wusb_prf(out, out_size, key, n, a, b, blen, 64); +} + +static inline int wusb_prf_128(void *out, size_t out_size, const u8 key[16], + const struct aes_ccm_nonce *n, + const struct aes_ccm_label *a, + const void *b, size_t blen) +{ + return wusb_prf(out, out_size, key, n, a, b, blen, 128); +} + +static inline int wusb_prf_256(void *out, size_t out_size, const u8 key[16], + const struct aes_ccm_nonce *n, + const struct aes_ccm_label *a, + const void *b, size_t blen) +{ + return wusb_prf(out, out_size, key, n, a, b, blen, 256); +} + +/* Key derivation WUSB1.0[6.5.1] */ +static inline int wusb_key_derive(struct wusb_keydvt_out *keydvt_out, + const u8 key[16], + const struct aes_ccm_nonce *n, + const struct wusb_keydvt_in *keydvt_in) +{ + const struct aes_ccm_label a = { .data = "Pair-wise keys" }; + return wusb_prf_256(keydvt_out, sizeof(*keydvt_out), key, n, &a, + keydvt_in, sizeof(*keydvt_in)); +} + +/* + * Out-of-band MIC Generation WUSB1.0[6.5.2] + * + * Compute the MIC over @key, @n and @hs and place it in @mic_out. + * + * @mic_out: Where to place the 8 byte MIC tag + * @key: KCK from the derivation process + * @n: CCM nonce, n->sfn == 0, TKID as established in the + * process. + * @hs: Handshake struct for phase 2 of the 4-way. + * hs->bStatus and hs->bReserved are zero. + * hs->bMessageNumber is 2 (WUSB1.0[7.3.2.5.2] + * hs->dest_addr is the device's USB address padded with 0 + * hs->src_addr is the hosts's UWB device address + * hs->mic is ignored (as we compute that value). + */ +static inline int wusb_oob_mic(u8 mic_out[8], const u8 key[16], + const struct aes_ccm_nonce *n, + const struct usb_handshake *hs) +{ + const struct aes_ccm_label a = { .data = "out-of-bandMIC" }; + return wusb_prf_64(mic_out, 8, key, n, &a, + hs, sizeof(*hs) - sizeof(hs->MIC)); +} + +#endif /* #ifndef __WUSB_H__ */ -- cgit v1.2.3 From 90ff96f22426a9d1a06df97dead0a9098facb567 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:23 +0100 Subject: wusb: add the Wireless USB core Add support for Ceritified Wireless USB 1.0 to the USB stack. This has been split into several patches for easier review. core (this patch): - host controller infrastructure - cluster reservation - UWB PAL registration - fake root hub protocol: - MMC management (start/stop, managing IEs) - device connection security: - device authentication and authorization build-system: - Kconfig and Kbuild files Signed-off-by: David Vrabel --- drivers/usb/wusbcore/dev-sysfs.c | 143 +++++++++++ drivers/usb/wusbcore/pal.c | 39 +++ drivers/usb/wusbcore/reservation.c | 115 +++++++++ drivers/usb/wusbcore/rh.c | 477 +++++++++++++++++++++++++++++++++++ drivers/usb/wusbcore/wusbhc.c | 416 +++++++++++++++++++++++++++++++ drivers/usb/wusbcore/wusbhc.h | 495 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1685 insertions(+) create mode 100644 drivers/usb/wusbcore/dev-sysfs.c create mode 100644 drivers/usb/wusbcore/pal.c create mode 100644 drivers/usb/wusbcore/reservation.c create mode 100644 drivers/usb/wusbcore/rh.c create mode 100644 drivers/usb/wusbcore/wusbhc.c create mode 100644 drivers/usb/wusbcore/wusbhc.h diff --git a/drivers/usb/wusbcore/dev-sysfs.c b/drivers/usb/wusbcore/dev-sysfs.c new file mode 100644 index 00000000000..7897a19652e --- /dev/null +++ b/drivers/usb/wusbcore/dev-sysfs.c @@ -0,0 +1,143 @@ +/* + * WUSB devices + * sysfs bindings + * + * Copyright (C) 2007 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Get them out of the way... + */ + +#include +#include +#include +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 4 +#include + +static ssize_t wusb_disconnect_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + unsigned command; + u8 port_idx; + + if (sscanf(buf, "%u", &command) != 1) + return -EINVAL; + if (command == 0) + return size; + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + + mutex_lock(&wusbhc->mutex); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + __wusbhc_dev_disable(wusbhc, port_idx); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return size; +} +static DEVICE_ATTR(wusb_disconnect, 0200, NULL, wusb_disconnect_store); + +static ssize_t wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t result; + struct wusb_dev *wusb_dev; + + wusb_dev = wusb_dev_get_by_usb_dev(to_usb_device(dev)); + if (wusb_dev == NULL) + return -ENODEV; + result = ckhdid_printf(buf, PAGE_SIZE, &wusb_dev->cdid); + strcat(buf, "\n"); + wusb_dev_put(wusb_dev); + return result + 1; +} +static DEVICE_ATTR(wusb_cdid, 0444, wusb_cdid_show, NULL); + +static ssize_t wusb_ck_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int result; + struct usb_device *usb_dev; + struct wusbhc *wusbhc; + struct wusb_ckhdid ck; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &ck.data[0] , &ck.data[1], + &ck.data[2] , &ck.data[3], + &ck.data[4] , &ck.data[5], + &ck.data[6] , &ck.data[7], + &ck.data[8] , &ck.data[9], + &ck.data[10], &ck.data[11], + &ck.data[12], &ck.data[13], + &ck.data[14], &ck.data[15]); + if (result != 16) + return -EINVAL; + + usb_dev = to_usb_device(dev); + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return -ENODEV; + result = wusb_dev_4way_handshake(wusbhc, usb_dev->wusb_dev, &ck); + memset(&ck, 0, sizeof(ck)); + wusbhc_put(wusbhc); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_ck, 0200, NULL, wusb_ck_store); + +static struct attribute *wusb_dev_attrs[] = { + &dev_attr_wusb_disconnect.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_ck.attr, + NULL, +}; + +static struct attribute_group wusb_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusb_dev_attrs, +}; + +int wusb_dev_sysfs_add(struct wusbhc *wusbhc, struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + int result = sysfs_create_group(&usb_dev->dev.kobj, + &wusb_dev_attr_group); + struct device *dev = &usb_dev->dev; + if (result < 0) + dev_err(dev, "Cannot register WUSB-dev attributes: %d\n", + result); + return result; +} + +void wusb_dev_sysfs_rm(struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + if (usb_dev) + sysfs_remove_group(&usb_dev->dev.kobj, &wusb_dev_attr_group); +} diff --git a/drivers/usb/wusbcore/pal.c b/drivers/usb/wusbcore/pal.c new file mode 100644 index 00000000000..cc126b44473 --- /dev/null +++ b/drivers/usb/wusbcore/pal.c @@ -0,0 +1,39 @@ +/* + * Wireless USB Host Controller + * UWB Protocol Adaptation Layer (PAL) glue. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "wusbhc.h" + +/** + * wusbhc_pal_register - register the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +int wusbhc_pal_register(struct wusbhc *wusbhc) +{ + uwb_pal_init(&wusbhc->pal); + + return uwb_pal_register(wusbhc->uwb_rc, &wusbhc->pal); +} + +/** + * wusbhc_pal_register - unregister the WUSB HC as a UWB PAL + * @wusbhc: the WUSB HC + */ +void wusbhc_pal_unregister(struct wusbhc *wusbhc) +{ + uwb_pal_unregister(wusbhc->uwb_rc, &wusbhc->pal); +} diff --git a/drivers/usb/wusbcore/reservation.c b/drivers/usb/wusbcore/reservation.c new file mode 100644 index 00000000000..fc63e77ded2 --- /dev/null +++ b/drivers/usb/wusbcore/reservation.c @@ -0,0 +1,115 @@ +/* + * WUSB cluster reservation management + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +#include "wusbhc.h" + +/* + * WUSB cluster reservations are multicast reservations with the + * broadcast cluster ID (BCID) as the target DevAddr. + * + * FIXME: consider adjusting the reservation depending on what devices + * are attached. + */ + +static int wusbhc_bwa_set(struct wusbhc *wusbhc, u8 stream, + const struct uwb_mas_bm *mas) +{ + if (mas == NULL) + mas = &uwb_mas_bm_zero; + return wusbhc->bwa_set(wusbhc, stream, mas); +} + +/** + * wusbhc_rsv_complete_cb - WUSB HC reservation complete callback + * @rsv: the reservation + * + * Either set or clear the HC's view of the reservation. + * + * FIXME: when a reservation is denied the HC should be stopped. + */ +static void wusbhc_rsv_complete_cb(struct uwb_rsv *rsv) +{ + struct wusbhc *wusbhc = rsv->pal_priv; + struct device *dev = wusbhc->dev; + char buf[72]; + + switch (rsv->state) { + case UWB_RSV_STATE_O_ESTABLISHED: + bitmap_scnprintf(buf, sizeof(buf), rsv->mas.bm, UWB_NUM_MAS); + dev_dbg(dev, "established reservation: %s\n", buf); + wusbhc_bwa_set(wusbhc, rsv->stream, &rsv->mas); + break; + case UWB_RSV_STATE_NONE: + dev_dbg(dev, "removed reservation\n"); + wusbhc_bwa_set(wusbhc, 0, NULL); + wusbhc->rsv = NULL; + break; + default: + dev_dbg(dev, "unexpected reservation state: %d\n", rsv->state); + break; + } +} + + +/** + * wusbhc_rsv_establish - establish a reservation for the cluster + * @wusbhc: the WUSB HC requesting a bandwith reservation + */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc) +{ + struct uwb_rc *rc = wusbhc->uwb_rc; + struct uwb_rsv *rsv; + struct uwb_dev_addr bcid; + int ret; + + rsv = uwb_rsv_create(rc, wusbhc_rsv_complete_cb, wusbhc); + if (rsv == NULL) + return -ENOMEM; + + bcid.data[0] = wusbhc->cluster_id; + bcid.data[1] = 0; + + rsv->owner = &rc->uwb_dev; + rsv->target.type = UWB_RSV_TARGET_DEVADDR; + rsv->target.devaddr = bcid; + rsv->type = UWB_DRP_TYPE_PRIVATE; + rsv->max_mas = 256; + rsv->min_mas = 16; /* one MAS per zone? */ + rsv->sparsity = 16; /* at least one MAS in each zone? */ + rsv->is_multicast = true; + + ret = uwb_rsv_establish(rsv); + if (ret == 0) + wusbhc->rsv = rsv; + else + uwb_rsv_destroy(rsv); + return ret; +} + + +/** + * wusbhc_rsv_terminate - terminate any cluster reservation + * @wusbhc: the WUSB host whose reservation is to be terminated + */ +void wusbhc_rsv_terminate(struct wusbhc *wusbhc) +{ + if (wusbhc->rsv) + uwb_rsv_terminate(wusbhc->rsv); +} diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c new file mode 100644 index 00000000000..267a6432510 --- /dev/null +++ b/drivers/usb/wusbcore/rh.c @@ -0,0 +1,477 @@ +/* + * Wireless USB Host Controller + * Root Hub operations + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We fake a root hub that has fake ports (as many as simultaneous + * devices the Wireless USB Host Controller can deal with). For each + * port we keep an state in @wusbhc->port[index] identical to the one + * specified in the USB2.0[ch11] spec and some extra device + * information that complements the one in 'struct usb_device' (as + * this lacs a hcpriv pointer). + * + * Note this is common to WHCI and HWA host controllers. + * + * Through here we enable most of the state changes that the USB stack + * will use to connect or disconnect devices. We need to do some + * forced adaptation of Wireless USB device states vs. wired: + * + * USB: WUSB: + * + * Port Powered-off port slot n/a + * Powered-on port slot available + * Disconnected port slot available + * Connected port slot assigned device + * device sent DN_Connect + * device was authenticated + * Enabled device is authenticated, transitioned + * from unauth -> auth -> default address + * -> enabled + * Reset disconnect + * Disable disconnect + * + * This maps the standard USB port states with the WUSB device states + * so we can fake ports without having to modify the USB stack. + * + * FIXME: this process will change in the future + * + * + * ENTRY POINTS + * + * Our entry points into here are, as in hcd.c, the USB stack root hub + * ops defined in the usb_hcd struct: + * + * wusbhc_rh_status_data() Provide hub and port status data bitmap + * + * wusbhc_rh_control() Execution of all the major requests + * you can do to a hub (Set|Clear + * features, get descriptors, status, etc). + * + * wusbhc_rh_[suspend|resume]() That + * + * wusbhc_rh_start_port_reset() ??? unimplemented + */ +#include "wusbhc.h" + +#define D_LOCAL 0 +#include + +/* + * Reset a fake port + * + * This can be called to reset a port from any other state or to reset + * it when connecting. In Wireless USB they are different; when doing + * a new connect that involves going over the authentication. When + * just reseting, its a different story. + * + * The Linux USB stack resets a port twice before it considers it + * enabled, so we have to detect and ignore that. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Supposedly we are the only thread accesing @wusbhc->port; in any + * case, maybe we should move the mutex locking from + * wusbhc_devconnect_auth() to here. + * + * @port_idx refers to the wusbhc's port index, not the USB port number + */ +static int wusbhc_rh_port_reset(struct wusbhc *wusbhc, u8 port_idx) +{ + int result = 0; + struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx); + + d_fnstart(3, wusbhc->dev, "(wusbhc %p port_idx %u)\n", + wusbhc, port_idx); + if (port->reset_count == 0) { + wusbhc_devconnect_auth(wusbhc, port_idx); + port->reset_count++; + } else if (port->reset_count == 1) + /* see header */ + d_printf(2, wusbhc->dev, "Ignoring second reset on port_idx " + "%u\n", port_idx); + else + result = wusbhc_dev_reset(wusbhc, port_idx); + d_fnend(3, wusbhc->dev, "(wusbhc %p port_idx %u) = %d\n", + wusbhc, port_idx, result); + return result; +} + +/* + * Return the hub change status bitmap + * + * The bits in the change status bitmap are cleared when a + * ClearPortFeature request is issued (USB2.0[11.12.3,11.12.4]. + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * WARNING!! This gets called from atomic context; we cannot get the + * mutex--the only race condition we can find is some bit + * changing just after we copy it, which shouldn't be too + * big of a problem [and we can't make it an spinlock + * because other parts need to take it and sleep] . + * + * @usb_hcd is refcounted, so it won't dissapear under us + * and before killing a host, the polling of the root hub + * would be stopped anyway. + */ +int wusbhc_rh_status_data(struct usb_hcd *usb_hcd, char *_buf) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + size_t cnt, size; + unsigned long *buf = (unsigned long *) _buf; + + d_fnstart(1, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + /* WE DON'T LOCK, see comment */ + size = wusbhc->ports_max + 1 /* hub bit */; + size = (size + 8 - 1) / 8; /* round to bytes */ + for (cnt = 0; cnt < wusbhc->ports_max; cnt++) + if (wusb_port_by_idx(wusbhc, cnt)->change) + set_bit(cnt + 1, buf); + else + clear_bit(cnt + 1, buf); + d_fnend(1, wusbhc->dev, "(wusbhc %p) %u, buffer:\n", wusbhc, (int)size); + d_dump(1, wusbhc->dev, _buf, size); + return size; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_status_data); + +/* + * Return the hub's desciptor + * + * NOTE: almost cut and paste from ehci-hub.c + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked + */ +static int wusbhc_rh_get_hub_descr(struct wusbhc *wusbhc, u16 wValue, + u16 wIndex, + struct usb_hub_descriptor *descr, + u16 wLength) +{ + u16 temp = 1 + (wusbhc->ports_max / 8); + u8 length = 7 + 2 * temp; + + if (wLength < length) + return -ENOSPC; + descr->bDescLength = 7 + 2 * temp; + descr->bDescriptorType = 0x29; /* HUB type */ + descr->bNbrPorts = wusbhc->ports_max; + descr->wHubCharacteristics = cpu_to_le16( + 0x00 /* All ports power at once */ + | 0x00 /* not part of compound device */ + | 0x10 /* No overcurrent protection */ + | 0x00 /* 8 FS think time FIXME ?? */ + | 0x00); /* No port indicators */ + descr->bPwrOn2PwrGood = 0; + descr->bHubContrCurrent = 0; + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&descr->bitmap[0], 0, temp); + memset(&descr->bitmap[temp], 0xff, temp); + return 0; +} + +/* + * Clear a hub feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_clear_hub_feat(struct wusbhc *wusbhc, u16 feature) +{ + int result; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(%p, feature 0x%04u)\n", wusbhc, feature); + switch (feature) { + case C_HUB_LOCAL_POWER: + /* FIXME: maybe plug bit 0 to the power input status, + * if any? + * see wusbhc_rh_get_hub_status() */ + case C_HUB_OVER_CURRENT: + result = 0; + break; + default: + result = -EPIPE; + } + d_fnend(4, dev, "(%p, feature 0x%04u), %d\n", wusbhc, feature, result); + return result; +} + +/* + * Return hub status (it is always zero...) + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + * + * Nothing to do, so no locking needed ;) + */ +static int wusbhc_rh_get_hub_status(struct wusbhc *wusbhc, u32 *buf, + u16 wLength) +{ + /* FIXME: maybe plug bit 0 to the power input status (if any)? */ + *buf = 0; + return 0; +} + +/* + * Set a port feature + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_set_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + int result = -EINVAL; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d)\n", + feature, selector, port_idx); + + if (port_idx > wusbhc->ports_max) + goto error; + + switch (feature) { + /* According to USB2.0[11.24.2.13]p2, these features + * are not required to be implemented. */ + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_RESET: + result = 0; + break; + + case USB_PORT_FEAT_POWER: + /* No such thing, but we fake it works */ + mutex_lock(&wusbhc->mutex); + wusb_port_by_idx(wusbhc, port_idx)->status |= USB_PORT_STAT_POWER; + mutex_unlock(&wusbhc->mutex); + result = 0; + break; + case USB_PORT_FEAT_RESET: + result = wusbhc_rh_port_reset(wusbhc, port_idx); + break; + case USB_PORT_FEAT_ENABLE: + case USB_PORT_FEAT_SUSPEND: + dev_err(dev, "(port_idx %d) set feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + result = -ENOSYS; + break; + default: + dev_err(dev, "(port_idx %d) set feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + result = -EPIPE; + break; + } +error: + d_fnend(4, dev, "(feat 0x%04u, selector 0x%u, port_idx %d) = %d\n", + feature, selector, port_idx, result); + return result; +} + +/* + * Clear a port feature... + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature, + u8 selector, u8 port_idx) +{ + int result = -EINVAL; + struct device *dev = wusbhc->dev; + + d_fnstart(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d)\n", + wusbhc, feature, selector, port_idx); + + if (port_idx > wusbhc->ports_max) + goto error; + + mutex_lock(&wusbhc->mutex); + result = 0; + switch (feature) { + case USB_PORT_FEAT_POWER: /* fake port always on */ + /* According to USB2.0[11.24.2.7.1.4], no need to implement? */ + case USB_PORT_FEAT_C_OVER_CURRENT: + break; + case USB_PORT_FEAT_C_RESET: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_RESET; + break; + case USB_PORT_FEAT_C_CONNECTION: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_CONNECTION; + break; + case USB_PORT_FEAT_ENABLE: + __wusbhc_dev_disable(wusbhc, port_idx); + break; + case USB_PORT_FEAT_C_ENABLE: + wusb_port_by_idx(wusbhc, port_idx)->change &= ~USB_PORT_STAT_C_ENABLE; + break; + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_SUSPEND: + case 0xffff: /* ??? FIXME */ + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNIMPLEMENTED\n", + port_idx, feature, selector); + /* dump_stack(); */ + result = -ENOSYS; + break; + default: + dev_err(dev, "(port_idx %d) Clear feat %d/%d UNKNOWN\n", + port_idx, feature, selector); + result = -EPIPE; + break; + } + mutex_unlock(&wusbhc->mutex); +error: + d_fnend(4, dev, "(wusbhc %p feat 0x%04x selector %d port_idx %d) = " + "%d\n", wusbhc, feature, selector, port_idx, result); + return result; +} + +/* + * Return the port's status + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +static int wusbhc_rh_get_port_status(struct wusbhc *wusbhc, u16 port_idx, + u32 *_buf, u16 wLength) +{ + int result = -EINVAL; + u16 *buf = (u16 *) _buf; + + d_fnstart(1, wusbhc->dev, "(wusbhc %p port_idx %u wLength %u)\n", + wusbhc, port_idx, wLength); + if (port_idx > wusbhc->ports_max) + goto error; + mutex_lock(&wusbhc->mutex); + buf[0] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->status); + buf[1] = cpu_to_le16(wusb_port_by_idx(wusbhc, port_idx)->change); + result = 0; + mutex_unlock(&wusbhc->mutex); +error: + d_fnend(1, wusbhc->dev, "(wusbhc %p) = %d, buffer:\n", wusbhc, result); + d_dump(1, wusbhc->dev, _buf, wLength); + return result; +} + +/* + * Entry point for Root Hub operations + * + * @wusbhc is assumed referenced and @wusbhc->mutex unlocked. + */ +int wusbhc_rh_control(struct usb_hcd *usb_hcd, u16 reqntype, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + int result = -ENOSYS; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + + switch (reqntype) { + case GetHubDescriptor: + result = wusbhc_rh_get_hub_descr( + wusbhc, wValue, wIndex, + (struct usb_hub_descriptor *) buf, wLength); + break; + case ClearHubFeature: + result = wusbhc_rh_clear_hub_feat(wusbhc, wValue); + break; + case GetHubStatus: + result = wusbhc_rh_get_hub_status(wusbhc, (u32 *)buf, wLength); + break; + + case SetPortFeature: + result = wusbhc_rh_set_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case ClearPortFeature: + result = wusbhc_rh_clear_port_feat(wusbhc, wValue, wIndex >> 8, + (wIndex & 0xff) - 1); + break; + case GetPortStatus: + result = wusbhc_rh_get_port_status(wusbhc, wIndex - 1, + (u32 *)buf, wLength); + break; + + case SetHubFeature: + default: + dev_err(wusbhc->dev, "%s (%p [%p], %x, %x, %x, %p, %x) " + "UNIMPLEMENTED\n", __func__, usb_hcd, wusbhc, reqntype, + wValue, wIndex, buf, wLength); + /* dump_stack(); */ + result = -ENOSYS; + } + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_control); + +int wusbhc_rh_suspend(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_suspend); + +int wusbhc_rh_resume(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, wusbhc); + /* dump_stack(); */ + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_resume); + +int wusbhc_rh_start_port_reset(struct usb_hcd *usb_hcd, unsigned port_idx) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + dev_err(wusbhc->dev, "%s (%p [%p], port_idx %u) UNIMPLEMENTED\n", + __func__, usb_hcd, wusbhc, port_idx); + WARN_ON(1); + return -ENOSYS; +} +EXPORT_SYMBOL_GPL(wusbhc_rh_start_port_reset); + +static void wusb_port_init(struct wusb_port *port) +{ + port->status |= USB_PORT_STAT_HIGH_SPEED; +} + +/* + * Alloc fake port specific fields and status. + */ +int wusbhc_rh_create(struct wusbhc *wusbhc) +{ + int result = -ENOMEM; + size_t port_size, itr; + port_size = wusbhc->ports_max * sizeof(wusbhc->port[0]); + wusbhc->port = kzalloc(port_size, GFP_KERNEL); + if (wusbhc->port == NULL) + goto error_port_alloc; + for (itr = 0; itr < wusbhc->ports_max; itr++) + wusb_port_init(&wusbhc->port[itr]); + result = 0; +error_port_alloc: + return result; +} + +void wusbhc_rh_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->port); +} diff --git a/drivers/usb/wusbcore/wusbhc.c b/drivers/usb/wusbcore/wusbhc.c new file mode 100644 index 00000000000..1149b1e59c8 --- /dev/null +++ b/drivers/usb/wusbcore/wusbhc.c @@ -0,0 +1,416 @@ +/* + * Wireless USB Host Controller + * sysfs glue, wusbcore module support and life cycle management + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * Creation/destruction of wusbhc is split in two parts; that that + * doesn't require the HCD to be added (wusbhc_{create,destroy}) and + * the one that requires (phase B, wusbhc_b_{create,destroy}). + * + * This is so because usb_add_hcd() will start the HC, and thus, all + * the HC specific stuff has to be already initialiazed (like sysfs + * thingies). + */ +#include +#include +#include "wusbhc.h" + +/** + * Extract the wusbhc that corresponds to a USB Host Controller class device + * + * WARNING! Apply only if @dev is that of a + * wusbhc.usb_hcd.self->class_dev; otherwise, you loose. + */ +static struct wusbhc *usbhc_dev_to_wusbhc(struct device *dev) +{ + struct usb_bus *usb_bus = dev_get_drvdata(dev); + struct usb_hcd *usb_hcd = bus_to_hcd(usb_bus); + return usb_hcd_to_wusbhc(usb_hcd); +} + +/* + * Show & store the current WUSB trust timeout + * + * We don't do locking--it is an 'atomic' value. + * + * The units that we store/show are always MILLISECONDS. However, the + * value of trust_timeout is jiffies. + */ +static ssize_t wusb_trust_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + + return scnprintf(buf, PAGE_SIZE, "%u\n", wusbhc->trust_timeout); +} + +static ssize_t wusb_trust_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + ssize_t result = -ENOSYS; + unsigned trust_timeout; + + result = sscanf(buf, "%u", &trust_timeout); + if (result != 1) { + result = -EINVAL; + goto out; + } + /* FIXME: maybe we should check for range validity? */ + wusbhc->trust_timeout = trust_timeout; + cancel_delayed_work(&wusbhc->keep_alive_timer); + flush_workqueue(wusbd); + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (trust_timeout * CONFIG_HZ)/1000/2); +out: + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_trust_timeout, 0644, wusb_trust_timeout_show, + wusb_trust_timeout_store); + +/* + * Show & store the current WUSB CHID + */ +static ssize_t wusb_chid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + ssize_t result = 0; + + if (wusbhc->wuie_host_info != NULL) + result += ckhdid_printf(buf, PAGE_SIZE, + &wusbhc->wuie_host_info->CHID); + return result; +} + +/* + * Store a new CHID + * + * This will (FIXME) trigger many changes. + * + * - Send an all zeros CHID and it will stop the controller + * - Send a non-zero CHID and it will start it + * (unless it was started, it will just change the CHID, + * diconnecting all devices first). + * + * So first we scan the MMC we are sent and then we act on it. We + * read it in the same format as we print it, an ASCII string of 16 + * hex bytes. + * + * See wusbhc_chid_set() for more info. + */ +static ssize_t wusb_chid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct wusbhc *wusbhc = usbhc_dev_to_wusbhc(dev); + struct wusb_ckhdid chid; + ssize_t result; + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &chid.data[0] , &chid.data[1] , + &chid.data[2] , &chid.data[3] , + &chid.data[4] , &chid.data[5] , + &chid.data[6] , &chid.data[7] , + &chid.data[8] , &chid.data[9] , + &chid.data[10], &chid.data[11], + &chid.data[12], &chid.data[13], + &chid.data[14], &chid.data[15]); + if (result != 16) { + dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits): " + "%d\n", (int)result); + return -EINVAL; + } + result = wusbhc_chid_set(wusbhc, &chid); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_chid, 0644, wusb_chid_show, wusb_chid_store); + +/* Group all the WUSBHC attributes */ +static struct attribute *wusbhc_attrs[] = { + &dev_attr_wusb_trust_timeout.attr, + &dev_attr_wusb_chid.attr, + NULL, +}; + +static struct attribute_group wusbhc_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = wusbhc_attrs, +}; + +/* + * Create a wusbhc instance + * + * NOTEs: + * + * - assumes *wusbhc has been zeroed and wusbhc->usb_hcd has been + * initialized but not added. + * + * - fill out ports_max, mmcies_max and mmcie_{add,rm} before calling. + * + * - fill out wusbhc->uwb_rc and refcount it before calling + * - fill out the wusbhc->sec_modes array + */ +int wusbhc_create(struct wusbhc *wusbhc) +{ + int result = 0; + + wusbhc->trust_timeout = WUSB_TRUST_TIMEOUT_MS; + mutex_init(&wusbhc->mutex); + result = wusbhc_mmcie_create(wusbhc); + if (result < 0) + goto error_mmcie_create; + result = wusbhc_devconnect_create(wusbhc); + if (result < 0) + goto error_devconnect_create; + result = wusbhc_rh_create(wusbhc); + if (result < 0) + goto error_rh_create; + result = wusbhc_sec_create(wusbhc); + if (result < 0) + goto error_sec_create; + result = wusbhc_pal_register(wusbhc); + if (result < 0) + goto error_pal_register; + return 0; + +error_pal_register: + wusbhc_sec_destroy(wusbhc); +error_sec_create: + wusbhc_rh_destroy(wusbhc); +error_rh_create: + wusbhc_devconnect_destroy(wusbhc); +error_devconnect_create: + wusbhc_mmcie_destroy(wusbhc); +error_mmcie_create: + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_create); + +static inline struct kobject *wusbhc_kobj(struct wusbhc *wusbhc) +{ + return &wusbhc->usb_hcd.self.controller->kobj; +} + +/* + * Phase B of a wusbhc instance creation + * + * Creates fields that depend on wusbhc->usb_hcd having been + * added. This is where we create the sysfs files in + * /sys/class/usb_host/usb_hostX/. + * + * NOTE: Assumes wusbhc->usb_hcd has been already added by the upper + * layer (hwahc or whci) + */ +int wusbhc_b_create(struct wusbhc *wusbhc) +{ + int result = 0; + struct device *dev = wusbhc->usb_hcd.self.controller; + + result = sysfs_create_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); + if (result < 0) { + dev_err(dev, "Cannot register WUSBHC attributes: %d\n", result); + goto error_create_attr_group; + } + /* Yep, I plan to add stuff here... */ +error_create_attr_group: + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_b_create); + +void wusbhc_b_destroy(struct wusbhc *wusbhc) +{ + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); +} +EXPORT_SYMBOL_GPL(wusbhc_b_destroy); + +void wusbhc_destroy(struct wusbhc *wusbhc) +{ + wusbhc_pal_unregister(wusbhc); + wusbhc_sec_destroy(wusbhc); + wusbhc_rh_destroy(wusbhc); + wusbhc_devconnect_destroy(wusbhc); + wusbhc_mmcie_destroy(wusbhc); +} +EXPORT_SYMBOL_GPL(wusbhc_destroy); + +struct workqueue_struct *wusbd; +EXPORT_SYMBOL_GPL(wusbd); + +/* + * WUSB Cluster ID allocation map + * + * Each WUSB bus in a channel is identified with a Cluster Id in the + * unauth address pace (WUSB1.0[4.3]). We take the range 0xe0 to 0xff + * (that's space for 31 WUSB controllers, as 0xff can't be taken). We + * start taking from 0xff, 0xfe, 0xfd... (hence the += or -= 0xff). + * + * For each one we taken, we pin it in the bitap + */ +#define CLUSTER_IDS 32 +static DECLARE_BITMAP(wusb_cluster_id_table, CLUSTER_IDS); +static DEFINE_SPINLOCK(wusb_cluster_ids_lock); + +/* + * Get a WUSB Cluster ID + * + * Need to release with wusb_cluster_id_put() when done w/ it. + */ +/* FIXME: coordinate with the choose_addres() from the USB stack */ +/* we want to leave the top of the 128 range for cluster addresses and + * the bottom for device addresses (as we map them one on one with + * ports). */ +u8 wusb_cluster_id_get(void) +{ + u8 id; + spin_lock(&wusb_cluster_ids_lock); + id = find_first_zero_bit(wusb_cluster_id_table, CLUSTER_IDS); + if (id > CLUSTER_IDS) { + id = 0; + goto out; + } + set_bit(id, wusb_cluster_id_table); + id = (u8) 0xff - id; +out: + spin_unlock(&wusb_cluster_ids_lock); + return id; + +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_get); + +/* + * Release a WUSB Cluster ID + * + * Obtained it with wusb_cluster_id_get() + */ +void wusb_cluster_id_put(u8 id) +{ + id = 0xff - id; + BUG_ON(id >= CLUSTER_IDS); + spin_lock(&wusb_cluster_ids_lock); + WARN_ON(!test_bit(id, wusb_cluster_id_table)); + clear_bit(id, wusb_cluster_id_table); + spin_unlock(&wusb_cluster_ids_lock); +} +EXPORT_SYMBOL_GPL(wusb_cluster_id_put); + +/** + * wusbhc_giveback_urb - return an URB to the USB core + * @wusbhc: the host controller the URB is from. + * @urb: the URB. + * @status: the URB's status. + * + * Return an URB to the USB core doing some additional WUSB specific + * processing. + * + * - After a successful transfer, update the trust timeout timestamp + * for the WUSB device. + * + * - [WUSB] sections 4.13 and 7.5.1 specifies the stop retrasmittion + * condition for the WCONNECTACK_IE is that the host has observed + * the associated device responding to a control transfer. + */ +void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, int status) +{ + struct wusb_dev *wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); + + if (status == 0) { + wusb_dev->entry_ts = jiffies; + + /* wusbhc_devconnect_acked() can't be called from from + atomic context so defer it to a work queue. */ + if (!list_empty(&wusb_dev->cack_node)) + queue_work(wusbd, &wusb_dev->devconnect_acked_work); + } + + usb_hcd_giveback_urb(&wusbhc->usb_hcd, urb, status); +} +EXPORT_SYMBOL_GPL(wusbhc_giveback_urb); + +/** + * wusbhc_reset_all - reset the HC hardware + * @wusbhc: the host controller to reset. + * + * Request a full hardware reset of the chip. This will also reset + * the radio controller and any other PALs. + */ +void wusbhc_reset_all(struct wusbhc *wusbhc) +{ + uwb_rc_reset_all(wusbhc->uwb_rc); +} +EXPORT_SYMBOL_GPL(wusbhc_reset_all); + +static struct notifier_block wusb_usb_notifier = { + .notifier_call = wusb_usb_ncb, + .priority = INT_MAX /* Need to be called first of all */ +}; + +static int __init wusbcore_init(void) +{ + int result; + result = wusb_crypto_init(); + if (result < 0) + goto error_crypto_init; + /* WQ is singlethread because we need to serialize notifications */ + wusbd = create_singlethread_workqueue("wusbd"); + if (wusbd == NULL) { + result = -ENOMEM; + printk(KERN_ERR "WUSB-core: Cannot create wusbd workqueue\n"); + goto error_wusbd_create; + } + usb_register_notify(&wusb_usb_notifier); + bitmap_zero(wusb_cluster_id_table, CLUSTER_IDS); + set_bit(0, wusb_cluster_id_table); /* reserve Cluster ID 0xff */ + return 0; + +error_wusbd_create: + wusb_crypto_exit(); +error_crypto_init: + return result; + +} +module_init(wusbcore_init); + +static void __exit wusbcore_exit(void) +{ + clear_bit(0, wusb_cluster_id_table); + if (!bitmap_empty(wusb_cluster_id_table, CLUSTER_IDS)) { + char buf[256]; + bitmap_scnprintf(buf, sizeof(buf), wusb_cluster_id_table, + CLUSTER_IDS); + printk(KERN_ERR "BUG: WUSB Cluster IDs not released " + "on exit: %s\n", buf); + WARN_ON(1); + } + usb_unregister_notify(&wusb_usb_notifier); + destroy_workqueue(wusbd); + wusb_crypto_exit(); +} +module_exit(wusbcore_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wusbhc.h b/drivers/usb/wusbcore/wusbhc.h new file mode 100644 index 00000000000..d0c132434f1 --- /dev/null +++ b/drivers/usb/wusbcore/wusbhc.h @@ -0,0 +1,495 @@ +/* + * Wireless USB Host Controller + * Common infrastructure for WHCI and HWA WUSB-HC drivers + * + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver implements parts common to all Wireless USB Host + * Controllers (struct wusbhc, embedding a struct usb_hcd) and is used + * by: + * + * - hwahc: HWA, USB-dongle that implements a Wireless USB host + * controller, (Wireless USB 1.0 Host-Wire-Adapter specification). + * + * - whci: WHCI, a PCI card with a wireless host controller + * (Wireless Host Controller Interface 1.0 specification). + * + * Check out the Design-overview.txt file in the source documentation + * for other details on the implementation. + * + * Main blocks: + * + * rh Root Hub emulation (part of the HCD glue) + * + * devconnect Handle all the issues related to device connection, + * authentication, disconnection, timeout, reseting, + * keepalives, etc. + * + * mmc MMC IE broadcasting handling + * + * A host controller driver just initializes its stuff and as part of + * that, creates a 'struct wusbhc' instance that handles all the + * common WUSB mechanisms. Links in the function ops that are specific + * to it and then registers the host controller. Ready to run. + */ + +#ifndef __WUSBHC_H__ +#define __WUSBHC_H__ + +#include +#include +#include +#include +#include +/* FIXME: Yes, I know: BAD--it's not my fault the USB HC iface is not + * public */ +#include +#include +#include + + +/** + * Wireless USB device + * + * Describe a WUSB device connected to the cluster. This struct + * belongs to the 'struct wusb_port' it is attached to and it is + * responsible for putting and clearing the pointer to it. + * + * Note this "complements" the 'struct usb_device' that the usb_hcd + * keeps for each connected USB device. However, it extends some + * information that is not available (there is no hcpriv ptr in it!) + * *and* most importantly, it's life cycle is different. It is created + * as soon as we get a DN_Connect (connect request notification) from + * the device through the WUSB host controller; the USB stack doesn't + * create the device until we authenticate it. FIXME: this will + * change. + * + * @bos: This is allocated when the BOS descriptors are read from + * the device and freed upon the wusb_dev struct dying. + * @wusb_cap_descr: points into @bos, and has been verified to be size + * safe. + */ +struct wusb_dev { + struct kref refcnt; + struct wusbhc *wusbhc; + struct list_head cack_node; /* Connect-Ack list */ + u8 port_idx; + u8 addr; + u8 beacon_type:4; + struct usb_encryption_descriptor ccm1_etd; + struct wusb_ckhdid cdid; + unsigned long entry_ts; + struct usb_bos_descriptor *bos; + struct usb_wireless_cap_descriptor *wusb_cap_descr; + struct uwb_mas_bm availability; + struct work_struct devconnect_acked_work; + struct urb *set_gtk_urb; + struct usb_ctrlrequest *set_gtk_req; + struct usb_device *usb_dev; +}; + +#define WUSB_DEV_ADDR_UNAUTH 0x80 + +static inline void wusb_dev_init(struct wusb_dev *wusb_dev) +{ + kref_init(&wusb_dev->refcnt); + /* no need to init the cack_node */ +} + +extern void wusb_dev_destroy(struct kref *_wusb_dev); + +static inline struct wusb_dev *wusb_dev_get(struct wusb_dev *wusb_dev) +{ + kref_get(&wusb_dev->refcnt); + return wusb_dev; +} + +static inline void wusb_dev_put(struct wusb_dev *wusb_dev) +{ + kref_put(&wusb_dev->refcnt, wusb_dev_destroy); +} + +/** + * Wireless USB Host Controlller root hub "fake" ports + * (state and device information) + * + * Wireless USB is wireless, so there are no ports; but we + * fake'em. Each RC can connect a max of devices at the same time + * (given in the Wireless Adapter descriptor, bNumPorts or WHCI's + * caps), referred to in wusbhc->ports_max. + * + * See rh.c for more information. + * + * The @status and @change use the same bits as in USB2.0[11.24.2.7], + * so we don't have to do much when getting the port's status. + * + * WUSB1.0[7.1], USB2.0[11.24.2.7.1,fig 11-10], + * include/linux/usb_ch9.h (#define USB_PORT_STAT_*) + */ +struct wusb_port { + u16 status; + u16 change; + struct wusb_dev *wusb_dev; /* connected device's info */ + unsigned reset_count; + u32 ptk_tkid; +}; + +/** + * WUSB Host Controller specifics + * + * All fields that are common to all Wireless USB controller types + * (HWA and WHCI) are grouped here. Host Controller + * functions/operations that only deal with general Wireless USB HC + * issues use this data type to refer to the host. + * + * @usb_hcd Instantiation of a USB host controller + * (initialized by upper layer [HWA=HC or WHCI]. + * + * @dev Device that implements this; initialized by the + * upper layer (HWA-HC, WHCI...); this device should + * have a refcount. + * + * @trust_timeout After this time without hearing for device + * activity, we consider the device gone and we have to + * re-authenticate. + * + * Can be accessed w/o locking--however, read to a + * local variable then use. + * + * @chid WUSB Cluster Host ID: this is supposed to be a + * unique value that doesn't change across reboots (so + * that your devices do not require re-association). + * + * Read/Write protected by @mutex + * + * @dev_info This array has ports_max elements. It is used to + * give the HC information about the WUSB devices (see + * 'struct wusb_dev_info'). + * + * For HWA we need to allocate it in heap; for WHCI it + * needs to be permanently mapped, so we keep it for + * both and make it easy. Call wusbhc->dev_info_set() + * to update an entry. + * + * @ports_max Number of simultaneous device connections (fake + * ports) this HC will take. Read-only. + * + * @port Array of port status for each fake root port. Guaranteed to + * always be the same lenght during device existence + * [this allows for some unlocked but referenced reading]. + * + * @mmcies_max Max number of Information Elements this HC can send + * in its MMC. Read-only. + * + * @mmcie_add HC specific operation (WHCI or HWA) for adding an + * MMCIE. + * + * @mmcie_rm HC specific operation (WHCI or HWA) for removing an + * MMCIE. + * + * @enc_types Array which describes the encryptions methods + * supported by the host as described in WUSB1.0 -- + * one entry per supported method. As of WUSB1.0 there + * is only four methods, we make space for eight just in + * case they decide to add some more (and pray they do + * it in sequential order). if 'enc_types[enc_method] + * != 0', then it is supported by the host. enc_method + * is USB_ENC_TYPE*. + * + * @set_ptk: Set the PTK and enable encryption for a device. Or, if + * the supplied key is NULL, disable encryption for that + * device. + * + * @set_gtk: Set the GTK to be used for all future broadcast packets + * (i.e., MMCs). With some hardware, setting the GTK may start + * MMC transmission. + * + * NOTE: + * + * - If wusb_dev->usb_dev is not NULL, then usb_dev is valid + * (wusb_dev has a refcount on it). Likewise, if usb_dev->wusb_dev + * is not NULL, usb_dev->wusb_dev is valid (usb_dev keeps a + * refcount on it). + * + * Most of the times when you need to use it, it will be non-NULL, + * so there is no real need to check for it (wusb_dev will + * dissapear before usb_dev). + * + * - The following fields need to be filled out before calling + * wusbhc_create(): ports_max, mmcies_max, mmcie_{add,rm}. + * + * - there is no wusbhc_init() method, we do everything in + * wusbhc_create(). + * + * - Creation is done in two phases, wusbhc_create() and + * wusbhc_create_b(); b are the parts that need to be called after + * calling usb_hcd_add(&wusbhc->usb_hcd). + */ +struct wusbhc { + struct usb_hcd usb_hcd; /* HAS TO BE 1st */ + struct device *dev; + struct uwb_rc *uwb_rc; + struct uwb_pal pal; + + unsigned trust_timeout; /* in jiffies */ + struct wuie_host_info *wuie_host_info; /* Includes CHID */ + + struct mutex mutex; /* locks everything else */ + u16 cluster_id; /* Wireless USB Cluster ID */ + struct wusb_port *port; /* Fake port status handling */ + struct wusb_dev_info *dev_info; /* for Set Device Info mgmt */ + u8 ports_max; + unsigned active:1; /* currently xmit'ing MMCs */ + struct wuie_keep_alive keep_alive_ie; /* protected by mutex */ + struct delayed_work keep_alive_timer; + struct list_head cack_list; /* Connect acknowledging */ + size_t cack_count; /* protected by 'mutex' */ + struct wuie_connect_ack cack_ie; + struct uwb_rsv *rsv; /* cluster bandwidth reservation */ + + struct mutex mmcie_mutex; /* MMC WUIE handling */ + struct wuie_hdr **mmcie; /* WUIE array */ + u8 mmcies_max; + /* FIXME: make wusbhc_ops? */ + int (*start)(struct wusbhc *wusbhc); + void (*stop)(struct wusbhc *wusbhc); + int (*mmcie_add)(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); + int (*mmcie_rm)(struct wusbhc *wusbhc, u8 handle); + int (*dev_info_set)(struct wusbhc *, struct wusb_dev *wusb_dev); + int (*bwa_set)(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *); + int (*set_ptk)(struct wusbhc *wusbhc, u8 port_idx, + u32 tkid, const void *key, size_t key_size); + int (*set_gtk)(struct wusbhc *wusbhc, + u32 tkid, const void *key, size_t key_size); + int (*set_num_dnts)(struct wusbhc *wusbhc, u8 interval, u8 slots); + + struct { + struct usb_key_descriptor descr; + u8 data[16]; /* GTK key data */ + } __attribute__((packed)) gtk; + u8 gtk_index; + u32 gtk_tkid; + struct work_struct gtk_rekey_done_work; + int pending_set_gtks; + + struct usb_encryption_descriptor *ccm1_etd; +}; + +#define usb_hcd_to_wusbhc(u) container_of((u), struct wusbhc, usb_hcd) + + +extern int wusbhc_create(struct wusbhc *); +extern int wusbhc_b_create(struct wusbhc *); +extern void wusbhc_b_destroy(struct wusbhc *); +extern void wusbhc_destroy(struct wusbhc *); +extern int wusb_dev_sysfs_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sysfs_rm(struct wusb_dev *); +extern int wusbhc_sec_create(struct wusbhc *); +extern int wusbhc_sec_start(struct wusbhc *); +extern void wusbhc_sec_stop(struct wusbhc *); +extern void wusbhc_sec_destroy(struct wusbhc *); +extern void wusbhc_giveback_urb(struct wusbhc *wusbhc, struct urb *urb, + int status); +void wusbhc_reset_all(struct wusbhc *wusbhc); + +int wusbhc_pal_register(struct wusbhc *wusbhc); +void wusbhc_pal_unregister(struct wusbhc *wusbhc); + +/* + * Return @usb_dev's @usb_hcd (properly referenced) or NULL if gone + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * This is a safe assumption as @usb_dev->bus is referenced all the + * time during the @usb_dev life cycle. + */ +static inline struct usb_hcd *usb_hcd_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct usb_hcd *usb_hcd; + usb_hcd = container_of(usb_dev->bus, struct usb_hcd, self); + return usb_get_hcd(usb_hcd); +} + +/* + * Increment the reference count on a wusbhc. + * + * @wusbhc's life cycle is identical to that of the underlying usb_hcd. + */ +static inline struct wusbhc *wusbhc_get(struct wusbhc *wusbhc) +{ + return usb_get_hcd(&wusbhc->usb_hcd) ? wusbhc : NULL; +} + +/* + * Return the wusbhc associated to a @usb_dev + * + * @usb_dev: USB device, UNLOCKED and referenced (or otherwise, safe ptr) + * + * @returns: wusbhc for @usb_dev; NULL if the @usb_dev is being torn down. + * WARNING: referenced at the usb_hcd level, unlocked + * + * FIXME: move offline + */ +static inline struct wusbhc *wusbhc_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc = NULL; + struct usb_hcd *usb_hcd; + if (usb_dev->devnum > 1 && !usb_dev->wusb) { + /* but root hubs */ + dev_err(&usb_dev->dev, "devnum %d wusb %d\n", usb_dev->devnum, + usb_dev->wusb); + BUG_ON(usb_dev->devnum > 1 && !usb_dev->wusb); + } + usb_hcd = usb_hcd_get_by_usb_dev(usb_dev); + if (usb_hcd == NULL) + return NULL; + BUG_ON(usb_hcd->wireless == 0); + return wusbhc = usb_hcd_to_wusbhc(usb_hcd); +} + + +static inline void wusbhc_put(struct wusbhc *wusbhc) +{ + usb_put_hcd(&wusbhc->usb_hcd); +} + +int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid); +void wusbhc_stop(struct wusbhc *wusbhc); +extern int wusbhc_chid_set(struct wusbhc *, const struct wusb_ckhdid *); + +/* Device connect handling */ +extern int wusbhc_devconnect_create(struct wusbhc *); +extern void wusbhc_devconnect_destroy(struct wusbhc *); +extern int wusbhc_devconnect_start(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid); +extern void wusbhc_devconnect_stop(struct wusbhc *wusbhc); +extern int wusbhc_devconnect_auth(struct wusbhc *, u8); +extern void wusbhc_handle_dn(struct wusbhc *, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size); +extern int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port); +extern void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port); +extern int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv); +extern int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + u8 addr); + +/* Wireless USB fake Root Hub methods */ +extern int wusbhc_rh_create(struct wusbhc *); +extern void wusbhc_rh_destroy(struct wusbhc *); + +extern int wusbhc_rh_status_data(struct usb_hcd *, char *); +extern int wusbhc_rh_control(struct usb_hcd *, u16, u16, u16, char *, u16); +extern int wusbhc_rh_suspend(struct usb_hcd *); +extern int wusbhc_rh_resume(struct usb_hcd *); +extern int wusbhc_rh_start_port_reset(struct usb_hcd *, unsigned); + +/* MMC handling */ +extern int wusbhc_mmcie_create(struct wusbhc *); +extern void wusbhc_mmcie_destroy(struct wusbhc *); +extern int wusbhc_mmcie_set(struct wusbhc *, u8 interval, u8 repeat_cnt, + struct wuie_hdr *); +extern void wusbhc_mmcie_rm(struct wusbhc *, struct wuie_hdr *); + +/* Bandwidth reservation */ +int wusbhc_rsv_establish(struct wusbhc *wusbhc); +void wusbhc_rsv_terminate(struct wusbhc *wusbhc); + +/* + * I've always said + * I wanted a wedding in a church... + * + * but lately I've been thinking about + * the Botanical Gardens. + * + * We could do it by the tulips. + * It'll be beautiful + * + * --Security! + */ +extern int wusb_dev_sec_add(struct wusbhc *, struct usb_device *, + struct wusb_dev *); +extern void wusb_dev_sec_rm(struct wusb_dev *) ; +extern int wusb_dev_4way_handshake(struct wusbhc *, struct wusb_dev *, + struct wusb_ckhdid *ck); +void wusbhc_gtk_rekey(struct wusbhc *wusbhc); + + +/* WUSB Cluster ID handling */ +extern u8 wusb_cluster_id_get(void); +extern void wusb_cluster_id_put(u8); + +/* + * wusb_port_by_idx - return the port associated to a zero-based port index + * + * NOTE: valid without locking as long as wusbhc is referenced (as the + * number of ports doesn't change). The data pointed to has to + * be verified though :) + */ +static inline struct wusb_port *wusb_port_by_idx(struct wusbhc *wusbhc, + u8 port_idx) +{ + return &wusbhc->port[port_idx]; +} + +/* + * wusb_port_no_to_idx - Convert port number (per usb_dev->portnum) to + * a port_idx. + * + * USB stack USB ports are 1 based!! + * + * NOTE: only valid for WUSB devices!!! + */ +static inline u8 wusb_port_no_to_idx(u8 port_no) +{ + return port_no - 1; +} + +extern struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *, + struct usb_device *); + +/* + * Return a referenced wusb_dev given a @usb_dev + * + * Returns NULL if the usb_dev is being torn down. + * + * FIXME: move offline + */ +static inline +struct wusb_dev *wusb_dev_get_by_usb_dev(struct usb_device *usb_dev) +{ + struct wusbhc *wusbhc; + struct wusb_dev *wusb_dev; + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + return NULL; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + mutex_unlock(&wusbhc->mutex); + wusbhc_put(wusbhc); + return wusb_dev; +} + +/* Misc */ + +extern struct workqueue_struct *wusbd; +#endif /* #ifndef __WUSBHC_H__ */ -- cgit v1.2.3 From b69fada68b92fa7061d59a3e54b428759a5e5717 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:24 +0100 Subject: wusb: add the Wireless USB core (protocol) Add the WUSB protocol (MMC management and device connection) code. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/devconnect.c | 1314 +++++++++++++++++++++++++++++++++++++ drivers/usb/wusbcore/mmc.c | 329 ++++++++++ 2 files changed, 1643 insertions(+) create mode 100644 drivers/usb/wusbcore/devconnect.c create mode 100644 drivers/usb/wusbcore/mmc.c diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c new file mode 100644 index 00000000000..f05f9b4d775 --- /dev/null +++ b/drivers/usb/wusbcore/devconnect.c @@ -0,0 +1,1314 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * Device Connect handling + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * FIXME: this file needs to be broken up, it's grown too big + * + * + * WUSB1.0[7.1, 7.5.1, ] + * + * WUSB device connection is kind of messy. Some background: + * + * When a device wants to connect it scans the UWB radio channels + * looking for a WUSB Channel; a WUSB channel is defined by MMCs + * (Micro Managed Commands or something like that) [see + * Design-overview for more on this] . + * + * So, device scans the radio, finds MMCs and thus a host and checks + * when the next DNTS is. It sends a Device Notification Connect + * (DN_Connect); the host picks it up (through nep.c and notif.c, ends + * up in wusb_devconnect_ack(), which creates a wusb_dev structure in + * wusbhc->port[port_number].wusb_dev), assigns an unauth address + * to the device (this means from 0x80 to 0xfe) and sends, in the MMC + * a Connect Ack Information Element (ConnAck IE). + * + * So now the device now has a WUSB address. From now on, we use + * that to talk to it in the RPipes. + * + * ASSUMPTIONS: + * + * - We use the the as device address the port number where it is + * connected (port 0 doesn't exist). For unauth, it is 128 + that. + * + * ROADMAP: + * + * This file contains the logic for doing that--entry points: + * + * wusb_devconnect_ack() Ack a device until _acked() called. + * Called by notif.c:wusb_handle_dn_connect() + * when a DN_Connect is received. + * + * wusbhc_devconnect_auth() Called by rh.c:wusbhc_rh_port_reset() when + * doing the device connect sequence. + * + * wusb_devconnect_acked() Ack done, release resources. + * + * wusb_handle_dn_alive() Called by notif.c:wusb_handle_dn() + * for processing a DN_Alive pong from a device. + * + * wusb_handle_dn_disconnect()Called by notif.c:wusb_handle_dn() to + * process a disconenct request from a + * device. + * + * wusb_dev_reset() Called by rh.c:wusbhc_rh_port_reset() when + * resetting a device. + * + * __wusb_dev_disable() Called by rh.c:wusbhc_rh_clear_port_feat() when + * disabling a port. + * + * wusb_devconnect_create() Called when creating the host by + * lc.c:wusbhc_create(). + * + * wusb_devconnect_destroy() Cleanup called removing the host. Called + * by lc.c:wusbhc_destroy(). + * + * Each Wireless USB host maintains a list of DN_Connect requests + * (actually we maintain a list of pending Connect Acks, the + * wusbhc->ca_list). + * + * LIFE CYCLE OF port->wusb_dev + * + * Before the @wusbhc structure put()s the reference it owns for + * port->wusb_dev [and clean the wusb_dev pointer], it needs to + * lock @wusbhc->mutex. + */ + +#include +#include +#include +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 1 +#include + +static void wusbhc_devconnect_acked_work(struct work_struct *work); + +static void wusb_dev_free(struct wusb_dev *wusb_dev) +{ + if (wusb_dev) { + kfree(wusb_dev->set_gtk_req); + usb_free_urb(wusb_dev->set_gtk_urb); + kfree(wusb_dev); + } +} + +static struct wusb_dev *wusb_dev_alloc(struct wusbhc *wusbhc) +{ + struct wusb_dev *wusb_dev; + struct urb *urb; + struct usb_ctrlrequest *req; + + wusb_dev = kzalloc(sizeof(*wusb_dev), GFP_KERNEL); + if (wusb_dev == NULL) + goto err; + + wusb_dev->wusbhc = wusbhc; + + INIT_WORK(&wusb_dev->devconnect_acked_work, wusbhc_devconnect_acked_work); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (urb == NULL) + goto err; + + req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (req == NULL) + goto err; + + req->bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req->bRequest = USB_REQ_SET_DESCRIPTOR; + req->wValue = cpu_to_le16(USB_DT_KEY << 8 | wusbhc->gtk_index); + req->wIndex = 0; + req->wLength = cpu_to_le16(wusbhc->gtk.descr.bLength); + + wusb_dev->set_gtk_urb = urb; + wusb_dev->set_gtk_req = req; + + return wusb_dev; +err: + wusb_dev_free(wusb_dev); + return NULL; +} + + +/* + * Using the Connect-Ack list, fill out the @wusbhc Connect-Ack WUSB IE + * properly so that it can be added to the MMC. + * + * We just get the @wusbhc->ca_list and fill out the first four ones or + * less (per-spec WUSB1.0[7.5, before T7-38). If the ConnectAck WUSB + * IE is not allocated, we alloc it. + * + * @wusbhc->mutex must be taken + */ +static void wusbhc_fill_cack_ie(struct wusbhc *wusbhc) +{ + unsigned cnt; + struct wusb_dev *dev_itr; + struct wuie_connect_ack *cack_ie; + + cack_ie = &wusbhc->cack_ie; + cnt = 0; + list_for_each_entry(dev_itr, &wusbhc->cack_list, cack_node) { + cack_ie->blk[cnt].CDID = dev_itr->cdid; + cack_ie->blk[cnt].bDeviceAddress = dev_itr->addr; + if (++cnt >= WUIE_ELT_MAX) + break; + } + cack_ie->hdr.bLength = sizeof(cack_ie->hdr) + + cnt * sizeof(cack_ie->blk[0]); +} + +/* + * Register a new device that wants to connect + * + * A new device wants to connect, so we add it to the Connect-Ack + * list. We give it an address in the unauthorized range (bit 8 set); + * user space will have to drive authorization further on. + * + * @dev_addr: address to use for the device (which is also the port + * number). + * + * @wusbhc->mutex must be taken + */ +static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc, + struct wusb_dn_connect *dnc, + const char *pr_cdid, u8 port_idx) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + int new_connection = wusb_dn_connect_new_connection(dnc); + u8 dev_addr; + int result; + + d_fnstart(3, dev, "(wusbhc %p port_idx %d)\n", wusbhc, port_idx); + /* Is it registered already? */ + list_for_each_entry(wusb_dev, &wusbhc->cack_list, cack_node) + if (!memcmp(&wusb_dev->cdid, &dnc->CDID, + sizeof(wusb_dev->cdid))) + return wusb_dev; + /* We don't have it, create an entry, register it */ + wusb_dev = wusb_dev_alloc(wusbhc); + if (wusb_dev == NULL) { + if (printk_ratelimit()) + dev_err(dev, "DN CONNECT: no memory to process %s's %s " + "request\n", pr_cdid, + new_connection ? "connect" : "reconnect"); + return NULL; + } + wusb_dev_init(wusb_dev); + wusb_dev->cdid = dnc->CDID; + wusb_dev->port_idx = port_idx; + + /* + * Devices are always available within the cluster reservation + * and since the hardware will take the intersection of the + * per-device availability and the cluster reservation, the + * per-device availability can simply be set to always + * available. + */ + bitmap_fill(wusb_dev->availability.bm, UWB_NUM_MAS); + + /* FIXME: handle reconnects instead of assuming connects are + always new. */ + if (1 && new_connection == 0) + new_connection = 1; + if (new_connection) { + dev_addr = (port_idx + 2) | WUSB_DEV_ADDR_UNAUTH; + + dev_info(dev, "Connecting new WUSB device to address %u, " + "port %u\n", dev_addr, port_idx); + + result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr); + if (result) + return NULL; + } + wusb_dev->entry_ts = jiffies; + list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list); + wusbhc->cack_count++; + wusbhc_fill_cack_ie(wusbhc); + d_fnend(3, dev, "(wusbhc %p port_idx %d)\n", wusbhc, port_idx); + return wusb_dev; +} + +/* + * Remove a Connect-Ack context entry from the HCs view + * + * @wusbhc->mutex must be taken + */ +static void wusbhc_cack_rm(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + d_fnstart(3, dev, "(wusbhc %p wusb_dev %p)\n", wusbhc, wusb_dev); + list_del_init(&wusb_dev->cack_node); + wusbhc->cack_count--; + wusbhc_fill_cack_ie(wusbhc); + d_fnend(3, dev, "(wusbhc %p wusb_dev %p) = void\n", wusbhc, wusb_dev); +} + +/* + * @wusbhc->mutex must be taken */ +static +void wusbhc_devconnect_acked(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + d_fnstart(3, dev, "(wusbhc %p wusb_dev %p)\n", wusbhc, wusb_dev); + wusbhc_cack_rm(wusbhc, wusb_dev); + if (wusbhc->cack_count) + wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr); + else + wusbhc_mmcie_rm(wusbhc, &wusbhc->cack_ie.hdr); + d_fnend(3, dev, "(wusbhc %p wusb_dev %p) = void\n", wusbhc, wusb_dev); +} + +static void wusbhc_devconnect_acked_work(struct work_struct *work) +{ + struct wusb_dev *wusb_dev = container_of(work, struct wusb_dev, + devconnect_acked_work); + struct wusbhc *wusbhc = wusb_dev->wusbhc; + + mutex_lock(&wusbhc->mutex); + wusbhc_devconnect_acked(wusbhc, wusb_dev); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Ack a device for connection + * + * FIXME: docs + * + * @pr_cdid: Printable CDID...hex Use @dnc->cdid for the real deal. + * + * So we get the connect ack IE (may have been allocated already), + * find an empty connect block, an empty virtual port, create an + * address with it (see below), make it an unauth addr [bit 7 set] and + * set the MMC. + * + * Addresses: because WUSB hosts have no downstream hubs, we can do a + * 1:1 mapping between 'port number' and device + * address. This simplifies many things, as during this + * initial connect phase the USB stack has no knoledge of + * the device and hasn't assigned an address yet--we know + * USB's choose_address() will use the same euristics we + * use here, so we can assume which address will be assigned. + * + * USB stack always assigns address 1 to the root hub, so + * to the port number we add 2 (thus virtual port #0 is + * addr #2). + * + * @wusbhc shall be referenced + */ +static +void wusbhc_devconnect_ack(struct wusbhc *wusbhc, struct wusb_dn_connect *dnc, + const char *pr_cdid) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wusb_port *port; + unsigned idx, devnum; + + d_fnstart(3, dev, "(%p, %p, %s)\n", wusbhc, dnc, pr_cdid); + mutex_lock(&wusbhc->mutex); + + /* Check we are not handling it already */ + for (idx = 0; idx < wusbhc->ports_max; idx++) { + port = wusb_port_by_idx(wusbhc, idx); + if (port->wusb_dev + && !memcmp(&dnc->CDID, &port->wusb_dev->cdid, + sizeof(dnc->CDID))) { + if (printk_ratelimit()) + dev_err(dev, "Already handling dev %s " + " (it might be slow)\n", pr_cdid); + goto error_unlock; + } + } + /* Look up those fake ports we have for a free one */ + for (idx = 0; idx < wusbhc->ports_max; idx++) { + port = wusb_port_by_idx(wusbhc, idx); + if ((port->status & USB_PORT_STAT_POWER) + && !(port->status & USB_PORT_STAT_CONNECTION)) + break; + } + if (idx >= wusbhc->ports_max) { + dev_err(dev, "Host controller can't connect more devices " + "(%u already connected); device %s rejected\n", + wusbhc->ports_max, pr_cdid); + /* NOTE: we could send a WUIE_Disconnect here, but we haven't + * event acked, so the device will eventually timeout the + * connection, right? */ + goto error_unlock; + } + + devnum = idx + 2; + + /* Make sure we are using no crypto on that "virtual port" */ + wusbhc->set_ptk(wusbhc, idx, 0, NULL, 0); + + /* Grab a filled in Connect-Ack context, fill out the + * Connect-Ack Wireless USB IE, set the MMC */ + wusb_dev = wusbhc_cack_add(wusbhc, dnc, pr_cdid, idx); + if (wusb_dev == NULL) + goto error_unlock; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->cack_ie.hdr); + if (result < 0) + goto error_unlock; + /* Give the device at least 2ms (WUSB1.0[7.5.1p3]), let's do + * three for a good measure */ + msleep(3); + port->wusb_dev = wusb_dev; + port->status |= USB_PORT_STAT_CONNECTION; + port->change |= USB_PORT_STAT_C_CONNECTION; + port->reset_count = 0; + /* Now the port status changed to connected; khubd will + * pick the change up and try to reset the port to bring it to + * the enabled state--so this process returns up to the stack + * and it calls back into wusbhc_rh_port_reset() who will call + * devconnect_auth(). + */ +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(3, dev, "(%p, %p, %s) = void\n", wusbhc, dnc, pr_cdid); + return; + +} + +/* + * Disconnect a Wireless USB device from its fake port + * + * Marks the port as disconnected so that khubd can pick up the change + * and drops our knowledge about the device. + * + * Assumes there is a device connected + * + * @port_index: zero based port number + * + * NOTE: @wusbhc->mutex is locked + * + * WARNING: From here it is not very safe to access anything hanging off + * wusb_dev + */ +static void __wusbhc_dev_disconnect(struct wusbhc *wusbhc, + struct wusb_port *port) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev = port->wusb_dev; + + d_fnstart(3, dev, "(wusbhc %p, port %p)\n", wusbhc, port); + port->status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE + | USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESET + | USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED); + port->change |= USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE; + if (wusb_dev) { + if (!list_empty(&wusb_dev->cack_node)) + list_del_init(&wusb_dev->cack_node); + /* For the one in cack_add() */ + wusb_dev_put(wusb_dev); + } + port->wusb_dev = NULL; + /* don't reset the reset_count to zero or wusbhc_rh_port_reset will get + * confused! We only reset to zero when we connect a new device. + */ + + /* After a device disconnects, change the GTK (see [WUSB] + * section 6.2.11.2). */ + wusbhc_gtk_rekey(wusbhc); + + d_fnend(3, dev, "(wusbhc %p, port %p) = void\n", wusbhc, port); + /* The Wireless USB part has forgotten about the device already; now + * khubd's timer will pick up the disconnection and remove the USB + * device from the system + */ +} + +/* + * Authenticate a device into the WUSB Cluster + * + * Called from the Root Hub code (rh.c:wusbhc_rh_port_reset()) when + * asking for a reset on a port that is not enabled (ie: first connect + * on the port). + * + * Performs the 4way handshake to allow the device to comunicate w/ the + * WUSB Cluster securely; once done, issue a request to the device for + * it to change to address 0. + * + * This mimics the reset step of Wired USB that once resetting a + * device, leaves the port in enabled state and the dev with the + * default address (0). + * + * WUSB1.0[7.1.2] + * + * @port_idx: port where the change happened--This is the index into + * the wusbhc port array, not the USB port number. + */ +int wusbhc_devconnect_auth(struct wusbhc *wusbhc, u8 port_idx) +{ + struct device *dev = wusbhc->dev; + struct wusb_port *port = wusb_port_by_idx(wusbhc, port_idx); + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + port->status &= ~USB_PORT_STAT_RESET; + port->status |= USB_PORT_STAT_ENABLE; + port->change |= USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_ENABLE; + d_fnend(3, dev, "(%p, %u) = 0\n", wusbhc, port_idx); + return 0; +} + +/* + * Refresh the list of keep alives to emit in the MMC + * + * Some devices don't respond to keep alives unless they've been + * authenticated, so skip unauthenticated devices. + * + * We only publish the first four devices that have a coming timeout + * condition. Then when we are done processing those, we go for the + * next ones. We ignore the ones that have timed out already (they'll + * be purged). + * + * This might cause the first devices to timeout the last devices in + * the port array...FIXME: come up with a better algorithm? + * + * Note we can't do much about MMC's ops errors; we hope next refresh + * will kind of handle it. + * + * NOTE: @wusbhc->mutex is locked + */ +static void __wusbhc_keep_alive(struct wusbhc *wusbhc) +{ + int result; + struct device *dev = wusbhc->dev; + unsigned cnt; + struct wusb_dev *wusb_dev; + struct wusb_port *wusb_port; + struct wuie_keep_alive *ie = &wusbhc->keep_alive_ie; + unsigned keep_alives, old_keep_alives; + + d_fnstart(5, dev, "(wusbhc %p)\n", wusbhc); + old_keep_alives = ie->hdr.bLength - sizeof(ie->hdr); + keep_alives = 0; + for (cnt = 0; + keep_alives <= WUIE_ELT_MAX && cnt < wusbhc->ports_max; + cnt++) { + unsigned tt = msecs_to_jiffies(wusbhc->trust_timeout); + + wusb_port = wusb_port_by_idx(wusbhc, cnt); + wusb_dev = wusb_port->wusb_dev; + + if (wusb_dev == NULL) + continue; + if (wusb_dev->usb_dev == NULL || !wusb_dev->usb_dev->authenticated) + continue; + + if (time_after(jiffies, wusb_dev->entry_ts + tt)) { + dev_err(dev, "KEEPALIVE: device %u timed out\n", + wusb_dev->addr); + __wusbhc_dev_disconnect(wusbhc, wusb_port); + } else if (time_after(jiffies, wusb_dev->entry_ts + tt/2)) { + /* Approaching timeout cut out, need to refresh */ + ie->bDeviceAddress[keep_alives++] = wusb_dev->addr; + } + } + if (keep_alives & 0x1) /* pad to even number ([WUSB] section 7.5.9) */ + ie->bDeviceAddress[keep_alives++] = 0x7f; + ie->hdr.bLength = sizeof(ie->hdr) + + keep_alives*sizeof(ie->bDeviceAddress[0]); + if (keep_alives > 0) { + result = wusbhc_mmcie_set(wusbhc, 10, 5, &ie->hdr); + if (result < 0 && printk_ratelimit()) + dev_err(dev, "KEEPALIVE: can't set MMC: %d\n", result); + } else if (old_keep_alives != 0) + wusbhc_mmcie_rm(wusbhc, &ie->hdr); + d_fnend(5, dev, "(wusbhc %p) = void\n", wusbhc); +} + +/* + * Do a run through all devices checking for timeouts + */ +static void wusbhc_keep_alive_run(struct work_struct *ws) +{ + struct delayed_work *dw = + container_of(ws, struct delayed_work, work); + struct wusbhc *wusbhc = + container_of(dw, struct wusbhc, keep_alive_timer); + + d_fnstart(5, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + if (wusbhc->active) { + mutex_lock(&wusbhc->mutex); + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (wusbhc->trust_timeout * CONFIG_HZ)/1000/2); + } + d_fnend(5, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); + return; +} + +/* + * Find the wusb_dev from its device address. + * + * The device can be found directly from the address (see + * wusb_cack_add() for where the device address is set to port_idx + * +2), except when the address is zero. + */ +static struct wusb_dev *wusbhc_find_dev_by_addr(struct wusbhc *wusbhc, u8 addr) +{ + int p; + + if (addr == 0xff) /* unconnected */ + return NULL; + + if (addr > 0) { + int port = (addr & ~0x80) - 2; + if (port < 0 || port >= wusbhc->ports_max) + return NULL; + return wusb_port_by_idx(wusbhc, port)->wusb_dev; + } + + /* Look for the device with address 0. */ + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev = wusb_port_by_idx(wusbhc, p)->wusb_dev; + if (wusb_dev && wusb_dev->addr == addr) + return wusb_dev; + } + return NULL; +} + +/* + * Handle a DN_Alive notification (WUSB1.0[7.6.1]) + * + * This just updates the device activity timestamp and then refreshes + * the keep alive IE. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_alive(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + + d_printf(2, dev, "DN ALIVE: device 0x%02x pong\n", wusb_dev->addr); + + mutex_lock(&wusbhc->mutex); + wusb_dev->entry_ts = jiffies; + __wusbhc_keep_alive(wusbhc); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Handle a DN_Connect notification (WUSB1.0[7.6.1]) + * + * @wusbhc + * @pkt_hdr + * @size: Size of the buffer where the notification resides; if the + * notification data suggests there should be more data than + * available, an error will be signaled and the whole buffer + * consumed. + * + * @wusbhc->mutex shall be held + */ +static void wusbhc_handle_dn_connect(struct wusbhc *wusbhc, + struct wusb_dn_hdr *dn_hdr, + size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dn_connect *dnc; + char pr_cdid[WUSB_CKHDID_STRSIZE]; + static const char *beacon_behaviour[] = { + "reserved", + "self-beacon", + "directed-beacon", + "no-beacon" + }; + + d_fnstart(3, dev, "(%p, %p, %zu)\n", wusbhc, dn_hdr, size); + if (size < sizeof(*dnc)) { + dev_err(dev, "DN CONNECT: short notification (%zu < %zu)\n", + size, sizeof(*dnc)); + goto out; + } + + dnc = container_of(dn_hdr, struct wusb_dn_connect, hdr); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &dnc->CDID); + dev_info(dev, "DN CONNECT: device %s @ %x (%s) wants to %s\n", + pr_cdid, + wusb_dn_connect_prev_dev_addr(dnc), + beacon_behaviour[wusb_dn_connect_beacon_behavior(dnc)], + wusb_dn_connect_new_connection(dnc) ? "connect" : "reconnect"); + /* ACK the connect */ + wusbhc_devconnect_ack(wusbhc, dnc, pr_cdid); +out: + d_fnend(3, dev, "(%p, %p, %zu) = void\n", + wusbhc, dn_hdr, size); + return; +} + +/* + * Handle a DN_Disconnect notification (WUSB1.0[7.6.1]) + * + * Device is going down -- do the disconnect. + * + * @wusbhc shall be referenced and unlocked + */ +static void wusbhc_handle_dn_disconnect(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct device *dev = wusbhc->dev; + + dev_info(dev, "DN DISCONNECT: device 0x%02x going down\n", wusb_dev->addr); + + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, wusb_dev->port_idx)); + mutex_unlock(&wusbhc->mutex); +} + +/* + * Reset a WUSB device on a HWA + * + * @wusbhc + * @port_idx Index of the port where the device is + * + * In Wireless USB, a reset is more or less equivalent to a full + * disconnect; so we just do a full disconnect and send the device a + * Device Reset IE (WUSB1.0[7.5.11]) giving it a few millisecs (6 MMCs). + * + * @wusbhc should be refcounted and unlocked + */ +int wusbhc_dev_reset(struct wusbhc *wusbhc, u8 port_idx) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wuie_reset *ie; + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + mutex_lock(&wusbhc->mutex); + result = 0; + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev == NULL) { + /* reset no device? ignore */ + dev_dbg(dev, "RESET: no device at port %u, ignoring\n", + port_idx); + goto error_unlock; + } + result = -ENOMEM; + ie = kzalloc(sizeof(*ie), GFP_KERNEL); + if (ie == NULL) + goto error_unlock; + ie->hdr.bLength = sizeof(ie->hdr) + sizeof(ie->CDID); + ie->hdr.bIEIdentifier = WUIE_ID_RESET_DEVICE; + ie->CDID = wusb_dev->cdid; + result = wusbhc_mmcie_set(wusbhc, 0xff, 6, &ie->hdr); + if (result < 0) { + dev_err(dev, "RESET: cant's set MMC: %d\n", result); + goto error_kfree; + } + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + + /* 120ms, hopefully 6 MMCs (FIXME) */ + msleep(120); + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +error_kfree: + kfree(ie); +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result); + return result; +} + +/* + * Handle a Device Notification coming a host + * + * The Device Notification comes from a host (HWA, DWA or WHCI) + * wrapped in a set of headers. Somebody else has peeled off those + * headers for us and we just get one Device Notifications. + * + * Invalid DNs (e.g., too short) are discarded. + * + * @wusbhc shall be referenced + * + * FIXMES: + * - implement priorities as in WUSB1.0[Table 7-55]? + */ +void wusbhc_handle_dn(struct wusbhc *wusbhc, u8 srcaddr, + struct wusb_dn_hdr *dn_hdr, size_t size) +{ + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + + d_fnstart(3, dev, "(%p, %p)\n", wusbhc, dn_hdr); + + if (size < sizeof(struct wusb_dn_hdr)) { + dev_err(dev, "DN data shorter than DN header (%d < %d)\n", + (int)size, (int)sizeof(struct wusb_dn_hdr)); + goto out; + } + + wusb_dev = wusbhc_find_dev_by_addr(wusbhc, srcaddr); + if (wusb_dev == NULL && dn_hdr->bType != WUSB_DN_CONNECT) { + dev_dbg(dev, "ignoring DN %d from unconnected device %02x\n", + dn_hdr->bType, srcaddr); + goto out; + } + + switch (dn_hdr->bType) { + case WUSB_DN_CONNECT: + wusbhc_handle_dn_connect(wusbhc, dn_hdr, size); + break; + case WUSB_DN_ALIVE: + wusbhc_handle_dn_alive(wusbhc, wusb_dev); + break; + case WUSB_DN_DISCONNECT: + wusbhc_handle_dn_disconnect(wusbhc, wusb_dev); + break; + case WUSB_DN_MASAVAILCHANGED: + case WUSB_DN_RWAKE: + case WUSB_DN_SLEEP: + /* FIXME: handle these DNs. */ + break; + case WUSB_DN_EPRDY: + /* The hardware handles these. */ + break; + default: + dev_warn(dev, "unknown DN %u (%d octets) from %u\n", + dn_hdr->bType, (int)size, srcaddr); + } +out: + d_fnend(3, dev, "(%p, %p) = void\n", wusbhc, dn_hdr); + return; +} +EXPORT_SYMBOL_GPL(wusbhc_handle_dn); + +/* + * Disconnect a WUSB device from a the cluster + * + * @wusbhc + * @port Fake port where the device is (wusbhc index, not USB port number). + * + * In Wireless USB, a disconnect is basically telling the device he is + * being disconnected and forgetting about him. + * + * We send the device a Device Disconnect IE (WUSB1.0[7.5.11]) for 100 + * ms and then keep going. + * + * We don't do much in case of error; we always pretend we disabled + * the port and disconnected the device. If physically the request + * didn't get there (many things can fail in the way there), the stack + * will reject the device's communication attempts. + * + * @wusbhc should be refcounted and locked + */ +void __wusbhc_dev_disable(struct wusbhc *wusbhc, u8 port_idx) +{ + int result; + struct device *dev = wusbhc->dev; + struct wusb_dev *wusb_dev; + struct wuie_disconnect *ie; + + d_fnstart(3, dev, "(%p, %u)\n", wusbhc, port_idx); + result = 0; + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev == NULL) { + /* reset no device? ignore */ + dev_dbg(dev, "DISCONNECT: no device at port %u, ignoring\n", + port_idx); + goto error; + } + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + + result = -ENOMEM; + ie = kzalloc(sizeof(*ie), GFP_KERNEL); + if (ie == NULL) + goto error; + ie->hdr.bLength = sizeof(*ie); + ie->hdr.bIEIdentifier = WUIE_ID_DEVICE_DISCONNECT; + ie->bDeviceAddress = wusb_dev->addr; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &ie->hdr); + if (result < 0) { + dev_err(dev, "DISCONNECT: can't set MMC: %d\n", result); + goto error_kfree; + } + + /* 120ms, hopefully 6 MMCs */ + msleep(100); + wusbhc_mmcie_rm(wusbhc, &ie->hdr); +error_kfree: + kfree(ie); +error: + d_fnend(3, dev, "(%p, %u) = %d\n", wusbhc, port_idx, result); + return; +} + +static void wusb_cap_descr_printf(const unsigned level, struct device *dev, + const struct usb_wireless_cap_descriptor *wcd) +{ + d_printf(level, dev, + "WUSB Capability Descriptor\n" + " bDevCapabilityType 0x%02x\n" + " bmAttributes 0x%02x\n" + " wPhyRates 0x%04x\n" + " bmTFITXPowerInfo 0x%02x\n" + " bmFFITXPowerInfo 0x%02x\n" + " bmBandGroup 0x%04x\n" + " bReserved 0x%02x\n", + wcd->bDevCapabilityType, + wcd->bmAttributes, + le16_to_cpu(wcd->wPHYRates), + wcd->bmTFITXPowerInfo, + wcd->bmFFITXPowerInfo, + wcd->bmBandGroup, + wcd->bReserved); +} + +/* + * Walk over the BOS descriptor, verify and grok it + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * The BOS descriptor is defined at WUSB1.0[7.4.1], and it defines a + * "flexible" way to wrap all kinds of descriptors inside an standard + * descriptor (wonder why they didn't use normal descriptors, + * btw). Not like they lack code. + * + * At the end we go to look for the WUSB Device Capabilities + * (WUSB1.0[7.4.1.1]) that is wrapped in a device capability descriptor + * that is part of the BOS descriptor set. That tells us what does the + * device support (dual role, beacon type, UWB PHY rates). + */ +static int wusb_dev_bos_grok(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev, + struct usb_bos_descriptor *bos, size_t desc_size) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + void *itr, *top; + + /* Walk over BOS capabilities, verify them */ + itr = (void *)bos + sizeof(*bos); + top = itr + desc_size - sizeof(*bos); + while (itr < top) { + struct usb_dev_cap_header *cap_hdr = itr; + size_t cap_size; + u8 cap_type; + if (top - itr < sizeof(*cap_hdr)) { + dev_err(dev, "Device BUG? premature end of BOS header " + "data [offset 0x%02x]: only %zu bytes left\n", + (int)(itr - (void *)bos), top - itr); + result = -ENOSPC; + goto error_bad_cap; + } + cap_size = cap_hdr->bLength; + cap_type = cap_hdr->bDevCapabilityType; + d_printf(4, dev, "BOS Capability: 0x%02x (%zu bytes)\n", + cap_type, cap_size); + if (cap_size == 0) + break; + if (cap_size > top - itr) { + dev_err(dev, "Device BUG? premature end of BOS data " + "[offset 0x%02x cap %02x %zu bytes]: " + "only %zu bytes left\n", + (int)(itr - (void *)bos), + cap_type, cap_size, top - itr); + result = -EBADF; + goto error_bad_cap; + } + d_dump(3, dev, itr, cap_size); + switch (cap_type) { + case USB_CAP_TYPE_WIRELESS_USB: + if (cap_size != sizeof(*wusb_dev->wusb_cap_descr)) + dev_err(dev, "Device BUG? WUSB Capability " + "descriptor is %zu bytes vs %zu " + "needed\n", cap_size, + sizeof(*wusb_dev->wusb_cap_descr)); + else { + wusb_dev->wusb_cap_descr = itr; + wusb_cap_descr_printf(3, dev, itr); + } + break; + default: + dev_err(dev, "BUG? Unknown BOS capability 0x%02x " + "(%zu bytes) at offset 0x%02x\n", cap_type, + cap_size, (int)(itr - (void *)bos)); + } + itr += cap_size; + } + result = 0; +error_bad_cap: + return result; +} + +/* + * Add information from the BOS descriptors to the device + * + * @usb_dev: referenced + * @wusb_dev: referenced and unlocked + * + * So what we do is we alloc a space for the BOS descriptor of 64 + * bytes; read the first four bytes which include the wTotalLength + * field (WUSB1.0[T7-26]) and if it fits in those 64 bytes, read the + * whole thing. If not we realloc to that size. + * + * Then we call the groking function, that will fill up + * wusb_dev->wusb_cap_descr, which is what we'll need later on. + */ +static int wusb_dev_bos_add(struct usb_device *usb_dev, + struct wusb_dev *wusb_dev) +{ + ssize_t result; + struct device *dev = &usb_dev->dev; + struct usb_bos_descriptor *bos; + size_t alloc_size = 32, desc_size = 4; + + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 4) { + dev_err(dev, "Can't get BOS descriptor or too short: %zd\n", + result); + goto error_get_descriptor; + } + desc_size = le16_to_cpu(bos->wTotalLength); + if (desc_size >= alloc_size) { + kfree(bos); + alloc_size = desc_size; + bos = kmalloc(alloc_size, GFP_KERNEL); + if (bos == NULL) + return -ENOMEM; + } + result = usb_get_descriptor(usb_dev, USB_DT_BOS, 0, bos, desc_size); + if (result < 0 || result != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + if (result < sizeof(*bos) + || le16_to_cpu(bos->wTotalLength) != desc_size) { + dev_err(dev, "Can't get BOS descriptor or too short (need " + "%zu bytes): %zd\n", desc_size, result); + goto error_get_descriptor; + } + d_printf(2, dev, "Got BOS descriptor %zd bytes, %u capabilities\n", + result, bos->bNumDeviceCaps); + d_dump(2, dev, bos, result); + result = wusb_dev_bos_grok(usb_dev, wusb_dev, bos, result); + if (result < 0) + goto error_bad_bos; + wusb_dev->bos = bos; + return 0; + +error_bad_bos: +error_get_descriptor: + kfree(bos); + wusb_dev->wusb_cap_descr = NULL; + return result; +} + +static void wusb_dev_bos_rm(struct wusb_dev *wusb_dev) +{ + kfree(wusb_dev->bos); + wusb_dev->wusb_cap_descr = NULL; +}; + +static struct usb_wireless_cap_descriptor wusb_cap_descr_default = { + .bLength = sizeof(wusb_cap_descr_default), + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_CAP_TYPE_WIRELESS_USB, + + .bmAttributes = USB_WIRELESS_BEACON_NONE, + .wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53), + .bmTFITXPowerInfo = 0, + .bmFFITXPowerInfo = 0, + .bmBandGroup = cpu_to_le16(0x0001), /* WUSB1.0[7.4.1] bottom */ + .bReserved = 0 +}; + +/* + * USB stack's device addition Notifier Callback + * + * Called from drivers/usb/core/hub.c when a new device is added; we + * use this hook to perform certain WUSB specific setup work on the + * new device. As well, it is the first time we can connect the + * wusb_dev and the usb_dev. So we note it down in wusb_dev and take a + * reference that we'll drop. + * + * First we need to determine if the device is a WUSB device (else we + * ignore it). For that we use the speed setting (USB_SPEED_VARIABLE) + * [FIXME: maybe we'd need something more definitive]. If so, we track + * it's usb_busd and from there, the WUSB HC. + * + * Because all WUSB HCs are contained in a 'struct wusbhc', voila, we + * get the wusbhc for the device. + * + * We have a reference on @usb_dev (as we are called at the end of its + * enumeration). + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_add_ncb(struct usb_device *usb_dev) +{ + int result = 0; + struct wusb_dev *wusb_dev; + struct wusbhc *wusbhc; + struct device *dev = &usb_dev->dev; + u8 port_idx; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + d_fnstart(3, dev, "(usb_dev %p)\n", usb_dev); + + wusbhc = wusbhc_get_by_usb_dev(usb_dev); + if (wusbhc == NULL) + goto error_nodev; + mutex_lock(&wusbhc->mutex); + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, usb_dev); + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + mutex_unlock(&wusbhc->mutex); + if (wusb_dev == NULL) + goto error_nodev; + wusb_dev->usb_dev = usb_get_dev(usb_dev); + usb_dev->wusb_dev = wusb_dev_get(wusb_dev); + result = wusb_dev_sec_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot enable security: %d\n", result); + goto error_sec_add; + } + /* Now query the device for it's BOS and attach it to wusb_dev */ + result = wusb_dev_bos_add(usb_dev, wusb_dev); + if (result < 0) { + dev_err(dev, "Cannot get BOS descriptors: %d\n", result); + goto error_bos_add; + } + result = wusb_dev_sysfs_add(wusbhc, usb_dev, wusb_dev); + if (result < 0) + goto error_add_sysfs; +out: + wusb_dev_put(wusb_dev); + wusbhc_put(wusbhc); +error_nodev: + d_fnend(3, dev, "(usb_dev %p) = void\n", usb_dev); + return; + + wusb_dev_sysfs_rm(wusb_dev); +error_add_sysfs: + wusb_dev_bos_rm(wusb_dev); +error_bos_add: + wusb_dev_sec_rm(wusb_dev); +error_sec_add: + mutex_lock(&wusbhc->mutex); + __wusbhc_dev_disconnect(wusbhc, wusb_port_by_idx(wusbhc, port_idx)); + mutex_unlock(&wusbhc->mutex); + goto out; +} + +/* + * Undo all the steps done at connection by the notifier callback + * + * NOTE: @usb_dev locked + */ +static void wusb_dev_rm_ncb(struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (usb_dev->wusb == 0 || usb_dev->devnum == 1) + return; /* skip non wusb and wusb RHs */ + + wusb_dev_sysfs_rm(wusb_dev); + wusb_dev_bos_rm(wusb_dev); + wusb_dev_sec_rm(wusb_dev); + wusb_dev->usb_dev = NULL; + usb_dev->wusb_dev = NULL; + wusb_dev_put(wusb_dev); + usb_put_dev(usb_dev); +} + +/* + * Handle notifications from the USB stack (notifier call back) + * + * This is called when the USB stack does a + * usb_{bus,device}_{add,remove}() so we can do WUSB specific + * handling. It is called with [for the case of + * USB_DEVICE_{ADD,REMOVE} with the usb_dev locked. + */ +int wusb_usb_ncb(struct notifier_block *nb, unsigned long val, + void *priv) +{ + int result = NOTIFY_OK; + + switch (val) { + case USB_DEVICE_ADD: + wusb_dev_add_ncb(priv); + break; + case USB_DEVICE_REMOVE: + wusb_dev_rm_ncb(priv); + break; + case USB_BUS_ADD: + /* ignore (for now) */ + case USB_BUS_REMOVE: + break; + default: + WARN_ON(1); + result = NOTIFY_BAD; + }; + return result; +} + +/* + * Return a referenced wusb_dev given a @wusbhc and @usb_dev + */ +struct wusb_dev *__wusb_dev_get_by_usb_dev(struct wusbhc *wusbhc, + struct usb_device *usb_dev) +{ + struct wusb_dev *wusb_dev; + u8 port_idx; + + port_idx = wusb_port_no_to_idx(usb_dev->portnum); + BUG_ON(port_idx > wusbhc->ports_max); + wusb_dev = wusb_port_by_idx(wusbhc, port_idx)->wusb_dev; + if (wusb_dev != NULL) /* ops, device is gone */ + wusb_dev_get(wusb_dev); + return wusb_dev; +} +EXPORT_SYMBOL_GPL(__wusb_dev_get_by_usb_dev); + +void wusb_dev_destroy(struct kref *_wusb_dev) +{ + struct wusb_dev *wusb_dev + = container_of(_wusb_dev, struct wusb_dev, refcnt); + list_del_init(&wusb_dev->cack_node); + wusb_dev_free(wusb_dev); + d_fnend(1, NULL, "%s (wusb_dev %p) = void\n", __func__, wusb_dev); +} +EXPORT_SYMBOL_GPL(wusb_dev_destroy); + +/* + * Create all the device connect handling infrastructure + * + * This is basically the device info array, Connect Acknowledgement + * (cack) lists, keep-alive timers (and delayed work thread). + */ +int wusbhc_devconnect_create(struct wusbhc *wusbhc) +{ + d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + + wusbhc->keep_alive_ie.hdr.bIEIdentifier = WUIE_ID_KEEP_ALIVE; + wusbhc->keep_alive_ie.hdr.bLength = sizeof(wusbhc->keep_alive_ie.hdr); + INIT_DELAYED_WORK(&wusbhc->keep_alive_timer, wusbhc_keep_alive_run); + + wusbhc->cack_ie.hdr.bIEIdentifier = WUIE_ID_CONNECTACK; + wusbhc->cack_ie.hdr.bLength = sizeof(wusbhc->cack_ie.hdr); + INIT_LIST_HEAD(&wusbhc->cack_list); + + d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); + return 0; +} + +/* + * Release all resources taken by the devconnect stuff + */ +void wusbhc_devconnect_destroy(struct wusbhc *wusbhc) +{ + d_fnstart(3, wusbhc->dev, "(wusbhc %p)\n", wusbhc); + d_fnend(3, wusbhc->dev, "(wusbhc %p) = void\n", wusbhc); +} + +/* + * wusbhc_devconnect_start - start accepting device connections + * @wusbhc: the WUSB HC + * + * Sets the Host Info IE to accept all new connections. + * + * FIXME: This also enables the keep alives but this is not necessary + * until there are connected and authenticated devices. + */ +int wusbhc_devconnect_start(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid) +{ + struct device *dev = wusbhc->dev; + struct wuie_host_info *hi; + int result; + + hi = kzalloc(sizeof(*hi), GFP_KERNEL); + if (hi == NULL) + return -ENOMEM; + + hi->hdr.bLength = sizeof(*hi); + hi->hdr.bIEIdentifier = WUIE_ID_HOST_INFO; + hi->attributes = cpu_to_le16((wusbhc->rsv->stream << 3) | WUIE_HI_CAP_ALL); + hi->CHID = *chid; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &hi->hdr); + if (result < 0) { + dev_err(dev, "Cannot add Host Info MMCIE: %d\n", result); + goto error_mmcie_set; + } + wusbhc->wuie_host_info = hi; + + queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, + (wusbhc->trust_timeout*CONFIG_HZ)/1000/2); + + return 0; + +error_mmcie_set: + kfree(hi); + return result; +} + +/* + * wusbhc_devconnect_stop - stop managing connected devices + * @wusbhc: the WUSB HC + * + * Removes the Host Info IE and stops the keep alives. + * + * FIXME: should this disconnect all devices? + */ +void wusbhc_devconnect_stop(struct wusbhc *wusbhc) +{ + cancel_delayed_work_sync(&wusbhc->keep_alive_timer); + WARN_ON(!list_empty(&wusbhc->cack_list)); + + wusbhc_mmcie_rm(wusbhc, &wusbhc->wuie_host_info->hdr); + kfree(wusbhc->wuie_host_info); + wusbhc->wuie_host_info = NULL; +} + +/* + * wusb_set_dev_addr - set the WUSB device address used by the host + * @wusbhc: the WUSB HC the device is connect to + * @wusb_dev: the WUSB device + * @addr: new device address + */ +int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr) +{ + int result; + + wusb_dev->addr = addr; + result = wusbhc->dev_info_set(wusbhc, wusb_dev); + if (result) + dev_err(wusbhc->dev, "device %d: failed to set device " + "address\n", wusb_dev->port_idx); + else + dev_info(wusbhc->dev, "device %d: %s addr %u\n", + wusb_dev->port_idx, + (addr & WUSB_DEV_ADDR_UNAUTH) ? "unauth" : "auth", + wusb_dev->addr); + + return result; +} diff --git a/drivers/usb/wusbcore/mmc.c b/drivers/usb/wusbcore/mmc.c new file mode 100644 index 00000000000..e5390b77aaa --- /dev/null +++ b/drivers/usb/wusbcore/mmc.c @@ -0,0 +1,329 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * MMC (Microscheduled Management Command) handling + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * WUIEs and MMC IEs...well, they are almost the same at the end. MMC + * IEs are Wireless USB IEs that go into the MMC period...[what is + * that? look in Design-overview.txt]. + * + * + * This is a simple subsystem to keep track of which IEs are being + * sent by the host in the MMC period. + * + * For each WUIE we ask to send, we keep it in an array, so we can + * request its removal later, or replace the content. They are tracked + * by pointer, so be sure to use the same pointer if you want to + * remove it or update the contents. + * + * FIXME: + * - add timers that autoremove intervalled IEs? + */ +#include +#include "wusbhc.h" + +/* Initialize the MMCIEs handling mechanism */ +int wusbhc_mmcie_create(struct wusbhc *wusbhc) +{ + u8 mmcies = wusbhc->mmcies_max; + wusbhc->mmcie = kzalloc(mmcies * sizeof(wusbhc->mmcie[0]), GFP_KERNEL); + if (wusbhc->mmcie == NULL) + return -ENOMEM; + mutex_init(&wusbhc->mmcie_mutex); + return 0; +} + +/* Release resources used by the MMCIEs handling mechanism */ +void wusbhc_mmcie_destroy(struct wusbhc *wusbhc) +{ + kfree(wusbhc->mmcie); +} + +/* + * Add or replace an MMC Wireless USB IE. + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * THE CALLER ALWAYS OWNS THE POINTER (we don't free it + * on remove, we just forget about it). + * @returns: 0 if ok, < 0 errno code on error. + * + * Goes over the *whole* @wusbhc->mmcie array looking for (a) the + * first free spot and (b) if @wuie is already in the array (aka: + * transmitted in the MMCs) the spot were it is. + * + * If present, we "overwrite it" (update). + * + * + * NOTE: Need special ordering rules -- see below WUSB1.0 Table 7-38. + * The host uses the handle as the 'sort' index. We + * allocate the last one always for the WUIE_ID_HOST_INFO, and + * the rest, first come first serve in inverse order. + * + * Host software must make sure that it adds the other IEs in + * the right order... the host hardware is responsible for + * placing the WCTA IEs in the right place with the other IEs + * set by host software. + * + * NOTE: we can access wusbhc->wa_descr without locking because it is + * read only. + */ +int wusbhc_mmcie_set(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + struct wuie_hdr *wuie) +{ + int result = -ENOBUFS; + struct device *dev = wusbhc->dev; + unsigned handle, itr; + + /* Search a handle, taking into account the ordering */ + mutex_lock(&wusbhc->mmcie_mutex); + switch (wuie->bIEIdentifier) { + case WUIE_ID_HOST_INFO: + /* Always last */ + handle = wusbhc->mmcies_max - 1; + break; + case WUIE_ID_ISOCH_DISCARD: + dev_err(wusbhc->dev, "Special ordering case for WUIE ID 0x%x " + "unimplemented\n", wuie->bIEIdentifier); + result = -ENOSYS; + goto error_unlock; + default: + /* search for it or find the last empty slot */ + handle = ~0; + for (itr = 0; itr < wusbhc->mmcies_max - 1; itr++) { + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + break; + } + if (wusbhc->mmcie[itr] == NULL) + handle = itr; + } + if (handle == ~0) { + if (printk_ratelimit()) + dev_err(dev, "MMC handle space exhausted\n"); + goto error_unlock; + } + } + result = (wusbhc->mmcie_add)(wusbhc, interval, repeat_cnt, handle, + wuie); + if (result >= 0) + wusbhc->mmcie[handle] = wuie; +error_unlock: + mutex_unlock(&wusbhc->mmcie_mutex); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_set); + +/* + * Remove an MMC IE previously added with wusbhc_mmcie_set() + * + * @wuie Pointer used to add the WUIE + */ +void wusbhc_mmcie_rm(struct wusbhc *wusbhc, struct wuie_hdr *wuie) +{ + int result; + struct device *dev = wusbhc->dev; + unsigned handle, itr; + + mutex_lock(&wusbhc->mmcie_mutex); + for (itr = 0; itr < wusbhc->mmcies_max; itr++) + if (wusbhc->mmcie[itr] == wuie) { + handle = itr; + goto found; + } + mutex_unlock(&wusbhc->mmcie_mutex); + return; + +found: + result = (wusbhc->mmcie_rm)(wusbhc, handle); + if (result == 0) + wusbhc->mmcie[itr] = NULL; + else if (printk_ratelimit()) + dev_err(dev, "MMC: Failed to remove IE %p (0x%02x)\n", + wuie, wuie->bIEIdentifier); + mutex_unlock(&wusbhc->mmcie_mutex); + return; +} +EXPORT_SYMBOL_GPL(wusbhc_mmcie_rm); + +/* + * wusbhc_start - start transmitting MMCs and accepting connections + * @wusbhc: the HC to start + * @chid: the CHID to use for this host + * + * Establishes a cluster reservation, enables device connections, and + * starts MMCs with appropriate DNTS parameters. + */ +int wusbhc_start(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid) +{ + int result; + struct device *dev = wusbhc->dev; + + WARN_ON(wusbhc->wuie_host_info != NULL); + + result = wusbhc_rsv_establish(wusbhc); + if (result < 0) { + dev_err(dev, "cannot establish cluster reservation: %d\n", + result); + goto error_rsv_establish; + } + + result = wusbhc_devconnect_start(wusbhc, chid); + if (result < 0) { + dev_err(dev, "error enabling device connections: %d\n", result); + goto error_devconnect_start; + } + + result = wusbhc_sec_start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting security in the HC: %d\n", result); + goto error_sec_start; + } + /* FIXME: the choice of the DNTS parameters is somewhat + * arbitrary */ + result = wusbhc->set_num_dnts(wusbhc, 0, 15); + if (result < 0) { + dev_err(dev, "Cannot set DNTS parameters: %d\n", result); + goto error_set_num_dnts; + } + result = wusbhc->start(wusbhc); + if (result < 0) { + dev_err(dev, "error starting wusbch: %d\n", result); + goto error_wusbhc_start; + } + wusbhc->active = 1; + return 0; + +error_wusbhc_start: + wusbhc_sec_stop(wusbhc); +error_set_num_dnts: +error_sec_start: + wusbhc_devconnect_stop(wusbhc); +error_devconnect_start: + wusbhc_rsv_terminate(wusbhc); +error_rsv_establish: + return result; +} + +/* + * Disconnect all from the WUSB Channel + * + * Send a Host Disconnect IE in the MMC, wait, don't send it any more + */ +static int __wusbhc_host_disconnect_ie(struct wusbhc *wusbhc) +{ + int result = -ENOMEM; + struct wuie_host_disconnect *host_disconnect_ie; + might_sleep(); + host_disconnect_ie = kmalloc(sizeof(*host_disconnect_ie), GFP_KERNEL); + if (host_disconnect_ie == NULL) + goto error_alloc; + host_disconnect_ie->hdr.bLength = sizeof(*host_disconnect_ie); + host_disconnect_ie->hdr.bIEIdentifier = WUIE_ID_HOST_DISCONNECT; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &host_disconnect_ie->hdr); + if (result < 0) + goto error_mmcie_set; + + /* WUSB1.0[8.5.3.1 & 7.5.2] */ + msleep(100); + wusbhc_mmcie_rm(wusbhc, &host_disconnect_ie->hdr); +error_mmcie_set: + kfree(host_disconnect_ie); +error_alloc: + return result; +} + +/* + * wusbhc_stop - stop transmitting MMCs + * @wusbhc: the HC to stop + * + * Send a Host Disconnect IE, wait, remove all the MMCs (stop sending MMCs). + * + * If we can't allocate a Host Stop IE, screw it, we don't notify the + * devices we are disconnecting... + */ +void wusbhc_stop(struct wusbhc *wusbhc) +{ + if (wusbhc->active) { + wusbhc->active = 0; + wusbhc->stop(wusbhc); + wusbhc_sec_stop(wusbhc); + __wusbhc_host_disconnect_ie(wusbhc); + wusbhc_devconnect_stop(wusbhc); + wusbhc_rsv_terminate(wusbhc); + } +} +EXPORT_SYMBOL_GPL(wusbhc_stop); + +/* + * Change the CHID in a WUSB Channel + * + * If it is just a new CHID, send a Host Disconnect IE and then change + * the CHID IE. + */ +static int __wusbhc_chid_change(struct wusbhc *wusbhc, + const struct wusb_ckhdid *chid) +{ + int result = -ENOSYS; + struct device *dev = wusbhc->dev; + dev_err(dev, "%s() not implemented yet\n", __func__); + return result; + + BUG_ON(wusbhc->wuie_host_info == NULL); + __wusbhc_host_disconnect_ie(wusbhc); + wusbhc->wuie_host_info->CHID = *chid; + result = wusbhc_mmcie_set(wusbhc, 0, 0, &wusbhc->wuie_host_info->hdr); + if (result < 0) + dev_err(dev, "Can't update Host Info WUSB IE: %d\n", result); + return result; +} + +/* + * Set/reset/update a new CHID + * + * Depending on the previous state of the MMCs, start, stop or change + * the sent MMC. This effectively switches the host controller on and + * off (radio wise). + */ +int wusbhc_chid_set(struct wusbhc *wusbhc, const struct wusb_ckhdid *chid) +{ + int result = 0; + + if (memcmp(chid, &wusb_ckhdid_zero, sizeof(chid)) == 0) + chid = NULL; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + if (chid) + result = __wusbhc_chid_change(wusbhc, chid); + else + wusbhc_stop(wusbhc); + } else { + if (chid) + wusbhc_start(wusbhc, chid); + } + mutex_unlock(&wusbhc->mutex); + return result; +} +EXPORT_SYMBOL_GPL(wusbhc_chid_set); -- cgit v1.2.3 From d59db761b8559f07a7161ca3387d6c6949667ede Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:25 +0100 Subject: wusb: add the Wireless USB core (security) Add the WUSB security (authentication) code. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/crypto.c | 538 +++++++++++++++++++++++++++++++++ drivers/usb/wusbcore/security.c | 642 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1180 insertions(+) create mode 100644 drivers/usb/wusbcore/crypto.c create mode 100644 drivers/usb/wusbcore/security.c diff --git a/drivers/usb/wusbcore/crypto.c b/drivers/usb/wusbcore/crypto.c new file mode 100644 index 00000000000..c36c4389baa --- /dev/null +++ b/drivers/usb/wusbcore/crypto.c @@ -0,0 +1,538 @@ +/* + * Ultra Wide Band + * AES-128 CCM Encryption + * + * Copyright (C) 2007 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * We don't do any encryption here; we use the Linux Kernel's AES-128 + * crypto modules to construct keys and payload blocks in a way + * defined by WUSB1.0[6]. Check the erratas, as typos are are patched + * there. + * + * Thanks a zillion to John Keys for his help and clarifications over + * the designed-by-a-committee text. + * + * So the idea is that there is this basic Pseudo-Random-Function + * defined in WUSB1.0[6.5] which is the core of everything. It works + * by tweaking some blocks, AES crypting them and then xoring + * something else with them (this seems to be called CBC(AES) -- can + * you tell I know jack about crypto?). So we just funnel it into the + * Linux Crypto API. + * + * We leave a crypto test module so we can verify that vectors match, + * every now and then. + * + * Block size: 16 bytes -- AES seems to do things in 'block sizes'. I + * am learning a lot... + * + * Conveniently, some data structures that need to be + * funneled through AES are...16 bytes in size! + */ + +#include +#include +#include +#include +#include +#include +#define D_LOCAL 0 +#include + + +/* + * Block of data, as understood by AES-CCM + * + * The code assumes this structure is nothing but a 16 byte array + * (packed in a struct to avoid common mess ups that I usually do with + * arrays and enforcing type checking). + */ +struct aes_ccm_block { + u8 data[16]; +} __attribute__((packed)); + +/* + * Counter-mode Blocks (WUSB1.0[6.4]) + * + * According to CCM (or so it seems), for the purpose of calculating + * the MIC, the message is broken in N counter-mode blocks, B0, B1, + * ... BN. + * + * B0 contains flags, the CCM nonce and l(m). + * + * B1 contains l(a), the MAC header, the encryption offset and padding. + * + * If EO is nonzero, additional blocks are built from payload bytes + * until EO is exahusted (FIXME: padding to 16 bytes, I guess). The + * padding is not xmitted. + */ + +/* WUSB1.0[T6.4] */ +struct aes_ccm_b0 { + u8 flags; /* 0x59, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 lm; +} __attribute__((packed)); + +/* WUSB1.0[T6.5] */ +struct aes_ccm_b1 { + __be16 la; + u8 mac_header[10]; + __le16 eo; + u8 security_reserved; /* This is always zero */ + u8 padding; /* 0 */ +} __attribute__((packed)); + +/* + * Encryption Blocks (WUSB1.0[6.4.4]) + * + * CCM uses Ax blocks to generate a keystream with which the MIC and + * the message's payload are encoded. A0 always encrypts/decrypts the + * MIC. Ax (x>0) are used for the sucesive payload blocks. + * + * The x is the counter, and is increased for each block. + */ +struct aes_ccm_a { + u8 flags; /* 0x01, per CCM spec */ + struct aes_ccm_nonce ccm_nonce; + __be16 counter; /* Value of x */ +} __attribute__((packed)); + +static void bytewise_xor(void *_bo, const void *_bi1, const void *_bi2, + size_t size) +{ + u8 *bo = _bo; + const u8 *bi1 = _bi1, *bi2 = _bi2; + size_t itr; + for (itr = 0; itr < size; itr++) + bo[itr] = bi1[itr] ^ bi2[itr]; +} + +/* + * CC-MAC function WUSB1.0[6.5] + * + * Take a data string and produce the encrypted CBC Counter-mode MIC + * + * Note the names for most function arguments are made to (more or + * less) match those used in the pseudo-function definition given in + * WUSB1.0[6.5]. + * + * @tfm_cbc: CBC(AES) blkcipher handle (initialized) + * + * @tfm_aes: AES cipher handle (initialized) + * + * @mic: buffer for placing the computed MIC (Message Integrity + * Code). This is exactly 8 bytes, and we expect the buffer to + * be at least eight bytes in length. + * + * @key: 128 bit symmetric key + * + * @n: CCM nonce + * + * @a: ASCII string, 14 bytes long (I guess zero padded if needed; + * we use exactly 14 bytes). + * + * @b: data stream to be processed; cannot be a global or const local + * (will confuse the scatterlists) + * + * @blen: size of b... + * + * Still not very clear how this is done, but looks like this: we + * create block B0 (as WUSB1.0[6.5] says), then we AES-crypt it with + * @key. We bytewise xor B0 with B1 (1) and AES-crypt that. Then we + * take the payload and divide it in blocks (16 bytes), xor them with + * the previous crypto result (16 bytes) and crypt it, repeat the next + * block with the output of the previous one, rinse wash (I guess this + * is what AES CBC mode means...but I truly have no idea). So we use + * the CBC(AES) blkcipher, that does precisely that. The IV (Initial + * Vector) is 16 bytes and is set to zero, so + * + * See rfc3610. Linux crypto has a CBC implementation, but the + * documentation is scarce, to say the least, and the example code is + * so intricated that is difficult to understand how things work. Most + * of this is guess work -- bite me. + * + * (1) Created as 6.5 says, again, using as l(a) 'Blen + 14', and + * using the 14 bytes of @a to fill up + * b1.{mac_header,e0,security_reserved,padding}. + * + * NOTE: The definiton of l(a) in WUSB1.0[6.5] vs the definition of + * l(m) is orthogonal, they bear no relationship, so it is not + * in conflict with the parameter's relation that + * WUSB1.0[6.4.2]) defines. + * + * NOTE: WUSB1.0[A.1]: Host Nonce is missing a nibble? (1e); fixed in + * first errata released on 2005/07. + * + * NOTE: we need to clean IV to zero at each invocation to make sure + * we start with a fresh empty Initial Vector, so that the CBC + * works ok. + * + * NOTE: blen is not aligned to a block size, we'll pad zeros, that's + * what sg[4] is for. Maybe there is a smarter way to do this. + */ +static int wusb_ccm_mac(struct crypto_blkcipher *tfm_cbc, + struct crypto_cipher *tfm_aes, void *mic, + const struct aes_ccm_nonce *n, + const struct aes_ccm_label *a, const void *b, + size_t blen) +{ + int result = 0; + struct blkcipher_desc desc; + struct aes_ccm_b0 b0; + struct aes_ccm_b1 b1; + struct aes_ccm_a ax; + struct scatterlist sg[4], sg_dst; + void *iv, *dst_buf; + size_t ivsize, dst_size; + const u8 bzero[16] = { 0 }; + size_t zero_padding; + + d_fnstart(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, " + "n %p, a %p, b %p, blen %zu)\n", + tfm_cbc, tfm_aes, mic, n, a, b, blen); + /* + * These checks should be compile time optimized out + * ensure @a fills b1's mac_header and following fields + */ + WARN_ON(sizeof(*a) != sizeof(b1) - sizeof(b1.la)); + WARN_ON(sizeof(b0) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(b1) != sizeof(struct aes_ccm_block)); + WARN_ON(sizeof(ax) != sizeof(struct aes_ccm_block)); + + result = -ENOMEM; + zero_padding = sizeof(struct aes_ccm_block) + - blen % sizeof(struct aes_ccm_block); + zero_padding = blen % sizeof(struct aes_ccm_block); + if (zero_padding) + zero_padding = sizeof(struct aes_ccm_block) - zero_padding; + dst_size = blen + sizeof(b0) + sizeof(b1) + zero_padding; + dst_buf = kzalloc(dst_size, GFP_KERNEL); + if (dst_buf == NULL) { + printk(KERN_ERR "E: can't alloc destination buffer\n"); + goto error_dst_buf; + } + + iv = crypto_blkcipher_crt(tfm_cbc)->iv; + ivsize = crypto_blkcipher_ivsize(tfm_cbc); + memset(iv, 0, ivsize); + + /* Setup B0 */ + b0.flags = 0x59; /* Format B0 */ + b0.ccm_nonce = *n; + b0.lm = cpu_to_be16(0); /* WUSB1.0[6.5] sez l(m) is 0 */ + + /* Setup B1 + * + * The WUSB spec is anything but clear! WUSB1.0[6.5] + * says that to initialize B1 from A with 'l(a) = blen + + * 14'--after clarification, it means to use A's contents + * for MAC Header, EO, sec reserved and padding. + */ + b1.la = cpu_to_be16(blen + 14); + memcpy(&b1.mac_header, a, sizeof(*a)); + + d_printf(4, NULL, "I: B0 (%zu bytes)\n", sizeof(b0)); + d_dump(4, NULL, &b0, sizeof(b0)); + d_printf(4, NULL, "I: B1 (%zu bytes)\n", sizeof(b1)); + d_dump(4, NULL, &b1, sizeof(b1)); + d_printf(4, NULL, "I: B (%zu bytes)\n", blen); + d_dump(4, NULL, b, blen); + d_printf(4, NULL, "I: B 0-padding (%zu bytes)\n", zero_padding); + d_printf(4, NULL, "D: IV before crypto (%zu)\n", ivsize); + d_dump(4, NULL, iv, ivsize); + + sg_init_table(sg, ARRAY_SIZE(sg)); + sg_set_buf(&sg[0], &b0, sizeof(b0)); + sg_set_buf(&sg[1], &b1, sizeof(b1)); + sg_set_buf(&sg[2], b, blen); + /* 0 if well behaved :) */ + sg_set_buf(&sg[3], bzero, zero_padding); + sg_init_one(&sg_dst, dst_buf, dst_size); + + desc.tfm = tfm_cbc; + desc.flags = 0; + result = crypto_blkcipher_encrypt(&desc, &sg_dst, sg, dst_size); + if (result < 0) { + printk(KERN_ERR "E: can't compute CBC-MAC tag (MIC): %d\n", + result); + goto error_cbc_crypt; + } + d_printf(4, NULL, "D: MIC tag\n"); + d_dump(4, NULL, iv, ivsize); + + /* Now we crypt the MIC Tag (*iv) with Ax -- values per WUSB1.0[6.5] + * The procedure is to AES crypt the A0 block and XOR the MIC + * Tag agains it; we only do the first 8 bytes and place it + * directly in the destination buffer. + * + * POS Crypto API: size is assumed to be AES's block size. + * Thanks for documenting it -- tip taken from airo.c + */ + ax.flags = 0x01; /* as per WUSB 1.0 spec */ + ax.ccm_nonce = *n; + ax.counter = 0; + crypto_cipher_encrypt_one(tfm_aes, (void *)&ax, (void *)&ax); + bytewise_xor(mic, &ax, iv, 8); + d_printf(4, NULL, "D: CTR[MIC]\n"); + d_dump(4, NULL, &ax, 8); + d_printf(4, NULL, "D: CCM-MIC tag\n"); + d_dump(4, NULL, mic, 8); + result = 8; +error_cbc_crypt: + kfree(dst_buf); +error_dst_buf: + d_fnend(3, NULL, "(tfm_cbc %p, tfm_aes %p, mic %p, " + "n %p, a %p, b %p, blen %zu)\n", + tfm_cbc, tfm_aes, mic, n, a, b, blen); + return result; +} + +/* + * WUSB Pseudo Random Function (WUSB1.0[6.5]) + * + * @b: buffer to the source data; cannot be a global or const local + * (will confuse the scatterlists) + */ +ssize_t wusb_prf(void *out, size_t out_size, + const u8 key[16], const struct aes_ccm_nonce *_n, + const struct aes_ccm_label *a, + const void *b, size_t blen, size_t len) +{ + ssize_t result, bytes = 0, bitr; + struct aes_ccm_nonce n = *_n; + struct crypto_blkcipher *tfm_cbc; + struct crypto_cipher *tfm_aes; + u64 sfn = 0; + __le64 sfn_le; + + d_fnstart(3, NULL, "(out %p, out_size %zu, key %p, _n %p, " + "a %p, b %p, blen %zu, len %zu)\n", out, out_size, + key, _n, a, b, blen, len); + + tfm_cbc = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_cbc)) { + result = PTR_ERR(tfm_cbc); + printk(KERN_ERR "E: can't load CBC(AES): %d\n", (int)result); + goto error_alloc_cbc; + } + result = crypto_blkcipher_setkey(tfm_cbc, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set CBC key: %d\n", (int)result); + goto error_setkey_cbc; + } + + tfm_aes = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm_aes)) { + result = PTR_ERR(tfm_aes); + printk(KERN_ERR "E: can't load AES: %d\n", (int)result); + goto error_alloc_aes; + } + result = crypto_cipher_setkey(tfm_aes, key, 16); + if (result < 0) { + printk(KERN_ERR "E: can't set AES key: %d\n", (int)result); + goto error_setkey_aes; + } + + for (bitr = 0; bitr < (len + 63) / 64; bitr++) { + sfn_le = cpu_to_le64(sfn++); + memcpy(&n.sfn, &sfn_le, sizeof(n.sfn)); /* n.sfn++... */ + result = wusb_ccm_mac(tfm_cbc, tfm_aes, out + bytes, + &n, a, b, blen); + if (result < 0) + goto error_ccm_mac; + bytes += result; + } + result = bytes; +error_ccm_mac: +error_setkey_aes: + crypto_free_cipher(tfm_aes); +error_alloc_aes: +error_setkey_cbc: + crypto_free_blkcipher(tfm_cbc); +error_alloc_cbc: + d_fnend(3, NULL, "(out %p, out_size %zu, key %p, _n %p, " + "a %p, b %p, blen %zu, len %zu) = %d\n", out, out_size, + key, _n, a, b, blen, len, (int)bytes); + return result; +} + +/* WUSB1.0[A.2] test vectors */ +static const u8 stv_hsmic_key[16] = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f +}; + +static const struct aes_ccm_nonce stv_hsmic_n = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +/* + * Out-of-band MIC Generation verification code + * + */ +static int wusb_oob_mic_verify(void) +{ + int result; + u8 mic[8]; + /* WUSB1.0[A.2] test vectors + * + * Need to keep it in the local stack as GCC 4.1.3something + * messes up and generates noise. + */ + struct usb_handshake stv_hsmic_hs = { + .bMessageNumber = 2, + .bStatus = 00, + .tTKID = { 0x76, 0x98, 0x01 }, + .bReserved = 00, + .CDID = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f }, + .nonce = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f }, + .MIC = { 0x75, 0x6a, 0x97, 0x51, 0x0c, 0x8c, + 0x14, 0x7b } , + }; + size_t hs_size; + + result = wusb_oob_mic(mic, stv_hsmic_key, &stv_hsmic_n, &stv_hsmic_hs); + if (result < 0) + printk(KERN_ERR "E: WUSB OOB MIC test: failed: %d\n", result); + else if (memcmp(stv_hsmic_hs.MIC, mic, sizeof(mic))) { + printk(KERN_ERR "E: OOB MIC test: " + "mismatch between MIC result and WUSB1.0[A2]\n"); + hs_size = sizeof(stv_hsmic_hs) - sizeof(stv_hsmic_hs.MIC); + printk(KERN_ERR "E: Handshake2 in: (%zu bytes)\n", hs_size); + dump_bytes(NULL, &stv_hsmic_hs, hs_size); + printk(KERN_ERR "E: CCM Nonce in: (%zu bytes)\n", + sizeof(stv_hsmic_n)); + dump_bytes(NULL, &stv_hsmic_n, sizeof(stv_hsmic_n)); + printk(KERN_ERR "E: MIC out:\n"); + dump_bytes(NULL, mic, sizeof(mic)); + printk(KERN_ERR "E: MIC out (from WUSB1.0[A.2]):\n"); + dump_bytes(NULL, stv_hsmic_hs.MIC, sizeof(stv_hsmic_hs.MIC)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Test vectors for Key derivation + * + * These come from WUSB1.0[6.5.1], the vectors in WUSB1.0[A.1] + * (errata corrected in 2005/07). + */ +static const u8 stv_key_a1[16] __attribute__ ((__aligned__(4))) = { + 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, + 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f +}; + +static const struct aes_ccm_nonce stv_keydvt_n_a1 = { + .sfn = { 0 }, + .tkid = { 0x76, 0x98, 0x01, }, + .dest_addr = { .data = { 0xbe, 0x00 } }, + .src_addr = { .data = { 0x76, 0x98 } }, +}; + +static const struct wusb_keydvt_out stv_keydvt_out_a1 = { + .kck = { + 0x4b, 0x79, 0xa3, 0xcf, 0xe5, 0x53, 0x23, 0x9d, + 0xd7, 0xc1, 0x6d, 0x1c, 0x2d, 0xab, 0x6d, 0x3f + }, + .ptk = { + 0xc8, 0x70, 0x62, 0x82, 0xb6, 0x7c, 0xe9, 0x06, + 0x7b, 0xc5, 0x25, 0x69, 0xf2, 0x36, 0x61, 0x2d + } +}; + +/* + * Performa a test to make sure we match the vectors defined in + * WUSB1.0[A.1](Errata2006/12) + */ +static int wusb_key_derive_verify(void) +{ + int result = 0; + struct wusb_keydvt_out keydvt_out; + /* These come from WUSB1.0[A.1] + 2006/12 errata + * NOTE: can't make this const or global -- somehow it seems + * the scatterlists for crypto get confused and we get + * bad data. There is no doc on this... */ + struct wusb_keydvt_in stv_keydvt_in_a1 = { + .hnonce = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }, + .dnonce = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f + } + }; + + result = wusb_key_derive(&keydvt_out, stv_key_a1, &stv_keydvt_n_a1, + &stv_keydvt_in_a1); + if (result < 0) + printk(KERN_ERR "E: WUSB key derivation test: " + "derivation failed: %d\n", result); + if (memcmp(&stv_keydvt_out_a1, &keydvt_out, sizeof(keydvt_out))) { + printk(KERN_ERR "E: WUSB key derivation test: " + "mismatch between key derivation result " + "and WUSB1.0[A1] Errata 2006/12\n"); + printk(KERN_ERR "E: keydvt in: key (%zu bytes)\n", + sizeof(stv_key_a1)); + dump_bytes(NULL, stv_key_a1, sizeof(stv_key_a1)); + printk(KERN_ERR "E: keydvt in: nonce (%zu bytes)\n", + sizeof(stv_keydvt_n_a1)); + dump_bytes(NULL, &stv_keydvt_n_a1, sizeof(stv_keydvt_n_a1)); + printk(KERN_ERR "E: keydvt in: hnonce & dnonce (%zu bytes)\n", + sizeof(stv_keydvt_in_a1)); + dump_bytes(NULL, &stv_keydvt_in_a1, sizeof(stv_keydvt_in_a1)); + printk(KERN_ERR "E: keydvt out: KCK\n"); + dump_bytes(NULL, &keydvt_out.kck, sizeof(keydvt_out.kck)); + printk(KERN_ERR "E: keydvt out: PTK\n"); + dump_bytes(NULL, &keydvt_out.ptk, sizeof(keydvt_out.ptk)); + result = -EINVAL; + } else + result = 0; + return result; +} + +/* + * Initialize crypto system + * + * FIXME: we do nothing now, other than verifying. Later on we'll + * cache the encryption stuff, so that's why we have a separate init. + */ +int wusb_crypto_init(void) +{ + int result; + + result = wusb_key_derive_verify(); + if (result < 0) + return result; + return wusb_oob_mic_verify(); +} + +void wusb_crypto_exit(void) +{ + /* FIXME: free cached crypto transforms */ +} diff --git a/drivers/usb/wusbcore/security.c b/drivers/usb/wusbcore/security.c new file mode 100644 index 00000000000..a101cad6a8d --- /dev/null +++ b/drivers/usb/wusbcore/security.c @@ -0,0 +1,642 @@ +/* + * Wireless USB Host Controller + * Security support: encryption enablement, etc + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ +#include +#include +#include +#include "wusbhc.h" + +/* + * DEBUG & SECURITY WARNING!!!! + * + * If you enable this past 1, the debug code will weaken the + * cryptographic safety of the system (on purpose, for debugging). + * + * Weaken means: + * we print secret keys and intermediate values all the way, + */ +#undef D_LOCAL +#define D_LOCAL 2 +#include + +static void wusbhc_set_gtk_callback(struct urb *urb); +static void wusbhc_gtk_rekey_done_work(struct work_struct *work); + +int wusbhc_sec_create(struct wusbhc *wusbhc) +{ + wusbhc->gtk.descr.bLength = sizeof(wusbhc->gtk.descr) + sizeof(wusbhc->gtk.data); + wusbhc->gtk.descr.bDescriptorType = USB_DT_KEY; + wusbhc->gtk.descr.bReserved = 0; + + wusbhc->gtk_index = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + INIT_WORK(&wusbhc->gtk_rekey_done_work, wusbhc_gtk_rekey_done_work); + + return 0; +} + + +/* Called when the HC is destroyed */ +void wusbhc_sec_destroy(struct wusbhc *wusbhc) +{ +} + + +/** + * wusbhc_next_tkid - generate a new, currently unused, TKID + * @wusbhc: the WUSB host controller + * @wusb_dev: the device whose PTK the TKID is for + * (or NULL for a TKID for a GTK) + * + * The generated TKID consist of two parts: the device's authenicated + * address (or 0 or a GTK); and an incrementing number. This ensures + * that TKIDs cannot be shared between devices and by the time the + * incrementing number wraps around the older TKIDs will no longer be + * in use (a maximum of two keys may be active at any one time). + */ +static u32 wusbhc_next_tkid(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + u32 *tkid; + u32 addr; + + if (wusb_dev == NULL) { + tkid = &wusbhc->gtk_tkid; + addr = 0; + } else { + tkid = &wusb_port_by_idx(wusbhc, wusb_dev->port_idx)->ptk_tkid; + addr = wusb_dev->addr & 0x7f; + } + + *tkid = (addr << 8) | ((*tkid + 1) & 0xff); + + return *tkid; +} + +static void wusbhc_generate_gtk(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + u32 tkid; + + tkid = wusbhc_next_tkid(wusbhc, NULL); + + wusbhc->gtk.descr.tTKID[0] = (tkid >> 0) & 0xff; + wusbhc->gtk.descr.tTKID[1] = (tkid >> 8) & 0xff; + wusbhc->gtk.descr.tTKID[2] = (tkid >> 16) & 0xff; + + get_random_bytes(wusbhc->gtk.descr.bKeyData, key_size); +} + +/** + * wusbhc_sec_start - start the security management process + * @wusbhc: the WUSB host controller + * + * Generate and set an initial GTK on the host controller. + * + * Called when the HC is started. + */ +int wusbhc_sec_start(struct wusbhc *wusbhc) +{ + const size_t key_size = sizeof(wusbhc->gtk.data); + int result; + + wusbhc_generate_gtk(wusbhc); + + result = wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, + &wusbhc->gtk.descr.bKeyData, key_size); + if (result < 0) + dev_err(wusbhc->dev, "cannot set GTK for the host: %d\n", + result); + + return result; +} + +/** + * wusbhc_sec_stop - stop the security management process + * @wusbhc: the WUSB host controller + * + * Wait for any pending GTK rekeys to stop. + */ +void wusbhc_sec_stop(struct wusbhc *wusbhc) +{ + cancel_work_sync(&wusbhc->gtk_rekey_done_work); +} + + +/** @returns encryption type name */ +const char *wusb_et_name(u8 x) +{ + switch (x) { + case USB_ENC_TYPE_UNSECURE: return "unsecure"; + case USB_ENC_TYPE_WIRED: return "wired"; + case USB_ENC_TYPE_CCM_1: return "CCM-1"; + case USB_ENC_TYPE_RSA_1: return "RSA-1"; + default: return "unknown"; + } +} +EXPORT_SYMBOL_GPL(wusb_et_name); + +/* + * Set the device encryption method + * + * We tell the device which encryption method to use; we do this when + * setting up the device's security. + */ +static int wusb_dev_set_encryption(struct usb_device *usb_dev, int value) +{ + int result; + struct device *dev = &usb_dev->dev; + struct wusb_dev *wusb_dev = usb_dev->wusb_dev; + + if (value) { + value = wusb_dev->ccm1_etd.bEncryptionValue; + } else { + /* FIXME: should be wusb_dev->etd[UNSECURE].bEncryptionValue */ + value = 0; + } + /* Set device's */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + value, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Can't set device's WUSB encryption to " + "%s (value %d): %d\n", + wusb_et_name(wusb_dev->ccm1_etd.bEncryptionType), + wusb_dev->ccm1_etd.bEncryptionValue, result); + return result; +} + +/* + * Set the GTK to be used by a device. + * + * The device must be authenticated. + */ +static int wusb_dev_set_gtk(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct usb_device *usb_dev = wusb_dev->usb_dev; + + return usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_DT_KEY << 8 | wusbhc->gtk_index, 0, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + 1000); +} + + +/* FIXME: prototype for adding security */ +int wusb_dev_sec_add(struct wusbhc *wusbhc, + struct usb_device *usb_dev, struct wusb_dev *wusb_dev) +{ + int result, bytes, secd_size; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor secd; + const struct usb_encryption_descriptor *etd, *ccm1_etd = NULL; + void *secd_buf; + const void *itr, *top; + char buf[64]; + + d_fnstart(3, dev, "(usb_dev %p, wusb_dev %p)\n", usb_dev, wusb_dev); + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, &secd, sizeof(secd)); + if (result < sizeof(secd)) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto error_secd; + } + secd_size = le16_to_cpu(secd.wTotalLength); + d_printf(5, dev, "got %d bytes of sec descriptor, total is %d\n", + result, secd_size); + secd_buf = kmalloc(secd_size, GFP_KERNEL); + if (secd_buf == NULL) { + dev_err(dev, "Can't allocate space for security descriptors\n"); + goto error_secd_alloc; + } + result = usb_get_descriptor(usb_dev, USB_DT_SECURITY, + 0, secd_buf, secd_size); + if (result < secd_size) { + dev_err(dev, "Can't read security descriptor or " + "not enough data: %d\n", result); + goto error_secd_all; + } + d_printf(5, dev, "got %d bytes of sec descriptors\n", result); + bytes = 0; + itr = secd_buf + sizeof(secd); + top = secd_buf + result; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad device security descriptor; " + "not enough data (%zu vs %zu bytes left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad device encryption descriptor; " + "descriptor is too short " + "(%u vs %zu needed)\n", + etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x/%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue, etd->bAuthKeyIndex); + if (etd->bEncryptionType == USB_ENC_TYPE_CCM_1) + ccm1_etd = etd; + } + /* This code only supports CCM1 as of now. */ + /* FIXME: user has to choose which sec mode to use? + * In theory we want CCM */ + if (ccm1_etd == NULL) { + dev_err(dev, "WUSB device doesn't support CCM1 encryption, " + "can't use!\n"); + result = -EINVAL; + goto error_no_ccm1; + } + wusb_dev->ccm1_etd = *ccm1_etd; + dev_info(dev, "supported encryption: %s; using %s (0x%02x/%02x)\n", + buf, wusb_et_name(ccm1_etd->bEncryptionType), + ccm1_etd->bEncryptionValue, ccm1_etd->bAuthKeyIndex); + result = 0; + kfree(secd_buf); +out: + d_fnend(3, dev, "(usb_dev %p, wusb_dev %p) = %d\n", + usb_dev, wusb_dev, result); + return result; + + +error_no_ccm1: +error_secd_all: + kfree(secd_buf); +error_secd_alloc: +error_secd: + goto out; +} + +void wusb_dev_sec_rm(struct wusb_dev *wusb_dev) +{ + /* Nothing so far */ +} + +static void hs_printk(unsigned level, struct device *dev, + struct usb_handshake *hs) +{ + d_printf(level, dev, + " bMessageNumber: %u\n" + " bStatus: %u\n" + " tTKID: %02x %02x %02x\n" + " CDID: %02x %02x %02x %02x %02x %02x %02x %02x\n" + " %02x %02x %02x %02x %02x %02x %02x %02x\n" + " nonce: %02x %02x %02x %02x %02x %02x %02x %02x\n" + " %02x %02x %02x %02x %02x %02x %02x %02x\n" + " MIC: %02x %02x %02x %02x %02x %02x %02x %02x\n", + hs->bMessageNumber, hs->bStatus, + hs->tTKID[2], hs->tTKID[1], hs->tTKID[0], + hs->CDID[0], hs->CDID[1], hs->CDID[2], hs->CDID[3], + hs->CDID[4], hs->CDID[5], hs->CDID[6], hs->CDID[7], + hs->CDID[8], hs->CDID[9], hs->CDID[10], hs->CDID[11], + hs->CDID[12], hs->CDID[13], hs->CDID[14], hs->CDID[15], + hs->nonce[0], hs->nonce[1], hs->nonce[2], hs->nonce[3], + hs->nonce[4], hs->nonce[5], hs->nonce[6], hs->nonce[7], + hs->nonce[8], hs->nonce[9], hs->nonce[10], hs->nonce[11], + hs->nonce[12], hs->nonce[13], hs->nonce[14], hs->nonce[15], + hs->MIC[0], hs->MIC[1], hs->MIC[2], hs->MIC[3], + hs->MIC[4], hs->MIC[5], hs->MIC[6], hs->MIC[7]); +} + +/** + * Update the address of an unauthenticated WUSB device + * + * Once we have successfully authenticated, we take it to addr0 state + * and then to a normal address. + * + * Before the device's address (as known by it) was usb_dev->devnum | + * 0x80 (unauthenticated address). With this we update it to usb_dev->devnum. + */ +static int wusb_dev_update_address(struct wusbhc *wusbhc, + struct wusb_dev *wusb_dev) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u8 new_address = wusb_dev->addr & 0x7F; + + /* Set address 0 */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + 0, 0, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address 0: %d\n", + result); + goto error_addr0; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, 0); + if (result < 0) + goto error_addr0; + usb_ep0_reinit(usb_dev); + + /* Set new (authenticated) address. */ + result = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_ADDRESS, 0, + new_address, 0, NULL, 0, + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "auth failed: can't set address %u: %d\n", + new_address, result); + goto error_addr; + } + result = wusb_set_dev_addr(wusbhc, wusb_dev, new_address); + if (result < 0) + goto error_addr; + usb_ep0_reinit(usb_dev); + usb_dev->authenticated = 1; +error_addr: +error_addr0: + return result; +} + +/* + * + * + */ +/* FIXME: split and cleanup */ +int wusb_dev_4way_handshake(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, + struct wusb_ckhdid *ck) +{ + int result = -ENOMEM; + struct usb_device *usb_dev = wusb_dev->usb_dev; + struct device *dev = &usb_dev->dev; + u32 tkid; + __le32 tkid_le; + struct usb_handshake *hs; + struct aes_ccm_nonce ccm_n; + u8 mic[8]; + struct wusb_keydvt_in keydvt_in; + struct wusb_keydvt_out keydvt_out; + + hs = kzalloc(3*sizeof(hs[0]), GFP_KERNEL); + if (hs == NULL) { + dev_err(dev, "can't allocate handshake data\n"); + goto error_kzalloc; + } + + /* We need to turn encryption before beginning the 4way + * hshake (WUSB1.0[.3.2.2]) */ + result = wusb_dev_set_encryption(usb_dev, 1); + if (result < 0) + goto error_dev_set_encryption; + + tkid = wusbhc_next_tkid(wusbhc, wusb_dev); + tkid_le = cpu_to_le32(tkid); + + hs[0].bMessageNumber = 1; + hs[0].bStatus = 0; + memcpy(hs[0].tTKID, &tkid_le, sizeof(hs[0].tTKID)); + hs[0].bReserved = 0; + memcpy(hs[0].CDID, &wusb_dev->cdid, sizeof(hs[0].CDID)); + get_random_bytes(&hs[0].nonce, sizeof(hs[0].nonce)); + memset(hs[0].MIC, 0, sizeof(hs[0].MIC)); /* Per WUSB1.0[T7-22] */ + + d_printf(1, dev, "I: sending hs1:\n"); + hs_printk(2, dev, &hs[0]); + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 1, 0, &hs[0], sizeof(hs[0]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake1: request failed: %d\n", result); + goto error_hs1; + } + + /* Handshake 2, from the device -- need to verify fields */ + result = usb_control_msg( + usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_GET_HANDSHAKE, + USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 2, 0, &hs[1], sizeof(hs[1]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake2: request failed: %d\n", result); + goto error_hs2; + } + d_printf(1, dev, "got HS2:\n"); + hs_printk(2, dev, &hs[1]); + + result = -EINVAL; + if (hs[1].bMessageNumber != 2) { + dev_err(dev, "Handshake2 failed: bad message number %u\n", + hs[1].bMessageNumber); + goto error_hs2; + } + if (hs[1].bStatus != 0) { + dev_err(dev, "Handshake2 failed: bad status %u\n", + hs[1].bStatus); + goto error_hs2; + } + if (memcmp(hs[0].tTKID, hs[1].tTKID, sizeof(hs[0].tTKID))) { + dev_err(dev, "Handshake2 failed: TKID mismatch " + "(#1 0x%02x%02x%02x vs #2 0x%02x%02x%02x)\n", + hs[0].tTKID[0], hs[0].tTKID[1], hs[0].tTKID[2], + hs[1].tTKID[0], hs[1].tTKID[1], hs[1].tTKID[2]); + goto error_hs2; + } + if (memcmp(hs[0].CDID, hs[1].CDID, sizeof(hs[0].CDID))) { + dev_err(dev, "Handshake2 failed: CDID mismatch\n"); + goto error_hs2; + } + + /* Setup the CCM nonce */ + memset(&ccm_n.sfn, 0, sizeof(ccm_n.sfn)); /* Per WUSB1.0[6.5.2] */ + memcpy(ccm_n.tkid, &tkid_le, sizeof(ccm_n.tkid)); + ccm_n.src_addr = wusbhc->uwb_rc->uwb_dev.dev_addr; + ccm_n.dest_addr.data[0] = wusb_dev->addr; + ccm_n.dest_addr.data[1] = 0; + + /* Derive the KCK and PTK from CK, the CCM, H and D nonces */ + memcpy(keydvt_in.hnonce, hs[0].nonce, sizeof(keydvt_in.hnonce)); + memcpy(keydvt_in.dnonce, hs[1].nonce, sizeof(keydvt_in.dnonce)); + result = wusb_key_derive(&keydvt_out, ck->data, &ccm_n, &keydvt_in); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot derive keys: %d\n", + result); + goto error_hs2; + } + d_printf(2, dev, "KCK:\n"); + d_dump(2, dev, keydvt_out.kck, sizeof(keydvt_out.kck)); + d_printf(2, dev, "PTK:\n"); + d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk)); + + /* Compute MIC and verify it */ + result = wusb_oob_mic(mic, keydvt_out.kck, &ccm_n, &hs[1]); + if (result < 0) { + dev_err(dev, "Handshake2 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + d_printf(2, dev, "MIC:\n"); + d_dump(2, dev, mic, sizeof(mic)); + if (memcmp(hs[1].MIC, mic, sizeof(hs[1].MIC))) { + dev_err(dev, "Handshake2 failed: MIC mismatch\n"); + goto error_hs2; + } + + /* Send Handshake3 */ + hs[2].bMessageNumber = 3; + hs[2].bStatus = 0; + memcpy(hs[2].tTKID, &tkid_le, sizeof(hs[2].tTKID)); + hs[2].bReserved = 0; + memcpy(hs[2].CDID, &wusb_dev->cdid, sizeof(hs[2].CDID)); + memcpy(hs[2].nonce, hs[0].nonce, sizeof(hs[2].nonce)); + result = wusb_oob_mic(hs[2].MIC, keydvt_out.kck, &ccm_n, &hs[2]); + if (result < 0) { + dev_err(dev, "Handshake3 failed: cannot compute MIC: %d\n", + result); + goto error_hs2; + } + + d_printf(1, dev, "I: sending hs3:\n"); + hs_printk(2, dev, &hs[2]); + + result = usb_control_msg( + usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_HANDSHAKE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + 3, 0, &hs[2], sizeof(hs[2]), 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Handshake3: request failed: %d\n", result); + goto error_hs3; + } + + d_printf(1, dev, "I: turning on encryption on host for device\n"); + d_dump(2, dev, keydvt_out.ptk, sizeof(keydvt_out.ptk)); + result = wusbhc->set_ptk(wusbhc, wusb_dev->port_idx, tkid, + keydvt_out.ptk, sizeof(keydvt_out.ptk)); + if (result < 0) + goto error_wusbhc_set_ptk; + + d_printf(1, dev, "I: setting a GTK\n"); + result = wusb_dev_set_gtk(wusbhc, wusb_dev); + if (result < 0) { + dev_err(dev, "Set GTK for device: request failed: %d\n", + result); + goto error_wusbhc_set_gtk; + } + + /* Update the device's address from unauth to auth */ + if (usb_dev->authenticated == 0) { + d_printf(1, dev, "I: updating addres to auth from non-auth\n"); + result = wusb_dev_update_address(wusbhc, wusb_dev); + if (result < 0) + goto error_dev_update_address; + } + result = 0; + d_printf(1, dev, "I: 4way handshke done, device authenticated\n"); + +error_dev_update_address: +error_wusbhc_set_gtk: +error_wusbhc_set_ptk: +error_hs3: +error_hs2: +error_hs1: + memset(hs, 0, 3*sizeof(hs[0])); + memset(&keydvt_out, 0, sizeof(keydvt_out)); + memset(&keydvt_in, 0, sizeof(keydvt_in)); + memset(&ccm_n, 0, sizeof(ccm_n)); + memset(mic, 0, sizeof(mic)); + if (result < 0) { + /* error path */ + wusb_dev_set_encryption(usb_dev, 0); + } +error_dev_set_encryption: + kfree(hs); +error_kzalloc: + return result; +} + +/* + * Once all connected and authenticated devices have received the new + * GTK, switch the host to using it. + */ +static void wusbhc_gtk_rekey_done_work(struct work_struct *work) +{ + struct wusbhc *wusbhc = container_of(work, struct wusbhc, gtk_rekey_done_work); + size_t key_size = sizeof(wusbhc->gtk.data); + + mutex_lock(&wusbhc->mutex); + + if (--wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); + + mutex_unlock(&wusbhc->mutex); +} + +static void wusbhc_set_gtk_callback(struct urb *urb) +{ + struct wusbhc *wusbhc = urb->context; + + queue_work(wusbd, &wusbhc->gtk_rekey_done_work); +} + +/** + * wusbhc_gtk_rekey - generate and distribute a new GTK + * @wusbhc: the WUSB host controller + * + * Generate a new GTK and distribute it to all connected and + * authenticated devices. When all devices have the new GTK, the host + * starts using it. + * + * This must be called after every device disconnect (see [WUSB] + * section 6.2.11.2). + */ +void wusbhc_gtk_rekey(struct wusbhc *wusbhc) +{ + static const size_t key_size = sizeof(wusbhc->gtk.data); + int p; + + wusbhc_generate_gtk(wusbhc); + + for (p = 0; p < wusbhc->ports_max; p++) { + struct wusb_dev *wusb_dev; + + wusb_dev = wusbhc->port[p].wusb_dev; + if (!wusb_dev || !wusb_dev->usb_dev | !wusb_dev->usb_dev->authenticated) + continue; + + usb_fill_control_urb(wusb_dev->set_gtk_urb, wusb_dev->usb_dev, + usb_sndctrlpipe(wusb_dev->usb_dev, 0), + (void *)wusb_dev->set_gtk_req, + &wusbhc->gtk.descr, wusbhc->gtk.descr.bLength, + wusbhc_set_gtk_callback, wusbhc); + if (usb_submit_urb(wusb_dev->set_gtk_urb, GFP_KERNEL) == 0) + wusbhc->pending_set_gtks++; + } + if (wusbhc->pending_set_gtks == 0) + wusbhc->set_gtk(wusbhc, wusbhc->gtk_tkid, &wusbhc->gtk.descr.bKeyData, key_size); +} -- cgit v1.2.3 From 470cc4150367d369bdc98ee04902b04baa2b2464 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 17 Sep 2008 16:34:26 +0100 Subject: wusb: add the Wireless USB core (build-system) Add the WUSB build system (Kconfig and Kbuild) files. Signed-off-by: Greg Kroah-Hartman Signed-off-by: David Vrabel --- drivers/usb/Kconfig | 2 ++ drivers/usb/Makefile | 2 ++ drivers/usb/wusbcore/Kconfig | 17 +++++++++++++++++ drivers/usb/wusbcore/Makefile | 12 ++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 drivers/usb/wusbcore/Kconfig create mode 100644 drivers/usb/wusbcore/Makefile diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index bcefbddeba5..c23a9857ee6 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -97,6 +97,8 @@ source "drivers/usb/core/Kconfig" source "drivers/usb/mon/Kconfig" +source "drivers/usb/wusbcore/Kconfig" + source "drivers/usb/host/Kconfig" source "drivers/usb/musb/Kconfig" diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index a419c42e880..19ede32f0df 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -19,6 +19,8 @@ obj-$(CONFIG_USB_R8A66597_HCD) += host/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ +obj-$(CONFIG_USB_WUSB) += wusbcore/ + obj-$(CONFIG_USB_ACM) += class/ obj-$(CONFIG_USB_PRINTER) += class/ diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig new file mode 100644 index 00000000000..add077e5c5d --- /dev/null +++ b/drivers/usb/wusbcore/Kconfig @@ -0,0 +1,17 @@ +# +# Wireless USB Core configuration +# +config USB_WUSB + tristate "Enable Wireless USB extensions" + depends on USB + select UWB + select CRYPTO + select CRYPTO_BLKCIPHER + select CRYPTO_CBC + select CRYPTO_MANAGER + select CRYPTO_AES + help + Enable the host-side support for Wireless USB. + + To compile this support select Y (built in). It is safe to + select even if you don't have the hardware. diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile new file mode 100644 index 00000000000..ea1dbfca613 --- /dev/null +++ b/drivers/usb/wusbcore/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_USB_WUSB) += wusbcore.o + +wusbcore-objs := \ + crypto.o \ + devconnect.o \ + dev-sysfs.o \ + mmc.o \ + pal.o \ + rh.o \ + reservation.o \ + security.o \ + wusbhc.o -- cgit v1.2.3 From 870d5395045bfe8e5213525152682c863a10f8d2 Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:27 +0100 Subject: wusb: add the USB wusb-cbaf driver Add a driver for cable based associated of (Wireless) USB devices. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/Makefile | 4 +- drivers/usb/wusbcore/cbaf.c | 620 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 drivers/usb/wusbcore/cbaf.c diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile index ea1dbfca613..6504f42ca36 100644 --- a/drivers/usb/wusbcore/Makefile +++ b/drivers/usb/wusbcore/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_USB_WUSB) += wusbcore.o +obj-$(CONFIG_USB_WUSB) += wusbcore.o wusb-cbaf.o wusbcore-objs := \ crypto.o \ @@ -10,3 +10,5 @@ wusbcore-objs := \ reservation.o \ security.o \ wusbhc.o + +wusb-cbaf-objs := cbaf.o diff --git a/drivers/usb/wusbcore/cbaf.c b/drivers/usb/wusbcore/cbaf.c new file mode 100644 index 00000000000..584eabe274b --- /dev/null +++ b/drivers/usb/wusbcore/cbaf.c @@ -0,0 +1,620 @@ +/* + * Wireless USB - Cable Based Association + * + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * WUSB devices have to be paired (authenticated in WUSB lingo) so + * that they can connect to the system. + * + * One way of pairing is using CBA-Cable Based Authentication, devices + * that can connect via wired or wireless USB. First time you plug + * them with a cable, pairing is done between host and device and + * subsequent times, you can connect wirelessly without having to + * pair. That's the idea. + * + * This driver does nothing Earth shattering. It just provides an + * interface to chat with the wire-connected device so we can get a + * CDID (device ID) that might have been previously associated to a + * CHID (host ID) and to set up a new triplet + * (connection context), with the CK being the secret, or connection + * key. This is the pairing data. + * + * When a device with the CBA capability connects, the probe routine + * just creates a bunch of sysfs files that a user space enumeration + * manager uses to allow it to connect wirelessly to the system or not. + * + * The process goes like this: + * + * 1. device plugs, cbaf is loaded, notifications happen + * + * 2. the connection manager sees a device with CBAF capability (the + * wusb_{host_info,cdid,cc} files are in /sys/device/blah/OURDEVICE). + * + * 3. CM (connection manager) writes the CHID (host ID) and a host + * name into the wusb_host_info file. This gets sent to the device. + * + * 4. CM cats the wusb_cdid file; this asks the device if it has any + * CDID associated to the CHDI we just wrote before. If it does, it + * is printed, along with the device 'friendly name' and the band + * groups the device supports. + * + * 5. CM looks up its database + * + * 5.1 If it has a matching CHID,CDID entry, the device has been + * authorized before (paired). Now we can optionally ask the user + * if he wants to allow the device to connect. Then we generate a + * new CDID and CK, send it to the device and update the database + * (writing to the wusb_cc file so they are uploaded to the device). + * + * 5.2 If the CDID is zero (or we didn't find a matching CDID in our + * database), we assume the device is not known. We ask the user + * if s/he wants to allow the device to be connected wirelessly + * to the system. If nope, nothing else is done (FIXME: maybe + * send a zero CDID to clean up our CHID?). If yes, we generate + * random CDID and CKs (and write them to the wusb_cc file so + * they are uploaded to the device). + * + * 6. device is unplugged + * + * When the device tries to connect wirelessly, it will present it's + * CDID to the WUSB host controller with ID CHID, which will query the + * database. If found, the host will (with a 4way handshake) challenge + * the device to demonstrate it has the CK secret key (from our + * database) without actually exchanging it. Once satisfied, crypto + * keys are derived from the CK, the device is connected and all + * communication is crypted. + * + * + * NOTES ABOUT THE IMPLEMENTATION + * + * The descriptors sent back and forth use this horrible format from + * hell on which each field is actually a field ID, field length and + * then the field itself. How stupid can that get, taking into account + * the structures are defined by the spec?? oh well. + * + * + * FIXME: we don't provide a way to tell the device the pairing failed + * (ie: send a CC_DATA_FAIL). Should add some day. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef D_LOCAL +#define D_LOCAL 6 +#include + +/* An instance of a Cable-Based-Association-Framework device */ +struct cbaf { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + void *buffer; + size_t buffer_size; + + struct wusb_ckhdid chid;/* Host Information */ + char host_name[65]; /* max length: + Assoc Models Suplement 1.0[T4-7] */ + u16 host_band_groups; + + struct wusb_ckhdid cdid;/* Device Information */ + char device_name[65]; /* max length: + Assoc Models Suplement 1.0[T4-7] */ + u16 device_band_groups; + struct wusb_ckhdid ck; /* Connection Key */ +}; + +/* + * Verify that a CBAF USB-interface has what we need + * + * (like we care, we are going to fail the enumeration if not :) + * + * FIXME: ugly function, need to split + */ +static int cbaf_check(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_assoc_info *assoc_info; + struct wusb_cbaf_assoc_request *assoc_request; + size_t assoc_size; + void *itr, *top; + unsigned ar_index; + int ar_rhi_idx = -1, ar_assoc_idx = -1; + + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_INFORMATION, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "cannot get available association types: %d\n", + result); + goto error_get_assoc_types; + } + assoc_info = cbaf->buffer; + if (result < sizeof(*assoc_info)) { + dev_err(dev, "not enough data to decode association info " + "header (%zu vs %zu bytes required)\n", + (size_t)result, sizeof(*assoc_info)); + goto error_bad_header; + } + assoc_size = le16_to_cpu(assoc_info->Length); + if (result < assoc_size) { + dev_err(dev, "not enough data to decode association info " + "(%zu vs %zu bytes required)\n", + (size_t)assoc_size, sizeof(*assoc_info)); + goto error_bad_data; + } + /* + * From now on, we just verify, but won't error out unless we + * don't find the AR_TYPE_WUSB_{RETRIEVE_HOST_INFO,ASSOCIATE} + * types. + */ + ar_index = 0; + itr = cbaf->buffer + sizeof(*assoc_info); + top = cbaf->buffer + assoc_size; + d_printf(1, dev, "Found %u association requests (%zu bytes)\n", + assoc_info->NumAssociationRequests, assoc_size); + while (itr < top) { + u16 ar_type, ar_subtype; + u32 ar_size; + const char *ar_name; + + assoc_request = itr; + if (top - itr < sizeof(*assoc_request)) { + dev_err(dev, "not enough data to decode associaton " + "request (%zu vs %zu bytes needed)\n", + top - itr, sizeof(*assoc_request)); + break; + } + ar_type = le16_to_cpu(assoc_request->AssociationTypeId); + ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId); + ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize); + switch (ar_type) { + case AR_TYPE_WUSB: + /* Verify we have what is mandated by AMS1.0 */ + switch (ar_subtype) { + case AR_TYPE_WUSB_RETRIEVE_HOST_INFO: + ar_name = "retrieve_host_info"; + ar_rhi_idx = ar_index; + break; + case AR_TYPE_WUSB_ASSOCIATE: + /* send assoc data */ + ar_name = "associate"; + ar_assoc_idx = ar_index; + break; + default: + ar_name = "unknown"; + }; + break; + default: + ar_name = "unknown"; + }; + d_printf(1, dev, "association request #%02u: 0x%04x/%04x " + "(%zu bytes): %s\n", + assoc_request->AssociationDataIndex, ar_type, + ar_subtype, (size_t)ar_size, ar_name); + + itr += sizeof(*assoc_request); + ar_index++; + } + if (ar_rhi_idx == -1) { + dev_err(dev, "Missing RETRIEVE_HOST_INFO association " + "request\n"); + goto error_bad_reqs; + } + if (ar_assoc_idx == -1) { + dev_err(dev, "Missing ASSOCIATE association request\n"); + goto error_bad_reqs; + } + return 0; + +error_bad_header: +error_bad_data: +error_bad_reqs: +error_get_assoc_types: + return -EINVAL; +} + +static const struct wusb_cbaf_host_info cbaf_host_info_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB), + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_RETRIEVE_HOST_INFO), + .CHID_hdr = WUSB_AR_CHID, + .LangID_hdr = WUSB_AR_LangID, + .HostFriendlyName_hdr = WUSB_AR_HostFriendlyName, +}; + +/* Send WUSB host information (CHID and name) to a CBAF device */ +static int cbaf_send_host_info(struct cbaf *cbaf) +{ + struct wusb_cbaf_host_info *hi; + size_t hi_size; + + hi = cbaf->buffer; + memset(hi, 0, sizeof(*hi)); + *hi = cbaf_host_info_defaults; + hi->CHID = cbaf->chid; + hi->LangID = 0; /* FIXME: I guess... */ + strncpy(hi->HostFriendlyName, cbaf->host_name, + hi->HostFriendlyName_hdr.len); + hi->HostFriendlyName_hdr.len = + cpu_to_le16(strlen(hi->HostFriendlyName)); + hi_size = sizeof(*hi) + strlen(hi->HostFriendlyName); + return usb_control_msg(cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0101, + cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + hi, hi_size, 1000 /* FIXME: arbitrary */); +} + +/* Show current CHID info we have set from user space */ +static ssize_t cbaf_wusb_host_info_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_chid[WUSB_CKHDID_STRSIZE]; + + ckhdid_printf(pr_chid, sizeof(pr_chid), &cbaf->chid); + return scnprintf(buf, PAGE_SIZE, "CHID: %s\nName: %s\n", + pr_chid, cbaf->host_name); +} + +/* + * Get a host info CHID from user space and send it to the device. + * + * The user can recover a CC from the device associated to that CHID + * by cat'ing wusb_connection_context. + */ +static ssize_t cbaf_wusb_host_info_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%04hx %64s\n", + &cbaf->chid.data[0] , &cbaf->chid.data[1], + &cbaf->chid.data[2] , &cbaf->chid.data[3], + &cbaf->chid.data[4] , &cbaf->chid.data[5], + &cbaf->chid.data[6] , &cbaf->chid.data[7], + &cbaf->chid.data[8] , &cbaf->chid.data[9], + &cbaf->chid.data[10], &cbaf->chid.data[11], + &cbaf->chid.data[12], &cbaf->chid.data[13], + &cbaf->chid.data[14], &cbaf->chid.data[15], + &cbaf->host_band_groups, cbaf->host_name); + if (result != 18) { + dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits, " + "a 16 bit hex band group mask " + "and a host name, got only %d)\n", (int)result); + return -EINVAL; + } + result = cbaf_send_host_info(cbaf); + if (result < 0) + dev_err(dev, "Couldn't send host information to device: %d\n", + (int)result); + else + d_printf(1, dev, "HI sent, wusb_cc can be read now\n"); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_host_info, 0600, cbaf_wusb_host_info_show, + cbaf_wusb_host_info_store); + +static const struct wusb_cbaf_device_info cbaf_device_info_defaults = { + .Length_hdr = WUSB_AR_Length, + .CDID_hdr = WUSB_AR_CDID, + .BandGroups_hdr = WUSB_AR_BandGroups, + .LangID_hdr = WUSB_AR_LangID, + .DeviceFriendlyName_hdr = WUSB_AR_DeviceFriendlyName, +}; + +/* + * Get device's information (CDID) associated to CHID + * + * The device will return it's information (CDID, name, bandgroups) + * associated to the CHID we have set before, or 0 CDID and default + * name and bandgroup if no CHID set or unknown. + */ +static int cbaf_cdid_get(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_device_info *di; + size_t needed, dev_name_size; + + di = cbaf->buffer; + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_REQUEST, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot request device information: %d\n", result); + goto error_req_di; + } + needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length); + if (result < needed) { + dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs " + "%zu bytes needed)\n", (size_t)result, needed); + goto error_bad_di; + } + cbaf->cdid = di->CDID; + dev_name_size = le16_to_cpu(di->DeviceFriendlyName_hdr.len); + dev_name_size = dev_name_size > 65 - 1 ? 65 - 1 : dev_name_size; + memcpy(cbaf->device_name, di->DeviceFriendlyName, dev_name_size); + cbaf->device_name[dev_name_size] = 0; + cbaf->device_band_groups = le16_to_cpu(di->BandGroups); + result = 0; +error_req_di: +error_bad_di: + return result; +} + +/* + * Get device information and print it to sysfs + * + * See cbaf_cdid_get() + */ +static ssize_t cbaf_wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_cdid[WUSB_CKHDID_STRSIZE]; + + result = cbaf_cdid_get(cbaf); + if (result < 0) { + dev_err(dev, "Cannot read device information: %d\n", + (int)result); + goto error_get_di; + } + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); + result = scnprintf(buf, PAGE_SIZE, + "CDID: %s\nName: %s\nBand_groups: 0x%04x\n", + pr_cdid, cbaf->device_name, + cbaf->device_band_groups); +error_get_di: + return result; +} +static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, NULL); + +static const struct wusb_cbaf_cc_data cbaf_cc_data_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationTypeId = cpu_to_le16(AR_TYPE_WUSB), + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .AssociationSubTypeId = cpu_to_le16(AR_TYPE_WUSB_ASSOCIATE), + .Length_hdr = WUSB_AR_Length, + .Length = cpu_to_le32(sizeof(struct wusb_cbaf_cc_data)), + .ConnectionContext_hdr = WUSB_AR_ConnectionContext, + .BandGroups_hdr = WUSB_AR_BandGroups, +}; + +static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = { + .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, + .AssociationSubTypeId_hdr = WUSB_AR_AssociationSubTypeId, + .Length_hdr = WUSB_AR_Length, + .AssociationStatus_hdr = WUSB_AR_AssociationStatus, +}; + +/* + * Send a new CC to the device + * + * So we update the CK and send the whole thing to the device + */ +static int cbaf_cc_upload(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_cc_data *ccd; + char pr_cdid[WUSB_CKHDID_STRSIZE]; + + ccd = cbaf->buffer; + *ccd = cbaf_cc_data_defaults; + ccd->CHID = cbaf->chid; + ccd->CDID = cbaf->cdid; + ccd->CK = cbaf->ck; + ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups); + result = usb_control_msg( + cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_SET_ASSOCIATION_RESPONSE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */); + d_printf(1, dev, "Uploaded CC:\n"); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); + d_printf(1, dev, " CHID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); + d_printf(1, dev, " CDID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CK); + d_printf(1, dev, " CK %s\n", pr_cdid); + d_printf(1, dev, " bandgroups 0x%04x\n", cbaf->host_band_groups); + return result; +} + +/* + * Send a new CC to the device + * + * We take the CDID and CK from user space, the rest from the info we + * set with host_info. + */ +static ssize_t cbaf_wusb_cc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, + "CDID: %02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n" + "CK: %02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx\n", + &cbaf->cdid.data[0] , &cbaf->cdid.data[1], + &cbaf->cdid.data[2] , &cbaf->cdid.data[3], + &cbaf->cdid.data[4] , &cbaf->cdid.data[5], + &cbaf->cdid.data[6] , &cbaf->cdid.data[7], + &cbaf->cdid.data[8] , &cbaf->cdid.data[9], + &cbaf->cdid.data[10], &cbaf->cdid.data[11], + &cbaf->cdid.data[12], &cbaf->cdid.data[13], + &cbaf->cdid.data[14], &cbaf->cdid.data[15], + + &cbaf->ck.data[0] , &cbaf->ck.data[1], + &cbaf->ck.data[2] , &cbaf->ck.data[3], + &cbaf->ck.data[4] , &cbaf->ck.data[5], + &cbaf->ck.data[6] , &cbaf->ck.data[7], + &cbaf->ck.data[8] , &cbaf->ck.data[9], + &cbaf->ck.data[10], &cbaf->ck.data[11], + &cbaf->ck.data[12], &cbaf->ck.data[13], + &cbaf->ck.data[14], &cbaf->ck.data[15]); + if (result != 32) { + dev_err(dev, "Unrecognized CHID/CK (need 32 8-bit " + "hex digits, got only %d)\n", (int)result); + return -EINVAL; + } + result = cbaf_cc_upload(cbaf); + if (result < 0) + dev_err(dev, "Couldn't upload connection context: %d\n", + (int)result); + else + d_printf(1, dev, "Connection context uploaded\n"); + return result < 0 ? result : size; +} +static DEVICE_ATTR(wusb_cc, 0600, NULL, cbaf_wusb_cc_store); + +static struct attribute *cbaf_dev_attrs[] = { + &dev_attr_wusb_host_info.attr, + &dev_attr_wusb_cdid.attr, + &dev_attr_wusb_cc.attr, + NULL, +}; + +static struct attribute_group cbaf_dev_attr_group = { + .name = NULL, /* we want them in the same directory */ + .attrs = cbaf_dev_attrs, +}; + +static int cbaf_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + int result; + struct cbaf *cbaf; + struct device *dev = &iface->dev; + + result = -ENOMEM; + cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL); + if (cbaf == NULL) { + dev_err(dev, "Unable to allocate instance\n"); + goto error_kzalloc; + } + cbaf->buffer = kmalloc(512, GFP_KERNEL); + if (cbaf->buffer == NULL) + goto error_kmalloc_buffer; + cbaf->buffer_size = 512; + cbaf->usb_dev = usb_get_dev(interface_to_usbdev(iface)); + cbaf->usb_iface = usb_get_intf(iface); + result = cbaf_check(cbaf); + if (result < 0) + goto error_check; + result = sysfs_create_group(&dev->kobj, &cbaf_dev_attr_group); + if (result < 0) { + dev_err(dev, "Can't register sysfs attr group: %d\n", result); + goto error_create_group; + } + usb_set_intfdata(iface, cbaf); + d_printf(2, dev, "CBA attached\n"); + return 0; + +error_create_group: +error_check: + kfree(cbaf->buffer); +error_kmalloc_buffer: + kfree(cbaf); +error_kzalloc: + return result; +} + +static void cbaf_disconnect(struct usb_interface *iface) +{ + struct cbaf *cbaf = usb_get_intfdata(iface); + struct device *dev = &iface->dev; + sysfs_remove_group(&dev->kobj, &cbaf_dev_attr_group); + usb_set_intfdata(iface, NULL); + usb_put_intf(iface); + kfree(cbaf->buffer); + /* paranoia: clean up crypto keys */ + memset(cbaf, 0, sizeof(*cbaf)); + kfree(cbaf); + d_printf(1, dev, "CBA detached\n"); +} + +static struct usb_device_id cbaf_id_table[] = { + { USB_INTERFACE_INFO(0xef, 0x03, 0x01), }, + { }, +}; +MODULE_DEVICE_TABLE(usb, cbaf_id_table); + +static struct usb_driver cbaf_driver = { + .name = "wusb-cbaf", + .id_table = cbaf_id_table, + .probe = cbaf_probe, + .disconnect = cbaf_disconnect, +}; + +static int __init cbaf_driver_init(void) +{ + return usb_register(&cbaf_driver); +} +module_init(cbaf_driver_init); + +static void __exit cbaf_driver_exit(void) +{ + usb_deregister(&cbaf_driver); +} +module_exit(cbaf_driver_exit); + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB Cable Based Association"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 7e6133aa42920ea87ad9791a0fb2b95d1a23b8f9 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:28 +0100 Subject: wusb: WHCI host controller driver A driver for Wireless USB host controllers that comply with the Wireless Host Controller Interface (HCI) specification as published by Intel. The latest publically available version of the specification (0.95) is supported (except for isochronous transfers). Build fixes by Randy Dunlap Signed-off-by: David Vrabel --- drivers/usb/host/Kconfig | 12 + drivers/usb/host/Makefile | 2 + drivers/usb/host/whci/Kbuild | 11 + drivers/usb/host/whci/asl.c | 367 ++++++++++++++++++++++++++ drivers/usb/host/whci/hcd.c | 339 ++++++++++++++++++++++++ drivers/usb/host/whci/hw.c | 87 ++++++ drivers/usb/host/whci/init.c | 188 +++++++++++++ drivers/usb/host/whci/int.c | 95 +++++++ drivers/usb/host/whci/pzl.c | 398 ++++++++++++++++++++++++++++ drivers/usb/host/whci/qset.c | 567 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/whci/whcd.h | 197 ++++++++++++++ drivers/usb/host/whci/whci-hc.h | 416 +++++++++++++++++++++++++++++ drivers/usb/host/whci/wusb.c | 241 +++++++++++++++++ 13 files changed, 2920 insertions(+) create mode 100644 drivers/usb/host/whci/Kbuild create mode 100644 drivers/usb/host/whci/asl.c create mode 100644 drivers/usb/host/whci/hcd.c create mode 100644 drivers/usb/host/whci/hw.c create mode 100644 drivers/usb/host/whci/init.c create mode 100644 drivers/usb/host/whci/int.c create mode 100644 drivers/usb/host/whci/pzl.c create mode 100644 drivers/usb/host/whci/qset.c create mode 100644 drivers/usb/host/whci/whcd.h create mode 100644 drivers/usb/host/whci/whci-hc.h create mode 100644 drivers/usb/host/whci/wusb.c diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 228797e54f9..d3ba351398e 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -305,3 +305,15 @@ config SUPERH_ON_CHIP_R8A66597 help This driver enables support for the on-chip R8A66597 in the SH7366 and SH7723 processors. + +config USB_WHCI_HCD + tristate "Wireless USB Host Controller Interface (WHCI) driver" + depends on PCI && USB + select USB_WUSB + select UWB_WHCI + help + A driver for PCI-based Wireless USB Host Controllers that are + compliant with the WHCI specification. + + To compile this driver a module, choose M here: the module + will be called "whci-hcd". diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index f1edda2dcfd..c90cf865ccf 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -8,6 +8,8 @@ endif isp1760-objs := isp1760-hcd.o isp1760-if.o +obj-$(CONFIG_USB_WHCI_HCD) += whci/ + obj-$(CONFIG_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild new file mode 100644 index 00000000000..26a3871ea0f --- /dev/null +++ b/drivers/usb/host/whci/Kbuild @@ -0,0 +1,11 @@ +obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o + +whci-hcd-y := \ + asl.o \ + hcd.o \ + hw.o \ + init.o \ + int.o \ + pzl.o \ + qset.o \ + wusb.o diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c new file mode 100644 index 00000000000..4d7078e5057 --- /dev/null +++ b/drivers/usb/host/whci/asl.c @@ -0,0 +1,367 @@ +/* + * Wireless Host Controller (WHC) asynchronous schedule management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#define D_LOCAL 0 +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 4 +static void dump_asl(struct whc *whc, const char *tag) +{ + struct device *dev = &whc->umc->dev; + struct whc_qset *qset; + + d_printf(4, dev, "ASL %s\n", tag); + + list_for_each_entry(qset, &whc->async_list, list_node) { + dump_qset(qset, dev); + } +} +#else +static inline void dump_asl(struct whc *whc, const char *tag) +{ +} +#endif + + +static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset, + struct whc_qset **next, struct whc_qset **prev) +{ + struct list_head *n, *p; + + BUG_ON(list_empty(&whc->async_list)); + + n = qset->list_node.next; + if (n == &whc->async_list) + n = n->next; + p = qset->list_node.prev; + if (p == &whc->async_list) + p = p->prev; + + *next = container_of(n, struct whc_qset, list_node); + *prev = container_of(p, struct whc_qset, list_node); + +} + +static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->async_list); + qset->in_sw_list = true; +} + +static void asl_qset_insert(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *next, *prev; + + qset_clear(whc, qset); + + /* Link into ASL. */ + qset_get_next_prev(whc, qset, &next, &prev); + whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma); + whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma); + qset->in_hw_list = true; +} + +static void asl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + struct whc_qset *prev, *next; + + qset_get_next_prev(whc, qset, &next, &prev); + + list_move(&qset->list_node, &whc->async_removed_list); + qset->in_sw_list = false; + + /* + * No more qsets in the ASL? The caller must stop the ASL as + * it's no longer valid. + */ + if (list_empty(&whc->async_list)) + return; + + /* Remove from ASL. */ + whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma); + qset->in_hw_list = false; +} + +/** + * process_qset - process any recently inactivated or halted qTDs in a + * qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns any additional WUSBCMD bits for the ASL sync command (i.e., + * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed). + */ +static uint32_t process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + update |= qset_add_qtds(whc, qset); + +done: + /* + * Remove this qset from the ASL if requested, but only if has + * no qTDs. + */ + if (qset->remove && qset->ntds == 0) { + asl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + return update; +} + +void asl_start(struct whc *whc) +{ + struct whc_qset *qset; + + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + + le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR); + + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED, + 1000, "start ASL"); +} + +void asl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_ASYNC_SCHED, 0, + 1000, "stop ASL"); +} + +void asl_update(struct whc *whc, uint32_t wusbcmd) +{ + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->async_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0); +} + +/** + * scan_async_work - scan the ASL for qsets to process. + * + * Process each qset in the ASL in turn and then signal the WHC that + * the ASL has been updated. + * + * Then start, stop or update the asynchronous schedule as required. + */ +void scan_async_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, async_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + + spin_lock_irq(&whc->lock); + + dump_asl(whc, "before processing"); + + /* + * Transerve the software list backwards so new qsets can be + * safely inserted into the ASL without making it non-circular. + */ + list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) { + if (!qset->in_hw_list) { + asl_qset_insert(whc, qset); + update |= WHC_UPDATE_ADDED; + } + + update |= process_qset(whc, qset); + } + + dump_asl(whc, "after processing"); + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_ASYNC_QSET_RM; + asl_update(whc, wusbcmd); + } + + /* + * Now that the ASL is updated, complete the removal of any + * removed qsets. + */ + spin_lock(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) { + qset_remove_complete(whc, qset); + } + + spin_unlock(&whc->lock); +} + +/** + * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL). + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the ASL. + */ +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (!qset->in_sw_list) + asl_qset_insert_begin(whc, qset); + } + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->async_work); + + return 0; +} + +/** + * asl_urb_dequeue - remove an URB (qset) from the async list. + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed from the ASL so the qTDs + * can be removed. + */ +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(whc, std); + else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + asl_qset_remove(whc, qset); + wurb->status = status; + wurb->is_async = true; + queue_work(whc->workqueue, &wurb->dequeue_work); + +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * asl_qset_delete - delete a qset from the ASL + */ +void asl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->async_work); + qset_delete(whc, qset); +} + +/** + * asl_init - initialize the asynchronous schedule list + * + * A dummy qset with no qTDs is added to the ASL to simplify removing + * qsets (no need to stop the ASL when the last qset is removed). + */ +int asl_init(struct whc *whc) +{ + struct whc_qset *qset; + + qset = qset_alloc(whc, GFP_KERNEL); + if (qset == NULL) + return -ENOMEM; + + asl_qset_insert_begin(whc, qset); + asl_qset_insert(whc, qset); + + return 0; +} + +/** + * asl_clean_up - free ASL resources + * + * The ASL is stopped and empty except for the dummy qset. + */ +void asl_clean_up(struct whc *whc) +{ + struct whc_qset *qset; + + if (!list_empty(&whc->async_list)) { + qset = list_first_entry(&whc->async_list, struct whc_qset, list_node); + list_del(&qset->list_node); + qset_free(whc, qset); + } +} diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c new file mode 100644 index 00000000000..ef3ad4dca94 --- /dev/null +++ b/drivers/usb/host/whci/hcd.c @@ -0,0 +1,339 @@ +/* + * Wireless Host Controller (WHC) driver. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * One time initialization. + * + * Nothing to do here. + */ +static int whc_reset(struct usb_hcd *usb_hcd) +{ + return 0; +} + +/* + * Start the wireless host controller. + * + * Start device notification. + * + * Put hc into run state, set DNTS parameters. + */ +static int whc_start(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u8 bcid; + int ret; + + mutex_lock(&wusbhc->mutex); + + le_writel(WUSBINTR_GEN_CMD_DONE + | WUSBINTR_HOST_ERR + | WUSBINTR_ASYNC_SCHED_SYNCED + | WUSBINTR_DNTS_INT + | WUSBINTR_ERR_INT + | WUSBINTR_INT, + whc->base + WUSBINTR); + + /* set cluster ID */ + bcid = wusb_cluster_id_get(); + ret = whc_set_cluster_id(whc, bcid); + if (ret < 0) + goto out; + wusbhc->cluster_id = bcid; + + /* start HC */ + whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN); + + usb_hcd->uses_new_polling = 1; + usb_hcd->poll_rh = 1; + usb_hcd->state = HC_STATE_RUNNING; + +out: + mutex_unlock(&wusbhc->mutex); + return ret; +} + + +/* + * Stop the wireless host controller. + * + * Stop device notification. + * + * Wait for pending transfer to stop? Put hc into stop state? + */ +static void whc_stop(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + mutex_lock(&wusbhc->mutex); + + wusbhc_stop(wusbhc); + + /* stop HC */ + le_writel(0, whc->base + WUSBINTR); + whc_write_wusbcmd(whc, WUSBCMD_RUN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_HCHALTED, WUSBSTS_HCHALTED, + 100, "HC to halt"); + + wusb_cluster_id_put(wusbhc->cluster_id); + + mutex_unlock(&wusbhc->mutex); +} + +static int whc_get_frame_number(struct usb_hcd *usb_hcd) +{ + /* Frame numbers are not applicable to WUSB. */ + return -ENOSYS; +} + + +/* + * Queue an URB to the ASL or PZL + */ +static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_enqueue(whc, urb, mem_flags); + break; + case PIPE_ISOCHRONOUS: + dev_err(&whc->umc->dev, "isochronous transfers unsupported\n"); + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_enqueue(whc, urb, mem_flags); + break; + }; + + return ret; +} + +/* + * Remove a queued URB from the ASL or PZL. + */ +static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_INTERRUPT: + ret = pzl_urb_dequeue(whc, urb, status); + break; + case PIPE_ISOCHRONOUS: + ret = -ENOTSUPP; + break; + case PIPE_CONTROL: + case PIPE_BULK: + default: + ret = asl_urb_dequeue(whc, urb, status); + break; + }; + + return ret; +} + +/* + * Wait for all URBs to the endpoint to be completed, then delete the + * qset. + */ +static void whc_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + struct whc_qset *qset; + + qset = ep->hcpriv; + if (qset) { + ep->hcpriv = NULL; + if (usb_endpoint_xfer_bulk(&ep->desc) + || usb_endpoint_xfer_control(&ep->desc)) + asl_qset_delete(whc, qset); + else + pzl_qset_delete(whc, qset); + } +} + +static struct hc_driver whc_hc_driver = { + .description = "whci-hcd", + .product_desc = "Wireless host controller", + .hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd), + .irq = whc_int_handler, + .flags = HCD_USB2, + + .reset = whc_reset, + .start = whc_start, + .stop = whc_stop, + .get_frame_number = whc_get_frame_number, + .urb_enqueue = whc_urb_enqueue, + .urb_dequeue = whc_urb_dequeue, + .endpoint_disable = whc_endpoint_disable, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int whc_probe(struct umc_dev *umc) +{ + int ret = -ENOMEM; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc = NULL; + struct whc *whc = NULL; + struct device *dev = &umc->dev; + + usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to create hcd\n"); + goto error; + } + + usb_hcd->wireless = 1; + + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + whc = wusbhc_to_whc(wusbhc); + whc->umc = umc; + + ret = whc_init(whc); + if (ret) + goto error; + + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent); + if (!wusbhc->uwb_rc) { + ret = -ENODEV; + dev_err(dev, "cannot get radio controller\n"); + goto error; + } + + if (whc->n_devices > USB_MAXCHILDREN) { + dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n", + whc->n_devices); + wusbhc->ports_max = USB_MAXCHILDREN; + } else + wusbhc->ports_max = whc->n_devices; + wusbhc->mmcies_max = whc->n_mmc_ies; + wusbhc->start = whc_wusbhc_start; + wusbhc->stop = whc_wusbhc_stop; + wusbhc->mmcie_add = whc_mmcie_add; + wusbhc->mmcie_rm = whc_mmcie_rm; + wusbhc->dev_info_set = whc_dev_info_set; + wusbhc->bwa_set = whc_bwa_set; + wusbhc->set_num_dnts = whc_set_num_dnts; + wusbhc->set_ptk = whc_set_ptk; + wusbhc->set_gtk = whc_set_gtk; + + ret = wusbhc_create(wusbhc); + if (ret) + goto error_wusbhc_create; + + ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED); + if (ret) { + dev_err(dev, "cannot add HCD: %d\n", ret); + goto error_usb_add_hcd; + } + + ret = wusbhc_b_create(wusbhc); + if (ret) { + dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret); + goto error_wusbhc_b_create; + } + + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_usb_add_hcd: + wusbhc_destroy(wusbhc); +error_wusbhc_create: + uwb_rc_put(wusbhc->uwb_rc); +error: + whc_clean_up(whc); + if (usb_hcd) + usb_put_hcd(usb_hcd); + return ret; +} + + +static void whc_remove(struct umc_dev *umc) +{ + struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev); + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (usb_hcd) { + wusbhc_b_destroy(wusbhc); + usb_remove_hcd(usb_hcd); + wusbhc_destroy(wusbhc); + uwb_rc_put(wusbhc->uwb_rc); + whc_clean_up(whc); + usb_put_hcd(usb_hcd); + } +} + +static struct umc_driver whci_hc_driver = { + .name = "whci-hcd", + .cap_id = UMC_CAP_ID_WHCI_WUSB_HC, + .probe = whc_probe, + .remove = whc_remove, +}; + +static int __init whci_hc_driver_init(void) +{ + return umc_driver_register(&whci_hc_driver); +} +module_init(whci_hc_driver_init); + +static void __exit whci_hc_driver_exit(void) +{ + umc_driver_unregister(&whci_hc_driver); +} +module_exit(whci_hc_driver_exit); + +/* PCI device ID's that we handle (so it gets loaded) */ +static struct pci_device_id whci_hcd_id_table[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, + { /* empty last entry */ } +}; +MODULE_DEVICE_TABLE(pci, whci_hcd_id_table); + +MODULE_DESCRIPTION("WHCI Wireless USB host controller driver"); +MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c new file mode 100644 index 00000000000..ac86e59c122 --- /dev/null +++ b/drivers/usb/host/whci/hw.c @@ -0,0 +1,87 @@ +/* + * Wireless Host Controller (WHC) hardware access helpers. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val) +{ + unsigned long flags; + u32 cmd; + + spin_lock_irqsave(&whc->lock, flags); + + cmd = le_readl(whc->base + WUSBCMD); + cmd = (cmd & ~mask) | val; + le_writel(cmd, whc->base + WUSBCMD); + + spin_unlock_irqrestore(&whc->lock, flags); +} + +/** + * whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register + * @whc: the WHCI HC + * @cmd: command to start. + * @params: parameters for the command (the WUSBGENCMDPARAMS register value). + * @addr: pointer to any data for the command (may be NULL). + * @len: length of the data (if any). + */ +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len) +{ + unsigned long flags; + dma_addr_t dma_addr; + int t; + + mutex_lock(&whc->mutex); + + /* Wait for previous command to complete. */ + t = wait_event_timeout(whc->cmd_wq, + (le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0, + WHC_GENCMD_TIMEOUT_MS); + if (t == 0) { + dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n", + le_readl(whc->base + WUSBGENCMDSTS), + le_readl(whc->base + WUSBGENCMDPARAMS)); + return -ETIMEDOUT; + } + + if (addr) { + memcpy(whc->gen_cmd_buf, addr, len); + dma_addr = whc->gen_cmd_buf_dma; + } else + dma_addr = 0; + + /* Poke registers to start cmd. */ + spin_lock_irqsave(&whc->lock, flags); + + le_writel(params, whc->base + WUSBGENCMDPARAMS); + le_writeq(dma_addr, whc->base + WUSBGENADDR); + + le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd, + whc->base + WUSBGENCMDSTS); + + spin_unlock_irqrestore(&whc->lock, flags); + + mutex_unlock(&whc->mutex); + + return 0; +} diff --git a/drivers/usb/host/whci/init.c b/drivers/usb/host/whci/init.c new file mode 100644 index 00000000000..34a783cb013 --- /dev/null +++ b/drivers/usb/host/whci/init.c @@ -0,0 +1,188 @@ +/* + * Wireless Host Controller (WHC) initialization. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +/* + * Reset the host controller. + */ +static void whc_hw_reset(struct whc *whc) +{ + le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD); + whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0, + 100, "reset"); +} + +static void whc_hw_init_di_buf(struct whc *whc) +{ + int d; + + /* Disable all entries in the Device Information buffer. */ + for (d = 0; d < whc->n_devices; d++) + whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE; + + le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR); +} + +static void whc_hw_init_dn_buf(struct whc *whc) +{ + /* Clear the Device Notification buffer to ensure the V (valid) + * bits are clear. */ + memset(whc->dn_buf, 0, 4096); + + le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR); +} + +int whc_init(struct whc *whc) +{ + u32 whcsparams; + int ret, i; + resource_size_t start, len; + + spin_lock_init(&whc->lock); + mutex_init(&whc->mutex); + init_waitqueue_head(&whc->cmd_wq); + init_waitqueue_head(&whc->async_list_wq); + init_waitqueue_head(&whc->periodic_list_wq); + whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev)); + if (whc->workqueue == NULL) { + ret = -ENOMEM; + goto error; + } + INIT_WORK(&whc->dn_work, whc_dn_work); + + INIT_WORK(&whc->async_work, scan_async_work); + INIT_LIST_HEAD(&whc->async_list); + INIT_LIST_HEAD(&whc->async_removed_list); + + INIT_WORK(&whc->periodic_work, scan_periodic_work); + for (i = 0; i < 5; i++) + INIT_LIST_HEAD(&whc->periodic_list[i]); + INIT_LIST_HEAD(&whc->periodic_removed_list); + + /* Map HC registers. */ + start = whc->umc->resource.start; + len = whc->umc->resource.end - start + 1; + if (!request_mem_region(start, len, "whci-hc")) { + dev_err(&whc->umc->dev, "can't request HC region\n"); + ret = -EBUSY; + goto error; + } + whc->base_phys = start; + whc->base = ioremap(start, len); + if (!whc->base) { + dev_err(&whc->umc->dev, "ioremap\n"); + ret = -ENOMEM; + goto error; + } + + whc_hw_reset(whc); + + /* Read maximum number of devices, keys and MMC IEs. */ + whcsparams = le_readl(whc->base + WHCSPARAMS); + whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams); + whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams); + whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams); + + dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n", + whc->n_devices, whc->n_keys, whc->n_mmc_ies); + + whc->qset_pool = dma_pool_create("qset", &whc->umc->dev, + sizeof(struct whc_qset), 64, 0); + if (whc->qset_pool == NULL) { + ret = -ENOMEM; + goto error; + } + + ret = asl_init(whc); + if (ret < 0) + goto error; + ret = pzl_init(whc); + if (ret < 0) + goto error; + + /* Allocate and initialize a buffer for generic commands, the + Device Information buffer, and the Device Notification + buffer. */ + + whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + &whc->gen_cmd_buf_dma, GFP_KERNEL); + if (whc->gen_cmd_buf == NULL) { + ret = -ENOMEM; + goto error; + } + + whc->dn_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + &whc->dn_buf_dma, GFP_KERNEL); + if (!whc->dn_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_dn_buf(whc); + + whc->di_buf = dma_alloc_coherent(&whc->umc->dev, + sizeof(struct di_buf_entry) * whc->n_devices, + &whc->di_buf_dma, GFP_KERNEL); + if (!whc->di_buf) { + ret = -ENOMEM; + goto error; + } + whc_hw_init_di_buf(whc); + + return 0; + +error: + whc_clean_up(whc); + return ret; +} + +void whc_clean_up(struct whc *whc) +{ + resource_size_t len; + + if (whc->di_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices, + whc->di_buf, whc->di_buf_dma); + if (whc->dn_buf) + dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES, + whc->dn_buf, whc->dn_buf_dma); + if (whc->gen_cmd_buf) + dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN, + whc->gen_cmd_buf, whc->gen_cmd_buf_dma); + + pzl_clean_up(whc); + asl_clean_up(whc); + + if (whc->qset_pool) + dma_pool_destroy(whc->qset_pool); + + len = whc->umc->resource.end - whc->umc->resource.start + 1; + if (whc->base) + iounmap(whc->base); + if (whc->base_phys) + release_mem_region(whc->base_phys, len); + + if (whc->workqueue) + destroy_workqueue(whc->workqueue); +} diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c new file mode 100644 index 00000000000..fce01174aa9 --- /dev/null +++ b/drivers/usb/host/whci/int.c @@ -0,0 +1,95 @@ +/* + * Wireless Host Controller (WHC) interrupt handling. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +static void transfer_done(struct whc *whc) +{ + queue_work(whc->workqueue, &whc->async_work); + queue_work(whc->workqueue, &whc->periodic_work); +} + +irqreturn_t whc_int_handler(struct usb_hcd *hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd); + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 sts; + + sts = le_readl(whc->base + WUSBSTS); + if (!(sts & WUSBSTS_INT_MASK)) + return IRQ_NONE; + le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS); + + if (sts & WUSBSTS_GEN_CMD_DONE) + wake_up(&whc->cmd_wq); + + if (sts & WUSBSTS_HOST_ERR) + dev_err(&whc->umc->dev, "FIXME: host system error\n"); + + if (sts & WUSBSTS_ASYNC_SCHED_SYNCED) + wake_up(&whc->async_list_wq); + + if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED) + wake_up(&whc->periodic_list_wq); + + if (sts & WUSBSTS_DNTS_INT) + queue_work(whc->workqueue, &whc->dn_work); + + /* + * A transfer completed (see [WHCI] section 4.7.1.2 for when + * this occurs). + */ + if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT)) + transfer_done(whc); + + return IRQ_HANDLED; +} + +static int process_dn_buf(struct whc *whc) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct dn_buf_entry *dn; + int processed = 0; + + for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) { + if (dn->status & WHC_DN_STATUS_VALID) { + wusbhc_handle_dn(wusbhc, dn->src_addr, + (struct wusb_dn_hdr *)dn->dn_data, + dn->msg_size); + dn->status &= ~WHC_DN_STATUS_VALID; + processed++; + } + } + return processed; +} + +void whc_dn_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, dn_work); + int processed; + + do { + processed = process_dn_buf(whc); + } while (processed); +} diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c new file mode 100644 index 00000000000..8d62df0c330 --- /dev/null +++ b/drivers/usb/host/whci/pzl.c @@ -0,0 +1,398 @@ +/* + * Wireless Host Controller (WHC) periodic schedule management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#define D_LOCAL 0 +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 4 +static void dump_pzl(struct whc *whc, const char *tag) +{ + struct device *dev = &whc->umc->dev; + struct whc_qset *qset; + int period = 0; + + d_printf(4, dev, "PZL %s\n", tag); + + for (period = 0; period < 5; period++) { + d_printf(4, dev, "Period %d\n", period); + list_for_each_entry(qset, &whc->periodic_list[period], list_node) { + dump_qset(qset, dev); + } + } +} +#else +static inline void dump_pzl(struct whc *whc, const char *tag) +{ +} +#endif + +static void update_pzl_pointers(struct whc *whc, int period, u64 addr) +{ + switch (period) { + case 0: + whc_qset_set_link_ptr(&whc->pz_list[0], addr); + whc_qset_set_link_ptr(&whc->pz_list[2], addr); + whc_qset_set_link_ptr(&whc->pz_list[4], addr); + whc_qset_set_link_ptr(&whc->pz_list[6], addr); + whc_qset_set_link_ptr(&whc->pz_list[8], addr); + whc_qset_set_link_ptr(&whc->pz_list[10], addr); + whc_qset_set_link_ptr(&whc->pz_list[12], addr); + whc_qset_set_link_ptr(&whc->pz_list[14], addr); + break; + case 1: + whc_qset_set_link_ptr(&whc->pz_list[1], addr); + whc_qset_set_link_ptr(&whc->pz_list[5], addr); + whc_qset_set_link_ptr(&whc->pz_list[9], addr); + whc_qset_set_link_ptr(&whc->pz_list[13], addr); + break; + case 2: + whc_qset_set_link_ptr(&whc->pz_list[3], addr); + whc_qset_set_link_ptr(&whc->pz_list[11], addr); + break; + case 3: + whc_qset_set_link_ptr(&whc->pz_list[7], addr); + break; + case 4: + whc_qset_set_link_ptr(&whc->pz_list[15], addr); + break; + } +} + +/* + * Return the 'period' to use for this qset. The minimum interval for + * the endpoint is used so whatever urbs are submitted the device is + * polled often enough. + */ +static int qset_get_period(struct whc *whc, struct whc_qset *qset) +{ + uint8_t bInterval = qset->ep->desc.bInterval; + + if (bInterval < 6) + bInterval = 6; + if (bInterval > 10) + bInterval = 10; + return bInterval - 6; +} + +static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset) +{ + int period; + + period = qset_get_period(whc, qset); + + qset_clear(whc, qset); + list_move(&qset->list_node, &whc->periodic_list[period]); + qset->in_sw_list = true; +} + +static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset) +{ + list_move(&qset->list_node, &whc->periodic_removed_list); + qset->in_hw_list = false; + qset->in_sw_list = false; +} + +/** + * pzl_process_qset - process any recently inactivated or halted qTDs + * in a qset. + * + * After inactive qTDs are removed, new qTDs can be added if the + * urb queue still contains URBs. + * + * Returns the schedule updates required. + */ +static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset) +{ + enum whc_update update = 0; + uint32_t status = 0; + + while (qset->ntds) { + struct whc_qtd *td; + int t; + + t = qset->td_start; + td = &qset->qtd[qset->td_start]; + status = le32_to_cpu(td->status); + + /* + * Nothing to do with a still active qTD. + */ + if (status & QTD_STS_ACTIVE) + break; + + if (status & QTD_STS_HALTED) { + /* Ug, an error. */ + process_halted_qtd(whc, qset, td); + goto done; + } + + /* Mmm, a completed qTD. */ + process_inactive_qtd(whc, qset, td); + } + + update |= qset_add_qtds(whc, qset); + +done: + /* + * If there are no qTDs in this qset, remove it from the PZL. + */ + if (qset->remove && qset->ntds == 0) { + pzl_qset_remove(whc, qset); + update |= WHC_UPDATE_REMOVED; + } + + return update; +} + +/** + * pzl_start - start the periodic schedule + * @whc: the WHCI host controller + * + * The PZL must be valid (e.g., all entries in the list should have + * the T bit set). + */ +void pzl_start(struct whc *whc) +{ + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED, + 1000, "start PZL"); +} + +/** + * pzl_stop - stop the periodic schedule + * @whc: the WHCI host controller + */ +void pzl_stop(struct whc *whc) +{ + whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0); + whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS, + WUSBSTS_PERIODIC_SCHED, 0, + 1000, "stop PZL"); +} + +void pzl_update(struct whc *whc, uint32_t wusbcmd) +{ + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + wait_event(whc->periodic_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0); +} + +static void update_pzl_hw_view(struct whc *whc) +{ + struct whc_qset *qset, *t; + int period; + u64 tmp_qh = 0; + + for (period = 0; period < 5; period++) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + whc_qset_set_link_ptr(&qset->qh.link, tmp_qh); + tmp_qh = qset->qset_dma; + qset->in_hw_list = true; + } + update_pzl_pointers(whc, period, tmp_qh); + } +} + +/** + * scan_periodic_work - scan the PZL for qsets to process. + * + * Process each qset in the PZL in turn and then signal the WHC that + * the PZL has been updated. + * + * Then start, stop or update the periodic schedule as required. + */ +void scan_periodic_work(struct work_struct *work) +{ + struct whc *whc = container_of(work, struct whc, periodic_work); + struct whc_qset *qset, *t; + enum whc_update update = 0; + int period; + + spin_lock_irq(&whc->lock); + + dump_pzl(whc, "before processing"); + + for (period = 4; period >= 0; period--) { + list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { + if (!qset->in_hw_list) + update |= WHC_UPDATE_ADDED; + update |= pzl_process_qset(whc, qset); + } + } + + if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED)) + update_pzl_hw_view(whc); + + dump_pzl(whc, "after processing"); + + spin_unlock_irq(&whc->lock); + + if (update) { + uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB; + if (update & WHC_UPDATE_REMOVED) + wusbcmd |= WUSBCMD_PERIODIC_QSET_RM; + pzl_update(whc, wusbcmd); + } + + /* + * Now that the PZL is updated, complete the removal of any + * removed qsets. + */ + spin_lock(&whc->lock); + + list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) { + qset_remove_complete(whc, qset); + } + + spin_unlock(&whc->lock); +} + +/** + * pzl_urb_enqueue - queue an URB onto the periodic list (PZL) + * @whc: the WHCI host controller + * @urb: the URB to enqueue + * @mem_flags: flags for any memory allocations + * + * The qset for the endpoint is obtained and the urb queued on to it. + * + * Work is scheduled to update the hardware's view of the PZL. + */ +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags) +{ + struct whc_qset *qset; + int err; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + qset = get_qset(whc, urb, GFP_ATOMIC); + if (qset == NULL) + err = -ENOMEM; + else + err = qset_add_urb(whc, qset, urb, GFP_ATOMIC); + if (!err) { + usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb); + if (!qset->in_sw_list) + qset_insert_in_sw_list(whc, qset); + } + + spin_unlock_irqrestore(&whc->lock, flags); + + if (!err) + queue_work(whc->workqueue, &whc->periodic_work); + + return 0; +} + +/** + * pzl_urb_dequeue - remove an URB (qset) from the periodic list + * @whc: the WHCI host controller + * @urb: the URB to dequeue + * @status: the current status of the URB + * + * URBs that do yet have qTDs can simply be removed from the software + * queue, otherwise the qset must be removed so the qTDs can be safely + * removed. + */ +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status) +{ + struct whc_urb *wurb = urb->hcpriv; + struct whc_qset *qset = wurb->qset; + struct whc_std *std, *t; + int ret; + unsigned long flags; + + spin_lock_irqsave(&whc->lock, flags); + + ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status); + if (ret < 0) + goto out; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(whc, std); + else + std->qtd = NULL; /* so this std is re-added when the qset is */ + } + + pzl_qset_remove(whc, qset); + wurb->status = status; + wurb->is_async = false; + queue_work(whc->workqueue, &wurb->dequeue_work); + +out: + spin_unlock_irqrestore(&whc->lock, flags); + + return ret; +} + +/** + * pzl_qset_delete - delete a qset from the PZL + */ +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset) +{ + qset->remove = 1; + queue_work(whc->workqueue, &whc->periodic_work); + qset_delete(whc, qset); +} + + +/** + * pzl_init - initialize the periodic zone list + * @whc: the WHCI host controller + */ +int pzl_init(struct whc *whc) +{ + int i; + + whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16, + &whc->pz_list_dma, GFP_KERNEL); + if (whc->pz_list == NULL) + return -ENOMEM; + + /* Set T bit on all elements in PZL. */ + for (i = 0; i < 16; i++) + whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T); + + le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE); + + return 0; +} + +/** + * pzl_clean_up - free PZL resources + * @whc: the WHCI host controller + * + * The PZL is stopped and empty. + */ +void pzl_clean_up(struct whc *whc) +{ + if (whc->pz_list) + dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list, + whc->pz_list_dma); +} diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c new file mode 100644 index 00000000000..0420037d2e1 --- /dev/null +++ b/drivers/usb/host/whci/qset.c @@ -0,0 +1,567 @@ +/* + * Wireless Host Controller (WHC) qset management. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +void dump_qset(struct whc_qset *qset, struct device *dev) +{ + struct whc_std *std; + struct urb *urb = NULL; + int i; + + dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma); + dev_dbg(dev, " -> %08x\n", (u32)qset->qh.link); + dev_dbg(dev, " info: %08x %08x %08x\n", + qset->qh.info1, qset->qh.info2, qset->qh.info3); + dev_dbg(dev, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count); + dev_dbg(dev, " TD: sts: %08x opts: %08x\n", + qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options); + + for (i = 0; i < WHCI_QSET_TD_MAX; i++) { + dev_dbg(dev, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n", + i == qset->td_start ? 'S' : ' ', + i == qset->td_end ? 'E' : ' ', + i, qset->qtd[i].status, qset->qtd[i].options, + (u32)qset->qtd[i].page_list_ptr); + } + dev_dbg(dev, " ntds: %d\n", qset->ntds); + list_for_each_entry(std, &qset->stds, list_node) { + if (urb != std->urb) { + urb = std->urb; + dev_dbg(dev, " urb %p transferred: %d bytes\n", urb, + urb->actual_length); + } + if (std->qtd) + dev_dbg(dev, " sTD[%td]: %zu bytes @ %08x\n", + std->qtd - &qset->qtd[0], + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + else + dev_dbg(dev, " sTD[-]: %zd bytes @ %08x\n", + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + } +} + +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags) +{ + struct whc_qset *qset; + dma_addr_t dma; + + qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma); + if (qset == NULL) + return NULL; + memset(qset, 0, sizeof(struct whc_qset)); + + qset->qset_dma = dma; + qset->whc = whc; + + INIT_LIST_HEAD(&qset->list_node); + INIT_LIST_HEAD(&qset->stds); + + return qset; +} + +/** + * qset_fill_qh - fill the static endpoint state in a qset's QHead + * @qset: the qset whose QH needs initializing with static endpoint + * state + * @urb: an urb for a transfer to this endpoint + */ +static void qset_fill_qh(struct whc_qset *qset, struct urb *urb) +{ + struct usb_device *usb_dev = urb->dev; + struct usb_wireless_ep_comp_descriptor *epcd; + bool is_out; + + is_out = usb_pipeout(urb->pipe); + + epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra; + + if (epcd) { + qset->max_seq = epcd->bMaxSequence; + qset->max_burst = epcd->bMaxBurst; + } else { + qset->max_seq = 2; + qset->max_burst = 1; + } + + qset->qh.info1 = cpu_to_le32( + QH_INFO1_EP(usb_pipeendpoint(urb->pipe)) + | (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN) + | usb_pipe_to_qh_type(urb->pipe) + | QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum)) + | QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out)) + ); + qset->qh.info2 = cpu_to_le32( + QH_INFO2_BURST(qset->max_burst) + | QH_INFO2_DBP(0) + | QH_INFO2_MAX_COUNT(3) + | QH_INFO2_MAX_RETRY(3) + | QH_INFO2_MAX_SEQ(qset->max_seq - 1) + ); + /* FIXME: where can we obtain these Tx parameters from? Why + * doesn't the chip know what Tx power to use? It knows the Rx + * strength and can presumably guess the Tx power required + * from that? */ + qset->qh.info3 = cpu_to_le32( + QH_INFO3_TX_RATE_53_3 + | QH_INFO3_TX_PWR(0) /* 0 == max power */ + ); +} + +/** + * qset_clear - clear fields in a qset so it may be reinserted into a + * schedule + */ +void qset_clear(struct whc *whc, struct whc_qset *qset) +{ + qset->td_start = qset->td_end = qset->ntds = 0; + qset->remove = 0; + + qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T); + qset->qh.status = cpu_to_le16(QH_STATUS_ICUR(qset->td_start)); + qset->qh.err_count = 0; + qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1); + qset->qh.scratch[0] = 0; + qset->qh.scratch[1] = 0; + qset->qh.scratch[2] = 0; + + memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay)); + + init_completion(&qset->remove_complete); +} + +/** + * get_qset - get the qset for an async endpoint + * + * A new qset is created if one does not already exist. + */ +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_qset *qset; + + qset = urb->ep->hcpriv; + if (qset == NULL) { + qset = qset_alloc(whc, mem_flags); + if (qset == NULL) + return NULL; + + qset->ep = urb->ep; + urb->ep->hcpriv = qset; + qset_fill_qh(qset, urb); + } + return qset; +} + +void qset_remove_complete(struct whc *whc, struct whc_qset *qset) +{ + list_del_init(&qset->list_node); + complete(&qset->remove_complete); +} + +/** + * qset_add_qtds - add qTDs for an URB to a qset + * + * Returns true if the list (ASL/PZL) must be updated because (for a + * WHCI 0.95 controller) an activated qTD was pointed to be iCur. + */ +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset) +{ + struct whc_std *std; + enum whc_update update = 0; + + list_for_each_entry(std, &qset->stds, list_node) { + struct whc_qtd *qtd; + uint32_t status; + + if (qset->ntds >= WHCI_QSET_TD_MAX + || (qset->pause_after_urb && std->urb != qset->pause_after_urb)) + break; + + if (std->qtd) + continue; /* already has a qTD */ + + qtd = std->qtd = &qset->qtd[qset->td_end]; + + /* Fill in setup bytes for control transfers. */ + if (usb_pipecontrol(std->urb->pipe)) + memcpy(qtd->setup, std->urb->setup_packet, 8); + + status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len); + + if (whc_std_last(std) && usb_pipeout(std->urb->pipe)) + status |= QTD_STS_LAST_PKT; + + /* + * For an IN transfer the iAlt field should be set so + * the h/w will automatically advance to the next + * transfer. However, if there are 8 or more TDs + * remaining in this transfer then iAlt cannot be set + * as it could point to somewhere in this transfer. + */ + if (std->ntds_remaining < WHCI_QSET_TD_MAX) { + int ialt; + ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX; + status |= QTD_STS_IALT(ialt); + } else if (usb_pipein(std->urb->pipe)) + qset->pause_after_urb = std->urb; + + if (std->num_pointers) + qtd->options = cpu_to_le32(QTD_OPT_IOC); + else + qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL); + qtd->page_list_ptr = cpu_to_le64(std->dma_addr); + + qtd->status = cpu_to_le32(status); + + if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end) + update = WHC_UPDATE_UPDATED; + + if (++qset->td_end >= WHCI_QSET_TD_MAX) + qset->td_end = 0; + qset->ntds++; + } + + return update; +} + +/** + * qset_remove_qtd - remove the first qTD from a qset. + * + * The qTD might be still active (if it's part of a IN URB that + * resulted in a short read) so ensure it's deactivated. + */ +static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset) +{ + qset->qtd[qset->td_start].status = 0; + + if (++qset->td_start >= WHCI_QSET_TD_MAX) + qset->td_start = 0; + qset->ntds--; +} + +/** + * qset_free_std - remove an sTD and free it. + * @whc: the WHCI host controller + * @std: the sTD to remove and free. + */ +void qset_free_std(struct whc *whc, struct whc_std *std) +{ + list_del(&std->list_node); + if (std->num_pointers) { + dma_unmap_single(whc->wusbhc.dev, std->dma_addr, + std->num_pointers * sizeof(struct whc_page_list_entry), + DMA_TO_DEVICE); + kfree(std->pl_virt); + } + + kfree(std); +} + +/** + * qset_remove_qtds - remove an URB's qTDs (and sTDs). + */ +static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset, + struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb != urb) + break; + if (std->qtd != NULL) + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + } +} + +/** + * qset_free_stds - free any remaining sTDs for an URB. + */ +static void qset_free_stds(struct whc_qset *qset, struct urb *urb) +{ + struct whc_std *std, *t; + + list_for_each_entry_safe(std, t, &qset->stds, list_node) { + if (std->urb == urb) + qset_free_std(qset->whc, std); + } +} + +static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags) +{ + dma_addr_t dma_addr = std->dma_addr; + dma_addr_t sp, ep; + size_t std_len = std->len; + size_t pl_len; + int p; + + sp = ALIGN(dma_addr, WHCI_PAGE_SIZE); + ep = dma_addr + std_len; + std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE); + + pl_len = std->num_pointers * sizeof(struct whc_page_list_entry); + std->pl_virt = kmalloc(pl_len, mem_flags); + if (std->pl_virt == NULL) + return -ENOMEM; + std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE); + + for (p = 0; p < std->num_pointers; p++) { + std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr); + dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE); + } + + return 0; +} + +/** + * urb_dequeue_work - executes asl/pzl update and gives back the urb to the system. + */ +static void urb_dequeue_work(struct work_struct *work) +{ + struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work); + struct whc_qset *qset = wurb->qset; + struct whc *whc = qset->whc; + unsigned long flags; + + if (wurb->is_async == true) + asl_update(whc, WUSBCMD_ASYNC_UPDATED + | WUSBCMD_ASYNC_SYNCED_DB + | WUSBCMD_ASYNC_QSET_RM); + else + pzl_update(whc, WUSBCMD_PERIODIC_UPDATED + | WUSBCMD_PERIODIC_SYNCED_DB + | WUSBCMD_PERIODIC_QSET_RM); + + spin_lock_irqsave(&whc->lock, flags); + qset_remove_urb(whc, qset, wurb->urb, wurb->status); + spin_unlock_irqrestore(&whc->lock, flags); +} + +/** + * qset_add_urb - add an urb to the qset's queue. + * + * The URB is chopped into sTDs, one for each qTD that will required. + * At least one qTD (and sTD) is required even if the transfer has no + * data (e.g., for some control transfers). + */ +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags) +{ + struct whc_urb *wurb; + int remaining = urb->transfer_buffer_length; + u64 transfer_dma = urb->transfer_dma; + int ntds_remaining; + + ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE); + if (ntds_remaining == 0) + ntds_remaining = 1; + + wurb = kzalloc(sizeof(struct whc_urb), mem_flags); + if (wurb == NULL) + goto err_no_mem; + urb->hcpriv = wurb; + wurb->qset = qset; + wurb->urb = urb; + INIT_WORK(&wurb->dequeue_work, urb_dequeue_work); + + while (ntds_remaining) { + struct whc_std *std; + size_t std_len; + + std = kmalloc(sizeof(struct whc_std), mem_flags); + if (std == NULL) + goto err_no_mem; + + std_len = remaining; + if (std_len > QTD_MAX_XFER_SIZE) + std_len = QTD_MAX_XFER_SIZE; + + std->urb = urb; + std->dma_addr = transfer_dma; + std->len = std_len; + std->ntds_remaining = ntds_remaining; + std->qtd = NULL; + + INIT_LIST_HEAD(&std->list_node); + list_add_tail(&std->list_node, &qset->stds); + + if (std_len > WHCI_PAGE_SIZE) { + if (qset_fill_page_list(whc, std, mem_flags) < 0) + goto err_no_mem; + } else + std->num_pointers = 0; + + ntds_remaining--; + remaining -= std_len; + transfer_dma += std_len; + } + + return 0; + +err_no_mem: + qset_free_stds(qset, urb); + return -ENOMEM; +} + +/** + * qset_remove_urb - remove an URB from the urb queue. + * + * The URB is returned to the USB subsystem. + */ +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + struct whc_urb *wurb = urb->hcpriv; + + usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb); + /* Drop the lock as urb->complete() may enqueue another urb. */ + spin_unlock(&whc->lock); + wusbhc_giveback_urb(wusbhc, urb, status); + spin_lock(&whc->lock); + + kfree(wurb); +} + +/** + * get_urb_status_from_qtd - get the completed urb status from qTD status + * @urb: completed urb + * @status: qTD status + */ +static int get_urb_status_from_qtd(struct urb *urb, u32 status) +{ + if (status & QTD_STS_HALTED) { + if (status & QTD_STS_DBE) + return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM; + else if (status & QTD_STS_BABBLE) + return -EOVERFLOW; + else if (status & QTD_STS_RCE) + return -ETIME; + return -EPIPE; + } + if (usb_pipein(urb->pipe) + && (urb->transfer_flags & URB_SHORT_NOT_OK) + && urb->actual_length < urb->transfer_buffer_length) + return -EREMOTEIO; + return 0; +} + +/** + * process_inactive_qtd - process an inactive (but not halted) qTD. + * + * Update the urb with the transfer bytes from the qTD, if the urb is + * completely transfered or (in the case of an IN only) the LPF is + * set, then the transfer is complete and the urb should be returned + * to the system. + */ +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + uint32_t status; + bool complete; + + status = le32_to_cpu(qtd->status); + + urb->actual_length += std->len - QTD_STS_TO_LEN(status); + + if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT)) + complete = true; + else + complete = whc_std_last(std); + + qset_remove_qtd(whc, qset); + qset_free_std(whc, std); + + /* + * Transfers for this URB are complete? Then return it to the + * USB subsystem. + */ + if (complete) { + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status)); + + /* + * If iAlt isn't valid then the hardware didn't + * advance iCur. Adjust the start and end pointers to + * match iCur. + */ + if (!(status & QTD_STS_IALT_VALID)) + qset->td_start = qset->td_end + = QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status)); + qset->pause_after_urb = NULL; + } +} + +/** + * process_halted_qtd - process a qset with a halted qtd + * + * Remove all the qTDs for the failed URB and return the failed URB to + * the USB subsystem. Then remove all other qTDs so the qset can be + * removed. + * + * FIXME: this is the point where rate adaptation can be done. If a + * transfer failed because it exceeded the maximum number of retries + * then it could be reactivated with a slower rate without having to + * remove the qset. + */ +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd) +{ + struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node); + struct urb *urb = std->urb; + int urb_status; + + urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status)); + + qset_remove_qtds(whc, qset, urb); + qset_remove_urb(whc, qset, urb, urb_status); + + list_for_each_entry(std, &qset->stds, list_node) { + if (qset->ntds == 0) + break; + qset_remove_qtd(whc, qset); + std->qtd = NULL; + } + + qset->remove = 1; +} + +void qset_free(struct whc *whc, struct whc_qset *qset) +{ + dma_pool_free(whc->qset_pool, qset, qset->qset_dma); +} + +/** + * qset_delete - wait for a qset to be unused, then free it. + */ +void qset_delete(struct whc *whc, struct whc_qset *qset) +{ + wait_for_completion(&qset->remove_complete); + qset_free(whc, qset); +} diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h new file mode 100644 index 00000000000..1d2a53bd39f --- /dev/null +++ b/drivers/usb/host/whci/whcd.h @@ -0,0 +1,197 @@ +/* + * Wireless Host Controller (WHC) private header. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#ifndef __WHCD_H +#define __WHCD_H + +#include +#include + +#include "whci-hc.h" + +/* Generic command timeout. */ +#define WHC_GENCMD_TIMEOUT_MS 100 + + +struct whc { + struct wusbhc wusbhc; + struct umc_dev *umc; + + resource_size_t base_phys; + void __iomem *base; + int irq; + + u8 n_devices; + u8 n_keys; + u8 n_mmc_ies; + + u64 *pz_list; + struct dn_buf_entry *dn_buf; + struct di_buf_entry *di_buf; + dma_addr_t pz_list_dma; + dma_addr_t dn_buf_dma; + dma_addr_t di_buf_dma; + + spinlock_t lock; + struct mutex mutex; + + void * gen_cmd_buf; + dma_addr_t gen_cmd_buf_dma; + wait_queue_head_t cmd_wq; + + struct workqueue_struct *workqueue; + struct work_struct dn_work; + + struct dma_pool *qset_pool; + + struct list_head async_list; + struct list_head async_removed_list; + wait_queue_head_t async_list_wq; + struct work_struct async_work; + + struct list_head periodic_list[5]; + struct list_head periodic_removed_list; + wait_queue_head_t periodic_list_wq; + struct work_struct periodic_work; +}; + +#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc)) + +/** + * struct whc_std - a software TD. + * @urb: the URB this sTD is for. + * @offset: start of the URB's data for this TD. + * @len: the length of data in the associated TD. + * @ntds_remaining: number of TDs (starting from this one) in this transfer. + * + * Queued URBs may require more TDs than are available in a qset so we + * use a list of these "software TDs" (sTDs) to hold per-TD data. + */ +struct whc_std { + struct urb *urb; + size_t len; + int ntds_remaining; + struct whc_qtd *qtd; + + struct list_head list_node; + int num_pointers; + dma_addr_t dma_addr; + struct whc_page_list_entry *pl_virt; +}; + +/** + * struct whc_urb - per URB host controller structure. + * @urb: the URB this struct is for. + * @qset: the qset associated to the URB. + * @dequeue_work: the work to remove the URB when dequeued. + * @is_async: the URB belongs to async sheduler or not. + * @status: the status to be returned when calling wusbhc_giveback_urb. + */ +struct whc_urb { + struct urb *urb; + struct whc_qset *qset; + struct work_struct dequeue_work; + bool is_async; + int status; +}; + +/** + * whc_std_last - is this sTD the URB's last? + * @std: the sTD to check. + */ +static inline bool whc_std_last(struct whc_std *std) +{ + return std->ntds_remaining <= 1; +} + +enum whc_update { + WHC_UPDATE_ADDED = 0x01, + WHC_UPDATE_REMOVED = 0x02, + WHC_UPDATE_UPDATED = 0x04, +}; + +/* init.c */ +int whc_init(struct whc *whc); +void whc_clean_up(struct whc *whc); + +/* hw.c */ +void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val); +int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len); + +/* wusb.c */ +int whc_wusbhc_start(struct wusbhc *wusbhc); +void whc_wusbhc_stop(struct wusbhc *wusbhc); +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie); +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle); +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm); +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev); +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots); +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size); +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size); +int whc_set_cluster_id(struct whc *whc, u8 bcid); + +/* int.c */ +irqreturn_t whc_int_handler(struct usb_hcd *hcd); +void whc_dn_work(struct work_struct *work); + +/* asl.c */ +void asl_start(struct whc *whc); +void asl_stop(struct whc *whc); +int asl_init(struct whc *whc); +void asl_clean_up(struct whc *whc); +int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void asl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_async_work(struct work_struct *work); + +/* pzl.c */ +int pzl_init(struct whc *whc); +void pzl_clean_up(struct whc *whc); +void pzl_start(struct whc *whc); +void pzl_stop(struct whc *whc); +int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags); +int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status); +void pzl_qset_delete(struct whc *whc, struct whc_qset *qset); +void scan_periodic_work(struct work_struct *work); + +/* qset.c */ +struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags); +void qset_free(struct whc *whc, struct whc_qset *qset); +struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags); +void qset_delete(struct whc *whc, struct whc_qset *qset); +void qset_clear(struct whc *whc, struct whc_qset *qset); +int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb, + gfp_t mem_flags); +void qset_free_std(struct whc *whc, struct whc_std *std); +void qset_remove_urb(struct whc *whc, struct whc_qset *qset, + struct urb *urb, int status); +void process_halted_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, + struct whc_qtd *qtd); +enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset); +void qset_remove_complete(struct whc *whc, struct whc_qset *qset); +void dump_qset(struct whc_qset *qset, struct device *dev); +void pzl_update(struct whc *whc, uint32_t wusbcmd); +void asl_update(struct whc *whc, uint32_t wusbcmd); + +#endif /* #ifndef __WHCD_H */ diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h new file mode 100644 index 00000000000..bff1eb7a35c --- /dev/null +++ b/drivers/usb/host/whci/whci-hc.h @@ -0,0 +1,416 @@ +/* + * Wireless Host Controller (WHC) data structures. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +#ifndef _WHCI_WHCI_HC_H +#define _WHCI_WHCI_HC_H + +#include + +/** + * WHCI_PAGE_SIZE - page size use by WHCI + * + * WHCI assumes that host system uses pages of 4096 octets. + */ +#define WHCI_PAGE_SIZE 4096 + + +/** + * QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single + * qtd. + * + * This is 2^20 - 1. + */ +#define QTD_MAX_XFER_SIZE 1048575 + + +/** + * struct whc_qtd - Queue Element Transfer Descriptors (qTD) + * + * This describes the data for a bulk, control or interrupt transfer. + * + * [WHCI] section 3.2.4 + */ +struct whc_qtd { + __le32 status; /*< remaining transfer len and transfer status */ + __le32 options; + __le64 page_list_ptr; /*< physical pointer to data buffer page list*/ + __u8 setup[8]; /*< setup data for control transfers */ +} __attribute__((packed)); + +#define QTD_STS_ACTIVE (1 << 31) /* enable execution of transaction */ +#define QTD_STS_HALTED (1 << 30) /* transfer halted */ +#define QTD_STS_DBE (1 << 29) /* data buffer error */ +#define QTD_STS_BABBLE (1 << 28) /* babble detected */ +#define QTD_STS_RCE (1 << 27) /* retry count exceeded */ +#define QTD_STS_LAST_PKT (1 << 26) /* set Last Packet Flag in WUSB header */ +#define QTD_STS_INACTIVE (1 << 25) /* queue set is marked inactive */ +#define QTD_STS_IALT_VALID (1 << 23) /* iAlt field is valid */ +#define QTD_STS_IALT(i) (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */ +#define QTD_STS_LEN(l) ((l) << 0) /* transfer length */ +#define QTD_STS_TO_LEN(s) ((s) & 0x000fffff) + +#define QTD_OPT_IOC (1 << 1) /* page_list_ptr points to buffer directly */ +#define QTD_OPT_SMALL (1 << 0) /* interrupt on complete */ + +/** + * struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD) + * + * This describes the data and other parameters for an isochronous + * transfer. + * + * [WHCI] section 3.2.5 + */ +struct whc_itd { + __le16 presentation_time; /*< presentation time for OUT transfers */ + __u8 num_segments; /*< number of data segments in segment list */ + __u8 status; /*< command execution status */ + __le32 options; /*< misc transfer options */ + __le64 page_list_ptr; /*< physical pointer to data buffer page list */ + __le64 seg_list_ptr; /*< physical pointer to segment list */ +} __attribute__((packed)); + +#define ITD_STS_ACTIVE (1 << 7) /* enable execution of transaction */ +#define ITD_STS_DBE (1 << 5) /* data buffer error */ +#define ITD_STS_BABBLE (1 << 4) /* babble detected */ +#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */ + +#define ITD_OPT_IOC (1 << 1) /* interrupt on complete */ +#define ITD_OPT_SMALL (1 << 0) /* page_list_ptr points to buffer directly */ + +/** + * Page list entry. + * + * A TD's page list must contain sufficient page list entries for the + * total data length in the TD. + * + * [WHCI] section 3.2.4.3 + */ +struct whc_page_list_entry { + __le64 buf_ptr; /*< physical pointer to buffer */ +} __attribute__((packed)); + +/** + * struct whc_seg_list_entry - Segment list entry. + * + * Describes a portion of the data buffer described in the containing + * qTD's page list. + * + * seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr + * + qtd->seg_list_ptr[seg].offset; + * + * Segments can't cross page boundries. + * + * [WHCI] section 3.2.5.5 + */ +struct whc_seg_list_entry { + __le16 len; /*< segment length */ + __u8 idx; /*< index into page list */ + __u8 status; /*< segment status */ + __le16 offset; /*< 12 bit offset into page */ +} __attribute__((packed)); + +/** + * struct whc_qhead - endpoint and status information for a qset. + * + * [WHCI] section 3.2.6 + */ +struct whc_qhead { + __le64 link; /*< next qset in list */ + __le32 info1; + __le32 info2; + __le32 info3; + __le16 status; + __le16 err_count; /*< transaction error count */ + __le32 cur_window; + __le32 scratch[3]; /*< h/w scratch area */ + union { + struct whc_qtd qtd; + struct whc_itd itd; + } overlay; +} __attribute__((packed)); + +#define QH_LINK_PTR_MASK (~0x03Full) +#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK) +#define QH_LINK_IQS (1 << 4) /* isochronous queue set */ +#define QH_LINK_NTDS(n) (((n) - 1) << 1) /* number of TDs in queue set */ +#define QH_LINK_T (1 << 0) /* last queue set in periodic schedule list */ + +#define QH_INFO1_EP(e) ((e) << 0) /* endpoint number */ +#define QH_INFO1_DIR_IN (1 << 4) /* IN transfer */ +#define QH_INFO1_DIR_OUT (0 << 4) /* OUT transfer */ +#define QH_INFO1_TR_TYPE_CTRL (0x0 << 5) /* control transfer */ +#define QH_INFO1_TR_TYPE_ISOC (0x1 << 5) /* isochronous transfer */ +#define QH_INFO1_TR_TYPE_BULK (0x2 << 5) /* bulk transfer */ +#define QH_INFO1_TR_TYPE_INT (0x3 << 5) /* interrupt */ +#define QH_INFO1_TR_TYPE_LP_INT (0x7 << 5) /* low power interrupt */ +#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8) /* index into device info buffer */ +#define QH_INFO1_SET_INACTIVE (1 << 15) /* set inactive after transfer */ +#define QH_INFO1_MAX_PKT_LEN(l) ((l) << 16) /* maximum packet length */ + +#define QH_INFO2_BURST(b) ((b) << 0) /* maximum burst length */ +#define QH_INFO2_DBP(p) ((p) << 5) /* data burst policy (see [WUSB] table 5-7) */ +#define QH_INFO2_MAX_COUNT(c) ((c) << 8) /* max isoc/int pkts per zone */ +#define QH_INFO2_RQS (1 << 15) /* reactivate queue set */ +#define QH_INFO2_MAX_RETRY(r) ((r) << 16) /* maximum transaction retries */ +#define QH_INFO2_MAX_SEQ(s) ((s) << 20) /* maximum sequence number */ +#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */ +#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */ + +#define QH_INFO3_TX_RATE_53_3 (0 << 24) +#define QH_INFO3_TX_RATE_80 (1 << 24) +#define QH_INFO3_TX_RATE_106_7 (2 << 24) +#define QH_INFO3_TX_RATE_160 (3 << 24) +#define QH_INFO3_TX_RATE_200 (4 << 24) +#define QH_INFO3_TX_RATE_320 (5 << 24) +#define QH_INFO3_TX_RATE_400 (6 << 24) +#define QH_INFO3_TX_RATE_480 (7 << 24) +#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */ + +#define QH_STATUS_FLOW_CTRL (1 << 15) +#define QH_STATUS_ICUR(i) ((i) << 5) +#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7) + +/** + * usb_pipe_to_qh_type - USB core pipe type to QH transfer type + * + * Returns the QH type field for a USB core pipe type. + */ +static inline unsigned usb_pipe_to_qh_type(unsigned pipe) +{ + static const unsigned type[] = { + [PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC, + [PIPE_INTERRUPT] = QH_INFO1_TR_TYPE_INT, + [PIPE_CONTROL] = QH_INFO1_TR_TYPE_CTRL, + [PIPE_BULK] = QH_INFO1_TR_TYPE_BULK, + }; + return type[usb_pipetype(pipe)]; +} + +/** + * Maxiumum number of TDs in a qset. + */ +#define WHCI_QSET_TD_MAX 8 + +/** + * struct whc_qset - WUSB data transfers to a specific endpoint + * @qh: the QHead of this qset + * @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt + * transfers) + * @itd: up to 8 iTDs (for qsets for isochronous transfers) + * @qset_dma: DMA address for this qset + * @whc: WHCI HC this qset is for + * @ep: endpoint + * @stds: list of sTDs queued to this qset + * @ntds: number of qTDs queued (not necessarily the same as nTDs + * field in the QH) + * @td_start: index of the first qTD in the list + * @td_end: index of next free qTD in the list (provided + * ntds < WHCI_QSET_TD_MAX) + * + * Queue Sets (qsets) are added to the asynchronous schedule list + * (ASL) or the periodic zone list (PZL). + * + * qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate). + * Each TD may refer to at most 1 MiB of data. If a single transfer + * has > 8MiB of data, TDs can be reused as they are completed since + * the TD list is used as a circular buffer. Similarly, several + * (smaller) transfers may be queued in a qset. + * + * WHCI controllers may cache portions of the qsets in the ASL and + * PZL, requiring the WHCD to inform the WHC that the lists have been + * updated (fields changed or qsets inserted or removed). For safe + * insertion and removal of qsets from the lists the schedule must be + * stopped to avoid races in updating the QH link pointers. + * + * Since the HC is free to execute qsets in any order, all transfers + * to an endpoint should use the same qset to ensure transfers are + * executed in the order they're submitted. + * + * [WHCI] section 3.2.3 + */ +struct whc_qset { + struct whc_qhead qh; + union { + struct whc_qtd qtd[WHCI_QSET_TD_MAX]; + struct whc_itd itd[WHCI_QSET_TD_MAX]; + }; + + /* private data for WHCD */ + dma_addr_t qset_dma; + struct whc *whc; + struct usb_host_endpoint *ep; + struct list_head stds; + int ntds; + int td_start; + int td_end; + struct list_head list_node; + unsigned in_sw_list:1; + unsigned in_hw_list:1; + unsigned remove:1; + struct urb *pause_after_urb; + struct completion remove_complete; + int max_burst; + int max_seq; +}; + +static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target) +{ + if (target) + *ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target); + else + *ptr = QH_LINK_T; +} + +/** + * struct di_buf_entry - Device Information (DI) buffer entry. + * + * There's one of these per connected device. + */ +struct di_buf_entry { + __le32 availability_info[8]; /*< MAS availability information, one MAS per bit */ + __le32 addr_sec_info; /*< addressing and security info */ + __le32 reserved[7]; +} __attribute__((packed)); + +#define WHC_DI_SECURE (1 << 31) +#define WHC_DI_DISABLE (1 << 30) +#define WHC_DI_KEY_IDX(k) ((k) << 8) +#define WHC_DI_KEY_IDX_MASK 0x0000ff00 +#define WHC_DI_DEV_ADDR(a) ((a) << 0) +#define WHC_DI_DEV_ADDR_MASK 0x000000ff + +/** + * struct dn_buf_entry - Device Notification (DN) buffer entry. + * + * [WHCI] section 3.2.8 + */ +struct dn_buf_entry { + __u8 msg_size; /*< number of octets of valid DN data */ + __u8 reserved1; + __u8 src_addr; /*< source address */ + __u8 status; /*< buffer entry status */ + __le32 tkid; /*< TKID for source device, valid if secure bit is set */ + __u8 dn_data[56]; /*< up to 56 octets of DN data */ +} __attribute__((packed)); + +#define WHC_DN_STATUS_VALID (1 << 7) /* buffer entry is valid */ +#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */ + +#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry)) + +/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of + data. [WHCI] section 2.4.7. */ +#define WHC_GEN_CMD_DATA_LEN 256 + +/* + * HC registers. + * + * [WHCI] section 2.4 + */ + +#define WHCIVERSION 0x00 + +#define WHCSPARAMS 0x04 +# define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff) +# define WHCSPARAMS_TO_N_KEYS(p) (((p) >> 8) & 0xff) +# define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f) + +#define WUSBCMD 0x08 +# define WUSBCMD_BCID(b) ((b) << 16) +# define WUSBCMD_BCID_MASK (0xff << 16) +# define WUSBCMD_ASYNC_QSET_RM (1 << 12) +# define WUSBCMD_PERIODIC_QSET_RM (1 << 11) +# define WUSBCMD_WUSBSI(s) ((s) << 8) +# define WUSBCMD_WUSBSI_MASK (0x7 << 8) +# define WUSBCMD_ASYNC_SYNCED_DB (1 << 7) +# define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6) +# define WUSBCMD_ASYNC_UPDATED (1 << 5) +# define WUSBCMD_PERIODIC_UPDATED (1 << 4) +# define WUSBCMD_ASYNC_EN (1 << 3) +# define WUSBCMD_PERIODIC_EN (1 << 2) +# define WUSBCMD_WHCRESET (1 << 1) +# define WUSBCMD_RUN (1 << 0) + +#define WUSBSTS 0x0c +# define WUSBSTS_ASYNC_SCHED (1 << 15) +# define WUSBSTS_PERIODIC_SCHED (1 << 14) +# define WUSBSTS_DNTS_SCHED (1 << 13) +# define WUSBSTS_HCHALTED (1 << 12) +# define WUSBSTS_GEN_CMD_DONE (1 << 9) +# define WUSBSTS_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBSTS_DNTS_OVERFLOW (1 << 7) +# define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBSTS_HOST_ERR (1 << 5) +# define WUSBSTS_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBSTS_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBSTS_DNTS_INT (1 << 2) +# define WUSBSTS_ERR_INT (1 << 1) +# define WUSBSTS_INT (1 << 0) +# define WUSBSTS_INT_MASK 0x3ff + +#define WUSBINTR 0x10 +# define WUSBINTR_GEN_CMD_DONE (1 << 9) +# define WUSBINTR_CHAN_TIME_ROLLOVER (1 << 8) +# define WUSBINTR_DNTS_OVERFLOW (1 << 7) +# define WUSBINTR_BPST_ADJUSTMENT_CHANGED (1 << 6) +# define WUSBINTR_HOST_ERR (1 << 5) +# define WUSBINTR_ASYNC_SCHED_SYNCED (1 << 4) +# define WUSBINTR_PERIODIC_SCHED_SYNCED (1 << 3) +# define WUSBINTR_DNTS_INT (1 << 2) +# define WUSBINTR_ERR_INT (1 << 1) +# define WUSBINTR_INT (1 << 0) +# define WUSBINTR_ALL 0x3ff + +#define WUSBGENCMDSTS 0x14 +# define WUSBGENCMDSTS_ACTIVE (1 << 31) +# define WUSBGENCMDSTS_ERROR (1 << 24) +# define WUSBGENCMDSTS_IOC (1 << 23) +# define WUSBGENCMDSTS_MMCIE_ADD 0x01 +# define WUSBGENCMDSTS_MMCIE_RM 0x02 +# define WUSBGENCMDSTS_SET_MAS 0x03 +# define WUSBGENCMDSTS_CHAN_STOP 0x04 +# define WUSBGENCMDSTS_RWP_EN 0x05 + +#define WUSBGENCMDPARAMS 0x18 +#define WUSBGENADDR 0x20 +#define WUSBASYNCLISTADDR 0x28 +#define WUSBDNTSBUFADDR 0x30 +#define WUSBDEVICEINFOADDR 0x38 + +#define WUSBSETSECKEYCMD 0x40 +# define WUSBSETSECKEYCMD_SET (1 << 31) +# define WUSBSETSECKEYCMD_ERASE (1 << 30) +# define WUSBSETSECKEYCMD_GTK (1 << 8) +# define WUSBSETSECKEYCMD_IDX(i) ((i) << 0) + +#define WUSBTKID 0x44 +#define WUSBSECKEY 0x48 +#define WUSBPERIODICLISTBASE 0x58 +#define WUSBMASINDEX 0x60 + +#define WUSBDNTSCTRL 0x64 +# define WUSBDNTSCTRL_ACTIVE (1 << 31) +# define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8) +# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0) + +#define WUSBTIME 0x68 +#define WUSBBPST 0x6c +#define WUSBDIBUPDATED 0x70 + +#endif /* #ifndef _WHCI_WHCI_HC_H */ diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c new file mode 100644 index 00000000000..66e4ddcd961 --- /dev/null +++ b/drivers/usb/host/whci/wusb.c @@ -0,0 +1,241 @@ +/* + * Wireless Host Controller (WHC) WUSB operations. + * + * Copyright (C) 2007 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#define D_LOCAL 1 +#include + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +#if D_LOCAL >= 1 +static void dump_di(struct whc *whc, int idx) +{ + struct di_buf_entry *di = &whc->di_buf[idx]; + struct device *dev = &whc->umc->dev; + char buf[128]; + + bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS); + + d_printf(1, dev, "DI[%d]\n", idx); + d_printf(1, dev, " availability: %s\n", buf); + d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n", + (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ', + (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ', + (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8, + (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK)); +} +#else +static inline void dump_di(struct whc *whc, int idx) +{ +} +#endif + +static int whc_update_di(struct whc *whc, int idx) +{ + int offset = idx / 32; + u32 bit = 1 << (idx % 32); + + dump_di(whc, idx); + + le_writel(bit, whc->base + WUSBDIBUPDATED + offset); + + return whci_wait_for(&whc->umc->dev, + whc->base + WUSBDIBUPDATED + offset, bit, 0, + 100, "DI update"); +} + +/* + * WHCI starts and stops MMCs based on there being a valid GTK so + * these need only start/stop the asynchronous and periodic schedules. + */ + +int whc_wusbhc_start(struct wusbhc *wusbhc) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + asl_start(whc); + pzl_start(whc); + + return 0; +} + +void whc_wusbhc_stop(struct wusbhc *wusbhc) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + pzl_stop(whc); + asl_stop(whc); +} + +int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, + u8 handle, struct wuie_hdr *wuie) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = (interval << 24) + | (repeat_cnt << 16) + | (wuie->bLength << 8) + | handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength); +} + +int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 params; + + params = handle; + + return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0); +} + +int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + + if (stream_index >= 0) + whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index)); + + return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm)); +} + +int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int idx = wusb_dev->port_idx; + struct di_buf_entry *di = &whc->di_buf[idx]; + int ret; + + mutex_lock(&whc->mutex); + + uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability); + di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK); + di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr); + + ret = whc_update_di(whc, idx); + + mutex_unlock(&whc->mutex); + + return ret; +} + +/* + * Set the number of Device Notification Time Slots (DNTS) and enable + * device notifications. + */ +int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + u32 dntsctrl; + + dntsctrl = WUSBDNTSCTRL_ACTIVE + | WUSBDNTSCTRL_INTERVAL(interval) + | WUSBDNTSCTRL_SLOTS(slots); + + le_writel(dntsctrl, whc->base + WUSBDNTSCTRL); + + return 0; +} + +static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid, + const void *key, size_t key_size, bool is_gtk) +{ + uint32_t setkeycmd; + uint32_t seckey[4]; + int i; + int ret; + + memcpy(seckey, key, key_size); + setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index); + if (is_gtk) + setkeycmd |= WUSBSETSECKEYCMD_GTK; + + le_writel(tkid, whc->base + WUSBTKID); + for (i = 0; i < 4; i++) + le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i); + le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD); + + ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD, + WUSBSETSECKEYCMD_SET, 0, 100, "set key"); + + return ret; +} + +/** + * whc_set_ptk - set the PTK to use for a device. + * + * The index into the key table for this PTK is the same as the + * device's port index. + */ +int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *ptk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + struct di_buf_entry *di = &whc->di_buf[port_idx]; + int ret; + + mutex_lock(&whc->mutex); + + if (ptk) { + ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false); + if (ret) + goto out; + + di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK; + di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx); + } else + di->addr_sec_info &= ~WHC_DI_SECURE; + + ret = whc_update_di(whc, port_idx); +out: + mutex_unlock(&whc->mutex); + return ret; +} + +/** + * whc_set_gtk - set the GTK for subsequent broadcast packets + * + * The GTK is stored in the last entry in the key table (the previous + * N_DEVICES entries are for the per-device PTKs). + */ +int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *gtk, size_t key_size) +{ + struct whc *whc = wusbhc_to_whc(wusbhc); + int ret; + + mutex_lock(&whc->mutex); + + ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true); + + mutex_unlock(&whc->mutex); + + return ret; +} + +int whc_set_cluster_id(struct whc *whc, u8 bcid) +{ + whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid)); + return 0; +} -- cgit v1.2.3 From df3654236e31f6cf425ed2ee5a74ceac366a7a9e Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:29 +0100 Subject: wusb: add the Wire Adapter (WA) core Common code for supporting Host Wire Adapters and Device Wire Adapters. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/Makefile | 6 + drivers/usb/wusbcore/wa-hc.c | 95 +++ drivers/usb/wusbcore/wa-hc.h | 417 ++++++++++ drivers/usb/wusbcore/wa-nep.c | 310 +++++++ drivers/usb/wusbcore/wa-rpipe.c | 562 +++++++++++++ drivers/usb/wusbcore/wa-xfer.c | 1709 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 3099 insertions(+) create mode 100644 drivers/usb/wusbcore/wa-hc.c create mode 100644 drivers/usb/wusbcore/wa-hc.h create mode 100644 drivers/usb/wusbcore/wa-nep.c create mode 100644 drivers/usb/wusbcore/wa-rpipe.c create mode 100644 drivers/usb/wusbcore/wa-xfer.c diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile index 6504f42ca36..7a4d0072403 100644 --- a/drivers/usb/wusbcore/Makefile +++ b/drivers/usb/wusbcore/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_USB_WUSB) += wusbcore.o wusb-cbaf.o +obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o wusbcore-objs := \ crypto.o \ @@ -12,3 +13,8 @@ wusbcore-objs := \ wusbhc.o wusb-cbaf-objs := cbaf.o + +wusb-wa-objs := wa-hc.o \ + wa-nep.o \ + wa-rpipe.o \ + wa-xfer.o diff --git a/drivers/usb/wusbcore/wa-hc.c b/drivers/usb/wusbcore/wa-hc.c new file mode 100644 index 00000000000..9d04722415b --- /dev/null +++ b/drivers/usb/wusbcore/wa-hc.c @@ -0,0 +1,95 @@ +/* + * Wire Adapter Host Controller Driver + * Common items to HWA and DWA based HCDs + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + */ +#include "wusbhc.h" +#include "wa-hc.h" + +/** + * Assumes + * + * wa->usb_dev and wa->usb_iface initialized and refcounted, + * wa->wa_descr initialized. + */ +int wa_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + + result = wa_rpipes_create(wa); + if (result < 0) + goto error_rpipes_create; + /* Fill up Data Transfer EP pointers */ + wa->dti_epd = &iface->cur_altsetting->endpoint[1].desc; + wa->dto_epd = &iface->cur_altsetting->endpoint[2].desc; + wa->xfer_result_size = le16_to_cpu(wa->dti_epd->wMaxPacketSize); + wa->xfer_result = kmalloc(wa->xfer_result_size, GFP_KERNEL); + if (wa->xfer_result == NULL) + goto error_xfer_result_alloc; + result = wa_nep_create(wa, iface); + if (result < 0) { + dev_err(dev, "WA-CDS: can't initialize notif endpoint: %d\n", + result); + goto error_nep_create; + } + return 0; + +error_nep_create: + kfree(wa->xfer_result); +error_xfer_result_alloc: + wa_rpipes_destroy(wa); +error_rpipes_create: + return result; +} +EXPORT_SYMBOL_GPL(wa_create); + + +void __wa_destroy(struct wahc *wa) +{ + if (wa->dti_urb) { + usb_kill_urb(wa->dti_urb); + usb_put_urb(wa->dti_urb); + usb_kill_urb(wa->buf_in_urb); + usb_put_urb(wa->buf_in_urb); + } + kfree(wa->xfer_result); + wa_nep_destroy(wa); + wa_rpipes_destroy(wa); +} +EXPORT_SYMBOL_GPL(__wa_destroy); + +/** + * wa_reset_all - reset the WA device + * @wa: the WA to be reset + * + * For HWAs the radio controller and all other PALs are also reset. + */ +void wa_reset_all(struct wahc *wa) +{ + /* FIXME: assuming HWA. */ + wusbhc_reset_all(wa->wusb); +} + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Wireless USB Wire Adapter core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/wusbcore/wa-hc.h b/drivers/usb/wusbcore/wa-hc.h new file mode 100644 index 00000000000..586d350cdb4 --- /dev/null +++ b/drivers/usb/wusbcore/wa-hc.h @@ -0,0 +1,417 @@ +/* + * HWA Host Controller Driver + * Wire Adapter Control/Data Streaming Iface (WUSB1.0[8]) + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This driver implements a USB Host Controller (struct usb_hcd) for a + * Wireless USB Host Controller based on the Wireless USB 1.0 + * Host-Wire-Adapter specification (in layman terms, a USB-dongle that + * implements a Wireless USB host). + * + * Check out the Design-overview.txt file in the source documentation + * for other details on the implementation. + * + * Main blocks: + * + * driver glue with the driver API, workqueue daemon + * + * lc RC instance life cycle management (create, destroy...) + * + * hcd glue with the USB API Host Controller Interface API. + * + * nep Notification EndPoint managent: collect notifications + * and queue them with the workqueue daemon. + * + * Handle notifications as coming from the NEP. Sends them + * off others to their respective modules (eg: connect, + * disconnect and reset go to devconnect). + * + * rpipe Remote Pipe management; rpipe is what we use to write + * to an endpoint on a WUSB device that is connected to a + * HWA RC. + * + * xfer Transfer managment -- this is all the code that gets a + * buffer and pushes it to a device (or viceversa). * + * + * Some day a lot of this code will be shared between this driver and + * the drivers for DWA (xfer, rpipe). + * + * All starts at driver.c:hwahc_probe(), when one of this guys is + * connected. hwahc_disconnect() stops it. + * + * During operation, the main driver is devices connecting or + * disconnecting. They cause the HWA RC to send notifications into + * nep.c:hwahc_nep_cb() that will dispatch them to + * notif.c:wa_notif_dispatch(). From there they will fan to cause + * device connects, disconnects, etc. + * + * Note much of the activity is difficult to follow. For example a + * device connect goes to devconnect, which will cause the "fake" root + * hub port to show a connect and stop there. Then khubd will notice + * and call into the rh.c:hwahc_rc_port_reset() code to authenticate + * the device (and this might require user intervention) and enable + * the port. + * + * We also have a timer workqueue going from devconnect.c that + * schedules in hwahc_devconnect_create(). + * + * The rest of the traffic is in the usual entry points of a USB HCD, + * which are hooked up in driver.c:hwahc_rc_driver, and defined in + * hcd.c. + */ + +#ifndef __HWAHC_INTERNAL_H__ +#define __HWAHC_INTERNAL_H__ + +#include +#include +#include +#include +#include +#include +#include + +struct wusbhc; +struct wahc; +extern void wa_urb_enqueue_run(struct work_struct *ws); + +/** + * RPipe instance + * + * @descr's fields are kept in LE, as we need to send it back and + * forth. + * + * @wa is referenced when set + * + * @segs_available is the number of requests segments that still can + * be submitted to the controller without overloading + * it. It is initialized to descr->wRequests when + * aiming. + * + * A rpipe supports a max of descr->wRequests at the same time; before + * submitting seg_lock has to be taken. If segs_avail > 0, then we can + * submit; if not, we have to queue them. + */ +struct wa_rpipe { + struct kref refcnt; + struct usb_rpipe_descriptor descr; + struct usb_host_endpoint *ep; + struct wahc *wa; + spinlock_t seg_lock; + struct list_head seg_list; + atomic_t segs_available; + u8 buffer[1]; /* For reads/writes on USB */ +}; + + +/** + * Instance of a HWA Host Controller + * + * Except where a more specific lock/mutex applies or atomic, all + * fields protected by @mutex. + * + * @wa_descr Can be accessed without locking because it is in + * the same area where the device descriptors were + * read, so it is guaranteed to exist umodified while + * the device exists. + * + * Endianess has been converted to CPU's. + * + * @nep_* can be accessed without locking as its processing is + * serialized; we submit a NEP URB and it comes to + * hwahc_nep_cb(), which won't issue another URB until it is + * done processing it. + * + * @xfer_list: + * + * List of active transfers to verify existence from a xfer id + * gotten from the xfer result message. Can't use urb->list because + * it goes by endpoint, and we don't know the endpoint at the time + * when we get the xfer result message. We can't really rely on the + * pointer (will have to change for 64 bits) as the xfer id is 32 bits. + * + * @xfer_delayed_list: List of transfers that need to be started + * (with a workqueue, because they were + * submitted from an atomic context). + * + * FIXME: this needs to be layered up: a wusbhc layer (for sharing + * comonalities with WHCI), a wa layer (for sharing + * comonalities with DWA-RC). + */ +struct wahc { + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + + /* HC to deliver notifications */ + union { + struct wusbhc *wusb; + struct dwahc *dwa; + }; + + const struct usb_endpoint_descriptor *dto_epd, *dti_epd; + const struct usb_wa_descriptor *wa_descr; + + struct urb *nep_urb; /* Notification EndPoint [lockless] */ + struct edc nep_edc; + void *nep_buffer; + size_t nep_buffer_size; + + atomic_t notifs_queued; + + u16 rpipes; + unsigned long *rpipe_bm; /* rpipe usage bitmap */ + spinlock_t rpipe_bm_lock; /* protect rpipe_bm */ + struct mutex rpipe_mutex; /* assigning resources to endpoints */ + + struct urb *dti_urb; /* URB for reading xfer results */ + struct urb *buf_in_urb; /* URB for reading data in */ + struct edc dti_edc; /* DTI error density counter */ + struct wa_xfer_result *xfer_result; /* real size = dti_ep maxpktsize */ + size_t xfer_result_size; + + s32 status; /* For reading status */ + + struct list_head xfer_list; + struct list_head xfer_delayed_list; + spinlock_t xfer_list_lock; + struct work_struct xfer_work; + atomic_t xfer_id_count; +}; + + +extern int wa_create(struct wahc *wa, struct usb_interface *iface); +extern void __wa_destroy(struct wahc *wa); +void wa_reset_all(struct wahc *wa); + + +/* Miscellaneous constants */ +enum { + /** Max number of EPROTO errors we tolerate on the NEP in a + * period of time */ + HWAHC_EPROTO_MAX = 16, + /** Period of time for EPROTO errors (in jiffies) */ + HWAHC_EPROTO_PERIOD = 4 * HZ, +}; + + +/* Notification endpoint handling */ +extern int wa_nep_create(struct wahc *, struct usb_interface *); +extern void wa_nep_destroy(struct wahc *); + +static inline int wa_nep_arm(struct wahc *wa, gfp_t gfp_mask) +{ + struct urb *urb = wa->nep_urb; + urb->transfer_buffer = wa->nep_buffer; + urb->transfer_buffer_length = wa->nep_buffer_size; + return usb_submit_urb(urb, gfp_mask); +} + +static inline void wa_nep_disarm(struct wahc *wa) +{ + usb_kill_urb(wa->nep_urb); +} + + +/* RPipes */ +static inline void wa_rpipe_init(struct wahc *wa) +{ + spin_lock_init(&wa->rpipe_bm_lock); + mutex_init(&wa->rpipe_mutex); +} + +static inline void wa_init(struct wahc *wa) +{ + edc_init(&wa->nep_edc); + atomic_set(&wa->notifs_queued, 0); + wa_rpipe_init(wa); + edc_init(&wa->dti_edc); + INIT_LIST_HEAD(&wa->xfer_list); + INIT_LIST_HEAD(&wa->xfer_delayed_list); + spin_lock_init(&wa->xfer_list_lock); + INIT_WORK(&wa->xfer_work, wa_urb_enqueue_run); + atomic_set(&wa->xfer_id_count, 1); +} + +/** + * Destroy a pipe (when refcount drops to zero) + * + * Assumes it has been moved to the "QUIESCING" state. + */ +struct wa_xfer; +extern void rpipe_destroy(struct kref *_rpipe); +static inline +void __rpipe_get(struct wa_rpipe *rpipe) +{ + kref_get(&rpipe->refcnt); +} +extern int rpipe_get_by_ep(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +static inline void rpipe_put(struct wa_rpipe *rpipe) +{ + kref_put(&rpipe->refcnt, rpipe_destroy); + +} +extern void rpipe_ep_disable(struct wahc *, struct usb_host_endpoint *); +extern int wa_rpipes_create(struct wahc *); +extern void wa_rpipes_destroy(struct wahc *); +static inline void rpipe_avail_dec(struct wa_rpipe *rpipe) +{ + atomic_dec(&rpipe->segs_available); +} + +/** + * Returns true if the rpipe is ready to submit more segments. + */ +static inline int rpipe_avail_inc(struct wa_rpipe *rpipe) +{ + return atomic_inc_return(&rpipe->segs_available) > 0 + && !list_empty(&rpipe->seg_list); +} + + +/* Transferring data */ +extern int wa_urb_enqueue(struct wahc *, struct usb_host_endpoint *, + struct urb *, gfp_t); +extern int wa_urb_dequeue(struct wahc *, struct urb *); +extern void wa_handle_notif_xfer(struct wahc *, struct wa_notif_hdr *); + + +/* Misc + * + * FIXME: Refcounting for the actual @hwahc object is not correct; I + * mean, this should be refcounting on the HCD underneath, but + * it is not. In any case, the semantics for HCD refcounting + * are *weird*...on refcount reaching zero it just frees + * it...no RC specific function is called...unless I miss + * something. + * + * FIXME: has to go away in favour of an 'struct' hcd based sollution + */ +static inline struct wahc *wa_get(struct wahc *wa) +{ + usb_get_intf(wa->usb_iface); + return wa; +} + +static inline void wa_put(struct wahc *wa) +{ + usb_put_intf(wa->usb_iface); +} + + +static inline int __wa_feature(struct wahc *wa, unsigned op, u16 feature) +{ + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + op ? USB_REQ_SET_FEATURE : USB_REQ_CLEAR_FEATURE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + feature, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + + +static inline int __wa_set_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 1, feature); +} + + +static inline int __wa_clear_feature(struct wahc *wa, u16 feature) +{ + return __wa_feature(wa, 0, feature); +} + + +/** + * Return the status of a Wire Adapter + * + * @wa: Wire Adapter instance + * @returns < 0 errno code on error, or status bitmap as described + * in WUSB1.0[8.3.1.6]. + * + * NOTE: need malloc, some arches don't take USB from the stack + */ +static inline +s32 __wa_get_status(struct wahc *wa) +{ + s32 result; + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + &wa->status, sizeof(wa->status), + 1000 /* FIXME: arbitrary */); + if (result >= 0) + result = wa->status; + return result; +} + + +/** + * Waits until the Wire Adapter's status matches @mask/@value + * + * @wa: Wire Adapter instance. + * @returns < 0 errno code on error, otherwise status. + * + * Loop until the WAs status matches the mask and value (status & mask + * == value). Timeout if it doesn't happen. + * + * FIXME: is there an official specification on how long status + * changes can take? + */ +static inline s32 __wa_wait_status(struct wahc *wa, u32 mask, u32 value) +{ + s32 result; + unsigned loops = 10; + do { + msleep(50); + result = __wa_get_status(wa); + if ((result & mask) == value) + break; + if (loops-- == 0) { + result = -ETIMEDOUT; + break; + } + } while (result >= 0); + return result; +} + + +/** Command @hwahc to stop, @returns 0 if ok, < 0 errno code on error */ +static inline int __wa_stop(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + result = __wa_clear_feature(wa, WA_ENABLE); + if (result < 0 && result != -ENODEV) { + dev_err(dev, "error commanding HC to stop: %d\n", result); + goto out; + } + result = __wa_wait_status(wa, WA_ENABLE, 0); + if (result < 0 && result != -ENODEV) + dev_err(dev, "error waiting for HC to stop: %d\n", result); +out: + return 0; +} + + +#endif /* #ifndef __HWAHC_INTERNAL_H__ */ diff --git a/drivers/usb/wusbcore/wa-nep.c b/drivers/usb/wusbcore/wa-nep.c new file mode 100644 index 00000000000..3f542990c73 --- /dev/null +++ b/drivers/usb/wusbcore/wa-nep.c @@ -0,0 +1,310 @@ +/* + * WUSB Wire Adapter: Control/Data Streaming Interface (WUSB[8]) + * Notification EndPoint support + * + * Copyright (C) 2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * This part takes care of getting the notification from the hw + * only and dispatching through wusbwad into + * wa_notif_dispatch. Handling is done there. + * + * WA notifications are limited in size; most of them are three or + * four bytes long, and the longest is the HWA Device Notification, + * which would not exceed 38 bytes (DNs are limited in payload to 32 + * bytes plus 3 bytes header (WUSB1.0[7.6p2]), plus 3 bytes HWA + * header (WUSB1.0[8.5.4.2]). + * + * It is not clear if more than one Device Notification can be packed + * in a HWA Notification, I assume no because of the wording in + * WUSB1.0[8.5.4.2]. In any case, the bigger any notification could + * get is 256 bytes (as the bLength field is a byte). + * + * So what we do is we have this buffer and read into it; when a + * notification arrives we schedule work to a specific, single thread + * workqueue (so notifications are serialized) and copy the + * notification data. After scheduling the work, we rearm the read from + * the notification endpoint. + * + * Entry points here are: + * + * wa_nep_[create|destroy]() To initialize/release this subsystem + * + * wa_nep_cb() Callback for the notification + * endpoint; when data is ready, this + * does the dispatching. + */ +#include +#include +#include +#include "wa-hc.h" +#include "wusbhc.h" + +/* Structure for queueing notifications to the workqueue */ +struct wa_notif_work { + struct work_struct work; + struct wahc *wa; + size_t size; + u8 data[]; +}; + +/* + * Process incoming notifications from the WA's Notification EndPoint + * [the wuswad daemon, basically] + * + * @_nw: Pointer to a descriptor which has the pointer to the + * @wa, the size of the buffer and the work queue + * structure (so we can free all when done). + * @returns 0 if ok, < 0 errno code on error. + * + * All notifications follow the same format; they need to start with a + * 'struct wa_notif_hdr' header, so it is easy to parse through + * them. We just break the buffer in individual notifications (the + * standard doesn't say if it can be done or is forbidden, so we are + * cautious) and dispatch each. + * + * So the handling layers are is: + * + * WA specific notification (from NEP) + * Device Notification Received -> wa_handle_notif_dn() + * WUSB Device notification generic handling + * BPST Adjustment -> wa_handle_notif_bpst_adj() + * ... -> ... + * + * @wa has to be referenced + */ +static void wa_notif_dispatch(struct work_struct *ws) +{ + void *itr; + u8 missing = 0; + struct wa_notif_work *nw = container_of(ws, struct wa_notif_work, work); + struct wahc *wa = nw->wa; + struct wa_notif_hdr *notif_hdr; + size_t size; + + struct device *dev = &wa->usb_iface->dev; + +#if 0 + /* FIXME: need to check for this??? */ + if (usb_hcd->state == HC_STATE_QUIESCING) /* Going down? */ + goto out; /* screw it */ +#endif + atomic_dec(&wa->notifs_queued); /* Throttling ctl */ + dev = &wa->usb_iface->dev; + size = nw->size; + itr = nw->data; + + while (size) { + if (size < sizeof(*notif_hdr)) { + missing = sizeof(*notif_hdr) - size; + goto exhausted_buffer; + } + notif_hdr = itr; + if (size < notif_hdr->bLength) + goto exhausted_buffer; + itr += notif_hdr->bLength; + size -= notif_hdr->bLength; + /* Dispatch the notification [don't use itr or size!] */ + switch (notif_hdr->bNotifyType) { + case HWA_NOTIF_DN: { + struct hwa_notif_dn *hwa_dn; + hwa_dn = container_of(notif_hdr, struct hwa_notif_dn, + hdr); + wusbhc_handle_dn(wa->wusb, hwa_dn->bSourceDeviceAddr, + hwa_dn->dndata, + notif_hdr->bLength - sizeof(*hwa_dn)); + break; + } + case WA_NOTIF_TRANSFER: + wa_handle_notif_xfer(wa, notif_hdr); + break; + case DWA_NOTIF_RWAKE: + case DWA_NOTIF_PORTSTATUS: + case HWA_NOTIF_BPST_ADJ: + /* FIXME: unimplemented WA NOTIFs */ + /* fallthru */ + default: + if (printk_ratelimit()) { + dev_err(dev, "HWA: unknown notification 0x%x, " + "%zu bytes; discarding\n", + notif_hdr->bNotifyType, + (size_t)notif_hdr->bLength); + dump_bytes(dev, notif_hdr, 16); + } + break; + } + } +out: + wa_put(wa); + kfree(nw); + return; + + /* THIS SHOULD NOT HAPPEN + * + * Buffer exahusted with partial data remaining; just warn and + * discard the data, as this should not happen. + */ +exhausted_buffer: + if (!printk_ratelimit()) + goto out; + dev_warn(dev, "HWA: device sent short notification, " + "%d bytes missing; discarding %d bytes.\n", + missing, (int)size); + dump_bytes(dev, itr, size); + goto out; +} + +/* + * Deliver incoming WA notifications to the wusbwa workqueue + * + * @wa: Pointer the Wire Adapter Controller Data Streaming + * instance (part of an 'struct usb_hcd'). + * @size: Size of the received buffer + * @returns 0 if ok, < 0 errno code on error. + * + * The input buffer is @wa->nep_buffer, with @size bytes + * (guaranteed to fit in the allocated space, + * @wa->nep_buffer_size). + */ +static int wa_nep_queue(struct wahc *wa, size_t size) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_work *nw; + + /* dev_fnstart(dev, "(wa %p, size %zu)\n", wa, size); */ + BUG_ON(size > wa->nep_buffer_size); + if (size == 0) + goto out; + if (atomic_read(&wa->notifs_queued) > 200) { + if (printk_ratelimit()) + dev_err(dev, "Too many notifications queued, " + "throttling back\n"); + goto out; + } + nw = kzalloc(sizeof(*nw) + size, GFP_ATOMIC); + if (nw == NULL) { + if (printk_ratelimit()) + dev_err(dev, "No memory to queue notification\n"); + goto out; + } + INIT_WORK(&nw->work, wa_notif_dispatch); + nw->wa = wa_get(wa); + nw->size = size; + memcpy(nw->data, wa->nep_buffer, size); + atomic_inc(&wa->notifs_queued); /* Throttling ctl */ + queue_work(wusbd, &nw->work); +out: + /* dev_fnend(dev, "(wa %p, size %zu) = result\n", wa, size, result); */ + return result; +} + +/* + * Callback for the notification event endpoint + * + * Check's that everything is fine and then passes the data to be + * queued to the workqueue. + */ +static void wa_nep_cb(struct urb *urb) +{ + int result; + struct wahc *wa = urb->context; + struct device *dev = &wa->usb_iface->dev; + + switch (result = urb->status) { + case 0: + result = wa_nep_queue(wa, urb->actual_length); + if (result < 0) + dev_err(dev, "NEP: unable to process notification(s): " + "%d\n", result); + break; + case -ECONNRESET: /* Not an error, but a controlled situation; */ + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: + dev_dbg(dev, "NEP: going down %d\n", urb->status); + goto out; + default: /* On general errors, we retry unless it gets ugly */ + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "NEP: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + goto out; + } + dev_err(dev, "NEP: URB error %d\n", urb->status); + } + result = wa_nep_arm(wa, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "NEP: cannot submit URB: %d\n", result); + wa_reset_all(wa); + } +out: + return; +} + +/* + * Initialize @wa's notification and event's endpoint stuff + * + * This includes the allocating the read buffer, the context ID + * allocation bitmap, the URB and submitting the URB. + */ +int wa_nep_create(struct wahc *wa, struct usb_interface *iface) +{ + int result; + struct usb_endpoint_descriptor *epd; + struct usb_device *usb_dev = interface_to_usbdev(iface); + struct device *dev = &iface->dev; + + edc_init(&wa->nep_edc); + epd = &iface->cur_altsetting->endpoint[0].desc; + wa->nep_buffer_size = 1024; + wa->nep_buffer = kmalloc(wa->nep_buffer_size, GFP_KERNEL); + if (wa->nep_buffer == NULL) { + dev_err(dev, "Unable to allocate notification's read buffer\n"); + goto error_nep_buffer; + } + wa->nep_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->nep_urb == NULL) { + dev_err(dev, "Unable to allocate notification URB\n"); + goto error_urb_alloc; + } + usb_fill_int_urb(wa->nep_urb, usb_dev, + usb_rcvintpipe(usb_dev, epd->bEndpointAddress), + wa->nep_buffer, wa->nep_buffer_size, + wa_nep_cb, wa, epd->bInterval); + result = wa_nep_arm(wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "Cannot submit notification URB: %d\n", result); + goto error_nep_arm; + } + return 0; + +error_nep_arm: + usb_free_urb(wa->nep_urb); +error_urb_alloc: + kfree(wa->nep_buffer); +error_nep_buffer: + return -ENOMEM; +} + +void wa_nep_destroy(struct wahc *wa) +{ + wa_nep_disarm(wa); + usb_free_urb(wa->nep_urb); + kfree(wa->nep_buffer); +} diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c new file mode 100644 index 00000000000..bfe3752e7c9 --- /dev/null +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -0,0 +1,562 @@ +/* + * WUSB Wire Adapter + * rpipe management + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * FIXME: docs + * + * RPIPE + * + * Targetted at different downstream endpoints + * + * Descriptor: use to config the remote pipe. + * + * The number of blocks could be dynamic (wBlocks in descriptor is + * 0)--need to schedule them then. + * + * Each bit in wa->rpipe_bm represents if an rpipe is being used or + * not. Rpipes are represented with a 'struct wa_rpipe' that is + * attached to the hcpriv member of a 'struct usb_host_endpoint'. + * + * When you need to xfer data to an endpoint, you get an rpipe for it + * with wa_ep_rpipe_get(), which gives you a reference to the rpipe + * and keeps a single one (the first one) with the endpoint. When you + * are done transferring, you drop that reference. At the end the + * rpipe is always allocated and bound to the endpoint. There it might + * be recycled when not used. + * + * Addresses: + * + * We use a 1:1 mapping mechanism between port address (0 based + * index, actually) and the address. The USB stack knows about this. + * + * USB Stack port number 4 (1 based) + * WUSB code port index 3 (0 based) + * USB Addresss 5 (2 based -- 0 is for default, 1 for root hub) + * + * Now, because we don't use the concept as default address exactly + * like the (wired) USB code does, we need to kind of skip it. So we + * never take addresses from the urb->pipe, but from the + * urb->dev->devnum, to make sure that we always have the right + * destination address. + */ +#include +#include +#include +#include "wusbhc.h" +#include "wa-hc.h" + +#define D_LOCAL 0 +#include + + +static int __rpipe_get_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* Get the RPIPE descriptor -- we cannot use the usb_get_descriptor() + * function because the arguments are different. + */ + d_printf(1, dev, "rpipe %u: get descr\n", index); + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "rpipe %u: get descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: got short descriptor " + "(%zd vs %zd bytes needed)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; +} + +/* + * + * The descriptor is assumed to be properly initialized (ie: you got + * it through __rpipe_get_descr()). + */ +static int __rpipe_set_descr(struct wahc *wa, + struct usb_rpipe_descriptor *descr, u16 index) +{ + ssize_t result; + struct device *dev = &wa->usb_iface->dev; + + /* we cannot use the usb_get_descriptor() function because the + * arguments are different. + */ + d_printf(1, dev, "rpipe %u: set descr\n", index); + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DT_RPIPE<<8, index, descr, sizeof(*descr), + HZ / 10); + if (result < 0) { + dev_err(dev, "rpipe %u: set descriptor failed: %d\n", + index, (int)result); + goto error; + } + if (result < sizeof(*descr)) { + dev_err(dev, "rpipe %u: sent short descriptor " + "(%zd vs %zd bytes required)\n", + index, result, sizeof(*descr)); + result = -EINVAL; + goto error; + } + result = 0; + +error: + return result; + +} + +static void rpipe_init(struct wa_rpipe *rpipe) +{ + kref_init(&rpipe->refcnt); + spin_lock_init(&rpipe->seg_lock); + INIT_LIST_HEAD(&rpipe->seg_list); +} + +static unsigned rpipe_get_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + rpipe_idx = find_next_zero_bit(wa->rpipe_bm, wa->rpipes, rpipe_idx); + if (rpipe_idx < wa->rpipes) + set_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); + + return rpipe_idx; +} + +static void rpipe_put_idx(struct wahc *wa, unsigned rpipe_idx) +{ + unsigned long flags; + + spin_lock_irqsave(&wa->rpipe_bm_lock, flags); + clear_bit(rpipe_idx, wa->rpipe_bm); + spin_unlock_irqrestore(&wa->rpipe_bm_lock, flags); +} + +void rpipe_destroy(struct kref *_rpipe) +{ + struct wa_rpipe *rpipe = container_of(_rpipe, struct wa_rpipe, refcnt); + u8 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + d_fnstart(1, NULL, "(rpipe %p %u)\n", rpipe, index); + if (rpipe->ep) + rpipe->ep->hcpriv = NULL; + rpipe_put_idx(rpipe->wa, index); + wa_put(rpipe->wa); + kfree(rpipe); + d_fnend(1, NULL, "(rpipe %p %u)\n", rpipe, index); +} +EXPORT_SYMBOL_GPL(rpipe_destroy); + +/* + * Locate an idle rpipe, create an structure for it and return it + * + * @wa is referenced and unlocked + * @crs enum rpipe_attr, required endpoint characteristics + * + * The rpipe can be used only sequentially (not in parallel). + * + * The rpipe is moved into the "ready" state. + */ +static int rpipe_get_idle(struct wa_rpipe **prpipe, struct wahc *wa, u8 crs, + gfp_t gfp) +{ + int result; + unsigned rpipe_idx; + struct wa_rpipe *rpipe; + struct device *dev = &wa->usb_iface->dev; + + d_fnstart(3, dev, "(wa %p crs 0x%02x)\n", wa, crs); + rpipe = kzalloc(sizeof(*rpipe), gfp); + if (rpipe == NULL) + return -ENOMEM; + rpipe_init(rpipe); + + /* Look for an idle pipe */ + for (rpipe_idx = 0; rpipe_idx < wa->rpipes; rpipe_idx++) { + rpipe_idx = rpipe_get_idx(wa, rpipe_idx); + if (rpipe_idx >= wa->rpipes) /* no more pipes :( */ + break; + result = __rpipe_get_descr(wa, &rpipe->descr, rpipe_idx); + if (result < 0) + dev_err(dev, "Can't get descriptor for rpipe %u: %d\n", + rpipe_idx, result); + else if ((rpipe->descr.bmCharacteristics & crs) != 0) + goto found; + rpipe_put_idx(wa, rpipe_idx); + } + *prpipe = NULL; + kfree(rpipe); + d_fnend(3, dev, "(wa %p crs 0x%02x) = -ENXIO\n", wa, crs); + return -ENXIO; + +found: + set_bit(rpipe_idx, wa->rpipe_bm); + rpipe->wa = wa_get(wa); + *prpipe = rpipe; + d_fnstart(3, dev, "(wa %p crs 0x%02x) = 0\n", wa, crs); + return 0; +} + +static int __rpipe_reset(struct wahc *wa, unsigned index) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + + d_printf(1, dev, "rpipe %u: reset\n", index); + result = usb_control_msg( + wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_RESET, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "rpipe %u: reset failed: %d\n", + index, result); + return result; +} + +/* + * Fake companion descriptor for ep0 + * + * See WUSB1.0[7.4.4], most of this is zero for bulk/int/ctl + */ +static struct usb_wireless_ep_comp_descriptor epc0 = { + .bLength = sizeof(epc0), + .bDescriptorType = USB_DT_WIRELESS_ENDPOINT_COMP, +/* .bMaxBurst = 1, */ + .bMaxSequence = 31, +}; + +/* + * Look for EP companion descriptor + * + * Get there, look for Inara in the endpoint's extra descriptors + */ +static struct usb_wireless_ep_comp_descriptor *rpipe_epc_find( + struct device *dev, struct usb_host_endpoint *ep) +{ + void *itr; + size_t itr_size; + struct usb_descriptor_header *hdr; + struct usb_wireless_ep_comp_descriptor *epcd; + + d_fnstart(3, dev, "(ep %p)\n", ep); + if (ep->desc.bEndpointAddress == 0) { + epcd = &epc0; + goto out; + } + itr = ep->extra; + itr_size = ep->extralen; + epcd = NULL; + while (itr_size > 0) { + if (itr_size < sizeof(*hdr)) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptors " + "at offset %zu: only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, itr_size); + break; + } + hdr = itr; + if (hdr->bDescriptorType == USB_DT_WIRELESS_ENDPOINT_COMP) { + epcd = itr; + break; + } + if (hdr->bLength > itr_size) { + dev_err(dev, "HW Bug? ep 0x%02x: extra descriptor " + "at offset %zu (type 0x%02x) " + "length %d but only %zu bytes left\n", + ep->desc.bEndpointAddress, + itr - (void *) ep->extra, hdr->bDescriptorType, + hdr->bLength, itr_size); + break; + } + itr += hdr->bLength; + itr_size -= hdr->bDescriptorType; + } +out: + d_fnend(3, dev, "(ep %p) = %p\n", ep, epcd); + return epcd; +} + +/* + * Aim an rpipe to its device & endpoint destination + * + * Make sure we change the address to unauthenticathed if the device + * is WUSB and it is not authenticated. + */ +static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa, + struct usb_host_endpoint *ep, struct urb *urb, gfp_t gfp) +{ + int result = -ENOMSG; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + struct usb_wireless_ep_comp_descriptor *epcd; + u8 unauth; + + d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n", + rpipe, wa, ep, urb); + epcd = rpipe_epc_find(dev, ep); + if (epcd == NULL) { + dev_err(dev, "ep 0x%02x: can't find companion descriptor\n", + ep->desc.bEndpointAddress); + goto error; + } + unauth = usb_dev->wusb && !usb_dev->authenticated ? 0x80 : 0; + __rpipe_reset(wa, le16_to_cpu(rpipe->descr.wRPipeIndex)); + atomic_set(&rpipe->segs_available, le16_to_cpu(rpipe->descr.wRequests)); + /* FIXME: block allocation system; request with queuing and timeout */ + /* FIXME: compute so seg_size > ep->maxpktsize */ + rpipe->descr.wBlocks = cpu_to_le16(16); /* given */ + /* ep0 maxpktsize is 0x200 (WUSB1.0[4.8.1]) */ + rpipe->descr.wMaxPacketSize = cpu_to_le16(ep->desc.wMaxPacketSize); + rpipe->descr.bHSHubAddress = 0; /* reserved: zero */ + rpipe->descr.bHSHubPort = wusb_port_no_to_idx(urb->dev->portnum); + /* FIXME: use maximum speed as supported or recommended by device */ + rpipe->descr.bSpeed = usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200; + d_printf(2, dev, "addr %u (0x%02x) rpipe #%u ep# %u speed %d\n", + urb->dev->devnum, urb->dev->devnum | unauth, + le16_to_cpu(rpipe->descr.wRPipeIndex), + usb_pipeendpoint(urb->pipe), rpipe->descr.bSpeed); + /* see security.c:wusb_update_address() */ + if (unlikely(urb->dev->devnum == 0x80)) + rpipe->descr.bDeviceAddress = 0; + else + rpipe->descr.bDeviceAddress = urb->dev->devnum | unauth; + rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress; + /* FIXME: bDataSequence */ + rpipe->descr.bDataSequence = 0; + /* FIXME: dwCurrentWindow */ + rpipe->descr.dwCurrentWindow = cpu_to_le32(1); + /* FIXME: bMaxDataSequence */ + rpipe->descr.bMaxDataSequence = epcd->bMaxSequence - 1; + rpipe->descr.bInterval = ep->desc.bInterval; + /* FIXME: bOverTheAirInterval */ + rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */ + /* FIXME: xmit power & preamble blah blah */ + rpipe->descr.bmAttribute = ep->desc.bmAttributes & 0x03; + /* rpipe->descr.bmCharacteristics RO */ + /* FIXME: bmRetryOptions */ + rpipe->descr.bmRetryOptions = 15; + /* FIXME: use for assessing link quality? */ + rpipe->descr.wNumTransactionErrors = 0; + result = __rpipe_set_descr(wa, &rpipe->descr, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + if (result < 0) { + dev_err(dev, "Cannot aim rpipe: %d\n", result); + goto error; + } + result = 0; +error: + d_fnend(3, dev, "(rpipe %p wa %p ep %p urb %p) = %d\n", + rpipe, wa, ep, urb, result); + return result; +} + +/* + * Check an aimed rpipe to make sure it points to where we want + * + * We use bit 19 of the Linux USB pipe bitmap for unauth vs auth + * space; when it is like that, we or 0x80 to make an unauth address. + */ +static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa, + const struct usb_host_endpoint *ep, + const struct urb *urb, gfp_t gfp) +{ + int result = 0; /* better code for lack of companion? */ + struct device *dev = &wa->usb_iface->dev; + struct usb_device *usb_dev = urb->dev; + u8 unauth = (usb_dev->wusb && !usb_dev->authenticated) ? 0x80 : 0; + u8 portnum = wusb_port_no_to_idx(urb->dev->portnum); + + d_fnstart(3, dev, "(rpipe %p wa %p ep %p, urb %p)\n", + rpipe, wa, ep, urb); +#define AIM_CHECK(rdf, val, text) \ + do { \ + if (rpipe->descr.rdf != (val)) { \ + dev_err(dev, \ + "rpipe aim discrepancy: " #rdf " " text "\n", \ + rpipe->descr.rdf, (val)); \ + result = -EINVAL; \ + WARN_ON(1); \ + } \ + } while (0) + AIM_CHECK(wMaxPacketSize, cpu_to_le16(ep->desc.wMaxPacketSize), + "(%u vs %u)"); + AIM_CHECK(bHSHubPort, portnum, "(%u vs %u)"); + AIM_CHECK(bSpeed, usb_pipeendpoint(urb->pipe) == 0 ? + UWB_PHY_RATE_53 : UWB_PHY_RATE_200, + "(%u vs %u)"); + AIM_CHECK(bDeviceAddress, urb->dev->devnum | unauth, "(%u vs %u)"); + AIM_CHECK(bEndpointAddress, ep->desc.bEndpointAddress, "(%u vs %u)"); + AIM_CHECK(bInterval, ep->desc.bInterval, "(%u vs %u)"); + AIM_CHECK(bmAttribute, ep->desc.bmAttributes & 0x03, "(%u vs %u)"); +#undef AIM_CHECK + return result; +} + +#ifndef CONFIG_BUG +#define CONFIG_BUG 0 +#endif + +/* + * Make sure there is an rpipe allocated for an endpoint + * + * If already allocated, we just refcount it; if not, we get an + * idle one, aim it to the right location and take it. + * + * Attaches to ep->hcpriv and rpipe->ep to ep. + */ +int rpipe_get_by_ep(struct wahc *wa, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t gfp) +{ + int result = 0; + struct device *dev = &wa->usb_iface->dev; + struct wa_rpipe *rpipe; + u8 eptype; + + d_fnstart(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb, + gfp); + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + if (CONFIG_BUG == 1) { + result = rpipe_check_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) + goto error; + } + __rpipe_get(rpipe); + d_printf(2, dev, "ep 0x%02x: reusing rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } else { + /* hmm, assign idle rpipe, aim it */ + result = -ENOBUFS; + eptype = ep->desc.bmAttributes & 0x03; + result = rpipe_get_idle(&rpipe, wa, 1 << eptype, gfp); + if (result < 0) + goto error; + result = rpipe_aim(rpipe, wa, ep, urb, gfp); + if (result < 0) { + rpipe_put(rpipe); + goto error; + } + ep->hcpriv = rpipe; + rpipe->ep = ep; + __rpipe_get(rpipe); /* for caching into ep->hcpriv */ + d_printf(2, dev, "ep 0x%02x: using rpipe %u\n", + ep->desc.bEndpointAddress, + le16_to_cpu(rpipe->descr.wRPipeIndex)); + } + d_dump(4, dev, &rpipe->descr, sizeof(rpipe->descr)); +error: + mutex_unlock(&wa->rpipe_mutex); + d_fnend(3, dev, "(wa %p ep %p urb %p gfp 0x%08x)\n", wa, ep, urb, gfp); + return result; +} + +/* + * Allocate the bitmap for each rpipe. + */ +int wa_rpipes_create(struct wahc *wa) +{ + wa->rpipes = wa->wa_descr->wNumRPipes; + wa->rpipe_bm = kzalloc(BITS_TO_LONGS(wa->rpipes)*sizeof(unsigned long), + GFP_KERNEL); + if (wa->rpipe_bm == NULL) + return -ENOMEM; + return 0; +} + +void wa_rpipes_destroy(struct wahc *wa) +{ + struct device *dev = &wa->usb_iface->dev; + d_fnstart(3, dev, "(wa %p)\n", wa); + if (!bitmap_empty(wa->rpipe_bm, wa->rpipes)) { + char buf[256]; + WARN_ON(1); + bitmap_scnprintf(buf, sizeof(buf), wa->rpipe_bm, wa->rpipes); + dev_err(dev, "BUG: pipes not released on exit: %s\n", buf); + } + kfree(wa->rpipe_bm); + d_fnend(3, dev, "(wa %p)\n", wa); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, Abort any pending + * transfers and put it. If the rpipe ends up being destroyed, + * __rpipe_destroy() will cleanup ep->hcpriv. + * + * This is called before calling hcd->stop(), so you don't need to do + * anything else in there. + */ +void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep) +{ + struct device *dev = &wa->usb_iface->dev; + struct wa_rpipe *rpipe; + d_fnstart(2, dev, "(wa %p ep %p)\n", wa, ep); + mutex_lock(&wa->rpipe_mutex); + rpipe = ep->hcpriv; + if (rpipe != NULL) { + unsigned rc = atomic_read(&rpipe->refcnt.refcount); + int result; + u16 index = le16_to_cpu(rpipe->descr.wRPipeIndex); + + if (rc != 1) + d_printf(1, dev, "(wa %p ep %p) rpipe %p refcnt %u\n", + wa, ep, rpipe, rc); + + d_printf(1, dev, "rpipe %u: abort\n", index); + result = usb_control_msg( + wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), + USB_REQ_RPIPE_ABORT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0 && result != -ENODEV /* dev is gone */) + d_printf(1, dev, "(wa %p rpipe %u): abort failed: %d\n", + wa, index, result); + rpipe_put(rpipe); + } + mutex_unlock(&wa->rpipe_mutex); + d_fnend(2, dev, "(wa %p ep %p)\n", wa, ep); + return; +} +EXPORT_SYMBOL_GPL(rpipe_ep_disable); diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c new file mode 100644 index 00000000000..7d192f3e674 --- /dev/null +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -0,0 +1,1709 @@ +/* + * WUSB Wire Adapter + * Data transfer and URB enqueing + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * How transfers work: get a buffer, break it up in segments (segment + * size is a multiple of the maxpacket size). For each segment issue a + * segment request (struct wa_xfer_*), then send the data buffer if + * out or nothing if in (all over the DTO endpoint). + * + * For each submitted segment request, a notification will come over + * the NEP endpoint and a transfer result (struct xfer_result) will + * arrive in the DTI URB. Read it, get the xfer ID, see if there is + * data coming (inbound transfer), schedule a read and handle it. + * + * Sounds simple, it is a pain to implement. + * + * + * ENTRY POINTS + * + * FIXME + * + * LIFE CYCLE / STATE DIAGRAM + * + * FIXME + * + * THIS CODE IS DISGUSTING + * + * Warned you are; it's my second try and still not happy with it. + * + * NOTES: + * + * - No iso + * + * - Supports DMA xfers, control, bulk and maybe interrupt + * + * - Does not recycle unused rpipes + * + * An rpipe is assigned to an endpoint the first time it is used, + * and then it's there, assigned, until the endpoint is disabled + * (destroyed [{h,d}wahc_op_ep_disable()]. The assignment of the + * rpipe to the endpoint is done under the wa->rpipe_sem semaphore + * (should be a mutex). + * + * Two methods it could be done: + * + * (a) set up a timer everytime an rpipe's use count drops to 1 + * (which means unused) or when a transfer ends. Reset the + * timer when a xfer is queued. If the timer expires, release + * the rpipe [see rpipe_ep_disable()]. + * + * (b) when looking for free rpipes to attach [rpipe_get_by_ep()], + * when none are found go over the list, check their endpoint + * and their activity record (if no last-xfer-done-ts in the + * last x seconds) take it + * + * However, due to the fact that we have a set of limited + * resources (max-segments-at-the-same-time per xfer, + * xfers-per-ripe, blocks-per-rpipe, rpipes-per-host), at the end + * we are going to have to rebuild all this based on an scheduler, + * to where we have a list of transactions to do and based on the + * availability of the different requried components (blocks, + * rpipes, segment slots, etc), we go scheduling them. Painful. + */ +#include +#include +#include +#include "wa-hc.h" +#include "wusbhc.h" + +#undef D_LOCAL +#define D_LOCAL 0 /* 0 disabled, > 0 different levels... */ +#include + +enum { + WA_SEGS_MAX = 255, +}; + +enum wa_seg_status { + WA_SEG_NOTREADY, + WA_SEG_READY, + WA_SEG_DELAYED, + WA_SEG_SUBMITTED, + WA_SEG_PENDING, + WA_SEG_DTI_PENDING, + WA_SEG_DONE, + WA_SEG_ERROR, + WA_SEG_ABORTED, +}; + +static void wa_xfer_delayed_run(struct wa_rpipe *); + +/* + * Life cycle governed by 'struct urb' (the refcount of the struct is + * that of the 'struct urb' and usb_free_urb() would free the whole + * struct). + */ +struct wa_seg { + struct urb urb; + struct urb *dto_urb; /* for data output? */ + struct list_head list_node; /* for rpipe->req_list */ + struct wa_xfer *xfer; /* out xfer */ + u8 index; /* which segment we are */ + enum wa_seg_status status; + ssize_t result; /* bytes xfered or error */ + struct wa_xfer_hdr xfer_hdr; + u8 xfer_extra[]; /* xtra space for xfer_hdr_ctl */ +}; + +static void wa_seg_init(struct wa_seg *seg) +{ + /* usb_init_urb() repeats a lot of work, so we do it here */ + kref_init(&seg->urb.kref); +} + +/* + * Protected by xfer->lock + * + */ +struct wa_xfer { + struct kref refcnt; + struct list_head list_node; + spinlock_t lock; + u32 id; + + struct wahc *wa; /* Wire adapter we are plugged to */ + struct usb_host_endpoint *ep; + struct urb *urb; /* URB we are transfering for */ + struct wa_seg **seg; /* transfer segments */ + u8 segs, segs_submitted, segs_done; + unsigned is_inbound:1; + unsigned is_dma:1; + size_t seg_size; + int result; + + gfp_t gfp; /* allocation mask */ + + struct wusb_dev *wusb_dev; /* for activity timestamps */ +}; + +static inline void wa_xfer_init(struct wa_xfer *xfer) +{ + kref_init(&xfer->refcnt); + INIT_LIST_HEAD(&xfer->list_node); + spin_lock_init(&xfer->lock); +} + +/* + * Destory a transfer structure + * + * Note that the xfer->seg[index] thingies follow the URB life cycle, + * so we need to put them, not free them. + */ +static void wa_xfer_destroy(struct kref *_xfer) +{ + struct wa_xfer *xfer = container_of(_xfer, struct wa_xfer, refcnt); + if (xfer->seg) { + unsigned cnt; + for (cnt = 0; cnt < xfer->segs; cnt++) { + if (xfer->is_inbound) + usb_put_urb(xfer->seg[cnt]->dto_urb); + usb_put_urb(&xfer->seg[cnt]->urb); + } + } + kfree(xfer); + d_printf(2, NULL, "xfer %p destroyed\n", xfer); +} + +static void wa_xfer_get(struct wa_xfer *xfer) +{ + kref_get(&xfer->refcnt); +} + +static void wa_xfer_put(struct wa_xfer *xfer) +{ + d_fnstart(3, NULL, "(xfer %p) -- ref count bef put %d\n", + xfer, atomic_read(&xfer->refcnt.refcount)); + kref_put(&xfer->refcnt, wa_xfer_destroy); + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); +} + +/* + * xfer is referenced + * + * xfer->lock has to be unlocked + * + * We take xfer->lock for setting the result; this is a barrier + * against drivers/usb/core/hcd.c:unlink1() being called after we call + * usb_hcd_giveback_urb() and wa_urb_dequeue() trying to get a + * reference to the transfer. + */ +static void wa_xfer_giveback(struct wa_xfer *xfer) +{ + unsigned long flags; + d_fnstart(3, NULL, "(xfer %p)\n", xfer); + spin_lock_irqsave(&xfer->wa->xfer_list_lock, flags); + list_del_init(&xfer->list_node); + spin_unlock_irqrestore(&xfer->wa->xfer_list_lock, flags); + /* FIXME: segmentation broken -- kills DWA */ + wusbhc_giveback_urb(xfer->wa->wusb, xfer->urb, xfer->result); + wa_put(xfer->wa); + wa_xfer_put(xfer); + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); +} + +/* + * xfer is referenced + * + * xfer->lock has to be unlocked + */ +static void wa_xfer_completion(struct wa_xfer *xfer) +{ + d_fnstart(3, NULL, "(xfer %p)\n", xfer); + if (xfer->wusb_dev) + wusb_dev_put(xfer->wusb_dev); + rpipe_put(xfer->ep->hcpriv); + wa_xfer_giveback(xfer); + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); + return; +} + +/* + * If transfer is done, wrap it up and return true + * + * xfer->lock has to be locked + */ +static unsigned __wa_xfer_is_done(struct wa_xfer *xfer) +{ + unsigned result, cnt; + struct wa_seg *seg; + struct urb *urb = xfer->urb; + unsigned found_short = 0; + + d_fnstart(3, NULL, "(xfer %p)\n", xfer); + result = xfer->segs_done == xfer->segs_submitted; + if (result == 0) + goto out; + urb->actual_length = 0; + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt]; + switch (seg->status) { + case WA_SEG_DONE: + if (found_short && seg->result > 0) { + if (printk_ratelimit()) + printk(KERN_ERR "xfer %p#%u: bad short " + "segments (%zu)\n", xfer, cnt, + seg->result); + urb->status = -EINVAL; + goto out; + } + urb->actual_length += seg->result; + if (seg->result < xfer->seg_size + && cnt != xfer->segs-1) + found_short = 1; + d_printf(2, NULL, "xfer %p#%u: DONE short %d " + "result %zu urb->actual_length %d\n", + xfer, seg->index, found_short, seg->result, + urb->actual_length); + break; + case WA_SEG_ERROR: + xfer->result = seg->result; + d_printf(2, NULL, "xfer %p#%u: ERROR result %zu\n", + xfer, seg->index, seg->result); + goto out; + case WA_SEG_ABORTED: + WARN_ON(urb->status != -ECONNRESET + && urb->status != -ENOENT); + d_printf(2, NULL, "xfer %p#%u ABORTED: result %d\n", + xfer, seg->index, urb->status); + xfer->result = urb->status; + goto out; + default: + /* if (printk_ratelimit()) */ + printk(KERN_ERR "xfer %p#%u: " + "is_done bad state %d\n", + xfer, cnt, seg->status); + xfer->result = -EINVAL; + WARN_ON(1); + goto out; + } + } + xfer->result = 0; +out: + d_fnend(3, NULL, "(xfer %p) = void\n", xfer); + return result; +} + +/* + * Initialize a transfer's ID + * + * We need to use a sequential number; if we use the pointer or the + * hash of the pointer, it can repeat over sequential transfers and + * then it will confuse the HWA....wonder why in hell they put a 32 + * bit handle in there then. + */ +static void wa_xfer_id_init(struct wa_xfer *xfer) +{ + xfer->id = atomic_add_return(1, &xfer->wa->xfer_id_count); +} + +/* + * Return the xfer's ID associated with xfer + * + * Need to generate a + */ +static u32 wa_xfer_id(struct wa_xfer *xfer) +{ + return xfer->id; +} + +/* + * Search for a transfer list ID on the HCD's URB list + * + * For 32 bit architectures, we use the pointer itself; for 64 bits, a + * 32-bit hash of the pointer. + * + * @returns NULL if not found. + */ +static struct wa_xfer *wa_xfer_get_by_id(struct wahc *wa, u32 id) +{ + unsigned long flags; + struct wa_xfer *xfer_itr; + spin_lock_irqsave(&wa->xfer_list_lock, flags); + list_for_each_entry(xfer_itr, &wa->xfer_list, list_node) { + if (id == xfer_itr->id) { + wa_xfer_get(xfer_itr); + goto out; + } + } + xfer_itr = NULL; +out: + spin_unlock_irqrestore(&wa->xfer_list_lock, flags); + return xfer_itr; +} + +struct wa_xfer_abort_buffer { + struct urb urb; + struct wa_xfer_abort cmd; +}; + +static void __wa_xfer_abort_cb(struct urb *urb) +{ + struct wa_xfer_abort_buffer *b = urb->context; + usb_put_urb(&b->urb); +} + +/* + * Aborts an ongoing transaction + * + * Assumes the transfer is referenced and locked and in a submitted + * state (mainly that there is an endpoint/rpipe assigned). + * + * The callback (see above) does nothing but freeing up the data by + * putting the URB. Because the URB is allocated at the head of the + * struct, the whole space we allocated is kfreed. + * + * We'll get an 'aborted transaction' xfer result on DTI, that'll + * politely ignore because at this point the transaction has been + * marked as aborted already. + */ +static void __wa_xfer_abort(struct wa_xfer *xfer) +{ + int result; + struct device *dev = &xfer->wa->usb_iface->dev; + struct wa_xfer_abort_buffer *b; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + b = kmalloc(sizeof(*b), GFP_ATOMIC); + if (b == NULL) + goto error_kmalloc; + b->cmd.bLength = sizeof(b->cmd); + b->cmd.bRequestType = WA_XFER_ABORT; + b->cmd.wRPipe = rpipe->descr.wRPipeIndex; + b->cmd.dwTransferID = wa_xfer_id(xfer); + + usb_init_urb(&b->urb); + usb_fill_bulk_urb(&b->urb, xfer->wa->usb_dev, + usb_sndbulkpipe(xfer->wa->usb_dev, + xfer->wa->dto_epd->bEndpointAddress), + &b->cmd, sizeof(b->cmd), __wa_xfer_abort_cb, b); + result = usb_submit_urb(&b->urb, GFP_ATOMIC); + if (result < 0) + goto error_submit; + return; /* callback frees! */ + + +error_submit: + if (printk_ratelimit()) + dev_err(dev, "xfer %p: Can't submit abort request: %d\n", + xfer, result); + kfree(b); +error_kmalloc: + return; + +} + +/* + * + * @returns < 0 on error, transfer segment request size if ok + */ +static ssize_t __wa_xfer_setup_sizes(struct wa_xfer *xfer, + enum wa_xfer_type *pxfer_type) +{ + ssize_t result; + struct device *dev = &xfer->wa->usb_iface->dev; + size_t maxpktsize; + struct urb *urb = xfer->urb; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + d_fnstart(3, dev, "(xfer %p [rpipe %p] urb %p)\n", + xfer, rpipe, urb); + switch (rpipe->descr.bmAttribute & 0x3) { + case USB_ENDPOINT_XFER_CONTROL: + *pxfer_type = WA_XFER_TYPE_CTL; + result = sizeof(struct wa_xfer_ctl); + break; + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_BULK: + *pxfer_type = WA_XFER_TYPE_BI; + result = sizeof(struct wa_xfer_bi); + break; + case USB_ENDPOINT_XFER_ISOC: + dev_err(dev, "FIXME: ISOC not implemented\n"); + result = -ENOSYS; + goto error; + default: + /* never happens */ + BUG(); + result = -EINVAL; /* shut gcc up */ + }; + xfer->is_inbound = urb->pipe & USB_DIR_IN ? 1 : 0; + xfer->is_dma = urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? 1 : 0; + xfer->seg_size = le16_to_cpu(rpipe->descr.wBlocks) + * 1 << (xfer->wa->wa_descr->bRPipeBlockSize - 1); + /* Compute the segment size and make sure it is a multiple of + * the maxpktsize (WUSB1.0[8.3.3.1])...not really too much of + * a check (FIXME) */ + maxpktsize = le16_to_cpu(rpipe->descr.wMaxPacketSize); + if (xfer->seg_size < maxpktsize) { + dev_err(dev, "HW BUG? seg_size %zu smaller than maxpktsize " + "%zu\n", xfer->seg_size, maxpktsize); + result = -EINVAL; + goto error; + } + xfer->seg_size = (xfer->seg_size / maxpktsize) * maxpktsize; + xfer->segs = (urb->transfer_buffer_length + xfer->seg_size - 1) + / xfer->seg_size; + if (xfer->segs >= WA_SEGS_MAX) { + dev_err(dev, "BUG? ops, number of segments %d bigger than %d\n", + (int)(urb->transfer_buffer_length / xfer->seg_size), + WA_SEGS_MAX); + result = -EINVAL; + goto error; + } + if (xfer->segs == 0 && *pxfer_type == WA_XFER_TYPE_CTL) + xfer->segs = 1; +error: + d_fnend(3, dev, "(xfer %p [rpipe %p] urb %p) = %d\n", + xfer, rpipe, urb, (int)result); + return result; +} + +/** Fill in the common request header and xfer-type specific data. */ +static void __wa_xfer_setup_hdr0(struct wa_xfer *xfer, + struct wa_xfer_hdr *xfer_hdr0, + enum wa_xfer_type xfer_type, + size_t xfer_hdr_size) +{ + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + + xfer_hdr0 = &xfer->seg[0]->xfer_hdr; + xfer_hdr0->bLength = xfer_hdr_size; + xfer_hdr0->bRequestType = xfer_type; + xfer_hdr0->wRPipe = rpipe->descr.wRPipeIndex; + xfer_hdr0->dwTransferID = wa_xfer_id(xfer); + xfer_hdr0->bTransferSegment = 0; + switch (xfer_type) { + case WA_XFER_TYPE_CTL: { + struct wa_xfer_ctl *xfer_ctl = + container_of(xfer_hdr0, struct wa_xfer_ctl, hdr); + xfer_ctl->bmAttribute = xfer->is_inbound ? 1 : 0; + BUG_ON(xfer->urb->transfer_flags & URB_NO_SETUP_DMA_MAP + && xfer->urb->setup_packet == NULL); + memcpy(&xfer_ctl->baSetupData, xfer->urb->setup_packet, + sizeof(xfer_ctl->baSetupData)); + break; + } + case WA_XFER_TYPE_BI: + break; + case WA_XFER_TYPE_ISO: + printk(KERN_ERR "FIXME: ISOC not implemented\n"); + default: + BUG(); + }; +} + +/* + * Callback for the OUT data phase of the segment request + * + * Check wa_seg_cb(); most comments also apply here because this + * function does almost the same thing and they work closely + * together. + * + * If the seg request has failed but this DTO phase has suceeded, + * wa_seg_cb() has already failed the segment and moved the + * status to WA_SEG_ERROR, so this will go through 'case 0' and + * effectively do nothing. + */ +static void wa_seg_dto_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned long flags; + unsigned rpipe_ready = 0; + u8 done = 0; + + d_fnstart(3, NULL, "(urb %p [%d])\n", urb, urb->status); + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + d_printf(2, dev, "xfer %p#%u: data out done (%d bytes)\n", + xfer, seg->index, urb->actual_length); + if (seg->status < WA_SEG_PENDING) + seg->status = WA_SEG_PENDING; + seg->result = urb->actual_length; + spin_unlock_irqrestore(&xfer->lock, flags); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: data out error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + if (seg->status != WA_SEG_ERROR) { + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + __wa_xfer_abort(xfer); + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + } + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } + d_fnend(3, NULL, "(urb %p [%d]) = void\n", urb, urb->status); +} + +/* + * Callback for the segment request + * + * If succesful transition state (unless already transitioned or + * outbound transfer); otherwise, take a note of the error, mark this + * segment done and try completion. + * + * Note we don't access until we are sure that the transfer hasn't + * been cancelled (ECONNRESET, ENOENT), which could mean that + * seg->xfer could be already gone. + * + * We have to check before setting the status to WA_SEG_PENDING + * because sometimes the xfer result callback arrives before this + * callback (geeeeeeze), so it might happen that we are already in + * another state. As well, we don't set it if the transfer is inbound, + * as in that case, wa_seg_dto_cb will do it when the OUT data phase + * finishes. + */ +static void wa_seg_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned long flags; + unsigned rpipe_ready; + u8 done = 0; + + d_fnstart(3, NULL, "(urb %p [%d])\n", urb, urb->status); + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + d_printf(2, dev, "xfer %p#%u: request done\n", + xfer, seg->index); + if (xfer->is_inbound && seg->status < WA_SEG_PENDING) + seg->status = WA_SEG_PENDING; + spin_unlock_irqrestore(&xfer->lock, flags); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: request error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + usb_unlink_urb(seg->dto_urb); + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + __wa_xfer_abort(xfer); + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } + d_fnend(3, NULL, "(urb %p [%d]) = void\n", urb, urb->status); +} + +/* + * Allocate the segs array and initialize each of them + * + * The segments are freed by wa_xfer_destroy() when the xfer use count + * drops to zero; however, because each segment is given the same life + * cycle as the USB URB it contains, it is actually freed by + * usb_put_urb() on the contained USB URB (twisted, eh?). + */ +static int __wa_xfer_setup_segs(struct wa_xfer *xfer, size_t xfer_hdr_size) +{ + int result, cnt; + size_t alloc_size = sizeof(*xfer->seg[0]) + - sizeof(xfer->seg[0]->xfer_hdr) + xfer_hdr_size; + struct usb_device *usb_dev = xfer->wa->usb_dev; + const struct usb_endpoint_descriptor *dto_epd = xfer->wa->dto_epd; + struct wa_seg *seg; + size_t buf_itr, buf_size, buf_itr_size; + + result = -ENOMEM; + xfer->seg = kzalloc(xfer->segs * sizeof(xfer->seg[0]), GFP_ATOMIC); + if (xfer->seg == NULL) + goto error_segs_kzalloc; + buf_itr = 0; + buf_size = xfer->urb->transfer_buffer_length; + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt] = kzalloc(alloc_size, GFP_ATOMIC); + if (seg == NULL) + goto error_seg_kzalloc; + wa_seg_init(seg); + seg->xfer = xfer; + seg->index = cnt; + usb_fill_bulk_urb(&seg->urb, usb_dev, + usb_sndbulkpipe(usb_dev, + dto_epd->bEndpointAddress), + &seg->xfer_hdr, xfer_hdr_size, + wa_seg_cb, seg); + buf_itr_size = buf_size > xfer->seg_size ? + xfer->seg_size : buf_size; + if (xfer->is_inbound == 0 && buf_size > 0) { + seg->dto_urb = usb_alloc_urb(0, GFP_ATOMIC); + if (seg->dto_urb == NULL) + goto error_dto_alloc; + usb_fill_bulk_urb( + seg->dto_urb, usb_dev, + usb_sndbulkpipe(usb_dev, + dto_epd->bEndpointAddress), + NULL, 0, wa_seg_dto_cb, seg); + if (xfer->is_dma) { + seg->dto_urb->transfer_dma = + xfer->urb->transfer_dma + buf_itr; + seg->dto_urb->transfer_flags |= + URB_NO_TRANSFER_DMA_MAP; + } else + seg->dto_urb->transfer_buffer = + xfer->urb->transfer_buffer + buf_itr; + seg->dto_urb->transfer_buffer_length = buf_itr_size; + } + seg->status = WA_SEG_READY; + buf_itr += buf_itr_size; + buf_size -= buf_itr_size; + } + return 0; + +error_dto_alloc: + kfree(xfer->seg[cnt]); + cnt--; +error_seg_kzalloc: + /* use the fact that cnt is left at were it failed */ + for (; cnt > 0; cnt--) { + if (xfer->is_inbound == 0) + kfree(xfer->seg[cnt]->dto_urb); + kfree(xfer->seg[cnt]); + } +error_segs_kzalloc: + return result; +} + +/* + * Allocates all the stuff needed to submit a transfer + * + * Breaks the whole data buffer in a list of segments, each one has a + * structure allocated to it and linked in xfer->seg[index] + * + * FIXME: merge setup_segs() and the last part of this function, no + * need to do two for loops when we could run everything in a + * single one + */ +static int __wa_xfer_setup(struct wa_xfer *xfer, struct urb *urb) +{ + int result; + struct device *dev = &xfer->wa->usb_iface->dev; + enum wa_xfer_type xfer_type = 0; /* shut up GCC */ + size_t xfer_hdr_size, cnt, transfer_size; + struct wa_xfer_hdr *xfer_hdr0, *xfer_hdr; + + d_fnstart(3, dev, "(xfer %p [rpipe %p] urb %p)\n", + xfer, xfer->ep->hcpriv, urb); + + result = __wa_xfer_setup_sizes(xfer, &xfer_type); + if (result < 0) + goto error_setup_sizes; + xfer_hdr_size = result; + result = __wa_xfer_setup_segs(xfer, xfer_hdr_size); + if (result < 0) { + dev_err(dev, "xfer %p: Failed to allocate %d segments: %d\n", + xfer, xfer->segs, result); + goto error_setup_segs; + } + /* Fill the first header */ + xfer_hdr0 = &xfer->seg[0]->xfer_hdr; + wa_xfer_id_init(xfer); + __wa_xfer_setup_hdr0(xfer, xfer_hdr0, xfer_type, xfer_hdr_size); + + /* Fill remainig headers */ + xfer_hdr = xfer_hdr0; + transfer_size = urb->transfer_buffer_length; + xfer_hdr0->dwTransferLength = transfer_size > xfer->seg_size ? + xfer->seg_size : transfer_size; + transfer_size -= xfer->seg_size; + for (cnt = 1; cnt < xfer->segs; cnt++) { + xfer_hdr = &xfer->seg[cnt]->xfer_hdr; + memcpy(xfer_hdr, xfer_hdr0, xfer_hdr_size); + xfer_hdr->bTransferSegment = cnt; + xfer_hdr->dwTransferLength = transfer_size > xfer->seg_size ? + cpu_to_le32(xfer->seg_size) + : cpu_to_le32(transfer_size); + xfer->seg[cnt]->status = WA_SEG_READY; + transfer_size -= xfer->seg_size; + } + xfer_hdr->bTransferSegment |= 0x80; /* this is the last segment */ + result = 0; +error_setup_segs: +error_setup_sizes: + d_fnend(3, dev, "(xfer %p [rpipe %p] urb %p) = %d\n", + xfer, xfer->ep->hcpriv, urb, result); + return result; +} + +/* + * + * + * rpipe->seg_lock is held! + */ +static int __wa_seg_submit(struct wa_rpipe *rpipe, struct wa_xfer *xfer, + struct wa_seg *seg) +{ + int result; + result = usb_submit_urb(&seg->urb, GFP_ATOMIC); + if (result < 0) { + printk(KERN_ERR "xfer %p#%u: REQ submit failed: %d\n", + xfer, seg->index, result); + goto error_seg_submit; + } + if (seg->dto_urb) { + result = usb_submit_urb(seg->dto_urb, GFP_ATOMIC); + if (result < 0) { + printk(KERN_ERR "xfer %p#%u: DTO submit failed: %d\n", + xfer, seg->index, result); + goto error_dto_submit; + } + } + seg->status = WA_SEG_SUBMITTED; + rpipe_avail_dec(rpipe); + return 0; + +error_dto_submit: + usb_unlink_urb(&seg->urb); +error_seg_submit: + seg->status = WA_SEG_ERROR; + seg->result = result; + return result; +} + +/* + * Execute more queued request segments until the maximum concurrent allowed + * + * The ugly unlock/lock sequence on the error path is needed as the + * xfer->lock normally nests the seg_lock and not viceversa. + * + */ +static void wa_xfer_delayed_run(struct wa_rpipe *rpipe) +{ + int result; + struct device *dev = &rpipe->wa->usb_iface->dev; + struct wa_seg *seg; + struct wa_xfer *xfer; + unsigned long flags; + + d_fnstart(1, dev, "(rpipe #%d) %d segments available\n", + le16_to_cpu(rpipe->descr.wRPipeIndex), + atomic_read(&rpipe->segs_available)); + spin_lock_irqsave(&rpipe->seg_lock, flags); + while (atomic_read(&rpipe->segs_available) > 0 + && !list_empty(&rpipe->seg_list)) { + seg = list_entry(rpipe->seg_list.next, struct wa_seg, + list_node); + list_del(&seg->list_node); + xfer = seg->xfer; + result = __wa_seg_submit(rpipe, xfer, seg); + d_printf(1, dev, "xfer %p#%u submitted from delayed " + "[%d segments available] %d\n", + xfer, seg->index, + atomic_read(&rpipe->segs_available), result); + if (unlikely(result < 0)) { + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + spin_lock_irqsave(&xfer->lock, flags); + __wa_xfer_abort(xfer); + xfer->segs_done++; + spin_unlock_irqrestore(&xfer->lock, flags); + spin_lock_irqsave(&rpipe->seg_lock, flags); + } + } + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + d_fnend(1, dev, "(rpipe #%d) = void, %d segments available\n", + le16_to_cpu(rpipe->descr.wRPipeIndex), + atomic_read(&rpipe->segs_available)); + +} + +/* + * + * xfer->lock is taken + * + * On failure submitting we just stop submitting and return error; + * wa_urb_enqueue_b() will execute the completion path + */ +static int __wa_xfer_submit(struct wa_xfer *xfer) +{ + int result; + struct wahc *wa = xfer->wa; + struct device *dev = &wa->usb_iface->dev; + unsigned cnt; + struct wa_seg *seg; + unsigned long flags; + struct wa_rpipe *rpipe = xfer->ep->hcpriv; + size_t maxrequests = le16_to_cpu(rpipe->descr.wRequests); + u8 available; + u8 empty; + + d_fnstart(3, dev, "(xfer %p [rpipe %p])\n", + xfer, xfer->ep->hcpriv); + + spin_lock_irqsave(&wa->xfer_list_lock, flags); + list_add_tail(&xfer->list_node, &wa->xfer_list); + spin_unlock_irqrestore(&wa->xfer_list_lock, flags); + + BUG_ON(atomic_read(&rpipe->segs_available) > maxrequests); + result = 0; + spin_lock_irqsave(&rpipe->seg_lock, flags); + for (cnt = 0; cnt < xfer->segs; cnt++) { + available = atomic_read(&rpipe->segs_available); + empty = list_empty(&rpipe->seg_list); + seg = xfer->seg[cnt]; + d_printf(2, dev, "xfer %p#%u: available %u empty %u (%s)\n", + xfer, cnt, available, empty, + available == 0 || !empty ? "delayed" : "submitted"); + if (available == 0 || !empty) { + d_printf(1, dev, "xfer %p#%u: delayed\n", xfer, cnt); + seg->status = WA_SEG_DELAYED; + list_add_tail(&seg->list_node, &rpipe->seg_list); + } else { + result = __wa_seg_submit(rpipe, xfer, seg); + if (result < 0) + goto error_seg_submit; + } + xfer->segs_submitted++; + } + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + d_fnend(3, dev, "(xfer %p [rpipe %p]) = void\n", xfer, + xfer->ep->hcpriv); + return result; + +error_seg_submit: + __wa_xfer_abort(xfer); + spin_unlock_irqrestore(&rpipe->seg_lock, flags); + d_fnend(3, dev, "(xfer %p [rpipe %p]) = void\n", xfer, + xfer->ep->hcpriv); + return result; +} + +/* + * Second part of a URB/transfer enqueuement + * + * Assumes this comes from wa_urb_enqueue() [maybe through + * wa_urb_enqueue_run()]. At this point: + * + * xfer->wa filled and refcounted + * xfer->ep filled with rpipe refcounted if + * delayed == 0 + * xfer->urb filled and refcounted (this is the case when called + * from wa_urb_enqueue() as we come from usb_submit_urb() + * and when called by wa_urb_enqueue_run(), as we took an + * extra ref dropped by _run() after we return). + * xfer->gfp filled + * + * If we fail at __wa_xfer_submit(), then we just check if we are done + * and if so, we run the completion procedure. However, if we are not + * yet done, we do nothing and wait for the completion handlers from + * the submitted URBs or from the xfer-result path to kick in. If xfer + * result never kicks in, the xfer will timeout from the USB code and + * dequeue() will be called. + */ +static void wa_urb_enqueue_b(struct wa_xfer *xfer) +{ + int result; + unsigned long flags; + struct urb *urb = xfer->urb; + struct wahc *wa = xfer->wa; + struct wusbhc *wusbhc = wa->wusb; + struct device *dev = &wa->usb_iface->dev; + struct wusb_dev *wusb_dev; + unsigned done; + + d_fnstart(3, dev, "(wa %p urb %p)\n", wa, urb); + result = rpipe_get_by_ep(wa, xfer->ep, urb, xfer->gfp); + if (result < 0) + goto error_rpipe_get; + result = -ENODEV; + /* FIXME: segmentation broken -- kills DWA */ + mutex_lock(&wusbhc->mutex); /* get a WUSB dev */ + if (urb->dev == NULL) + goto error_dev_gone; + wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); + if (wusb_dev == NULL) { + mutex_unlock(&wusbhc->mutex); + goto error_dev_gone; + } + mutex_unlock(&wusbhc->mutex); + + spin_lock_irqsave(&xfer->lock, flags); + xfer->wusb_dev = wusb_dev; + result = urb->status; + if (urb->status != -EINPROGRESS) + goto error_dequeued; + + result = __wa_xfer_setup(xfer, urb); + if (result < 0) + goto error_xfer_setup; + result = __wa_xfer_submit(xfer); + if (result < 0) + goto error_xfer_submit; + spin_unlock_irqrestore(&xfer->lock, flags); + d_fnend(3, dev, "(wa %p urb %p) = void\n", wa, urb); + return; + + /* this is basically wa_xfer_completion() broken up wa_xfer_giveback() + * does a wa_xfer_put() that will call wa_xfer_destroy() and clean + * upundo setup(). + */ +error_xfer_setup: +error_dequeued: + spin_unlock_irqrestore(&xfer->lock, flags); + /* FIXME: segmentation broken, kills DWA */ + if (wusb_dev) + wusb_dev_put(wusb_dev); +error_dev_gone: + rpipe_put(xfer->ep->hcpriv); +error_rpipe_get: + xfer->result = result; + wa_xfer_giveback(xfer); + d_fnend(3, dev, "(wa %p urb %p) = (void) %d\n", wa, urb, result); + return; + +error_xfer_submit: + done = __wa_xfer_is_done(xfer); + xfer->result = result; + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + d_fnend(3, dev, "(wa %p urb %p) = (void) %d\n", wa, urb, result); + return; +} + +/* + * Execute the delayed transfers in the Wire Adapter @wa + * + * We need to be careful here, as dequeue() could be called in the + * middle. That's why we do the whole thing under the + * wa->xfer_list_lock. If dequeue() jumps in, it first locks urb->lock + * and then checks the list -- so as we would be acquiring in inverse + * order, we just drop the lock once we have the xfer and reacquire it + * later. + */ +void wa_urb_enqueue_run(struct work_struct *ws) +{ + struct wahc *wa = container_of(ws, struct wahc, xfer_work); + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer *xfer, *next; + struct urb *urb; + + d_fnstart(3, dev, "(wa %p)\n", wa); + spin_lock_irq(&wa->xfer_list_lock); + list_for_each_entry_safe(xfer, next, &wa->xfer_delayed_list, + list_node) { + list_del_init(&xfer->list_node); + spin_unlock_irq(&wa->xfer_list_lock); + + urb = xfer->urb; + wa_urb_enqueue_b(xfer); + usb_put_urb(urb); /* taken when queuing */ + + spin_lock_irq(&wa->xfer_list_lock); + } + spin_unlock_irq(&wa->xfer_list_lock); + d_fnend(3, dev, "(wa %p) = void\n", wa); +} +EXPORT_SYMBOL_GPL(wa_urb_enqueue_run); + +/* + * Submit a transfer to the Wire Adapter in a delayed way + * + * The process of enqueuing involves possible sleeps() [see + * enqueue_b(), for the rpipe_get() and the mutex_lock()]. If we are + * in an atomic section, we defer the enqueue_b() call--else we call direct. + * + * @urb: We own a reference to it done by the HCI Linux USB stack that + * will be given up by calling usb_hcd_giveback_urb() or by + * returning error from this function -> ergo we don't have to + * refcount it. + */ +int wa_urb_enqueue(struct wahc *wa, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t gfp) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer *xfer; + unsigned long my_flags; + unsigned cant_sleep = irqs_disabled() | in_atomic(); + + d_fnstart(3, dev, "(wa %p ep %p urb %p [%d] gfp 0x%x)\n", + wa, ep, urb, urb->transfer_buffer_length, gfp); + + if (urb->transfer_buffer == NULL + && !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + && urb->transfer_buffer_length != 0) { + dev_err(dev, "BUG? urb %p: NULL xfer buffer & NODMA\n", urb); + dump_stack(); + } + + result = -ENOMEM; + xfer = kzalloc(sizeof(*xfer), gfp); + if (xfer == NULL) + goto error_kmalloc; + + result = -ENOENT; + if (urb->status != -EINPROGRESS) /* cancelled */ + goto error_dequeued; /* before starting? */ + wa_xfer_init(xfer); + xfer->wa = wa_get(wa); + xfer->urb = urb; + xfer->gfp = gfp; + xfer->ep = ep; + urb->hcpriv = xfer; + d_printf(2, dev, "xfer %p urb %p pipe 0x%02x [%d bytes] %s %s %s\n", + xfer, urb, urb->pipe, urb->transfer_buffer_length, + urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP ? "dma" : "nodma", + urb->pipe & USB_DIR_IN ? "inbound" : "outbound", + cant_sleep ? "deferred" : "inline"); + if (cant_sleep) { + usb_get_urb(urb); + spin_lock_irqsave(&wa->xfer_list_lock, my_flags); + list_add_tail(&xfer->list_node, &wa->xfer_delayed_list); + spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags); + queue_work(wusbd, &wa->xfer_work); + } else { + wa_urb_enqueue_b(xfer); + } + d_fnend(3, dev, "(wa %p ep %p urb %p [%d] gfp 0x%x) = 0\n", + wa, ep, urb, urb->transfer_buffer_length, gfp); + return 0; + +error_dequeued: + kfree(xfer); +error_kmalloc: + d_fnend(3, dev, "(wa %p ep %p urb %p [%d] gfp 0x%x) = %d\n", + wa, ep, urb, urb->transfer_buffer_length, gfp, result); + return result; +} +EXPORT_SYMBOL_GPL(wa_urb_enqueue); + +/* + * Dequeue a URB and make sure uwb_hcd_giveback_urb() [completion + * handler] is called. + * + * Until a transfer goes successfully through wa_urb_enqueue() it + * needs to be dequeued with completion calling; when stuck in delayed + * or before wa_xfer_setup() is called, we need to do completion. + * + * not setup If there is no hcpriv yet, that means that that enqueue + * still had no time to set the xfer up. Because + * urb->status should be other than -EINPROGRESS, + * enqueue() will catch that and bail out. + * + * If the transfer has gone through setup, we just need to clean it + * up. If it has gone through submit(), we have to abort it [with an + * asynch request] and then make sure we cancel each segment. + * + */ +int wa_urb_dequeue(struct wahc *wa, struct urb *urb) +{ + struct device *dev = &wa->usb_iface->dev; + unsigned long flags, flags2; + struct wa_xfer *xfer; + struct wa_seg *seg; + struct wa_rpipe *rpipe; + unsigned cnt; + unsigned rpipe_ready = 0; + + d_fnstart(3, dev, "(wa %p, urb %p)\n", wa, urb); + + d_printf(1, dev, "xfer %p urb %p: aborting\n", urb->hcpriv, urb); + xfer = urb->hcpriv; + if (xfer == NULL) { + /* NOthing setup yet enqueue will see urb->status != + * -EINPROGRESS (by hcd layer) and bail out with + * error, no need to do completion + */ + BUG_ON(urb->status == -EINPROGRESS); + goto out; + } + spin_lock_irqsave(&xfer->lock, flags); + rpipe = xfer->ep->hcpriv; + /* Check the delayed list -> if there, release and complete */ + spin_lock_irqsave(&wa->xfer_list_lock, flags2); + if (!list_empty(&xfer->list_node) && xfer->seg == NULL) + goto dequeue_delayed; + spin_unlock_irqrestore(&wa->xfer_list_lock, flags2); + if (xfer->seg == NULL) /* still hasn't reached */ + goto out_unlock; /* setup(), enqueue_b() completes */ + /* Ok, the xfer is in flight already, it's been setup and submitted.*/ + __wa_xfer_abort(xfer); + for (cnt = 0; cnt < xfer->segs; cnt++) { + seg = xfer->seg[cnt]; + switch (seg->status) { + case WA_SEG_NOTREADY: + case WA_SEG_READY: + printk(KERN_ERR "xfer %p#%u: dequeue bad state %u\n", + xfer, cnt, seg->status); + WARN_ON(1); + break; + case WA_SEG_DELAYED: + seg->status = WA_SEG_ABORTED; + spin_lock_irqsave(&rpipe->seg_lock, flags2); + list_del(&seg->list_node); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + spin_unlock_irqrestore(&rpipe->seg_lock, flags2); + break; + case WA_SEG_SUBMITTED: + seg->status = WA_SEG_ABORTED; + usb_unlink_urb(&seg->urb); + if (xfer->is_inbound == 0) + usb_unlink_urb(seg->dto_urb); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_PENDING: + seg->status = WA_SEG_ABORTED; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_DTI_PENDING: + usb_unlink_urb(wa->dti_urb); + seg->status = WA_SEG_ABORTED; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + break; + case WA_SEG_DONE: + case WA_SEG_ERROR: + case WA_SEG_ABORTED: + break; + } + } + xfer->result = urb->status; /* -ENOENT or -ECONNRESET */ + __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + d_fnend(3, dev, "(wa %p, urb %p) = 0\n", wa, urb); + return 0; + +out_unlock: + spin_unlock_irqrestore(&xfer->lock, flags); +out: + d_fnend(3, dev, "(wa %p, urb %p) = 0\n", wa, urb); + return 0; + +dequeue_delayed: + list_del_init(&xfer->list_node); + spin_unlock_irqrestore(&wa->xfer_list_lock, flags2); + xfer->result = urb->status; + spin_unlock_irqrestore(&xfer->lock, flags); + wa_xfer_giveback(xfer); + usb_put_urb(urb); /* we got a ref in enqueue() */ + d_fnend(3, dev, "(wa %p, urb %p) = 0\n", wa, urb); + return 0; +} +EXPORT_SYMBOL_GPL(wa_urb_dequeue); + +/* + * Translation from WA status codes (WUSB1.0 Table 8.15) to errno + * codes + * + * Positive errno values are internal inconsistencies and should be + * flagged louder. Negative are to be passed up to the user in the + * normal way. + * + * @status: USB WA status code -- high two bits are stripped. + */ +static int wa_xfer_status_to_errno(u8 status) +{ + int errno; + u8 real_status = status; + static int xlat[] = { + [WA_XFER_STATUS_SUCCESS] = 0, + [WA_XFER_STATUS_HALTED] = -EPIPE, + [WA_XFER_STATUS_DATA_BUFFER_ERROR] = -ENOBUFS, + [WA_XFER_STATUS_BABBLE] = -EOVERFLOW, + [WA_XFER_RESERVED] = EINVAL, + [WA_XFER_STATUS_NOT_FOUND] = 0, + [WA_XFER_STATUS_INSUFFICIENT_RESOURCE] = -ENOMEM, + [WA_XFER_STATUS_TRANSACTION_ERROR] = -EILSEQ, + [WA_XFER_STATUS_ABORTED] = -EINTR, + [WA_XFER_STATUS_RPIPE_NOT_READY] = EINVAL, + [WA_XFER_INVALID_FORMAT] = EINVAL, + [WA_XFER_UNEXPECTED_SEGMENT_NUMBER] = EINVAL, + [WA_XFER_STATUS_RPIPE_TYPE_MISMATCH] = EINVAL, + }; + status &= 0x3f; + + if (status == 0) + return 0; + if (status >= ARRAY_SIZE(xlat)) { + if (printk_ratelimit()) + printk(KERN_ERR "%s(): BUG? " + "Unknown WA transfer status 0x%02x\n", + __func__, real_status); + return -EINVAL; + } + errno = xlat[status]; + if (unlikely(errno > 0)) { + if (printk_ratelimit()) + printk(KERN_ERR "%s(): BUG? " + "Inconsistent WA status: 0x%02x\n", + __func__, real_status); + errno = -errno; + } + return errno; +} + +/* + * Process a xfer result completion message + * + * inbound transfers: need to schedule a DTI read + * + * FIXME: this functio needs to be broken up in parts + */ +static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + unsigned long flags; + u8 seg_idx; + struct wa_seg *seg; + struct wa_rpipe *rpipe; + struct wa_xfer_result *xfer_result = wa->xfer_result; + u8 done = 0; + u8 usb_status; + unsigned rpipe_ready = 0; + + d_fnstart(3, dev, "(wa %p xfer %p)\n", wa, xfer); + spin_lock_irqsave(&xfer->lock, flags); + seg_idx = xfer_result->bTransferSegment & 0x7f; + if (unlikely(seg_idx >= xfer->segs)) + goto error_bad_seg; + seg = xfer->seg[seg_idx]; + rpipe = xfer->ep->hcpriv; + usb_status = xfer_result->bTransferStatus; + d_printf(2, dev, "xfer %p#%u: bTransferStatus 0x%02x (seg %u)\n", + xfer, seg_idx, usb_status, seg->status); + if (seg->status == WA_SEG_ABORTED + || seg->status == WA_SEG_ERROR) /* already handled */ + goto segment_aborted; + if (seg->status == WA_SEG_SUBMITTED) /* ops, got here */ + seg->status = WA_SEG_PENDING; /* before wa_seg{_dto}_cb() */ + if (seg->status != WA_SEG_PENDING) { + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: Bad segment state %u\n", + xfer, seg_idx, seg->status); + seg->status = WA_SEG_PENDING; /* workaround/"fix" it */ + } + if (usb_status & 0x80) { + seg->result = wa_xfer_status_to_errno(usb_status); + dev_err(dev, "DTI: xfer %p#%u failed (0x%02x)\n", + xfer, seg->index, usb_status); + goto error_complete; + } + /* FIXME: we ignore warnings, tally them for stats */ + if (usb_status & 0x40) /* Warning?... */ + usb_status = 0; /* ... pass */ + if (xfer->is_inbound) { /* IN data phase: read to buffer */ + seg->status = WA_SEG_DTI_PENDING; + BUG_ON(wa->buf_in_urb->status == -EINPROGRESS); + if (xfer->is_dma) { + wa->buf_in_urb->transfer_dma = + xfer->urb->transfer_dma + + seg_idx * xfer->seg_size; + wa->buf_in_urb->transfer_flags + |= URB_NO_TRANSFER_DMA_MAP; + } else { + wa->buf_in_urb->transfer_buffer = + xfer->urb->transfer_buffer + + seg_idx * xfer->seg_size; + wa->buf_in_urb->transfer_flags + &= ~URB_NO_TRANSFER_DMA_MAP; + } + wa->buf_in_urb->transfer_buffer_length = + le32_to_cpu(xfer_result->dwTransferLength); + wa->buf_in_urb->context = seg; + result = usb_submit_urb(wa->buf_in_urb, GFP_ATOMIC); + if (result < 0) + goto error_submit_buf_in; + } else { + /* OUT data phase, complete it -- */ + seg->status = WA_SEG_DONE; + seg->result = le32_to_cpu(xfer_result->dwTransferLength); + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + } + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + d_fnend(3, dev, "(wa %p xfer %p) = void\n", wa, xfer); + return; + + +error_submit_buf_in: + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: can't submit DTI data phase: %d\n", + xfer, seg_idx, result); + seg->result = result; +error_complete: + seg->status = WA_SEG_ERROR; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + __wa_xfer_abort(xfer); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + d_fnend(3, dev, "(wa %p xfer %p) = void [segment/DTI-submit error]\n", + wa, xfer); + return; + + +error_bad_seg: + spin_unlock_irqrestore(&xfer->lock, flags); + wa_urb_dequeue(wa, xfer->urb); + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: bad segment\n", xfer, seg_idx); + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + d_fnend(3, dev, "(wa %p xfer %p) = void [bad seg]\n", wa, xfer); + return; + + +segment_aborted: + /* nothing to do, as the aborter did the completion */ + spin_unlock_irqrestore(&xfer->lock, flags); + d_fnend(3, dev, "(wa %p xfer %p) = void [segment aborted]\n", + wa, xfer); + return; + +} + +/* + * Callback for the IN data phase + * + * If succesful transition state; otherwise, take a note of the + * error, mark this segment done and try completion. + * + * Note we don't access until we are sure that the transfer hasn't + * been cancelled (ECONNRESET, ENOENT), which could mean that + * seg->xfer could be already gone. + */ +static void wa_buf_in_cb(struct urb *urb) +{ + struct wa_seg *seg = urb->context; + struct wa_xfer *xfer = seg->xfer; + struct wahc *wa; + struct device *dev; + struct wa_rpipe *rpipe; + unsigned rpipe_ready; + unsigned long flags; + u8 done = 0; + + d_fnstart(3, NULL, "(urb %p [%d])\n", urb, urb->status); + switch (urb->status) { + case 0: + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + d_printf(2, dev, "xfer %p#%u: data in done (%zu bytes)\n", + xfer, seg->index, (size_t)urb->actual_length); + seg->status = WA_SEG_DONE; + seg->result = urb->actual_length; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + break; + case -ECONNRESET: /* URB unlinked; no need to do anything */ + case -ENOENT: /* as it was done by the who unlinked us */ + break; + default: /* Other errors ... */ + spin_lock_irqsave(&xfer->lock, flags); + wa = xfer->wa; + dev = &wa->usb_iface->dev; + rpipe = xfer->ep->hcpriv; + if (printk_ratelimit()) + dev_err(dev, "xfer %p#%u: data in error %d\n", + xfer, seg->index, urb->status); + if (edc_inc(&wa->nep_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)){ + dev_err(dev, "DTO: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + } + seg->status = WA_SEG_ERROR; + seg->result = urb->status; + xfer->segs_done++; + rpipe_ready = rpipe_avail_inc(rpipe); + __wa_xfer_abort(xfer); + done = __wa_xfer_is_done(xfer); + spin_unlock_irqrestore(&xfer->lock, flags); + if (done) + wa_xfer_completion(xfer); + if (rpipe_ready) + wa_xfer_delayed_run(rpipe); + } + d_fnend(3, NULL, "(urb %p [%d]) = void\n", urb, urb->status); +} + +/* + * Handle an incoming transfer result buffer + * + * Given a transfer result buffer, it completes the transfer (possibly + * scheduling and buffer in read) and then resubmits the DTI URB for a + * new transfer result read. + * + * + * The xfer_result DTI URB state machine + * + * States: OFF | RXR (Read-Xfer-Result) | RBI (Read-Buffer-In) + * + * We start in OFF mode, the first xfer_result notification [through + * wa_handle_notif_xfer()] moves us to RXR by posting the DTI-URB to + * read. + * + * We receive a buffer -- if it is not a xfer_result, we complain and + * repost the DTI-URB. If it is a xfer_result then do the xfer seg + * request accounting. If it is an IN segment, we move to RBI and post + * a BUF-IN-URB to the right buffer. The BUF-IN-URB callback will + * repost the DTI-URB and move to RXR state. if there was no IN + * segment, it will repost the DTI-URB. + * + * We go back to OFF when we detect a ENOENT or ESHUTDOWN (or too many + * errors) in the URBs. + */ +static void wa_xfer_result_cb(struct urb *urb) +{ + int result; + struct wahc *wa = urb->context; + struct device *dev = &wa->usb_iface->dev; + struct wa_xfer_result *xfer_result; + u32 xfer_id; + struct wa_xfer *xfer; + u8 usb_status; + + d_fnstart(3, dev, "(%p)\n", wa); + BUG_ON(wa->dti_urb != urb); + switch (wa->dti_urb->status) { + case 0: + /* We have a xfer result buffer; check it */ + d_printf(2, dev, "DTI: xfer result %d bytes at %p\n", + urb->actual_length, urb->transfer_buffer); + d_dump(3, dev, urb->transfer_buffer, urb->actual_length); + if (wa->dti_urb->actual_length != sizeof(*xfer_result)) { + dev_err(dev, "DTI Error: xfer result--bad size " + "xfer result (%d bytes vs %zu needed)\n", + urb->actual_length, sizeof(*xfer_result)); + break; + } + xfer_result = wa->xfer_result; + if (xfer_result->hdr.bLength != sizeof(*xfer_result)) { + dev_err(dev, "DTI Error: xfer result--" + "bad header length %u\n", + xfer_result->hdr.bLength); + break; + } + if (xfer_result->hdr.bNotifyType != WA_XFER_RESULT) { + dev_err(dev, "DTI Error: xfer result--" + "bad header type 0x%02x\n", + xfer_result->hdr.bNotifyType); + break; + } + usb_status = xfer_result->bTransferStatus & 0x3f; + if (usb_status == WA_XFER_STATUS_ABORTED + || usb_status == WA_XFER_STATUS_NOT_FOUND) + /* taken care of already */ + break; + xfer_id = xfer_result->dwTransferID; + xfer = wa_xfer_get_by_id(wa, xfer_id); + if (xfer == NULL) { + /* FIXME: transaction might have been cancelled */ + dev_err(dev, "DTI Error: xfer result--" + "unknown xfer 0x%08x (status 0x%02x)\n", + xfer_id, usb_status); + break; + } + wa_xfer_result_chew(wa, xfer); + wa_xfer_put(xfer); + break; + case -ENOENT: /* (we killed the URB)...so, no broadcast */ + case -ESHUTDOWN: /* going away! */ + dev_dbg(dev, "DTI: going down! %d\n", urb->status); + goto out; + default: + /* Unknown error */ + if (edc_inc(&wa->dti_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "DTI: URB max acceptable errors " + "exceeded, resetting device\n"); + wa_reset_all(wa); + goto out; + } + if (printk_ratelimit()) + dev_err(dev, "DTI: URB error %d\n", urb->status); + break; + } + /* Resubmit the DTI URB */ + result = usb_submit_urb(wa->dti_urb, GFP_ATOMIC); + if (result < 0) { + dev_err(dev, "DTI Error: Could not submit DTI URB (%d), " + "resetting\n", result); + wa_reset_all(wa); + } +out: + d_fnend(3, dev, "(%p) = void\n", wa); + return; +} + +/* + * Transfer complete notification + * + * Called from the notif.c code. We get a notification on EP2 saying + * that some endpoint has some transfer result data available. We are + * about to read it. + * + * To speed up things, we always have a URB reading the DTI URB; we + * don't really set it up and start it until the first xfer complete + * notification arrives, which is what we do here. + * + * Follow up in wa_xfer_result_cb(), as that's where the whole state + * machine starts. + * + * So here we just initialize the DTI URB for reading transfer result + * notifications and also the buffer-in URB, for reading buffers. Then + * we just submit the DTI URB. + * + * @wa shall be referenced + */ +void wa_handle_notif_xfer(struct wahc *wa, struct wa_notif_hdr *notif_hdr) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + struct wa_notif_xfer *notif_xfer; + const struct usb_endpoint_descriptor *dti_epd = wa->dti_epd; + + d_fnstart(4, dev, "(%p, %p)\n", wa, notif_hdr); + notif_xfer = container_of(notif_hdr, struct wa_notif_xfer, hdr); + BUG_ON(notif_hdr->bNotifyType != WA_NOTIF_TRANSFER); + + if ((0x80 | notif_xfer->bEndpoint) != dti_epd->bEndpointAddress) { + /* FIXME: hardcoded limitation, adapt */ + dev_err(dev, "BUG: DTI ep is %u, not %u (hack me)\n", + notif_xfer->bEndpoint, dti_epd->bEndpointAddress); + goto error; + } + if (wa->dti_urb != NULL) /* DTI URB already started */ + goto out; + + wa->dti_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->dti_urb == NULL) { + dev_err(dev, "Can't allocate DTI URB\n"); + goto error_dti_urb_alloc; + } + usb_fill_bulk_urb( + wa->dti_urb, wa->usb_dev, + usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + wa->xfer_result, wa->xfer_result_size, + wa_xfer_result_cb, wa); + + wa->buf_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (wa->buf_in_urb == NULL) { + dev_err(dev, "Can't allocate BUF-IN URB\n"); + goto error_buf_in_urb_alloc; + } + usb_fill_bulk_urb( + wa->buf_in_urb, wa->usb_dev, + usb_rcvbulkpipe(wa->usb_dev, 0x80 | notif_xfer->bEndpoint), + NULL, 0, wa_buf_in_cb, wa); + result = usb_submit_urb(wa->dti_urb, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "DTI Error: Could not submit DTI URB (%d), " + "resetting\n", result); + goto error_dti_urb_submit; + } +out: + d_fnend(4, dev, "(%p, %p) = void\n", wa, notif_hdr); + return; + +error_dti_urb_submit: + usb_put_urb(wa->buf_in_urb); +error_buf_in_urb_alloc: + usb_put_urb(wa->dti_urb); + wa->dti_urb = NULL; +error_dti_urb_alloc: +error: + wa_reset_all(wa); + d_fnend(4, dev, "(%p, %p) = void\n", wa, notif_hdr); + return; +} -- cgit v1.2.3 From d09318b8ab2eabb65b6fa0dc04dab1822846eabb Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:30 +0100 Subject: wusb: add HWA host controller driver Add a driver for Wireless USB host controllers connected via USB (a Host Wire Adapter or HWA). Signed-off-by: David Vrabel --- drivers/usb/Makefile | 1 + drivers/usb/host/Kconfig | 14 + drivers/usb/host/Makefile | 1 + drivers/usb/host/hwa-hc.c | 925 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 941 insertions(+) create mode 100644 drivers/usb/host/hwa-hc.c diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 19ede32f0df..8b7c419b876 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += host/ obj-$(CONFIG_USB_SL811_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ +obj-$(CONFIG_USB_HWA_HCD) += host/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index d3ba351398e..515044129bb 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -317,3 +317,17 @@ config USB_WHCI_HCD To compile this driver a module, choose M here: the module will be called "whci-hcd". + +config USB_HWA_HCD + tristate "Host Wire Adapter (HWA) driver" + depends on USB + select USB_WUSB + select UWB_HWA + help + This driver enables you to connect Wireless USB devices to + your system using a Host Wire Adaptor USB dongle. This is an + UWB Radio Controller and WUSB Host Controller connected to + your machine via USB (specified in WUSB1.0). + + To compile this driver a module, choose M here: the module + will be called "hwa-hc". diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index c90cf865ccf..23be2222404 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o +obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c new file mode 100644 index 00000000000..64be4d88df1 --- /dev/null +++ b/drivers/usb/host/hwa-hc.c @@ -0,0 +1,925 @@ +/* + * Host Wire Adapter: + * Driver glue, HWA-specific functions, bridges to WAHC and WUSBHC + * + * Copyright (C) 2005-2006 Intel Corporation + * Inaky Perez-Gonzalez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * + * The HWA driver is a simple layer that forwards requests to the WAHC + * (Wire Adater Host Controller) or WUSBHC (Wireless USB Host + * Controller) layers. + * + * Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB + * Host Controller that is connected to your system via USB (a USB + * dongle that implements a USB host...). There is also a Device Wired + * Adaptor, DWA (Wireless USB hub) that uses the same mechanism for + * transferring data (it is after all a USB host connected via + * Wireless USB), we have a common layer called Wire Adapter Host + * Controller that does all the hard work. The WUSBHC (Wireless USB + * Host Controller) is the part common to WUSB Host Controllers, the + * HWA and the PCI-based one, that is implemented following the WHCI + * spec. All these layers are implemented in ../wusbcore. + * + * The main functions are hwahc_op_urb_{en,de}queue(), that pass the + * job of converting a URB to a Wire Adapter + * + * Entry points: + * + * hwahc_driver_*() Driver initialization, registration and + * teardown. + * + * hwahc_probe() New device came up, create an instance for + * it [from device enumeration]. + * + * hwahc_disconnect() Remove device instance [from device + * enumeration]. + * + * [__]hwahc_op_*() Host-Wire-Adaptor specific functions for + * starting/stopping/etc (some might be made also + * DWA). + */ +#include +#include +#include +#include +#include +#include +#include +#include "../wusbcore/wa-hc.h" +#include "../wusbcore/wusbhc.h" + +#define D_LOCAL 0 +#include + +struct hwahc { + struct wusbhc wusbhc; /* has to be 1st */ + struct wahc wa; + u8 buffer[16]; /* for misc usb transactions */ +}; + +/** + * FIXME should be wusbhc + * + * NOTE: we need to cache the Cluster ID because later...there is no + * way to get it :) + */ +static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_CLUSTER_ID, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + cluster_id, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n", + cluster_id, result); + else + wusbhc->cluster_id = cluster_id; + dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id); + return result; +} + +static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_NUM_DNTS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | slots, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Reset a WUSB host controller and wait for it to complete doing it. + * + * @usb_hcd: Pointer to WUSB Host Controller instance. + * + */ +static int hwahc_op_reset(struct usb_hcd *usb_hcd) +{ + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + wa_nep_disarm(&hwahc->wa); + result = __wa_set_feature(&hwahc->wa, WA_RESET); + if (result < 0) { + dev_err(dev, "error commanding HC to reset: %d\n", result); + goto error_unlock; + } + d_printf(3, dev, "reset: waiting for device to change state\n"); + result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0); + if (result < 0) { + dev_err(dev, "error waiting for HC to reset: %d\n", result); + goto error_unlock; + } +error_unlock: + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; +} + +/* + * FIXME: break this function up + */ +static int hwahc_op_start(struct usb_hcd *usb_hcd) +{ + u8 addr; + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + /* Set up a Host Info WUSB Information Element */ + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + result = -ENOSPC; + mutex_lock(&wusbhc->mutex); + /* Start the numbering from the top so that the bottom + * range of the unauth addr space is used for devices, + * the top for HCs; use 0xfe - RC# */ + addr = wusb_cluster_id_get(); + if (addr == 0) + goto error_cluster_id_get; + result = __hwahc_set_cluster_id(hwahc, addr); + if (result < 0) + goto error_set_cluster_id; + + result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "cannot listen to notifications: %d\n", result); + goto error_stop; + } + usb_hcd->uses_new_polling = 1; + usb_hcd->poll_rh = 1; + usb_hcd->state = HC_STATE_RUNNING; + result = 0; +out: + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; + +error_stop: + __wa_stop(&hwahc->wa); +error_set_cluster_id: + wusb_cluster_id_put(wusbhc->cluster_id); +error_cluster_id_get: + goto out; + +} + +/* + * FIXME: break this function up + */ +static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + /* Set up a Host Info WUSB Information Element */ + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + result = -ENOSPC; + + result = __wa_set_feature(&hwahc->wa, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error commanding HC to start: %d\n", result); + goto error_stop; + } + result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error waiting for HC to start: %d\n", result); + goto error_stop; + } + result = 0; +out: + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return result; + +error_stop: + result = __wa_clear_feature(&hwahc->wa, WA_ENABLE); + goto out; +} + +static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + dev_err(wusbhc->dev, "%s (%p [%p], 0x%lx) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc, *(unsigned long *) &msg); + return -ENOSYS; +} + +static int hwahc_op_resume(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc); + return -ENOSYS; +} + +static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + /* Nothing for now */ + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return; +} + +/* + * No need to abort pipes, as when this is called, all the children + * has been disconnected and that has done it [through + * usb_disable_interface() -> usb_disable_endpoint() -> + * hwahc_op_ep_disable() - >rpipe_ep_disable()]. + */ +static void hwahc_op_stop(struct usb_hcd *usb_hcd) +{ + int result; + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + + d_fnstart(4, dev, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + wusbhc_stop(wusbhc); + wa_nep_disarm(&hwahc->wa); + result = __wa_stop(&hwahc->wa); + wusb_cluster_id_put(wusbhc->cluster_id); + mutex_unlock(&wusbhc->mutex); + d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); + return; +} + +static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, + usb_hcd, hwahc); + return -ENOSYS; +} + +static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, + gfp_t gfp) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp); +} + +static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, + int status) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + return wa_urb_dequeue(&hwahc->wa, urb); +} + +/* + * Release resources allocated for an endpoint + * + * If there is an associated rpipe to this endpoint, go ahead and put it. + */ +static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + rpipe_ep_disable(&hwahc->wa, ep); +} + +/* + * Set the UWB MAS allocation for the WUSB cluster + * + * @stream_index: stream to use (-1 for cancelling the allocation) + * @mas: mas bitmap to use + */ +static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index, + const struct uwb_mas_bm *mas) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + struct device *dev = &wa->usb_iface->dev; + u8 mas_le[UWB_NUM_MAS/8]; + + /* Set the stream index */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_STREAM_IDX, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + stream_index, + wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot set WUSB stream index: %d\n", result); + goto out; + } + uwb_mas_bm_copy_le(mas_le, mas); + /* Set the MAS allocation */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_WUSB_MAS, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber, + mas_le, 32, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result); +out: + return result; +} + +/* + * Add an IE to the host's MMC + * + * @interval: See WUSB1.0[8.5.3.1] + * @repeat_cnt: See WUSB1.0[8.5.3.1] + * @handle: See WUSB1.0[8.5.3.1] + * @wuie: Pointer to the header of the WUSB IE data to add. + * MUST BE allocated in a kmalloc buffer (no stack or + * vmalloc). + * + * NOTE: the format of the WUSB IEs for MMCs are different to the + * normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length + + * Id in WUSB IEs). Standards...you gotta love'em. + */ +static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval, + u8 repeat_cnt, u8 handle, + struct wuie_hdr *wuie) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_ADD_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + interval << 8 | repeat_cnt, + handle << 8 | iface_no, + wuie, wuie->bLength, 1000 /* FIXME: arbitrary */); +} + +/* + * Remove an IE to the host's MMC + * + * @handle: See WUSB1.0[8.5.3.1] + */ +static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_REMOVE_MMC_IE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, handle << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); +} + +/* + * Update device information for a given fake port + * + * @port_idx: Fake port to which device is connected (wusbhc index, not + * USB port number). + */ +static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc, + struct wusb_dev *wusb_dev) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct hwa_dev_info *dev_info; + int ret; + + /* fill out the Device Info buffer and send it */ + dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL); + if (!dev_info) + return -ENOMEM; + uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability, + &wusb_dev->availability); + dev_info->bDeviceAddress = wusb_dev->addr; + + /* + * If the descriptors haven't been read yet, use a default PHY + * rate of 53.3 Mbit/s only. The correct value will be used + * when this will be called again as part of the + * authentication process (which occurs after the descriptors + * have been read). + */ + if (wusb_dev->wusb_cap_descr) + dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates; + else + dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53); + + ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_SET_DEV_INFO, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, wusb_dev->port_idx << 8 | iface_no, + dev_info, sizeof(struct hwa_dev_info), + 1000 /* FIXME: arbitrary */); + kfree(dev_info); + return ret; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size, + u8 key_idx) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + struct usb_key_descriptor *keyd; + size_t keyd_len; + + keyd_len = sizeof(*keyd) + key_size; + keyd = kzalloc(keyd_len, GFP_KERNEL); + if (keyd == NULL) + return -ENOMEM; + + keyd->bLength = keyd_len; + keyd->bDescriptorType = USB_DT_KEY; + keyd->tTKID[0] = (tkid >> 0) & 0xff; + keyd->tTKID[1] = (tkid >> 8) & 0xff; + keyd->tTKID[2] = (tkid >> 16) & 0xff; + memcpy(keyd->bKeyData, key, key_size); + + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_DESCRIPTOR, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + USB_DT_KEY << 8 | key_idx, + port_idx << 8 | iface_no, + keyd, keyd_len, 1000 /* FIXME: arbitrary */); + + memset(keyd, 0, sizeof(*keyd)); /* clear keys etc. */ + kfree(keyd); + return result; +} + +/* + * Set host's idea of which encryption (and key) method to use when + * talking to ad evice on a given port. + * + * If key is NULL, it means disable encryption for that "virtual port" + * (used when we disconnect). + */ +static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, + const void *key, size_t key_size) +{ + int result = -ENOMEM; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + u8 encryption_value; + + /* Tell the host which key to use to talk to the device */ + if (key) { + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + result = __hwahc_dev_set_key(wusbhc, port_idx, tkid, + key, key_size, key_idx); + if (result < 0) + goto error_set_key; + encryption_value = wusbhc->ccm1_etd->bEncryptionValue; + } else { + /* FIXME: this should come from wusbhc->etd[UNSECURE].value */ + encryption_value = 0; + } + + /* Set the encryption type for commmunicating with the device */ + result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + USB_REQ_SET_ENCRYPTION, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + encryption_value, port_idx << 8 | iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (result < 0) + dev_err(wusbhc->dev, "Can't set host's WUSB encryption for " + "port index %u to %s (value %d): %d\n", port_idx, + wusb_et_name(wusbhc->ccm1_etd->bEncryptionType), + wusbhc->ccm1_etd->bEncryptionValue, result); +error_set_key: + return result; +} + +/* + * Set host's GTK key + */ +static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid, + const void *key, size_t key_size) +{ + u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK, + WUSB_KEY_INDEX_ORIGINATOR_HOST); + + return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx); +} + +/* + * Get the Wire Adapter class-specific descriptor + * + * NOTE: this descriptor comes with the big bundled configuration + * descriptor that includes the interfaces' and endpoints', so + * we just look for it in the cached copy kept by the USB stack. + * + * NOTE2: We convert LE fields to CPU order. + */ +static int wa_fill_descr(struct wahc *wa) +{ + int result; + struct device *dev = &wa->usb_iface->dev; + char *itr; + struct usb_device *usb_dev = wa->usb_dev; + struct usb_descriptor_header *hdr; + struct usb_wa_descriptor *wa_descr; + size_t itr_size, actconfig_idx; + + actconfig_idx = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[actconfig_idx]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + while (itr_size >= sizeof(*hdr)) { + hdr = (struct usb_descriptor_header *) itr; + d_printf(3, dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); + if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER) + goto found; + itr += hdr->bLength; + itr_size -= hdr->bLength; + } + dev_err(dev, "cannot find Wire Adapter Class descriptor\n"); + return -ENODEV; + +found: + result = -EINVAL; + if (hdr->bLength > itr_size) { /* is it available? */ + dev_err(dev, "incomplete Wire Adapter Class descriptor " + "(%zu bytes left, %u needed)\n", + itr_size, hdr->bLength); + goto error; + } + if (hdr->bLength < sizeof(*wa->wa_descr)) { + dev_err(dev, "short Wire Adapter Class descriptor\n"); + goto error; + } + wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr; + /* Make LE fields CPU order */ + wa_descr->bcdWAVersion = le16_to_cpu(wa_descr->bcdWAVersion); + wa_descr->wNumRPipes = le16_to_cpu(wa_descr->wNumRPipes); + wa_descr->wRPipeMaxBlock = le16_to_cpu(wa_descr->wRPipeMaxBlock); + if (wa_descr->bcdWAVersion > 0x0100) + dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n", + wa_descr->bcdWAVersion & 0xff00 >> 8, + wa_descr->bcdWAVersion & 0x00ff); + result = 0; +error: + return result; +} + +static struct hc_driver hwahc_hc_driver = { + .description = "hwa-hcd", + .product_desc = "Wireless USB HWA host controller", + .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd), + .irq = NULL, /* FIXME */ + .flags = HCD_USB2, /* FIXME */ + .reset = hwahc_op_reset, + .start = hwahc_op_start, + .pci_suspend = hwahc_op_suspend, + .pci_resume = hwahc_op_resume, + .stop = hwahc_op_stop, + .get_frame_number = hwahc_op_get_frame_number, + .urb_enqueue = hwahc_op_urb_enqueue, + .urb_dequeue = hwahc_op_urb_dequeue, + .endpoint_disable = hwahc_op_endpoint_disable, + + .hub_status_data = wusbhc_rh_status_data, + .hub_control = wusbhc_rh_control, + .bus_suspend = wusbhc_rh_suspend, + .bus_resume = wusbhc_rh_resume, + .start_port_reset = wusbhc_rh_start_port_reset, +}; + +static int hwahc_security_create(struct hwahc *hwahc) +{ + int result; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct usb_device *usb_dev = hwahc->wa.usb_dev; + struct device *dev = &usb_dev->dev; + struct usb_security_descriptor *secd; + struct usb_encryption_descriptor *etd; + void *itr, *top; + size_t itr_size, needed, bytes; + u8 index; + char buf[64]; + + /* Find the host's security descriptors in the config descr bundle */ + index = (usb_dev->actconfig - usb_dev->config) / + sizeof(usb_dev->config[0]); + itr = usb_dev->rawdescriptors[index]; + itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); + top = itr + itr_size; + result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index], + le16_to_cpu(usb_dev->actconfig->desc.wTotalLength), + USB_DT_SECURITY, (void **) &secd); + if (result == -1) { + dev_warn(dev, "BUG? WUSB host has no security descriptors\n"); + return 0; + } + needed = sizeof(*secd); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptor header (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + needed = le16_to_cpu(secd->wTotalLength); + if (top - (void *)secd < needed) { + dev_err(dev, "BUG? Not enough data to process security " + "descriptors (%zu bytes left vs %zu needed)\n", + top - (void *) secd, needed); + return 0; + } + /* Walk over the sec descriptors and store CCM1's on wusbhc */ + itr = (void *) secd + sizeof(*secd); + top = (void *) secd + le16_to_cpu(secd->wTotalLength); + index = 0; + bytes = 0; + while (itr < top) { + etd = itr; + if (top - itr < sizeof(*etd)) { + dev_err(dev, "BUG: bad host security descriptor; " + "not enough data (%zu vs %zu left)\n", + top - itr, sizeof(*etd)); + break; + } + if (etd->bLength < sizeof(*etd)) { + dev_err(dev, "BUG: bad host encryption descriptor; " + "descriptor is too short " + "(%zu vs %zu needed)\n", + (size_t)etd->bLength, sizeof(*etd)); + break; + } + itr += etd->bLength; + bytes += snprintf(buf + bytes, sizeof(buf) - bytes, + "%s (0x%02x) ", + wusb_et_name(etd->bEncryptionType), + etd->bEncryptionValue); + wusbhc->ccm1_etd = etd; + } + dev_info(dev, "supported encryption types: %s\n", buf); + if (wusbhc->ccm1_etd == NULL) { + dev_err(dev, "E: host doesn't support CCM-1 crypto\n"); + return 0; + } + /* Pretty print what we support */ + return 0; +} + +static void hwahc_security_release(struct hwahc *hwahc) +{ + /* nothing to do here so far... */ +} + +static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface) +{ + int result; + struct device *dev = &iface->dev; + struct wusbhc *wusbhc = &hwahc->wusbhc; + struct wahc *wa = &hwahc->wa; + struct usb_device *usb_dev = interface_to_usbdev(iface); + + wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */ + wa->usb_iface = usb_get_intf(iface); + wusbhc->dev = dev; + wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent); + if (wusbhc->uwb_rc == NULL) { + result = -ENODEV; + dev_err(dev, "Cannot get associated UWB Host Controller\n"); + goto error_rc_get; + } + result = wa_fill_descr(wa); /* Get the device descriptor */ + if (result < 0) + goto error_fill_descriptor; + if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) { + dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB " + "adapter (%u ports)\n", wa->wa_descr->bNumPorts); + wusbhc->ports_max = USB_MAXCHILDREN; + } else { + wusbhc->ports_max = wa->wa_descr->bNumPorts; + } + wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs; + wusbhc->start = __hwahc_op_wusbhc_start; + wusbhc->stop = __hwahc_op_wusbhc_stop; + wusbhc->mmcie_add = __hwahc_op_mmcie_add; + wusbhc->mmcie_rm = __hwahc_op_mmcie_rm; + wusbhc->dev_info_set = __hwahc_op_dev_info_set; + wusbhc->bwa_set = __hwahc_op_bwa_set; + wusbhc->set_num_dnts = __hwahc_op_set_num_dnts; + wusbhc->set_ptk = __hwahc_op_set_ptk; + wusbhc->set_gtk = __hwahc_op_set_gtk; + result = hwahc_security_create(hwahc); + if (result < 0) { + dev_err(dev, "Can't initialize security: %d\n", result); + goto error_security_create; + } + wa->wusb = wusbhc; /* FIXME: ugly, need to fix */ + result = wusbhc_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Can't create WUSB HC structures: %d\n", result); + goto error_wusbhc_create; + } + result = wa_create(&hwahc->wa, iface); + if (result < 0) + goto error_wa_create; + return 0; + +error_wa_create: + wusbhc_destroy(&hwahc->wusbhc); +error_wusbhc_create: + /* WA Descr fill allocs no resources */ +error_security_create: +error_fill_descriptor: + uwb_rc_put(wusbhc->uwb_rc); +error_rc_get: + usb_put_intf(iface); + usb_put_dev(usb_dev); + return result; +} + +static void hwahc_destroy(struct hwahc *hwahc) +{ + struct wusbhc *wusbhc = &hwahc->wusbhc; + + d_fnstart(1, NULL, "(hwahc %p)\n", hwahc); + mutex_lock(&wusbhc->mutex); + __wa_destroy(&hwahc->wa); + wusbhc_destroy(&hwahc->wusbhc); + hwahc_security_release(hwahc); + hwahc->wusbhc.dev = NULL; + uwb_rc_put(wusbhc->uwb_rc); + usb_put_intf(hwahc->wa.usb_iface); + usb_put_dev(hwahc->wa.usb_dev); + mutex_unlock(&wusbhc->mutex); + d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc); +} + +static void hwahc_init(struct hwahc *hwahc) +{ + wa_init(&hwahc->wa); +} + +static int hwahc_probe(struct usb_interface *usb_iface, + const struct usb_device_id *id) +{ + int result; + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + struct device *dev = &usb_iface->dev; + + d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id); + result = -ENOMEM; + usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa"); + if (usb_hcd == NULL) { + dev_err(dev, "unable to allocate instance\n"); + goto error_alloc; + } + usb_hcd->wireless = 1; + usb_hcd->flags |= HCD_FLAG_SAW_IRQ; + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + hwahc_init(hwahc); + result = hwahc_create(hwahc, usb_iface); + if (result < 0) { + dev_err(dev, "Cannot initialize internals: %d\n", result); + goto error_hwahc_create; + } + result = usb_add_hcd(usb_hcd, 0, 0); + if (result < 0) { + dev_err(dev, "Cannot add HCD: %d\n", result); + goto error_add_hcd; + } + result = wusbhc_b_create(&hwahc->wusbhc); + if (result < 0) { + dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result); + goto error_wusbhc_b_create; + } + d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id); + return 0; + +error_wusbhc_b_create: + usb_remove_hcd(usb_hcd); +error_add_hcd: + hwahc_destroy(hwahc); +error_hwahc_create: + usb_put_hcd(usb_hcd); +error_alloc: + d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result); + return result; +} + +static void hwahc_disconnect(struct usb_interface *usb_iface) +{ + struct usb_hcd *usb_hcd; + struct wusbhc *wusbhc; + struct hwahc *hwahc; + + usb_hcd = usb_get_intfdata(usb_iface); + wusbhc = usb_hcd_to_wusbhc(usb_hcd); + hwahc = container_of(wusbhc, struct hwahc, wusbhc); + + d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface); + wusbhc_b_destroy(&hwahc->wusbhc); + usb_remove_hcd(usb_hcd); + hwahc_destroy(hwahc); + usb_put_hcd(usb_hcd); + d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc, + usb_iface); +} + +/** USB device ID's that we handle */ +static struct usb_device_id hwahc_id_table[] = { + /* FIXME: use class labels for this */ + { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, + {}, +}; +MODULE_DEVICE_TABLE(usb, hwahc_id_table); + +static struct usb_driver hwahc_driver = { + .name = "hwa-hc", + .probe = hwahc_probe, + .disconnect = hwahc_disconnect, + .id_table = hwahc_id_table, +}; + +static int __init hwahc_driver_init(void) +{ + int result; + result = usb_register(&hwahc_driver); + if (result < 0) { + printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n", + result); + goto error_usb_register; + } + return 0; + +error_usb_register: + return result; + +} +module_init(hwahc_driver_init); + +static void __exit hwahc_driver_exit(void) +{ + usb_deregister(&hwahc_driver); +} +module_exit(hwahc_driver_exit); + + +MODULE_AUTHOR("Inaky Perez-Gonzalez "); +MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From f51c23baedf191734364d3aae034d8c9c4cd8cf8 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:31 +0100 Subject: wusb: fix error path for wusb_set_dev_addr() Error path for wusb_set_dev_addr() was handled incorrectly. Fix it by considering error only when return value is negative. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/usb/wusbcore/devconnect.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c index f05f9b4d775..30d7020e1c5 100644 --- a/drivers/usb/wusbcore/devconnect.c +++ b/drivers/usb/wusbcore/devconnect.c @@ -239,8 +239,8 @@ static struct wusb_dev *wusbhc_cack_add(struct wusbhc *wusbhc, "port %u\n", dev_addr, port_idx); result = wusb_set_dev_addr(wusbhc, wusb_dev, dev_addr); - if (result) - return NULL; + if (result < 0) + return NULL; } wusb_dev->entry_ts = jiffies; list_add_tail(&wusb_dev->cack_node, &wusbhc->cack_list); @@ -1301,7 +1301,7 @@ int wusb_set_dev_addr(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev, u8 addr) wusb_dev->addr = addr; result = wusbhc->dev_info_set(wusbhc, wusb_dev); - if (result) + if (result < 0) dev_err(wusbhc->dev, "device %d: failed to set device " "address\n", wusb_dev->port_idx); else -- cgit v1.2.3 From b0a81328c287052cedf28e06d9b4648ad10b72d7 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:32 +0100 Subject: wusb: fix bmRequestType for Abort RPipe request WUSB 1.0 (Table 8-4) mentions that Abort RPipe requests must have bmRequestType equal to 0x25, although current implementation sets bmRequestType to 0xa5. This patch fixes this typo. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/usb/wusbcore/wa-rpipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c index bfe3752e7c9..f18e4aae66e 100644 --- a/drivers/usb/wusbcore/wa-rpipe.c +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -548,7 +548,7 @@ void rpipe_ep_disable(struct wahc *wa, struct usb_host_endpoint *ep) result = usb_control_msg( wa->usb_dev, usb_rcvctrlpipe(wa->usb_dev, 0), USB_REQ_RPIPE_ABORT, - USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_RPIPE, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_RPIPE, 0, index, NULL, 0, 1000 /* FIXME: arbitrary */); if (result < 0 && result != -ENODEV /* dev is gone */) d_printf(1, dev, "(wa %p rpipe %u): abort failed: %d\n", -- cgit v1.2.3 From 9d839477f85438695bd870898ffa77347e61b637 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:33 +0100 Subject: uwb: Fix handling IEs with empty IE data in uwb_est_get_size() A DRP notification may sometimes have empty IE data. This patch fixes uwb_est_get_size() so that this case is handled properly. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/uwb/est.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/uwb/est.c b/drivers/uwb/est.c index 1667afb704d..3791cd95b63 100644 --- a/drivers/uwb/est.c +++ b/drivers/uwb/est.c @@ -395,7 +395,7 @@ ssize_t uwb_est_get_size(struct uwb_rc *uwb_rc, struct uwb_est *est, case UWB_EST_8: type_size = sizeof(u8); break; default: BUG(); } - if (offset + type_size >= rceb_size) { + if (offset + type_size > rceb_size) { if (printk_ratelimit()) dev_err(dev, "EST %p 0x%04x/%04x/%04x[%u]: " "not enough data to read extra size\n", -- cgit v1.2.3 From 9d53b1bebe23a6efe4f0a0ff75df2424e013fe07 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:34 +0100 Subject: uwb: initialize the debug sub-system Call uwb_dbg_init() so the debugfs files are accessible. Signed-off-by: David Vrabel --- drivers/uwb/driver.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/uwb/driver.c b/drivers/uwb/driver.c index 7eee8e40830..521cdeb8497 100644 --- a/drivers/uwb/driver.c +++ b/drivers/uwb/driver.c @@ -119,6 +119,7 @@ static int __init uwb_subsys_init(void) if (result < 0) goto error_uwb_rc_class_register; uwbd_start(); + uwb_dbg_init(); return 0; error_uwb_rc_class_register: @@ -130,6 +131,7 @@ module_init(uwb_subsys_init); static void __exit uwb_subsys_exit(void) { + uwb_dbg_exit(); uwbd_stop(); class_unregister(&uwb_rc_class); uwb_est_destroy(); -- cgit v1.2.3 From e477a4982ff4e94d7de0bc522817b152f52e6bf9 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:35 +0100 Subject: uwb: disable command/event filtering for D-Link DUB-1210 The D-Link DUB-1210 HWA uses commands and events from the WHCI specification, although reporting itself as WUSB compliant. Therefore, we disable WUSB command/event filtering for it. USB_DEVICE_AND_INTERFACE_INFO is used for matching only the RC interface. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/uwb/hwa-rc.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/uwb/hwa-rc.c b/drivers/uwb/hwa-rc.c index f822a1872e4..9b7e23d9482 100644 --- a/drivers/uwb/hwa-rc.c +++ b/drivers/uwb/hwa-rc.c @@ -62,6 +62,9 @@ #define D_LOCAL 1 #include +/* The device uses commands and events from the WHCI specification, although + * reporting itself as WUSB compliant. */ +#define WUSB_QUIRK_WHCI_CMD_EVT 0x01 /** * Descriptor for an instance of the UWB Radio Control Driver that @@ -835,8 +838,13 @@ static int hwarc_probe(struct usb_interface *iface, uwb_rc->stop = hwarc_neep_release; uwb_rc->cmd = hwarc_cmd; uwb_rc->reset = hwarc_reset; - uwb_rc->filter_cmd = hwarc_filter_cmd; - uwb_rc->filter_event = hwarc_filter_event; + if (id->driver_info & WUSB_QUIRK_WHCI_CMD_EVT) { + uwb_rc->filter_cmd = NULL; + uwb_rc->filter_event = NULL; + } else { + uwb_rc->filter_cmd = hwarc_filter_cmd; + uwb_rc->filter_event = hwarc_filter_event; + } result = uwb_rc_add(uwb_rc, dev, hwarc); if (result < 0) @@ -876,6 +884,10 @@ static void hwarc_disconnect(struct usb_interface *iface) /** USB device ID's that we handle */ static struct usb_device_id hwarc_id_table[] = { + /* D-Link DUB-1210 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3d02, 0xe0, 0x01, 0x02), + .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, + /* Generic match for the Radio Control interface */ { USB_INTERFACE_INFO(0xe0, 0x01, 0x02), }, { }, }; -- cgit v1.2.3 From fa21183326882a886ecf74cfebcfb479d56e8469 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:36 +0100 Subject: uwb: add Intel i1480 HWA to the UWB RC quirk table The Intel i1480 HWA uses WHCI commands/events even though reporting itself as WUSB compliant. This patch fixes this by marking it with the WUSB_QUIRK_WHCI_CMD_EVT flag, which disables WUSB command/event filtering. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/uwb/hwa-rc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/uwb/hwa-rc.c b/drivers/uwb/hwa-rc.c index 9b7e23d9482..3d26fa0f8ae 100644 --- a/drivers/uwb/hwa-rc.c +++ b/drivers/uwb/hwa-rc.c @@ -887,6 +887,9 @@ static struct usb_device_id hwarc_id_table[] = { /* D-Link DUB-1210 */ { USB_DEVICE_AND_INTERFACE_INFO(0x07d1, 0x3d02, 0xe0, 0x01, 0x02), .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, + /* Intel i1480 (using firmware 1.3PA2-20070828) */ + { USB_DEVICE_AND_INTERFACE_INFO(0x8086, 0x0c3b, 0xe0, 0x01, 0x02), + .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, /* Generic match for the Radio Control interface */ { USB_INTERFACE_INFO(0xe0, 0x01, 0x02), }, { }, -- cgit v1.2.3 From 8c7e8cb85557cc500122f3e489936582b7d11a7c Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:37 +0100 Subject: uwb: i1480: remove MAC/PHY information checking function Some hardware/firmware combinations (most notably an IOGear HWA using the i1480 firmware) kill the host controller after issuing a GET_MAC_PHY_INFO command. Removing this check seems harmless otherwise. The patch fixes the issue where the HC is killed, showing the message: ehci_hcd 0000:00:1d.7: HC died; cleaning up After this error, USB comes back only after reloading the ehci_hcd module. Signed-off-by: Anderson Lizardo Signed-off-by: David Vrabel --- drivers/uwb/i1480/dfu/dfu.c | 102 ++------------------------------------ drivers/uwb/i1480/dfu/i1480-dfu.h | 3 -- 2 files changed, 4 insertions(+), 101 deletions(-) diff --git a/drivers/uwb/i1480/dfu/dfu.c b/drivers/uwb/i1480/dfu/dfu.c index ebffaf54215..aec4146757d 100644 --- a/drivers/uwb/i1480/dfu/dfu.c +++ b/drivers/uwb/i1480/dfu/dfu.c @@ -127,98 +127,6 @@ error: EXPORT_SYMBOL_GPL(i1480_cmd); -/** - * Get information about the MAC and PHY - * - * @wa: Wired adaptor - * @neh: Notification/event handler - * @reply: Pointer to the reply event buffer - * @returns: 0 if ok, < 0 errno code on error. - */ -static -int i1480_cmd_get_mac_phy_info(struct i1480 *i1480) -{ - int result; - struct uwb_rccb *cmd = i1480->cmd_buf; - struct i1480_evt_confirm_GMPI *reply = i1480->evt_buf; - - cmd->bCommandType = i1480_CET_VS1; - cmd->wCommand = cpu_to_le16(i1480_CMD_GET_MAC_PHY_INFO); - reply->rceb.bEventType = i1480_CET_VS1; - reply->rceb.wEvent = i1480_EVT_GET_MAC_PHY_INFO; - result = i1480_cmd(i1480, "GET_MAC_PHY_INFO", sizeof(*cmd), - sizeof(*reply)); - if (result < 0) - goto out; - if (le16_to_cpu(reply->status) != 0x00) { - dev_err(i1480->dev, - "GET_MAC_PHY_INFO: command execution failed: %d\n", - reply->status); - result = -EIO; - } -out: - return result; -} - - -/** - * Get i1480's info and print it - * - * @wa: Wire Adapter - * @neh: Notification/event handler - * @returns: 0 if ok, < 0 errno code on error. - */ -static -int i1480_check_info(struct i1480 *i1480) -{ - struct i1480_evt_confirm_GMPI *reply = i1480->evt_buf; - int result; - unsigned mac_fw_rev; -#if i1480_FW <= 0x00000302 - unsigned phy_fw_rev; -#endif - if (i1480->quirk_no_check_info) { - dev_err(i1480->dev, "firmware info check disabled\n"); - return 0; - } - - result = i1480_cmd_get_mac_phy_info(i1480); - if (result < 0) { - dev_err(i1480->dev, "Cannot get MAC & PHY information: %d\n", - result); - goto out; - } - mac_fw_rev = le16_to_cpu(reply->mac_fw_rev); -#if i1480_FW > 0x00000302 - dev_info(i1480->dev, - "HW v%02hx " - "MAC FW v%02hx.%02hx caps %04hx " - "PHY type %02hx v%02hx caps %02hx %02hx %02hx\n", - reply->hw_rev, mac_fw_rev >> 8, mac_fw_rev & 0xff, - le16_to_cpu(reply->mac_caps), - reply->phy_vendor, reply->phy_rev, - reply->phy_caps[0], reply->phy_caps[1], reply->phy_caps[2]); -#else - phy_fw_rev = le16_to_cpu(reply->phy_fw_rev); - dev_info(i1480->dev, "MAC FW v%02hx.%02hx caps %04hx " - " PHY FW v%02hx.%02hx caps %04hx\n", - mac_fw_rev >> 8, mac_fw_rev & 0xff, - le16_to_cpu(reply->mac_caps), - phy_fw_rev >> 8, phy_fw_rev & 0xff, - le16_to_cpu(reply->phy_caps)); -#endif - dev_dbg(i1480->dev, - "key-stores:%hu mcast-addr-stores:%hu sec-modes:%hu\n", - (unsigned short) reply->key_stores, - le16_to_cpu(reply->mcast_addr_stores), - (unsigned short) reply->sec_mode_supported); - /* FIXME: complain if fw version too low -- pending for - * numbering to stabilize */ -out: - return result; -} - - static int i1480_print_state(struct i1480 *i1480) { @@ -264,12 +172,10 @@ int i1480_fw_upload(struct i1480 *i1480) i1480_print_state(i1480); goto error_rc_release; } - result = i1480_check_info(i1480); - if (result < 0) { - dev_warn(i1480->dev, "Warning! Cannot check firmware info: %d\n", - result); - result = 0; - } + /* + * FIXME: find some reliable way to check whether firmware is running + * properly. Maybe use some standard request that has no side effects? + */ dev_info(i1480->dev, "firmware uploaded successfully\n"); error_rc_release: if (i1480->rc_release) diff --git a/drivers/uwb/i1480/dfu/i1480-dfu.h b/drivers/uwb/i1480/dfu/i1480-dfu.h index 4103b287ac7..8035418de13 100644 --- a/drivers/uwb/i1480/dfu/i1480-dfu.h +++ b/drivers/uwb/i1480/dfu/i1480-dfu.h @@ -56,7 +56,6 @@ * request_firmware() * i1480_mpi_write() * i1480->cmd() [i1480_{usb,pci}_cmd()] - * i1480_check_info() * * Once the probe function enumerates the device and uploads the * firmware, we just exit with -ENODEV, as we don't really want to @@ -130,8 +129,6 @@ struct i1480 { void *evt_buf, *cmd_buf; ssize_t evt_result; struct completion evt_complete; - - u8 quirk_no_check_info:1; }; static inline -- cgit v1.2.3 From b5784f10d559b3c7b25874b21702ad0907b6fcb7 Mon Sep 17 00:00:00 2001 From: Anderson Lizardo Date: Wed, 17 Sep 2008 16:34:38 +0100 Subject: uwb: i1480/GUWA100U: fix firmware download issues IOGear firmware versions >= 1.4.12224 fail to be downloaded because of a spurious (and harmless) RCEB received after the download notification. This patch handles this RCEB and keeps compatibility with future versions that might not emit this RCEB. i1480_rceb_check() is reused to check for the RCEB. It is also refactored with improved comments and reused in another place in mac.c where the checking was being duplicated. This patch was tested on both i1480 and GUWA100U HWAs, with all firmware versions currently available. Signed-off-by: Anderson Lizardo --- drivers/uwb/i1480/dfu/dfu.c | 54 ++++++++++++++++++++++++++++++--------- drivers/uwb/i1480/dfu/i1480-dfu.h | 2 +- drivers/uwb/i1480/dfu/mac.c | 4 +-- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/drivers/uwb/i1480/dfu/dfu.c b/drivers/uwb/i1480/dfu/dfu.c index aec4146757d..9097b3b3038 100644 --- a/drivers/uwb/i1480/dfu/dfu.c +++ b/drivers/uwb/i1480/dfu/dfu.c @@ -37,29 +37,45 @@ #define D_LOCAL 0 #include -/** @return 0 if If @evt is a valid reply event; otherwise complain */ +/** + * i1480_rceb_check - Check RCEB for expected field values + * @i1480: pointer to device for which RCEB is being checked + * @rceb: RCEB being checked + * @cmd: which command the RCEB is related to + * @context: expected context + * @expected_type: expected event type + * @expected_event: expected event + * + * If @cmd is NULL, do not print error messages, but still return an error + * code. + * + * Return 0 if @rceb matches the expected values, -EINVAL otherwise. + */ int i1480_rceb_check(const struct i1480 *i1480, const struct uwb_rceb *rceb, - const char *cmd, u8 context, - unsigned expected_type, unsigned expected_event) + const char *cmd, u8 context, u8 expected_type, + unsigned expected_event) { int result = 0; struct device *dev = i1480->dev; if (rceb->bEventContext != context) { - dev_err(dev, "%s: " - "unexpected context id 0x%02x (expected 0x%02x)\n", - cmd, rceb->bEventContext, context); + if (cmd) + dev_err(dev, "%s: unexpected context id 0x%02x " + "(expected 0x%02x)\n", cmd, + rceb->bEventContext, context); result = -EINVAL; } if (rceb->bEventType != expected_type) { - dev_err(dev, "%s: " - "unexpected event type 0x%02x (expected 0x%02x)\n", - cmd, rceb->bEventType, expected_type); + if (cmd) + dev_err(dev, "%s: unexpected event type 0x%02x " + "(expected 0x%02x)\n", cmd, + rceb->bEventType, expected_type); result = -EINVAL; } if (le16_to_cpu(rceb->wEvent) != expected_event) { - dev_err(dev, "%s: " - "unexpected event 0x%04x (expected 0x%04x)\n", - cmd, le16_to_cpu(rceb->wEvent), expected_event); + if (cmd) + dev_err(dev, "%s: unexpected event 0x%04x " + "(expected 0x%04x)\n", cmd, + le16_to_cpu(rceb->wEvent), expected_event); result = -EINVAL; } return result; @@ -110,6 +126,20 @@ ssize_t i1480_cmd(struct i1480 *i1480, const char *cmd_name, size_t cmd_size, cmd_name, result); goto error; } + /* + * Firmware versions >= 1.4.12224 for IOGear GUWA100U generate a + * spurious notification after firmware is downloaded. So check whether + * the receibed RCEB is such notification before assuming that the + * command has failed. + */ + if (i1480_rceb_check(i1480, i1480->evt_buf, NULL, + 0, 0xfd, 0x0022) == 0) { + /* Now wait for the actual RCEB for this command. */ + result = i1480->wait_init_done(i1480); + if (result < 0) + goto error; + result = i1480->evt_result; + } if (result != reply_size) { dev_err(i1480->dev, "%s returned only %zu bytes, %zu expected\n", cmd_name, result, reply_size); diff --git a/drivers/uwb/i1480/dfu/i1480-dfu.h b/drivers/uwb/i1480/dfu/i1480-dfu.h index 8035418de13..46f45e800f3 100644 --- a/drivers/uwb/i1480/dfu/i1480-dfu.h +++ b/drivers/uwb/i1480/dfu/i1480-dfu.h @@ -145,7 +145,7 @@ extern int i1480_phy_fw_upload(struct i1480 *); extern ssize_t i1480_cmd(struct i1480 *, const char *, size_t, size_t); extern int i1480_rceb_check(const struct i1480 *, const struct uwb_rceb *, const char *, u8, - unsigned, unsigned); + u8, unsigned); enum { /* Vendor specific command type */ diff --git a/drivers/uwb/i1480/dfu/mac.c b/drivers/uwb/i1480/dfu/mac.c index 3d445541e8e..8d069907a3b 100644 --- a/drivers/uwb/i1480/dfu/mac.c +++ b/drivers/uwb/i1480/dfu/mac.c @@ -507,8 +507,8 @@ int i1480_mac_fw_upload(struct i1480 *i1480) goto error_size; } result = -EIO; - if (rcebe->rceb.bEventType != i1480_CET_VS1 - || le16_to_cpu(rcebe->rceb.wEvent) != i1480_EVT_RM_INIT_DONE) { + if (i1480_rceb_check(i1480, &rcebe->rceb, NULL, 0, i1480_CET_VS1, + i1480_EVT_RM_INIT_DONE) < 0) { dev_err(i1480->dev, "wrong initialization event 0x%02x/%04x/%02x " "received; expected 0x%02x/%04x/00\n", rcebe->rceb.bEventType, le16_to_cpu(rcebe->rceb.wEvent), -- cgit v1.2.3 From b63795fa3a41151040b86119750a7df508d40cda Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:39 +0100 Subject: uwb: dont tranmit identification IEs The current identification IE doesn't include any useful information (the vendor ID is from the EUI-48) and it causes problems with certain hardware/firmware so don't transmit one. Signed-off-by: David Vrabel --- drivers/uwb/ie.c | 29 ----------------------------- drivers/uwb/lc-rc.c | 6 ------ drivers/uwb/uwb-internal.h | 1 - 3 files changed, 36 deletions(-) diff --git a/drivers/uwb/ie.c b/drivers/uwb/ie.c index d54fe09a986..cf6f3d152b9 100644 --- a/drivers/uwb/ie.c +++ b/drivers/uwb/ie.c @@ -539,32 +539,3 @@ int uwb_rc_ie_rm(struct uwb_rc *uwb_rc, enum uwb_ie element_id) return result; } EXPORT_SYMBOL_GPL(uwb_rc_ie_rm); - - -/** - * Create and set new Identification IE - * - * Currently only sets the Vendor ID. The Vendor ID is set from the OUI, - * which is obtained from the first three bytes from the MAC address. - */ -int uwb_rc_set_identification_ie(struct uwb_rc *uwb_rc) -{ - struct { - struct uwb_identification_ie id_ie; - struct uwb_dev_info dev_info; - struct uwb_vendor_id vendor_id; - } ie_data; - - ie_data.id_ie.hdr.element_id = UWB_IDENTIFICATION_IE; - ie_data.id_ie.hdr.length = sizeof(struct uwb_dev_info) + - sizeof(struct uwb_vendor_id); - - ie_data.dev_info.type = UWB_DEV_INFO_VENDOR_ID; - ie_data.dev_info.length = sizeof(struct uwb_vendor_id); - - ie_data.vendor_id.data[0] = uwb_rc->uwb_dev.mac_addr.data[0]; - ie_data.vendor_id.data[1] = uwb_rc->uwb_dev.mac_addr.data[1]; - ie_data.vendor_id.data[2] = uwb_rc->uwb_dev.mac_addr.data[2]; - - return uwb_rc_ie_add(uwb_rc, &ie_data.id_ie.hdr, sizeof(ie_data)); -} diff --git a/drivers/uwb/lc-rc.c b/drivers/uwb/lc-rc.c index a21c96bff23..ee5772f00d4 100644 --- a/drivers/uwb/lc-rc.c +++ b/drivers/uwb/lc-rc.c @@ -211,12 +211,6 @@ static int uwb_rc_setup(struct uwb_rc *rc) dev_err(dev, "cannot setup IE subsystem: %d\n", result); goto error_ie_setup; } - result = uwb_rc_set_identification_ie(rc); - if (result < 0) { - dev_err(dev, "cannot set Identification IE: %d\n", - result); - goto error_set_id_ie; - } result = uwb_rsv_setup(rc); if (result < 0) { dev_err(dev, "cannot setup reservation subsystem: %d\n", result); diff --git a/drivers/uwb/uwb-internal.h b/drivers/uwb/uwb-internal.h index 4f525a88985..2ad307d1296 100644 --- a/drivers/uwb/uwb-internal.h +++ b/drivers/uwb/uwb-internal.h @@ -74,7 +74,6 @@ extern void uwb_rc_ie_release(struct uwb_rc *); extern int uwb_rc_ie_add(struct uwb_rc *, const struct uwb_ie_hdr *, size_t); extern int uwb_rc_ie_rm(struct uwb_rc *, enum uwb_ie); -extern int uwb_rc_set_identification_ie(struct uwb_rc *); extern const char *uwb_rc_strerror(unsigned code); -- cgit v1.2.3 From b60066c141997ac2e4ef08459b75638ae86ae781 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:40 +0100 Subject: uwb: add symlinks in sysfs between radio controllers and PALs Add a facility for PALs to have symlinks to their radio controller (and vice-versa) and make WUSB host controllers use this. Signed-off-by: David Vrabel --- drivers/usb/wusbcore/pal.c | 3 +++ drivers/usb/wusbcore/wusbhc.c | 16 +++++++++------- drivers/uwb/pal.c | 20 ++++++++++++++++++++ include/linux/uwb.h | 6 +++++- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/drivers/usb/wusbcore/pal.c b/drivers/usb/wusbcore/pal.c index cc126b44473..7cc51e9905c 100644 --- a/drivers/usb/wusbcore/pal.c +++ b/drivers/usb/wusbcore/pal.c @@ -26,6 +26,9 @@ int wusbhc_pal_register(struct wusbhc *wusbhc) { uwb_pal_init(&wusbhc->pal); + wusbhc->pal.name = "wusbhc"; + wusbhc->pal.device = wusbhc->usb_hcd.self.controller; + return uwb_pal_register(wusbhc->uwb_rc, &wusbhc->pal); } diff --git a/drivers/usb/wusbcore/wusbhc.c b/drivers/usb/wusbcore/wusbhc.c index 1149b1e59c8..07c63a31c79 100644 --- a/drivers/usb/wusbcore/wusbhc.c +++ b/drivers/usb/wusbcore/wusbhc.c @@ -192,13 +192,8 @@ int wusbhc_create(struct wusbhc *wusbhc) result = wusbhc_sec_create(wusbhc); if (result < 0) goto error_sec_create; - result = wusbhc_pal_register(wusbhc); - if (result < 0) - goto error_pal_register; return 0; -error_pal_register: - wusbhc_sec_destroy(wusbhc); error_sec_create: wusbhc_rh_destroy(wusbhc); error_rh_create: @@ -235,7 +230,14 @@ int wusbhc_b_create(struct wusbhc *wusbhc) dev_err(dev, "Cannot register WUSBHC attributes: %d\n", result); goto error_create_attr_group; } - /* Yep, I plan to add stuff here... */ + + result = wusbhc_pal_register(wusbhc); + if (result < 0) + goto error_pal_register; + return 0; + +error_pal_register: + sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); error_create_attr_group: return result; } @@ -243,13 +245,13 @@ EXPORT_SYMBOL_GPL(wusbhc_b_create); void wusbhc_b_destroy(struct wusbhc *wusbhc) { + wusbhc_pal_unregister(wusbhc); sysfs_remove_group(wusbhc_kobj(wusbhc), &wusbhc_attr_group); } EXPORT_SYMBOL_GPL(wusbhc_b_destroy); void wusbhc_destroy(struct wusbhc *wusbhc) { - wusbhc_pal_unregister(wusbhc); wusbhc_sec_destroy(wusbhc); wusbhc_rh_destroy(wusbhc); wusbhc_devconnect_destroy(wusbhc); diff --git a/drivers/uwb/pal.c b/drivers/uwb/pal.c index 5508993a820..1afb38eacb9 100644 --- a/drivers/uwb/pal.c +++ b/drivers/uwb/pal.c @@ -39,6 +39,21 @@ EXPORT_SYMBOL_GPL(uwb_pal_init); */ int uwb_pal_register(struct uwb_rc *rc, struct uwb_pal *pal) { + int ret; + + if (pal->device) { + ret = sysfs_create_link(&pal->device->kobj, + &rc->uwb_dev.dev.kobj, "uwb_rc"); + if (ret < 0) + return ret; + ret = sysfs_create_link(&rc->uwb_dev.dev.kobj, + &pal->device->kobj, pal->name); + if (ret < 0) { + sysfs_remove_link(&pal->device->kobj, "uwb_rc"); + return ret; + } + } + spin_lock(&rc->pal_lock); list_add(&pal->node, &rc->pals); spin_unlock(&rc->pal_lock); @@ -57,6 +72,11 @@ void uwb_pal_unregister(struct uwb_rc *rc, struct uwb_pal *pal) spin_lock(&rc->pal_lock); list_del(&pal->node); spin_unlock(&rc->pal_lock); + + if (pal->device) { + sysfs_remove_link(&rc->uwb_dev.dev.kobj, pal->name); + sysfs_remove_link(&pal->device->kobj, "uwb_rc"); + } } EXPORT_SYMBOL_GPL(uwb_pal_unregister); diff --git a/include/linux/uwb.h b/include/linux/uwb.h index 0cd35937e12..f9ccbd9a2ce 100644 --- a/include/linux/uwb.h +++ b/include/linux/uwb.h @@ -361,6 +361,9 @@ struct uwb_rc { /** * struct uwb_pal - a UWB PAL + * @name: descriptive name for this PAL (wushc, wlp, etc.). + * @device: a device for the PAL. Used to link the PAL and the radio + * controller in sysfs. * @new_rsv: called when a peer requests a reservation (may be NULL if * the PAL cannot accept reservation requests). * @@ -379,7 +382,8 @@ struct uwb_rc { */ struct uwb_pal { struct list_head node; - + const char *name; + struct device *device; void (*new_rsv)(struct uwb_rsv *rsv); }; -- cgit v1.2.3 From c8cf2465fcfc16877f4f9e8dccc6b89b543fa2c5 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:41 +0100 Subject: uwb: document UWB and WUSB sysfs files Add some brief documentation on the UWB and WUSB related sysfs files. Signed-off-by: David Vrabel --- Documentation/ABI/testing/sysfs-bus-umc | 28 +++++ Documentation/ABI/testing/sysfs-bus-usb | 43 ++++++++ Documentation/ABI/testing/sysfs-class-usb_host | 25 +++++ Documentation/ABI/testing/sysfs-class-uwb_rc | 144 +++++++++++++++++++++++++ Documentation/ABI/testing/sysfs-wusb_cbaf | 100 +++++++++++++++++ 5 files changed, 340 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-umc create mode 100644 Documentation/ABI/testing/sysfs-class-usb_host create mode 100644 Documentation/ABI/testing/sysfs-class-uwb_rc create mode 100644 Documentation/ABI/testing/sysfs-wusb_cbaf diff --git a/Documentation/ABI/testing/sysfs-bus-umc b/Documentation/ABI/testing/sysfs-bus-umc new file mode 100644 index 00000000000..948fec41244 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-umc @@ -0,0 +1,28 @@ +What: /sys/bus/umc/ +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + The Wireless Host Controller Interface (WHCI) + specification describes a PCI-based device with + multiple capabilities; the UWB Multi-interface + Controller (UMC). + + The umc bus presents each of the individual + capabilties as a device. + +What: /sys/bus/umc/devices/.../capability_id +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + The ID of this capability, with 0 being the radio + controller capability. + +What: /sys/bus/umc/devices/.../version +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + The specification version this capability's hardware + interface complies with. diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 11a3c1682ce..273a7f099f4 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -85,3 +85,46 @@ Description: Users: PowerTOP http://www.lesswatts.org/projects/powertop/ + +What: /sys/bus/usb/device/.../authorized +Date: July 2008 +KernelVersion: 2.6.26 +Contact: David Vrabel +Description: + Authorized devices are available for use by device + drivers, non-authorized one are not. By default, wired + USB devices are authorized. + + Certified Wireless USB devices are not authorized + initially and should be (by writing 1) after the + device has been authenticated. + +What: /sys/bus/usb/device/.../wusb_cdid +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + For Certified Wireless USB devices only. + + A devices's CDID, as 16 space-separated hex octets. + +What: /sys/bus/usb/device/.../wusb_ck +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + For Certified Wireless USB devices only. + + Write the device's connection key (CK) to start the + authentication of the device. The CK is 16 + space-separated hex octets. + +What: /sys/bus/usb/device/.../wusb_disconnect +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + For Certified Wireless USB devices only. + + Write a 1 to force the device to disconnect + (equivalent to unplugging a wired USB device). diff --git a/Documentation/ABI/testing/sysfs-class-usb_host b/Documentation/ABI/testing/sysfs-class-usb_host new file mode 100644 index 00000000000..46b66ad1f1b --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-usb_host @@ -0,0 +1,25 @@ +What: /sys/class/usb_host/usb_hostN/wusb_chid +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + Write the CHID (16 space-separated hex octets) for this host controller. + This starts the host controller, allowing it to accept connection from + WUSB devices. + + Set an all zero CHID to stop the host controller. + +What: /sys/class/usb_host/usb_hostN/wusb_trust_timeout +Date: July 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + Devices that haven't sent a WUSB packet to the host + within 'wusb_trust_timeout' ms are considered to have + disconnected and are removed. The default value of + 4000 ms is the value required by the WUSB + specification. + + Since this relates to security (specifically, the + lifetime of PTKs and GTKs) it should not be changed + from the default. diff --git a/Documentation/ABI/testing/sysfs-class-uwb_rc b/Documentation/ABI/testing/sysfs-class-uwb_rc new file mode 100644 index 00000000000..a0d18dbeb7a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-uwb_rc @@ -0,0 +1,144 @@ +What: /sys/class/uwb_rc +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + Interfaces for WiMedia Ultra Wideband Common Radio + Platform (UWB) radio controllers. + + Familiarity with the ECMA-368 'High Rate Ultra + Wideband MAC and PHY Specification' is assumed. + +What: /sys/class/uwb_rc/beacon_timeout_ms +Date: July 2008 +KernelVersion: 2.6.27 +Description: + If no beacons are received from a device for at least + this time, the device will be considered to have gone + and it will be removed. The default is 3 superframes + (~197 ms) as required by the specification. + +What: /sys/class/uwb_rc/uwbN/ +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + An individual UWB radio controller. + +What: /sys/class/uwb_rc/uwbN/beacon +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + Write: + + [] + + to start beaconing on a specific channel, or stop + beaconing if is -1. Valid channels depends + on the radio controller's supported band groups. + + may be used to try and join a specific + beacon group if more than one was found during a scan. + +What: /sys/class/uwb_rc/uwbN/scan +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + Write: + + [] + + to start (or stop) scanning on a channel. is one of: + 0 - scan + 1 - scan outside BP + 2 - scan while inactive + 3 - scanning disabled + 4 - scan (with start time of ) + +What: /sys/class/uwb_rc/uwbN/mac_address +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + The EUI-48, in colon-separated hex octets, for this + radio controller. A write will change the radio + controller's EUI-48 but only do so while the device is + not beaconing or scanning. + +What: /sys/class/uwb_rc/uwbN/wusbhc +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + A symlink to the device (if any) of the WUSB Host + Controller PAL using this radio controller. + +What: /sys/class/uwb_rc/uwbN// +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + A neighbour UWB device that has either been detected + as part of a scan or is a member of the radio + controllers beacon group. + +What: /sys/class/uwb_rc/uwbN//BPST +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + The time (using the radio controllers internal 1 ms + interval superframe timer) of the last beacon from + this device was received. + +What: /sys/class/uwb_rc/uwbN//DevAddr +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + The current DevAddr of this device in colon separated + hex octets. + +What: /sys/class/uwb_rc/uwbN//EUI_48 +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + + The EUI-48 of this device in colon separated hex + octets. + +What: /sys/class/uwb_rc/uwbN//BPST +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + +What: /sys/class/uwb_rc/uwbN//IEs +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + The latest IEs included in this device's beacon, in + space separated hex octets with one IE per line. + +What: /sys/class/uwb_rc/uwbN//LQE +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + Link Quality Estimate - the Signal to Noise Ratio + (SNR) of all packets received from this device in dB. + This gives an estimate on a suitable PHY rate. Refer + to [ECMA-368] section 13.3 for more details. + +What: /sys/class/uwb_rc/uwbN//RSSI +Date: July 2008 +KernelVersion: 2.6.27 +Contact: linux-usb@vger.kernel.org +Description: + Received Signal Strength Indication - the strength of + the received signal in dB. LQE is a more useful + measure of the radio link quality. diff --git a/Documentation/ABI/testing/sysfs-wusb_cbaf b/Documentation/ABI/testing/sysfs-wusb_cbaf new file mode 100644 index 00000000000..a99c5f86a37 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-wusb_cbaf @@ -0,0 +1,100 @@ +What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_* +Date: August 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + Various files for managing Cable Based Association of + (wireless) USB devices. + + The sequence of operations should be: + + 1. Device is plugged in. + + 2. The connection manager (CM) sees a device with CBA capability. + (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE). + + 3. The CM writes the host name, supported band groups, + and the CHID (host ID) into the wusb_host_name, + wusb_host_band_groups and wusb_chid files. These + get sent to the device and the CDID (if any) for + this host is requested. + + 4. The CM can verify that the device's supported band + groups (wusb_device_band_groups) are compatible + with the host. + + 5. The CM reads the wusb_cdid file. + + 6. The CM looks it up its database. + + - If it has a matching CHID,CDID entry, the device + has been authorized before and nothing further + needs to be done. + + - If the CDID is zero (or the CM doesn't find a + matching CDID in its database), the device is + assumed to be not known. The CM may associate + the host with device by: writing a randomly + generated CDID to wusb_cdid and then a random CK + to wusb_ck (this uploads the new CC to the + device). + + CMD may choose to prompt the user before + associating with a new device. + + 7. Device is unplugged. + + References: + [WUSB-AM] Association Models Supplement to the + Certified Wireless Universal Serial Bus + Specification, version 1.0. + +What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_chid +Date: August 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + The CHID of the host formatted as 16 space-separated + hex octets. + + Writes fetches device's supported band groups and the + the CDID for any existing association with this host. + +What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_host_name +Date: August 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + A friendly name for the host as a UTF-8 encoded string. + +What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_host_band_groups +Date: August 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + The band groups supported by the host, in the format + defined in [WUSB-AM]. + +What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_device_band_groups +Date: August 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + The band groups supported by the device, in the format + defined in [WUSB-AM]. + +What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_cdid +Date: August 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + The device's CDID formatted as 16 space-separated hex + octets. + +What: /sys/bus/usb/drivers/wusb_cbaf/.../wusb_ck +Date: August 2008 +KernelVersion: 2.6.27 +Contact: David Vrabel +Description: + Write 16 space-separated random, hex octets to + associate with the device. -- cgit v1.2.3 From f1fa035f2bcb2b03cb90249e05ec2ae6927a7302 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Wed, 17 Sep 2008 16:34:42 +0100 Subject: wusb: wusb-cbaf (CBA driver) sysfs ABI simplification Simplify the sysfs ABI of the wusb-cbaf (Cable Based Association) driver: use one value per file and cause the write of the CHID to fetch the CDID (instead of requiring a separate read). Update the example wusb-cbaf script to work with this revised ABI. Signed-off-by: Felipe Zimmerle Signed-off-by: David Vrabel --- Documentation/usb/wusb-cbaf | 60 ++--- drivers/usb/wusbcore/Kconfig | 23 ++ drivers/usb/wusbcore/Makefile | 8 +- drivers/usb/wusbcore/cbaf.c | 521 +++++++++++++++++++++++------------------- 4 files changed, 350 insertions(+), 262 deletions(-) diff --git a/Documentation/usb/wusb-cbaf b/Documentation/usb/wusb-cbaf index a385478ba12..2e78b70f3ad 100644 --- a/Documentation/usb/wusb-cbaf +++ b/Documentation/usb/wusb-cbaf @@ -70,32 +70,42 @@ EOF # FIXME: CHID should come from a database :), band group from the host host_CHID="00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff" host_band_group="0001" -host_name="Linux-WUSB" +host_name=$(hostname) devs="$(echo /sys/bus/usb/drivers/wusb-cbaf/[0-9]*)" -hdevs="$(find /sys -name wusb_chid -printf "%h\n")" +hdevs="$(for h in /sys/class/uwb_rc/*/wusbhc; do readlink -f $h; done)" result=0 case $1 in start) for dev in ${2:-$hdevs} do - uwb_rc=$(find $(dirname $(dirname $dev)) -iname uwb_rc:uwb*) - if cat $uwb_rc/uwb_rc/beacon | grep -q "channel: -1" + uwb_rc=$(readlink -f $dev/uwb_rc) + if cat $uwb_rc/beacon | grep -q -- "-1" then - echo 13 0 | cat > $uwb_rc/uwb_rc/beacon - echo I: started beaconing on ch 13 in host $(basename $uwb_rc) + echo 13 0 > $uwb_rc/beacon + echo I: started beaconing on ch 13 on $(basename $uwb_rc) >&2 fi - echo $host_CHID | cat > $dev/wusb_chid - echo I: started host $(basename $dev) + echo $host_CHID > $dev/wusb_chid + echo I: started host $(basename $dev) >&2 + done + ;; + stop) + for dev in ${2:-$hdevs} + do + echo 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 > $dev/wusb_chid + echo I: stopped host $(basename $dev) >&2 + uwb_rc=$(readlink -f $dev/uwb_rc) + echo -1 | cat > $uwb_rc/beacon + echo I: stopped beaconing on $(basename $uwb_rc) >&2 done ;; set-chid) shift - for dev in ${2:-$devs} - do - echo "${2:-$host_CHID}" "${3:-$host_band_group}" "${4:-$host_name}" \ - | cat > $dev/wusb_host_info + for dev in ${2:-$devs}; do + echo "${4:-$host_name}" > $dev/wusb_host_name + echo "${3:-$host_band_group}" > $dev/wusb_host_band_groups + echo ${2:-$host_CHID} > $dev/wusb_chid done ;; get-cdid) @@ -105,21 +115,17 @@ case $1 in done ;; set-cc) - for dev in ${2:-$devs} - do - shift - CDID="$(head --bytes=16 /dev/urandom | od -tx1 -An)" - CK="$(head --bytes=16 /dev/urandom | od -tx1 -An)" - cat > $dev/wusb_cc < $dev/wusb_cdid + echo "$CK" > $dev/wusb_ck + + echo I: CC set >&2 + echo "CHID: $(cat $dev/wusb_chid)" + echo "CDID:$CDID" + echo "CK: $CK" done ;; help|h|--help|-h) diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig index add077e5c5d..4ea76693ad4 100644 --- a/drivers/usb/wusbcore/Kconfig +++ b/drivers/usb/wusbcore/Kconfig @@ -15,3 +15,26 @@ config USB_WUSB To compile this support select Y (built in). It is safe to select even if you don't have the hardware. + +config USB_WUSB_CBAF + tristate "Support WUSB Cable Based Association (CBA)" + depends on USB + help + Some WUSB devices support Cable Based Association. It's used to + enable the secure communication between the host and the + device. + + Enable this option if your WUSB device must to be connected + via wired USB before establishing a wireless link. + + It is safe to select even if you don't have a compatible + hardware. + +config USB_WUSB_CBAF_DEBUG + bool "Enable CBA debug messages" + depends on USB_WUSB_CBAF + help + Say Y here if you want the CBA to produce a bunch of debug messages + to the system log. Select this if you are having a problem with + CBA support and want to see more of what is going on. + diff --git a/drivers/usb/wusbcore/Makefile b/drivers/usb/wusbcore/Makefile index 7a4d0072403..75f1ade6625 100644 --- a/drivers/usb/wusbcore/Makefile +++ b/drivers/usb/wusbcore/Makefile @@ -1,5 +1,7 @@ -obj-$(CONFIG_USB_WUSB) += wusbcore.o wusb-cbaf.o +obj-$(CONFIG_USB_WUSB) += wusbcore.o obj-$(CONFIG_USB_HWA_HCD) += wusb-wa.o +obj-$(CONFIG_USB_WUSB_CBAF) += wusb-cbaf.o + wusbcore-objs := \ crypto.o \ @@ -18,3 +20,7 @@ wusb-wa-objs := wa-hc.o \ wa-nep.o \ wa-rpipe.o \ wa-xfer.o + +ifeq ($(CONFIG_USB_WUSB_CBAF_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/usb/wusbcore/cbaf.c b/drivers/usb/wusbcore/cbaf.c index 584eabe274b..ab4788d1785 100644 --- a/drivers/usb/wusbcore/cbaf.c +++ b/drivers/usb/wusbcore/cbaf.c @@ -4,6 +4,7 @@ * * Copyright (C) 2006 Intel Corporation * Inaky Perez-Gonzalez + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version @@ -20,14 +21,13 @@ * 02110-1301, USA. * * - * WUSB devices have to be paired (authenticated in WUSB lingo) so + * WUSB devices have to be paired (associated in WUSB lingo) so * that they can connect to the system. * - * One way of pairing is using CBA-Cable Based Authentication, devices - * that can connect via wired or wireless USB. First time you plug - * them with a cable, pairing is done between host and device and - * subsequent times, you can connect wirelessly without having to - * pair. That's the idea. + * One way of pairing is using CBA-Cable Based Association. First + * time you plug the device with a cable, association is done between + * host and device and subsequent times, you can connect wirelessly + * without having to associate again. That's the idea. * * This driver does nothing Earth shattering. It just provides an * interface to chat with the wire-connected device so we can get a @@ -42,56 +42,49 @@ * * The process goes like this: * - * 1. device plugs, cbaf is loaded, notifications happen + * 1. Device plugs, cbaf is loaded, notifications happen. * - * 2. the connection manager sees a device with CBAF capability (the - * wusb_{host_info,cdid,cc} files are in /sys/device/blah/OURDEVICE). + * 2. The connection manager (CM) sees a device with CBAF capability + * (the wusb_chid etc. files in /sys/devices/blah/OURDEVICE). * - * 3. CM (connection manager) writes the CHID (host ID) and a host - * name into the wusb_host_info file. This gets sent to the device. + * 3. The CM writes the host name, supported band groups, and the CHID + * (host ID) into the wusb_host_name, wusb_host_band_groups and + * wusb_chid files. These get sent to the device and the CDID (if + * any) for this host is requested. * - * 4. CM cats the wusb_cdid file; this asks the device if it has any - * CDID associated to the CHDI we just wrote before. If it does, it - * is printed, along with the device 'friendly name' and the band - * groups the device supports. + * 4. The CM can verify that the device's supported band groups + * (wusb_device_band_groups) are compatible with the host. * - * 5. CM looks up its database + * 5. The CM reads the wusb_cdid file. * - * 5.1 If it has a matching CHID,CDID entry, the device has been - * authorized before (paired). Now we can optionally ask the user - * if he wants to allow the device to connect. Then we generate a - * new CDID and CK, send it to the device and update the database - * (writing to the wusb_cc file so they are uploaded to the device). + * 6. The CM looks up its database * - * 5.2 If the CDID is zero (or we didn't find a matching CDID in our - * database), we assume the device is not known. We ask the user - * if s/he wants to allow the device to be connected wirelessly - * to the system. If nope, nothing else is done (FIXME: maybe - * send a zero CDID to clean up our CHID?). If yes, we generate - * random CDID and CKs (and write them to the wusb_cc file so - * they are uploaded to the device). + * 6.1 If it has a matching CHID,CDID entry, the device has been + * authorized before (paired) and nothing further needs to be + * done. * - * 6. device is unplugged + * 6.2 If the CDID is zero (or the CM doesn't find a matching CDID in + * its database), the device is assumed to be not known. The CM + * may associate the host with device by: writing a randomly + * generated CDID to wusb_cdid and then a random CK to wusb_ck + * (this uploads the new CC to the device). * - * When the device tries to connect wirelessly, it will present it's - * CDID to the WUSB host controller with ID CHID, which will query the - * database. If found, the host will (with a 4way handshake) challenge - * the device to demonstrate it has the CK secret key (from our - * database) without actually exchanging it. Once satisfied, crypto - * keys are derived from the CK, the device is connected and all - * communication is crypted. + * CMD may choose to prompt the user before associating with a new + * device. * + * 7. Device is unplugged. * - * NOTES ABOUT THE IMPLEMENTATION + * When the device tries to connect wirelessly, it will present its + * CDID to the WUSB host controller. The CM will query the + * database. If the CHID/CDID pair found, it will (with a 4-way + * handshake) challenge the device to demonstrate it has the CK secret + * key (from our database) without actually exchanging it. Once + * satisfied, crypto keys are derived from the CK, the device is + * connected and all communication is encrypted. * - * The descriptors sent back and forth use this horrible format from - * hell on which each field is actually a field ID, field length and - * then the field itself. How stupid can that get, taking into account - * the structures are defined by the spec?? oh well. - * - * - * FIXME: we don't provide a way to tell the device the pairing failed - * (ie: send a CC_DATA_FAIL). Should add some day. + * References: + * [WUSB-AM] Association Models Supplement to the Certified Wireless + * Universal Serial Bus Specification, version 1.0. */ #include #include @@ -105,9 +98,7 @@ #include #include -#undef D_LOCAL -#define D_LOCAL 6 -#include +#define CBA_NAME_LEN 0x40 /* [WUSB-AM] table 4-7 */ /* An instance of a Cable-Based-Association-Framework device */ struct cbaf { @@ -116,24 +107,27 @@ struct cbaf { void *buffer; size_t buffer_size; - struct wusb_ckhdid chid;/* Host Information */ - char host_name[65]; /* max length: - Assoc Models Suplement 1.0[T4-7] */ + struct wusb_ckhdid chid; + char host_name[CBA_NAME_LEN]; u16 host_band_groups; - struct wusb_ckhdid cdid;/* Device Information */ - char device_name[65]; /* max length: - Assoc Models Suplement 1.0[T4-7] */ + struct wusb_ckhdid cdid; + char device_name[CBA_NAME_LEN]; u16 device_band_groups; - struct wusb_ckhdid ck; /* Connection Key */ + + struct wusb_ckhdid ck; }; /* * Verify that a CBAF USB-interface has what we need * - * (like we care, we are going to fail the enumeration if not :) + * According to [WUSB-AM], CBA devices should provide at least two + * interfaces: + * - RETRIEVE_HOST_INFO + * - ASSOCIATE * - * FIXME: ugly function, need to split + * If the device doesn't provide these interfaces, we do not know how + * to deal with it. */ static int cbaf_check(struct cbaf *cbaf) { @@ -143,8 +137,7 @@ static int cbaf_check(struct cbaf *cbaf) struct wusb_cbaf_assoc_request *assoc_request; size_t assoc_size; void *itr, *top; - unsigned ar_index; - int ar_rhi_idx = -1, ar_assoc_idx = -1; + int ar_rhi = 0, ar_assoc = 0; result = usb_control_msg( cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), @@ -153,93 +146,91 @@ static int cbaf_check(struct cbaf *cbaf) 0, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, cbaf->buffer, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); if (result < 0) { - dev_err(dev, "cannot get available association types: %d\n", + dev_err(dev, "Cannot get available association types: %d\n", result); - goto error_get_assoc_types; + return result; } + assoc_info = cbaf->buffer; if (result < sizeof(*assoc_info)) { - dev_err(dev, "not enough data to decode association info " + dev_err(dev, "Not enough data to decode association info " "header (%zu vs %zu bytes required)\n", (size_t)result, sizeof(*assoc_info)); - goto error_bad_header; + return result; } + assoc_size = le16_to_cpu(assoc_info->Length); if (result < assoc_size) { - dev_err(dev, "not enough data to decode association info " + dev_err(dev, "Not enough data to decode association info " "(%zu vs %zu bytes required)\n", (size_t)assoc_size, sizeof(*assoc_info)); - goto error_bad_data; + return result; } /* * From now on, we just verify, but won't error out unless we * don't find the AR_TYPE_WUSB_{RETRIEVE_HOST_INFO,ASSOCIATE} * types. */ - ar_index = 0; itr = cbaf->buffer + sizeof(*assoc_info); top = cbaf->buffer + assoc_size; - d_printf(1, dev, "Found %u association requests (%zu bytes)\n", + dev_dbg(dev, "Found %u association requests (%zu bytes)\n", assoc_info->NumAssociationRequests, assoc_size); + while (itr < top) { u16 ar_type, ar_subtype; u32 ar_size; const char *ar_name; assoc_request = itr; + if (top - itr < sizeof(*assoc_request)) { - dev_err(dev, "not enough data to decode associaton " + dev_err(dev, "Not enough data to decode associaton " "request (%zu vs %zu bytes needed)\n", top - itr, sizeof(*assoc_request)); break; } + ar_type = le16_to_cpu(assoc_request->AssociationTypeId); ar_subtype = le16_to_cpu(assoc_request->AssociationSubTypeId); ar_size = le32_to_cpu(assoc_request->AssociationTypeInfoSize); + ar_name = "unknown"; + switch (ar_type) { case AR_TYPE_WUSB: - /* Verify we have what is mandated by AMS1.0 */ + /* Verify we have what is mandated by [WUSB-AM]. */ switch (ar_subtype) { case AR_TYPE_WUSB_RETRIEVE_HOST_INFO: - ar_name = "retrieve_host_info"; - ar_rhi_idx = ar_index; + ar_name = "RETRIEVE_HOST_INFO"; + ar_rhi = 1; break; case AR_TYPE_WUSB_ASSOCIATE: /* send assoc data */ - ar_name = "associate"; - ar_assoc_idx = ar_index; + ar_name = "ASSOCIATE"; + ar_assoc = 1; break; - default: - ar_name = "unknown"; }; break; - default: - ar_name = "unknown"; }; - d_printf(1, dev, "association request #%02u: 0x%04x/%04x " + + dev_dbg(dev, "Association request #%02u: 0x%04x/%04x " "(%zu bytes): %s\n", assoc_request->AssociationDataIndex, ar_type, ar_subtype, (size_t)ar_size, ar_name); itr += sizeof(*assoc_request); - ar_index++; } - if (ar_rhi_idx == -1) { + + if (!ar_rhi) { dev_err(dev, "Missing RETRIEVE_HOST_INFO association " "request\n"); - goto error_bad_reqs; + return -EINVAL; } - if (ar_assoc_idx == -1) { + if (!ar_assoc) { dev_err(dev, "Missing ASSOCIATE association request\n"); - goto error_bad_reqs; + return -EINVAL; } - return 0; -error_bad_header: -error_bad_data: -error_bad_reqs: -error_get_assoc_types: - return -EINVAL; + return 0; } static const struct wusb_cbaf_host_info cbaf_host_info_defaults = { @@ -256,6 +247,7 @@ static const struct wusb_cbaf_host_info cbaf_host_info_defaults = { static int cbaf_send_host_info(struct cbaf *cbaf) { struct wusb_cbaf_host_info *hi; + size_t name_len; size_t hi_size; hi = cbaf->buffer; @@ -263,11 +255,11 @@ static int cbaf_send_host_info(struct cbaf *cbaf) *hi = cbaf_host_info_defaults; hi->CHID = cbaf->chid; hi->LangID = 0; /* FIXME: I guess... */ - strncpy(hi->HostFriendlyName, cbaf->host_name, - hi->HostFriendlyName_hdr.len); - hi->HostFriendlyName_hdr.len = - cpu_to_le16(strlen(hi->HostFriendlyName)); - hi_size = sizeof(*hi) + strlen(hi->HostFriendlyName); + strlcpy(hi->HostFriendlyName, cbaf->host_name, CBA_NAME_LEN); + name_len = strlen(cbaf->host_name); + hi->HostFriendlyName_hdr.len = cpu_to_le16(name_len); + hi_size = sizeof(*hi) + name_len; + return usb_control_msg(cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), CBAF_REQ_SET_ASSOCIATION_RESPONSE, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, @@ -276,8 +268,47 @@ static int cbaf_send_host_info(struct cbaf *cbaf) hi, hi_size, 1000 /* FIXME: arbitrary */); } -/* Show current CHID info we have set from user space */ -static ssize_t cbaf_wusb_host_info_show(struct device *dev, +/* + * Get device's information (CDID) associated to CHID + * + * The device will return it's information (CDID, name, bandgroups) + * associated to the CHID we have set before, or 0 CDID and default + * name and bandgroup if no CHID set or unknown. + */ +static int cbaf_cdid_get(struct cbaf *cbaf) +{ + int result; + struct device *dev = &cbaf->usb_iface->dev; + struct wusb_cbaf_device_info *di; + size_t needed; + + di = cbaf->buffer; + result = usb_control_msg( + cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), + CBAF_REQ_GET_ASSOCIATION_REQUEST, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, + di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); + if (result < 0) { + dev_err(dev, "Cannot request device information: %d\n", result); + return result; + } + + needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length); + if (result < needed) { + dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs " + "%zu bytes needed)\n", (size_t)result, needed); + return result; + } + + strlcpy(cbaf->device_name, di->DeviceFriendlyName, CBA_NAME_LEN); + cbaf->cdid = di->CDID; + cbaf->device_band_groups = le16_to_cpu(di->BandGroups); + + return 0; +} + +static ssize_t cbaf_wusb_chid_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -286,17 +317,10 @@ static ssize_t cbaf_wusb_host_info_show(struct device *dev, char pr_chid[WUSB_CKHDID_STRSIZE]; ckhdid_printf(pr_chid, sizeof(pr_chid), &cbaf->chid); - return scnprintf(buf, PAGE_SIZE, "CHID: %s\nName: %s\n", - pr_chid, cbaf->host_name); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_chid); } -/* - * Get a host info CHID from user space and send it to the device. - * - * The user can recover a CC from the device associated to that CHID - * by cat'ing wusb_connection_context. - */ -static ssize_t cbaf_wusb_host_info_store(struct device *dev, +static ssize_t cbaf_wusb_chid_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { @@ -308,8 +332,7 @@ static ssize_t cbaf_wusb_host_info_store(struct device *dev, "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx " - "%04hx %64s\n", + "%02hhx %02hhx %02hhx %02hhx", &cbaf->chid.data[0] , &cbaf->chid.data[1], &cbaf->chid.data[2] , &cbaf->chid.data[3], &cbaf->chid.data[4] , &cbaf->chid.data[5], @@ -317,24 +340,79 @@ static ssize_t cbaf_wusb_host_info_store(struct device *dev, &cbaf->chid.data[8] , &cbaf->chid.data[9], &cbaf->chid.data[10], &cbaf->chid.data[11], &cbaf->chid.data[12], &cbaf->chid.data[13], - &cbaf->chid.data[14], &cbaf->chid.data[15], - &cbaf->host_band_groups, cbaf->host_name); - if (result != 18) { - dev_err(dev, "Unrecognized CHID (need 16 8-bit hex digits, " - "a 16 bit hex band group mask " - "and a host name, got only %d)\n", (int)result); + &cbaf->chid.data[14], &cbaf->chid.data[15]); + + if (result != 16) return -EINVAL; - } + result = cbaf_send_host_info(cbaf); if (result < 0) - dev_err(dev, "Couldn't send host information to device: %d\n", - (int)result); - else - d_printf(1, dev, "HI sent, wusb_cc can be read now\n"); - return result < 0 ? result : size; + return result; + result = cbaf_cdid_get(cbaf); + if (result < 0) + return -result; + return size; +} +static DEVICE_ATTR(wusb_chid, 0600, cbaf_wusb_chid_show, cbaf_wusb_chid_store); + +static ssize_t cbaf_wusb_host_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->host_name); +} + +static ssize_t cbaf_wusb_host_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + result = sscanf(buf, "%63s", cbaf->host_name); + if (result != 1) + return -EINVAL; + + return size; +} +static DEVICE_ATTR(wusb_host_name, 0600, cbaf_wusb_host_name_show, + cbaf_wusb_host_name_store); + +static ssize_t cbaf_wusb_host_band_groups_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->host_band_groups); +} + +static ssize_t cbaf_wusb_host_band_groups_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t result; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + u16 band_groups = 0; + + result = sscanf(buf, "%04hx", &band_groups); + if (result != 1) + return -EINVAL; + + cbaf->host_band_groups = band_groups; + + return size; } -static DEVICE_ATTR(wusb_host_info, 0600, cbaf_wusb_host_info_show, - cbaf_wusb_host_info_store); + +static DEVICE_ATTR(wusb_host_band_groups, 0600, + cbaf_wusb_host_band_groups_show, + cbaf_wusb_host_band_groups_store); static const struct wusb_cbaf_device_info cbaf_device_info_defaults = { .Length_hdr = WUSB_AR_Length, @@ -344,77 +422,72 @@ static const struct wusb_cbaf_device_info cbaf_device_info_defaults = { .DeviceFriendlyName_hdr = WUSB_AR_DeviceFriendlyName, }; -/* - * Get device's information (CDID) associated to CHID - * - * The device will return it's information (CDID, name, bandgroups) - * associated to the CHID we have set before, or 0 CDID and default - * name and bandgroup if no CHID set or unknown. - */ -static int cbaf_cdid_get(struct cbaf *cbaf) +static ssize_t cbaf_wusb_cdid_show(struct device *dev, + struct device_attribute *attr, char *buf) { - int result; - struct device *dev = &cbaf->usb_iface->dev; - struct wusb_cbaf_device_info *di; - size_t needed, dev_name_size; + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + char pr_cdid[WUSB_CKHDID_STRSIZE]; - di = cbaf->buffer; - result = usb_control_msg( - cbaf->usb_dev, usb_rcvctrlpipe(cbaf->usb_dev, 0), - CBAF_REQ_GET_ASSOCIATION_REQUEST, - USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, - 0x0200, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, - di, cbaf->buffer_size, 1000 /* FIXME: arbitrary */); - if (result < 0) { - dev_err(dev, "Cannot request device information: %d\n", result); - goto error_req_di; - } - needed = result < sizeof(*di) ? sizeof(*di) : le32_to_cpu(di->Length); - if (result < needed) { - dev_err(dev, "Not enough data in DEVICE_INFO reply (%zu vs " - "%zu bytes needed)\n", (size_t)result, needed); - goto error_bad_di; - } - cbaf->cdid = di->CDID; - dev_name_size = le16_to_cpu(di->DeviceFriendlyName_hdr.len); - dev_name_size = dev_name_size > 65 - 1 ? 65 - 1 : dev_name_size; - memcpy(cbaf->device_name, di->DeviceFriendlyName, dev_name_size); - cbaf->device_name[dev_name_size] = 0; - cbaf->device_band_groups = le16_to_cpu(di->BandGroups); - result = 0; -error_req_di: -error_bad_di: - return result; + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); + return scnprintf(buf, PAGE_SIZE, "%s\n", pr_cdid); } -/* - * Get device information and print it to sysfs - * - * See cbaf_cdid_get() - */ -static ssize_t cbaf_wusb_cdid_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t cbaf_wusb_cdid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) { ssize_t result; struct usb_interface *iface = to_usb_interface(dev); struct cbaf *cbaf = usb_get_intfdata(iface); - char pr_cdid[WUSB_CKHDID_STRSIZE]; + struct wusb_ckhdid cdid; - result = cbaf_cdid_get(cbaf); - if (result < 0) { - dev_err(dev, "Cannot read device information: %d\n", - (int)result); - goto error_get_di; - } - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &cbaf->cdid); - result = scnprintf(buf, PAGE_SIZE, - "CDID: %s\nName: %s\nBand_groups: 0x%04x\n", - pr_cdid, cbaf->device_name, - cbaf->device_band_groups); -error_get_di: - return result; + result = sscanf(buf, + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx " + "%02hhx %02hhx %02hhx %02hhx", + &cdid.data[0] , &cdid.data[1], + &cdid.data[2] , &cdid.data[3], + &cdid.data[4] , &cdid.data[5], + &cdid.data[6] , &cdid.data[7], + &cdid.data[8] , &cdid.data[9], + &cdid.data[10], &cdid.data[11], + &cdid.data[12], &cdid.data[13], + &cdid.data[14], &cdid.data[15]); + if (result != 16) + return -EINVAL; + + cbaf->cdid = cdid; + + return size; +} +static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, cbaf_wusb_cdid_store); + +static ssize_t cbaf_wusb_device_band_groups_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "0x%04x\n", cbaf->device_band_groups); +} + +static DEVICE_ATTR(wusb_device_band_groups, 0600, + cbaf_wusb_device_band_groups_show, + NULL); + +static ssize_t cbaf_wusb_device_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *iface = to_usb_interface(dev); + struct cbaf *cbaf = usb_get_intfdata(iface); + + return scnprintf(buf, PAGE_SIZE, "%s\n", cbaf->device_name); } -static DEVICE_ATTR(wusb_cdid, 0600, cbaf_wusb_cdid_show, NULL); +static DEVICE_ATTR(wusb_device_name, 0600, cbaf_wusb_device_name_show, NULL); static const struct wusb_cbaf_cc_data cbaf_cc_data_defaults = { .AssociationTypeId_hdr = WUSB_AR_AssociationTypeId, @@ -435,9 +508,7 @@ static const struct wusb_cbaf_cc_data_fail cbaf_cc_data_fail_defaults = { }; /* - * Send a new CC to the device - * - * So we update the CK and send the whole thing to the device + * Send a new CC to the device. */ static int cbaf_cc_upload(struct cbaf *cbaf) { @@ -452,30 +523,25 @@ static int cbaf_cc_upload(struct cbaf *cbaf) ccd->CDID = cbaf->cdid; ccd->CK = cbaf->ck; ccd->BandGroups = cpu_to_le16(cbaf->host_band_groups); + + dev_dbg(dev, "Trying to upload CC:\n"); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); + dev_dbg(dev, " CHID %s\n", pr_cdid); + ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); + dev_dbg(dev, " CDID %s\n", pr_cdid); + dev_dbg(dev, " Bandgroups 0x%04x\n", cbaf->host_band_groups); + result = usb_control_msg( cbaf->usb_dev, usb_sndctrlpipe(cbaf->usb_dev, 0), CBAF_REQ_SET_ASSOCIATION_RESPONSE, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0x0201, cbaf->usb_iface->cur_altsetting->desc.bInterfaceNumber, ccd, sizeof(*ccd), 1000 /* FIXME: arbitrary */); - d_printf(1, dev, "Uploaded CC:\n"); - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CHID); - d_printf(1, dev, " CHID %s\n", pr_cdid); - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CDID); - d_printf(1, dev, " CDID %s\n", pr_cdid); - ckhdid_printf(pr_cdid, sizeof(pr_cdid), &ccd->CK); - d_printf(1, dev, " CK %s\n", pr_cdid); - d_printf(1, dev, " bandgroups 0x%04x\n", cbaf->host_band_groups); + return result; } -/* - * Send a new CC to the device - * - * We take the CDID and CK from user space, the rest from the info we - * set with host_info. - */ -static ssize_t cbaf_wusb_cc_store(struct device *dev, +static ssize_t cbaf_wusb_ck_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { @@ -484,23 +550,10 @@ static ssize_t cbaf_wusb_cc_store(struct device *dev, struct cbaf *cbaf = usb_get_intfdata(iface); result = sscanf(buf, - "CDID: %02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx\n" - "CK: %02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " "%02hhx %02hhx %02hhx %02hhx " - "%02hhx %02hhx %02hhx %02hhx\n", - &cbaf->cdid.data[0] , &cbaf->cdid.data[1], - &cbaf->cdid.data[2] , &cbaf->cdid.data[3], - &cbaf->cdid.data[4] , &cbaf->cdid.data[5], - &cbaf->cdid.data[6] , &cbaf->cdid.data[7], - &cbaf->cdid.data[8] , &cbaf->cdid.data[9], - &cbaf->cdid.data[10], &cbaf->cdid.data[11], - &cbaf->cdid.data[12], &cbaf->cdid.data[13], - &cbaf->cdid.data[14], &cbaf->cdid.data[15], - + "%02hhx %02hhx %02hhx %02hhx", &cbaf->ck.data[0] , &cbaf->ck.data[1], &cbaf->ck.data[2] , &cbaf->ck.data[3], &cbaf->ck.data[4] , &cbaf->ck.data[5], @@ -509,25 +562,25 @@ static ssize_t cbaf_wusb_cc_store(struct device *dev, &cbaf->ck.data[10], &cbaf->ck.data[11], &cbaf->ck.data[12], &cbaf->ck.data[13], &cbaf->ck.data[14], &cbaf->ck.data[15]); - if (result != 32) { - dev_err(dev, "Unrecognized CHID/CK (need 32 8-bit " - "hex digits, got only %d)\n", (int)result); + if (result != 16) return -EINVAL; - } + result = cbaf_cc_upload(cbaf); if (result < 0) - dev_err(dev, "Couldn't upload connection context: %d\n", - (int)result); - else - d_printf(1, dev, "Connection context uploaded\n"); - return result < 0 ? result : size; + return result; + + return size; } -static DEVICE_ATTR(wusb_cc, 0600, NULL, cbaf_wusb_cc_store); +static DEVICE_ATTR(wusb_ck, 0600, NULL, cbaf_wusb_ck_store); static struct attribute *cbaf_dev_attrs[] = { - &dev_attr_wusb_host_info.attr, + &dev_attr_wusb_host_name.attr, + &dev_attr_wusb_host_band_groups.attr, + &dev_attr_wusb_chid.attr, &dev_attr_wusb_cdid.attr, - &dev_attr_wusb_cc.attr, + &dev_attr_wusb_device_name.attr, + &dev_attr_wusb_device_band_groups.attr, + &dev_attr_wusb_ck.attr, NULL, }; @@ -539,32 +592,33 @@ static struct attribute_group cbaf_dev_attr_group = { static int cbaf_probe(struct usb_interface *iface, const struct usb_device_id *id) { - int result; struct cbaf *cbaf; struct device *dev = &iface->dev; + int result = -ENOMEM; - result = -ENOMEM; cbaf = kzalloc(sizeof(*cbaf), GFP_KERNEL); - if (cbaf == NULL) { - dev_err(dev, "Unable to allocate instance\n"); + if (cbaf == NULL) goto error_kzalloc; - } cbaf->buffer = kmalloc(512, GFP_KERNEL); if (cbaf->buffer == NULL) goto error_kmalloc_buffer; + cbaf->buffer_size = 512; cbaf->usb_dev = usb_get_dev(interface_to_usbdev(iface)); cbaf->usb_iface = usb_get_intf(iface); result = cbaf_check(cbaf); - if (result < 0) + if (result < 0) { + dev_err(dev, "This device is not WUSB-CBAF compliant" + "and is not supported yet.\n"); goto error_check; + } + result = sysfs_create_group(&dev->kobj, &cbaf_dev_attr_group); if (result < 0) { dev_err(dev, "Can't register sysfs attr group: %d\n", result); goto error_create_group; } usb_set_intfdata(iface, cbaf); - d_printf(2, dev, "CBA attached\n"); return 0; error_create_group: @@ -587,7 +641,6 @@ static void cbaf_disconnect(struct usb_interface *iface) /* paranoia: clean up crypto keys */ memset(cbaf, 0, sizeof(*cbaf)); kfree(cbaf); - d_printf(1, dev, "CBA detached\n"); } static struct usb_device_id cbaf_id_table[] = { -- cgit v1.2.3 From 6a7c3e464eb75310d011a6f2ea2953e6f5d91d55 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:43 +0100 Subject: uwb: depend on EXPERIMENTAL The UWB stack has some sysfs APIs that will change thus it's best marked as EXPERIMENTAL until these APIs are finalized. Signed-off-by: David Vrabel --- drivers/usb/host/Kconfig | 6 ++++-- drivers/usb/wusbcore/Kconfig | 3 ++- drivers/uwb/Kconfig | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 515044129bb..72fb655e603 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -307,7 +307,8 @@ config SUPERH_ON_CHIP_R8A66597 SH7366 and SH7723 processors. config USB_WHCI_HCD - tristate "Wireless USB Host Controller Interface (WHCI) driver" + tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)" + depends on EXPERIMENTAL depends on PCI && USB select USB_WUSB select UWB_WHCI @@ -319,7 +320,8 @@ config USB_WHCI_HCD will be called "whci-hcd". config USB_HWA_HCD - tristate "Host Wire Adapter (HWA) driver" + tristate "Host Wire Adapter (HWA) driver (EXPERIMENTAL)" + depends on EXPERIMENTAL depends on USB select USB_WUSB select UWB_HWA diff --git a/drivers/usb/wusbcore/Kconfig b/drivers/usb/wusbcore/Kconfig index 4ea76693ad4..eb09a0a14a8 100644 --- a/drivers/usb/wusbcore/Kconfig +++ b/drivers/usb/wusbcore/Kconfig @@ -2,7 +2,8 @@ # Wireless USB Core configuration # config USB_WUSB - tristate "Enable Wireless USB extensions" + tristate "Enable Wireless USB extensions (EXPERIMENTAL)" + depends on EXPERIMENTAL depends on USB select UWB select CRYPTO diff --git a/drivers/uwb/Kconfig b/drivers/uwb/Kconfig index 317e9fefbc0..ca783127af3 100644 --- a/drivers/uwb/Kconfig +++ b/drivers/uwb/Kconfig @@ -3,7 +3,8 @@ # menuconfig UWB - tristate "Ultra Wide Band devices" + tristate "Ultra Wideband devices (EXPERIMENTAL)" + depends on EXPERIMENTAL depends on PCI default n help -- cgit v1.2.3 From 18332a80c3e2000336ed2cb7ef9a1c6a3991ab61 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 17 Sep 2008 16:34:44 +0100 Subject: uwb: add entries in the MAINTAINERS file Add entries for the UWB, WUSB, and WLP subsystems to the MAINTAINERS file. Signed-off-by: David Vrabel --- MAINTAINERS | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 0a613cb926c..63458274ebd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1048,6 +1048,12 @@ L: cbe-oss-dev@ozlabs.org W: http://www.ibm.com/developerworks/power/cell/ S: Supported +CERTIFIED WIRELESS USB (WUSB) SUBSYSTEM: +P: David Vrabel +M: david.vrabel@csr.com +L: linux-usb@vger.kernel.org +S: Supported + CFAG12864B LCD DRIVER P: Miguel Ojeda Sandonis M: miguel.ojeda.sandonis@gmail.com @@ -4137,6 +4143,12 @@ L: sparclinux@vger.kernel.org T: git kernel.org:/pub/scm/linux/kernel/git/davem/sparc-2.6.git S: Maintained +ULTRA-WIDEBAND (UWB) SUBSYSTEM: +P: David Vrabel +M: david.vrabel@csr.com +L: linux-usb@vger.kernel.org +S: Supported + UNIFORM CDROM DRIVER P: Jens Axboe M: axboe@kernel.dk @@ -4532,6 +4544,11 @@ M: zaga@fly.cc.fer.hr L: linux-scsi@vger.kernel.org S: Maintained +WIMEDIA LLC PROTOCOL (WLP) SUBSYSTEM +P: David Vrabel +M: david.vrabel@csr.com +S: Maintained + WISTRON LAPTOP BUTTON DRIVER P: Miloslav Trmac M: mitr@volny.cz -- cgit v1.2.3 From edfa042c880f62848aa55b3e71e538fe383929da Mon Sep 17 00:00:00 2001 From: Inaky Perez-Gonzalez Date: Wed, 17 Sep 2008 16:34:45 +0100 Subject: uwb: add credits for the original developers of the UWB/WUSB/WLP subsystems Signed-off-by: Inaky Perez-Gonzalez Signed-off-by: David Vrabel --- CREDITS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CREDITS b/CREDITS index c62dcb3b7e2..4fad6717e56 100644 --- a/CREDITS +++ b/CREDITS @@ -598,6 +598,11 @@ S: Tamsui town, Taipei county, S: Taiwan 251 S: Republic of China +N: Reinette Chatre +E: reinette.chatre@intel.com +D: WiMedia Link Protocol implementation +D: UWB stack bits and pieces + N: Michael Elizabeth Chastain E: mec@shout.net D: Configure, Menuconfig, xconfig @@ -2695,6 +2700,12 @@ S: Demonstratsii 8-382 S: Tula 300000 S: Russia +N: Inaky Perez-Gonzalez +E: inaky.perez-gonzalez@intel.com +D: UWB stack, HWA-RC driver and HWA-HC drivers +D: Wireless USB additions to the USB stack +D: WiMedia Link Protocol bits and pieces + N: Gordon Peters E: GordPeters@smarttech.com D: Isochronous receive for IEEE 1394 driver (OHCI module). -- cgit v1.2.3 From 45f197ade73ba95681b9803680c75352fc0a1c0a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Sat, 20 Sep 2008 12:58:40 +0200 Subject: x86, oprofile: BUG: using smp_processor_id() in preemptible code Add __raw access before setting per cpu variable switch_index, to avoid the following BUG: [ 449.166827] BUG: using smp_processor_id() in preemptible [00000000] code: modprobe/6998 [ 449.166848] caller is op_nmi_init+0xf0/0x2b0 [oprofile] [ 449.166855] Pid: 6998, comm: modprobe Not tainted 2.6.27-rc5-mm1 #29 [ 449.166860] Call Trace: [ 449.166872] [] debug_smp_processor_id+0xd7/0xe0 [ 449.166887] [] op_nmi_init+0xf0/0x2b0 [oprofile] [ 449.166902] [] oprofile_init+0x0/0x60 [oprofile] [ 449.166915] [] oprofile_arch_init+0x9/0x30 [oprofile] [ 449.166928] [] oprofile_init+0x1e/0x60 [oprofile] [ 449.166937] [] _stext+0x3b/0x160 [ 449.166946] [] __mutex_unlock_slowpath+0xe5/0x190 [ 449.166955] [] trace_hardirqs_on_caller+0xca/0x140 [ 449.166965] [] sys_init_module+0xdc/0x210 [ 449.166972] [] system_call_fastpath+0x16/0x1b Signed-off-by: Andrea Righi Acked-by: Robert Richter Signed-off-by: Ingo Molnar --- arch/x86/oprofile/nmi_int.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/oprofile/nmi_int.c b/arch/x86/oprofile/nmi_int.c index fb4902bc6f1..4108d02c529 100644 --- a/arch/x86/oprofile/nmi_int.c +++ b/arch/x86/oprofile/nmi_int.c @@ -551,7 +551,7 @@ int __init op_nmi_init(struct oprofile_operations *ops) } /* default values, can be overwritten by model */ - __get_cpu_var(switch_index) = 0; + __raw_get_cpu_var(switch_index) = 0; ops->create_files = nmi_create_files; ops->setup = nmi_setup; ops->shutdown = nmi_shutdown; -- cgit v1.2.3 From a0ad05c75aa362c91f4d9cd91ff375a739574dd8 Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Mon, 1 Sep 2008 14:27:02 +0200 Subject: Introduce FW_BUG, FW_WARN and FW_INFO to consistenly tell users about BIOS bugs The idea is to add this to printk after the severity: printk(KERN_ERR FW_BUG "This is not our fault, BIOS developer: fix it by simply add ...\n"); If a Firmware issue should be hidden, because it is work-arounded, but you still want to see something popping up e.g. for info only: printk(KERN_INFO FW_INFO "This is done stupid, we can handle it, but it should better be avoided in future\n"); or on the Linuxfirmwarekit to tell vendors that they did something stupid or wrong without bothering the user: printk(KERN_INFO FW_BUG "This is done stupid, we can handle it, but it should better be avoided in future\n"); Some use cases: - If a user sees a [Firmware Bug] message in the kernel he should first update the BIOS before wasting time with debugging and submiting on old firmware code to mailing lists. - The linuxfirmwarekit (http://www.linuxfirmwarekit.org) tries to detect firmware bugs. It currently is doing that in userspace which results in: - Huge test scripts that could be a one liner in the kernel - A lot of BIOS bugs are already absorbed by the kernel What do we need such a stupid linuxfirmwarekit for? - Vendors: Can test their BIOSes for Linux compatibility. There will be the time when vendors realize that the test utils on Linux are more strict and using them increases the qualitity and stability of their products. - Vendors: Can easily fix up their BIOSes and be more Linux compatible by: dmesg |grep "Firmware Bug" and send the result to their BIOS developer colleagues who should know what the messages are about and how to fix them, without the need of studying kernel code. - Distributions: can do a first automated HW/BIOS checks. This can then be done without the need of asking kernel developers who need to dig down the code and explain the details. Certification can/will just be rejected until dmesg |grep "Firmware Bug" is empty. - Thus this can be used as an instrument to enforce cleaner BIOS code. Currently every stupid Windows ACPI bug is re-implemented in Linux which is a rather unfortunate situation. We already have the power to avoid this in e.g. memory or cpu hot-plug ACPI implementations, because Linux certification is a must for most vendors in the server area. Working towards being able to do that in the laptop area (vendors are starting to look at Linux here also and will use this tool) is the goal. At least provide them a tool to make it as easy for this guys (e.g. not needing to browse kernel code) as possible. - The ordinary Linux user: can go into the next shop, boots the firmwarekit on his most preferred machines. He chooses one without BIOS bugs. Unsupported HW is ok, he likes to try out latest projects which might support them or likes to dig on it on his own, but he hates to workaround broken BIOSes like hell. I double checked with the firmwarekit. There they have: So the mapping generally is (also depending on how likely the BIOS is to blame, this could sometimes be difficult): FW_INFO = INFO FW_WARN = WARN FW_BUG = FAIL For more info about the linuxfirmwarekit and why this is needed can be found here: http://www.linuxfirmwarekit.org While severity matches with the firmwarekit, it might be tricky to hide messages from the user. E.g. we recently found out that on HP BIOSes negative temperatures are returned, which seem to indicate that the thermal zone is invalid. We can work around that gracefully by ignoring the thermal zone and we do not want to bother the ordinary user with a frightening message: Firmware Bug: thermal management absolutely broken but want to hide it from the user. But in the linuxfirmwarekit this should be shown as a real show stopper (the temperatures could really be wrong, broken thermal management is one of the worst things that can happen and the BIOS guys of the machine must implement this properly). It is intended to do that (hide it from the user with KERN_INFO msg, but still print it as a BIOS bug) by: printk(KERN_INFO FW_BUG "Negativ temperature values detected. Try to workarounded, BIOS must get fixed\n"); Hope that works out..., no idea how to better hide it as printk is the only way to easily provide this functionality. Signed-off-by: Thomas Renninger Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- include/linux/kernel.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 2651f805ba6..0b19848e380 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -190,6 +190,30 @@ extern int kernel_text_address(unsigned long addr); struct pid; extern struct pid *session_of_pgrp(struct pid *pgrp); +/* + * FW_BUG + * Add this to a message where you are sure the firmware is buggy or behaves + * really stupid or out of spec. Be aware that the responsible BIOS developer + * should be able to fix this issue or at least get a concrete idea of the + * problem by reading your message without the need of looking at the kernel + * code. + * + * Use it for definite and high priority BIOS bugs. + * + * FW_WARN + * Use it for not that clear (e.g. could the kernel messed up things already?) + * and medium priority BIOS bugs. + * + * FW_INFO + * Use this one if you want to tell the user or vendor about something + * suspicious, but generally harmless related to the firmware. + * + * Use it for information or very low priority BIOS bugs. + */ +#define FW_BUG "[Firmware Bug]: " +#define FW_WARN "[Firmware Warn]: " +#define FW_INFO "[Firmware Info]: " + #ifdef CONFIG_PRINTK asmlinkage int vprintk(const char *fmt, va_list args) __attribute__ ((format (printf, 1, 0))); -- cgit v1.2.3 From 910dfae298f7b3dae0e9a52736182a3b0559ca35 Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Mon, 1 Sep 2008 14:27:04 +0200 Subject: ACPI: cpufreq, processor: Detect old BIOS, not supporting CPU freq on a recent CPU. On Intel CPUs it is rather common and a good hint that BIOSes which do provide _PPC func, but not the frequencies itself in _PSS function, are old and need to be updated for CPU freq support. Tell the user/vendor he has a BIOS/firmware problem. Make use of FW_BUG interface to give vendors and users the ability to automatically check with (or let linuxfirmwarekit do that): dmesg |grep "Firmware Bug" Signed-off-by: Thomas Renninger Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/acpi/processor_perflib.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/processor_perflib.c b/drivers/acpi/processor_perflib.c index 80c251ec6d2..242f8143008 100644 --- a/drivers/acpi/processor_perflib.c +++ b/drivers/acpi/processor_perflib.c @@ -38,6 +38,7 @@ #include #endif +#include #include #include @@ -334,7 +335,6 @@ static int acpi_processor_get_performance_info(struct acpi_processor *pr) acpi_status status = AE_OK; acpi_handle handle = NULL; - if (!pr || !pr->performance || !pr->handle) return -EINVAL; @@ -347,13 +347,25 @@ static int acpi_processor_get_performance_info(struct acpi_processor *pr) result = acpi_processor_get_performance_control(pr); if (result) - return result; + goto update_bios; result = acpi_processor_get_performance_states(pr); if (result) - return result; + goto update_bios; return 0; + + /* + * Having _PPC but missing frequencies (_PSS, _PCT) is a very good hint that + * the BIOS is older than the CPU and does not know its frequencies + */ + update_bios: + if (ACPI_SUCCESS(acpi_get_handle(pr->handle, "_PPC", &handle))){ + if(boot_cpu_has(X86_FEATURE_EST)) + printk(KERN_WARNING FW_BUG "BIOS needs update for CPU " + "frequency support\n"); + } + return result; } int acpi_processor_notify_smm(struct module *calling_module) -- cgit v1.2.3 From 2fd47094f92fa2bdbf99be33294a7b6b97785a70 Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Mon, 1 Sep 2008 14:27:03 +0200 Subject: CPUFREQ: powernow-k8: Try to detect old BIOS, not supporting CPU freq on a recent AMD CPUs. Make use of FW_BUG interface to give vendors and users the ability to automatically check for powernow-k8 related BIOS bugs by: dmesg |grep "Firmware Bug" Signed-off-by: Thomas Renninger Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- arch/x86/kernel/cpu/cpufreq/powernow-k8.c | 42 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/arch/x86/kernel/cpu/cpufreq/powernow-k8.c b/arch/x86/kernel/cpu/cpufreq/powernow-k8.c index 84bb395038d..4e0c6abd7ca 100644 --- a/arch/x86/kernel/cpu/cpufreq/powernow-k8.c +++ b/arch/x86/kernel/cpu/cpufreq/powernow-k8.c @@ -45,7 +45,6 @@ #endif #define PFX "powernow-k8: " -#define BFX PFX "BIOS error: " #define VERSION "version 2.20.00" #include "powernow-k8.h" @@ -536,35 +535,40 @@ static int check_pst_table(struct powernow_k8_data *data, struct pst_s *pst, u8 for (j = 0; j < data->numps; j++) { if (pst[j].vid > LEAST_VID) { - printk(KERN_ERR PFX "vid %d invalid : 0x%x\n", j, pst[j].vid); + printk(KERN_ERR FW_BUG PFX "vid %d invalid : 0x%x\n", + j, pst[j].vid); return -EINVAL; } if (pst[j].vid < data->rvo) { /* vid + rvo >= 0 */ - printk(KERN_ERR BFX "0 vid exceeded with pstate %d\n", j); + printk(KERN_ERR FW_BUG PFX "0 vid exceeded with pstate" + " %d\n", j); return -ENODEV; } if (pst[j].vid < maxvid + data->rvo) { /* vid + rvo >= maxvid */ - printk(KERN_ERR BFX "maxvid exceeded with pstate %d\n", j); + printk(KERN_ERR FW_BUG PFX "maxvid exceeded with pstate" + " %d\n", j); return -ENODEV; } if (pst[j].fid > MAX_FID) { - printk(KERN_ERR BFX "maxfid exceeded with pstate %d\n", j); + printk(KERN_ERR FW_BUG PFX "maxfid exceeded with pstate" + " %d\n", j); return -ENODEV; } if (j && (pst[j].fid < HI_FID_TABLE_BOTTOM)) { /* Only first fid is allowed to be in "low" range */ - printk(KERN_ERR BFX "two low fids - %d : 0x%x\n", j, pst[j].fid); + printk(KERN_ERR FW_BUG PFX "two low fids - %d : " + "0x%x\n", j, pst[j].fid); return -EINVAL; } if (pst[j].fid < lastfid) lastfid = pst[j].fid; } if (lastfid & 1) { - printk(KERN_ERR BFX "lastfid invalid\n"); + printk(KERN_ERR FW_BUG PFX "lastfid invalid\n"); return -EINVAL; } if (lastfid > LO_FID_TABLE_TOP) - printk(KERN_INFO BFX "first fid not from lo freq table\n"); + printk(KERN_INFO FW_BUG PFX "first fid not from lo freq table\n"); return 0; } @@ -672,13 +676,13 @@ static int find_psb_table(struct powernow_k8_data *data) dprintk("table vers: 0x%x\n", psb->tableversion); if (psb->tableversion != PSB_VERSION_1_4) { - printk(KERN_ERR BFX "PSB table is not v1.4\n"); + printk(KERN_ERR FW_BUG PFX "PSB table is not v1.4\n"); return -ENODEV; } dprintk("flags: 0x%x\n", psb->flags1); if (psb->flags1) { - printk(KERN_ERR BFX "unknown flags\n"); + printk(KERN_ERR FW_BUG PFX "unknown flags\n"); return -ENODEV; } @@ -705,7 +709,7 @@ static int find_psb_table(struct powernow_k8_data *data) } } if (cpst != 1) { - printk(KERN_ERR BFX "numpst must be 1\n"); + printk(KERN_ERR FW_BUG PFX "numpst must be 1\n"); return -ENODEV; } @@ -1130,17 +1134,19 @@ static int __cpuinit powernowk8_cpu_init(struct cpufreq_policy *pol) "ACPI Processor module before starting this " "driver.\n"); #else - printk(KERN_ERR PFX "Your BIOS does not provide ACPI " - "_PSS objects in a way that Linux understands. " - "Please report this to the Linux ACPI maintainers" - " and complain to your BIOS vendor.\n"); + printk(KERN_ERR FW_BUG PFX "Your BIOS does not provide" + " ACPI _PSS objects in a way that Linux " + "understands. Please report this to the Linux " + "ACPI maintainers and complain to your BIOS " + "vendor.\n"); #endif kfree(data); return -ENODEV; } if (pol->cpu != 0) { - printk(KERN_ERR PFX "No ACPI _PSS objects for CPU other than " - "CPU0. Complain to your BIOS vendor.\n"); + printk(KERN_ERR FW_BUG PFX "No ACPI _PSS objects for " + "CPU other than CPU0. Complain to your BIOS " + "vendor.\n"); kfree(data); return -ENODEV; } @@ -1193,7 +1199,7 @@ static int __cpuinit powernowk8_cpu_init(struct cpufreq_policy *pol) /* min/max the cpu is capable of */ if (cpufreq_frequency_table_cpuinfo(pol, data->powernow_table)) { - printk(KERN_ERR PFX "invalid powernow_table\n"); + printk(KERN_ERR FW_BUG PFX "invalid powernow_table\n"); powernow_k8_cpu_exit_acpi(data); kfree(data->powernow_table); kfree(data); -- cgit v1.2.3 From 8b59560a3baf2e7c24e0fb92ea5d09eca92805db Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:02:03 +0800 Subject: ACPI: dock: avoid check _STA method In some BIOSes, every _STA method call will send a notification again, this cause freeze. And in some BIOSes, it appears _STA should be called after _DCK. This tries to avoid calls _STA, and still keep the device present check. http://bugzilla.kernel.org/show_bug.cgi?id=10431 Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/dock.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 7d2edf143f1..25d2161ae9d 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -604,14 +604,17 @@ static int handle_eject_request(struct dock_station *ds, u32 event) static void dock_notify(acpi_handle handle, u32 event, void *data) { struct dock_station *ds = data; + struct acpi_device *tmp; switch (event) { case ACPI_NOTIFY_BUS_CHECK: - if (!dock_in_progress(ds) && dock_present(ds)) { + if (!dock_in_progress(ds) && acpi_bus_get_device(ds->handle, + &tmp)) { begin_dock(ds); dock(ds); if (!dock_present(ds)) { printk(KERN_ERR PREFIX "Unable to dock!\n"); + complete_dock(ds); break; } atomic_notifier_call_chain(&dock_notifier_list, -- cgit v1.2.3 From 82545394e0690aaef446cb262aa5dac0f9c7156e Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:02:41 +0800 Subject: dock: fix eject request process (2.6.27-rc1 regression) commit 2a7feab28d3fc060d320eaba192e49dad1079b7e introduces a bug. My thinkpad actually will send an eject_request and we should follow the eject process to finish the eject, otherwise system still thinks the bay is present. Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/dock.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 25d2161ae9d..78d27cef573 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -575,11 +575,6 @@ static int handle_eject_request(struct dock_station *ds, u32 event) */ dock_event(ds, event, UNDOCK_EVENT); - if (!dock_present(ds)) { - complete_undock(ds); - return -ENODEV; - } - hotplug_dock_devices(ds, ACPI_NOTIFY_EJECT_REQUEST); undock(ds); eject_dock(ds); -- cgit v1.2.3 From 406f692d0803d73acd3984c1e11719d3a913fd5e Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:03:26 +0800 Subject: dock: add _LCK support support _LCK method, which is a optional method for hotplug lenb: we have not seen _LCK used in the field yet Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/dock.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 78d27cef573..7bdf93b7469 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -452,6 +452,25 @@ static inline void complete_undock(struct dock_station *ds) ds->flags &= ~(DOCK_UNDOCKING); } +static void dock_lock(struct dock_station *ds, int lock) +{ + struct acpi_object_list arg_list; + union acpi_object arg; + acpi_status status; + + arg_list.count = 1; + arg_list.pointer = &arg; + arg.type = ACPI_TYPE_INTEGER; + arg.integer.value = !!lock; + status = acpi_evaluate_object(ds->handle, "_LCK", &arg_list, NULL); + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { + if (lock) + printk(KERN_WARNING PREFIX "Locking device failed\n"); + else + printk(KERN_WARNING PREFIX "Unlocking device failed\n"); + } +} + /** * dock_in_progress - see if we are in the middle of handling a dock event * @ds: the dock station @@ -577,6 +596,7 @@ static int handle_eject_request(struct dock_station *ds, u32 event) hotplug_dock_devices(ds, ACPI_NOTIFY_EJECT_REQUEST); undock(ds); + dock_lock(ds, 0); eject_dock(ds); if (dock_present(ds)) { printk(KERN_ERR PREFIX "Unable to undock!\n"); @@ -617,6 +637,7 @@ static void dock_notify(acpi_handle handle, u32 event, void *data) hotplug_dock_devices(ds, event); complete_dock(ds); dock_event(ds, event, DOCK_EVENT); + dock_lock(ds, 1); } break; case ACPI_NOTIFY_DEVICE_CHECK: -- cgit v1.2.3 From db350b084dc2cf816288643861ce07b0562dd723 Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:03:58 +0800 Subject: dock: add bay and battery hotplug support Make the dock driver support bay and battery hotplug. They are all regarded as dock, so handling can be unified. Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/dock.c | 223 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 175 insertions(+), 48 deletions(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 7bdf93b7469..799a0fdbb62 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -48,7 +48,6 @@ MODULE_PARM_DESC(immediate_undock, "1 (default) will cause the driver to " " before undocking"); static struct atomic_notifier_head dock_notifier_list; -static struct platform_device *dock_device; static char dock_device_name[] = "dock"; static const struct acpi_device_id dock_device_ids[] = { @@ -65,7 +64,12 @@ struct dock_station { struct mutex hp_lock; struct list_head dependent_devices; struct list_head hotplug_devices; + + struct list_head sibiling; + struct platform_device *dock_device; }; +static LIST_HEAD(dock_stations); +static int dock_station_count; struct dock_dependent_device { struct list_head list; @@ -77,11 +81,12 @@ struct dock_dependent_device { #define DOCK_DOCKING 0x00000001 #define DOCK_UNDOCKING 0x00000002 +#define DOCK_IS_DOCK 0x00000010 +#define DOCK_IS_ATA 0x00000020 +#define DOCK_IS_BAT 0x00000040 #define DOCK_EVENT 3 #define UNDOCK_EVENT 2 -static struct dock_station *dock_station; - /***************************************************************************** * Dock Dependent device functions * *****************************************************************************/ @@ -199,6 +204,60 @@ static int is_dock(acpi_handle handle) return 1; } +static int is_ejectable(acpi_handle handle) +{ + acpi_status status; + acpi_handle tmp; + + status = acpi_get_handle(handle, "_EJ0", &tmp); + if (ACPI_FAILURE(status)) + return 0; + return 1; +} + +static int is_ata(acpi_handle handle) +{ + acpi_handle tmp; + + if ((ACPI_SUCCESS(acpi_get_handle(handle, "_GTF", &tmp))) || + (ACPI_SUCCESS(acpi_get_handle(handle, "_GTM", &tmp))) || + (ACPI_SUCCESS(acpi_get_handle(handle, "_STM", &tmp))) || + (ACPI_SUCCESS(acpi_get_handle(handle, "_SDD", &tmp)))) + return 1; + + return 0; +} + +static int is_battery(acpi_handle handle) +{ + struct acpi_device_info *info; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + int ret = 1; + + if (!ACPI_SUCCESS(acpi_get_object_info(handle, &buffer))) + return 0; + info = buffer.pointer; + if (!(info->valid & ACPI_VALID_HID)) + ret = 0; + else + ret = !strcmp("PNP0C0A", info->hardware_id.value); + + kfree(buffer.pointer); + return ret; +} + +static int is_ejectable_bay(acpi_handle handle) +{ + acpi_handle phandle; + if (!is_ejectable(handle)) + return 0; + if (is_battery(handle) || is_ata(handle)) + return 1; + if (!acpi_get_parent(handle, &phandle) && is_ata(phandle)) + return 1; + return 0; +} + /** * is_dock_device - see if a device is on a dock station * @handle: acpi handle of the device @@ -209,11 +268,17 @@ static int is_dock(acpi_handle handle) */ int is_dock_device(acpi_handle handle) { - if (!dock_station) + struct dock_station *dock_station; + + if (!dock_station_count) return 0; - if (is_dock(handle) || find_dock_dependent_device(dock_station, handle)) + if (is_dock(handle)) return 1; + list_for_each_entry(dock_station, &dock_stations, sibiling) { + if (find_dock_dependent_device(dock_station, handle)) + return 1; + } return 0; } @@ -341,7 +406,7 @@ static void hotplug_dock_devices(struct dock_station *ds, u32 event) static void dock_event(struct dock_station *ds, u32 event, int num) { - struct device *dev = &dock_device->dev; + struct device *dev = &ds->dock_device->dev; char event_string[13]; char *envp[] = { event_string, NULL }; @@ -414,7 +479,7 @@ static void handle_dock(struct dock_station *ds, int dock) arg.type = ACPI_TYPE_INTEGER; arg.integer.value = dock; status = acpi_evaluate_object(ds->handle, "_DCK", &arg_list, &buffer); - if (ACPI_FAILURE(status)) + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) printk(KERN_ERR PREFIX "%s - failed to execute _DCK\n", (char *)name_buffer.pointer); kfree(buffer.pointer); @@ -498,7 +563,7 @@ static int dock_in_progress(struct dock_station *ds) */ int register_dock_notifier(struct notifier_block *nb) { - if (!dock_station) + if (!dock_station_count) return -ENODEV; return atomic_notifier_chain_register(&dock_notifier_list, nb); @@ -512,7 +577,7 @@ EXPORT_SYMBOL_GPL(register_dock_notifier); */ void unregister_dock_notifier(struct notifier_block *nb) { - if (!dock_station) + if (!dock_station_count) return; atomic_notifier_chain_unregister(&dock_notifier_list, nb); @@ -535,20 +600,23 @@ register_hotplug_dock_device(acpi_handle handle, acpi_notify_handler handler, void *context) { struct dock_dependent_device *dd; + struct dock_station *dock_station; - if (!dock_station) + if (!dock_station_count) return -ENODEV; /* * make sure this handle is for a device dependent on the dock, * this would include the dock station itself */ - dd = find_dock_dependent_device(dock_station, handle); - if (dd) { - dd->handler = handler; - dd->context = context; - dock_add_hotplug_device(dock_station, dd); - return 0; + list_for_each_entry(dock_station, &dock_stations, sibiling) { + dd = find_dock_dependent_device(dock_station, handle); + if (dd) { + dd->handler = handler; + dd->context = context; + dock_add_hotplug_device(dock_station, dd); + return 0; + } } return -EINVAL; @@ -563,13 +631,16 @@ EXPORT_SYMBOL_GPL(register_hotplug_dock_device); void unregister_hotplug_dock_device(acpi_handle handle) { struct dock_dependent_device *dd; + struct dock_station *dock_station; - if (!dock_station) + if (!dock_station_count) return; - dd = find_dock_dependent_device(dock_station, handle); - if (dd) - dock_del_hotplug_device(dock_station, dd); + list_for_each_entry(dock_station, &dock_stations, sibiling) { + dd = find_dock_dependent_device(dock_station, handle); + if (dd) + dock_del_hotplug_device(dock_station, dd); + } } EXPORT_SYMBOL_GPL(unregister_hotplug_dock_device); @@ -620,9 +691,28 @@ static void dock_notify(acpi_handle handle, u32 event, void *data) { struct dock_station *ds = data; struct acpi_device *tmp; + int surprise_removal = 0; + /* + * According to acpi spec 3.0a, if a DEVICE_CHECK notification + * is sent and _DCK is present, it is assumed to mean an undock + * request. + */ + if ((ds->flags & DOCK_IS_DOCK) && event == ACPI_NOTIFY_DEVICE_CHECK) + event = ACPI_NOTIFY_EJECT_REQUEST; + + /* + * dock station: BUS_CHECK - docked or surprise removal + * DEVICE_CHECK - undocked + * other device: BUS_CHECK/DEVICE_CHECK - added or surprise removal + * + * To simplify event handling, dock dependent device handler always + * get ACPI_NOTIFY_BUS_CHECK/ACPI_NOTIFY_DEVICE_CHECK for add and + * ACPI_NOTIFY_EJECT_REQUEST for removal + */ switch (event) { case ACPI_NOTIFY_BUS_CHECK: + case ACPI_NOTIFY_DEVICE_CHECK: if (!dock_in_progress(ds) && acpi_bus_get_device(ds->handle, &tmp)) { begin_dock(ds); @@ -638,20 +728,17 @@ static void dock_notify(acpi_handle handle, u32 event, void *data) complete_dock(ds); dock_event(ds, event, DOCK_EVENT); dock_lock(ds, 1); + break; } - break; - case ACPI_NOTIFY_DEVICE_CHECK: - /* - * According to acpi spec 3.0a, if a DEVICE_CHECK notification - * is sent and _DCK is present, it is assumed to mean an - * undock request. This notify routine will only be called - * for objects defining _DCK, so we will fall through to eject - * request here. However, we will pass an eject request through - * to the driver who wish to hotplug. - */ + if (dock_present(ds) || dock_in_progress(ds)) + break; + /* This is a surprise removal */ + surprise_removal = 1; + event = ACPI_NOTIFY_EJECT_REQUEST; + /* Fall back */ case ACPI_NOTIFY_EJECT_REQUEST: begin_undock(ds); - if (immediate_undock) + if (immediate_undock || surprise_removal) handle_eject_request(ds, event); else dock_event(ds, event, UNDOCK_EVENT); @@ -707,6 +794,8 @@ fdd_out: static ssize_t show_docked(struct device *dev, struct device_attribute *attr, char *buf) { + struct dock_station *dock_station = *((struct dock_station **) + dev->platform_data); return snprintf(buf, PAGE_SIZE, "%d\n", dock_present(dock_station)); } @@ -718,6 +807,8 @@ static DEVICE_ATTR(docked, S_IRUGO, show_docked, NULL); static ssize_t show_flags(struct device *dev, struct device_attribute *attr, char *buf) { + struct dock_station *dock_station = *((struct dock_station **) + dev->platform_data); return snprintf(buf, PAGE_SIZE, "%d\n", dock_station->flags); } @@ -730,6 +821,8 @@ static ssize_t write_undock(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; + struct dock_station *dock_station = *((struct dock_station **) + dev->platform_data); if (!count) return -EINVAL; @@ -747,6 +840,8 @@ static ssize_t show_dock_uid(struct device *dev, struct device_attribute *attr, char *buf) { unsigned long lbuf; + struct dock_station *dock_station = *((struct dock_station **) + dev->platform_data); acpi_status status = acpi_evaluate_integer(dock_station->handle, "_UID", NULL, &lbuf); if (ACPI_FAILURE(status)) @@ -768,6 +863,8 @@ static int dock_add(acpi_handle handle) int ret; acpi_status status; struct dock_dependent_device *dd; + struct dock_station *dock_station; + struct platform_device *dock_device; /* allocate & initialize the dock_station private data */ dock_station = kzalloc(sizeof(*dock_station), GFP_KERNEL); @@ -777,22 +874,34 @@ static int dock_add(acpi_handle handle) dock_station->last_dock_time = jiffies - HZ; INIT_LIST_HEAD(&dock_station->dependent_devices); INIT_LIST_HEAD(&dock_station->hotplug_devices); + INIT_LIST_HEAD(&dock_station->sibiling); spin_lock_init(&dock_station->dd_lock); mutex_init(&dock_station->hp_lock); ATOMIC_INIT_NOTIFIER_HEAD(&dock_notifier_list); /* initialize platform device stuff */ - dock_device = - platform_device_register_simple(dock_device_name, 0, NULL, 0); + dock_station->dock_device = + platform_device_register_simple(dock_device_name, + dock_station_count, NULL, 0); + dock_device = dock_station->dock_device; if (IS_ERR(dock_device)) { kfree(dock_station); dock_station = NULL; return PTR_ERR(dock_device); } + platform_device_add_data(dock_device, &dock_station, + sizeof(struct dock_station *)); /* we want the dock device to send uevents */ dock_device->dev.uevent_suppress = 0; + if (is_dock(handle)) + dock_station->flags |= DOCK_IS_DOCK; + if (is_ata(handle)) + dock_station->flags |= DOCK_IS_ATA; + if (is_battery(handle)) + dock_station->flags |= DOCK_IS_BAT; + ret = device_create_file(&dock_device->dev, &dev_attr_docked); if (ret) { printk("Error %d adding sysfs file\n", ret); @@ -858,8 +967,8 @@ static int dock_add(acpi_handle handle) goto dock_add_err; } - printk(KERN_INFO PREFIX "%s\n", ACPI_DOCK_DRIVER_DESCRIPTION); - + dock_station_count++; + list_add(&dock_station->sibiling, &dock_stations); return 0; dock_add_err: @@ -878,12 +987,13 @@ dock_add_err_unregister: /** * dock_remove - free up resources related to the dock station */ -static int dock_remove(void) +static int dock_remove(struct dock_station *dock_station) { struct dock_dependent_device *dd, *tmp; acpi_status status; + struct platform_device *dock_device = dock_station->dock_device; - if (!dock_station) + if (!dock_station_count) return 0; /* remove dependent devices */ @@ -923,41 +1033,58 @@ static int dock_remove(void) static acpi_status find_dock(acpi_handle handle, u32 lvl, void *context, void **rv) { - int *count = context; acpi_status status = AE_OK; if (is_dock(handle)) { if (dock_add(handle) >= 0) { - (*count)++; status = AE_CTRL_TERMINATE; } } return status; } -static int __init dock_init(void) +static acpi_status +find_bay(acpi_handle handle, u32 lvl, void *context, void **rv) { - int num = 0; - - dock_station = NULL; + /* If bay is in a dock, it's already handled */ + if (is_ejectable_bay(handle) && !is_dock_device(handle)) + dock_add(handle); + return AE_OK; +} +static int __init dock_init(void) +{ if (acpi_disabled) return 0; /* look for a dock station */ acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, find_dock, &num, NULL); + ACPI_UINT32_MAX, find_dock, NULL, NULL); - if (!num) - printk(KERN_INFO "No dock devices found.\n"); + /* look for bay */ + acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, find_bay, NULL, NULL); + if (!dock_station_count) { + printk(KERN_INFO PREFIX "No dock devices found.\n"); + return 0; + } + printk(KERN_INFO PREFIX "%s: %d docks/bays found\n", + ACPI_DOCK_DRIVER_DESCRIPTION, dock_station_count); return 0; } static void __exit dock_exit(void) { - dock_remove(); + struct dock_station *dock_station; + + list_for_each_entry(dock_station, &dock_stations, sibiling) + dock_remove(dock_station); } -postcore_initcall(dock_init); +/* + * Must be called before drivers of devices in dock, otherwise we can't know + * which devices are in a dock + */ +subsys_initcall(dock_init); module_exit(dock_exit); -- cgit v1.2.3 From 6bd00a61ab63d4ceb635ae0316353c11c900b8d8 Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:04:29 +0800 Subject: ACPI: introduce notifier change to avoid duplicates The battery driver already registers notification handler. To avoid registering notification handler again, introduce a notifier chain in global system notifier handler and use it in dock driver. Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/bus.c | 15 +++++++++++++++ drivers/acpi/dock.c | 46 ++++++++++++++++++++++++---------------------- include/acpi/acpi_bus.h | 3 +++ 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index ccae305ee55..0dc44945725 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -496,6 +496,19 @@ static int acpi_bus_check_scope(struct acpi_device *device) return 0; } +static BLOCKING_NOTIFIER_HEAD(acpi_bus_notify_list); +int register_acpi_bus_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&acpi_bus_notify_list, nb); +} +EXPORT_SYMBOL_GPL(register_acpi_bus_notifier); + +void unregister_acpi_bus_notifier(struct notifier_block *nb) +{ + blocking_notifier_chain_unregister(&acpi_bus_notify_list, nb); +} +EXPORT_SYMBOL_GPL(unregister_acpi_bus_notifier); + /** * acpi_bus_notify * --------------- @@ -506,6 +519,8 @@ static void acpi_bus_notify(acpi_handle handle, u32 type, void *data) int result = 0; struct acpi_device *device = NULL; + blocking_notifier_call_chain(&acpi_bus_notify_list, + type, (void *)handle); if (acpi_bus_get_device(handle, &device)) return; diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 799a0fdbb62..2563bc62987 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -748,6 +748,28 @@ static void dock_notify(acpi_handle handle, u32 event, void *data) } } +static int acpi_dock_notifier_call(struct notifier_block *this, + unsigned long event, void *data) +{ + struct dock_station *dock_station; + acpi_handle handle = (acpi_handle)data; + + if (event != ACPI_NOTIFY_BUS_CHECK && event != ACPI_NOTIFY_DEVICE_CHECK + && event != ACPI_NOTIFY_EJECT_REQUEST) + return 0; + list_for_each_entry(dock_station, &dock_stations, sibiling) { + if (dock_station->handle == handle) { + dock_notify(handle, event, dock_station); + return 0 ; + } + } + return 0; +} + +static struct notifier_block dock_acpi_notifier = { + .notifier_call = acpi_dock_notifier_call, +}; + /** * find_dock_devices - find devices on the dock station * @handle: the handle of the device we are examining @@ -861,7 +883,6 @@ static DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); static int dock_add(acpi_handle handle) { int ret; - acpi_status status; struct dock_dependent_device *dd; struct dock_station *dock_station; struct platform_device *dock_device; @@ -956,23 +977,10 @@ static int dock_add(acpi_handle handle) } add_dock_dependent_device(dock_station, dd); - /* register for dock events */ - status = acpi_install_notify_handler(dock_station->handle, - ACPI_SYSTEM_NOTIFY, - dock_notify, dock_station); - - if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX "Error installing notify handler\n"); - ret = -ENODEV; - goto dock_add_err; - } - dock_station_count++; list_add(&dock_station->sibiling, &dock_stations); return 0; -dock_add_err: - kfree(dd); dock_add_err_unregister: device_remove_file(&dock_device->dev, &dev_attr_docked); device_remove_file(&dock_device->dev, &dev_attr_undock); @@ -990,7 +998,6 @@ dock_add_err_unregister: static int dock_remove(struct dock_station *dock_station) { struct dock_dependent_device *dd, *tmp; - acpi_status status; struct platform_device *dock_device = dock_station->dock_device; if (!dock_station_count) @@ -1001,13 +1008,6 @@ static int dock_remove(struct dock_station *dock_station) list) kfree(dd); - /* remove dock notify handler */ - status = acpi_remove_notify_handler(dock_station->handle, - ACPI_SYSTEM_NOTIFY, - dock_notify); - if (ACPI_FAILURE(status)) - printk(KERN_ERR "Error removing notify handler\n"); - /* cleanup sysfs */ device_remove_file(&dock_device->dev, &dev_attr_docked); device_remove_file(&dock_device->dev, &dev_attr_undock); @@ -1069,6 +1069,7 @@ static int __init dock_init(void) return 0; } + register_acpi_bus_notifier(&dock_acpi_notifier); printk(KERN_INFO PREFIX "%s: %d docks/bays found\n", ACPI_DOCK_DRIVER_DESCRIPTION, dock_station_count); return 0; @@ -1078,6 +1079,7 @@ static void __exit dock_exit(void) { struct dock_station *dock_station; + unregister_acpi_bus_notifier(&dock_acpi_notifier); list_for_each_entry(dock_station, &dock_stations, sibiling) dock_remove(dock_station); } diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a5ac0bc7f52..f74f882609f 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -327,6 +327,9 @@ int acpi_bus_get_private_data(acpi_handle, void **); extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32); extern int register_acpi_notifier(struct notifier_block *); extern int unregister_acpi_notifier(struct notifier_block *); + +extern int register_acpi_bus_notifier(struct notifier_block *nb); +extern void unregister_acpi_bus_notifier(struct notifier_block *nb); /* * External Functions */ -- cgit v1.2.3 From 19cd847ab24fefe9e50101ec94479e0400a08650 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Thu, 28 Aug 2008 10:05:06 +0800 Subject: ACPI: fix hotplug race The hotplug notification handler and drivers' notification handler all run in one workqueue. Before hotplug removes an acpi device, the device driver's notification handler is already be recorded to run just after global notification handler. After hotplug notification handler runs, acpica will notice a NULL notification handler and crash. So now we run run hotplug in another workqueue and wait for all acpi notication handlers finish. This was found in battery hotplug, but actually all hotplug can be affected. Signed-off-by: Zhang Rui Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/dock.c | 25 ++++++++++++++++++++++++- drivers/acpi/osl.c | 46 +++++++++++++++++++++++++++++++++++++++++----- include/acpi/acpiosxf.h | 3 +++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 2563bc62987..4b395b1e61b 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -748,6 +748,20 @@ static void dock_notify(acpi_handle handle, u32 event, void *data) } } +struct dock_data { + acpi_handle handle; + unsigned long event; + struct dock_station *ds; +}; + +static void acpi_dock_deferred_cb(void *context) +{ + struct dock_data *data = (struct dock_data *)context; + + dock_notify(data->handle, data->event, data->ds); + kfree(data); +} + static int acpi_dock_notifier_call(struct notifier_block *this, unsigned long event, void *data) { @@ -759,7 +773,16 @@ static int acpi_dock_notifier_call(struct notifier_block *this, return 0; list_for_each_entry(dock_station, &dock_stations, sibiling) { if (dock_station->handle == handle) { - dock_notify(handle, event, dock_station); + struct dock_data *dock_data; + + dock_data = kmalloc(sizeof(*dock_data), GFP_KERNEL); + if (!dock_data) + return 0; + dock_data->handle = handle; + dock_data->event = event; + dock_data->ds = dock_station; + acpi_os_hotplug_execute(acpi_dock_deferred_cb, + dock_data); return 0 ; } } diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 235a1386888..750e0df1560 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -682,6 +682,22 @@ static void acpi_os_execute_deferred(struct work_struct *work) return; } +static void acpi_os_execute_hp_deferred(struct work_struct *work) +{ + struct acpi_os_dpc *dpc = container_of(work, struct acpi_os_dpc, work); + if (!dpc) { + printk(KERN_ERR PREFIX "Invalid (NULL) context\n"); + return; + } + + acpi_os_wait_events_complete(NULL); + + dpc->function(dpc->context); + kfree(dpc); + + return; +} + /******************************************************************************* * * FUNCTION: acpi_os_execute @@ -697,12 +713,13 @@ static void acpi_os_execute_deferred(struct work_struct *work) * ******************************************************************************/ -acpi_status acpi_os_execute(acpi_execute_type type, - acpi_osd_exec_callback function, void *context) +static acpi_status __acpi_os_execute(acpi_execute_type type, + acpi_osd_exec_callback function, void *context, int hp) { acpi_status status = AE_OK; struct acpi_os_dpc *dpc; struct workqueue_struct *queue; + int ret; ACPI_DEBUG_PRINT((ACPI_DB_EXEC, "Scheduling function [%p(%p)] for deferred execution.\n", function, context)); @@ -726,9 +743,17 @@ acpi_status acpi_os_execute(acpi_execute_type type, dpc->function = function; dpc->context = context; - INIT_WORK(&dpc->work, acpi_os_execute_deferred); - queue = (type == OSL_NOTIFY_HANDLER) ? kacpi_notify_wq : kacpid_wq; - if (!queue_work(queue, &dpc->work)) { + if (!hp) { + INIT_WORK(&dpc->work, acpi_os_execute_deferred); + queue = (type == OSL_NOTIFY_HANDLER) ? + kacpi_notify_wq : kacpid_wq; + ret = queue_work(queue, &dpc->work); + } else { + INIT_WORK(&dpc->work, acpi_os_execute_hp_deferred); + ret = schedule_work(&dpc->work); + } + + if (!ret) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Call to queue_work() failed.\n")); status = AE_ERROR; @@ -737,8 +762,19 @@ acpi_status acpi_os_execute(acpi_execute_type type, return_ACPI_STATUS(status); } +acpi_status acpi_os_execute(acpi_execute_type type, + acpi_osd_exec_callback function, void *context) +{ + return __acpi_os_execute(type, function, context, 0); +} EXPORT_SYMBOL(acpi_os_execute); +acpi_status acpi_os_hotplug_execute(acpi_osd_exec_callback function, + void *context) +{ + return __acpi_os_execute(0, function, context, 1); +} + void acpi_os_wait_events_complete(void *context) { flush_workqueue(kacpid_wq); diff --git a/include/acpi/acpiosxf.h b/include/acpi/acpiosxf.h index 3f93a6b4e17..b91440ac0d1 100644 --- a/include/acpi/acpiosxf.h +++ b/include/acpi/acpiosxf.h @@ -193,6 +193,9 @@ acpi_status acpi_os_execute(acpi_execute_type type, acpi_osd_exec_callback function, void *context); +acpi_status +acpi_os_hotplug_execute(acpi_osd_exec_callback function, void *context); + void acpi_os_wait_events_complete(void *context); void acpi_os_sleep(acpi_integer milliseconds); -- cgit v1.2.3 From f730ae1838635a02aa60834762c61566911d004c Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:05:45 +0800 Subject: libata: remove functions now handed by ACPI dock driver dock driver can handle ata(bay) hotplug now. dock driver already handles _EJ0 and _STA, so remove them. Also libata doesn't need register notification handler anymore. Signed-off-by: Shaohua Li Acked-by: Tejun Heo Signed-off-by: Len Brown --- drivers/acpi/dock.c | 3 +- drivers/ata/libata-acpi.c | 107 ++++++---------------------------------------- 2 files changed, 16 insertions(+), 94 deletions(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 4b395b1e61b..f19f643fb36 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -738,7 +738,8 @@ static void dock_notify(acpi_handle handle, u32 event, void *data) /* Fall back */ case ACPI_NOTIFY_EJECT_REQUEST: begin_undock(ds); - if (immediate_undock || surprise_removal) + if ((immediate_undock && !(ds->flags & DOCK_IS_ATA)) + || surprise_removal) handle_eject_request(ds, event); else dock_event(ds, event, UNDOCK_EVENT); diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 9330b7922f6..97727be7e15 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -120,21 +120,6 @@ static void ata_acpi_associate_ide_port(struct ata_port *ap) ap->pflags |= ATA_PFLAG_INIT_GTM_VALID; } -static void ata_acpi_eject_device(acpi_handle handle) -{ - struct acpi_object_list arg_list; - union acpi_object arg; - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = 1; - - if (ACPI_FAILURE(acpi_evaluate_object(handle, "_EJ0", - &arg_list, NULL))) - printk(KERN_ERR "Failed to evaluate _EJ0!\n"); -} - /* @ap and @dev are the same as ata_acpi_handle_hotplug() */ static void ata_acpi_detach_device(struct ata_port *ap, struct ata_device *dev) { @@ -157,7 +142,6 @@ static void ata_acpi_detach_device(struct ata_port *ap, struct ata_device *dev) * @ap: ATA port ACPI event occurred * @dev: ATA device ACPI event occurred (can be NULL) * @event: ACPI event which occurred - * @is_dock_event: boolean indicating whether the event was a dock one * * All ACPI bay / device realted events end up in this function. If * the event is port-wide @dev is NULL. If the event is specific to a @@ -171,115 +155,58 @@ static void ata_acpi_detach_device(struct ata_port *ap, struct ata_device *dev) * ACPI notify handler context. May sleep. */ static void ata_acpi_handle_hotplug(struct ata_port *ap, struct ata_device *dev, - u32 event, int is_dock_event) + u32 event) { - char event_string[12]; - char *envp[] = { event_string, NULL }; struct ata_eh_info *ehi = &ap->link.eh_info; - struct kobject *kobj = NULL; int wait = 0; unsigned long flags; - acpi_handle handle, tmphandle; - unsigned long sta; - acpi_status status; + acpi_handle handle; - if (dev) { - if (dev->sdev) - kobj = &dev->sdev->sdev_gendev.kobj; + if (dev) handle = dev->acpi_handle; - } else { - kobj = &ap->dev->kobj; + else handle = ap->acpi_handle; - } - - status = acpi_get_handle(handle, "_EJ0", &tmphandle); - if (ACPI_FAILURE(status)) - /* This device does not support hotplug */ - return; - - if (event == ACPI_NOTIFY_BUS_CHECK || - event == ACPI_NOTIFY_DEVICE_CHECK) - status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); spin_lock_irqsave(ap->lock, flags); - + /* + * When dock driver calls into the routine, it will always use + * ACPI_NOTIFY_BUS_CHECK/ACPI_NOTIFY_DEVICE_CHECK for add and + * ACPI_NOTIFY_EJECT_REQUEST for remove + */ switch (event) { case ACPI_NOTIFY_BUS_CHECK: case ACPI_NOTIFY_DEVICE_CHECK: ata_ehi_push_desc(ehi, "ACPI event"); - if (ACPI_FAILURE(status)) { - ata_port_printk(ap, KERN_ERR, - "acpi: failed to determine bay status (0x%x)\n", - status); - break; - } - - if (sta) { - ata_ehi_hotplugged(ehi); - ata_port_freeze(ap); - } else { - /* The device has gone - unplug it */ - ata_acpi_detach_device(ap, dev); - wait = 1; - } + ata_ehi_hotplugged(ehi); + ata_port_freeze(ap); break; case ACPI_NOTIFY_EJECT_REQUEST: ata_ehi_push_desc(ehi, "ACPI event"); - if (!is_dock_event) - break; - - /* undock event - immediate unplug */ ata_acpi_detach_device(ap, dev); wait = 1; break; } - /* make sure kobj doesn't go away while ap->lock is released */ - kobject_get(kobj); - spin_unlock_irqrestore(ap->lock, flags); - if (wait) { + if (wait) ata_port_wait_eh(ap); - ata_acpi_eject_device(handle); - } - - if (kobj && !is_dock_event) { - sprintf(event_string, "BAY_EVENT=%d", event); - kobject_uevent_env(kobj, KOBJ_CHANGE, envp); - } - - kobject_put(kobj); } static void ata_acpi_dev_notify_dock(acpi_handle handle, u32 event, void *data) { struct ata_device *dev = data; - ata_acpi_handle_hotplug(dev->link->ap, dev, event, 1); + ata_acpi_handle_hotplug(dev->link->ap, dev, event); } static void ata_acpi_ap_notify_dock(acpi_handle handle, u32 event, void *data) { struct ata_port *ap = data; - ata_acpi_handle_hotplug(ap, NULL, event, 1); -} - -static void ata_acpi_dev_notify(acpi_handle handle, u32 event, void *data) -{ - struct ata_device *dev = data; - - ata_acpi_handle_hotplug(dev->link->ap, dev, event, 0); -} - -static void ata_acpi_ap_notify(acpi_handle handle, u32 event, void *data) -{ - struct ata_port *ap = data; - - ata_acpi_handle_hotplug(ap, NULL, event, 0); + ata_acpi_handle_hotplug(ap, NULL, event); } /** @@ -315,9 +242,6 @@ void ata_acpi_associate(struct ata_host *host) ata_acpi_associate_ide_port(ap); if (ap->acpi_handle) { - acpi_install_notify_handler(ap->acpi_handle, - ACPI_SYSTEM_NOTIFY, - ata_acpi_ap_notify, ap); /* we might be on a docking station */ register_hotplug_dock_device(ap->acpi_handle, ata_acpi_ap_notify_dock, ap); @@ -327,9 +251,6 @@ void ata_acpi_associate(struct ata_host *host) struct ata_device *dev = &ap->link.device[j]; if (dev->acpi_handle) { - acpi_install_notify_handler(dev->acpi_handle, - ACPI_SYSTEM_NOTIFY, - ata_acpi_dev_notify, dev); /* we might be on a docking station */ register_hotplug_dock_device(dev->acpi_handle, ata_acpi_dev_notify_dock, dev); -- cgit v1.2.3 From 1253f7aabfebc51446dbec5c8895c5c8846dfe06 Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:06:16 +0800 Subject: dock: introduce .uevent for devices in dock, eg libata dock's uevent reported itself, not ata. It might be difficult to find an ata device just according to a dock. This patch introduces docking ops for each device in a dock. when docking, dock driver can send device specific uevent. This should help dock station too (not just bay) Signed-off-by: Shaohua Li Acked-by: Tejun Heo Signed-off-by: Len Brown --- drivers/acpi/dock.c | 22 +++++++++++++------ drivers/ata/libata-acpi.c | 44 ++++++++++++++++++++++++++++++++++++-- drivers/pci/hotplug/acpiphp_glue.c | 6 ++++-- include/acpi/acpi_drivers.h | 9 ++++++-- 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index f19f643fb36..ac7dfefcb50 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -75,7 +75,7 @@ struct dock_dependent_device { struct list_head list; struct list_head hotplug_list; acpi_handle handle; - acpi_notify_handler handler; + struct acpi_dock_ops *ops; void *context; }; @@ -385,8 +385,8 @@ static void hotplug_dock_devices(struct dock_station *ds, u32 event) * First call driver specific hotplug functions */ list_for_each_entry(dd, &ds->hotplug_devices, hotplug_list) { - if (dd->handler) - dd->handler(dd->handle, event, dd->context); + if (dd->ops && dd->ops->handler) + dd->ops->handler(dd->handle, event, dd->context); } /* @@ -409,6 +409,7 @@ static void dock_event(struct dock_station *ds, u32 event, int num) struct device *dev = &ds->dock_device->dev; char event_string[13]; char *envp[] = { event_string, NULL }; + struct dock_dependent_device *dd; if (num == UNDOCK_EVENT) sprintf(event_string, "EVENT=undock"); @@ -419,7 +420,14 @@ static void dock_event(struct dock_station *ds, u32 event, int num) * Indicate that the status of the dock station has * changed. */ - kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + if (num == DOCK_EVENT) + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + + list_for_each_entry(dd, &ds->hotplug_devices, hotplug_list) + if (dd->ops && dd->ops->uevent) + dd->ops->uevent(dd->handle, event, dd->context); + if (num != DOCK_EVENT) + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); } /** @@ -588,7 +596,7 @@ EXPORT_SYMBOL_GPL(unregister_dock_notifier); /** * register_hotplug_dock_device - register a hotplug function * @handle: the handle of the device - * @handler: the acpi_notifier_handler to call after docking + * @ops: handlers to call after docking * @context: device specific data * * If a driver would like to perform a hotplug operation after a dock @@ -596,7 +604,7 @@ EXPORT_SYMBOL_GPL(unregister_dock_notifier); * the dock driver after _DCK is executed. */ int -register_hotplug_dock_device(acpi_handle handle, acpi_notify_handler handler, +register_hotplug_dock_device(acpi_handle handle, struct acpi_dock_ops *ops, void *context) { struct dock_dependent_device *dd; @@ -612,7 +620,7 @@ register_hotplug_dock_device(acpi_handle handle, acpi_notify_handler handler, list_for_each_entry(dock_station, &dock_stations, sibiling) { dd = find_dock_dependent_device(dock_station, handle); if (dd) { - dd->handler = handler; + dd->ops = ops; dd->context = context; dock_add_hotplug_device(dock_station, dd); return 0; diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 97727be7e15..c012307d0ba 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -209,6 +209,46 @@ static void ata_acpi_ap_notify_dock(acpi_handle handle, u32 event, void *data) ata_acpi_handle_hotplug(ap, NULL, event); } +static void ata_acpi_uevent(struct ata_port *ap, struct ata_device *dev, + u32 event) +{ + struct kobject *kobj = NULL; + char event_string[20]; + char *envp[] = { event_string, NULL }; + + if (dev) { + if (dev->sdev) + kobj = &dev->sdev->sdev_gendev.kobj; + } else + kobj = &ap->dev->kobj; + + if (kobj) { + snprintf(event_string, 20, "BAY_EVENT=%d", event); + kobject_uevent_env(kobj, KOBJ_CHANGE, envp); + } +} + +static void ata_acpi_ap_uevent(acpi_handle handle, u32 event, void *data) +{ + ata_acpi_uevent(data, NULL, event); +} + +static void ata_acpi_dev_uevent(acpi_handle handle, u32 event, void *data) +{ + struct ata_device *dev = data; + ata_acpi_uevent(dev->link->ap, dev, event); +} + +static struct acpi_dock_ops ata_acpi_dev_dock_ops = { + .handler = ata_acpi_dev_notify_dock, + .uevent = ata_acpi_dev_uevent, +}; + +static struct acpi_dock_ops ata_acpi_ap_dock_ops = { + .handler = ata_acpi_ap_notify_dock, + .uevent = ata_acpi_ap_uevent, +}; + /** * ata_acpi_associate - associate ATA host with ACPI objects * @host: target ATA host @@ -244,7 +284,7 @@ void ata_acpi_associate(struct ata_host *host) if (ap->acpi_handle) { /* we might be on a docking station */ register_hotplug_dock_device(ap->acpi_handle, - ata_acpi_ap_notify_dock, ap); + &ata_acpi_ap_dock_ops, ap); } for (j = 0; j < ata_link_max_devices(&ap->link); j++) { @@ -253,7 +293,7 @@ void ata_acpi_associate(struct ata_host *host) if (dev->acpi_handle) { /* we might be on a docking station */ register_hotplug_dock_device(dev->acpi_handle, - ata_acpi_dev_notify_dock, dev); + &ata_acpi_dev_dock_ops, dev); } } } diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index a3e4705dd8f..db54c5ef2aa 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -169,7 +169,9 @@ static int post_dock_fixups(struct notifier_block *nb, unsigned long val, } - +static struct acpi_dock_ops acpiphp_dock_ops = { + .handler = handle_hotplug_event_func, +}; /* callback routine to register each ACPI PCI slot object */ static acpi_status @@ -285,7 +287,7 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) */ newfunc->flags &= ~FUNC_HAS_EJ0; if (register_hotplug_dock_device(handle, - handle_hotplug_event_func, newfunc)) + &acpiphp_dock_ops, newfunc)) dbg("failed to register dock device\n"); /* we need to be notified when dock events happen diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index e5f38e5ce86..4f5042a0ef8 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -115,12 +115,17 @@ int acpi_processor_set_thermal_limit(acpi_handle handle, int type); /*-------------------------------------------------------------------------- Dock Station -------------------------------------------------------------------------- */ +struct acpi_dock_ops { + acpi_notify_handler handler; + acpi_notify_handler uevent; +}; + #if defined(CONFIG_ACPI_DOCK) || defined(CONFIG_ACPI_DOCK_MODULE) extern int is_dock_device(acpi_handle handle); extern int register_dock_notifier(struct notifier_block *nb); extern void unregister_dock_notifier(struct notifier_block *nb); extern int register_hotplug_dock_device(acpi_handle handle, - acpi_notify_handler handler, + struct acpi_dock_ops *ops, void *context); extern void unregister_hotplug_dock_device(acpi_handle handle); #else @@ -136,7 +141,7 @@ static inline void unregister_dock_notifier(struct notifier_block *nb) { } static inline int register_hotplug_dock_device(acpi_handle handle, - acpi_notify_handler handler, + struct acpi_dock_ops *ops, void *context) { return -ENODEV; -- cgit v1.2.3 From 4be9309d15e88e4a1e4a78deb52eb88c7da38c99 Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:06:44 +0800 Subject: bay: remove driver, all functions now handled by dock driver Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/Kconfig | 11 +- drivers/acpi/Makefile | 1 - drivers/acpi/bay.c | 411 -------------------------------------------------- 3 files changed, 2 insertions(+), 421 deletions(-) delete mode 100644 drivers/acpi/bay.c diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 735f5ea1747..3919d6dbe06 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -160,15 +160,8 @@ config ACPI_DOCK tristate "Dock" depends on EXPERIMENTAL help - This driver adds support for ACPI controlled docking stations - -config ACPI_BAY - tristate "Removable Drive Bay (EXPERIMENTAL)" - depends on EXPERIMENTAL - depends on ACPI_DOCK - help - This driver adds support for ACPI controlled removable drive - bays such as the IBM ultrabay or the Dell Module Bay. + This driver adds support for ACPI controlled docking stations and removable + drive bays such as the IBM ultrabay or the Dell Module Bay. config ACPI_PROCESSOR tristate "Processor" diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 52a4cd4b81d..ad4bfd558ff 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -45,7 +45,6 @@ obj-$(CONFIG_ACPI_BATTERY) += battery.o obj-$(CONFIG_ACPI_BUTTON) += button.o obj-$(CONFIG_ACPI_FAN) += fan.o obj-$(CONFIG_ACPI_DOCK) += dock.o -obj-$(CONFIG_ACPI_BAY) += bay.o obj-$(CONFIG_ACPI_VIDEO) += video.o obj-y += pci_root.o pci_link.o pci_irq.o pci_bind.o obj-$(CONFIG_ACPI_PCI_SLOT) += pci_slot.o diff --git a/drivers/acpi/bay.c b/drivers/acpi/bay.c deleted file mode 100644 index 61b6c5beb2d..00000000000 --- a/drivers/acpi/bay.c +++ /dev/null @@ -1,411 +0,0 @@ -/* - * bay.c - ACPI removable drive bay driver - * - * Copyright (C) 2006 Kristen Carlson Accardi - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -ACPI_MODULE_NAME("bay"); -MODULE_AUTHOR("Kristen Carlson Accardi"); -MODULE_DESCRIPTION("ACPI Removable Drive Bay Driver"); -MODULE_LICENSE("GPL"); -#define ACPI_BAY_CLASS "bay" -#define ACPI_BAY_COMPONENT 0x10000000 -#define _COMPONENT ACPI_BAY_COMPONENT -#define bay_dprintk(h,s) {\ - char prefix[80] = {'\0'};\ - struct acpi_buffer buffer = {sizeof(prefix), prefix};\ - acpi_get_name(h, ACPI_FULL_PATHNAME, &buffer);\ - printk(KERN_DEBUG PREFIX "%s: %s\n", prefix, s); } -static void bay_notify(acpi_handle handle, u32 event, void *data); - -static const struct acpi_device_id bay_device_ids[] = { - {"LNXIOBAY", 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, bay_device_ids); - -struct bay { - acpi_handle handle; - char *name; - struct list_head list; - struct platform_device *pdev; -}; - -static LIST_HEAD(drive_bays); - - -/***************************************************************************** - * Drive Bay functions * - *****************************************************************************/ -/** - * is_ejectable - see if a device is ejectable - * @handle: acpi handle of the device - * - * If an acpi object has a _EJ0 method, then it is ejectable - */ -static int is_ejectable(acpi_handle handle) -{ - acpi_status status; - acpi_handle tmp; - - status = acpi_get_handle(handle, "_EJ0", &tmp); - if (ACPI_FAILURE(status)) - return 0; - return 1; -} - -/** - * bay_present - see if the bay device is present - * @bay: the drive bay - * - * execute the _STA method. - */ -static int bay_present(struct bay *bay) -{ - unsigned long sta; - acpi_status status; - - if (bay) { - status = acpi_evaluate_integer(bay->handle, "_STA", NULL, &sta); - if (ACPI_SUCCESS(status) && sta) - return 1; - } - return 0; -} - -/** - * eject_device - respond to an eject request - * @handle - the device to eject - * - * Call this devices _EJ0 method. - */ -static void eject_device(acpi_handle handle) -{ - struct acpi_object_list arg_list; - union acpi_object arg; - - bay_dprintk(handle, "Ejecting device"); - - arg_list.count = 1; - arg_list.pointer = &arg; - arg.type = ACPI_TYPE_INTEGER; - arg.integer.value = 1; - - if (ACPI_FAILURE(acpi_evaluate_object(handle, "_EJ0", - &arg_list, NULL))) - pr_debug("Failed to evaluate _EJ0!\n"); -} - -/* - * show_present - read method for "present" file in sysfs - */ -static ssize_t show_present(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct bay *bay = dev_get_drvdata(dev); - return snprintf(buf, PAGE_SIZE, "%d\n", bay_present(bay)); - -} -static DEVICE_ATTR(present, S_IRUGO, show_present, NULL); - -/* - * write_eject - write method for "eject" file in sysfs - */ -static ssize_t write_eject(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct bay *bay = dev_get_drvdata(dev); - - if (!count) - return -EINVAL; - - eject_device(bay->handle); - return count; -} -static DEVICE_ATTR(eject, S_IWUSR, NULL, write_eject); - -/** - * is_ata - see if a device is an ata device - * @handle: acpi handle of the device - * - * If an acpi object has one of 4 ATA ACPI methods defined, - * then it is an ATA device - */ -static int is_ata(acpi_handle handle) -{ - acpi_handle tmp; - - if ((ACPI_SUCCESS(acpi_get_handle(handle, "_GTF", &tmp))) || - (ACPI_SUCCESS(acpi_get_handle(handle, "_GTM", &tmp))) || - (ACPI_SUCCESS(acpi_get_handle(handle, "_STM", &tmp))) || - (ACPI_SUCCESS(acpi_get_handle(handle, "_SDD", &tmp)))) - return 1; - - return 0; -} - -/** - * parent_is_ata(acpi_handle handle) - * - */ -static int parent_is_ata(acpi_handle handle) -{ - acpi_handle phandle; - - if (acpi_get_parent(handle, &phandle)) - return 0; - - return is_ata(phandle); -} - -/** - * is_ejectable_bay - see if a device is an ejectable drive bay - * @handle: acpi handle of the device - * - * If an acpi object is ejectable and has one of the ACPI ATA - * methods defined, then we can safely call it an ejectable - * drive bay - */ -static int is_ejectable_bay(acpi_handle handle) -{ - if ((is_ata(handle) || parent_is_ata(handle)) && is_ejectable(handle)) - return 1; - return 0; -} - -#if 0 -/** - * eject_removable_drive - try to eject this drive - * @dev : the device structure of the drive - * - * If a device is a removable drive that requires an _EJ0 method - * to be executed in order to safely remove from the system, do - * it. ATM - always returns success - */ -int eject_removable_drive(struct device *dev) -{ - acpi_handle handle = DEVICE_ACPI_HANDLE(dev); - - if (handle) { - bay_dprintk(handle, "Got device handle"); - if (is_ejectable_bay(handle)) - eject_device(handle); - } else { - printk("No acpi handle for device\n"); - } - - /* should I return an error code? */ - return 0; -} -EXPORT_SYMBOL_GPL(eject_removable_drive); -#endif /* 0 */ - -static int acpi_bay_add_fs(struct bay *bay) -{ - int ret; - struct device *dev = &bay->pdev->dev; - - ret = device_create_file(dev, &dev_attr_present); - if (ret) - goto add_fs_err; - ret = device_create_file(dev, &dev_attr_eject); - if (ret) { - device_remove_file(dev, &dev_attr_present); - goto add_fs_err; - } - return 0; - - add_fs_err: - bay_dprintk(bay->handle, "Error adding sysfs files\n"); - return ret; -} - -static void acpi_bay_remove_fs(struct bay *bay) -{ - struct device *dev = &bay->pdev->dev; - - /* cleanup sysfs */ - device_remove_file(dev, &dev_attr_present); - device_remove_file(dev, &dev_attr_eject); -} - -static int bay_is_dock_device(acpi_handle handle) -{ - acpi_handle parent; - - acpi_get_parent(handle, &parent); - - /* if the device or it's parent is dependent on the - * dock, then we are a dock device - */ - return (is_dock_device(handle) || is_dock_device(parent)); -} - -static int bay_add(acpi_handle handle, int id) -{ - acpi_status status; - struct bay *new_bay; - struct platform_device *pdev; - struct acpi_buffer nbuffer = {ACPI_ALLOCATE_BUFFER, NULL}; - acpi_get_name(handle, ACPI_FULL_PATHNAME, &nbuffer); - - bay_dprintk(handle, "Adding notify handler"); - - /* - * Initialize bay device structure - */ - new_bay = kzalloc(sizeof(*new_bay), GFP_ATOMIC); - INIT_LIST_HEAD(&new_bay->list); - new_bay->handle = handle; - new_bay->name = (char *)nbuffer.pointer; - - /* initialize platform device stuff */ - pdev = platform_device_register_simple(ACPI_BAY_CLASS, id, NULL, 0); - if (IS_ERR(pdev)) { - printk(KERN_ERR PREFIX "Error registering bay device\n"); - goto bay_add_err; - } - new_bay->pdev = pdev; - platform_set_drvdata(pdev, new_bay); - - /* - * we want the bay driver to be able to send uevents - */ - pdev->dev.uevent_suppress = 0; - - /* register for events on this device */ - status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, - bay_notify, new_bay); - if (ACPI_FAILURE(status)) { - printk(KERN_INFO PREFIX "Error installing bay notify handler\n"); - platform_device_unregister(new_bay->pdev); - goto bay_add_err; - } - - if (acpi_bay_add_fs(new_bay)) { - acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, - bay_notify); - platform_device_unregister(new_bay->pdev); - goto bay_add_err; - } - - /* if we are on a dock station, we should register for dock - * notifications. - */ - if (bay_is_dock_device(handle)) { - bay_dprintk(handle, "Is dependent on dock\n"); - register_hotplug_dock_device(handle, bay_notify, new_bay); - } - list_add(&new_bay->list, &drive_bays); - printk(KERN_INFO PREFIX "Bay [%s] Added\n", new_bay->name); - return 0; - -bay_add_err: - kfree(new_bay->name); - kfree(new_bay); - return -ENODEV; -} - -/** - * bay_notify - act upon an acpi bay notification - * @handle: the bay handle - * @event: the acpi event - * @data: our driver data struct - * - */ -static void bay_notify(acpi_handle handle, u32 event, void *data) -{ - struct bay *bay_dev = (struct bay *)data; - struct device *dev = &bay_dev->pdev->dev; - char event_string[12]; - char *envp[] = { event_string, NULL }; - - bay_dprintk(handle, "Bay event"); - sprintf(event_string, "BAY_EVENT=%d", event); - kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); -} - -static acpi_status -find_bay(acpi_handle handle, u32 lvl, void *context, void **rv) -{ - int *count = (int *)context; - - /* - * there could be more than one ejectable bay. - * so, just return AE_OK always so that every object - * will be checked. - */ - if (is_ejectable_bay(handle)) { - bay_dprintk(handle, "found ejectable bay"); - if (!bay_add(handle, *count)) - (*count)++; - } - return AE_OK; -} - -static int __init bay_init(void) -{ - int bays = 0; - - INIT_LIST_HEAD(&drive_bays); - - if (acpi_disabled) - return -ENODEV; - - /* look for dockable drive bays */ - acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, find_bay, &bays, NULL); - - if (!bays) - return -ENODEV; - - return 0; -} - -static void __exit bay_exit(void) -{ - struct bay *bay, *tmp; - - list_for_each_entry_safe(bay, tmp, &drive_bays, list) { - if (is_dock_device(bay->handle)) - unregister_hotplug_dock_device(bay->handle); - acpi_bay_remove_fs(bay); - acpi_remove_notify_handler(bay->handle, ACPI_SYSTEM_NOTIFY, - bay_notify); - platform_device_unregister(bay->pdev); - kfree(bay->name); - kfree(bay); - } -} - -postcore_initcall(bay_init); -module_exit(bay_exit); - -- cgit v1.2.3 From 61b836958371c717d1e6d4fea1d2c512969ad20b Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:07:14 +0800 Subject: dock: fix for ATA bay in a dock station an ATA bay can be in a dock and itself can be ejected separately. This patch handles such eject bay. Found by Holger. Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/dock.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index ac7dfefcb50..c877cc52600 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -609,6 +609,7 @@ register_hotplug_dock_device(acpi_handle handle, struct acpi_dock_ops *ops, { struct dock_dependent_device *dd; struct dock_station *dock_station; + int ret = -EINVAL; if (!dock_station_count) return -ENODEV; @@ -618,16 +619,21 @@ register_hotplug_dock_device(acpi_handle handle, struct acpi_dock_ops *ops, * this would include the dock station itself */ list_for_each_entry(dock_station, &dock_stations, sibiling) { + /* + * An ATA bay can be in a dock and itself can be ejected + * seperately, so there are two 'dock stations' which need the + * ops + */ dd = find_dock_dependent_device(dock_station, handle); if (dd) { dd->ops = ops; dd->context = context; dock_add_hotplug_device(dock_station, dd); - return 0; + ret = 0; } } - return -EINVAL; + return ret; } EXPORT_SYMBOL_GPL(register_hotplug_dock_device); @@ -1078,8 +1084,8 @@ find_dock(acpi_handle handle, u32 lvl, void *context, void **rv) static acpi_status find_bay(acpi_handle handle, u32 lvl, void *context, void **rv) { - /* If bay is in a dock, it's already handled */ - if (is_ejectable_bay(handle) && !is_dock_device(handle)) + /* If bay is a dock, it's already handled */ + if (is_ejectable_bay(handle) && !is_dock(handle)) dock_add(handle); return AE_OK; } -- cgit v1.2.3 From 8652b00fd6416773f113dab3dfa0d4509def825b Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 28 Aug 2008 10:07:45 +0800 Subject: dock: add 'type' sysfs file add a sysfs file to present dock type. Suggested by Holger. Signed-off-by: Shaohua Li Signed-off-by: Len Brown --- drivers/acpi/dock.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index c877cc52600..b7d15818215 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -911,6 +911,26 @@ static ssize_t show_dock_uid(struct device *dev, } static DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); +static ssize_t show_dock_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dock_station *dock_station = *((struct dock_station **) + dev->platform_data); + char *type; + + if (dock_station->flags & DOCK_IS_DOCK) + type = "dock_station"; + else if (dock_station->flags & DOCK_IS_ATA) + type = "ata_bay"; + else if (dock_station->flags & DOCK_IS_BAT) + type = "battery_bay"; + else + type = "unknown"; + + return snprintf(buf, PAGE_SIZE, "%s\n", type); +} +static DEVICE_ATTR(type, S_IRUGO, show_dock_type, NULL); + /** * dock_add - add a new dock station * @handle: the dock station handle @@ -999,6 +1019,9 @@ static int dock_add(acpi_handle handle) dock_station = NULL; return ret; } + ret = device_create_file(&dock_device->dev, &dev_attr_type); + if (ret) + printk(KERN_ERR"Error %d adding sysfs file\n", ret); /* Find dependent devices */ acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, @@ -1020,6 +1043,7 @@ static int dock_add(acpi_handle handle) return 0; dock_add_err_unregister: + device_remove_file(&dock_device->dev, &dev_attr_type); device_remove_file(&dock_device->dev, &dev_attr_docked); device_remove_file(&dock_device->dev, &dev_attr_undock); device_remove_file(&dock_device->dev, &dev_attr_uid); @@ -1047,6 +1071,7 @@ static int dock_remove(struct dock_station *dock_station) kfree(dd); /* cleanup sysfs */ + device_remove_file(&dock_device->dev, &dev_attr_type); device_remove_file(&dock_device->dev, &dev_attr_docked); device_remove_file(&dock_device->dev, &dev_attr_undock); device_remove_file(&dock_device->dev, &dev_attr_uid); -- cgit v1.2.3 From c5d191b8e531e33b823242f3d2c6b81d765e96dd Mon Sep 17 00:00:00 2001 From: Len Brown Date: Wed, 24 Sep 2008 02:53:25 -0400 Subject: dock: Shaohua Li is new maintainer Signed-off-by: Len Brown --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index cad81a24e83..7212af72314 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1417,8 +1417,8 @@ M: rdunlap@xenotime.net S: Maintained DOCKING STATION DRIVER -P: Kristen Carlson Accardi -M: kristen.c.accardi@intel.com +P: Shaohua Li +M: shaohua.li@intel.com L: linux-acpi@vger.kernel.org S: Supported -- cgit v1.2.3 From 709ee531c153038d81b30649b9eeed3c44a4d5cc Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Tue, 23 Sep 2008 17:46:57 +0200 Subject: panasonic-laptop: add Panasonic Let's Note laptop extras driver v0.94 This is a driver for ACPI extras such as hotkeys and backlight brightness control on various Panasonic "Let's Note" series laptop computers. It exports the backlight via the backlight class device API, and the hotkeys as input event device. Some more esoteric items like number of installed batteries are exported via sysfs device attributes. Hotkey events also generate old-style ACPI enents through /proc/acpi/event to interoperate with current versions of acpid. Signed-off-by: Harald Welte Acked-by: Henrique de Moraes Holschuh Acked-by: Matthew Garrett Signed-off-by: Len Brown --- MAINTAINERS | 5 + drivers/misc/Kconfig | 11 + drivers/misc/Makefile | 1 + drivers/misc/panasonic-laptop.c | 767 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 784 insertions(+) create mode 100644 drivers/misc/panasonic-laptop.c diff --git a/MAINTAINERS b/MAINTAINERS index cad81a24e83..fa3579382ed 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3148,6 +3148,11 @@ M: olof@lixom.net L: i2c@lm-sensors.org S: Maintained +PANASONIC LAPTOP ACPI EXTRAS DRIVER +P: Harald Welte +M: laforge@gnumonks.org +S: Maintained + PARALLEL PORT SUPPORT L: linux-parport@lists.infradead.org (subscribers-only) S: Orphan diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a726f3b01a6..4ed83575393 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -245,6 +245,17 @@ config MSI_LAPTOP If you have an MSI S270 laptop, say Y or M here. +config PANASONIC_LAPTOP + tristate "Panasonic Laptop Extras" + depends on X86 && INPUT + depends on BACKLIGHT_CLASS_DEVICE + ---help--- + This driver adds support for access to backlight control and hotkeys + on Panasonic Let's Note laptops. + + If you have a Panasonic Let's note laptop (such as the R1(N variant), + R2, R3, R5, T2, W2 and Y2 series), say Y. + config COMPAL_LAPTOP tristate "Compal Laptop Extras" depends on X86 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c6c13f60b45..909e2468cdc 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_SGI_IOC4) += ioc4.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o +obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o diff --git a/drivers/misc/panasonic-laptop.c b/drivers/misc/panasonic-laptop.c new file mode 100644 index 00000000000..9aecda5a399 --- /dev/null +++ b/drivers/misc/panasonic-laptop.c @@ -0,0 +1,767 @@ +/* + * Panasonic HotKey and LCD brightness control driver + * (C) 2004 Hiroshi Miura + * (C) 2004 NTT DATA Intellilink Co. http://www.intellilink.co.jp/ + * (C) YOKOTA Hiroshi + * (C) 2004 David Bronaugh + * (C) 2006-2008 Harald Welte + * + * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * publicshed by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + *--------------------------------------------------------------------------- + * + * ChangeLog: + * Sep.23, 2008 Harald Welte + * -v0.95 rename driver from drivers/acpi/pcc_acpi.c to + * drivers/misc/panasonic-laptop.c + * + * Jul.04, 2008 Harald Welte + * -v0.94 replace /proc interface with device attributes + * support {set,get}keycode on th input device + * + * Jun.27, 2008 Harald Welte + * -v0.92 merge with 2.6.26-rc6 input API changes + * remove broken <= 2.6.15 kernel support + * resolve all compiler warnings + * various coding style fixes (checkpatch.pl) + * add support for backlight api + * major code restructuring + * + * Dac.28, 2007 Harald Welte + * -v0.91 merge with 2.6.24-rc6 ACPI changes + * + * Nov.04, 2006 Hiroshi Miura + * -v0.9 remove warning about section reference. + * remove acpi_os_free + * add /proc/acpi/pcc/brightness interface for HAL access + * merge dbronaugh's enhancement + * Aug.17, 2004 David Bronaugh (dbronaugh) + * - Added screen brightness setting interface + * Thanks to FreeBSD crew (acpi_panasonic.c) + * for the ideas I needed to accomplish it + * + * May.29, 2006 Hiroshi Miura + * -v0.8.4 follow to change keyinput structure + * thanks Fabian Yamaguchi , + * Jacob Bower and + * Hiroshi Yokota for providing solutions. + * + * Oct.02, 2004 Hiroshi Miura + * -v0.8.2 merge code of YOKOTA Hiroshi + * . + * Add sticky key mode interface. + * Refactoring acpi_pcc_generate_keyinput(). + * + * Sep.15, 2004 Hiroshi Miura + * -v0.8 Generate key input event on input subsystem. + * This is based on yet another driver written by + * Ryuta Nakanishi. + * + * Sep.10, 2004 Hiroshi Miura + * -v0.7 Change proc interface functions using seq_file + * facility as same as other ACPI drivers. + * + * Aug.28, 2004 Hiroshi Miura + * -v0.6.4 Fix a silly error with status checking + * + * Aug.25, 2004 Hiroshi Miura + * -v0.6.3 replace read_acpi_int by standard function + * acpi_evaluate_integer + * some clean up and make smart copyright notice. + * fix return value of pcc_acpi_get_key() + * fix checking return value of acpi_bus_register_driver() + * + * Aug.22, 2004 David Bronaugh + * -v0.6.2 Add check on ACPI data (num_sifr) + * Coding style cleanups, better error messages/handling + * Fixed an off-by-one error in memory allocation + * + * Aug.21, 2004 David Bronaugh + * -v0.6.1 Fix a silly error with status checking + * + * Aug.20, 2004 David Bronaugh + * - v0.6 Correct brightness controls to reflect reality + * based on information gleaned by Hiroshi Miura + * and discussions with Hiroshi Miura + * + * Aug.10, 2004 Hiroshi Miura + * - v0.5 support LCD brightness control + * based on the disclosed information by MEI. + * + * Jul.25, 2004 Hiroshi Miura + * - v0.4 first post version + * add function to retrive SIFR + * + * Jul.24, 2004 Hiroshi Miura + * - v0.3 get proper status of hotkey + * + * Jul.22, 2004 Hiroshi Miura + * - v0.2 add HotKey handler + * + * Jul.17, 2004 Hiroshi Miura + * - v0.1 start from toshiba_acpi driver written by John Belmonte + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef ACPI_HOTKEY_COMPONENT +#define ACPI_HOTKEY_COMPONENT 0x10000000 +#endif + +#define _COMPONENT ACPI_HOTKEY_COMPONENT + +MODULE_AUTHOR("Hiroshi Miura, David Bronaugh and Harald Welte"); +MODULE_DESCRIPTION("ACPI HotKey driver for Panasonic Let's Note laptops"); +MODULE_LICENSE("GPL"); + +#define LOGPREFIX "pcc_acpi: " + +/* Define ACPI PATHs */ +/* Lets note hotkeys */ +#define METHOD_HKEY_QUERY "HINF" +#define METHOD_HKEY_SQTY "SQTY" +#define METHOD_HKEY_SINF "SINF" +#define METHOD_HKEY_SSET "SSET" +#define HKEY_NOTIFY 0x80 + +#define ACPI_PCC_DRIVER_NAME "Panasonic Laptop Support" +#define ACPI_PCC_DEVICE_NAME "Hotkey" +#define ACPI_PCC_CLASS "pcc" + +#define ACPI_PCC_INPUT_PHYS "panasonic/hkey0" + +/* LCD_TYPEs: 0 = Normal, 1 = Semi-transparent + ENV_STATEs: Normal temp=0x01, High temp=0x81, N/A=0x00 +*/ +enum SINF_BITS { SINF_NUM_BATTERIES = 0, + SINF_LCD_TYPE, + SINF_AC_MAX_BRIGHT, + SINF_AC_MIN_BRIGHT, + SINF_AC_CUR_BRIGHT, + SINF_DC_MAX_BRIGHT, + SINF_DC_MIN_BRIGHT, + SINF_DC_CUR_BRIGHT, + SINF_MUTE, + SINF_RESERVED, + SINF_ENV_STATE, + SINF_STICKY_KEY = 0x80, + }; +/* R1 handles SINF_AC_CUR_BRIGHT as SINF_CUR_BRIGHT, doesn't know AC state */ + +static int acpi_pcc_hotkey_add(struct acpi_device *device); +static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type); +static int acpi_pcc_hotkey_resume(struct acpi_device *device); + +static const struct acpi_device_id pcc_device_ids[] = { + { "MAT0012", 0}, + { "MAT0013", 0}, + { "MAT0018", 0}, + { "MAT0019", 0}, + { "", 0}, +}; + +static struct acpi_driver acpi_pcc_driver = { + .name = ACPI_PCC_DRIVER_NAME, + .class = ACPI_PCC_CLASS, + .ids = pcc_device_ids, + .ops = { + .add = acpi_pcc_hotkey_add, + .remove = acpi_pcc_hotkey_remove, + .resume = acpi_pcc_hotkey_resume, + }, +}; + +#define KEYMAP_SIZE 11 +static const int initial_keymap[KEYMAP_SIZE] = { + /* 0 */ KEY_RESERVED, + /* 1 */ KEY_BRIGHTNESSDOWN, + /* 2 */ KEY_BRIGHTNESSUP, + /* 3 */ KEY_DISPLAYTOGGLE, + /* 4 */ KEY_MUTE, + /* 5 */ KEY_VOLUMEDOWN, + /* 6 */ KEY_VOLUMEUP, + /* 7 */ KEY_SLEEP, + /* 8 */ KEY_PROG1, /* Change CPU boost */ + /* 9 */ KEY_BATTERY, + /* 10 */ KEY_SUSPEND, +}; + +struct pcc_acpi { + acpi_handle handle; + unsigned long num_sifr; + int sticky_mode; + u32 *sinf; + struct acpi_device *device; + struct input_dev *input_dev; + struct backlight_device *backlight; + int keymap[KEYMAP_SIZE]; +}; + +struct pcc_keyinput { + struct acpi_hotkey *hotkey; +}; + +/* method access functions */ +static int acpi_pcc_write_sset(struct pcc_acpi *pcc, int func, int val) +{ + union acpi_object in_objs[] = { + { .integer.type = ACPI_TYPE_INTEGER, + .integer.value = func, }, + { .integer.type = ACPI_TYPE_INTEGER, + .integer.value = val, }, + }; + struct acpi_object_list params = { + .count = ARRAY_SIZE(in_objs), + .pointer = in_objs, + }; + acpi_status status = AE_OK; + + ACPI_FUNCTION_TRACE("acpi_pcc_write_sset"); + + status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SSET, + ¶ms, NULL); + + return status == AE_OK; +} + +static inline int acpi_pcc_get_sqty(struct acpi_device *device) +{ + unsigned long s; + acpi_status status; + + ACPI_FUNCTION_TRACE("acpi_pcc_get_sqty"); + + status = acpi_evaluate_integer(device->handle, METHOD_HKEY_SQTY, + NULL, &s); + if (ACPI_SUCCESS(status)) + return s; + else { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "evaluation error HKEY.SQTY\n")); + return -EINVAL; + } +} + +static int acpi_pcc_retrieve_biosdata(struct pcc_acpi *pcc, u32 *sinf) +{ + acpi_status status; + struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object *hkey = NULL; + int i; + + ACPI_FUNCTION_TRACE("acpi_pcc_retrieve_biosdata"); + + status = acpi_evaluate_object(pcc->handle, METHOD_HKEY_SINF, 0, + &buffer); + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "evaluation error HKEY.SINF\n")); + return 0; + } + + hkey = buffer.pointer; + if (!hkey || (hkey->type != ACPI_TYPE_PACKAGE)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid HKEY.SINF\n")); + goto end; + } + + if (pcc->num_sifr < hkey->package.count) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "SQTY reports bad SINF length\n")); + status = AE_ERROR; + goto end; + } + + for (i = 0; i < hkey->package.count; i++) { + union acpi_object *element = &(hkey->package.elements[i]); + if (likely(element->type == ACPI_TYPE_INTEGER)) { + sinf[i] = element->integer.value; + } else + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Invalid HKEY.SINF data\n")); + } + sinf[hkey->package.count] = -1; + +end: + kfree(buffer.pointer); + return status == AE_OK; +} + +/* backlight API interface functions */ + +/* This driver currently treats AC and DC brightness identical, + * since we don't need to invent an interface to the core ACPI + * logic to receive events in case a power supply is plugged in + * or removed */ + +static int bl_get(struct backlight_device *bd) +{ + struct pcc_acpi *pcc = bl_get_data(bd); + + if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + return -EIO; + + return pcc->sinf[SINF_AC_CUR_BRIGHT]; +} + +static int bl_set_status(struct backlight_device *bd) +{ + struct pcc_acpi *pcc = bl_get_data(bd); + int bright = bd->props.brightness; + int rc; + + if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + return -EIO; + + if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT]) + bright = pcc->sinf[SINF_AC_MIN_BRIGHT]; + + if (bright < pcc->sinf[SINF_DC_MIN_BRIGHT]) + bright = pcc->sinf[SINF_DC_MIN_BRIGHT]; + + if (bright < pcc->sinf[SINF_AC_MIN_BRIGHT] || + bright > pcc->sinf[SINF_AC_MAX_BRIGHT]) + return -EINVAL; + + rc = acpi_pcc_write_sset(pcc, SINF_AC_CUR_BRIGHT, bright); + if (rc < 0) + return rc; + + return acpi_pcc_write_sset(pcc, SINF_DC_CUR_BRIGHT, bright); +} + +static struct backlight_ops pcc_backlight_ops = { + .get_brightness = bl_get, + .update_status = bl_set_status, +}; + + +/* sysfs user interface functions */ + +static ssize_t show_numbatt(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + return -EIO; + + return sprintf(buf, "%u\n", pcc->sinf[SINF_NUM_BATTERIES]); +} + +static ssize_t show_lcdtype(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + return -EIO; + + return sprintf(buf, "%u\n", pcc->sinf[SINF_LCD_TYPE]); +} + +static ssize_t show_mute(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + return -EIO; + + return sprintf(buf, "%u\n", pcc->sinf[SINF_MUTE]); +} + +static ssize_t show_sticky(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + + if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) + return -EIO; + + return sprintf(buf, "%u\n", pcc->sinf[SINF_STICKY_KEY]); +} + +static ssize_t set_sticky(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi = to_acpi_device(dev); + struct pcc_acpi *pcc = acpi_driver_data(acpi); + int val; + + if (count && sscanf(buf, "%i", &val) == 1 && + (val == 0 || val == 1)) { + acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, val); + pcc->sticky_mode = val; + } + + return count; +} + +static DEVICE_ATTR(numbatt, S_IRUGO, show_numbatt, NULL); +static DEVICE_ATTR(lcdtype, S_IRUGO, show_lcdtype, NULL); +static DEVICE_ATTR(mute, S_IRUGO, show_mute, NULL); +static DEVICE_ATTR(sticky_key, S_IRUGO | S_IWUSR, show_sticky, set_sticky); + +static struct attribute *pcc_sysfs_entries[] = { + &dev_attr_numbatt.attr, + &dev_attr_lcdtype.attr, + &dev_attr_mute.attr, + &dev_attr_sticky_key.attr, + NULL, +}; + +static struct attribute_group pcc_attr_group = { + .name = NULL, /* put in device directory */ + .attrs = pcc_sysfs_entries, +}; + + +/* hotkey input device driver */ + +static int pcc_getkeycode(struct input_dev *dev, int scancode, int *keycode) +{ + struct pcc_acpi *pcc = input_get_drvdata(dev); + + if (scancode >= ARRAY_SIZE(pcc->keymap)) + return -EINVAL; + + *keycode = pcc->keymap[scancode]; + + return 0; +} + +static int keymap_get_by_keycode(struct pcc_acpi *pcc, int keycode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++) { + if (pcc->keymap[i] == keycode) + return i+1; + } + + return 0; +} + +static int pcc_setkeycode(struct input_dev *dev, int scancode, int keycode) +{ + struct pcc_acpi *pcc = input_get_drvdata(dev); + int oldkeycode; + + if (scancode >= ARRAY_SIZE(pcc->keymap)) + return -EINVAL; + + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + oldkeycode = pcc->keymap[scancode]; + pcc->keymap[scancode] = keycode; + + set_bit(keycode, dev->keybit); + + if (!keymap_get_by_keycode(pcc, oldkeycode)) + clear_bit(oldkeycode, dev->keybit); + + return 0; +} + +static void acpi_pcc_generate_keyinput(struct pcc_acpi *pcc) +{ + struct input_dev *hotk_input_dev = pcc->input_dev; + int rc; + int key_code, hkey_num; + unsigned long result; + + ACPI_FUNCTION_TRACE("acpi_pcc_generate_keyinput"); + + rc = acpi_evaluate_integer(pcc->handle, METHOD_HKEY_QUERY, + NULL, &result); + if (!ACPI_SUCCESS(rc)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "error getting hotkey status\n")); + return; + } + + acpi_bus_generate_proc_event(pcc->device, HKEY_NOTIFY, result); + + hkey_num = result & 0xf; + + if (hkey_num < 0 || hkey_num > ARRAY_SIZE(pcc->keymap)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "hotkey number out of range: %d\n", + hkey_num)); + return; + } + + key_code = pcc->keymap[hkey_num]; + + if (key_code != KEY_RESERVED) { + int pushed = (result & 0x80) ? TRUE : FALSE; + + input_report_key(hotk_input_dev, key_code, pushed); + input_sync(hotk_input_dev); + } + + return; +} + +static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data) +{ + struct pcc_acpi *pcc = (struct pcc_acpi *) data; + + ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_notify"); + + switch (event) { + case HKEY_NOTIFY: + acpi_pcc_generate_keyinput(pcc); + break; + default: + /* nothing to do */ + break; + } +} + +static int acpi_pcc_init_input(struct pcc_acpi *pcc) +{ + int i, rc; + + ACPI_FUNCTION_TRACE("acpi_pcc_init_input"); + + pcc->input_dev = input_allocate_device(); + if (!pcc->input_dev) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Couldn't allocate input device for hotkey")); + return -ENOMEM; + } + + pcc->input_dev->evbit[0] = BIT(EV_KEY); + + pcc->input_dev->name = ACPI_PCC_DRIVER_NAME; + pcc->input_dev->phys = ACPI_PCC_INPUT_PHYS; + pcc->input_dev->id.bustype = BUS_HOST; + pcc->input_dev->id.vendor = 0x0001; + pcc->input_dev->id.product = 0x0001; + pcc->input_dev->id.version = 0x0100; + pcc->input_dev->getkeycode = pcc_getkeycode; + pcc->input_dev->setkeycode = pcc_setkeycode; + + /* load initial keymap */ + memcpy(pcc->keymap, initial_keymap, sizeof(pcc->keymap)); + + for (i = 0; i < ARRAY_SIZE(pcc->keymap); i++) + __set_bit(pcc->keymap[i], pcc->input_dev->keybit); + __clear_bit(KEY_RESERVED, pcc->input_dev->keybit); + + input_set_drvdata(pcc->input_dev, pcc); + + rc = input_register_device(pcc->input_dev); + if (rc < 0) + input_free_device(pcc->input_dev); + + return rc; +} + +/* kernel module interface */ + +static int acpi_pcc_hotkey_resume(struct acpi_device *device) +{ + struct pcc_acpi *pcc = acpi_driver_data(device); + acpi_status status = AE_OK; + + ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_resume"); + + if (device == NULL || pcc == NULL) + return -EINVAL; + + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Sticky mode restore: %d\n", + pcc->sticky_mode)); + + status = acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, pcc->sticky_mode); + + return status == AE_OK ? 0 : -EINVAL; +} + +static int acpi_pcc_hotkey_add(struct acpi_device *device) +{ + acpi_status status; + struct pcc_acpi *pcc; + int num_sifr, result; + + ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_add"); + + if (!device) + return -EINVAL; + + num_sifr = acpi_pcc_get_sqty(device); + + if (num_sifr > 255) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "num_sifr too large")); + return -ENODEV; + } + + pcc = kzalloc(sizeof(struct pcc_acpi), GFP_KERNEL); + if (!pcc) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Couldn't allocate mem for pcc")); + return -ENOMEM; + } + + pcc->sinf = kzalloc(sizeof(u32) * (num_sifr + 1), GFP_KERNEL); + if (!pcc->sinf) { + result = -ENOMEM; + goto out_hotkey; + } + + pcc->device = device; + pcc->handle = device->handle; + pcc->num_sifr = num_sifr; + acpi_driver_data(device) = pcc; + strcpy(acpi_device_name(device), ACPI_PCC_DEVICE_NAME); + strcpy(acpi_device_class(device), ACPI_PCC_CLASS); + + result = acpi_pcc_init_input(pcc); + if (result) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error installing keyinput handler\n")); + goto out_sinf; + } + + /* initialize hotkey input device */ + status = acpi_install_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY, + acpi_pcc_hotkey_notify, pcc); + + if (ACPI_FAILURE(status)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error installing notify handler\n")); + result = -ENODEV; + goto out_input; + } + + /* initialize backlight */ + pcc->backlight = backlight_device_register("panasonic", NULL, pcc, + &pcc_backlight_ops); + if (IS_ERR(pcc->backlight)) + goto out_notify; + + if (!acpi_pcc_retrieve_biosdata(pcc, pcc->sinf)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Couldn't retrieve BIOS data\n")); + goto out_backlight; + } + + /* read the initial brightness setting from the hardware */ + pcc->backlight->props.max_brightness = + pcc->sinf[SINF_AC_MAX_BRIGHT]; + pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT]; + + /* read the initial sticky key mode from the hardware */ + pcc->sticky_mode = pcc->sinf[SINF_STICKY_KEY]; + + /* add sysfs attributes */ + result = sysfs_create_group(&device->dev.kobj, &pcc_attr_group); + if (result) + goto out_backlight; + + return 0; + +out_backlight: + backlight_device_unregister(pcc->backlight); +out_notify: + acpi_remove_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY, + acpi_pcc_hotkey_notify); +out_input: + input_unregister_device(pcc->input_dev); + /* no need to input_free_device() since core input API refcount and + * free()s the device */ +out_sinf: + kfree(pcc->sinf); +out_hotkey: + kfree(pcc); + + return result; +} + +static int __init acpi_pcc_init(void) +{ + int result = 0; + + ACPI_FUNCTION_TRACE("acpi_pcc_init"); + + if (acpi_disabled) + return -ENODEV; + + result = acpi_bus_register_driver(&acpi_pcc_driver); + if (result < 0) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error registering hotkey driver\n")); + return -ENODEV; + } + + return 0; +} + +static int acpi_pcc_hotkey_remove(struct acpi_device *device, int type) +{ + struct pcc_acpi *pcc = acpi_driver_data(device); + + ACPI_FUNCTION_TRACE("acpi_pcc_hotkey_remove"); + + if (!device || !pcc) + return -EINVAL; + + sysfs_remove_group(&device->dev.kobj, &pcc_attr_group); + + backlight_device_unregister(pcc->backlight); + + acpi_remove_notify_handler(pcc->handle, ACPI_DEVICE_NOTIFY, + acpi_pcc_hotkey_notify); + + input_unregister_device(pcc->input_dev); + /* no need to input_free_device() since core input API refcount and + * free()s the device */ + + kfree(pcc->sinf); + kfree(pcc); + + return 0; +} + +static void __exit acpi_pcc_exit(void) +{ + ACPI_FUNCTION_TRACE("acpi_pcc_exit"); + + acpi_bus_unregister_driver(&acpi_pcc_driver); +} + +module_init(acpi_pcc_init); +module_exit(acpi_pcc_exit); -- cgit v1.2.3 From 4c168eaf7ea39f25a45a3d8c7eebc3fedb633a1d Mon Sep 17 00:00:00 2001 From: Robert Richter Date: Wed, 24 Sep 2008 11:08:52 +0200 Subject: Revert "Oprofile Multiplexing Patch" Reverting commit 1a960b402a51d80abf54e3f8e4972374ffe5f22d for the main branch. Multiplexing will be tracked on a separate feature branch. Conflicts: arch/x86/oprofile/nmi_int.c --- arch/x86/oprofile/nmi_int.c | 100 +++----------------------------------- arch/x86/oprofile/op_counter.h | 3 +- arch/x86/oprofile/op_model_amd.c | 76 ++++++++++++----------------- arch/x86/oprofile/op_model_p4.c | 4 -- arch/x86/oprofile/op_model_ppro.c | 2 - arch/x86/oprofile/op_x86_model.h | 3 -- drivers/oprofile/oprof.c | 58 ++-------------------- drivers/oprofile/oprof.h | 4 +- drivers/oprofile/oprofile_files.c | 39 +-------------- include/linux/oprofile.h | 3 -- 10 files changed, 45 insertions(+), 247 deletions(-) diff --git a/arch/x86/oprofile/nmi_int.c b/arch/x86/oprofile/nmi_int.c index 4108d02c529..287513a0981 100644 --- a/arch/x86/oprofile/nmi_int.c +++ b/arch/x86/oprofile/nmi_int.c @@ -23,18 +23,12 @@ #include "op_counter.h" #include "op_x86_model.h" -DEFINE_PER_CPU(int, switch_index); - static struct op_x86_model_spec const *model; static DEFINE_PER_CPU(struct op_msrs, cpu_msrs); static DEFINE_PER_CPU(unsigned long, saved_lvtpc); static int nmi_start(void); static void nmi_stop(void); -static void nmi_cpu_save_mpx_registers(struct op_msrs *msrs); -static void nmi_cpu_restore_mpx_registers(struct op_msrs *msrs); -static void nmi_cpu_stop(void *dummy); -static void nmi_cpu_start(void *dummy); /* 0 == registered but off, 1 == registered and on */ static int nmi_enabled = 0; @@ -87,47 +81,6 @@ static void exit_sysfs(void) #define exit_sysfs() do { } while (0) #endif /* CONFIG_PM */ -static void nmi_cpu_switch(void *dummy) -{ - int cpu = smp_processor_id(); - int si = per_cpu(switch_index, cpu); - struct op_msrs *msrs = &per_cpu(cpu_msrs, cpu); - - nmi_cpu_stop(NULL); - nmi_cpu_save_mpx_registers(msrs); - - /* move to next set */ - si += model->num_hardware_counters; - if ((si > model->num_counters) || (counter_config[si].count == 0)) - per_cpu(switch_index, smp_processor_id()) = 0; - else - per_cpu(switch_index, smp_processor_id()) = si; - - nmi_cpu_restore_mpx_registers(msrs); - model->setup_ctrs(msrs); - nmi_cpu_start(NULL); -} - -/* - * Quick check to see if multiplexing is necessary. - * The check should be sufficient since counters are used - * in ordre. - */ -static int nmi_multiplex_on(void) -{ - return counter_config[model->num_hardware_counters].count ? 0 : -EINVAL; -} - -static int nmi_switch_event(void) -{ - if (nmi_multiplex_on() < 0) - return -EINVAL; - - on_each_cpu(nmi_cpu_switch, NULL, 1); - - return 0; -} - static int profile_exceptions_notify(struct notifier_block *self, unsigned long val, void *data) { @@ -191,10 +144,11 @@ static void free_msrs(void) static int allocate_msrs(void) { - int i, success = 1; + int success = 1; size_t controls_size = sizeof(struct op_msr) * model->num_controls; size_t counters_size = sizeof(struct op_msr) * model->num_counters; + int i; for_each_possible_cpu(i) { per_cpu(cpu_msrs, i).counters = kmalloc(counters_size, GFP_KERNEL); @@ -202,8 +156,8 @@ static int allocate_msrs(void) success = 0; break; } - per_cpu(cpu_msrs, i).controls = - kmalloc(controls_size, GFP_KERNEL); + per_cpu(cpu_msrs, i).controls = kmalloc(controls_size, + GFP_KERNEL); if (!per_cpu(cpu_msrs, i).controls) { success = 0; break; @@ -247,8 +201,7 @@ static int nmi_setup(void) return err; } - /* - * We need to serialize save and setup for HT because the subset + /* We need to serialize save and setup for HT because the subset * of msrs are distinct for save and setup operations */ @@ -264,6 +217,7 @@ static int nmi_setup(void) per_cpu(cpu_msrs, 0).controls, sizeof(struct op_msr) * model->num_controls); } + } on_each_cpu(nmi_save_registers, NULL, 1); on_each_cpu(nmi_cpu_setup, NULL, 1); @@ -271,41 +225,7 @@ static int nmi_setup(void) return 0; } -static void nmi_cpu_save_mpx_registers(struct op_msrs *msrs) -{ - unsigned int si = __get_cpu_var(switch_index); - unsigned int const nr_ctrs = model->num_hardware_counters; - struct op_msr *counters = &msrs->counters[si]; - unsigned int i; - - for (i = 0; i < nr_ctrs; ++i) { - int offset = i + si; - if (counters[offset].addr) { - rdmsr(counters[offset].addr, - counters[offset].multiplex.low, - counters[offset].multiplex.high); - } - } -} - -static void nmi_cpu_restore_mpx_registers(struct op_msrs *msrs) -{ - unsigned int si = __get_cpu_var(switch_index); - unsigned int const nr_ctrs = model->num_hardware_counters; - struct op_msr *counters = &msrs->counters[si]; - unsigned int i; - - for (i = 0; i < nr_ctrs; ++i) { - int offset = i + si; - if (counters[offset].addr) { - wrmsr(counters[offset].addr, - counters[offset].multiplex.low, - counters[offset].multiplex.high); - } - } -} - -static void nmi_cpu_restore_registers(struct op_msrs *msrs) +static void nmi_restore_registers(struct op_msrs *msrs) { unsigned int const nr_ctrs = model->num_counters; unsigned int const nr_ctrls = model->num_controls; @@ -345,8 +265,7 @@ static void nmi_cpu_shutdown(void *dummy) apic_write(APIC_LVTERR, v | APIC_LVT_MASKED); apic_write(APIC_LVTPC, per_cpu(saved_lvtpc, cpu)); apic_write(APIC_LVTERR, v); - nmi_cpu_restore_registers(msrs); - __get_cpu_var(switch_index) = 0; + nmi_restore_registers(msrs); } static void nmi_shutdown(void) @@ -409,7 +328,6 @@ static int nmi_create_files(struct super_block *sb, struct dentry *root) oprofilefs_create_ulong(sb, dir, "unit_mask", &counter_config[i].unit_mask); oprofilefs_create_ulong(sb, dir, "kernel", &counter_config[i].kernel); oprofilefs_create_ulong(sb, dir, "user", &counter_config[i].user); - counter_config[i].save_count_low = 0; } return 0; @@ -551,14 +469,12 @@ int __init op_nmi_init(struct oprofile_operations *ops) } /* default values, can be overwritten by model */ - __raw_get_cpu_var(switch_index) = 0; ops->create_files = nmi_create_files; ops->setup = nmi_setup; ops->shutdown = nmi_shutdown; ops->start = nmi_start; ops->stop = nmi_stop; ops->cpu_type = cpu_type; - ops->switch_events = nmi_switch_event; if (model->init) ret = model->init(ops); diff --git a/arch/x86/oprofile/op_counter.h b/arch/x86/oprofile/op_counter.h index 786d6e01cf7..2880b15c467 100644 --- a/arch/x86/oprofile/op_counter.h +++ b/arch/x86/oprofile/op_counter.h @@ -10,14 +10,13 @@ #ifndef OP_COUNTER_H #define OP_COUNTER_H -#define OP_MAX_COUNTER 32 +#define OP_MAX_COUNTER 8 /* Per-perfctr configuration as set via * oprofilefs. */ struct op_counter_config { unsigned long count; - unsigned long save_count_low; unsigned long enabled; unsigned long event; unsigned long kernel; diff --git a/arch/x86/oprofile/op_model_amd.c b/arch/x86/oprofile/op_model_amd.c index bbf2b68bcc5..d9faf607b3a 100644 --- a/arch/x86/oprofile/op_model_amd.c +++ b/arch/x86/oprofile/op_model_amd.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -24,10 +23,8 @@ #include "op_x86_model.h" #include "op_counter.h" -#define NUM_COUNTERS 32 -#define NUM_HARDWARE_COUNTERS 4 -#define NUM_CONTROLS 32 -#define NUM_HARDWARE_CONTROLS 4 +#define NUM_COUNTERS 4 +#define NUM_CONTROLS 4 #define CTR_IS_RESERVED(msrs, c) (msrs->counters[(c)].addr ? 1 : 0) #define CTR_READ(l, h, msrs, c) do {rdmsr(msrs->counters[(c)].addr, (l), (h)); } while (0) @@ -51,7 +48,6 @@ #define CTRL_SET_GUEST_ONLY(val, h) (val |= ((h & 1) << 8)) static unsigned long reset_value[NUM_COUNTERS]; -DECLARE_PER_CPU(int, switch_index); #ifdef CONFIG_OPROFILE_IBS @@ -134,17 +130,15 @@ static void op_amd_fill_in_addresses(struct op_msrs * const msrs) int i; for (i = 0; i < NUM_COUNTERS; i++) { - int hw_counter = i % NUM_HARDWARE_COUNTERS; - if (reserve_perfctr_nmi(MSR_K7_PERFCTR0 + hw_counter)) - msrs->counters[i].addr = MSR_K7_PERFCTR0 + hw_counter; + if (reserve_perfctr_nmi(MSR_K7_PERFCTR0 + i)) + msrs->counters[i].addr = MSR_K7_PERFCTR0 + i; else msrs->counters[i].addr = 0; } for (i = 0; i < NUM_CONTROLS; i++) { - int hw_control = i % NUM_HARDWARE_CONTROLS; - if (reserve_evntsel_nmi(MSR_K7_EVNTSEL0 + hw_control)) - msrs->controls[i].addr = MSR_K7_EVNTSEL0 + hw_control; + if (reserve_evntsel_nmi(MSR_K7_EVNTSEL0 + i)) + msrs->controls[i].addr = MSR_K7_EVNTSEL0 + i; else msrs->controls[i].addr = 0; } @@ -156,16 +150,8 @@ static void op_amd_setup_ctrs(struct op_msrs const * const msrs) unsigned int low, high; int i; - for (i = 0; i < NUM_HARDWARE_CONTROLS; ++i) { - int offset = i + __get_cpu_var(switch_index); - if (counter_config[offset].enabled) - reset_value[offset] = counter_config[offset].count; - else - reset_value[offset] = 0; - } - /* clear all counters */ - for (i = 0 ; i < NUM_HARDWARE_CONTROLS; ++i) { + for (i = 0 ; i < NUM_CONTROLS; ++i) { if (unlikely(!CTRL_IS_RESERVED(msrs, i))) continue; CTRL_READ(low, high, msrs, i); @@ -175,31 +161,34 @@ static void op_amd_setup_ctrs(struct op_msrs const * const msrs) } /* avoid a false detection of ctr overflows in NMI handler */ - for (i = 0; i < NUM_HARDWARE_COUNTERS; ++i) { + for (i = 0; i < NUM_COUNTERS; ++i) { if (unlikely(!CTR_IS_RESERVED(msrs, i))) continue; CTR_WRITE(1, msrs, i); } /* enable active counters */ - for (i = 0; i < NUM_HARDWARE_COUNTERS; ++i) { - int offset = i + __get_cpu_var(switch_index); - if ((counter_config[offset].enabled) && (CTR_IS_RESERVED(msrs, i))) { - CTR_WRITE(counter_config[offset].count, msrs, i); + for (i = 0; i < NUM_COUNTERS; ++i) { + if ((counter_config[i].enabled) && (CTR_IS_RESERVED(msrs, i))) { + reset_value[i] = counter_config[i].count; + + CTR_WRITE(counter_config[i].count, msrs, i); CTRL_READ(low, high, msrs, i); CTRL_CLEAR_LO(low); CTRL_CLEAR_HI(high); CTRL_SET_ENABLE(low); - CTRL_SET_USR(low, counter_config[offset].user); - CTRL_SET_KERN(low, counter_config[offset].kernel); - CTRL_SET_UM(low, counter_config[offset].unit_mask); - CTRL_SET_EVENT_LOW(low, counter_config[offset].event); - CTRL_SET_EVENT_HIGH(high, counter_config[offset].event); + CTRL_SET_USR(low, counter_config[i].user); + CTRL_SET_KERN(low, counter_config[i].kernel); + CTRL_SET_UM(low, counter_config[i].unit_mask); + CTRL_SET_EVENT_LOW(low, counter_config[i].event); + CTRL_SET_EVENT_HIGH(high, counter_config[i].event); CTRL_SET_HOST_ONLY(high, 0); CTRL_SET_GUEST_ONLY(high, 0); CTRL_WRITE(low, high, msrs, i); + } else { + reset_value[i] = 0; } } } @@ -287,14 +276,13 @@ static int op_amd_check_ctrs(struct pt_regs * const regs, unsigned int low, high; int i; - for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { - int offset = i + __get_cpu_var(switch_index); - if (!reset_value[offset]) + for (i = 0 ; i < NUM_COUNTERS; ++i) { + if (!reset_value[i]) continue; CTR_READ(low, high, msrs, i); if (CTR_OVERFLOWED(low)) { - oprofile_add_sample(regs, offset); - CTR_WRITE(reset_value[offset], msrs, i); + oprofile_add_sample(regs, i); + CTR_WRITE(reset_value[i], msrs, i); } } @@ -310,10 +298,8 @@ static void op_amd_start(struct op_msrs const * const msrs) { unsigned int low, high; int i; - - for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { - int offset = i + __get_cpu_var(switch_index); - if (reset_value[offset]) { + for (i = 0 ; i < NUM_COUNTERS ; ++i) { + if (reset_value[i]) { CTRL_READ(low, high, msrs, i); CTRL_SET_ACTIVE(low); CTRL_WRITE(low, high, msrs, i); @@ -343,8 +329,8 @@ static void op_amd_stop(struct op_msrs const * const msrs) /* Subtle: stop on all counters to avoid race with * setting our pm callback */ - for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { - if (!reset_value[i + per_cpu(switch_index, smp_processor_id())]) + for (i = 0 ; i < NUM_COUNTERS ; ++i) { + if (!reset_value[i]) continue; CTRL_READ(low, high, msrs, i); CTRL_SET_INACTIVE(low); @@ -370,11 +356,11 @@ static void op_amd_shutdown(struct op_msrs const * const msrs) { int i; - for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { + for (i = 0 ; i < NUM_COUNTERS ; ++i) { if (CTR_IS_RESERVED(msrs, i)) release_perfctr_nmi(MSR_K7_PERFCTR0 + i); } - for (i = 0 ; i < NUM_HARDWARE_COUNTERS ; ++i) { + for (i = 0 ; i < NUM_CONTROLS ; ++i) { if (CTRL_IS_RESERVED(msrs, i)) release_evntsel_nmi(MSR_K7_EVNTSEL0 + i); } @@ -548,8 +534,6 @@ struct op_x86_model_spec const op_amd_spec = { .exit = op_amd_exit, .num_counters = NUM_COUNTERS, .num_controls = NUM_CONTROLS, - .num_hardware_counters = NUM_HARDWARE_COUNTERS, - .num_hardware_controls = NUM_HARDWARE_CONTROLS, .fill_in_addresses = &op_amd_fill_in_addresses, .setup_ctrs = &op_amd_setup_ctrs, .check_ctrs = &op_amd_check_ctrs, diff --git a/arch/x86/oprofile/op_model_p4.c b/arch/x86/oprofile/op_model_p4.c index cacba61ffba..43ac5af338d 100644 --- a/arch/x86/oprofile/op_model_p4.c +++ b/arch/x86/oprofile/op_model_p4.c @@ -700,8 +700,6 @@ static void p4_shutdown(struct op_msrs const * const msrs) struct op_x86_model_spec const op_p4_ht2_spec = { .num_counters = NUM_COUNTERS_HT2, .num_controls = NUM_CONTROLS_HT2, - .num_hardware_counters = NUM_COUNTERS_HT2, - .num_hardware_controls = NUM_CONTROLS_HT2, .fill_in_addresses = &p4_fill_in_addresses, .setup_ctrs = &p4_setup_ctrs, .check_ctrs = &p4_check_ctrs, @@ -714,8 +712,6 @@ struct op_x86_model_spec const op_p4_ht2_spec = { struct op_x86_model_spec const op_p4_spec = { .num_counters = NUM_COUNTERS_NON_HT, .num_controls = NUM_CONTROLS_NON_HT, - .num_hardware_counters = NUM_COUNTERS_NON_HT, - .num_hardware_controls = NUM_CONTROLS_NON_HT, .fill_in_addresses = &p4_fill_in_addresses, .setup_ctrs = &p4_setup_ctrs, .check_ctrs = &p4_check_ctrs, diff --git a/arch/x86/oprofile/op_model_ppro.c b/arch/x86/oprofile/op_model_ppro.c index e5811aa480e..eff431f6c57 100644 --- a/arch/x86/oprofile/op_model_ppro.c +++ b/arch/x86/oprofile/op_model_ppro.c @@ -183,8 +183,6 @@ static void ppro_shutdown(struct op_msrs const * const msrs) struct op_x86_model_spec const op_ppro_spec = { .num_counters = NUM_COUNTERS, .num_controls = NUM_CONTROLS, - .num_hardware_counters = NUM_COUNTERS, - .num_hardware_controls = NUM_CONTROLS, .fill_in_addresses = &ppro_fill_in_addresses, .setup_ctrs = &ppro_setup_ctrs, .check_ctrs = &ppro_check_ctrs, diff --git a/arch/x86/oprofile/op_x86_model.h b/arch/x86/oprofile/op_x86_model.h index e07ba107637..05a0261ba0c 100644 --- a/arch/x86/oprofile/op_x86_model.h +++ b/arch/x86/oprofile/op_x86_model.h @@ -19,7 +19,6 @@ struct op_saved_msr { struct op_msr { unsigned long addr; struct op_saved_msr saved; - struct op_saved_msr multiplex; }; struct op_msrs { @@ -35,8 +34,6 @@ struct pt_regs; struct op_x86_model_spec { int (*init)(struct oprofile_operations *ops); void (*exit)(void); - unsigned int const num_hardware_counters; - unsigned int const num_hardware_controls; unsigned int const num_counters; unsigned int const num_controls; void (*fill_in_addresses)(struct op_msrs * const msrs); diff --git a/drivers/oprofile/oprof.c b/drivers/oprofile/oprof.c index b2fa5df64a6..2c645170f06 100644 --- a/drivers/oprofile/oprof.c +++ b/drivers/oprofile/oprof.c @@ -12,8 +12,6 @@ #include #include #include -#include -#include #include #include "oprof.h" @@ -21,18 +19,13 @@ #include "cpu_buffer.h" #include "buffer_sync.h" #include "oprofile_stats.h" - -static unsigned long is_setup; -static void switch_worker(struct work_struct *work); -static DECLARE_DELAYED_WORK(switch_work, switch_worker); -static DEFINE_MUTEX(start_mutex); struct oprofile_operations oprofile_ops; -unsigned long timeout_jiffies; unsigned long oprofile_started; unsigned long backtrace_depth; -/* Multiplexing defaults at 1 msec*/ +static unsigned long is_setup; +static DEFINE_MUTEX(start_mutex); /* timer 0 - use performance monitoring hardware if available @@ -94,16 +87,6 @@ out: return err; } -static void start_switch_worker(void) -{ - schedule_delayed_work(&switch_work, timeout_jiffies); -} - -static void switch_worker(struct work_struct *work) -{ - if (!oprofile_ops.switch_events()) - start_switch_worker(); -} /* Actually start profiling (echo 1>/dev/oprofile/enable) */ int oprofile_start(void) @@ -111,6 +94,7 @@ int oprofile_start(void) int err = -EINVAL; mutex_lock(&start_mutex); + if (!is_setup) goto out; @@ -124,9 +108,6 @@ int oprofile_start(void) if ((err = oprofile_ops.start())) goto out; - if (oprofile_ops.switch_events) - start_switch_worker(); - oprofile_started = 1; out: mutex_unlock(&start_mutex); @@ -142,7 +123,6 @@ void oprofile_stop(void) goto out; oprofile_ops.stop(); oprofile_started = 0; - cancel_delayed_work_sync(&switch_work); /* wake up the daemon to read what remains */ wake_up_buffer_waiter(); out: @@ -175,32 +155,6 @@ post_sync: mutex_unlock(&start_mutex); } -/* User inputs in ms, converts to jiffies */ -int oprofile_set_timeout(unsigned long val_msec) -{ - int err = 0; - - mutex_lock(&start_mutex); - - if (oprofile_started) { - err = -EBUSY; - goto out; - } - - if (!oprofile_ops.switch_events) { - err = -EINVAL; - goto out; - } - - timeout_jiffies = msecs_to_jiffies(val_msec); - if (timeout_jiffies == MAX_JIFFY_OFFSET) - timeout_jiffies = msecs_to_jiffies(1); - -out: - mutex_unlock(&start_mutex); - return err; - -} int oprofile_set_backtrace(unsigned long val) { @@ -225,16 +179,10 @@ out: return err; } -static void __init oprofile_switch_timer_init(void) -{ - timeout_jiffies = msecs_to_jiffies(1); -} - static int __init oprofile_init(void) { int err; - oprofile_switch_timer_init(); err = oprofile_arch_init(&oprofile_ops); if (err < 0 || timer) { diff --git a/drivers/oprofile/oprof.h b/drivers/oprofile/oprof.h index c4406a7366b..18323650806 100644 --- a/drivers/oprofile/oprof.h +++ b/drivers/oprofile/oprof.h @@ -27,8 +27,7 @@ extern unsigned long fs_buffer_watershed; extern struct oprofile_operations oprofile_ops; extern unsigned long oprofile_started; extern unsigned long backtrace_depth; -extern unsigned long timeout_jiffies; - + struct super_block; struct dentry; @@ -36,6 +35,5 @@ void oprofile_create_files(struct super_block * sb, struct dentry * root); void oprofile_timer_init(struct oprofile_operations * ops); int oprofile_set_backtrace(unsigned long depth); -int oprofile_set_timeout(unsigned long time); #endif /* OPROF_H */ diff --git a/drivers/oprofile/oprofile_files.c b/drivers/oprofile/oprofile_files.c index cc4f5a1f8ef..ef953ba5ab6 100644 --- a/drivers/oprofile/oprofile_files.c +++ b/drivers/oprofile/oprofile_files.c @@ -9,7 +9,6 @@ #include #include -#include #include "event_buffer.h" #include "oprofile_stats.h" @@ -19,40 +18,6 @@ unsigned long fs_buffer_size = 131072; unsigned long fs_cpu_buffer_size = 8192; unsigned long fs_buffer_watershed = 32768; /* FIXME: tune */ -static ssize_t timeout_read(struct file *file, char __user *buf, - size_t count, loff_t *offset) -{ - return oprofilefs_ulong_to_user(jiffies_to_msecs(timeout_jiffies), - buf, count, offset); -} - - -static ssize_t timeout_write(struct file *file, char const __user *buf, - size_t count, loff_t *offset) -{ - unsigned long val; - int retval; - - if (*offset) - return -EINVAL; - - retval = oprofilefs_ulong_from_user(&val, buf, count); - if (retval) - return retval; - - retval = oprofile_set_timeout(val); - - if (retval) - return retval; - return count; -} - -static const struct file_operations timeout_fops = { - .read = timeout_read, - .write = timeout_write, -}; - - static ssize_t depth_read(struct file * file, char __user * buf, size_t count, loff_t * offset) { return oprofilefs_ulong_to_user(backtrace_depth, buf, count, offset); @@ -120,10 +85,11 @@ static ssize_t enable_write(struct file * file, char const __user * buf, size_t if (*offset) return -EINVAL; + retval = oprofilefs_ulong_from_user(&val, buf, count); if (retval) return retval; - + if (val) retval = oprofile_start(); else @@ -163,7 +129,6 @@ void oprofile_create_files(struct super_block * sb, struct dentry * root) oprofilefs_create_file(sb, root, "cpu_type", &cpu_type_fops); oprofilefs_create_file(sb, root, "backtrace_depth", &depth_fops); oprofilefs_create_file(sb, root, "pointer_size", &pointer_size_fops); - oprofilefs_create_file(sb, root, "timeout_ms", &timeout_fops); oprofile_create_stats_files(sb, root); if (oprofile_ops.create_files) oprofile_ops.create_files(sb, root); diff --git a/include/linux/oprofile.h b/include/linux/oprofile.h index 687f2f4c36a..bcb8f725427 100644 --- a/include/linux/oprofile.h +++ b/include/linux/oprofile.h @@ -67,9 +67,6 @@ struct oprofile_operations { /* Initiate a stack backtrace. Optional. */ void (*backtrace)(struct pt_regs * const regs, unsigned int depth); - - /* Multiplex between different events. Optional. */ - int (*switch_events)(void); /* CPU identification string. */ char * cpu_type; }; -- cgit v1.2.3 From 7c6db4e050601f359081fde418ca6dc4fc2d0011 Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Thu, 25 Sep 2008 21:00:31 +0400 Subject: ACPI: EC: do transaction from interrupt context It is easier and faster to do transaction directly from interrupt context rather than waking control thread. Also, cleaner GPE storm avoidance is implemented. References: http://bugzilla.kernel.org/show_bug.cgi?id=9998 http://bugzilla.kernel.org/show_bug.cgi?id=10724 http://bugzilla.kernel.org/show_bug.cgi?id=10919 http://bugzilla.kernel.org/show_bug.cgi?id=11309 http://bugzilla.kernel.org/show_bug.cgi?id=11549 Signed-off-by: Alexey Starikovskiy Tested-by: Sitsofe Wheeler Signed-off-by: Len Brown --- drivers/acpi/ec.c | 309 ++++++++++++++++++++++++++---------------------------- 1 file changed, 149 insertions(+), 160 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 13593f9f219..7f0d81c0ec9 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -1,7 +1,7 @@ /* - * ec.c - ACPI Embedded Controller Driver (v2.0) + * ec.c - ACPI Embedded Controller Driver (v2.1) * - * Copyright (C) 2006, 2007 Alexey Starikovskiy + * Copyright (C) 2006-2008 Alexey Starikovskiy * Copyright (C) 2006 Denis Sadykov * Copyright (C) 2004 Luming Yu * Copyright (C) 2001, 2002 Andy Grover @@ -26,7 +26,7 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -/* Uncomment next line to get verbose print outs*/ +/* Uncomment next line to get verbose printout */ /* #define DEBUG */ #include @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -65,22 +66,21 @@ enum ec_command { ACPI_EC_COMMAND_QUERY = 0x84, }; -/* EC events */ -enum ec_event { - ACPI_EC_EVENT_OBF_1 = 1, /* Output buffer full */ - ACPI_EC_EVENT_IBF_0, /* Input buffer empty */ -}; - #define ACPI_EC_DELAY 500 /* Wait 500ms max. during EC ops */ #define ACPI_EC_UDELAY_GLK 1000 /* Wait 1ms max. to get global lock */ #define ACPI_EC_UDELAY 100 /* Wait 100us before polling EC again */ +#define ACPI_EC_STORM_THRESHOLD 20 /* number of false interrupts + per one transaction */ + enum { - EC_FLAGS_WAIT_GPE = 0, /* Don't check status until GPE arrives */ EC_FLAGS_QUERY_PENDING, /* Query is pending */ - EC_FLAGS_GPE_MODE, /* Expect GPE to be sent for status change */ + EC_FLAGS_GPE_MODE, /* Expect GPE to be sent + * for status change */ EC_FLAGS_NO_GPE, /* Don't use GPE mode */ - EC_FLAGS_RESCHEDULE_POLL /* Re-schedule poll */ + EC_FLAGS_GPE_STORM, /* GPE storm detected */ + EC_FLAGS_HANDLERS_INSTALLED /* Handlers for GPE and + * OpReg are installed */ }; /* If we find an EC via the ECDT, we need to keep a ptr to its context */ @@ -95,6 +95,14 @@ struct acpi_ec_query_handler { u8 query_bit; }; +struct transaction_data { + const u8 *wdata; + u8 *rdata; + unsigned short irq_count; + u8 wlen; + u8 rlen; +}; + static struct acpi_ec { acpi_handle handle; unsigned long gpe; @@ -105,9 +113,8 @@ static struct acpi_ec { struct mutex lock; wait_queue_head_t wait; struct list_head list; - struct delayed_work work; - atomic_t irq_count; - u8 handlers_installed; + struct transaction_data *t; + spinlock_t t_lock; } *boot_ec, *first_ec; /* @@ -150,7 +157,7 @@ static inline u8 acpi_ec_read_data(struct acpi_ec *ec) { u8 x = inb(ec->data_addr); pr_debug(PREFIX "---> data = 0x%2.2x\n", x); - return inb(ec->data_addr); + return x; } static inline void acpi_ec_write_cmd(struct acpi_ec *ec, u8 command) @@ -165,68 +172,79 @@ static inline void acpi_ec_write_data(struct acpi_ec *ec, u8 data) outb(data, ec->data_addr); } -static inline int acpi_ec_check_status(struct acpi_ec *ec, enum ec_event event) +static int ec_transaction_done(struct acpi_ec *ec) { - if (test_bit(EC_FLAGS_WAIT_GPE, &ec->flags)) - return 0; - if (event == ACPI_EC_EVENT_OBF_1) { - if (acpi_ec_read_status(ec) & ACPI_EC_FLAG_OBF) - return 1; - } else if (event == ACPI_EC_EVENT_IBF_0) { - if (!(acpi_ec_read_status(ec) & ACPI_EC_FLAG_IBF)) - return 1; - } - - return 0; + unsigned long flags; + int ret = 0; + spin_lock_irqsave(&ec->t_lock, flags); + if (!ec->t || (!ec->t->wlen && !ec->t->rlen)) + ret = 1; + spin_unlock_irqrestore(&ec->t_lock, flags); + return ret; } -static void ec_schedule_ec_poll(struct acpi_ec *ec) +static void gpe_transaction(struct acpi_ec *ec, u8 status) { - if (test_bit(EC_FLAGS_RESCHEDULE_POLL, &ec->flags)) - schedule_delayed_work(&ec->work, - msecs_to_jiffies(ACPI_EC_DELAY)); + unsigned long flags; + spin_lock_irqsave(&ec->t_lock, flags); + if (!ec->t) + goto unlock; + if (ec->t->wlen > 0) { + if ((status & ACPI_EC_FLAG_IBF) == 0) { + acpi_ec_write_data(ec, *(ec->t->wdata++)); + --ec->t->wlen; + } else + /* false interrupt, state didn't change */ + ++ec->t->irq_count; + + } else if (ec->t->rlen > 0) { + if ((status & ACPI_EC_FLAG_OBF) == 1) { + *(ec->t->rdata++) = acpi_ec_read_data(ec); + --ec->t->rlen; + } else + /* false interrupt, state didn't change */ + ++ec->t->irq_count; + } +unlock: + spin_unlock_irqrestore(&ec->t_lock, flags); } -static void ec_switch_to_poll_mode(struct acpi_ec *ec) +static int acpi_ec_wait(struct acpi_ec *ec) { + if (wait_event_timeout(ec->wait, ec_transaction_done(ec), + msecs_to_jiffies(ACPI_EC_DELAY))) + return 0; + /* missing GPEs, switch back to poll mode */ + if (printk_ratelimit()) + pr_info(PREFIX "missing confirmations, " + "switch off interrupt mode.\n"); set_bit(EC_FLAGS_NO_GPE, &ec->flags); clear_bit(EC_FLAGS_GPE_MODE, &ec->flags); - acpi_disable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); - set_bit(EC_FLAGS_RESCHEDULE_POLL, &ec->flags); + return 1; } -static int acpi_ec_wait(struct acpi_ec *ec, enum ec_event event, int force_poll) +static void acpi_ec_gpe_query(void *ec_cxt); + +static int ec_check_sci(struct acpi_ec *ec, u8 state) { - atomic_set(&ec->irq_count, 0); - if (likely(test_bit(EC_FLAGS_GPE_MODE, &ec->flags)) && - likely(!force_poll)) { - if (wait_event_timeout(ec->wait, acpi_ec_check_status(ec, event), - msecs_to_jiffies(ACPI_EC_DELAY))) - return 0; - clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); - if (acpi_ec_check_status(ec, event)) { - /* missing GPEs, switch back to poll mode */ - if (printk_ratelimit()) - pr_info(PREFIX "missing confirmations, " - "switch off interrupt mode.\n"); - ec_switch_to_poll_mode(ec); - ec_schedule_ec_poll(ec); - return 0; - } - } else { - unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY); - clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); - while (time_before(jiffies, delay)) { - if (acpi_ec_check_status(ec, event)) - return 0; - msleep(1); - } - if (acpi_ec_check_status(ec,event)) + if (state & ACPI_EC_FLAG_SCI) { + if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) + return acpi_os_execute(OSL_EC_BURST_HANDLER, + acpi_ec_gpe_query, ec); + } + return 0; +} + +static int ec_poll(struct acpi_ec *ec) +{ + unsigned long delay = jiffies + msecs_to_jiffies(ACPI_EC_DELAY); + msleep(1); + while (time_before(jiffies, delay)) { + gpe_transaction(ec, acpi_ec_read_status(ec)); + msleep(1); + if (ec_transaction_done(ec)) return 0; } - pr_err(PREFIX "acpi_ec_wait timeout, status = 0x%2.2x, event = %s\n", - acpi_ec_read_status(ec), - (event == ACPI_EC_EVENT_OBF_1) ? "\"b0=1\"" : "\"b1=0\""); return -ETIME; } @@ -235,45 +253,51 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, u8 * rdata, unsigned rdata_len, int force_poll) { - int result = 0; - set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); + unsigned long tmp; + struct transaction_data t = {.wdata = wdata, .rdata = rdata, + .wlen = wdata_len, .rlen = rdata_len, + .irq_count = 0}; + int ret = 0; pr_debug(PREFIX "transaction start\n"); - acpi_ec_write_cmd(ec, command); - for (; wdata_len > 0; --wdata_len) { - result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, force_poll); - if (result) { - pr_err(PREFIX - "write_cmd timeout, command = %d\n", command); - goto end; - } - set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); - acpi_ec_write_data(ec, *(wdata++)); + /* disable GPE during transaction if storm is detected */ + if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) { + clear_bit(EC_FLAGS_GPE_MODE, &ec->flags); + acpi_disable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); } - - if (!rdata_len) { - result = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, force_poll); - if (result) { - pr_err(PREFIX - "finish-write timeout, command = %d\n", command); - goto end; - } - } else if (command == ACPI_EC_COMMAND_QUERY) + /* start transaction */ + spin_lock_irqsave(&ec->t_lock, tmp); + /* following two actions should be kept atomic */ + ec->t = &t; + acpi_ec_write_cmd(ec, command); + if (command == ACPI_EC_COMMAND_QUERY) clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); - - for (; rdata_len > 0; --rdata_len) { - result = acpi_ec_wait(ec, ACPI_EC_EVENT_OBF_1, force_poll); - if (result) { - pr_err(PREFIX "read timeout, command = %d\n", command); - goto end; - } - /* Don't expect GPE after last read */ - if (rdata_len > 1) - set_bit(EC_FLAGS_WAIT_GPE, &ec->flags); - *(rdata++) = acpi_ec_read_data(ec); - } - end: + spin_unlock_irqrestore(&ec->t_lock, tmp); + /* if we selected poll mode or failed in GPE-mode do a poll loop */ + if (force_poll || + !test_bit(EC_FLAGS_GPE_MODE, &ec->flags) || + acpi_ec_wait(ec)) + ret = ec_poll(ec); pr_debug(PREFIX "transaction end\n"); - return result; + spin_lock_irqsave(&ec->t_lock, tmp); + ec->t = NULL; + spin_unlock_irqrestore(&ec->t_lock, tmp); + if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) { + /* check if we received SCI during transaction */ + ec_check_sci(ec, acpi_ec_read_status(ec)); + /* it is safe to enable GPE outside of transaction */ + acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); + } else if (test_bit(EC_FLAGS_GPE_MODE, &ec->flags) && + t.irq_count > ACPI_EC_STORM_THRESHOLD) { + pr_debug(PREFIX "GPE storm detected\n"); + set_bit(EC_FLAGS_GPE_STORM, &ec->flags); + } + return ret; +} + +static int ec_check_ibf0(struct acpi_ec *ec) +{ + u8 status = acpi_ec_read_status(ec); + return (status & ACPI_EC_FLAG_IBF) == 0; } static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, @@ -283,40 +307,34 @@ static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, { int status; u32 glk; - if (!ec || (wdata_len && !wdata) || (rdata_len && !rdata)) return -EINVAL; - if (rdata) memset(rdata, 0, rdata_len); - mutex_lock(&ec->lock); if (ec->global_lock) { status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk); if (ACPI_FAILURE(status)) { - mutex_unlock(&ec->lock); - return -ENODEV; + status = -ENODEV; + goto unlock; } } - - status = acpi_ec_wait(ec, ACPI_EC_EVENT_IBF_0, 0); - if (status) { + if (!wait_event_timeout(ec->wait, ec_check_ibf0(ec), + msecs_to_jiffies(ACPI_EC_DELAY))) { pr_err(PREFIX "input buffer is not empty, " "aborting transaction\n"); + status = -ETIME; goto end; } - status = acpi_ec_transaction_unlocked(ec, command, wdata, wdata_len, rdata, rdata_len, force_poll); - - end: - +end: if (ec->global_lock) acpi_release_global_lock(glk); +unlock: mutex_unlock(&ec->lock); - return status; } @@ -332,7 +350,9 @@ int acpi_ec_burst_enable(struct acpi_ec *ec) int acpi_ec_burst_disable(struct acpi_ec *ec) { - return acpi_ec_transaction(ec, ACPI_EC_BURST_DISABLE, NULL, 0, NULL, 0, 0); + return (acpi_ec_read_status(ec) & ACPI_EC_FLAG_BURST) ? + acpi_ec_transaction(ec, ACPI_EC_BURST_DISABLE, + NULL, 0, NULL, 0, 0) : 0; } static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data) @@ -513,46 +533,26 @@ static void acpi_ec_gpe_query(void *ec_cxt) static u32 acpi_ec_gpe_handler(void *data) { - acpi_status status = AE_OK; struct acpi_ec *ec = data; - u8 state = acpi_ec_read_status(ec); + u8 status; pr_debug(PREFIX "~~~> interrupt\n"); - atomic_inc(&ec->irq_count); - if (atomic_read(&ec->irq_count) > 5) { - pr_err(PREFIX "GPE storm detected, disabling EC GPE\n"); - ec_switch_to_poll_mode(ec); - goto end; - } - clear_bit(EC_FLAGS_WAIT_GPE, &ec->flags); - if (test_bit(EC_FLAGS_GPE_MODE, &ec->flags)) + status = acpi_ec_read_status(ec); + + gpe_transaction(ec, status); + if (ec_transaction_done(ec) && (status & ACPI_EC_FLAG_IBF) == 0) wake_up(&ec->wait); - if (state & ACPI_EC_FLAG_SCI) { - if (!test_and_set_bit(EC_FLAGS_QUERY_PENDING, &ec->flags)) - status = acpi_os_execute(OSL_EC_BURST_HANDLER, - acpi_ec_gpe_query, ec); - } else if (!test_bit(EC_FLAGS_GPE_MODE, &ec->flags) && - !test_bit(EC_FLAGS_NO_GPE, &ec->flags) && - in_interrupt()) { + ec_check_sci(ec, status); + if (!test_bit(EC_FLAGS_GPE_MODE, &ec->flags) && + !test_bit(EC_FLAGS_NO_GPE, &ec->flags)) { /* this is non-query, must be confirmation */ if (printk_ratelimit()) pr_info(PREFIX "non-query interrupt received," " switching to interrupt mode\n"); set_bit(EC_FLAGS_GPE_MODE, &ec->flags); - clear_bit(EC_FLAGS_RESCHEDULE_POLL, &ec->flags); } -end: - ec_schedule_ec_poll(ec); - return ACPI_SUCCESS(status) ? - ACPI_INTERRUPT_HANDLED : ACPI_INTERRUPT_NOT_HANDLED; -} - -static void do_ec_poll(struct work_struct *work) -{ - struct acpi_ec *ec = container_of(work, struct acpi_ec, work.work); - atomic_set(&ec->irq_count, 0); - (void)acpi_ec_gpe_handler(ec); + return ACPI_INTERRUPT_HANDLED; } /* -------------------------------------------------------------------------- @@ -696,8 +696,7 @@ static struct acpi_ec *make_acpi_ec(void) mutex_init(&ec->lock); init_waitqueue_head(&ec->wait); INIT_LIST_HEAD(&ec->list); - INIT_DELAYED_WORK_DEFERRABLE(&ec->work, do_ec_poll); - atomic_set(&ec->irq_count, 0); + spin_lock_init(&ec->t_lock); return ec; } @@ -736,22 +735,15 @@ ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval) return AE_CTRL_TERMINATE; } -static void ec_poll_stop(struct acpi_ec *ec) -{ - clear_bit(EC_FLAGS_RESCHEDULE_POLL, &ec->flags); - cancel_delayed_work(&ec->work); -} - static void ec_remove_handlers(struct acpi_ec *ec) { - ec_poll_stop(ec); if (ACPI_FAILURE(acpi_remove_address_space_handler(ec->handle, ACPI_ADR_SPACE_EC, &acpi_ec_space_handler))) pr_err(PREFIX "failed to remove space handler\n"); if (ACPI_FAILURE(acpi_remove_gpe_handler(NULL, ec->gpe, &acpi_ec_gpe_handler))) pr_err(PREFIX "failed to remove gpe handler\n"); - ec->handlers_installed = 0; + clear_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags); } static int acpi_ec_add(struct acpi_device *device) @@ -846,17 +838,15 @@ ec_parse_io_ports(struct acpi_resource *resource, void *context) static int ec_install_handlers(struct acpi_ec *ec) { acpi_status status; - if (ec->handlers_installed) + if (test_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags)) return 0; status = acpi_install_gpe_handler(NULL, ec->gpe, - ACPI_GPE_EDGE_TRIGGERED, - &acpi_ec_gpe_handler, ec); + ACPI_GPE_EDGE_TRIGGERED, + &acpi_ec_gpe_handler, ec); if (ACPI_FAILURE(status)) return -ENODEV; - acpi_set_gpe_type(NULL, ec->gpe, ACPI_GPE_TYPE_RUNTIME); acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); - status = acpi_install_address_space_handler(ec->handle, ACPI_ADR_SPACE_EC, &acpi_ec_space_handler, @@ -866,7 +856,7 @@ static int ec_install_handlers(struct acpi_ec *ec) return -ENODEV; } - ec->handlers_installed = 1; + set_bit(EC_FLAGS_HANDLERS_INSTALLED, &ec->flags); return 0; } @@ -887,7 +877,6 @@ static int acpi_ec_start(struct acpi_device *device) /* EC is fully operational, allow queries */ clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); - ec_schedule_ec_poll(ec); return ret; } @@ -906,7 +895,7 @@ static int acpi_ec_stop(struct acpi_device *device, int type) int __init acpi_boot_ec_enable(void) { - if (!boot_ec || boot_ec->handlers_installed) + if (!boot_ec || test_bit(EC_FLAGS_HANDLERS_INSTALLED, &boot_ec->flags)) return 0; if (!ec_install_handlers(boot_ec)) { first_ec = boot_ec; -- cgit v1.2.3 From 8463200a00fe2aea938b40173198a0983f2929ef Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Fri, 26 Sep 2008 00:54:28 +0400 Subject: ACPI: EC: Rename some variables No functional changes. Signed-off-by: Alexey Starikovskiy Acked-by: Rafael J. Wysocki Signed-off-by: Len Brown --- drivers/acpi/ec.c | 118 +++++++++++++++++++++++++++++------------------------- 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 7f0d81c0ec9..453ba1e534e 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -95,10 +95,11 @@ struct acpi_ec_query_handler { u8 query_bit; }; -struct transaction_data { +struct transaction { const u8 *wdata; u8 *rdata; unsigned short irq_count; + u8 command; u8 wlen; u8 rlen; }; @@ -113,8 +114,8 @@ static struct acpi_ec { struct mutex lock; wait_queue_head_t wait; struct list_head list; - struct transaction_data *t; - spinlock_t t_lock; + struct transaction *curr; + spinlock_t curr_lock; } *boot_ec, *first_ec; /* @@ -176,37 +177,37 @@ static int ec_transaction_done(struct acpi_ec *ec) { unsigned long flags; int ret = 0; - spin_lock_irqsave(&ec->t_lock, flags); - if (!ec->t || (!ec->t->wlen && !ec->t->rlen)) + spin_lock_irqsave(&ec->curr_lock, flags); + if (!ec->curr || (!ec->curr->wlen && !ec->curr->rlen)) ret = 1; - spin_unlock_irqrestore(&ec->t_lock, flags); + spin_unlock_irqrestore(&ec->curr_lock, flags); return ret; } static void gpe_transaction(struct acpi_ec *ec, u8 status) { unsigned long flags; - spin_lock_irqsave(&ec->t_lock, flags); - if (!ec->t) + spin_lock_irqsave(&ec->curr_lock, flags); + if (!ec->curr) goto unlock; - if (ec->t->wlen > 0) { + if (ec->curr->wlen > 0) { if ((status & ACPI_EC_FLAG_IBF) == 0) { - acpi_ec_write_data(ec, *(ec->t->wdata++)); - --ec->t->wlen; + acpi_ec_write_data(ec, *(ec->curr->wdata++)); + --ec->curr->wlen; } else /* false interrupt, state didn't change */ - ++ec->t->irq_count; + ++ec->curr->irq_count; - } else if (ec->t->rlen > 0) { + } else if (ec->curr->rlen > 0) { if ((status & ACPI_EC_FLAG_OBF) == 1) { - *(ec->t->rdata++) = acpi_ec_read_data(ec); - --ec->t->rlen; + *(ec->curr->rdata++) = acpi_ec_read_data(ec); + --ec->curr->rlen; } else /* false interrupt, state didn't change */ - ++ec->t->irq_count; + ++ec->curr->irq_count; } unlock: - spin_unlock_irqrestore(&ec->t_lock, flags); + spin_unlock_irqrestore(&ec->curr_lock, flags); } static int acpi_ec_wait(struct acpi_ec *ec) @@ -248,15 +249,11 @@ static int ec_poll(struct acpi_ec *ec) return -ETIME; } -static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, - const u8 * wdata, unsigned wdata_len, - u8 * rdata, unsigned rdata_len, +static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, + struct transaction *t, int force_poll) { unsigned long tmp; - struct transaction_data t = {.wdata = wdata, .rdata = rdata, - .wlen = wdata_len, .rlen = rdata_len, - .irq_count = 0}; int ret = 0; pr_debug(PREFIX "transaction start\n"); /* disable GPE during transaction if storm is detected */ @@ -265,29 +262,30 @@ static int acpi_ec_transaction_unlocked(struct acpi_ec *ec, u8 command, acpi_disable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); } /* start transaction */ - spin_lock_irqsave(&ec->t_lock, tmp); + spin_lock_irqsave(&ec->curr_lock, tmp); /* following two actions should be kept atomic */ - ec->t = &t; - acpi_ec_write_cmd(ec, command); - if (command == ACPI_EC_COMMAND_QUERY) + t->irq_count = 0; + ec->curr = t; + acpi_ec_write_cmd(ec, ec->curr->command); + if (ec->curr->command == ACPI_EC_COMMAND_QUERY) clear_bit(EC_FLAGS_QUERY_PENDING, &ec->flags); - spin_unlock_irqrestore(&ec->t_lock, tmp); + spin_unlock_irqrestore(&ec->curr_lock, tmp); /* if we selected poll mode or failed in GPE-mode do a poll loop */ if (force_poll || !test_bit(EC_FLAGS_GPE_MODE, &ec->flags) || acpi_ec_wait(ec)) ret = ec_poll(ec); pr_debug(PREFIX "transaction end\n"); - spin_lock_irqsave(&ec->t_lock, tmp); - ec->t = NULL; - spin_unlock_irqrestore(&ec->t_lock, tmp); + spin_lock_irqsave(&ec->curr_lock, tmp); + ec->curr = NULL; + spin_unlock_irqrestore(&ec->curr_lock, tmp); if (test_bit(EC_FLAGS_GPE_STORM, &ec->flags)) { /* check if we received SCI during transaction */ ec_check_sci(ec, acpi_ec_read_status(ec)); /* it is safe to enable GPE outside of transaction */ acpi_enable_gpe(NULL, ec->gpe, ACPI_NOT_ISR); } else if (test_bit(EC_FLAGS_GPE_MODE, &ec->flags) && - t.irq_count > ACPI_EC_STORM_THRESHOLD) { + t->irq_count > ACPI_EC_STORM_THRESHOLD) { pr_debug(PREFIX "GPE storm detected\n"); set_bit(EC_FLAGS_GPE_STORM, &ec->flags); } @@ -300,17 +298,15 @@ static int ec_check_ibf0(struct acpi_ec *ec) return (status & ACPI_EC_FLAG_IBF) == 0; } -static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, - const u8 * wdata, unsigned wdata_len, - u8 * rdata, unsigned rdata_len, +static int acpi_ec_transaction(struct acpi_ec *ec, struct transaction *t, int force_poll) { int status; u32 glk; - if (!ec || (wdata_len && !wdata) || (rdata_len && !rdata)) + if (!ec || (!t) || (t->wlen && !t->wdata) || (t->rlen && !t->rdata)) return -EINVAL; - if (rdata) - memset(rdata, 0, rdata_len); + if (t->rdata) + memset(t->rdata, 0, t->rlen); mutex_lock(&ec->lock); if (ec->global_lock) { status = acpi_acquire_global_lock(ACPI_EC_UDELAY_GLK, &glk); @@ -326,10 +322,7 @@ static int acpi_ec_transaction(struct acpi_ec *ec, u8 command, status = -ETIME; goto end; } - status = acpi_ec_transaction_unlocked(ec, command, - wdata, wdata_len, - rdata, rdata_len, - force_poll); + status = acpi_ec_transaction_unlocked(ec, t, force_poll); end: if (ec->global_lock) acpi_release_global_lock(glk); @@ -345,23 +338,32 @@ unlock: int acpi_ec_burst_enable(struct acpi_ec *ec) { u8 d; - return acpi_ec_transaction(ec, ACPI_EC_BURST_ENABLE, NULL, 0, &d, 1, 0); + struct transaction t = {.command = ACPI_EC_BURST_ENABLE, + .wdata = NULL, .rdata = &d, + .wlen = 0, .rlen = 1}; + + return acpi_ec_transaction(ec, &t, 0); } int acpi_ec_burst_disable(struct acpi_ec *ec) { + struct transaction t = {.command = ACPI_EC_BURST_DISABLE, + .wdata = NULL, .rdata = NULL, + .wlen = 0, .rlen = 0}; + return (acpi_ec_read_status(ec) & ACPI_EC_FLAG_BURST) ? - acpi_ec_transaction(ec, ACPI_EC_BURST_DISABLE, - NULL, 0, NULL, 0, 0) : 0; + acpi_ec_transaction(ec, &t, 0) : 0; } static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data) { int result; u8 d; + struct transaction t = {.command = ACPI_EC_COMMAND_READ, + .wdata = &address, .rdata = &d, + .wlen = 1, .rlen = 1}; - result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_READ, - &address, 1, &d, 1, 0); + result = acpi_ec_transaction(ec, &t, 0); *data = d; return result; } @@ -369,8 +371,11 @@ static int acpi_ec_read(struct acpi_ec *ec, u8 address, u8 * data) static int acpi_ec_write(struct acpi_ec *ec, u8 address, u8 data) { u8 wdata[2] = { address, data }; - return acpi_ec_transaction(ec, ACPI_EC_COMMAND_WRITE, - wdata, 2, NULL, 0, 0); + struct transaction t = {.command = ACPI_EC_COMMAND_WRITE, + .wdata = wdata, .rdata = NULL, + .wlen = 2, .rlen = 0}; + + return acpi_ec_transaction(ec, &t, 0); } /* @@ -432,12 +437,13 @@ int ec_transaction(u8 command, u8 * rdata, unsigned rdata_len, int force_poll) { + struct transaction t = {.command = command, + .wdata = wdata, .rdata = rdata, + .wlen = wdata_len, .rlen = rdata_len}; if (!first_ec) return -ENODEV; - return acpi_ec_transaction(first_ec, command, wdata, - wdata_len, rdata, rdata_len, - force_poll); + return acpi_ec_transaction(first_ec, &t, force_poll); } EXPORT_SYMBOL(ec_transaction); @@ -446,7 +452,9 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 * data) { int result; u8 d; - + struct transaction t = {.command = ACPI_EC_COMMAND_QUERY, + .wdata = NULL, .rdata = &d, + .wlen = 0, .rlen = 1}; if (!ec || !data) return -EINVAL; @@ -456,7 +464,7 @@ static int acpi_ec_query(struct acpi_ec *ec, u8 * data) * bit to be cleared (and thus clearing the interrupt source). */ - result = acpi_ec_transaction(ec, ACPI_EC_COMMAND_QUERY, NULL, 0, &d, 1, 0); + result = acpi_ec_transaction(ec, &t, 0); if (result) return result; @@ -696,7 +704,7 @@ static struct acpi_ec *make_acpi_ec(void) mutex_init(&ec->lock); init_waitqueue_head(&ec->wait); INIT_LIST_HEAD(&ec->list); - spin_lock_init(&ec->t_lock); + spin_lock_init(&ec->curr_lock); return ec; } -- cgit v1.2.3 From cfb361f13c8136de78c406745abc4e4456e6d480 Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 18 Sep 2008 15:49:14 +0800 Subject: [IA64] utrace syscall.h support for ia64 Add asm/syscall.h for IA64. Utrace requires this. Signed-off-by: Shaohua Li Signed-off-by: Tony Luck --- arch/ia64/include/asm/ptrace.h | 6 +++ arch/ia64/include/asm/syscall.h | 92 +++++++++++++++++++++++++++++++++++++++++ arch/ia64/kernel/ptrace.c | 65 +++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 arch/ia64/include/asm/syscall.h diff --git a/arch/ia64/include/asm/ptrace.h b/arch/ia64/include/asm/ptrace.h index 15f8dcfe6ee..14055c636ad 100644 --- a/arch/ia64/include/asm/ptrace.h +++ b/arch/ia64/include/asm/ptrace.h @@ -240,6 +240,12 @@ struct switch_stack { */ # define instruction_pointer(regs) ((regs)->cr_iip + ia64_psr(regs)->ri) +static inline unsigned long user_stack_pointer(struct pt_regs *regs) +{ + /* FIXME: should this be bspstore + nr_dirty regs? */ + return regs->ar_bspstore; +} + #define regs_return_value(regs) ((regs)->r8) /* Conserve space in histogram by encoding slot bits in address diff --git a/arch/ia64/include/asm/syscall.h b/arch/ia64/include/asm/syscall.h new file mode 100644 index 00000000000..3fd4fa6c48d --- /dev/null +++ b/arch/ia64/include/asm/syscall.h @@ -0,0 +1,92 @@ +/* + * Access to user system call parameters and results + * + * Copyright (C) 2008 Intel Corp. Shaohua Li + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License v.2. + * + * See asm-generic/syscall.h for descriptions of what we must do here. + */ + +#ifndef _ASM_SYSCALL_H +#define _ASM_SYSCALL_H 1 + +#include +#include + +static inline long syscall_get_nr(struct task_struct *task, + struct pt_regs *regs) +{ + BUG_ON(IS_IA32_PROCESS(regs)); + + if ((long)regs->cr_ifs < 0) /* Not a syscall */ + return -1; + return regs->r15; +} + +static inline void syscall_rollback(struct task_struct *task, + struct pt_regs *regs) +{ + BUG_ON(IS_IA32_PROCESS(regs)); + /* do nothing */ +} + +static inline long syscall_get_error(struct task_struct *task, + struct pt_regs *regs) +{ + BUG_ON(IS_IA32_PROCESS(regs)); + + return regs->r10 == -1 ? regs->r8:0; +} + +static inline long syscall_get_return_value(struct task_struct *task, + struct pt_regs *regs) +{ + BUG_ON(IS_IA32_PROCESS(regs)); + + return regs->r8; +} + +static inline void syscall_set_return_value(struct task_struct *task, + struct pt_regs *regs, + int error, long val) +{ + BUG_ON(IS_IA32_PROCESS(regs)); + + if (error) { + /* error < 0, but ia64 uses > 0 return value */ + regs->r8 = -error; + regs->r10 = -1; + } else { + regs->r8 = val; + regs->r10 = 0; + } +} + +extern void ia64_syscall_get_set_arguments(struct task_struct *task, + struct pt_regs *regs, unsigned int i, unsigned int n, + unsigned long *args, int rw); +static inline void syscall_get_arguments(struct task_struct *task, + struct pt_regs *regs, + unsigned int i, unsigned int n, + unsigned long *args) +{ + BUG_ON(IS_IA32_PROCESS(regs)); + BUG_ON(i + n > 6); + + ia64_syscall_get_set_arguments(task, regs, i, n, args, 0); +} + +static inline void syscall_set_arguments(struct task_struct *task, + struct pt_regs *regs, + unsigned int i, unsigned int n, + unsigned long *args) +{ + BUG_ON(IS_IA32_PROCESS(regs)); + BUG_ON(i + n > 6); + + ia64_syscall_get_set_arguments(task, regs, i, n, args, 1); +} +#endif /* _ASM_SYSCALL_H */ diff --git a/arch/ia64/kernel/ptrace.c b/arch/ia64/kernel/ptrace.c index 2a9943b5947..12b1e9f0b7a 100644 --- a/arch/ia64/kernel/ptrace.c +++ b/arch/ia64/kernel/ptrace.c @@ -2199,3 +2199,68 @@ const struct user_regset_view *task_user_regset_view(struct task_struct *tsk) #endif return &user_ia64_view; } + +struct syscall_get_set_args { + unsigned int i; + unsigned int n; + unsigned long *args; + struct pt_regs *regs; + int rw; +}; + +static void syscall_get_set_args_cb(struct unw_frame_info *info, void *data) +{ + struct syscall_get_set_args *args = data; + struct pt_regs *pt = args->regs; + unsigned long *krbs, cfm, ndirty; + int i, count; + + if (unw_unwind_to_user(info) < 0) + return; + + cfm = pt->cr_ifs; + krbs = (unsigned long *)info->task + IA64_RBS_OFFSET/8; + ndirty = ia64_rse_num_regs(krbs, krbs + (pt->loadrs >> 19)); + + count = 0; + if (in_syscall(pt)) + count = min_t(int, args->n, cfm & 0x7f); + + for (i = 0; i < count; i++) { + if (args->rw) + *ia64_rse_skip_regs(krbs, ndirty + i + args->i) = + args->args[i]; + else + args->args[i] = *ia64_rse_skip_regs(krbs, + ndirty + i + args->i); + } + + if (!args->rw) { + while (i < args->n) { + args->args[i] = 0; + i++; + } + } +} + +void ia64_syscall_get_set_arguments(struct task_struct *task, + struct pt_regs *regs, unsigned int i, unsigned int n, + unsigned long *args, int rw) +{ + struct syscall_get_set_args data = { + .i = i, + .n = n, + .args = args, + .regs = regs, + .rw = rw, + }; + + if (task == current) + unw_init_running(syscall_get_set_args_cb, &data); + else { + struct unw_frame_info ufi; + memset(&ufi, 0, sizeof(ufi)); + unw_init_from_blocked_task(&ufi, task); + syscall_get_set_args_cb(&ufi, &data); + } +} -- cgit v1.2.3 From f14488ccfe0f41207e40520fab60dce356ed9e57 Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Mon, 6 Oct 2008 10:43:06 -0700 Subject: [IA64] utrace use generic trace hook Make IA64 use generic trace hook in some paths. Signed-off-by: Shaohua Li Signed-off-by: Tony Luck --- arch/ia64/ia32/ia32_entry.S | 5 ++++ arch/ia64/include/asm/thread_info.h | 3 --- arch/ia64/kernel/entry.S | 5 ++++ arch/ia64/kernel/perfmon.c | 7 +++--- arch/ia64/kernel/process.c | 21 +++++------------ arch/ia64/kernel/ptrace.c | 47 +++++++++++-------------------------- arch/ia64/kernel/signal.c | 8 +++++++ 7 files changed, 41 insertions(+), 55 deletions(-) diff --git a/arch/ia64/ia32/ia32_entry.S b/arch/ia64/ia32/ia32_entry.S index ff88c48c5d1..b905dcb791f 100644 --- a/arch/ia64/ia32/ia32_entry.S +++ b/arch/ia64/ia32/ia32_entry.S @@ -108,6 +108,11 @@ GLOBAL_ENTRY(ia32_trace_syscall) ;; st8 [r2]=r3 // initialize return code to -ENOSYS br.call.sptk.few rp=syscall_trace_enter // give parent a chance to catch syscall args + cmp.lt p6,p0=r8,r0 // check tracehook + adds r2=IA64_PT_REGS_R8_OFFSET+16,sp // r2 = &pt_regs.r8 + ;; +(p6) st8.spill [r2]=r8 // store return value in slot for r8 +(p6) br.spnt.few .ret4 .ret2: // Need to reload arguments (they may be changed by the tracing process) adds r2=IA64_PT_REGS_R1_OFFSET+16,sp // r2 = &pt_regs.r1 adds r3=IA64_PT_REGS_R13_OFFSET+16,sp // r3 = &pt_regs.r13 diff --git a/arch/ia64/include/asm/thread_info.h b/arch/ia64/include/asm/thread_info.h index 7c60fcdd2ef..ae6922626bf 100644 --- a/arch/ia64/include/asm/thread_info.h +++ b/arch/ia64/include/asm/thread_info.h @@ -87,9 +87,6 @@ struct thread_info { #define alloc_task_struct() ((struct task_struct *)__get_free_pages(GFP_KERNEL | __GFP_COMP, KERNEL_STACK_SIZE_ORDER)) #define free_task_struct(tsk) free_pages((unsigned long) (tsk), KERNEL_STACK_SIZE_ORDER) -#define tsk_set_notify_resume(tsk) \ - set_ti_thread_flag(task_thread_info(tsk), TIF_NOTIFY_RESUME) -extern void tsk_clear_notify_resume(struct task_struct *tsk); #endif /* !__ASSEMBLY */ /* diff --git a/arch/ia64/kernel/entry.S b/arch/ia64/kernel/entry.S index 0dd6c1419d8..7ef0c594f5e 100644 --- a/arch/ia64/kernel/entry.S +++ b/arch/ia64/kernel/entry.S @@ -534,6 +534,11 @@ GLOBAL_ENTRY(ia64_trace_syscall) stf.spill [r16]=f10 stf.spill [r17]=f11 br.call.sptk.many rp=syscall_trace_enter // give parent a chance to catch syscall args + cmp.lt p6,p0=r8,r0 // check tracehook + adds r2=PT(R8)+16,sp // r2 = &pt_regs.r8 + adds r3=PT(R10)+16,sp // r3 = &pt_regs.r10 + mov r10=0 +(p6) br.cond.sptk strace_error // syscall failed -> adds r16=PT(F6)+16,sp adds r17=PT(F7)+16,sp ;; diff --git a/arch/ia64/kernel/perfmon.c b/arch/ia64/kernel/perfmon.c index fc8f3509df2..ada4605d122 100644 --- a/arch/ia64/kernel/perfmon.c +++ b/arch/ia64/kernel/perfmon.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -3684,7 +3685,7 @@ pfm_restart(pfm_context_t *ctx, void *arg, int count, struct pt_regs *regs) PFM_SET_WORK_PENDING(task, 1); - tsk_set_notify_resume(task); + set_notify_resume(task); /* * XXX: send reschedule if task runs on another CPU @@ -5044,8 +5045,6 @@ pfm_handle_work(void) PFM_SET_WORK_PENDING(current, 0); - tsk_clear_notify_resume(current); - regs = task_pt_regs(current); /* @@ -5414,7 +5413,7 @@ pfm_overflow_handler(struct task_struct *task, pfm_context_t *ctx, u64 pmc0, str * when coming from ctxsw, current still points to the * previous task, therefore we must work with task and not current. */ - tsk_set_notify_resume(task); + set_notify_resume(task); } /* * defer until state is changed (shorten spin window). the context is locked diff --git a/arch/ia64/kernel/process.c b/arch/ia64/kernel/process.c index 3ab8373103e..341a0319a5b 100644 --- a/arch/ia64/kernel/process.c +++ b/arch/ia64/kernel/process.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -160,21 +161,6 @@ show_regs (struct pt_regs *regs) show_stack(NULL, NULL); } -void tsk_clear_notify_resume(struct task_struct *tsk) -{ -#ifdef CONFIG_PERFMON - if (tsk->thread.pfm_needs_checking) - return; -#endif - if (test_ti_thread_flag(task_thread_info(tsk), TIF_RESTORE_RSE)) - return; - clear_ti_thread_flag(task_thread_info(tsk), TIF_NOTIFY_RESUME); -} - -/* - * do_notify_resume_user(): - * Called from notify_resume_user at entry.S, with interrupts disabled. - */ void do_notify_resume_user(sigset_t *unused, struct sigscratch *scr, long in_syscall) { @@ -203,6 +189,11 @@ do_notify_resume_user(sigset_t *unused, struct sigscratch *scr, long in_syscall) ia64_do_signal(scr, in_syscall); } + if (test_thread_flag(TIF_NOTIFY_RESUME)) { + clear_thread_flag(TIF_NOTIFY_RESUME); + tracehook_notify_resume(&scr->pt); + } + /* copy user rbs to kernel rbs */ if (unlikely(test_thread_flag(TIF_RESTORE_RSE))) { local_irq_enable(); /* force interrupt enable */ diff --git a/arch/ia64/kernel/ptrace.c b/arch/ia64/kernel/ptrace.c index 12b1e9f0b7a..92c9689b7d9 100644 --- a/arch/ia64/kernel/ptrace.c +++ b/arch/ia64/kernel/ptrace.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -603,7 +604,7 @@ void ia64_ptrace_stop(void) { if (test_and_set_tsk_thread_flag(current, TIF_RESTORE_RSE)) return; - tsk_set_notify_resume(current); + set_notify_resume(current); unw_init_running(do_sync_rbs, ia64_sync_user_rbs); } @@ -613,7 +614,6 @@ void ia64_ptrace_stop(void) void ia64_sync_krbs(void) { clear_tsk_thread_flag(current, TIF_RESTORE_RSE); - tsk_clear_notify_resume(current); unw_init_running(do_sync_rbs, ia64_sync_kernel_rbs); } @@ -644,7 +644,7 @@ ptrace_attach_sync_user_rbs (struct task_struct *child) spin_lock_irq(&child->sighand->siglock); if (child->state == TASK_STOPPED && !test_and_set_tsk_thread_flag(child, TIF_RESTORE_RSE)) { - tsk_set_notify_resume(child); + set_notify_resume(child); child->state = TASK_TRACED; stopped = 1; @@ -1232,37 +1232,16 @@ arch_ptrace (struct task_struct *child, long request, long addr, long data) } -static void -syscall_trace (void) -{ - /* - * The 0x80 provides a way for the tracing parent to - * distinguish between a syscall stop and SIGTRAP delivery. - */ - ptrace_notify(SIGTRAP - | ((current->ptrace & PT_TRACESYSGOOD) ? 0x80 : 0)); - - /* - * This isn't the same as continuing with a signal, but it - * will do for normal use. strace only continues with a - * signal if the stopping signal is not SIGTRAP. -brl - */ - if (current->exit_code) { - send_sig(current->exit_code, current, 1); - current->exit_code = 0; - } -} - /* "asmlinkage" so the input arguments are preserved... */ -asmlinkage void +asmlinkage long syscall_trace_enter (long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6, long arg7, struct pt_regs regs) { - if (test_thread_flag(TIF_SYSCALL_TRACE) - && (current->ptrace & PT_PTRACED)) - syscall_trace(); + if (test_thread_flag(TIF_SYSCALL_TRACE)) + if (tracehook_report_syscall_entry(®s)) + return -ENOSYS; /* copy user rbs to kernel rbs */ if (test_thread_flag(TIF_RESTORE_RSE)) @@ -1283,6 +1262,7 @@ syscall_trace_enter (long arg0, long arg1, long arg2, long arg3, audit_syscall_entry(arch, syscall, arg0, arg1, arg2, arg3); } + return 0; } /* "asmlinkage" so the input arguments are preserved... */ @@ -1292,6 +1272,8 @@ syscall_trace_leave (long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6, long arg7, struct pt_regs regs) { + int step; + if (unlikely(current->audit_context)) { int success = AUDITSC_RESULT(regs.r10); long result = regs.r8; @@ -1301,10 +1283,9 @@ syscall_trace_leave (long arg0, long arg1, long arg2, long arg3, audit_syscall_exit(success, result); } - if ((test_thread_flag(TIF_SYSCALL_TRACE) - || test_thread_flag(TIF_SINGLESTEP)) - && (current->ptrace & PT_PTRACED)) - syscall_trace(); + step = test_thread_flag(TIF_SINGLESTEP); + if (step || test_thread_flag(TIF_SYSCALL_TRACE)) + tracehook_report_syscall_exit(®s, step); /* copy user rbs to kernel rbs */ if (test_thread_flag(TIF_RESTORE_RSE)) @@ -1940,7 +1921,7 @@ gpregs_writeback(struct task_struct *target, { if (test_and_set_tsk_thread_flag(target, TIF_RESTORE_RSE)) return 0; - tsk_set_notify_resume(target); + set_notify_resume(target); return do_regset_call(do_gpregs_writeback, target, regset, 0, 0, NULL, NULL); } diff --git a/arch/ia64/kernel/signal.c b/arch/ia64/kernel/signal.c index 19c5a78636f..e12500a9c44 100644 --- a/arch/ia64/kernel/signal.c +++ b/arch/ia64/kernel/signal.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -439,6 +440,13 @@ handle_signal (unsigned long sig, struct k_sigaction *ka, siginfo_t *info, sigse sigaddset(¤t->blocked, sig); recalc_sigpending(); spin_unlock_irq(¤t->sighand->siglock); + + /* + * Let tracing know that we've done the handler setup. + */ + tracehook_signal_handler(sig, info, ka, &scr->pt, + test_thread_flag(TIF_SINGLESTEP)); + return 1; } -- cgit v1.2.3 From 9690ad031290d86979b284bd6243313f58271bcc Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Wed, 1 Oct 2008 13:57:14 -0700 Subject: [IA64] utrace Enable trace hook config Signed-off-by: Shaohua Li Signed-off-by: Tony Luck --- arch/ia64/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/ia64/Kconfig b/arch/ia64/Kconfig index 48e496fe1e7..a36b014ee66 100644 --- a/arch/ia64/Kconfig +++ b/arch/ia64/Kconfig @@ -21,6 +21,7 @@ config IA64 select HAVE_KRETPROBES select HAVE_DMA_ATTRS select HAVE_KVM + select HAVE_ARCH_TRACEHOOK default y help The Itanium Processor Family is Intel's 64-bit successor to -- cgit v1.2.3 From 680973edf122fd95735ecfc077cf79645d2e5081 Mon Sep 17 00:00:00 2001 From: Shaohua Li Date: Thu, 18 Sep 2008 15:50:26 +0800 Subject: [IA64] utrace Convert compat ptrace to use compat_sys_ptrace Convert IA64 32-bit ptrace to use compat_sys_ptrace. Signed-off-by: Shaohua Li Signed-off-by: Tony Luck --- arch/ia64/ia32/ia32_entry.S | 2 +- arch/ia64/ia32/sys_ia32.c | 83 +++----------------------------------- arch/ia64/include/asm/ptrace.h | 2 + arch/ia64/include/asm/syscall.h | 89 ++++++++++++++++++++++++++++++++++++----- 4 files changed, 89 insertions(+), 87 deletions(-) diff --git a/arch/ia64/ia32/ia32_entry.S b/arch/ia64/ia32/ia32_entry.S index b905dcb791f..a73ec0089d4 100644 --- a/arch/ia64/ia32/ia32_entry.S +++ b/arch/ia64/ia32/ia32_entry.S @@ -204,7 +204,7 @@ ia32_syscall_table: data8 sys_setuid /* 16-bit version */ data8 sys_getuid /* 16-bit version */ data8 compat_sys_stime /* 25 */ - data8 sys32_ptrace + data8 compat_sys_ptrace data8 sys32_alarm data8 sys_ni_syscall data8 sys32_pause diff --git a/arch/ia64/ia32/sys_ia32.c b/arch/ia64/ia32/sys_ia32.c index bf196cbb379..5df5e4c90e4 100644 --- a/arch/ia64/ia32/sys_ia32.c +++ b/arch/ia64/ia32/sys_ia32.c @@ -1300,25 +1300,6 @@ sys32_waitpid (int pid, unsigned int *stat_addr, int options) return compat_sys_wait4(pid, stat_addr, options, NULL); } -static unsigned int -ia32_peek (struct task_struct *child, unsigned long addr, unsigned int *val) -{ - size_t copied; - unsigned int ret; - - copied = access_process_vm(child, addr, val, sizeof(*val), 0); - return (copied != sizeof(ret)) ? -EIO : 0; -} - -static unsigned int -ia32_poke (struct task_struct *child, unsigned long addr, unsigned int val) -{ - - if (access_process_vm(child, addr, &val, sizeof(val), 1) != sizeof(val)) - return -EIO; - return 0; -} - /* * The order in which registers are stored in the ptrace regs structure */ @@ -1616,49 +1597,15 @@ restore_ia32_fpxstate (struct task_struct *tsk, struct ia32_user_fxsr_struct __u return 0; } -asmlinkage long -sys32_ptrace (int request, pid_t pid, unsigned int addr, unsigned int data) +long compat_arch_ptrace(struct task_struct *child, compat_long_t request, + compat_ulong_t caddr, compat_ulong_t cdata) { - struct task_struct *child; - unsigned int value, tmp; + unsigned long addr = caddr; + unsigned long data = cdata; + unsigned int tmp; long i, ret; - lock_kernel(); - if (request == PTRACE_TRACEME) { - ret = ptrace_traceme(); - goto out; - } - - child = ptrace_get_task_struct(pid); - if (IS_ERR(child)) { - ret = PTR_ERR(child); - goto out; - } - - if (request == PTRACE_ATTACH) { - ret = sys_ptrace(request, pid, addr, data); - goto out_tsk; - } - - ret = ptrace_check_attach(child, request == PTRACE_KILL); - if (ret < 0) - goto out_tsk; - switch (request) { - case PTRACE_PEEKTEXT: - case PTRACE_PEEKDATA: /* read word at location addr */ - ret = ia32_peek(child, addr, &value); - if (ret == 0) - ret = put_user(value, (unsigned int __user *) compat_ptr(data)); - else - ret = -EIO; - goto out_tsk; - - case PTRACE_POKETEXT: - case PTRACE_POKEDATA: /* write the word at location addr */ - ret = ia32_poke(child, addr, data); - goto out_tsk; - case PTRACE_PEEKUSR: /* read word at addr in USER area */ ret = -EIO; if ((addr & 3) || addr > 17*sizeof(int)) @@ -1723,27 +1670,9 @@ sys32_ptrace (int request, pid_t pid, unsigned int addr, unsigned int data) compat_ptr(data)); break; - case PTRACE_GETEVENTMSG: - ret = put_user(child->ptrace_message, (unsigned int __user *) compat_ptr(data)); - break; - - case PTRACE_SYSCALL: /* continue, stop after next syscall */ - case PTRACE_CONT: /* restart after signal. */ - case PTRACE_KILL: - case PTRACE_SINGLESTEP: /* execute chile for one instruction */ - case PTRACE_DETACH: /* detach a process */ - ret = sys_ptrace(request, pid, addr, data); - break; - default: - ret = ptrace_request(child, request, addr, data); - break; - + return compat_ptrace_request(child, request, caddr, cdata); } - out_tsk: - put_task_struct(child); - out: - unlock_kernel(); return ret; } diff --git a/arch/ia64/include/asm/ptrace.h b/arch/ia64/include/asm/ptrace.h index 14055c636ad..6417c1ecb44 100644 --- a/arch/ia64/include/asm/ptrace.h +++ b/arch/ia64/include/asm/ptrace.h @@ -325,6 +325,8 @@ static inline unsigned long user_stack_pointer(struct pt_regs *regs) #define arch_has_block_step() (1) extern void user_enable_block_step(struct task_struct *); +#define __ARCH_WANT_COMPAT_SYS_PTRACE + #endif /* !__KERNEL__ */ /* pt_all_user_regs is used for PTRACE_GETREGS PTRACE_SETREGS */ diff --git a/arch/ia64/include/asm/syscall.h b/arch/ia64/include/asm/syscall.h index 3fd4fa6c48d..2f758a42f94 100644 --- a/arch/ia64/include/asm/syscall.h +++ b/arch/ia64/include/asm/syscall.h @@ -19,24 +19,35 @@ static inline long syscall_get_nr(struct task_struct *task, struct pt_regs *regs) { - BUG_ON(IS_IA32_PROCESS(regs)); - if ((long)regs->cr_ifs < 0) /* Not a syscall */ return -1; + +#ifdef CONFIG_IA32_SUPPORT + if (IS_IA32_PROCESS(regs)) + return regs->r1; +#endif + return regs->r15; } static inline void syscall_rollback(struct task_struct *task, struct pt_regs *regs) { - BUG_ON(IS_IA32_PROCESS(regs)); +#ifdef CONFIG_IA32_SUPPORT + if (IS_IA32_PROCESS(regs)) + regs->r8 = regs->r1; +#endif + /* do nothing */ } static inline long syscall_get_error(struct task_struct *task, struct pt_regs *regs) { - BUG_ON(IS_IA32_PROCESS(regs)); +#ifdef CONFIG_IA32_SUPPORT + if (IS_IA32_PROCESS(regs)) + return regs->r8; +#endif return regs->r10 == -1 ? regs->r8:0; } @@ -44,8 +55,6 @@ static inline long syscall_get_error(struct task_struct *task, static inline long syscall_get_return_value(struct task_struct *task, struct pt_regs *regs) { - BUG_ON(IS_IA32_PROCESS(regs)); - return regs->r8; } @@ -53,7 +62,12 @@ static inline void syscall_set_return_value(struct task_struct *task, struct pt_regs *regs, int error, long val) { - BUG_ON(IS_IA32_PROCESS(regs)); +#ifdef CONFIG_IA32_SUPPORT + if (IS_IA32_PROCESS(regs)) { + regs->r8 = (long) error ? error : val; + return; + } +#endif if (error) { /* error < 0, but ia64 uses > 0 return value */ @@ -73,9 +87,39 @@ static inline void syscall_get_arguments(struct task_struct *task, unsigned int i, unsigned int n, unsigned long *args) { - BUG_ON(IS_IA32_PROCESS(regs)); BUG_ON(i + n > 6); +#ifdef CONFIG_IA32_SUPPORT + if (IS_IA32_PROCESS(regs)) { + switch (i + n) { + case 6: + if (!n--) break; + *args++ = regs->r13; + case 5: + if (!n--) break; + *args++ = regs->r15; + case 4: + if (!n--) break; + *args++ = regs->r14; + case 3: + if (!n--) break; + *args++ = regs->r10; + case 2: + if (!n--) break; + *args++ = regs->r9; + case 1: + if (!n--) break; + *args++ = regs->r11; + case 0: + if (!n--) break; + default: + BUG(); + break; + } + + return; + } +#endif ia64_syscall_get_set_arguments(task, regs, i, n, args, 0); } @@ -84,9 +128,36 @@ static inline void syscall_set_arguments(struct task_struct *task, unsigned int i, unsigned int n, unsigned long *args) { - BUG_ON(IS_IA32_PROCESS(regs)); BUG_ON(i + n > 6); +#ifdef CONFIG_IA32_SUPPORT + if (IS_IA32_PROCESS(regs)) { + switch (i + n) { + case 6: + if (!n--) break; + regs->r13 = *args++; + case 5: + if (!n--) break; + regs->r15 = *args++; + case 4: + if (!n--) break; + regs->r14 = *args++; + case 3: + if (!n--) break; + regs->r10 = *args++; + case 2: + if (!n--) break; + regs->r9 = *args++; + case 1: + if (!n--) break; + regs->r11 = *args++; + case 0: + if (!n--) break; + } + + return; + } +#endif ia64_syscall_get_set_arguments(task, regs, i, n, args, 1); } #endif /* _ASM_SYSCALL_H */ -- cgit v1.2.3 From 80a4b18d19bf1f7b88a261088c00a0d6b310a722 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 6 Oct 2008 13:01:53 -0700 Subject: select: fix alpha OSF wrapper ... alpha calls the core select code from inside it's architecture code for emulating OSF; this patch makes it compile again Signed-off-by: Arjan van de Ven --- arch/alpha/kernel/osf_sys.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/arch/alpha/kernel/osf_sys.c b/arch/alpha/kernel/osf_sys.c index 8509dad3120..8e19acbf288 100644 --- a/arch/alpha/kernel/osf_sys.c +++ b/arch/alpha/kernel/osf_sys.c @@ -986,10 +986,12 @@ asmlinkage int osf_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval32 __user *tvp) { - s64 timeout = MAX_SCHEDULE_TIMEOUT; + struct timespec end_time, *to = NULL; if (tvp) { time_t sec, usec; + to = &end_time; + if (!access_ok(VERIFY_READ, tvp, sizeof(*tvp)) || __get_user(sec, &tvp->tv_sec) || __get_user(usec, &tvp->tv_usec)) { @@ -999,14 +1001,13 @@ osf_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, if (sec < 0 || usec < 0) return -EINVAL; - if ((unsigned long) sec < MAX_SELECT_SECONDS) { - timeout = (usec + 1000000/HZ - 1) / (1000000/HZ); - timeout += sec * (unsigned long) HZ; - } + if (poll_select_set_timeout(to, sec, usec * NSEC_PER_USEC)) + return -EINVAL; + } /* OSF does not copy back the remaining time. */ - return core_sys_select(n, inp, outp, exp, &timeout); + return core_sys_select(n, inp, outp, exp, to); } struct rusage32 { -- cgit v1.2.3 From faee816b1502385dc9bc5abf2960d1cc645844d1 Mon Sep 17 00:00:00 2001 From: Zhang Rui Date: Fri, 12 Sep 2008 11:12:25 +0800 Subject: ACPI: don't enable control method power button as wakeup device when Fixed Power button is used don't enable control method power button as wakeup device when Fixed Power button is used. http://bugzilla.kernel.org/show_bug.cgi?id=10503 Tested-by: walken@zoy.org Signed-off-by: Zhang Rui Signed-off-by: Len Brown --- drivers/acpi/scan.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index f6f52c1a2ab..5ce14aee5e3 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -744,6 +744,16 @@ static int acpi_bus_get_wakeup_device_flags(struct acpi_device *device) if (!acpi_match_device_ids(device, button_device_ids)) device->wakeup.flags.run_wake = 1; + /* + * Don't set Power button GPE as run_wake + * if Fixed Power button is used + */ + if (!strcmp(device->pnp.hardware_id, "PNP0C0C") && + !(acpi_gbl_FADT.flags & ACPI_FADT_POWER_BUTTON)) { + device->wakeup.flags.run_wake = 0; + device->wakeup.flags.valid = 0; + } + end: if (ACPI_FAILURE(status)) device->flags.wake_capable = 0; -- cgit v1.2.3 From 14e2acd8686507f8dee6d6269760d9ed145b2a89 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 6 Oct 2008 13:01:53 -0700 Subject: select: fix alpha OSF wrapper ... alpha calls the core select code from inside it's architecture code for emulating OSF; this patch makes it compile again Signed-off-by: Arjan van de Ven Signed-off-by: Ingo Molnar --- arch/alpha/kernel/osf_sys.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/arch/alpha/kernel/osf_sys.c b/arch/alpha/kernel/osf_sys.c index 8509dad3120..8e19acbf288 100644 --- a/arch/alpha/kernel/osf_sys.c +++ b/arch/alpha/kernel/osf_sys.c @@ -986,10 +986,12 @@ asmlinkage int osf_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval32 __user *tvp) { - s64 timeout = MAX_SCHEDULE_TIMEOUT; + struct timespec end_time, *to = NULL; if (tvp) { time_t sec, usec; + to = &end_time; + if (!access_ok(VERIFY_READ, tvp, sizeof(*tvp)) || __get_user(sec, &tvp->tv_sec) || __get_user(usec, &tvp->tv_usec)) { @@ -999,14 +1001,13 @@ osf_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, if (sec < 0 || usec < 0) return -EINVAL; - if ((unsigned long) sec < MAX_SELECT_SECONDS) { - timeout = (usec + 1000000/HZ - 1) / (1000000/HZ); - timeout += sec * (unsigned long) HZ; - } + if (poll_select_set_timeout(to, sec, usec * NSEC_PER_USEC)) + return -EINVAL; + } /* OSF does not copy back the remaining time. */ - return core_sys_select(n, inp, outp, exp, &timeout); + return core_sys_select(n, inp, outp, exp, to); } struct rusage32 { -- cgit v1.2.3 From 2075eb8d95612cadde91ef5be82691d97a2ea6c5 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Tue, 7 Oct 2008 10:57:54 -0700 Subject: rangetimer: fix x86 build failure for the !HRTIMERS case the timer peek function was on the wrong side of an ifdef, breaking for the !HRTIMERs case. Just provide an empty inline for that case since it doesn't make sense in that scenario. Signed-off-by: Arjan van de Ven --- include/linux/hrtimer.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index d93b1e1dc16..508ce20b8f9 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -283,6 +283,8 @@ static inline int hrtimer_is_hres_active(struct hrtimer *timer) return timer->base->cpu_base->hres_active; } +extern void hrtimer_peek_ahead_timers(void); + /* * The resolution of the clocks. The resolution value is returned in * the clock_getres() system call to give application programmers an @@ -305,6 +307,7 @@ static inline int hrtimer_is_hres_active(struct hrtimer *timer) * is expired in the next softirq when the clock was advanced. */ static inline void clock_was_set(void) { } +static inline void hrtimer_peek_ahead_timers(void) { } static inline void hres_timers_resume(void) { } @@ -328,7 +331,6 @@ extern ktime_t ktime_get_real(void); DECLARE_PER_CPU(struct tick_device, tick_cpu_device); -extern void hrtimer_peek_ahead_timers(void); /* Exported timer functions: */ -- cgit v1.2.3 From 0606e1abfcb66c3a29722162f5d2c7ad8b638237 Mon Sep 17 00:00:00 2001 From: Carlos Corbacho Date: Wed, 8 Oct 2008 21:40:21 +0100 Subject: acer-wmi: Add rfkill support for wireless and bluetooth This patch implements rfkill support for the wireless and bluetooth devices commonly found on Acer laptops. For now, we will always poll these devices once a second to guarantee we can catch state changes. On newer Acer laptops, it may be possible to rely on WMI events to do this instead, and experimental support for this will be added in a later patch. 3G has been deliberately left off for now, as we still have no way to detect it, (nor, AFAIK, has any Linux user tried the code) and on laptops that don't support 3G, trying to poll for the status will leave the logs full of ACPI tracebacks. The old sysfs interface for wireless and bluetooth will be removed in a later patch. (Thanks to Henrique de Moraes Holschuh and Dmitry Torokhov for reviewing this patch). Signed-off-by: Carlos Corbacho Acked-by: Henrique de Moraes Holschuh Signed-off-by: Len Brown --- drivers/misc/Kconfig | 1 + drivers/misc/acer-wmi.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a726f3b01a6..6abb95919c3 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -145,6 +145,7 @@ config ACER_WMI depends on NEW_LEDS depends on BACKLIGHT_CLASS_DEVICE depends on SERIO_I8042 + depends on RFKILL select ACPI_WMI ---help--- This is a driver for newer Acer (and Wistron) laptops. It adds diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c index d8b0d326e45..e3e11e88747 100644 --- a/drivers/misc/acer-wmi.c +++ b/drivers/misc/acer-wmi.c @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -157,6 +159,9 @@ struct acer_debug { u32 wmid_devices; }; +static struct rfkill *wireless_rfkill; +static struct rfkill *bluetooth_rfkill; + /* Each low-level interface must define at least some of the following */ struct wmi_interface { /* The WMI device type */ @@ -932,6 +937,125 @@ static void acer_backlight_exit(void) backlight_device_unregister(acer_backlight_device); } +/* + * Rfkill devices + */ +static struct workqueue_struct *rfkill_workqueue; + +static void acer_rfkill_update(struct work_struct *ignored); +static DECLARE_DELAYED_WORK(acer_rfkill_work, acer_rfkill_update); +static void acer_rfkill_update(struct work_struct *ignored) +{ + u32 state; + acpi_status status; + + status = get_u32(&state, ACER_CAP_WIRELESS); + if (ACPI_SUCCESS(status)) + rfkill_force_state(wireless_rfkill, state ? + RFKILL_STATE_UNBLOCKED : RFKILL_STATE_SOFT_BLOCKED); + + if (has_cap(ACER_CAP_BLUETOOTH)) { + status = get_u32(&state, ACER_CAP_BLUETOOTH); + if (ACPI_SUCCESS(status)) + rfkill_force_state(bluetooth_rfkill, state ? + RFKILL_STATE_UNBLOCKED : + RFKILL_STATE_SOFT_BLOCKED); + } + + queue_delayed_work(rfkill_workqueue, &acer_rfkill_work, + round_jiffies_relative(HZ)); +} + +static int acer_rfkill_set(void *data, enum rfkill_state state) +{ + acpi_status status; + u32 *cap = data; + status = set_u32((u32) (state == RFKILL_STATE_UNBLOCKED), *cap); + if (ACPI_FAILURE(status)) + return -ENODEV; + return 0; +} + +static struct rfkill * acer_rfkill_register(struct device *dev, +enum rfkill_type type, char *name, u32 cap) +{ + int err; + u32 state; + u32 *data; + struct rfkill *rfkill_dev; + + rfkill_dev = rfkill_allocate(dev, type); + if (!rfkill_dev) + return ERR_PTR(-ENOMEM); + rfkill_dev->name = name; + get_u32(&state, cap); + rfkill_dev->state = state ? RFKILL_STATE_UNBLOCKED : + RFKILL_STATE_SOFT_BLOCKED; + data = kzalloc(sizeof(u32), GFP_KERNEL); + if (!data) { + rfkill_free(rfkill_dev); + return ERR_PTR(-ENOMEM); + } + *data = cap; + rfkill_dev->data = data; + rfkill_dev->toggle_radio = acer_rfkill_set; + rfkill_dev->user_claim_unsupported = 1; + + err = rfkill_register(rfkill_dev); + if (err) { + kfree(rfkill_dev->data); + rfkill_free(rfkill_dev); + return ERR_PTR(err); + } + return rfkill_dev; +} + +static int acer_rfkill_init(struct device *dev) +{ + wireless_rfkill = acer_rfkill_register(dev, RFKILL_TYPE_WLAN, + "acer-wireless", ACER_CAP_WIRELESS); + if (IS_ERR(wireless_rfkill)) + return PTR_ERR(wireless_rfkill); + + if (has_cap(ACER_CAP_BLUETOOTH)) { + bluetooth_rfkill = acer_rfkill_register(dev, + RFKILL_TYPE_BLUETOOTH, "acer-bluetooth", + ACER_CAP_BLUETOOTH); + if (IS_ERR(bluetooth_rfkill)) { + kfree(wireless_rfkill->data); + rfkill_unregister(wireless_rfkill); + return PTR_ERR(bluetooth_rfkill); + } + } + + rfkill_workqueue = create_singlethread_workqueue("rfkill_workqueue"); + if (!rfkill_workqueue) { + if (has_cap(ACER_CAP_BLUETOOTH)) { + kfree(bluetooth_rfkill->data); + rfkill_unregister(bluetooth_rfkill); + } + kfree(wireless_rfkill->data); + rfkill_unregister(wireless_rfkill); + return -ENOMEM; + } + queue_delayed_work(rfkill_workqueue, &acer_rfkill_work, HZ); + + return 0; +} + +static void acer_rfkill_exit(void) +{ + cancel_delayed_work_sync(&acer_rfkill_work); + destroy_workqueue(rfkill_workqueue); + kfree(wireless_rfkill->data); + rfkill_unregister(wireless_rfkill); + if (has_cap(ACER_CAP_BLUETOOTH)) { + kfree(wireless_rfkill->data); + rfkill_unregister(bluetooth_rfkill); + } + return; +} + /* * Read/ write bool sysfs macro */ @@ -1026,7 +1150,9 @@ static int __devinit acer_platform_probe(struct platform_device *device) goto error_brightness; } - return 0; + err = acer_rfkill_init(&device->dev); + + return err; error_brightness: acer_led_exit(); @@ -1040,6 +1166,8 @@ static int acer_platform_remove(struct platform_device *device) acer_led_exit(); if (has_cap(ACER_CAP_BRIGHTNESS)) acer_backlight_exit(); + + acer_rfkill_exit(); return 0; } -- cgit v1.2.3 From 7d9a06de616f69374dab00396f27cf00962a72c9 Mon Sep 17 00:00:00 2001 From: Carlos Corbacho Date: Wed, 8 Oct 2008 21:40:26 +0100 Subject: acer-wmi: Remove wireless and bluetooth sysfs entries These are now replaced by the rfkill interface. Signed-off-by: Carlos Corbacho Signed-off-by: Len Brown --- Documentation/laptops/acer-wmi.txt | 28 ++-------- drivers/misc/acer-wmi.c | 101 ++++++++----------------------------- 2 files changed, 25 insertions(+), 104 deletions(-) diff --git a/Documentation/laptops/acer-wmi.txt b/Documentation/laptops/acer-wmi.txt index 69b5dd4e5a5..2b3a6b5260b 100644 --- a/Documentation/laptops/acer-wmi.txt +++ b/Documentation/laptops/acer-wmi.txt @@ -1,7 +1,7 @@ Acer Laptop WMI Extras Driver http://code.google.com/p/aceracpi -Version 0.1 -9th February 2008 +Version 0.2 +18th August 2008 Copyright 2007-2008 Carlos Corbacho @@ -87,17 +87,7 @@ acer-wmi come with built-in wireless. However, should you feel so inclined to ever wish to remove the card, or swap it out at some point, please get in touch with me, as we may well be able to gain some data on wireless card detection. -To read the status of the wireless radio (0=off, 1=on): -cat /sys/devices/platform/acer-wmi/wireless - -To enable the wireless radio: -echo 1 > /sys/devices/platform/acer-wmi/wireless - -To disable the wireless radio: -echo 0 > /sys/devices/platform/acer-wmi/wireless - -To set the state of the wireless radio when loading acer-wmi, pass: -wireless=X (where X is 0 or 1) +The wireless radio is exposed through rfkill. Bluetooth ********* @@ -117,17 +107,7 @@ For the adventurously minded - if you want to buy an internal bluetooth module off the internet that is compatible with your laptop and fit it, then it will work just fine with acer-wmi. -To read the status of the bluetooth module (0=off, 1=on): -cat /sys/devices/platform/acer-wmi/wireless - -To enable the bluetooth module: -echo 1 > /sys/devices/platform/acer-wmi/bluetooth - -To disable the bluetooth module: -echo 0 > /sys/devices/platform/acer-wmi/bluetooth - -To set the state of the bluetooth module when loading acer-wmi, pass: -bluetooth=X (where X is 0 or 1) +Bluetooth is exposed through rfkill. 3G ** diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c index e3e11e88747..8708d319378 100644 --- a/drivers/misc/acer-wmi.c +++ b/drivers/misc/acer-wmi.c @@ -125,21 +125,15 @@ enum interface_flags { static int max_brightness = 0xF; -static int wireless = -1; -static int bluetooth = -1; static int mailled = -1; static int brightness = -1; static int threeg = -1; static int force_series; module_param(mailled, int, 0444); -module_param(wireless, int, 0444); -module_param(bluetooth, int, 0444); module_param(brightness, int, 0444); module_param(threeg, int, 0444); module_param(force_series, int, 0444); -MODULE_PARM_DESC(wireless, "Set initial state of Wireless hardware"); -MODULE_PARM_DESC(bluetooth, "Set initial state of Bluetooth hardware"); MODULE_PARM_DESC(mailled, "Set initial state of Mail LED"); MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness"); MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware"); @@ -147,8 +141,6 @@ MODULE_PARM_DESC(force_series, "Force a different laptop series"); struct acer_data { int mailled; - int wireless; - int bluetooth; int threeg; int brightness; }; @@ -851,8 +843,6 @@ static void __init acer_commandline_init(void) * capability isn't available on the given interface */ set_u32(mailled, ACER_CAP_MAILLED); - set_u32(wireless, ACER_CAP_WIRELESS); - set_u32(bluetooth, ACER_CAP_BLUETOOTH); set_u32(threeg, ACER_CAP_THREEG); set_u32(brightness, ACER_CAP_BRIGHTNESS); } @@ -1057,40 +1047,30 @@ static void acer_rfkill_exit(void) } /* - * Read/ write bool sysfs macro + * sysfs interface */ -#define show_set_bool(value, cap) \ -static ssize_t \ -show_bool_##value(struct device *dev, struct device_attribute *attr, \ - char *buf) \ -{ \ +static ssize_t show_bool_threeg(struct device *dev, + struct device_attribute *attr, char *buf) +{ u32 result; \ - acpi_status status = get_u32(&result, cap); \ - if (ACPI_SUCCESS(status)) \ - return sprintf(buf, "%u\n", result); \ - return sprintf(buf, "Read error\n"); \ -} \ -\ -static ssize_t \ -set_bool_##value(struct device *dev, struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - u32 tmp = simple_strtoul(buf, NULL, 10); \ - acpi_status status = set_u32(tmp, cap); \ - if (ACPI_FAILURE(status)) \ - return -EINVAL; \ - return count; \ -} \ -static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \ - show_bool_##value, set_bool_##value); - -show_set_bool(wireless, ACER_CAP_WIRELESS); -show_set_bool(bluetooth, ACER_CAP_BLUETOOTH); -show_set_bool(threeg, ACER_CAP_THREEG); + acpi_status status = get_u32(&result, ACER_CAP_THREEG); + if (ACPI_SUCCESS(status)) + return sprintf(buf, "%u\n", result); + return sprintf(buf, "Read error\n"); +} + +static ssize_t set_bool_threeg(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u32 tmp = simple_strtoul(buf, NULL, 10); + acpi_status status = set_u32(tmp, ACER_CAP_THREEG); + if (ACPI_FAILURE(status)) + return -EINVAL; + return count; +} +static DEVICE_ATTR(threeg, S_IWUGO | S_IRUGO | S_IWUSR, show_bool_threeg, + set_bool_threeg); -/* - * Read interface sysfs macro - */ static ssize_t show_interface(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1180,16 +1160,6 @@ pm_message_t state) if (!data) return -ENOMEM; - if (has_cap(ACER_CAP_WIRELESS)) { - get_u32(&value, ACER_CAP_WIRELESS); - data->wireless = value; - } - - if (has_cap(ACER_CAP_BLUETOOTH)) { - get_u32(&value, ACER_CAP_BLUETOOTH); - data->bluetooth = value; - } - if (has_cap(ACER_CAP_MAILLED)) { get_u32(&value, ACER_CAP_MAILLED); data->mailled = value; @@ -1210,15 +1180,6 @@ static int acer_platform_resume(struct platform_device *device) if (!data) return -ENOMEM; - if (has_cap(ACER_CAP_WIRELESS)) - set_u32(data->wireless, ACER_CAP_WIRELESS); - - if (has_cap(ACER_CAP_BLUETOOTH)) - set_u32(data->bluetooth, ACER_CAP_BLUETOOTH); - - if (has_cap(ACER_CAP_THREEG)) - set_u32(data->threeg, ACER_CAP_THREEG); - if (has_cap(ACER_CAP_MAILLED)) set_u32(data->mailled, ACER_CAP_MAILLED); @@ -1243,12 +1204,6 @@ static struct platform_device *acer_platform_device; static int remove_sysfs(struct platform_device *device) { - if (has_cap(ACER_CAP_WIRELESS)) - device_remove_file(&device->dev, &dev_attr_wireless); - - if (has_cap(ACER_CAP_BLUETOOTH)) - device_remove_file(&device->dev, &dev_attr_bluetooth); - if (has_cap(ACER_CAP_THREEG)) device_remove_file(&device->dev, &dev_attr_threeg); @@ -1261,20 +1216,6 @@ static int create_sysfs(void) { int retval = -ENOMEM; - if (has_cap(ACER_CAP_WIRELESS)) { - retval = device_create_file(&acer_platform_device->dev, - &dev_attr_wireless); - if (retval) - goto error_sysfs; - } - - if (has_cap(ACER_CAP_BLUETOOTH)) { - retval = device_create_file(&acer_platform_device->dev, - &dev_attr_bluetooth); - if (retval) - goto error_sysfs; - } - if (has_cap(ACER_CAP_THREEG)) { retval = device_create_file(&acer_platform_device->dev, &dev_attr_threeg); -- cgit v1.2.3 From a66bfa7a4726e9cbdc985133af728a4bd9925d96 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Wed, 8 Oct 2008 21:40:32 +0100 Subject: ACPI: WMI: Enable event methods when registering notifiers According to the ACPI-WMI spec, event blocks may provide a function call for enabling/disabling them. This patch adds support for making these calls when registering or removing notifications. Without this, my Dell firmware provides no data in the event notification. Signed-off-by: Matthew Garrett Signed-off-by: Carlos Corbacho Signed-off-by: Len Brown --- drivers/acpi/wmi.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/wmi.c b/drivers/acpi/wmi.c index cfe2c833474..5464cfcf829 100644 --- a/drivers/acpi/wmi.c +++ b/drivers/acpi/wmi.c @@ -217,6 +217,35 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) return 0; } +static acpi_status wmi_method_enable(struct wmi_block *wblock, int enable) +{ + struct guid_block *block = NULL; + char method[5]; + struct acpi_object_list input; + union acpi_object params[1]; + acpi_status status; + acpi_handle handle; + + block = &wblock->gblock; + handle = wblock->handle; + + if (!block) + return AE_NOT_EXIST; + + input.count = 1; + input.pointer = params; + params[0].type = ACPI_TYPE_INTEGER; + params[0].integer.value = enable; + + snprintf(method, 5, "WE%02X", block->notify_id); + status = acpi_evaluate_object(handle, method, &input, NULL); + + if (status != AE_OK && status != AE_NOT_FOUND) + return status; + else + return AE_OK; +} + /* * Exported WMI functions */ @@ -427,6 +456,7 @@ acpi_status wmi_install_notify_handler(const char *guid, wmi_notify_handler handler, void *data) { struct wmi_block *block; + acpi_status status; if (!guid || !handler) return AE_BAD_PARAMETER; @@ -441,7 +471,9 @@ wmi_notify_handler handler, void *data) block->handler = handler; block->handler_data = data; - return AE_OK; + status = wmi_method_enable(block, 1); + + return status; } EXPORT_SYMBOL_GPL(wmi_install_notify_handler); @@ -453,6 +485,7 @@ EXPORT_SYMBOL_GPL(wmi_install_notify_handler); acpi_status wmi_remove_notify_handler(const char *guid) { struct wmi_block *block; + acpi_status status; if (!guid) return AE_BAD_PARAMETER; @@ -464,10 +497,12 @@ acpi_status wmi_remove_notify_handler(const char *guid) if (!block->handler) return AE_NULL_ENTRY; + status = wmi_method_enable(block, 0); + block->handler = NULL; block->handler_data = NULL; - return AE_OK; + return status; } EXPORT_SYMBOL_GPL(wmi_remove_notify_handler); -- cgit v1.2.3 From 6aea938f54ca785ce3bea67a8948698225c6ca58 Mon Sep 17 00:00:00 2001 From: Julien Brunel Date: Fri, 10 Oct 2008 12:00:19 -0700 Subject: RDMA/ucma: Test ucma_alloc_multicast() return against NULL, not with IS_ERR() In case of error, the function ucma_alloc_multicast() returns a NULL pointer, but never returns an ERR pointer. So after a call to this function, an IS_ERR test should be replaced by a NULL test. The semantic match that finds this problem is as follows: (http://www.emn.fr/x-info/coccinelle/) // @match bad_is_err_test@ expression x, E; @@ x = ucma_alloc_multicast(...) ... when != x = E IS_ERR(x) // Signed-off-by: Julien Brunel Signed-off-by: Julia Lawall Signed-off-by: Roland Dreier --- drivers/infiniband/core/ucma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/infiniband/core/ucma.c b/drivers/infiniband/core/ucma.c index 3ddacf39b7b..4346a24568f 100644 --- a/drivers/infiniband/core/ucma.c +++ b/drivers/infiniband/core/ucma.c @@ -904,8 +904,8 @@ static ssize_t ucma_join_multicast(struct ucma_file *file, mutex_lock(&file->mut); mc = ucma_alloc_multicast(ctx); - if (IS_ERR(mc)) { - ret = PTR_ERR(mc); + if (!mc) { + ret = -ENOMEM; goto err1; } -- cgit v1.2.3 From a3cdcbfa8fb1fccfe48d359da86e99546610c562 Mon Sep 17 00:00:00 2001 From: Yevgeny Petrilin Date: Fri, 10 Oct 2008 12:01:37 -0700 Subject: mlx4_core: Add QP range reservation support To allow allocating an aligned range of consecutive QP numbers, add an interface to reserve an aligned range of QP numbers and have the QP allocation function always take a QP number. This will be used for RSS support in the mlx4_en Ethernet driver and also potentially by IPoIB RSS support. Signed-off-by: Yevgeny Petrilin Signed-off-by: Roland Dreier --- drivers/infiniband/hw/mlx4/qp.c | 21 ++++++++++-- drivers/net/mlx4/alloc.c | 74 ++++++++++++++++++++++++++++++++++++++++- drivers/net/mlx4/mlx4.h | 2 ++ drivers/net/mlx4/qp.c | 45 +++++++++++++++++-------- include/linux/mlx4/device.h | 5 ++- 5 files changed, 129 insertions(+), 18 deletions(-) diff --git a/drivers/infiniband/hw/mlx4/qp.c b/drivers/infiniband/hw/mlx4/qp.c index baa01deb243..39167a797f9 100644 --- a/drivers/infiniband/hw/mlx4/qp.c +++ b/drivers/infiniband/hw/mlx4/qp.c @@ -451,6 +451,7 @@ static int create_qp_common(struct mlx4_ib_dev *dev, struct ib_pd *pd, struct ib_qp_init_attr *init_attr, struct ib_udata *udata, int sqpn, struct mlx4_ib_qp *qp) { + int qpn; int err; mutex_init(&qp->mutex); @@ -545,9 +546,17 @@ static int create_qp_common(struct mlx4_ib_dev *dev, struct ib_pd *pd, } } - err = mlx4_qp_alloc(dev->dev, sqpn, &qp->mqp); + if (sqpn) { + qpn = sqpn; + } else { + err = mlx4_qp_reserve_range(dev->dev, 1, 1, &qpn); + if (err) + goto err_wrid; + } + + err = mlx4_qp_alloc(dev->dev, qpn, &qp->mqp); if (err) - goto err_wrid; + goto err_qpn; /* * Hardware wants QPN written in big-endian order (after @@ -560,6 +569,10 @@ static int create_qp_common(struct mlx4_ib_dev *dev, struct ib_pd *pd, return 0; +err_qpn: + if (!sqpn) + mlx4_qp_release_range(dev->dev, qpn, 1); + err_wrid: if (pd->uobject) { if (!init_attr->srq) @@ -655,6 +668,10 @@ static void destroy_qp_common(struct mlx4_ib_dev *dev, struct mlx4_ib_qp *qp, mlx4_ib_unlock_cqs(send_cq, recv_cq); mlx4_qp_free(dev->dev, &qp->mqp); + + if (!is_sqp(dev, qp)) + mlx4_qp_release_range(dev->dev, qp->mqp.qpn, 1); + mlx4_mtt_cleanup(dev->dev, &qp->mtt); if (is_user) { diff --git a/drivers/net/mlx4/alloc.c b/drivers/net/mlx4/alloc.c index 096bca54bcf..e6c0d5bb5dc 100644 --- a/drivers/net/mlx4/alloc.c +++ b/drivers/net/mlx4/alloc.c @@ -65,10 +65,82 @@ u32 mlx4_bitmap_alloc(struct mlx4_bitmap *bitmap) void mlx4_bitmap_free(struct mlx4_bitmap *bitmap, u32 obj) { + mlx4_bitmap_free_range(bitmap, obj, 1); +} + +static unsigned long find_aligned_range(unsigned long *bitmap, + u32 start, u32 nbits, + int len, int align) +{ + unsigned long end, i; + +again: + start = ALIGN(start, align); + + while ((start < nbits) && test_bit(start, bitmap)) + start += align; + + if (start >= nbits) + return -1; + + end = start+len; + if (end > nbits) + return -1; + + for (i = start + 1; i < end; i++) { + if (test_bit(i, bitmap)) { + start = i + 1; + goto again; + } + } + + return start; +} + +u32 mlx4_bitmap_alloc_range(struct mlx4_bitmap *bitmap, int cnt, int align) +{ + u32 obj, i; + + if (likely(cnt == 1 && align == 1)) + return mlx4_bitmap_alloc(bitmap); + + spin_lock(&bitmap->lock); + + obj = find_aligned_range(bitmap->table, bitmap->last, + bitmap->max, cnt, align); + if (obj >= bitmap->max) { + bitmap->top = (bitmap->top + bitmap->max) & bitmap->mask; + obj = find_aligned_range(bitmap->table, 0, + bitmap->max, + cnt, align); + } + + if (obj < bitmap->max) { + for (i = 0; i < cnt; i++) + set_bit(obj + i, bitmap->table); + if (obj == bitmap->last) { + bitmap->last = (obj + cnt); + if (bitmap->last >= bitmap->max) + bitmap->last = 0; + } + obj |= bitmap->top; + } else + obj = -1; + + spin_unlock(&bitmap->lock); + + return obj; +} + +void mlx4_bitmap_free_range(struct mlx4_bitmap *bitmap, u32 obj, int cnt) +{ + u32 i; + obj &= bitmap->max - 1; spin_lock(&bitmap->lock); - clear_bit(obj, bitmap->table); + for (i = 0; i < cnt; i++) + clear_bit(obj + i, bitmap->table); bitmap->last = min(bitmap->last, obj); bitmap->top = (bitmap->top + bitmap->max) & bitmap->mask; spin_unlock(&bitmap->lock); diff --git a/drivers/net/mlx4/mlx4.h b/drivers/net/mlx4/mlx4.h index 5337e3ac3e7..b55ddab73f6 100644 --- a/drivers/net/mlx4/mlx4.h +++ b/drivers/net/mlx4/mlx4.h @@ -288,6 +288,8 @@ static inline struct mlx4_priv *mlx4_priv(struct mlx4_dev *dev) u32 mlx4_bitmap_alloc(struct mlx4_bitmap *bitmap); void mlx4_bitmap_free(struct mlx4_bitmap *bitmap, u32 obj); +u32 mlx4_bitmap_alloc_range(struct mlx4_bitmap *bitmap, int cnt, int align); +void mlx4_bitmap_free_range(struct mlx4_bitmap *bitmap, u32 obj, int cnt); int mlx4_bitmap_init(struct mlx4_bitmap *bitmap, u32 num, u32 mask, u32 reserved); void mlx4_bitmap_cleanup(struct mlx4_bitmap *bitmap); diff --git a/drivers/net/mlx4/qp.c b/drivers/net/mlx4/qp.c index c49a86044bf..98e0c40ba36 100644 --- a/drivers/net/mlx4/qp.c +++ b/drivers/net/mlx4/qp.c @@ -147,19 +147,42 @@ int mlx4_qp_modify(struct mlx4_dev *dev, struct mlx4_mtt *mtt, } EXPORT_SYMBOL_GPL(mlx4_qp_modify); -int mlx4_qp_alloc(struct mlx4_dev *dev, int sqpn, struct mlx4_qp *qp) +int mlx4_qp_reserve_range(struct mlx4_dev *dev, int cnt, int align, int *base) +{ + struct mlx4_priv *priv = mlx4_priv(dev); + struct mlx4_qp_table *qp_table = &priv->qp_table; + int qpn; + + qpn = mlx4_bitmap_alloc_range(&qp_table->bitmap, cnt, align); + if (qpn == -1) + return -ENOMEM; + + *base = qpn; + return 0; +} +EXPORT_SYMBOL_GPL(mlx4_qp_reserve_range); + +void mlx4_qp_release_range(struct mlx4_dev *dev, int base_qpn, int cnt) +{ + struct mlx4_priv *priv = mlx4_priv(dev); + struct mlx4_qp_table *qp_table = &priv->qp_table; + if (base_qpn < dev->caps.sqp_start + 8) + return; + + mlx4_bitmap_free_range(&qp_table->bitmap, base_qpn, cnt); +} +EXPORT_SYMBOL_GPL(mlx4_qp_release_range); + +int mlx4_qp_alloc(struct mlx4_dev *dev, int qpn, struct mlx4_qp *qp) { struct mlx4_priv *priv = mlx4_priv(dev); struct mlx4_qp_table *qp_table = &priv->qp_table; int err; - if (sqpn) - qp->qpn = sqpn; - else { - qp->qpn = mlx4_bitmap_alloc(&qp_table->bitmap); - if (qp->qpn == -1) - return -ENOMEM; - } + if (!qpn) + return -EINVAL; + + qp->qpn = qpn; err = mlx4_table_get(dev, &qp_table->qp_table, qp->qpn); if (err) @@ -208,9 +231,6 @@ err_put_qp: mlx4_table_put(dev, &qp_table->qp_table, qp->qpn); err_out: - if (!sqpn) - mlx4_bitmap_free(&qp_table->bitmap, qp->qpn); - return err; } EXPORT_SYMBOL_GPL(mlx4_qp_alloc); @@ -239,9 +259,6 @@ void mlx4_qp_free(struct mlx4_dev *dev, struct mlx4_qp *qp) mlx4_table_put(dev, &qp_table->altc_table, qp->qpn); mlx4_table_put(dev, &qp_table->auxc_table, qp->qpn); mlx4_table_put(dev, &qp_table->qp_table, qp->qpn); - - if (qp->qpn >= dev->caps.sqp_start + 8) - mlx4_bitmap_free(&qp_table->bitmap, qp->qpn); } EXPORT_SYMBOL_GPL(mlx4_qp_free); diff --git a/include/linux/mlx4/device.h b/include/linux/mlx4/device.h index b2f94446831..d21e879f3c9 100644 --- a/include/linux/mlx4/device.h +++ b/include/linux/mlx4/device.h @@ -400,7 +400,10 @@ int mlx4_cq_alloc(struct mlx4_dev *dev, int nent, struct mlx4_mtt *mtt, int collapsed); void mlx4_cq_free(struct mlx4_dev *dev, struct mlx4_cq *cq); -int mlx4_qp_alloc(struct mlx4_dev *dev, int sqpn, struct mlx4_qp *qp); +int mlx4_qp_reserve_range(struct mlx4_dev *dev, int cnt, int align, int *base); +void mlx4_qp_release_range(struct mlx4_dev *dev, int base_qpn, int cnt); + +int mlx4_qp_alloc(struct mlx4_dev *dev, int qpn, struct mlx4_qp *qp); void mlx4_qp_free(struct mlx4_dev *dev, struct mlx4_qp *qp); int mlx4_srq_alloc(struct mlx4_dev *dev, u32 pdn, struct mlx4_mtt *mtt, -- cgit v1.2.3 From fcea94ba0773a4bf78d109f2acd72d003f0621f6 Mon Sep 17 00:00:00 2001 From: Jan Beulich Date: Fri, 19 Sep 2008 15:50:32 -0700 Subject: ACPI: fix FADT parsing The (1.0 inherited) separate length fields in the FADT are byte granular. Further, PM1a/b may have distinct lengths (if using the v2 fields was okay) and may live in distinct address spaces. acpi_tb_convert_fadt() should account for all of these conditions. Apart from these changes I'm puzzled by the fact that, not just for acpi_gbl_xpm1{a,b}_enable, acpi_hw_low_level_{read,write}() get an explicit size passed rather than using the size found in the passed GAS. What happens on a platform that defines PM1{a,b} wider than 16 bits? Of course, acpi_hw_low_level_{read,write}() at present are entirely un-prepared to deal with sizes other than 8, 16, or 32, not to speak of a non-zero bit_offset or access_width... Signed-off-by: Jan Beulich Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/acpi/tables/tbfadt.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/tables/tbfadt.c b/drivers/acpi/tables/tbfadt.c index a4a41ba2484..2c7885e7ffb 100644 --- a/drivers/acpi/tables/tbfadt.c +++ b/drivers/acpi/tables/tbfadt.c @@ -50,7 +50,7 @@ ACPI_MODULE_NAME("tbfadt") /* Local prototypes */ static void inline acpi_tb_init_generic_address(struct acpi_generic_address *generic_address, - u8 bit_width, u64 address); + u8 byte_width, u64 address); static void acpi_tb_convert_fadt(void); @@ -111,7 +111,7 @@ static struct acpi_fadt_info fadt_info_table[] = { * FUNCTION: acpi_tb_init_generic_address * * PARAMETERS: generic_address - GAS struct to be initialized - * bit_width - Width of this register + * byte_width - Width of this register * Address - Address of the register * * RETURN: None @@ -124,7 +124,7 @@ static struct acpi_fadt_info fadt_info_table[] = { static void inline acpi_tb_init_generic_address(struct acpi_generic_address *generic_address, - u8 bit_width, u64 address) + u8 byte_width, u64 address) { /* @@ -136,7 +136,7 @@ acpi_tb_init_generic_address(struct acpi_generic_address *generic_address, /* All other fields are byte-wide */ generic_address->space_id = ACPI_ADR_SPACE_SYSTEM_IO; - generic_address->bit_width = bit_width; + generic_address->bit_width = byte_width << 3; generic_address->bit_offset = 0; generic_address->access_width = 0; } @@ -342,9 +342,20 @@ static void acpi_tb_convert_fadt(void) * useful to calculate them once, here. * * The PM event blocks are split into two register blocks, first is the - * PM Status Register block, followed immediately by the PM Enable Register - * block. Each is of length (pm1_event_length/2) + * PM Status Register block, followed immediately by the PM Enable + * Register block. Each is of length (xpm1x_event_block.bit_width/2). + * + * On various systems the v2 fields (and particularly the bit widths) + * cannot be relied upon, though. Hence resort to using the v1 length + * here (and warn about the inconsistency). */ + if (acpi_gbl_FADT.xpm1a_event_block.bit_width + != acpi_gbl_FADT.pm1_event_length * 8) + printk(KERN_WARNING "FADT: " + "X_PM1a_EVT_BLK.bit_width (%u) does not match" + " PM1_EVT_LEN (%u)\n", + acpi_gbl_FADT.xpm1a_event_block.bit_width, + acpi_gbl_FADT.pm1_event_length); pm1_register_length = (u8) ACPI_DIV_2(acpi_gbl_FADT.pm1_event_length); /* The PM1A register block is required */ @@ -360,13 +371,20 @@ static void acpi_tb_convert_fadt(void) /* The PM1B register block is optional, ignore if not present */ if (acpi_gbl_FADT.xpm1b_event_block.address) { + if (acpi_gbl_FADT.xpm1b_event_block.bit_width + != acpi_gbl_FADT.pm1_event_length * 8) + printk(KERN_WARNING "FADT: " + "X_PM1b_EVT_BLK.bit_width (%u) does not match" + " PM1_EVT_LEN (%u)\n", + acpi_gbl_FADT.xpm1b_event_block.bit_width, + acpi_gbl_FADT.pm1_event_length); acpi_tb_init_generic_address(&acpi_gbl_xpm1b_enable, pm1_register_length, (acpi_gbl_FADT.xpm1b_event_block. address + pm1_register_length)); /* Don't forget to copy space_id of the GAS */ acpi_gbl_xpm1b_enable.space_id = - acpi_gbl_FADT.xpm1a_event_block.space_id; + acpi_gbl_FADT.xpm1b_event_block.space_id; } } -- cgit v1.2.3 From 0e6a66e9cf231140d2e49064d48162728edb7746 Mon Sep 17 00:00:00 2001 From: Jonathan Woithe Date: Thu, 9 Oct 2008 13:44:40 +0930 Subject: fujitsu-laptop: better handling of P8010 hotkey This patch (mostly from Peter Gruber) improves the handling of the hotkeys for P8010 laptops by passing more accurate input events back to userspace. This is needed because the P8010 labels these buttons quite differently to earlier laptops. As part of this, a P8010-specific DMI callback check has been implemented. Finally there's some minor whitespace cleanups from running the source through Lindent. Signed-off-by: Peter Gruber Signed-off-by: Jonathan Woithe Signed-off-by: Len Brown --- drivers/misc/fujitsu-laptop.c | 123 ++++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/drivers/misc/fujitsu-laptop.c b/drivers/misc/fujitsu-laptop.c index 3e56203e494..f861db07277 100644 --- a/drivers/misc/fujitsu-laptop.c +++ b/drivers/misc/fujitsu-laptop.c @@ -44,8 +44,9 @@ * Hotkeys present on certain Fujitsu laptops (eg: the S6xxx series) are * also supported by this driver. * - * This driver has been tested on a Fujitsu Lifebook S6410 and S7020. It - * should work on most P-series and S-series Lifebooks, but YMMV. + * This driver has been tested on a Fujitsu Lifebook S6410, S7020 and + * P8010. It should work on most P-series and S-series Lifebooks, but + * YMMV. * * The module parameter use_alt_lcd_levels switches between different ACPI * brightness controls which are used by different Fujitsu laptops. In most @@ -65,7 +66,7 @@ #include #include -#define FUJITSU_DRIVER_VERSION "0.4.2" +#define FUJITSU_DRIVER_VERSION "0.4.3" #define FUJITSU_LCD_N_LEVELS 8 @@ -83,10 +84,10 @@ #define ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS 0x87 /* Hotkey details */ -#define LOCK_KEY 0x410 /* codes for the keys in the GIRB register */ -#define DISPLAY_KEY 0x411 /* keys are mapped to KEY_SCREENLOCK (the key with the key symbol) */ -#define ENERGY_KEY 0x412 /* KEY_MEDIA (the key with the laptop symbol, KEY_EMAIL (E key)) */ -#define REST_KEY 0x413 /* KEY_SUSPEND (R key) */ +#define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */ +#define KEY2_CODE 0x411 +#define KEY3_CODE 0x412 +#define KEY4_CODE 0x413 #define MAX_HOTKEY_RINGBUFFER_SIZE 100 #define RINGBUFFERSIZE 40 @@ -123,6 +124,7 @@ struct fujitsu_t { char phys[32]; struct backlight_device *bl_device; struct platform_device *pf_device; + int keycode1, keycode2, keycode3, keycode4; unsigned int max_brightness; unsigned int brightness_changed; @@ -430,7 +432,7 @@ static struct platform_driver fujitsupf_driver = { } }; -static int dmi_check_cb_s6410(const struct dmi_system_id *id) +static void dmi_check_cb_common(const struct dmi_system_id *id) { acpi_handle handle; int have_blnf; @@ -452,24 +454,40 @@ static int dmi_check_cb_s6410(const struct dmi_system_id *id) "auto-detecting disable_adjust\n"); disable_brightness_adjust = have_blnf ? 0 : 1; } +} + +static int dmi_check_cb_s6410(const struct dmi_system_id *id) +{ + dmi_check_cb_common(id); + fujitsu->keycode1 = KEY_SCREENLOCK; /* "Lock" */ + fujitsu->keycode2 = KEY_HELP; /* "Mobility Center" */ + return 0; +} + +static int dmi_check_cb_p8010(const struct dmi_system_id *id) +{ + dmi_check_cb_common(id); + fujitsu->keycode1 = KEY_HELP; /* "Support" */ + fujitsu->keycode3 = KEY_SWITCHVIDEOMODE; /* "Presentation" */ + fujitsu->keycode4 = KEY_WWW; /* "Internet" */ return 0; } static struct dmi_system_id __initdata fujitsu_dmi_table[] = { { - .ident = "Fujitsu Siemens", + .ident = "Fujitsu Siemens S6410", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"), }, .callback = dmi_check_cb_s6410}, { - .ident = "FUJITSU LifeBook P8010", + .ident = "Fujitsu LifeBook P8010", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"), - }, - .callback = dmi_check_cb_s6410}, + }, + .callback = dmi_check_cb_p8010}, {} }; @@ -547,7 +565,6 @@ static int acpi_fujitsu_add(struct acpi_device *device) } /* do config (detect defaults) */ - dmi_check_system(fujitsu_dmi_table); use_alt_lcd_levels = use_alt_lcd_levels == 1 ? 1 : 0; disable_brightness_keys = disable_brightness_keys == 1 ? 1 : 0; disable_brightness_adjust = disable_brightness_adjust == 1 ? 1 : 0; @@ -623,17 +640,17 @@ static void acpi_fujitsu_notify(acpi_handle handle, u32 event, void *data) keycode = 0; if (disable_brightness_keys != 1) { if (oldb == 0) { - acpi_bus_generate_proc_event(fujitsu-> - dev, - ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, - 0); + acpi_bus_generate_proc_event + (fujitsu->dev, + ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, + 0); keycode = KEY_BRIGHTNESSDOWN; } else if (oldb == (fujitsu->max_brightness) - 1) { - acpi_bus_generate_proc_event(fujitsu-> - dev, - ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, - 0); + acpi_bus_generate_proc_event + (fujitsu->dev, + ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, + 0); keycode = KEY_BRIGHTNESSUP; } } @@ -646,8 +663,7 @@ static void acpi_fujitsu_notify(acpi_handle handle, u32 event, void *data) } if (disable_brightness_keys != 1) { acpi_bus_generate_proc_event(fujitsu->dev, - ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, - 0); + ACPI_VIDEO_NOTIFY_INC_BRIGHTNESS, 0); keycode = KEY_BRIGHTNESSUP; } } else if (oldb > newb) { @@ -659,8 +675,7 @@ static void acpi_fujitsu_notify(acpi_handle handle, u32 event, void *data) } if (disable_brightness_keys != 1) { acpi_bus_generate_proc_event(fujitsu->dev, - ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, - 0); + ACPI_VIDEO_NOTIFY_DEC_BRIGHTNESS, 0); keycode = KEY_BRIGHTNESSDOWN; } } else { @@ -742,10 +757,10 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) input->id.product = 0x06; input->dev.parent = &device->dev; input->evbit[0] = BIT(EV_KEY); - set_bit(KEY_SCREENLOCK, input->keybit); - set_bit(KEY_MEDIA, input->keybit); - set_bit(KEY_EMAIL, input->keybit); - set_bit(KEY_SUSPEND, input->keybit); + set_bit(fujitsu->keycode1, input->keybit); + set_bit(fujitsu->keycode2, input->keybit); + set_bit(fujitsu->keycode3, input->keybit); + set_bit(fujitsu->keycode4, input->keybit); set_bit(KEY_UNKNOWN, input->keybit); error = input_register_device(input); @@ -833,24 +848,24 @@ static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event, irb); switch (irb & 0x4ff) { - case LOCK_KEY: - keycode = KEY_SCREENLOCK; + case KEY1_CODE: + keycode = fujitsu->keycode1; break; - case DISPLAY_KEY: - keycode = KEY_MEDIA; + case KEY2_CODE: + keycode = fujitsu->keycode2; break; - case ENERGY_KEY: - keycode = KEY_EMAIL; + case KEY3_CODE: + keycode = fujitsu->keycode3; break; - case REST_KEY: - keycode = KEY_SUSPEND; + case KEY4_CODE: + keycode = fujitsu->keycode4; break; case 0: keycode = 0; break; default: vdbg_printk(FUJLAPTOP_DBG_WARN, - "Unknown GIRB result [%x]\n", irb); + "Unknown GIRB result [%x]\n", irb); keycode = -1; break; } @@ -859,12 +874,12 @@ static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event, "Push keycode into ringbuffer [%d]\n", keycode); status = kfifo_put(fujitsu_hotkey->fifo, - (unsigned char *)&keycode, - sizeof(keycode)); + (unsigned char *)&keycode, + sizeof(keycode)); if (status != sizeof(keycode)) { vdbg_printk(FUJLAPTOP_DBG_WARN, - "Could not push keycode [0x%x]\n", - keycode); + "Could not push keycode [0x%x]\n", + keycode); } else { input_report_key(input, keycode, 1); input_sync(input); @@ -879,8 +894,8 @@ static void acpi_fujitsu_hotkey_notify(acpi_handle handle, u32 event, input_report_key(input, keycode_r, 0); input_sync(input); vdbg_printk(FUJLAPTOP_DBG_TRACE, - "Pop keycode from ringbuffer [%d]\n", - keycode_r); + "Pop keycode from ringbuffer [%d]\n", + keycode_r); } } } @@ -943,6 +958,11 @@ static int __init fujitsu_init(void) if (!fujitsu) return -ENOMEM; memset(fujitsu, 0, sizeof(struct fujitsu_t)); + fujitsu->keycode1 = KEY_PROG1; + fujitsu->keycode2 = KEY_PROG2; + fujitsu->keycode3 = KEY_PROG3; + fujitsu->keycode4 = KEY_PROG4; + dmi_check_system(fujitsu_dmi_table); result = acpi_bus_register_driver(&acpi_fujitsu_driver); if (result < 0) { @@ -1076,15 +1096,14 @@ MODULE_DESCRIPTION("Fujitsu laptop extras support"); MODULE_VERSION(FUJITSU_DRIVER_VERSION); MODULE_LICENSE("GPL"); -MODULE_ALIAS - ("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*"); -MODULE_ALIAS - ("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*"); +MODULE_ALIAS("dmi:*:svnFUJITSUSIEMENS:*:pvr:rvnFUJITSU:rnFJNB1D3:*:cvrS6410:*"); +MODULE_ALIAS("dmi:*:svnFUJITSU:*:pvr:rvnFUJITSU:rnFJNB19C:*:cvrS7020:*"); static struct pnp_device_id pnp_ids[] = { - { .id = "FUJ02bf" }, - { .id = "FUJ02B1" }, - { .id = "FUJ02E3" }, - { .id = "" } + {.id = "FUJ02bf"}, + {.id = "FUJ02B1"}, + {.id = "FUJ02E3"}, + {.id = ""} }; + MODULE_DEVICE_TABLE(pnp, pnp_ids); -- cgit v1.2.3 From 0540bbbe455e123a1692d26205ad1a29983883b0 Mon Sep 17 00:00:00 2001 From: Hoang-Nam Nguyen Date: Fri, 10 Oct 2008 14:40:39 -0700 Subject: IB/ehca: Don't allow creating UC QP with SRQ This patch prevents a UC QP to be created attached to an SRQ, since current firmware does not support this feature. Signed-off-by: Michael Faath Signed-off-by: Roland Dreier --- drivers/infiniband/hw/ehca/ehca_qp.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/infiniband/hw/ehca/ehca_qp.c b/drivers/infiniband/hw/ehca/ehca_qp.c index 4dbe2870e01..40b578d601c 100644 --- a/drivers/infiniband/hw/ehca/ehca_qp.c +++ b/drivers/infiniband/hw/ehca/ehca_qp.c @@ -502,6 +502,12 @@ static struct ehca_qp *internal_create_qp( if (init_attr->srq) { my_srq = container_of(init_attr->srq, struct ehca_qp, ib_srq); + if (qp_type == IB_QPT_UC) { + ehca_err(pd->device, "UC with SRQ not supported"); + atomic_dec(&shca->num_qps); + return ERR_PTR(-EINVAL); + } + has_srq = 1; parms.ext_type = EQPT_SRQBASE; parms.srq_qpn = my_srq->real_qp_num; -- cgit v1.2.3 From a195dcdcff33b8ef01a23cbc489fdfcdfa28c88e Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 19 Aug 2008 12:13:20 +0100 Subject: eeepc-laptop: Use standard interfaces eeepc-laptop currently only sends key events via ACPI and has non-standard rfkill control. Add an input device and use the rfkill infrastructure. Signed-off-by: Matthew Garrett Signed-off-by: Len Brown --- drivers/misc/eeepc-laptop.c | 228 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 19 deletions(-) diff --git a/drivers/misc/eeepc-laptop.c b/drivers/misc/eeepc-laptop.c index 1ee8501e90f..5baa10bcaf6 100644 --- a/drivers/misc/eeepc-laptop.c +++ b/drivers/misc/eeepc-laptop.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #define EEEPC_LAPTOP_VERSION "0.1" @@ -125,6 +127,10 @@ struct eeepc_hotk { by this BIOS */ uint init_flag; /* Init flags */ u16 event_count[128]; /* count for each event */ + struct input_dev *inputdev; + u16 *keycode_map; + struct rfkill *eeepc_wlan_rfkill; + struct rfkill *eeepc_bluetooth_rfkill; }; /* The actual device the driver binds to */ @@ -140,6 +146,27 @@ static struct platform_driver platform_driver = { static struct platform_device *platform_device; +struct key_entry { + char type; + u8 code; + u16 keycode; +}; + +enum { KE_KEY, KE_END }; + +static struct key_entry eeepc_keymap[] = { + /* Sleep already handled via generic ACPI code */ + {KE_KEY, 0x10, KEY_WLAN }, + {KE_KEY, 0x12, KEY_PROG1 }, + {KE_KEY, 0x13, KEY_MUTE }, + {KE_KEY, 0x14, KEY_VOLUMEDOWN }, + {KE_KEY, 0x15, KEY_VOLUMEUP }, + {KE_KEY, 0x30, KEY_SWITCHVIDEOMODE }, + {KE_KEY, 0x31, KEY_SWITCHVIDEOMODE }, + {KE_KEY, 0x32, KEY_SWITCHVIDEOMODE }, + {KE_END, 0}, +}; + /* * The hotkey driver declaration */ @@ -260,6 +287,44 @@ static int update_bl_status(struct backlight_device *bd) return set_brightness(bd, bd->props.brightness); } +/* + * Rfkill helpers + */ + +static int eeepc_wlan_rfkill_set(void *data, enum rfkill_state state) +{ + if (state == RFKILL_STATE_SOFT_BLOCKED) + return set_acpi(CM_ASL_WLAN, 0); + else + return set_acpi(CM_ASL_WLAN, 1); +} + +static int eeepc_wlan_rfkill_state(void *data, enum rfkill_state *state) +{ + if (get_acpi(CM_ASL_WLAN) == 1) + *state = RFKILL_STATE_UNBLOCKED; + else + *state = RFKILL_STATE_SOFT_BLOCKED; + return 0; +} + +static int eeepc_bluetooth_rfkill_set(void *data, enum rfkill_state state) +{ + if (state == RFKILL_STATE_SOFT_BLOCKED) + return set_acpi(CM_ASL_BLUETOOTH, 0); + else + return set_acpi(CM_ASL_BLUETOOTH, 1); +} + +static int eeepc_bluetooth_rfkill_state(void *data, enum rfkill_state *state) +{ + if (get_acpi(CM_ASL_BLUETOOTH) == 1) + *state = RFKILL_STATE_UNBLOCKED; + else + *state = RFKILL_STATE_SOFT_BLOCKED; + return 0; +} + /* * Sys helpers */ @@ -311,13 +376,11 @@ static ssize_t show_sys_acpi(int cm, char *buf) EEEPC_CREATE_DEVICE_ATTR(camera, CM_ASL_CAMERA); EEEPC_CREATE_DEVICE_ATTR(cardr, CM_ASL_CARDREADER); EEEPC_CREATE_DEVICE_ATTR(disp, CM_ASL_DISPLAYSWITCH); -EEEPC_CREATE_DEVICE_ATTR(wlan, CM_ASL_WLAN); static struct attribute *platform_attributes[] = { &dev_attr_camera.attr, &dev_attr_cardr.attr, &dev_attr_disp.attr, - &dev_attr_wlan.attr, NULL }; @@ -328,8 +391,64 @@ static struct attribute_group platform_attribute_group = { /* * Hotkey functions */ +static struct key_entry *eepc_get_entry_by_scancode(int code) +{ + struct key_entry *key; + + for (key = eeepc_keymap; key->type != KE_END; key++) + if (code == key->code) + return key; + + return NULL; +} + +static struct key_entry *eepc_get_entry_by_keycode(int code) +{ + struct key_entry *key; + + for (key = eeepc_keymap; key->type != KE_END; key++) + if (code == key->keycode && key->type == KE_KEY) + return key; + + return NULL; +} + +static int eeepc_getkeycode(struct input_dev *dev, int scancode, int *keycode) +{ + struct key_entry *key = eepc_get_entry_by_scancode(scancode); + + if (key && key->type == KE_KEY) { + *keycode = key->keycode; + return 0; + } + + return -EINVAL; +} + +static int eeepc_setkeycode(struct input_dev *dev, int scancode, int keycode) +{ + struct key_entry *key; + int old_keycode; + + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + key = eepc_get_entry_by_scancode(scancode); + if (key && key->type == KE_KEY) { + old_keycode = key->keycode; + key->keycode = keycode; + set_bit(keycode, dev->keybit); + if (!eepc_get_entry_by_keycode(old_keycode)) + clear_bit(old_keycode, dev->keybit); + return 0; + } + + return -EINVAL; +} + static int eeepc_hotk_check(void) { + const struct key_entry *key; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; int result; @@ -356,6 +475,31 @@ static int eeepc_hotk_check(void) "Get control methods supported: 0x%x\n", ehotk->cm_supported); } + ehotk->inputdev = input_allocate_device(); + if (!ehotk->inputdev) { + printk(EEEPC_INFO "Unable to allocate input device\n"); + return 0; + } + ehotk->inputdev->name = "Asus EeePC extra buttons"; + ehotk->inputdev->phys = EEEPC_HOTK_FILE "/input0"; + ehotk->inputdev->id.bustype = BUS_HOST; + ehotk->inputdev->getkeycode = eeepc_getkeycode; + ehotk->inputdev->setkeycode = eeepc_setkeycode; + + for (key = eeepc_keymap; key->type != KE_END; key++) { + switch (key->type) { + case KE_KEY: + set_bit(EV_KEY, ehotk->inputdev->evbit); + set_bit(key->keycode, ehotk->inputdev->keybit); + break; + } + } + result = input_register_device(ehotk->inputdev); + if (result) { + printk(EEEPC_INFO "Unable to register input device\n"); + input_free_device(ehotk->inputdev); + return 0; + } } else { printk(EEEPC_ERR "Hotkey device not present, aborting\n"); return -EINVAL; @@ -363,21 +507,6 @@ static int eeepc_hotk_check(void) return 0; } -static void notify_wlan(u32 *event) -{ - /* if DISABLE_ASL_WLAN is set, the notify code for fn+f2 - will always be 0x10 */ - if (ehotk->cm_supported & (0x1 << CM_ASL_WLAN)) { - const char *method = cm_getv[CM_ASL_WLAN]; - int value; - if (read_acpi_int(ehotk->handle, method, &value)) - printk(EEEPC_WARNING "Error reading %s\n", - method); - else if (value == 1) - *event = 0x11; - } -} - static void notify_brn(void) { struct backlight_device *bd = eeepc_backlight_device; @@ -386,14 +515,28 @@ static void notify_brn(void) static void eeepc_hotk_notify(acpi_handle handle, u32 event, void *data) { + static struct key_entry *key; if (!ehotk) return; - if (event == NOTIFY_WLAN_ON && (DISABLE_ASL_WLAN & ehotk->init_flag)) - notify_wlan(&event); if (event >= NOTIFY_BRN_MIN && event <= NOTIFY_BRN_MAX) notify_brn(); acpi_bus_generate_proc_event(ehotk->device, event, ehotk->event_count[event % 128]++); + if (ehotk->inputdev) { + key = eepc_get_entry_by_scancode(event); + if (key) { + switch (key->type) { + case KE_KEY: + input_report_key(ehotk->inputdev, key->keycode, + 1); + input_sync(ehotk->inputdev); + input_report_key(ehotk->inputdev, key->keycode, + 0); + input_sync(ehotk->inputdev); + break; + } + } + } } static int eeepc_hotk_add(struct acpi_device *device) @@ -420,6 +563,47 @@ static int eeepc_hotk_add(struct acpi_device *device) eeepc_hotk_notify, ehotk); if (ACPI_FAILURE(status)) printk(EEEPC_ERR "Error installing notify handler\n"); + + if (get_acpi(CM_ASL_WLAN) != -1) { + ehotk->eeepc_wlan_rfkill = rfkill_allocate(&device->dev, + RFKILL_TYPE_WLAN); + + if (!ehotk->eeepc_wlan_rfkill) + goto end; + + ehotk->eeepc_wlan_rfkill->name = "eeepc-wlan"; + ehotk->eeepc_wlan_rfkill->toggle_radio = eeepc_wlan_rfkill_set; + ehotk->eeepc_wlan_rfkill->get_state = eeepc_wlan_rfkill_state; + if (get_acpi(CM_ASL_WLAN) == 1) + ehotk->eeepc_wlan_rfkill->state = + RFKILL_STATE_UNBLOCKED; + else + ehotk->eeepc_wlan_rfkill->state = + RFKILL_STATE_SOFT_BLOCKED; + rfkill_register(ehotk->eeepc_wlan_rfkill); + } + + if (get_acpi(CM_ASL_BLUETOOTH) != -1) { + ehotk->eeepc_bluetooth_rfkill = + rfkill_allocate(&device->dev, RFKILL_TYPE_BLUETOOTH); + + if (!ehotk->eeepc_bluetooth_rfkill) + goto end; + + ehotk->eeepc_bluetooth_rfkill->name = "eeepc-bluetooth"; + ehotk->eeepc_bluetooth_rfkill->toggle_radio = + eeepc_bluetooth_rfkill_set; + ehotk->eeepc_bluetooth_rfkill->get_state = + eeepc_bluetooth_rfkill_state; + if (get_acpi(CM_ASL_BLUETOOTH) == 1) + ehotk->eeepc_bluetooth_rfkill->state = + RFKILL_STATE_UNBLOCKED; + else + ehotk->eeepc_bluetooth_rfkill->state = + RFKILL_STATE_SOFT_BLOCKED; + rfkill_register(ehotk->eeepc_bluetooth_rfkill); + } + end: if (result) { kfree(ehotk); @@ -553,6 +737,12 @@ static void eeepc_backlight_exit(void) { if (eeepc_backlight_device) backlight_device_unregister(eeepc_backlight_device); + if (ehotk->inputdev) + input_unregister_device(ehotk->inputdev); + if (ehotk->eeepc_wlan_rfkill) + rfkill_unregister(ehotk->eeepc_wlan_rfkill); + if (ehotk->eeepc_bluetooth_rfkill) + rfkill_unregister(ehotk->eeepc_bluetooth_rfkill); eeepc_backlight_device = NULL; } -- cgit v1.2.3 From d0d0f7432c9cbd52cb2f31d499f8292b13a7ecac Mon Sep 17 00:00:00 2001 From: Matt Mackall Date: Thu, 9 Oct 2008 12:41:50 -0500 Subject: x86: remove magic number from ACPI sleep stack buffer x86_64 SMP suspend to RAM uses a 10k temporary stack for saving the kernel state, but only 4k of it is used. Shrink it to 4k. Signed-off-by: Matt Mackall Acked-by: Pavel Machek Signed-off-by: Len Brown --- arch/x86/kernel/acpi/sleep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c index 426e5d91b63..29cf3403abe 100644 --- a/arch/x86/kernel/acpi/sleep.c +++ b/arch/x86/kernel/acpi/sleep.c @@ -97,7 +97,7 @@ int acpi_save_state_mem(void) #else /* CONFIG_64BIT */ header->trampoline_segment = setup_trampoline() >> 4; #ifdef CONFIG_SMP - stack_start.sp = temp_stack + 4096; + stack_start.sp = temp_stack + sizeof(temp_stack); #endif initial_code = (unsigned long)wakeup_long64; saved_magic = 0x123456789abcdef0; -- cgit v1.2.3 From 5000cadcf3188e935dae28c4fc7e24639704ea55 Mon Sep 17 00:00:00 2001 From: Matt Mackall Date: Thu, 9 Oct 2008 11:56:21 -0500 Subject: x86: trim ACPI sleep stack buffer x86_64 SMP suspend to RAM uses a 10k temporary stack for saving the kernel state, but only 4k of it is used. Shrink it to 4k. Signed-off-by: Matt Mackall Signed-off-by: Len Brown --- arch/x86/kernel/acpi/sleep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c index 29cf3403abe..55d10cbe65b 100644 --- a/arch/x86/kernel/acpi/sleep.c +++ b/arch/x86/kernel/acpi/sleep.c @@ -21,7 +21,7 @@ unsigned long acpi_realmode_flags; static unsigned long acpi_realmode; #if defined(CONFIG_SMP) && defined(CONFIG_64BIT) -static char temp_stack[10240]; +static char temp_stack[4096]; #endif /** -- cgit v1.2.3 From 4b5fcb353ba46adfa2f98546e3d34e7dc65221cb Mon Sep 17 00:00:00 2001 From: Alexey Starikovskiy Date: Wed, 10 Sep 2008 14:33:43 +0400 Subject: ACPI: acpi_driver_data could only be applied to acpi_device Signed-off-by: Alexey Starikovskiy CC: Hannes Reinecke Signed-off-by: Alexey Starikovskiy Signed-off-by: Len Brown --- drivers/acpi/scan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index f6f52c1a2ab..e7b8853c310 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -384,7 +384,7 @@ static int acpi_device_remove(struct device * dev) acpi_drv->ops.remove(acpi_dev, acpi_dev->removal_type); } acpi_dev->driver = NULL; - acpi_driver_data(dev) = NULL; + acpi_driver_data(acpi_dev) = NULL; put_device(dev); return 0; -- cgit v1.2.3 From 9e113e0014204bfb44a2baa29b2a141ede41b074 Mon Sep 17 00:00:00 2001 From: Harvey Harrison Date: Mon, 22 Sep 2008 14:37:29 -0700 Subject: ACPI: toshiba_acpi.c fix sparse signedness mismatch warnings set_bit expects unsigned int, and we start with a u32 anyway. drivers/acpi/toshiba_acpi.c:397:14: warning: incorrect type in argument 1 (different signedness) drivers/acpi/toshiba_acpi.c:397:14: expected unsigned int [usertype] *word drivers/acpi/toshiba_acpi.c:397:14: got int * drivers/acpi/toshiba_acpi.c:399:14: warning: incorrect type in argument 1 (different signedness) drivers/acpi/toshiba_acpi.c:399:14: expected unsigned int [usertype] *word drivers/acpi/toshiba_acpi.c:399:14: got int * drivers/acpi/toshiba_acpi.c:401:14: warning: incorrect type in argument 1 (different signedness) drivers/acpi/toshiba_acpi.c:401:14: expected unsigned int [usertype] *word drivers/acpi/toshiba_acpi.c:401:14: got int * Signed-off-by: Harvey Harrison Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/acpi/toshiba_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/toshiba_acpi.c b/drivers/acpi/toshiba_acpi.c index 0a43c8e0eff..4172d290f27 100644 --- a/drivers/acpi/toshiba_acpi.c +++ b/drivers/acpi/toshiba_acpi.c @@ -392,7 +392,7 @@ static unsigned long write_video(const char *buffer, unsigned long count) hci_read1(HCI_VIDEO_OUT, &video_out, &hci_result); if (hci_result == HCI_SUCCESS) { - int new_video_out = video_out; + unsigned int new_video_out = video_out; if (lcd_out != -1) _set_bit(&new_video_out, HCI_VIDEO_OUT_LCD, lcd_out); if (crt_out != -1) -- cgit v1.2.3 From db89b4f0dbab837d0f3de2c3e9427a8d5393afa3 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Mon, 22 Sep 2008 14:37:34 -0700 Subject: ACPI: catch calls of acpi_driver_data on pointer of wrong type Catch attempts to use of acpi_driver_data on pointers of wrong type. akpm: rewritten to use proper C typechecking and remove the "function"-used-as-lvalue thing. Signed-off-by: Pavel Machek Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- drivers/acpi/ac.c | 2 +- drivers/acpi/acpi_memhotplug.c | 2 +- drivers/acpi/asus_acpi.c | 2 +- drivers/acpi/battery.c | 2 +- drivers/acpi/button.c | 2 +- drivers/acpi/container.c | 2 +- drivers/acpi/ec.c | 4 ++-- drivers/acpi/fan.c | 2 +- drivers/acpi/pci_link.c | 2 +- drivers/acpi/pci_root.c | 2 +- drivers/acpi/power.c | 4 ++-- drivers/acpi/processor_core.c | 2 +- drivers/acpi/sbs.c | 2 +- drivers/acpi/sbshc.c | 4 ++-- drivers/acpi/scan.c | 4 ++-- drivers/acpi/thermal.c | 2 +- drivers/acpi/video.c | 6 +++--- drivers/misc/asus-laptop.c | 2 +- drivers/misc/eeepc-laptop.c | 2 +- drivers/misc/fujitsu-laptop.c | 4 ++-- drivers/misc/intel_menlow.c | 2 +- drivers/misc/thinkpad_acpi.c | 2 +- include/acpi/acpi_bus.h | 6 +++++- 23 files changed, 34 insertions(+), 30 deletions(-) diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 831883b7d6c..8b6a84a43b1 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -269,7 +269,7 @@ static int acpi_ac_add(struct acpi_device *device) ac->device = device; strcpy(acpi_device_name(device), ACPI_AC_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_AC_CLASS); - acpi_driver_data(device) = ac; + device->driver_data = ac; result = acpi_ac_get_state(ac); if (result) diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index 5f1127ad5a9..afd955fc0dc 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -403,7 +403,7 @@ static int acpi_memory_device_add(struct acpi_device *device) mem_device->device = device; sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME); sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS); - acpi_driver_data(device) = mem_device; + device->driver_data = mem_device; /* Get the range from the _CRS */ result = acpi_memory_get_device_resources(mem_device); diff --git a/drivers/acpi/asus_acpi.c b/drivers/acpi/asus_acpi.c index d3d0886d637..91571fc0f17 100644 --- a/drivers/acpi/asus_acpi.c +++ b/drivers/acpi/asus_acpi.c @@ -1321,7 +1321,7 @@ static int asus_hotk_add(struct acpi_device *device) hotk->handle = device->handle; strcpy(acpi_device_name(device), ACPI_HOTK_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_HOTK_CLASS); - acpi_driver_data(device) = hotk; + device->driver_data = hotk; hotk->device = device; result = asus_hotk_check(); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index b1c723f9f58..de8046933bc 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -804,7 +804,7 @@ static int acpi_battery_add(struct acpi_device *device) battery->device = device; strcpy(acpi_device_name(device), ACPI_BATTERY_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_BATTERY_CLASS); - acpi_driver_data(device) = battery; + device->driver_data = battery; mutex_init(&battery->lock); acpi_battery_update(battery); #ifdef CONFIG_ACPI_PROCFS_POWER diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 1dfec413588..e22033ea261 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -384,7 +384,7 @@ static int acpi_button_add(struct acpi_device *device) return -ENOMEM; button->device = device; - acpi_driver_data(device) = button; + device->driver_data = button; button->input = input = input_allocate_device(); if (!input) { diff --git a/drivers/acpi/container.c b/drivers/acpi/container.c index 3c25ec7a187..03ea8478e39 100644 --- a/drivers/acpi/container.c +++ b/drivers/acpi/container.c @@ -108,7 +108,7 @@ static int acpi_container_add(struct acpi_device *device) container->handle = device->handle; strcpy(acpi_device_name(device), ACPI_CONTAINER_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_CONTAINER_CLASS); - acpi_driver_data(device) = container; + device->driver_data = container; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device <%s> bid <%s>\n", acpi_device_name(device), acpi_device_bid(device))); diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 13593f9f219..5741d99d603 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -788,7 +788,7 @@ static int acpi_ec_add(struct acpi_device *device) if (!first_ec) first_ec = ec; - acpi_driver_data(device) = ec; + device->driver_data = ec; acpi_ec_add_fs(device); pr_info(PREFIX "GPE = 0x%lx, I/O: command/status = 0x%lx, data = 0x%lx\n", ec->gpe, ec->command_addr, ec->data_addr); @@ -813,7 +813,7 @@ static int acpi_ec_remove(struct acpi_device *device, int type) } mutex_unlock(&ec->lock); acpi_ec_remove_fs(device); - acpi_driver_data(device) = NULL; + device->driver_data = NULL; if (ec == first_ec) first_ec = NULL; kfree(ec); diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index 2655bc1b4ee..e603e407070 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -265,7 +265,7 @@ static int acpi_fan_add(struct acpi_device *device) dev_info(&device->dev, "registered as cooling_device%d\n", cdev->id); - acpi_driver_data(device) = cdev; + device->driver_data = cdev; result = sysfs_create_link(&device->dev.kobj, &cdev->device.kobj, "thermal_cooling"); diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index cf47805a744..54cd77a0a78 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -737,7 +737,7 @@ static int acpi_pci_link_add(struct acpi_device *device) link->device = device; strcpy(acpi_device_name(device), ACPI_PCI_LINK_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_PCI_LINK_CLASS); - acpi_driver_data(device) = link; + device->driver_data = link; mutex_lock(&acpi_link_lock); result = acpi_pci_link_get_possible(link); diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index c3fed31166b..add1a19bbbf 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -206,7 +206,7 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) root->device = device; strcpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); - acpi_driver_data(device) = root; + device->driver_data = root; device->ops.bind = acpi_pci_bind; diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 4ab21cb1c8c..1bef9429189 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -657,7 +657,7 @@ static int acpi_power_add(struct acpi_device *device) strcpy(resource->name, device->pnp.bus_id); strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_POWER_CLASS); - acpi_driver_data(device) = resource; + device->driver_data = resource; /* Evalute the object to get the system level and resource order. */ status = acpi_evaluate_object(device->handle, NULL, NULL, &buffer); @@ -733,7 +733,7 @@ static int acpi_power_resume(struct acpi_device *device) if (!device || !acpi_driver_data(device)) return -EINVAL; - resource = (struct acpi_power_resource *)acpi_driver_data(device); + resource = acpi_driver_data(device); result = acpi_power_get_state(resource, &state); if (result) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index ee68ac54c0d..28946164919 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -818,7 +818,7 @@ static int acpi_processor_add(struct acpi_device *device) pr->handle = device->handle; strcpy(acpi_device_name(device), ACPI_PROCESSOR_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_PROCESSOR_CLASS); - acpi_driver_data(device) = pr; + device->driver_data = pr; return 0; } diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 10a36512647..d0033250b5a 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -931,7 +931,7 @@ static int acpi_sbs_add(struct acpi_device *device) sbs->device = device; strcpy(acpi_device_name(device), ACPI_SBS_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_SBS_CLASS); - acpi_driver_data(device) = sbs; + device->driver_data = sbs; result = acpi_charger_add(sbs); if (result) diff --git a/drivers/acpi/sbshc.c b/drivers/acpi/sbshc.c index a4e3767b8c6..001d9143a2c 100644 --- a/drivers/acpi/sbshc.c +++ b/drivers/acpi/sbshc.c @@ -282,7 +282,7 @@ static int acpi_smbus_hc_add(struct acpi_device *device) hc->ec = acpi_driver_data(device->parent); hc->offset = (val >> 8) & 0xff; hc->query_bit = val & 0xff; - acpi_driver_data(device) = hc; + device->driver_data = hc; acpi_ec_add_query_handler(hc->ec, hc->query_bit, NULL, smbus_alarm, hc); printk(KERN_INFO PREFIX "SBS HC: EC = 0x%p, offset = 0x%0x, query_bit = 0x%0x\n", @@ -303,7 +303,7 @@ static int acpi_smbus_hc_remove(struct acpi_device *device, int type) hc = acpi_driver_data(device); acpi_ec_remove_query_handler(hc->ec, hc->query_bit); kfree(hc); - acpi_driver_data(device) = NULL; + device->driver_data = NULL; return 0; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index e7b8853c310..b88536ab040 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -384,7 +384,7 @@ static int acpi_device_remove(struct device * dev) acpi_drv->ops.remove(acpi_dev, acpi_dev->removal_type); } acpi_dev->driver = NULL; - acpi_driver_data(acpi_dev) = NULL; + acpi_dev->driver_data = NULL; put_device(dev); return 0; @@ -537,7 +537,7 @@ acpi_bus_driver_init(struct acpi_device *device, struct acpi_driver *driver) result = driver->ops.add(device); if (result) { device->driver = NULL; - acpi_driver_data(device) = NULL; + device->driver_data = NULL; return result; } diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 912703691d3..e052a75c29c 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -1647,7 +1647,7 @@ static int acpi_thermal_add(struct acpi_device *device) strcpy(tz->name, device->pnp.bus_id); strcpy(acpi_device_name(device), ACPI_THERMAL_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_THERMAL_CLASS); - acpi_driver_data(device) = tz; + device->driver_data = tz; mutex_init(&tz->lock); diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index e8a51a1700f..85c6554bcb4 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -1491,7 +1491,7 @@ acpi_video_bus_get_one_device(struct acpi_device *device, strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME); strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); - acpi_driver_data(device) = data; + device->driver_data = data; data->device_id = device_id; data->video = video; @@ -1982,7 +1982,7 @@ static int acpi_video_bus_add(struct acpi_device *device) video->device = device; strcpy(acpi_device_name(device), ACPI_VIDEO_BUS_NAME); strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); - acpi_driver_data(device) = video; + device->driver_data = video; acpi_video_bus_find_cap(video); error = acpi_video_bus_check(video); @@ -2058,7 +2058,7 @@ static int acpi_video_bus_add(struct acpi_device *device) acpi_video_bus_remove_fs(device); err_free_video: kfree(video); - acpi_driver_data(device) = NULL; + device->driver_data = NULL; return error; } diff --git a/drivers/misc/asus-laptop.c b/drivers/misc/asus-laptop.c index 7c6dfd03de9..de82f2f5a81 100644 --- a/drivers/misc/asus-laptop.c +++ b/drivers/misc/asus-laptop.c @@ -996,7 +996,7 @@ static int asus_hotk_add(struct acpi_device *device) hotk->handle = device->handle; strcpy(acpi_device_name(device), ASUS_HOTK_DEVICE_NAME); strcpy(acpi_device_class(device), ASUS_HOTK_CLASS); - acpi_driver_data(device) = hotk; + device->driver_data = hotk; hotk->device = device; result = asus_hotk_check(); diff --git a/drivers/misc/eeepc-laptop.c b/drivers/misc/eeepc-laptop.c index 1ee8501e90f..c1247056116 100644 --- a/drivers/misc/eeepc-laptop.c +++ b/drivers/misc/eeepc-laptop.c @@ -411,7 +411,7 @@ static int eeepc_hotk_add(struct acpi_device *device) ehotk->handle = device->handle; strcpy(acpi_device_name(device), EEEPC_HOTK_DEVICE_NAME); strcpy(acpi_device_class(device), EEEPC_HOTK_CLASS); - acpi_driver_data(device) = ehotk; + device->driver_data = ehotk; ehotk->device = device; result = eeepc_hotk_check(); if (result) diff --git a/drivers/misc/fujitsu-laptop.c b/drivers/misc/fujitsu-laptop.c index 3e56203e494..7b69cfb7de5 100644 --- a/drivers/misc/fujitsu-laptop.c +++ b/drivers/misc/fujitsu-laptop.c @@ -490,7 +490,7 @@ static int acpi_fujitsu_add(struct acpi_device *device) fujitsu->acpi_handle = device->handle; sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_DEVICE_NAME); sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS); - acpi_driver_data(device) = fujitsu; + device->driver_data = fujitsu; status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, @@ -703,7 +703,7 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_HOTKEY_DEVICE_NAME); sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS); - acpi_driver_data(device) = fujitsu_hotkey; + device->driver_data = fujitsu_hotkey; status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, diff --git a/drivers/misc/intel_menlow.c b/drivers/misc/intel_menlow.c index 80a13635240..cd3ea7faa5b 100644 --- a/drivers/misc/intel_menlow.c +++ b/drivers/misc/intel_menlow.c @@ -175,7 +175,7 @@ static int intel_menlow_memory_add(struct acpi_device *device) goto end; } - acpi_driver_data(device) = cdev; + device->driver_data = cdev; result = sysfs_create_link(&device->dev.kobj, &cdev->device.kobj, "thermal_cooling"); if (result) diff --git a/drivers/misc/thinkpad_acpi.c b/drivers/misc/thinkpad_acpi.c index 6b9300779a4..62aebaa8591 100644 --- a/drivers/misc/thinkpad_acpi.c +++ b/drivers/misc/thinkpad_acpi.c @@ -543,7 +543,7 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) return -ENODEV; } - acpi_driver_data(ibm->acpi->device) = ibm; + ibm->acpi->device->driver_data = ibm; sprintf(acpi_device_class(ibm->acpi->device), "%s/%s", TPACPI_ACPI_EVENT_PREFIX, ibm->name); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a5ac0bc7f52..47682465f1f 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -300,7 +300,11 @@ struct acpi_device { enum acpi_bus_removal_type removal_type; /* indicate for different removal type */ }; -#define acpi_driver_data(d) ((d)->driver_data) +static inline void *acpi_driver_data(struct acpi_device *d) +{ + return d->driver_data; +} + #define to_acpi_device(d) container_of(d, struct acpi_device, dev) #define to_acpi_driver(d) container_of(d, struct acpi_driver, drv) -- cgit v1.2.3 From b3cef8f6eba47a10b80aa3e7a99e27110e34f3df Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 19 Sep 2008 14:06:59 -0700 Subject: toshiba_acpi: depends on INPUT CONFIG_ACPI_TOSHIBA can =y when CONFIG_INPUT=m, so prevent that combination and its subsequent build errors: toshiba_acpi.c:(.text+0x3e877): undefined reference to `input_event' toshiba_acpi.c:(.text+0x3e98a): undefined reference to `input_unregister_polled_device' toshiba_acpi.c:(.text+0x3e994): undefined reference to `input_free_polled_device' toshiba_acpi.c:(.init.text+0x21b4): undefined reference to `input_allocate_polled_device' toshiba_acpi.c:(.init.text+0x2263): undefined reference to `input_register_polled_device' make[1]: *** [.tmp_vmlinux1] Error 1 Signed-off-by: Randy Dunlap Signed-off-by: Len Brown --- drivers/acpi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 735f5ea1747..79540db2980 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -259,7 +259,7 @@ config ACPI_ASUS config ACPI_TOSHIBA tristate "Toshiba Laptop Extras" - depends on X86 + depends on X86 && INPUT select BACKLIGHT_CLASS_DEVICE ---help--- This driver adds support for access to certain system settings -- cgit v1.2.3 From 2767840a5ca73fde62b25e0209aa9269ec4fa7c7 Mon Sep 17 00:00:00 2001 From: Roland Dreier Date: Fri, 10 Oct 2008 15:58:52 -0700 Subject: IPoIB: Always initialize poll_timer to avoid crash on unload ipoib_ib_dev_stop() does del_timer_sync(&priv->poll_timer), but if a P_key for an interface is not found, poll_timer is not initialized, so this leads to a crash or hang. Fix this by moving where poll_timer is initialized to ipoib_ib_dev_init(), which is always called. This fixes . Debugged-by: Yosef Etigin Signed-off-by: Roland Dreier --- drivers/infiniband/ulp/ipoib/ipoib_ib.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/infiniband/ulp/ipoib/ipoib_ib.c b/drivers/infiniband/ulp/ipoib/ipoib_ib.c index 0e748aeeae9..28eb6f03c58 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_ib.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_ib.c @@ -685,10 +685,6 @@ int ipoib_ib_dev_open(struct net_device *dev) queue_delayed_work(ipoib_workqueue, &priv->ah_reap_task, round_jiffies_relative(HZ)); - init_timer(&priv->poll_timer); - priv->poll_timer.function = ipoib_ib_tx_timer_func; - priv->poll_timer.data = (unsigned long)dev; - set_bit(IPOIB_FLAG_INITIALIZED, &priv->flags); return 0; @@ -906,6 +902,9 @@ int ipoib_ib_dev_init(struct net_device *dev, struct ib_device *ca, int port) return -ENODEV; } + setup_timer(&priv->poll_timer, ipoib_ib_tx_timer_func, + (unsigned long) dev); + if (dev->flags & IFF_UP) { if (ipoib_ib_dev_open(dev)) { ipoib_transport_dev_cleanup(dev); -- cgit v1.2.3 From 6a0b93bae96f292b6c091cce5abfb9a644e649e9 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:16 -0600 Subject: PNP: fix debug formatting (cosmetic) This patch just fixes indentation of a couple debug messages. Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/pnp/resource.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pnp/resource.c b/drivers/pnp/resource.c index 4cfe3a1efdf..e0206da6851 100644 --- a/drivers/pnp/resource.c +++ b/drivers/pnp/resource.c @@ -294,7 +294,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, u8 progif; if (pci->irq == irq) { - dev_dbg(&pnp->dev, "device %s using irq %d\n", + dev_dbg(&pnp->dev, " device %s using irq %d\n", pci_name(pci), irq); return 1; } @@ -316,7 +316,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, if ((progif & 0x5) != 0x5) if (pci_get_legacy_ide_irq(pci, 0) == irq || pci_get_legacy_ide_irq(pci, 1) == irq) { - dev_dbg(&pnp->dev, "legacy IDE device %s " + dev_dbg(&pnp->dev, " legacy IDE device %s " "using irq %d\n", pci_name(pci), irq); return 1; } -- cgit v1.2.3 From 958a1fdd3904b009c926cb840be48ce3c5f2389e Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:21 -0600 Subject: PNPACPI: use dev_printk when possible Use dev_printk() when possible for more informative error messages. Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/pnp/pnpacpi/rsparser.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/pnp/pnpacpi/rsparser.c b/drivers/pnp/pnpacpi/rsparser.c index 95015cbfd33..0461d4b82c4 100644 --- a/drivers/pnp/pnpacpi/rsparser.c +++ b/drivers/pnp/pnpacpi/rsparser.c @@ -132,7 +132,8 @@ static void pnpacpi_parse_allocated_irqresource(struct pnp_dev *dev, pnp_add_irq_resource(dev, irq, flags); } -static int dma_flags(int type, int bus_master, int transfer) +static int dma_flags(struct pnp_dev *dev, int type, int bus_master, + int transfer) { int flags = 0; @@ -154,7 +155,7 @@ static int dma_flags(int type, int bus_master, int transfer) default: /* Set a default value ? */ flags |= IORESOURCE_DMA_COMPATIBLE; - pnp_err("Invalid DMA type"); + dev_err(&dev->dev, "invalid DMA type %d\n", type); } switch (transfer) { case ACPI_TRANSFER_8: @@ -169,7 +170,7 @@ static int dma_flags(int type, int bus_master, int transfer) default: /* Set a default value ? */ flags |= IORESOURCE_DMA_8AND16BIT; - pnp_err("Invalid DMA transfer type"); + dev_err(&dev->dev, "invalid DMA transfer type %d\n", transfer); } return flags; @@ -336,7 +337,7 @@ static acpi_status pnpacpi_allocated_resource(struct acpi_resource *res, case ACPI_RESOURCE_TYPE_DMA: dma = &res->data.dma; if (dma->channel_count > 0 && dma->channels[0] != (u8) -1) - flags = dma_flags(dma->type, dma->bus_master, + flags = dma_flags(dev, dma->type, dma->bus_master, dma->transfer); else flags = IORESOURCE_DISABLED; @@ -477,7 +478,7 @@ static __init void pnpacpi_parse_dma_option(struct pnp_dev *dev, for (i = 0; i < p->channel_count; i++) map |= 1 << p->channels[i]; - flags = dma_flags(p->type, p->bus_master, p->transfer); + flags = dma_flags(dev, p->type, p->bus_master, p->transfer); pnp_register_dma_resource(dev, option_flags, map, flags); } @@ -608,8 +609,8 @@ static __init void pnpacpi_parse_address_option(struct pnp_dev *dev, unsigned char flags = 0; status = acpi_resource_to_address64(r, p); - if (!ACPI_SUCCESS(status)) { - pnp_warn("PnPACPI: failed to convert resource type %d", + if (ACPI_FAILURE(status)) { + dev_warn(&dev->dev, "can't convert resource type %d\n", r->type); return; } -- cgit v1.2.3 From c865d2f6eb160c15b74245b4891c8e945d67d96c Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:26 -0600 Subject: PNP: convert the last few pnp_info() uses to printk() There are only a few remaining uses of pnp_info(), so I just converted them to printk and removed the pnp_err(), pnp_info(), pnp_warn(), and pnp_dbg() wrappers. I also removed a couple debug messages that don't seem useful any more ("driver registered", "driver unregistered", "driver attached"). Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/pnp/driver.c | 4 ---- drivers/pnp/pnpacpi/core.c | 6 +++--- include/linux/pnp.h | 10 ---------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/drivers/pnp/driver.c b/drivers/pnp/driver.c index d3f869ee1d9..54673a4f590 100644 --- a/drivers/pnp/driver.c +++ b/drivers/pnp/driver.c @@ -114,7 +114,6 @@ static int pnp_device_probe(struct device *dev) } else goto fail; - dev_dbg(dev, "driver attached\n"); return error; fail: @@ -210,8 +209,6 @@ struct bus_type pnp_bus_type = { int pnp_register_driver(struct pnp_driver *drv) { - pnp_dbg("the driver '%s' has been registered", drv->name); - drv->driver.name = drv->name; drv->driver.bus = &pnp_bus_type; @@ -221,7 +218,6 @@ int pnp_register_driver(struct pnp_driver *drv) void pnp_unregister_driver(struct pnp_driver *drv) { driver_unregister(&drv->driver); - pnp_dbg("the driver '%s' has been unregistered", drv->name); } /** diff --git a/drivers/pnp/pnpacpi/core.c b/drivers/pnp/pnpacpi/core.c index c1b9ea34977..67c651bcaf7 100644 --- a/drivers/pnp/pnpacpi/core.c +++ b/drivers/pnp/pnpacpi/core.c @@ -255,14 +255,14 @@ int pnpacpi_disabled __initdata; static int __init pnpacpi_init(void) { if (acpi_disabled || pnpacpi_disabled) { - pnp_info("PnP ACPI: disabled"); + printk(KERN_INFO "pnp: PnP ACPI: disabled\n"); return 0; } - pnp_info("PnP ACPI init"); + printk(KERN_INFO "pnp: PnP ACPI init\n"); pnp_register_protocol(&pnpacpi_protocol); register_acpi_bus_type(&acpi_pnp_bus); acpi_get_devices(NULL, pnpacpi_add_device_handler, NULL, NULL); - pnp_info("PnP ACPI: found %d devices", num); + printk(KERN_INFO "pnp: PnP ACPI: found %d devices\n", num); unregister_acpi_bus_type(&acpi_pnp_bus); pnp_platform_devices = 1; return 0; diff --git a/include/linux/pnp.h b/include/linux/pnp.h index be764e514e3..05daecec16c 100644 --- a/include/linux/pnp.h +++ b/include/linux/pnp.h @@ -483,14 +483,4 @@ static inline void pnp_unregister_driver(struct pnp_driver *drv) { } #endif /* CONFIG_PNP */ -#define pnp_err(format, arg...) printk(KERN_ERR "pnp: " format "\n" , ## arg) -#define pnp_info(format, arg...) printk(KERN_INFO "pnp: " format "\n" , ## arg) -#define pnp_warn(format, arg...) printk(KERN_WARNING "pnp: " format "\n" , ## arg) - -#ifdef CONFIG_PNP_DEBUG -#define pnp_dbg(format, arg...) printk(KERN_DEBUG "pnp: " format "\n" , ## arg) -#else -#define pnp_dbg(format, arg...) do {} while (0) -#endif - #endif /* _LINUX_PNP_H */ -- cgit v1.2.3 From 668b21de141edcba3fb13a4f810641eea4740fd7 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:31 -0600 Subject: PNP: use new vsprintf symbolic function pointer format Use the '%pF' format to get rid of an "#ifdef DEBUG". Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/pnp/quirks.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/pnp/quirks.c b/drivers/pnp/quirks.c index 0bdf9b8a5e5..414bb094572 100644 --- a/drivers/pnp/quirks.c +++ b/drivers/pnp/quirks.c @@ -337,10 +337,8 @@ void pnp_fixup_device(struct pnp_dev *dev) for (f = pnp_fixups; *f->id; f++) { if (!compare_pnp_id(dev->id, f->id)) continue; -#ifdef DEBUG - dev_dbg(&dev->dev, "%s: calling ", f->id); - print_fn_descriptor_symbol("%s\n", f->quirk_function); -#endif + dev_dbg(&dev->dev, "%s: calling %pF\n", f->id, + f->quirk_function); f->quirk_function(dev); } } -- cgit v1.2.3 From 2663f60d434139298835af690f4891cec5c1e501 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:36 -0600 Subject: PNP: remove some uses of DEBUG ifdef Use scnprintf() to build up a buffer of PNP IDs to print. This makes the printk atomic and helps get rid of an #ifdef. Also remove an "#ifdef DEBUG" from some debug functions. The functions only produce debug output, so it's OK to run the function and just have the output be dropped at the end. Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/pnp/core.c | 18 ++++++++---------- drivers/pnp/support.c | 4 ---- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/drivers/pnp/core.c b/drivers/pnp/core.c index a411582bcd7..7cb1ffc14d4 100644 --- a/drivers/pnp/core.c +++ b/drivers/pnp/core.c @@ -185,6 +185,9 @@ int __pnp_add_device(struct pnp_dev *dev) int pnp_add_device(struct pnp_dev *dev) { int ret; + char buf[128]; + int len = 0; + struct pnp_id *id; if (dev->card) return -EINVAL; @@ -193,17 +196,12 @@ int pnp_add_device(struct pnp_dev *dev) if (ret) return ret; -#ifdef CONFIG_PNP_DEBUG - { - struct pnp_id *id; + buf[0] = '\0'; + for (id = dev->id; id; id = id->next) + len += scnprintf(buf + len, sizeof(buf) - len, " %s", id->id); - dev_printk(KERN_DEBUG, &dev->dev, "%s device, IDs", - dev->protocol->name); - for (id = dev->id; id; id = id->next) - printk(" %s", id->id); - printk(" (%s)\n", dev->active ? "active" : "disabled"); - } -#endif + dev_dbg(&dev->dev, "%s device, IDs%s (%s)\n", + dev->protocol->name, buf, dev->active ? "active" : "disabled"); return 0; } diff --git a/drivers/pnp/support.c b/drivers/pnp/support.c index b42df162071..714918655e0 100644 --- a/drivers/pnp/support.c +++ b/drivers/pnp/support.c @@ -75,7 +75,6 @@ char *pnp_resource_type_name(struct resource *res) void dbg_pnp_show_resources(struct pnp_dev *dev, char *desc) { -#ifdef DEBUG char buf[128]; int len; struct pnp_resource *pnp_res; @@ -118,7 +117,6 @@ void dbg_pnp_show_resources(struct pnp_dev *dev, char *desc) } dev_dbg(&dev->dev, "%s\n", buf); } -#endif } char *pnp_option_priority_name(struct pnp_option *option) @@ -136,7 +134,6 @@ char *pnp_option_priority_name(struct pnp_option *option) void dbg_pnp_show_option(struct pnp_dev *dev, struct pnp_option *option) { -#ifdef DEBUG char buf[128]; int len = 0, i; struct pnp_port *port; @@ -209,5 +206,4 @@ void dbg_pnp_show_option(struct pnp_dev *dev, struct pnp_option *option) break; } dev_dbg(&dev->dev, "%s\n", buf); -#endif } -- cgit v1.2.3 From 97ef062bbe08f46903f29ecdf432be302c977f3b Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:41 -0600 Subject: PNP: add CONFIG_PNP_DEBUG_MESSAGES and pnp_dbg() This adds the core function pnp_dbg() and a new config option to enable it. The PNP core debugging messages can be enabled at boot-time with the "pnp.debug" kernel parameter. Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- Documentation/kernel-parameters.txt | 4 ++++ drivers/pnp/Kconfig | 14 ++++++++++++++ drivers/pnp/base.h | 10 ++++++++++ drivers/pnp/core.c | 11 +++++++++++ 4 files changed, 39 insertions(+) diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 1150444a21a..7164d4e4136 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1660,6 +1660,10 @@ and is between 256 and 4096 characters. It is defined in the file Override pmtimer IOPort with a hex value. e.g. pmtmr=0x508 + pnp.debug [PNP] + Enable PNP debug messages. This depends on the + CONFIG_PNP_DEBUG_MESSAGES option. + pnpacpi= [ACPI] { off } diff --git a/drivers/pnp/Kconfig b/drivers/pnp/Kconfig index 821933f9aa5..8a3237f9f2a 100644 --- a/drivers/pnp/Kconfig +++ b/drivers/pnp/Kconfig @@ -20,6 +20,20 @@ menuconfig PNP If unsure, say Y. +config PNP_DEBUG_MESSAGES + default y + bool "PNP debugging messages" + depends on PNP + help + Say Y here if you want the PNP layer to be able to produce debugging + messages if needed. The messages can be enabled at boot-time with + the pnp.debug kernel parameter. + + This option allows you to save a bit of space if you do not want + the messages to even be built into the kernel. + + If you have any doubts about this, say Y here. + if PNP config PNP_DEBUG diff --git a/drivers/pnp/base.h b/drivers/pnp/base.h index 9fd7bb9b7dc..5e4e82c8265 100644 --- a/drivers/pnp/base.h +++ b/drivers/pnp/base.h @@ -166,3 +166,13 @@ struct pnp_resource *pnp_add_io_resource(struct pnp_dev *dev, struct pnp_resource *pnp_add_mem_resource(struct pnp_dev *dev, resource_size_t start, resource_size_t end, int flags); + +extern int pnp_debug; + +#if defined(CONFIG_PNP_DEBUG_MESSAGES) +#define pnp_dbg(dev, format, arg...) \ + ({ if (pnp_debug) dev_printk(KERN_DEBUG, dev, format, ## arg); 0; }) +#else +#define pnp_dbg(dev, format, arg...) \ + ({ if (0) dev_printk(KERN_DEBUG, dev, format, ## arg); 0; }) +#endif diff --git a/drivers/pnp/core.c b/drivers/pnp/core.c index 7cb1ffc14d4..61291b5bfe1 100644 --- a/drivers/pnp/core.c +++ b/drivers/pnp/core.c @@ -221,3 +221,14 @@ static int __init pnp_init(void) } subsys_initcall(pnp_init); + +int pnp_debug; + +#if defined(CONFIG_PNP_DEBUG_MESSAGES) +static int __init pnp_debug_setup(char *__unused) +{ + pnp_debug = 1; + return 1; +} +__setup("pnp.debug", pnp_debug_setup); +#endif -- cgit v1.2.3 From 2f53432c2aedbe79020e44525eb069d9138a01dd Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:47 -0600 Subject: PNP: convert to using pnp_dbg() pnp_dbg() is equivalent to dev_dbg() except that we can turn it on at boot-time with the "pnp.debug" kernel parameter, so we don't have to build a new kernel image. Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/pnp/core.c | 2 +- drivers/pnp/isapnp/core.c | 12 ++++++------ drivers/pnp/manager.c | 34 +++++++++++++++++----------------- drivers/pnp/pnpacpi/core.c | 4 ++-- drivers/pnp/pnpacpi/rsparser.c | 28 ++++++++++++++-------------- drivers/pnp/pnpbios/core.c | 4 ++-- drivers/pnp/pnpbios/rsparser.c | 18 +++++++++--------- drivers/pnp/quirks.c | 2 +- drivers/pnp/resource.c | 12 ++++++------ drivers/pnp/support.c | 10 +++++----- 10 files changed, 63 insertions(+), 63 deletions(-) diff --git a/drivers/pnp/core.c b/drivers/pnp/core.c index 61291b5bfe1..521561509a7 100644 --- a/drivers/pnp/core.c +++ b/drivers/pnp/core.c @@ -200,7 +200,7 @@ int pnp_add_device(struct pnp_dev *dev) for (id = dev->id; id; id = id->next) len += scnprintf(buf + len, sizeof(buf) - len, " %s", id->id); - dev_dbg(&dev->dev, "%s device, IDs%s (%s)\n", + pnp_dbg(&dev->dev, "%s device, IDs%s (%s)\n", dev->protocol->name, buf, dev->active ? "active" : "disabled"); return 0; } diff --git a/drivers/pnp/isapnp/core.c b/drivers/pnp/isapnp/core.c index 101a835e875..bd213ffbcd9 100644 --- a/drivers/pnp/isapnp/core.c +++ b/drivers/pnp/isapnp/core.c @@ -901,7 +901,7 @@ static int isapnp_get_resources(struct pnp_dev *dev) { int i, ret; - dev_dbg(&dev->dev, "get resources\n"); + pnp_dbg(&dev->dev, "get resources\n"); pnp_init_resources(dev); isapnp_cfg_begin(dev->card->number, dev->number); dev->active = isapnp_read_byte(ISAPNP_CFG_ACTIVATE); @@ -939,13 +939,13 @@ static int isapnp_set_resources(struct pnp_dev *dev) struct resource *res; int tmp; - dev_dbg(&dev->dev, "set resources\n"); + pnp_dbg(&dev->dev, "set resources\n"); isapnp_cfg_begin(dev->card->number, dev->number); dev->active = 1; for (tmp = 0; tmp < ISAPNP_MAX_PORT; tmp++) { res = pnp_get_resource(dev, IORESOURCE_IO, tmp); if (pnp_resource_enabled(res)) { - dev_dbg(&dev->dev, " set io %d to %#llx\n", + pnp_dbg(&dev->dev, " set io %d to %#llx\n", tmp, (unsigned long long) res->start); isapnp_write_word(ISAPNP_CFG_PORT + (tmp << 1), res->start); @@ -957,14 +957,14 @@ static int isapnp_set_resources(struct pnp_dev *dev) int irq = res->start; if (irq == 2) irq = 9; - dev_dbg(&dev->dev, " set irq %d to %d\n", tmp, irq); + pnp_dbg(&dev->dev, " set irq %d to %d\n", tmp, irq); isapnp_write_byte(ISAPNP_CFG_IRQ + (tmp << 1), irq); } } for (tmp = 0; tmp < ISAPNP_MAX_DMA; tmp++) { res = pnp_get_resource(dev, IORESOURCE_DMA, tmp); if (pnp_resource_enabled(res)) { - dev_dbg(&dev->dev, " set dma %d to %lld\n", + pnp_dbg(&dev->dev, " set dma %d to %lld\n", tmp, (unsigned long long) res->start); isapnp_write_byte(ISAPNP_CFG_DMA + tmp, res->start); } @@ -972,7 +972,7 @@ static int isapnp_set_resources(struct pnp_dev *dev) for (tmp = 0; tmp < ISAPNP_MAX_MEM; tmp++) { res = pnp_get_resource(dev, IORESOURCE_MEM, tmp); if (pnp_resource_enabled(res)) { - dev_dbg(&dev->dev, " set mem %d to %#llx\n", + pnp_dbg(&dev->dev, " set mem %d to %#llx\n", tmp, (unsigned long long) res->start); isapnp_write_word(ISAPNP_CFG_MEM + (tmp << 3), (res->start >> 8) & 0xffff); diff --git a/drivers/pnp/manager.c b/drivers/pnp/manager.c index b526eaad3f6..00fd3577b98 100644 --- a/drivers/pnp/manager.c +++ b/drivers/pnp/manager.c @@ -25,7 +25,7 @@ static int pnp_assign_port(struct pnp_dev *dev, struct pnp_port *rule, int idx) res = pnp_get_resource(dev, IORESOURCE_IO, idx); if (res) { - dev_dbg(&dev->dev, " io %d already set to %#llx-%#llx " + pnp_dbg(&dev->dev, " io %d already set to %#llx-%#llx " "flags %#lx\n", idx, (unsigned long long) res->start, (unsigned long long) res->end, res->flags); return 0; @@ -38,7 +38,7 @@ static int pnp_assign_port(struct pnp_dev *dev, struct pnp_port *rule, int idx) if (!rule->size) { res->flags |= IORESOURCE_DISABLED; - dev_dbg(&dev->dev, " io %d disabled\n", idx); + pnp_dbg(&dev->dev, " io %d disabled\n", idx); goto __add; } @@ -49,7 +49,7 @@ static int pnp_assign_port(struct pnp_dev *dev, struct pnp_port *rule, int idx) res->start += rule->align; res->end = res->start + rule->size - 1; if (res->start > rule->max || !rule->align) { - dev_dbg(&dev->dev, " couldn't assign io %d " + pnp_dbg(&dev->dev, " couldn't assign io %d " "(min %#llx max %#llx)\n", idx, (unsigned long long) rule->min, (unsigned long long) rule->max); @@ -68,7 +68,7 @@ static int pnp_assign_mem(struct pnp_dev *dev, struct pnp_mem *rule, int idx) res = pnp_get_resource(dev, IORESOURCE_MEM, idx); if (res) { - dev_dbg(&dev->dev, " mem %d already set to %#llx-%#llx " + pnp_dbg(&dev->dev, " mem %d already set to %#llx-%#llx " "flags %#lx\n", idx, (unsigned long long) res->start, (unsigned long long) res->end, res->flags); return 0; @@ -90,7 +90,7 @@ static int pnp_assign_mem(struct pnp_dev *dev, struct pnp_mem *rule, int idx) if (!rule->size) { res->flags |= IORESOURCE_DISABLED; - dev_dbg(&dev->dev, " mem %d disabled\n", idx); + pnp_dbg(&dev->dev, " mem %d disabled\n", idx); goto __add; } @@ -101,7 +101,7 @@ static int pnp_assign_mem(struct pnp_dev *dev, struct pnp_mem *rule, int idx) res->start += rule->align; res->end = res->start + rule->size - 1; if (res->start > rule->max || !rule->align) { - dev_dbg(&dev->dev, " couldn't assign mem %d " + pnp_dbg(&dev->dev, " couldn't assign mem %d " "(min %#llx max %#llx)\n", idx, (unsigned long long) rule->min, (unsigned long long) rule->max); @@ -126,7 +126,7 @@ static int pnp_assign_irq(struct pnp_dev *dev, struct pnp_irq *rule, int idx) res = pnp_get_resource(dev, IORESOURCE_IRQ, idx); if (res) { - dev_dbg(&dev->dev, " irq %d already set to %d flags %#lx\n", + pnp_dbg(&dev->dev, " irq %d already set to %d flags %#lx\n", idx, (int) res->start, res->flags); return 0; } @@ -138,7 +138,7 @@ static int pnp_assign_irq(struct pnp_dev *dev, struct pnp_irq *rule, int idx) if (bitmap_empty(rule->map.bits, PNP_IRQ_NR)) { res->flags |= IORESOURCE_DISABLED; - dev_dbg(&dev->dev, " irq %d disabled\n", idx); + pnp_dbg(&dev->dev, " irq %d disabled\n", idx); goto __add; } @@ -160,11 +160,11 @@ static int pnp_assign_irq(struct pnp_dev *dev, struct pnp_irq *rule, int idx) res->start = -1; res->end = -1; res->flags |= IORESOURCE_DISABLED; - dev_dbg(&dev->dev, " irq %d disabled (optional)\n", idx); + pnp_dbg(&dev->dev, " irq %d disabled (optional)\n", idx); goto __add; } - dev_dbg(&dev->dev, " couldn't assign irq %d\n", idx); + pnp_dbg(&dev->dev, " couldn't assign irq %d\n", idx); return -EBUSY; __add: @@ -184,7 +184,7 @@ static int pnp_assign_dma(struct pnp_dev *dev, struct pnp_dma *rule, int idx) res = pnp_get_resource(dev, IORESOURCE_DMA, idx); if (res) { - dev_dbg(&dev->dev, " dma %d already set to %d flags %#lx\n", + pnp_dbg(&dev->dev, " dma %d already set to %d flags %#lx\n", idx, (int) res->start, res->flags); return 0; } @@ -205,7 +205,7 @@ static int pnp_assign_dma(struct pnp_dev *dev, struct pnp_dma *rule, int idx) res->start = res->end = MAX_DMA_CHANNELS; #endif res->flags |= IORESOURCE_DISABLED; - dev_dbg(&dev->dev, " disable dma %d\n", idx); + pnp_dbg(&dev->dev, " disable dma %d\n", idx); __add: pnp_add_dma_resource(dev, res->start, res->flags); @@ -238,7 +238,7 @@ static int pnp_assign_resources(struct pnp_dev *dev, int set) int nport = 0, nmem = 0, nirq = 0, ndma = 0; int ret = 0; - dev_dbg(&dev->dev, "pnp_assign_resources, try dependent set %d\n", set); + pnp_dbg(&dev->dev, "pnp_assign_resources, try dependent set %d\n", set); mutex_lock(&pnp_res_mutex); pnp_clean_resource_table(dev); @@ -270,7 +270,7 @@ static int pnp_assign_resources(struct pnp_dev *dev, int set) mutex_unlock(&pnp_res_mutex); if (ret < 0) { - dev_dbg(&dev->dev, "pnp_assign_resources failed (%d)\n", ret); + pnp_dbg(&dev->dev, "pnp_assign_resources failed (%d)\n", ret); pnp_clean_resource_table(dev); } else dbg_pnp_show_resources(dev, "pnp_assign_resources succeeded"); @@ -286,7 +286,7 @@ int pnp_auto_config_dev(struct pnp_dev *dev) int i, ret; if (!pnp_can_configure(dev)) { - dev_dbg(&dev->dev, "configuration not supported\n"); + pnp_dbg(&dev->dev, "configuration not supported\n"); return -ENODEV; } @@ -313,7 +313,7 @@ int pnp_auto_config_dev(struct pnp_dev *dev) int pnp_start_dev(struct pnp_dev *dev) { if (!pnp_can_write(dev)) { - dev_dbg(&dev->dev, "activation not supported\n"); + pnp_dbg(&dev->dev, "activation not supported\n"); return -EINVAL; } @@ -336,7 +336,7 @@ int pnp_start_dev(struct pnp_dev *dev) int pnp_stop_dev(struct pnp_dev *dev) { if (!pnp_can_disable(dev)) { - dev_dbg(&dev->dev, "disabling not supported\n"); + pnp_dbg(&dev->dev, "disabling not supported\n"); return -EINVAL; } if (dev->protocol->disable(dev) < 0) { diff --git a/drivers/pnp/pnpacpi/core.c b/drivers/pnp/pnpacpi/core.c index 67c651bcaf7..b57d9d59dca 100644 --- a/drivers/pnp/pnpacpi/core.c +++ b/drivers/pnp/pnpacpi/core.c @@ -75,7 +75,7 @@ static int __init ispnpidacpi(char *id) static int pnpacpi_get_resources(struct pnp_dev *dev) { - dev_dbg(&dev->dev, "get resources\n"); + pnp_dbg(&dev->dev, "get resources\n"); return pnpacpi_parse_allocated_resource(dev); } @@ -86,7 +86,7 @@ static int pnpacpi_set_resources(struct pnp_dev *dev) int ret; acpi_status status; - dev_dbg(&dev->dev, "set resources\n"); + pnp_dbg(&dev->dev, "set resources\n"); ret = pnpacpi_build_resource_template(dev, &buffer); if (ret) return ret; diff --git a/drivers/pnp/pnpacpi/rsparser.c b/drivers/pnp/pnpacpi/rsparser.c index 0461d4b82c4..adf17856bac 100644 --- a/drivers/pnp/pnpacpi/rsparser.c +++ b/drivers/pnp/pnpacpi/rsparser.c @@ -450,7 +450,7 @@ int pnpacpi_parse_allocated_resource(struct pnp_dev *dev) acpi_handle handle = dev->data; acpi_status status; - dev_dbg(&dev->dev, "parse allocated resources\n"); + pnp_dbg(&dev->dev, "parse allocated resources\n"); pnp_init_resources(dev); @@ -736,7 +736,7 @@ int __init pnpacpi_parse_resource_option_data(struct pnp_dev *dev) acpi_status status; struct acpipnp_parse_option_s parse_data; - dev_dbg(&dev->dev, "parse resource options\n"); + pnp_dbg(&dev->dev, "parse resource options\n"); parse_data.dev = dev; parse_data.option_flags = 0; @@ -844,7 +844,7 @@ static void pnpacpi_encode_irq(struct pnp_dev *dev, if (!pnp_resource_enabled(p)) { irq->interrupt_count = 0; - dev_dbg(&dev->dev, " encode irq (%s)\n", + pnp_dbg(&dev->dev, " encode irq (%s)\n", p ? "disabled" : "missing"); return; } @@ -856,7 +856,7 @@ static void pnpacpi_encode_irq(struct pnp_dev *dev, irq->interrupt_count = 1; irq->interrupts[0] = p->start; - dev_dbg(&dev->dev, " encode irq %d %s %s %s (%d-byte descriptor)\n", + pnp_dbg(&dev->dev, " encode irq %d %s %s %s (%d-byte descriptor)\n", (int) p->start, triggering == ACPI_LEVEL_SENSITIVE ? "level" : "edge", polarity == ACPI_ACTIVE_LOW ? "low" : "high", @@ -873,7 +873,7 @@ static void pnpacpi_encode_ext_irq(struct pnp_dev *dev, if (!pnp_resource_enabled(p)) { extended_irq->interrupt_count = 0; - dev_dbg(&dev->dev, " encode extended irq (%s)\n", + pnp_dbg(&dev->dev, " encode extended irq (%s)\n", p ? "disabled" : "missing"); return; } @@ -886,7 +886,7 @@ static void pnpacpi_encode_ext_irq(struct pnp_dev *dev, extended_irq->interrupt_count = 1; extended_irq->interrupts[0] = p->start; - dev_dbg(&dev->dev, " encode irq %d %s %s %s\n", (int) p->start, + pnp_dbg(&dev->dev, " encode irq %d %s %s %s\n", (int) p->start, triggering == ACPI_LEVEL_SENSITIVE ? "level" : "edge", polarity == ACPI_ACTIVE_LOW ? "low" : "high", extended_irq->sharable == ACPI_SHARED ? "shared" : "exclusive"); @@ -900,7 +900,7 @@ static void pnpacpi_encode_dma(struct pnp_dev *dev, if (!pnp_resource_enabled(p)) { dma->channel_count = 0; - dev_dbg(&dev->dev, " encode dma (%s)\n", + pnp_dbg(&dev->dev, " encode dma (%s)\n", p ? "disabled" : "missing"); return; } @@ -935,7 +935,7 @@ static void pnpacpi_encode_dma(struct pnp_dev *dev, dma->channel_count = 1; dma->channels[0] = p->start; - dev_dbg(&dev->dev, " encode dma %d " + pnp_dbg(&dev->dev, " encode dma %d " "type %#x transfer %#x master %d\n", (int) p->start, dma->type, dma->transfer, dma->bus_master); } @@ -959,7 +959,7 @@ static void pnpacpi_encode_io(struct pnp_dev *dev, io->address_length = 0; } - dev_dbg(&dev->dev, " encode io %#x-%#x decode %#x\n", io->minimum, + pnp_dbg(&dev->dev, " encode io %#x-%#x decode %#x\n", io->minimum, io->minimum + io->address_length - 1, io->io_decode); } @@ -977,7 +977,7 @@ static void pnpacpi_encode_fixed_io(struct pnp_dev *dev, fixed_io->address_length = 0; } - dev_dbg(&dev->dev, " encode fixed_io %#x-%#x\n", fixed_io->address, + pnp_dbg(&dev->dev, " encode fixed_io %#x-%#x\n", fixed_io->address, fixed_io->address + fixed_io->address_length - 1); } @@ -1000,7 +1000,7 @@ static void pnpacpi_encode_mem24(struct pnp_dev *dev, memory24->address_length = 0; } - dev_dbg(&dev->dev, " encode mem24 %#x-%#x write_protect %#x\n", + pnp_dbg(&dev->dev, " encode mem24 %#x-%#x write_protect %#x\n", memory24->minimum, memory24->minimum + memory24->address_length - 1, memory24->write_protect); @@ -1024,7 +1024,7 @@ static void pnpacpi_encode_mem32(struct pnp_dev *dev, memory32->alignment = 0; } - dev_dbg(&dev->dev, " encode mem32 %#x-%#x write_protect %#x\n", + pnp_dbg(&dev->dev, " encode mem32 %#x-%#x write_protect %#x\n", memory32->minimum, memory32->minimum + memory32->address_length - 1, memory32->write_protect); @@ -1047,7 +1047,7 @@ static void pnpacpi_encode_fixed_mem32(struct pnp_dev *dev, fixed_memory32->address_length = 0; } - dev_dbg(&dev->dev, " encode fixed_mem32 %#x-%#x write_protect %#x\n", + pnp_dbg(&dev->dev, " encode fixed_mem32 %#x-%#x write_protect %#x\n", fixed_memory32->address, fixed_memory32->address + fixed_memory32->address_length - 1, fixed_memory32->write_protect); @@ -1061,7 +1061,7 @@ int pnpacpi_encode_resources(struct pnp_dev *dev, struct acpi_buffer *buffer) struct acpi_resource *resource = buffer->pointer; int port = 0, irq = 0, dma = 0, mem = 0; - dev_dbg(&dev->dev, "encode %d resources\n", res_cnt); + pnp_dbg(&dev->dev, "encode %d resources\n", res_cnt); while (i < res_cnt) { switch (resource->type) { case ACPI_RESOURCE_TYPE_IRQ: diff --git a/drivers/pnp/pnpbios/core.c b/drivers/pnp/pnpbios/core.c index 19a4be1a9a3..2f79eb2190e 100644 --- a/drivers/pnp/pnpbios/core.c +++ b/drivers/pnp/pnpbios/core.c @@ -211,7 +211,7 @@ static int pnpbios_get_resources(struct pnp_dev *dev) if (!pnpbios_is_dynamic(dev)) return -EPERM; - dev_dbg(&dev->dev, "get resources\n"); + pnp_dbg(&dev->dev, "get resources\n"); node = kzalloc(node_info.max_node_size, GFP_KERNEL); if (!node) return -1; @@ -234,7 +234,7 @@ static int pnpbios_set_resources(struct pnp_dev *dev) if (!pnpbios_is_dynamic(dev)) return -EPERM; - dev_dbg(&dev->dev, "set resources\n"); + pnp_dbg(&dev->dev, "set resources\n"); node = kzalloc(node_info.max_node_size, GFP_KERNEL); if (!node) return -1; diff --git a/drivers/pnp/pnpbios/rsparser.c b/drivers/pnp/pnpbios/rsparser.c index ca567671379..87b4f49a525 100644 --- a/drivers/pnp/pnpbios/rsparser.c +++ b/drivers/pnp/pnpbios/rsparser.c @@ -87,7 +87,7 @@ static unsigned char *pnpbios_parse_allocated_resource_data(struct pnp_dev *dev, if (!p) return NULL; - dev_dbg(&dev->dev, "parse allocated resources\n"); + pnp_dbg(&dev->dev, "parse allocated resources\n"); pnp_init_resources(dev); @@ -324,7 +324,7 @@ pnpbios_parse_resource_option_data(unsigned char *p, unsigned char *end, if (!p) return NULL; - dev_dbg(&dev->dev, "parse resource options\n"); + pnp_dbg(&dev->dev, "parse resource options\n"); option_flags = 0; while ((char *)p < (char *)end) { @@ -519,7 +519,7 @@ static void pnpbios_encode_mem(struct pnp_dev *dev, unsigned char *p, p[10] = (len >> 8) & 0xff; p[11] = ((len >> 8) >> 8) & 0xff; - dev_dbg(&dev->dev, " encode mem %#lx-%#lx\n", base, base + len - 1); + pnp_dbg(&dev->dev, " encode mem %#lx-%#lx\n", base, base + len - 1); } static void pnpbios_encode_mem32(struct pnp_dev *dev, unsigned char *p, @@ -549,7 +549,7 @@ static void pnpbios_encode_mem32(struct pnp_dev *dev, unsigned char *p, p[18] = (len >> 16) & 0xff; p[19] = (len >> 24) & 0xff; - dev_dbg(&dev->dev, " encode mem32 %#lx-%#lx\n", base, base + len - 1); + pnp_dbg(&dev->dev, " encode mem32 %#lx-%#lx\n", base, base + len - 1); } static void pnpbios_encode_fixed_mem32(struct pnp_dev *dev, unsigned char *p, @@ -575,7 +575,7 @@ static void pnpbios_encode_fixed_mem32(struct pnp_dev *dev, unsigned char *p, p[10] = (len >> 16) & 0xff; p[11] = (len >> 24) & 0xff; - dev_dbg(&dev->dev, " encode fixed_mem32 %#lx-%#lx\n", base, + pnp_dbg(&dev->dev, " encode fixed_mem32 %#lx-%#lx\n", base, base + len - 1); } @@ -592,7 +592,7 @@ static void pnpbios_encode_irq(struct pnp_dev *dev, unsigned char *p, p[1] = map & 0xff; p[2] = (map >> 8) & 0xff; - dev_dbg(&dev->dev, " encode irq mask %#lx\n", map); + pnp_dbg(&dev->dev, " encode irq mask %#lx\n", map); } static void pnpbios_encode_dma(struct pnp_dev *dev, unsigned char *p, @@ -607,7 +607,7 @@ static void pnpbios_encode_dma(struct pnp_dev *dev, unsigned char *p, p[1] = map & 0xff; - dev_dbg(&dev->dev, " encode dma mask %#lx\n", map); + pnp_dbg(&dev->dev, " encode dma mask %#lx\n", map); } static void pnpbios_encode_port(struct pnp_dev *dev, unsigned char *p, @@ -630,7 +630,7 @@ static void pnpbios_encode_port(struct pnp_dev *dev, unsigned char *p, p[5] = (base >> 8) & 0xff; p[7] = len & 0xff; - dev_dbg(&dev->dev, " encode io %#lx-%#lx\n", base, base + len - 1); + pnp_dbg(&dev->dev, " encode io %#lx-%#lx\n", base, base + len - 1); } static void pnpbios_encode_fixed_port(struct pnp_dev *dev, unsigned char *p, @@ -651,7 +651,7 @@ static void pnpbios_encode_fixed_port(struct pnp_dev *dev, unsigned char *p, p[2] = (base >> 8) & 0xff; p[3] = len & 0xff; - dev_dbg(&dev->dev, " encode fixed_io %#lx-%#lx\n", base, + pnp_dbg(&dev->dev, " encode fixed_io %#lx-%#lx\n", base, base + len - 1); } diff --git a/drivers/pnp/quirks.c b/drivers/pnp/quirks.c index 414bb094572..3d2e5609478 100644 --- a/drivers/pnp/quirks.c +++ b/drivers/pnp/quirks.c @@ -337,7 +337,7 @@ void pnp_fixup_device(struct pnp_dev *dev) for (f = pnp_fixups; *f->id; f++) { if (!compare_pnp_id(dev->id, f->id)) continue; - dev_dbg(&dev->dev, "%s: calling %pF\n", f->id, + pnp_dbg(&dev->dev, "%s: calling %pF\n", f->id, f->quirk_function); f->quirk_function(dev); } diff --git a/drivers/pnp/resource.c b/drivers/pnp/resource.c index e0206da6851..5ef463d89f1 100644 --- a/drivers/pnp/resource.c +++ b/drivers/pnp/resource.c @@ -294,7 +294,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, u8 progif; if (pci->irq == irq) { - dev_dbg(&pnp->dev, " device %s using irq %d\n", + pnp_dbg(&pnp->dev, " device %s using irq %d\n", pci_name(pci), irq); return 1; } @@ -316,7 +316,7 @@ static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, if ((progif & 0x5) != 0x5) if (pci_get_legacy_ide_irq(pci, 0) == irq || pci_get_legacy_ide_irq(pci, 1) == irq) { - dev_dbg(&pnp->dev, " legacy IDE device %s " + pnp_dbg(&pnp->dev, " legacy IDE device %s " "using irq %d\n", pci_name(pci), irq); return 1; } @@ -517,7 +517,7 @@ struct pnp_resource *pnp_add_irq_resource(struct pnp_dev *dev, int irq, res->start = irq; res->end = irq; - dev_dbg(&dev->dev, " add irq %d flags %#x\n", irq, flags); + pnp_dbg(&dev->dev, " add irq %d flags %#x\n", irq, flags); return pnp_res; } @@ -538,7 +538,7 @@ struct pnp_resource *pnp_add_dma_resource(struct pnp_dev *dev, int dma, res->start = dma; res->end = dma; - dev_dbg(&dev->dev, " add dma %d flags %#x\n", dma, flags); + pnp_dbg(&dev->dev, " add dma %d flags %#x\n", dma, flags); return pnp_res; } @@ -562,7 +562,7 @@ struct pnp_resource *pnp_add_io_resource(struct pnp_dev *dev, res->start = start; res->end = end; - dev_dbg(&dev->dev, " add io %#llx-%#llx flags %#x\n", + pnp_dbg(&dev->dev, " add io %#llx-%#llx flags %#x\n", (unsigned long long) start, (unsigned long long) end, flags); return pnp_res; } @@ -587,7 +587,7 @@ struct pnp_resource *pnp_add_mem_resource(struct pnp_dev *dev, res->start = start; res->end = end; - dev_dbg(&dev->dev, " add mem %#llx-%#llx flags %#x\n", + pnp_dbg(&dev->dev, " add mem %#llx-%#llx flags %#x\n", (unsigned long long) start, (unsigned long long) end, flags); return pnp_res; } diff --git a/drivers/pnp/support.c b/drivers/pnp/support.c index 714918655e0..63087d5ce60 100644 --- a/drivers/pnp/support.c +++ b/drivers/pnp/support.c @@ -81,11 +81,11 @@ void dbg_pnp_show_resources(struct pnp_dev *dev, char *desc) struct resource *res; if (list_empty(&dev->resources)) { - dev_dbg(&dev->dev, "%s: no current resources\n", desc); + pnp_dbg(&dev->dev, "%s: no current resources\n", desc); return; } - dev_dbg(&dev->dev, "%s: current resources:\n", desc); + pnp_dbg(&dev->dev, "%s: current resources:\n", desc); list_for_each_entry(pnp_res, &dev->resources, list) { res = &pnp_res->res; len = 0; @@ -94,7 +94,7 @@ void dbg_pnp_show_resources(struct pnp_dev *dev, char *desc) pnp_resource_type_name(res)); if (res->flags & IORESOURCE_DISABLED) { - dev_dbg(&dev->dev, "%sdisabled\n", buf); + pnp_dbg(&dev->dev, "%sdisabled\n", buf); continue; } @@ -115,7 +115,7 @@ void dbg_pnp_show_resources(struct pnp_dev *dev, char *desc) res->flags); break; } - dev_dbg(&dev->dev, "%s\n", buf); + pnp_dbg(&dev->dev, "%s\n", buf); } } @@ -205,5 +205,5 @@ void dbg_pnp_show_option(struct pnp_dev *dev, struct pnp_option *option) "flags %#x", dma->map, dma->flags); break; } - dev_dbg(&dev->dev, "%s\n", buf); + pnp_dbg(&dev->dev, "%s\n", buf); } -- cgit v1.2.3 From ac88a8f3f7df04774b386ddaf586040adb819b58 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 19 Aug 2008 16:53:52 -0600 Subject: PNP: remove old CONFIG_PNP_DEBUG option CONFIG_PNP_DEBUG is no longer used to turn on dev_dbg() in PNP, since we have pnp_dbg() which can be enabled at boot-time, so this patch removes the config option. Note that pnp_dock_event() checks "#ifdef DEBUG". But there's never been a clear path for enabling that via configgery. It happened that CONFIG_PNP_DEBUG enabled it after 1bd17e63a068db6, but that was accidental and only in 2.6.26. Signed-off-by: Bjorn Helgaas Signed-off-by: Andi Kleen Signed-off-by: Len Brown --- drivers/pnp/Kconfig | 6 ------ drivers/pnp/Makefile | 4 ---- drivers/pnp/isapnp/Makefile | 4 ---- drivers/pnp/pnpacpi/Makefile | 4 ---- drivers/pnp/pnpbios/Makefile | 4 ---- 5 files changed, 22 deletions(-) diff --git a/drivers/pnp/Kconfig b/drivers/pnp/Kconfig index 8a3237f9f2a..2a37b3fedb8 100644 --- a/drivers/pnp/Kconfig +++ b/drivers/pnp/Kconfig @@ -36,12 +36,6 @@ config PNP_DEBUG_MESSAGES if PNP -config PNP_DEBUG - bool "PnP Debug Messages" - help - Say Y if you want the Plug and Play Layer to print debug messages. - This is useful if you are developing a PnP driver or troubleshooting. - comment "Protocols" source "drivers/pnp/isapnp/Kconfig" diff --git a/drivers/pnp/Makefile b/drivers/pnp/Makefile index 26f5abc9c3f..a381a92fd1b 100644 --- a/drivers/pnp/Makefile +++ b/drivers/pnp/Makefile @@ -7,7 +7,3 @@ obj-y := core.o card.o driver.o resource.o manager.o support.o interface.o quir obj-$(CONFIG_PNPACPI) += pnpacpi/ obj-$(CONFIG_PNPBIOS) += pnpbios/ obj-$(CONFIG_ISAPNP) += isapnp/ - -ifeq ($(CONFIG_PNP_DEBUG),y) -EXTRA_CFLAGS += -DDEBUG -endif diff --git a/drivers/pnp/isapnp/Makefile b/drivers/pnp/isapnp/Makefile index 3e38f06f8d7..cac18bbfb81 100644 --- a/drivers/pnp/isapnp/Makefile +++ b/drivers/pnp/isapnp/Makefile @@ -5,7 +5,3 @@ isapnp-proc-$(CONFIG_PROC_FS) = proc.o obj-y := core.o compat.o $(isapnp-proc-y) - -ifeq ($(CONFIG_PNP_DEBUG),y) -EXTRA_CFLAGS += -DDEBUG -endif diff --git a/drivers/pnp/pnpacpi/Makefile b/drivers/pnp/pnpacpi/Makefile index 2d7a1e6908b..905326fcca8 100644 --- a/drivers/pnp/pnpacpi/Makefile +++ b/drivers/pnp/pnpacpi/Makefile @@ -3,7 +3,3 @@ # obj-y := core.o rsparser.o - -ifeq ($(CONFIG_PNP_DEBUG),y) -EXTRA_CFLAGS += -DDEBUG -endif diff --git a/drivers/pnp/pnpbios/Makefile b/drivers/pnp/pnpbios/Makefile index 310e2b3a771..3cd3ed76060 100644 --- a/drivers/pnp/pnpbios/Makefile +++ b/drivers/pnp/pnpbios/Makefile @@ -5,7 +5,3 @@ pnpbios-proc-$(CONFIG_PNPBIOS_PROC_FS) = proc.o obj-y := core.o bioscalls.o rsparser.o $(pnpbios-proc-y) - -ifeq ($(CONFIG_PNP_DEBUG),y) -EXTRA_CFLAGS += -DDEBUG -endif -- cgit v1.2.3 From ee297533279a802eac8b1cbea8e65b24b36a1aac Mon Sep 17 00:00:00 2001 From: Yinghai Lu Date: Wed, 24 Sep 2008 19:04:31 -0700 Subject: ACPI: don't load acpi_cpufreq if acpi=off Signed-off-by: Yinghai Lu Signed-off-by: Len Brown --- arch/x86/kernel/cpu/cpufreq/acpi-cpufreq.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/x86/kernel/cpu/cpufreq/acpi-cpufreq.c b/arch/x86/kernel/cpu/cpufreq/acpi-cpufreq.c index dd097b83583..9943b4c8774 100644 --- a/arch/x86/kernel/cpu/cpufreq/acpi-cpufreq.c +++ b/arch/x86/kernel/cpu/cpufreq/acpi-cpufreq.c @@ -779,6 +779,9 @@ static int __init acpi_cpufreq_init(void) { int ret; + if (acpi_disabled) + return 0; + dprintk("acpi_cpufreq_init\n"); ret = acpi_cpufreq_early_init(); -- cgit v1.2.3 From fa89b6089b5f4c7a5244b642caaca3e72b06ebe4 Mon Sep 17 00:00:00 2001 From: Yinghai Lu Date: Wed, 24 Sep 2008 19:04:32 -0700 Subject: ACPI: remove unused have_arch_parse_srat This was a workaround for 32bit numa SRAT processing, and we removed those workarounds, making 32 bit more like 64 bit. HAVE_ARCH_PARSE_SRAT is no longer defined anywhere. Signed-off-by: Yinghai Lu Signed-off-by: Andrew Morton Signed-off-by: Len Brown --- include/linux/acpi.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 702f79dad16..fd6a452b0ce 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -94,18 +94,10 @@ int acpi_parse_mcfg (struct acpi_table_header *header); void acpi_table_print_madt_entry (struct acpi_subtable_header *madt); /* the following four functions are architecture-dependent */ -#ifdef CONFIG_HAVE_ARCH_PARSE_SRAT -#define NR_NODE_MEMBLKS MAX_NUMNODES -#define acpi_numa_slit_init(slit) do {} while (0) -#define acpi_numa_processor_affinity_init(pa) do {} while (0) -#define acpi_numa_memory_affinity_init(ma) do {} while (0) -#define acpi_numa_arch_fixup() do {} while (0) -#else void acpi_numa_slit_init (struct acpi_table_slit *slit); void acpi_numa_processor_affinity_init (struct acpi_srat_cpu_affinity *pa); void acpi_numa_memory_affinity_init (struct acpi_srat_mem_affinity *ma); void acpi_numa_arch_fixup(void); -#endif #ifdef CONFIG_ACPI_HOTPLUG_CPU /* Arch dependent functions for cpu hotplug support */ -- cgit v1.2.3 From ae3a1b46099aa87f1662621580c985432ca1c9d9 Mon Sep 17 00:00:00 2001 From: Carlos Corbacho Date: Fri, 10 Oct 2008 17:33:35 +0100 Subject: acer-wmi: Remove private workqueue As per Dmitry Torokhov's suggestion, acer-wmi doesn't need a private workqueue, so remove it. Signed-off-by: Carlos Corbacho Cc: Dmitry Torokhov Signed-off-by: Len Brown --- drivers/misc/acer-wmi.c | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c index 8708d319378..cf4c39fbc66 100644 --- a/drivers/misc/acer-wmi.c +++ b/drivers/misc/acer-wmi.c @@ -930,8 +930,6 @@ static void acer_backlight_exit(void) /* * Rfkill devices */ -static struct workqueue_struct *rfkill_workqueue; - static void acer_rfkill_update(struct work_struct *ignored); static DECLARE_DELAYED_WORK(acer_rfkill_work, acer_rfkill_update); static void acer_rfkill_update(struct work_struct *ignored) @@ -952,8 +950,7 @@ static void acer_rfkill_update(struct work_struct *ignored) RFKILL_STATE_SOFT_BLOCKED); } - queue_delayed_work(rfkill_workqueue, &acer_rfkill_work, - round_jiffies_relative(HZ)); + schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ)); } static int acer_rfkill_set(void *data, enum rfkill_state state) @@ -1018,17 +1015,7 @@ static int acer_rfkill_init(struct device *dev) } } - rfkill_workqueue = create_singlethread_workqueue("rfkill_workqueue"); - if (!rfkill_workqueue) { - if (has_cap(ACER_CAP_BLUETOOTH)) { - kfree(bluetooth_rfkill->data); - rfkill_unregister(bluetooth_rfkill); - } - kfree(wireless_rfkill->data); - rfkill_unregister(wireless_rfkill); - return -ENOMEM; - } - queue_delayed_work(rfkill_workqueue, &acer_rfkill_work, HZ); + schedule_delayed_work(&acer_rfkill_work, round_jiffies_relative(HZ)); return 0; } @@ -1036,7 +1023,6 @@ static int acer_rfkill_init(struct device *dev) static void acer_rfkill_exit(void) { cancel_delayed_work_sync(&acer_rfkill_work); - destroy_workqueue(rfkill_workqueue); kfree(wireless_rfkill->data); rfkill_unregister(wireless_rfkill); if (has_cap(ACER_CAP_BLUETOOTH)) { -- cgit v1.2.3 From 0a918a9432cc30aede10f904253b66ea6ab485ac Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Sat, 11 Oct 2008 00:15:04 -0400 Subject: Subject: ACPI dock: Use ACPI_EXCEPTION instead of printk(KERN_ERR lenb: stripped patch down to what still applied to new dock.c Signed-off-by: Thomas Renninger Signed-off-by: Len Brown --- drivers/acpi/dock.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index b7d15818215..913bb1e89dd 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -488,8 +488,9 @@ static void handle_dock(struct dock_station *ds, int dock) arg.integer.value = dock; status = acpi_evaluate_object(ds->handle, "_DCK", &arg_list, &buffer); if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) - printk(KERN_ERR PREFIX "%s - failed to execute _DCK\n", - (char *)name_buffer.pointer); + ACPI_EXCEPTION((AE_INFO, status, "%s - failed to execute" + " _DCK\n", (char *)name_buffer.pointer)); + kfree(buffer.pointer); kfree(name_buffer.pointer); } -- cgit v1.2.3 From 455c8793d2c49eaecad038c8de83dade9fc3759f Mon Sep 17 00:00:00 2001 From: Zhao Yakui Date: Mon, 6 Oct 2008 10:31:36 +0800 Subject: ACPI: Enable EC device immediately after ACPI full initialization when there is no ECDT table and no _INI object for EC device, it will be enabled before scanning ACPI device. But it is too late after the following the commit is merged. >commit 7752d5cfe3d11ca0bb9c673ec38bd78ba6578f8e > Author: Robert Hancock > Date: Fri Feb 15 01:27:20 2008 -0800 >x86: validate against acpi motherboard resources After the above commit is merged, OS will check whether MCFG area is reserved in ACPI motherboard resources by calling the function of acpi_get_devices when there exists MCFG table. In the acpi_get_devices the _STA object will be evaluated to check the status of the ACPI device. On some broken BIOS the MYEC object of EC device is initialized as one, which indicates that EC operation region is already accessible before enabling EC device.So on these broken BIOS the EC operation region will be accessed in course of evaluating the _STA object before enabling EC device, which causes that OS will print the following warning messages: >ACPI Error (evregion-0315): No handler for Region [EC__] (ffff88007f8145e8) [EmbeddedControl] [20080609] >ACPI Error (exfldio-0290): Region EmbeddedControl(3) has no handler [20080321] >ACPI Error (psparse-0530): Method parse/execution failed [\_SB_.PCI0.SBRG. EC__.BAT1._STA] (Node ffff81013fc17a00), AE_NOT_EXIST >ACPI Error (uteval-0233): Method execution failed [\_SB_.PCI0.SBRG.EC__.BAT1. _STA] (Node ffff81013fc17a00), AE_NOT_EXIST Although the above warning message is harmless, it looks confusing. So it is necessary to enable EC device as early as possible.Maybe it is appropriate to enable it immediately after ACPI full initialization. http://bugzilla.kernel.org/show_bug.cgi?id=11255 http://bugzilla.kernel.org/show_bug.cgi?id=11374 http://bugzilla.kernel.org/show_bug.cgi?id=11660 Signed-off-by: Zhao Yakui Acked-by: Alexey Starikovskiy Signed-off-by: Len Brown --- drivers/acpi/bus.c | 6 ++++++ drivers/acpi/scan.c | 4 ---- include/acpi/acpi_drivers.h | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index ccae305ee55..0e0bbc6209e 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -749,6 +749,12 @@ static int __init acpi_bus_init(void) goto error1; } + /* + * Maybe EC region is required at bus_scan/acpi_get_devices. So it + * is necessary to enable it as early as possible. + */ + acpi_boot_ec_enable(); + printk(KERN_INFO PREFIX "Interpreter enabled\n"); /* Initialize sleep structures */ diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index f6f52c1a2ab..2ae218f5ea5 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1545,7 +1545,6 @@ static int acpi_bus_scan_fixed(struct acpi_device *root) return result; } -int __init acpi_boot_ec_enable(void); static int __init acpi_scan_init(void) { @@ -1579,9 +1578,6 @@ static int __init acpi_scan_init(void) */ result = acpi_bus_scan_fixed(acpi_root); - /* EC region might be needed at bus_scan, so enable it now */ - acpi_boot_ec_enable(); - if (!result) result = acpi_bus_scan(acpi_root, &ops); diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index e5f38e5ce86..ab926a70b39 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -100,6 +100,7 @@ int acpi_power_transition(struct acpi_device *device, int state); -------------------------------------------------------------------------- */ #ifdef CONFIG_ACPI_EC int acpi_ec_ecdt_probe(void); +int acpi_boot_ec_enable(void); #endif /* -------------------------------------------------------------------------- -- cgit v1.2.3 From 27663c5855b10af9ec67bc7dfba001426ba21222 Mon Sep 17 00:00:00 2001 From: Matthew Wilcox Date: Fri, 10 Oct 2008 02:22:59 -0400 Subject: ACPI: Change acpi_evaluate_integer to support 64-bit on 32-bit kernels As of version 2.0, ACPI can return 64-bit integers. The current acpi_evaluate_integer only supports 64-bit integers on 64-bit platforms. Change the argument to take a pointer to an acpi_integer so we support 64-bit integers on all platforms. lenb: replaced use of "acpi_integer" with "unsigned long long" lenb: fixed bug in acpi_thermal_trips_update() Signed-off-by: Matthew Wilcox Signed-off-by: Len Brown --- arch/ia64/sn/kernel/io_acpi_init.c | 8 +++---- drivers/acpi/ac.c | 2 +- drivers/acpi/acpi_memhotplug.c | 5 ++--- drivers/acpi/asus_acpi.c | 2 +- drivers/acpi/bay.c | 2 +- drivers/acpi/bus.c | 4 ++-- drivers/acpi/button.c | 4 ++-- drivers/acpi/container.c | 2 +- drivers/acpi/dock.c | 6 +++--- drivers/acpi/ec.c | 7 ++++-- drivers/acpi/numa.c | 2 +- drivers/acpi/osl.c | 5 ++--- drivers/acpi/pci_root.c | 2 +- drivers/acpi/pci_slot.c | 10 ++++----- drivers/acpi/power.c | 2 +- drivers/acpi/processor_core.c | 4 ++-- drivers/acpi/processor_perflib.c | 2 +- drivers/acpi/processor_throttling.c | 2 +- drivers/acpi/sbshc.c | 2 +- drivers/acpi/sleep/main.c | 2 +- drivers/acpi/thermal.c | 43 +++++++++++++++++++++++-------------- drivers/acpi/utils.c | 4 ++-- drivers/acpi/video.c | 42 ++++++++++++++++++------------------ drivers/ata/libata-acpi.c | 2 +- drivers/misc/asus-laptop.c | 12 +++++------ drivers/misc/eeepc-laptop.c | 2 +- drivers/misc/fujitsu-laptop.c | 8 +++---- drivers/misc/intel_menlow.c | 27 ++++++++++++----------- drivers/pci/hotplug/acpiphp_glue.c | 14 ++++++------ drivers/pci/hotplug/acpiphp_ibm.c | 2 +- drivers/pci/hotplug/sgi_hotplug.c | 4 ++-- include/acpi/acpi_bus.h | 2 +- 32 files changed, 125 insertions(+), 112 deletions(-) diff --git a/arch/ia64/sn/kernel/io_acpi_init.c b/arch/ia64/sn/kernel/io_acpi_init.c index 6568942a95f..a15baacaba4 100644 --- a/arch/ia64/sn/kernel/io_acpi_init.c +++ b/arch/ia64/sn/kernel/io_acpi_init.c @@ -232,7 +232,7 @@ exit: static unsigned int get_host_devfn(acpi_handle device_handle, acpi_handle rootbus_handle) { - unsigned long adr; + unsigned long long adr; acpi_handle child; unsigned int devfn; int function; @@ -292,8 +292,8 @@ get_host_devfn(acpi_handle device_handle, acpi_handle rootbus_handle) static acpi_status find_matching_device(acpi_handle handle, u32 lvl, void *context, void **rv) { - unsigned long bbn = -1; - unsigned long adr; + unsigned long long bbn = -1; + unsigned long long adr; acpi_handle parent = NULL; acpi_status status; unsigned int devfn; @@ -348,7 +348,7 @@ sn_acpi_get_pcidev_info(struct pci_dev *dev, struct pcidev_info **pcidev_info, unsigned int host_devfn; struct sn_pcidev_match pcidev_match; acpi_handle rootbus_handle; - unsigned long segment; + unsigned long long segment; acpi_status status; rootbus_handle = PCI_CONTROLLER(dev)->acpi_handle; diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 831883b7d6c..5e57a80c6cf 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -85,7 +85,7 @@ struct acpi_ac { struct power_supply charger; #endif struct acpi_device * device; - unsigned long state; + unsigned long long state; }; #define to_acpi_ac(x) container_of(x, struct acpi_ac, charger); diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index 5f1127ad5a9..2b773160e9c 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -194,8 +194,7 @@ acpi_memory_get_device(acpi_handle handle, static int acpi_memory_check_device(struct acpi_memory_device *mem_device) { - unsigned long current_status; - + unsigned long long current_status; /* Get device present/absent information from the _STA */ if (ACPI_FAILURE(acpi_evaluate_integer(mem_device->device->handle, "_STA", @@ -264,7 +263,7 @@ static int acpi_memory_powerdown_device(struct acpi_memory_device *mem_device) acpi_status status; struct acpi_object_list arg_list; union acpi_object arg; - unsigned long current_status; + unsigned long long current_status; /* Issue the _EJ0 command */ diff --git a/drivers/acpi/asus_acpi.c b/drivers/acpi/asus_acpi.c index d3d0886d637..4434209e453 100644 --- a/drivers/acpi/asus_acpi.c +++ b/drivers/acpi/asus_acpi.c @@ -753,7 +753,7 @@ static int get_lcd_state(void) /* That's what the AML code does */ lcd = out_obj.integer.value >> 8; } else if (hotk->model == F3Sa) { - unsigned long tmp; + unsigned long long tmp; union acpi_object param; struct acpi_object_list input; acpi_status status; diff --git a/drivers/acpi/bay.c b/drivers/acpi/bay.c index 61b6c5beb2d..23a51e4779f 100644 --- a/drivers/acpi/bay.c +++ b/drivers/acpi/bay.c @@ -90,7 +90,7 @@ static int is_ejectable(acpi_handle handle) */ static int bay_present(struct bay *bay) { - unsigned long sta; + unsigned long long sta; acpi_status status; if (bay) { diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index ccae305ee55..0885fc796fa 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -77,7 +77,7 @@ EXPORT_SYMBOL(acpi_bus_get_device); int acpi_bus_get_status(struct acpi_device *device) { acpi_status status = AE_OK; - unsigned long sta = 0; + unsigned long long sta = 0; if (!device) @@ -155,7 +155,7 @@ int acpi_bus_get_power(acpi_handle handle, int *state) int result = 0; acpi_status status = 0; struct acpi_device *device = NULL; - unsigned long psc = 0; + unsigned long long psc = 0; result = acpi_bus_get_device(handle, &device); diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 1dfec413588..baeb661ca95 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -145,7 +145,7 @@ static int acpi_button_state_seq_show(struct seq_file *seq, void *offset) { struct acpi_button *button = seq->private; acpi_status status; - unsigned long state; + unsigned long long state; if (!button || !button->device) return 0; @@ -253,7 +253,7 @@ static int acpi_button_remove_fs(struct acpi_device *device) -------------------------------------------------------------------------- */ static int acpi_lid_send_state(struct acpi_button *button) { - unsigned long state; + unsigned long long state; acpi_status status; status = acpi_evaluate_integer(button->device->handle, "_LID", NULL, diff --git a/drivers/acpi/container.c b/drivers/acpi/container.c index 3c25ec7a187..cfa5fd68168 100644 --- a/drivers/acpi/container.c +++ b/drivers/acpi/container.c @@ -76,7 +76,7 @@ static int is_device_present(acpi_handle handle) { acpi_handle temp; acpi_status status; - unsigned long sta; + unsigned long long sta; status = acpi_get_handle(handle, "_STA", &temp); diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index 7d2edf143f1..94857112b64 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -229,7 +229,7 @@ EXPORT_SYMBOL_GPL(is_dock_device); */ static int dock_present(struct dock_station *ds) { - unsigned long sta; + unsigned long long sta; acpi_status status; if (ds) { @@ -727,13 +727,13 @@ static DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock); static ssize_t show_dock_uid(struct device *dev, struct device_attribute *attr, char *buf) { - unsigned long lbuf; + unsigned long long lbuf; acpi_status status = acpi_evaluate_integer(dock_station->handle, "_UID", NULL, &lbuf); if (ACPI_FAILURE(status)) return 0; - return snprintf(buf, PAGE_SIZE, "%lx\n", lbuf); + return snprintf(buf, PAGE_SIZE, "%llx\n", lbuf); } static DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 13593f9f219..638a68679a4 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -718,6 +718,7 @@ static acpi_status ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval) { acpi_status status; + unsigned long long tmp; struct acpi_ec *ec = context; status = acpi_walk_resources(handle, METHOD_NAME__CRS, @@ -727,11 +728,13 @@ ec_parse_device(acpi_handle handle, u32 Level, void *context, void **retval) /* Get GPE bit assignment (EC events). */ /* TODO: Add support for _GPE returning a package */ - status = acpi_evaluate_integer(handle, "_GPE", NULL, &ec->gpe); + status = acpi_evaluate_integer(handle, "_GPE", NULL, &tmp); if (ACPI_FAILURE(status)) return status; + ec->gpe = tmp; /* Use the global lock for all EC transactions? */ - acpi_evaluate_integer(handle, "_GLK", NULL, &ec->global_lock); + acpi_evaluate_integer(handle, "_GLK", NULL, &tmp); + ec->global_lock = tmp; ec->handle = handle; return AE_CTRL_TERMINATE; } diff --git a/drivers/acpi/numa.c b/drivers/acpi/numa.c index cb9864e39ba..25ceae9191e 100644 --- a/drivers/acpi/numa.c +++ b/drivers/acpi/numa.c @@ -258,7 +258,7 @@ int __init acpi_numa_init(void) int acpi_get_pxm(acpi_handle h) { - unsigned long pxm; + unsigned long long pxm; acpi_status status; acpi_handle handle; acpi_handle phandle = h; diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 235a1386888..f58fcbbc810 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -608,7 +608,7 @@ static void acpi_os_derive_pci_id_2(acpi_handle rhandle, /* upper bound */ acpi_handle handle; struct acpi_pci_id *pci_id = *id; acpi_status status; - unsigned long temp; + unsigned long long temp; acpi_object_type type; acpi_get_parent(chandle, &handle); @@ -620,8 +620,7 @@ static void acpi_os_derive_pci_id_2(acpi_handle rhandle, /* upper bound */ if ((ACPI_FAILURE(status)) || (type != ACPI_TYPE_DEVICE)) return; - status = - acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &temp); if (ACPI_SUCCESS(status)) { u32 val; diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index c3fed31166b..18ff4e5c8d8 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -190,7 +190,7 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device) struct acpi_pci_root *root = NULL; struct acpi_pci_root *tmp; acpi_status status = AE_OK; - unsigned long value = 0; + unsigned long long value = 0; acpi_handle handle = NULL; struct acpi_device *child; diff --git a/drivers/acpi/pci_slot.c b/drivers/acpi/pci_slot.c index d5b4ef89887..9bb84d4596b 100644 --- a/drivers/acpi/pci_slot.c +++ b/drivers/acpi/pci_slot.c @@ -76,10 +76,10 @@ static struct acpi_pci_driver acpi_pci_slot_driver = { }; static int -check_slot(acpi_handle handle, unsigned long *sun) +check_slot(acpi_handle handle, unsigned long long *sun) { int device = -1; - unsigned long adr, sta; + unsigned long long adr, sta; acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -132,7 +132,7 @@ static acpi_status register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) { int device; - unsigned long sun; + unsigned long long sun; char name[SLOT_NAME_SIZE]; struct acpi_pci_slot *slot; struct pci_slot *pci_slot; @@ -182,7 +182,7 @@ static acpi_status walk_p2p_bridge(acpi_handle handle, u32 lvl, void *context, void **rv) { int device, function; - unsigned long adr; + unsigned long long adr; acpi_status status; acpi_handle dummy_handle; acpi_walk_callback user_function; @@ -239,7 +239,7 @@ static int walk_root_bridge(acpi_handle handle, acpi_walk_callback user_function) { int seg, bus; - unsigned long tmp; + unsigned long long tmp; acpi_status status; acpi_handle dummy_handle; struct pci_bus *pci_bus; diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 4ab21cb1c8c..89e5d2a8c33 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -131,7 +131,7 @@ acpi_power_get_context(acpi_handle handle, static int acpi_power_get_state(struct acpi_power_resource *resource, int *state) { acpi_status status = AE_OK; - unsigned long sta = 0; + unsigned long long sta = 0; if (!resource || !state) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index ee68ac54c0d..8a2787141f8 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -563,7 +563,7 @@ static int acpi_processor_get_info(struct acpi_processor *pr, unsigned has_uid) /* Check if it is a Device with HID and UID */ if (has_uid) { - unsigned long value; + unsigned long long value; status = acpi_evaluate_integer(pr->handle, METHOD_NAME__UID, NULL, &value); if (ACPI_FAILURE(status)) { @@ -875,7 +875,7 @@ static int acpi_processor_remove(struct acpi_device *device, int type) static int is_processor_present(acpi_handle handle) { acpi_status status; - unsigned long sta = 0; + unsigned long long sta = 0; status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); diff --git a/drivers/acpi/processor_perflib.c b/drivers/acpi/processor_perflib.c index 80c251ec6d2..f8129c7277f 100644 --- a/drivers/acpi/processor_perflib.c +++ b/drivers/acpi/processor_perflib.c @@ -126,7 +126,7 @@ static struct notifier_block acpi_ppc_notifier_block = { static int acpi_processor_get_platform_limit(struct acpi_processor *pr) { acpi_status status = 0; - unsigned long ppc = 0; + unsigned long long ppc = 0; if (!pr) diff --git a/drivers/acpi/processor_throttling.c b/drivers/acpi/processor_throttling.c index a56fc6c4394..3097b752003 100644 --- a/drivers/acpi/processor_throttling.c +++ b/drivers/acpi/processor_throttling.c @@ -274,7 +274,7 @@ static int acpi_processor_throttling_notifier(unsigned long event, void *data) static int acpi_processor_get_platform_limit(struct acpi_processor *pr) { acpi_status status = 0; - unsigned long tpc = 0; + unsigned long long tpc = 0; if (!pr) return -EINVAL; diff --git a/drivers/acpi/sbshc.c b/drivers/acpi/sbshc.c index a4e3767b8c6..81d23d38ec9 100644 --- a/drivers/acpi/sbshc.c +++ b/drivers/acpi/sbshc.c @@ -258,7 +258,7 @@ extern int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit, static int acpi_smbus_hc_add(struct acpi_device *device) { int status; - unsigned long val; + unsigned long long val; struct acpi_smb_hc *hc; if (!device) diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c index d13194a031b..b32ba565e69 100644 --- a/drivers/acpi/sleep/main.c +++ b/drivers/acpi/sleep/main.c @@ -444,7 +444,7 @@ int acpi_pm_device_sleep_state(struct device *dev, int *d_min_p) acpi_handle handle = DEVICE_ACPI_HANDLE(dev); struct acpi_device *adev; char acpi_method[] = "_SxD"; - unsigned long d_min, d_max; + unsigned long long d_min, d_max; if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &adev))) { printk(KERN_DEBUG "ACPI handle has no context!\n"); diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 912703691d3..47abb94bdfb 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -246,18 +246,18 @@ static const struct file_operations acpi_thermal_polling_fops = { static int acpi_thermal_get_temperature(struct acpi_thermal *tz) { acpi_status status = AE_OK; - + unsigned long long tmp; if (!tz) return -EINVAL; tz->last_temperature = tz->temperature; - status = - acpi_evaluate_integer(tz->device->handle, "_TMP", NULL, &tz->temperature); + status = acpi_evaluate_integer(tz->device->handle, "_TMP", NULL, &tmp); if (ACPI_FAILURE(status)) return -ENODEV; + tz->temperature = tmp; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Temperature is %lu dK\n", tz->temperature)); @@ -267,17 +267,16 @@ static int acpi_thermal_get_temperature(struct acpi_thermal *tz) static int acpi_thermal_get_polling_frequency(struct acpi_thermal *tz) { acpi_status status = AE_OK; - + unsigned long long tmp; if (!tz) return -EINVAL; - status = - acpi_evaluate_integer(tz->device->handle, "_TZP", NULL, - &tz->polling_frequency); + status = acpi_evaluate_integer(tz->device->handle, "_TZP", NULL, &tmp); if (ACPI_FAILURE(status)) return -ENODEV; + tz->polling_frequency = tmp; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Polling frequency is %lu dS\n", tz->polling_frequency)); @@ -356,6 +355,7 @@ do { \ static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) { acpi_status status = AE_OK; + unsigned long long tmp; struct acpi_handle_list devices; int valid = 0; int i; @@ -363,7 +363,8 @@ static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) /* Critical Shutdown (required) */ if (flag & ACPI_TRIPS_CRITICAL) { status = acpi_evaluate_integer(tz->device->handle, - "_CRT", NULL, &tz->trips.critical.temperature); + "_CRT", NULL, &tmp); + tz->trips.critical.temperature = tmp; /* * Treat freezing temperatures as invalid as well; some * BIOSes return really low values and cause reboots at startup. @@ -399,12 +400,13 @@ static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) /* Critical Sleep (optional) */ if (flag & ACPI_TRIPS_HOT) { status = acpi_evaluate_integer(tz->device->handle, - "_HOT", NULL, &tz->trips.hot.temperature); + "_HOT", NULL, &tmp); if (ACPI_FAILURE(status)) { tz->trips.hot.flags.valid = 0; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No hot threshold\n")); } else { + tz->trips.hot.temperature = tmp; tz->trips.hot.flags.valid = 1; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found hot threshold [%lu]\n", @@ -418,33 +420,40 @@ static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) if (psv == -1) { status = AE_SUPPORT; } else if (psv > 0) { - tz->trips.passive.temperature = CELSIUS_TO_KELVIN(psv); + tmp = CELSIUS_TO_KELVIN(psv); status = AE_OK; } else { status = acpi_evaluate_integer(tz->device->handle, - "_PSV", NULL, &tz->trips.passive.temperature); + "_PSV", NULL, &tmp); } if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; else { + tz->trips.passive.temperature = tmp; tz->trips.passive.flags.valid = 1; if (flag == ACPI_TRIPS_INIT) { status = acpi_evaluate_integer( tz->device->handle, "_TC1", - NULL, &tz->trips.passive.tc1); + NULL, &tmp); if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; + else + tz->trips.passive.tc1 = tmp; status = acpi_evaluate_integer( tz->device->handle, "_TC2", - NULL, &tz->trips.passive.tc2); + NULL, &tmp); if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; + else + tz->trips.passive.tc2 = tmp; status = acpi_evaluate_integer( tz->device->handle, "_TSP", - NULL, &tz->trips.passive.tsp); + NULL, &tmp); if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; + else + tz->trips.passive.tsp = tmp; } } } @@ -479,7 +488,7 @@ static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) if (flag & ACPI_TRIPS_ACTIVE) { status = acpi_evaluate_integer(tz->device->handle, - name, NULL, &tz->trips.active[i].temperature); + name, NULL, &tmp); if (ACPI_FAILURE(status)) { tz->trips.active[i].flags.valid = 0; if (i == 0) @@ -500,8 +509,10 @@ static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) tz->trips.active[i - 2].temperature : CELSIUS_TO_KELVIN(act)); break; - } else + } else { + tz->trips.active[i].temperature = tmp; tz->trips.active[i].flags.valid = 1; + } } name[2] = 'L'; diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 10092614381..e827be36ee8 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -256,7 +256,7 @@ EXPORT_SYMBOL(acpi_extract_package); acpi_status acpi_evaluate_integer(acpi_handle handle, acpi_string pathname, - struct acpi_object_list *arguments, unsigned long *data) + struct acpi_object_list *arguments, unsigned long long *data) { acpi_status status = AE_OK; union acpi_object *element; @@ -288,7 +288,7 @@ acpi_evaluate_integer(acpi_handle handle, *data = element->integer.value; kfree(element); - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Return value [%lu]\n", *data)); + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Return value [%llu]\n", *data)); return AE_OK; } diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index e8a51a1700f..79409c9d2ad 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -291,20 +291,20 @@ static int acpi_video_device_lcd_set_level(struct acpi_video_device *device, int level); static int acpi_video_device_lcd_get_level_current( struct acpi_video_device *device, - unsigned long *level); + unsigned long long *level); static int acpi_video_get_next_level(struct acpi_video_device *device, u32 level_current, u32 event); static void acpi_video_switch_brightness(struct acpi_video_device *device, int event); static int acpi_video_device_get_state(struct acpi_video_device *device, - unsigned long *state); + unsigned long long *state); static int acpi_video_output_get(struct output_device *od); static int acpi_video_device_set_state(struct acpi_video_device *device, int state); /*backlight device sysfs support*/ static int acpi_video_get_brightness(struct backlight_device *bd) { - unsigned long cur_level; + unsigned long long cur_level; int i; struct acpi_video_device *vd = (struct acpi_video_device *)bl_get_data(bd); @@ -336,7 +336,7 @@ static struct backlight_ops acpi_backlight_ops = { /*video output device sysfs support*/ static int acpi_video_output_get(struct output_device *od) { - unsigned long state; + unsigned long long state; struct acpi_video_device *vd = (struct acpi_video_device *)dev_get_drvdata(&od->dev); acpi_video_device_get_state(vd, &state); @@ -370,7 +370,7 @@ static int video_get_cur_state(struct thermal_cooling_device *cdev, char *buf) { struct acpi_device *device = cdev->devdata; struct acpi_video_device *video = acpi_driver_data(device); - unsigned long level; + unsigned long long level; int state; acpi_video_device_lcd_get_level_current(video, &level); @@ -410,7 +410,7 @@ static struct thermal_cooling_device_ops video_cooling_ops = { /* device */ static int -acpi_video_device_query(struct acpi_video_device *device, unsigned long *state) +acpi_video_device_query(struct acpi_video_device *device, unsigned long long *state) { int status; @@ -421,7 +421,7 @@ acpi_video_device_query(struct acpi_video_device *device, unsigned long *state) static int acpi_video_device_get_state(struct acpi_video_device *device, - unsigned long *state) + unsigned long long *state) { int status; @@ -436,7 +436,7 @@ acpi_video_device_set_state(struct acpi_video_device *device, int state) int status; union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; - unsigned long ret; + unsigned long long ret; arg0.integer.value = state; @@ -495,7 +495,7 @@ acpi_video_device_lcd_set_level(struct acpi_video_device *device, int level) static int acpi_video_device_lcd_get_level_current(struct acpi_video_device *device, - unsigned long *level) + unsigned long long *level) { if (device->cap._BQC) return acpi_evaluate_integer(device->dev->handle, "_BQC", NULL, @@ -549,7 +549,7 @@ static int acpi_video_bus_set_POST(struct acpi_video_bus *video, unsigned long option) { int status; - unsigned long tmp; + unsigned long long tmp; union acpi_object arg0 = { ACPI_TYPE_INTEGER }; struct acpi_object_list args = { 1, &arg0 }; @@ -564,7 +564,7 @@ acpi_video_bus_set_POST(struct acpi_video_bus *video, unsigned long option) } static int -acpi_video_bus_get_POST(struct acpi_video_bus *video, unsigned long *id) +acpi_video_bus_get_POST(struct acpi_video_bus *video, unsigned long long *id) { int status; @@ -575,7 +575,7 @@ acpi_video_bus_get_POST(struct acpi_video_bus *video, unsigned long *id) static int acpi_video_bus_POST_options(struct acpi_video_bus *video, - unsigned long *options) + unsigned long long *options) { int status; @@ -918,7 +918,7 @@ static int acpi_video_device_state_seq_show(struct seq_file *seq, void *offset) { int status; struct acpi_video_device *dev = seq->private; - unsigned long state; + unsigned long long state; if (!dev) @@ -927,14 +927,14 @@ static int acpi_video_device_state_seq_show(struct seq_file *seq, void *offset) status = acpi_video_device_get_state(dev, &state); seq_printf(seq, "state: "); if (ACPI_SUCCESS(status)) - seq_printf(seq, "0x%02lx\n", state); + seq_printf(seq, "0x%02llx\n", state); else seq_printf(seq, "\n"); status = acpi_video_device_query(dev, &state); seq_printf(seq, "query: "); if (ACPI_SUCCESS(status)) - seq_printf(seq, "0x%02lx\n", state); + seq_printf(seq, "0x%02llx\n", state); else seq_printf(seq, "\n"); @@ -1217,7 +1217,7 @@ static int acpi_video_bus_ROM_open_fs(struct inode *inode, struct file *file) static int acpi_video_bus_POST_info_seq_show(struct seq_file *seq, void *offset) { struct acpi_video_bus *video = seq->private; - unsigned long options; + unsigned long long options; int status; @@ -1232,7 +1232,7 @@ static int acpi_video_bus_POST_info_seq_show(struct seq_file *seq, void *offset) printk(KERN_WARNING PREFIX "This indicates a BIOS bug. Please contact the manufacturer.\n"); } - printk("%lx\n", options); + printk("%llx\n", options); seq_printf(seq, "can POST: "); if (options & 2) seq_printf(seq, " "); @@ -1256,7 +1256,7 @@ static int acpi_video_bus_POST_seq_show(struct seq_file *seq, void *offset) { struct acpi_video_bus *video = seq->private; int status; - unsigned long id; + unsigned long long id; if (!video) @@ -1303,7 +1303,7 @@ acpi_video_bus_write_POST(struct file *file, struct seq_file *m = file->private_data; struct acpi_video_bus *video = m->private; char str[12] = { 0 }; - unsigned long opt, options; + unsigned long long opt, options; if (!video || count + 1 > sizeof str) @@ -1473,7 +1473,7 @@ static int acpi_video_bus_get_one_device(struct acpi_device *device, struct acpi_video_bus *video) { - unsigned long device_id; + unsigned long long device_id; int status; struct acpi_video_device *data; struct acpi_video_device_attrib* attribute; @@ -1724,7 +1724,7 @@ acpi_video_get_next_level(struct acpi_video_device *device, static void acpi_video_switch_brightness(struct acpi_video_device *device, int event) { - unsigned long level_current, level_next; + unsigned long long level_current, level_next; if (!device->brightness) return; acpi_video_device_lcd_get_level_current(device, &level_current); diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 9330b7922f6..ae451876b8e 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -180,7 +180,7 @@ static void ata_acpi_handle_hotplug(struct ata_port *ap, struct ata_device *dev, int wait = 0; unsigned long flags; acpi_handle handle, tmphandle; - unsigned long sta; + unsigned long long sta; acpi_status status; if (dev) { diff --git a/drivers/misc/asus-laptop.c b/drivers/misc/asus-laptop.c index 7c6dfd03de9..967ecec8257 100644 --- a/drivers/misc/asus-laptop.c +++ b/drivers/misc/asus-laptop.c @@ -280,7 +280,7 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val, static int read_wireless_status(int mask) { - ulong status; + unsigned long long status; acpi_status rv = AE_OK; if (!wireless_status_handle) @@ -297,7 +297,7 @@ static int read_wireless_status(int mask) static int read_gps_status(void) { - ulong status; + unsigned long long status; acpi_status rv = AE_OK; rv = acpi_evaluate_integer(gps_status_handle, NULL, NULL, &status); @@ -404,7 +404,7 @@ static void lcd_blank(int blank) static int read_brightness(struct backlight_device *bd) { - ulong value; + unsigned long long value; acpi_status rv = AE_OK; rv = acpi_evaluate_integer(brightness_get_handle, NULL, NULL, &value); @@ -455,7 +455,7 @@ static ssize_t show_infos(struct device *dev, struct device_attribute *attr, char *page) { int len = 0; - ulong temp; + unsigned long long temp; char buf[16]; //enough for all info acpi_status rv = AE_OK; @@ -603,7 +603,7 @@ static void set_display(int value) static int read_display(void) { - ulong value = 0; + unsigned long long value = 0; acpi_status rv = AE_OK; /* In most of the case, we know how to set the display, but sometime @@ -849,7 +849,7 @@ static int asus_hotk_get_info(void) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *model = NULL; - ulong bsts_result, hwrs_result; + unsigned long long bsts_result, hwrs_result; char *string = NULL; acpi_status status; diff --git a/drivers/misc/eeepc-laptop.c b/drivers/misc/eeepc-laptop.c index 1ee8501e90f..616bcbd8def 100644 --- a/drivers/misc/eeepc-laptop.c +++ b/drivers/misc/eeepc-laptop.c @@ -204,7 +204,7 @@ static int write_acpi_int(acpi_handle handle, const char *method, int val, static int read_acpi_int(acpi_handle handle, const char *method, int *val) { acpi_status status; - ulong result; + unsigned long long result; status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); if (ACPI_FAILURE(status)) { diff --git a/drivers/misc/fujitsu-laptop.c b/drivers/misc/fujitsu-laptop.c index 3e56203e494..efd395a6472 100644 --- a/drivers/misc/fujitsu-laptop.c +++ b/drivers/misc/fujitsu-laptop.c @@ -224,7 +224,7 @@ static int set_lcd_level_alt(int level) static int get_lcd_level(void) { - unsigned long state = 0; + unsigned long long state = 0; acpi_status status = AE_OK; vdbg_printk(FUJLAPTOP_DBG_TRACE, "get lcd level via GBLL\n"); @@ -246,7 +246,7 @@ static int get_lcd_level(void) static int get_max_brightness(void) { - unsigned long state = 0; + unsigned long long state = 0; acpi_status status = AE_OK; vdbg_printk(FUJLAPTOP_DBG_TRACE, "get max lcd level via RBLL\n"); @@ -263,7 +263,7 @@ static int get_max_brightness(void) static int get_lcd_level_alt(void) { - unsigned long state = 0; + unsigned long long state = 0; acpi_status status = AE_OK; vdbg_printk(FUJLAPTOP_DBG_TRACE, "get lcd level via GBLS\n"); @@ -384,7 +384,7 @@ static ssize_t store_lcd_level(struct device *dev, static int get_irb(void) { - unsigned long state = 0; + unsigned long long state = 0; acpi_status status = AE_OK; vdbg_printk(FUJLAPTOP_DBG_TRACE, "Get irb\n"); diff --git a/drivers/misc/intel_menlow.c b/drivers/misc/intel_menlow.c index 80a13635240..bd372ea11b0 100644 --- a/drivers/misc/intel_menlow.c +++ b/drivers/misc/intel_menlow.c @@ -57,7 +57,7 @@ static int memory_get_int_max_bandwidth(struct thermal_cooling_device *cdev, { struct acpi_device *device = cdev->devdata; acpi_handle handle = device->handle; - unsigned long value; + unsigned long long value; struct acpi_object_list arg_list; union acpi_object arg; acpi_status status = AE_OK; @@ -90,7 +90,7 @@ static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev, { struct acpi_device *device = cdev->devdata; acpi_handle handle = device->handle; - unsigned long value; + unsigned long long value; struct acpi_object_list arg_list; union acpi_object arg; acpi_status status = AE_OK; @@ -115,7 +115,7 @@ static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, struct acpi_object_list arg_list; union acpi_object arg; acpi_status status; - int temp; + unsigned long long temp; unsigned long max_state; if (memory_get_int_max_bandwidth(cdev, &max_state)) @@ -131,7 +131,7 @@ static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev, status = acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list, - (unsigned long *)&temp); + &temp); printk(KERN_INFO "Bandwidth value was %d: status is %d\n", state, status); @@ -252,7 +252,8 @@ static DEFINE_MUTEX(intel_menlow_attr_lock); * @auxtype : AUX0/AUX1 * @buf: syfs buffer */ -static int sensor_get_auxtrip(acpi_handle handle, int index, int *value) +static int sensor_get_auxtrip(acpi_handle handle, int index, + unsigned long long *value) { acpi_status status; @@ -260,7 +261,7 @@ static int sensor_get_auxtrip(acpi_handle handle, int index, int *value) return -EINVAL; status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0, - NULL, (unsigned long *)value); + NULL, value); if (ACPI_FAILURE(status)) return -EIO; @@ -282,13 +283,13 @@ static int sensor_set_auxtrip(acpi_handle handle, int index, int value) struct acpi_object_list args = { 1, &arg }; - int temp; + unsigned long long temp; if (index != 0 && index != 1) return -EINVAL; status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1, - NULL, (unsigned long *)&temp); + NULL, &temp); if (ACPI_FAILURE(status)) return -EIO; if ((index && value < temp) || (!index && value > temp)) @@ -296,7 +297,7 @@ static int sensor_set_auxtrip(acpi_handle handle, int index, int value) arg.integer.value = value; status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0, - &args, (unsigned long *)&temp); + &args, &temp); if (ACPI_FAILURE(status)) return -EIO; @@ -312,7 +313,7 @@ static ssize_t aux0_show(struct device *dev, struct device_attribute *dev_attr, char *buf) { struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); - int value; + unsigned long long value; int result; result = sensor_get_auxtrip(attr->handle, 0, &value); @@ -324,7 +325,7 @@ static ssize_t aux1_show(struct device *dev, struct device_attribute *dev_attr, char *buf) { struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr); - int value; + unsigned long long value; int result; result = sensor_get_auxtrip(attr->handle, 1, &value); @@ -376,7 +377,7 @@ static ssize_t bios_enabled_show(struct device *dev, struct device_attribute *attr, char *buf) { acpi_status status; - unsigned long bios_enabled; + unsigned long long bios_enabled; status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled); if (ACPI_FAILURE(status)) @@ -492,7 +493,7 @@ static int __init intel_menlow_module_init(void) { int result = -ENODEV; acpi_status status; - unsigned long enable; + unsigned long long enable; if (acpi_disabled) return result; diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index a3e4705dd8f..3baee56fa81 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -180,7 +180,7 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv) struct acpiphp_func *newfunc; acpi_handle tmp; acpi_status status = AE_OK; - unsigned long adr, sun; + unsigned long long adr, sun; int device, function, retval; status = acpi_evaluate_integer(handle, "_ADR", NULL, &adr); @@ -528,7 +528,7 @@ find_p2p_bridge(acpi_handle handle, u32 lvl, void *context, void **rv) { acpi_status status; acpi_handle dummy_handle; - unsigned long tmp; + unsigned long long tmp; int device, function; struct pci_dev *dev; struct pci_bus *pci_bus = context; @@ -573,7 +573,7 @@ find_p2p_bridge(acpi_handle handle, u32 lvl, void *context, void **rv) static int add_bridge(acpi_handle handle) { acpi_status status; - unsigned long tmp; + unsigned long long tmp; int seg, bus; acpi_handle dummy_handle; struct pci_bus *pci_bus; @@ -767,7 +767,7 @@ static int get_gsi_base(acpi_handle handle, u32 *gsi_base) { acpi_status status; int result = -1; - unsigned long gsb; + unsigned long long gsb; struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; union acpi_object *obj; void *table; @@ -808,7 +808,7 @@ static acpi_status ioapic_add(acpi_handle handle, u32 lvl, void *context, void **rv) { acpi_status status; - unsigned long sta; + unsigned long long sta; acpi_handle tmp; struct pci_dev *pdev; u32 gsi_base; @@ -872,7 +872,7 @@ static acpi_status ioapic_remove(acpi_handle handle, u32 lvl, void *context, void **rv) { acpi_status status; - unsigned long sta; + unsigned long long sta; acpi_handle tmp; u32 gsi_base; struct acpiphp_ioapic *pos, *n, *ioapic = NULL; @@ -1264,7 +1264,7 @@ static int disable_device(struct acpiphp_slot *slot) static unsigned int get_slot_status(struct acpiphp_slot *slot) { acpi_status status; - unsigned long sta = 0; + unsigned long long sta = 0; u32 dvid; struct list_head *l; struct acpiphp_func *func; diff --git a/drivers/pci/hotplug/acpiphp_ibm.c b/drivers/pci/hotplug/acpiphp_ibm.c index 2b7c45e3937..b291ee68b4f 100644 --- a/drivers/pci/hotplug/acpiphp_ibm.c +++ b/drivers/pci/hotplug/acpiphp_ibm.c @@ -183,7 +183,7 @@ static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status) union acpi_object args[2]; struct acpi_object_list params = { .pointer = args, .count = 2 }; acpi_status stat; - unsigned long rc; + unsigned long long rc; union apci_descriptor *ibm_slot; ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot)); diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c index 410fe0394a8..59f42501acc 100644 --- a/drivers/pci/hotplug/sgi_hotplug.c +++ b/drivers/pci/hotplug/sgi_hotplug.c @@ -418,7 +418,7 @@ static int enable_slot(struct hotplug_slot *bss_hotplug_slot) /* * Add the slot's devices to the ACPI infrastructure */ if (SN_ACPI_BASE_SUPPORT() && ssdt) { - unsigned long adr; + unsigned long long adr; struct acpi_device *pdevice; struct acpi_device *device; acpi_handle phandle; @@ -510,7 +510,7 @@ static int disable_slot(struct hotplug_slot *bss_hotplug_slot) /* free the ACPI resources for the slot */ if (SN_ACPI_BASE_SUPPORT() && PCI_CONTROLLER(slot->pci_bus)->acpi_handle) { - unsigned long adr; + unsigned long long adr; struct acpi_device *device; acpi_handle phandle; acpi_handle chandle = NULL; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a5ac0bc7f52..c52f10dee36 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -46,7 +46,7 @@ acpi_extract_package(union acpi_object *package, acpi_status acpi_evaluate_integer(acpi_handle handle, acpi_string pathname, - struct acpi_object_list *arguments, unsigned long *data); + struct acpi_object_list *arguments, unsigned long long *data); acpi_status acpi_evaluate_reference(acpi_handle handle, acpi_string pathname, -- cgit v1.2.3 From 030aebd2e439a2ebcca2b0ce30a02ed84feb043e Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Sat, 11 Oct 2008 12:25:45 -0700 Subject: rangetimer: fix BUG_ON reported by Ingo There's a small race/chance that, while hrtimers are enabled globally, they're later not enabled when we're calling the hrtimer_interrupt() function, which then BUG_ON()'s for that. This patch closes that race/gap. Signed-off-by: Arjan van de Ven --- kernel/hrtimer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/hrtimer.c b/kernel/hrtimer.c index eb2cf984959..b17657d8d81 100644 --- a/kernel/hrtimer.c +++ b/kernel/hrtimer.c @@ -1395,11 +1395,15 @@ void hrtimer_peek_ahead_timers(void) unsigned long flags; struct tick_device *td; struct clock_event_device *dev; - + struct hrtimer_cpu_base *cpu_base; if (hrtimer_hres_active()) return; local_irq_save(flags); + cpu_base = &__get_cpu_var(hrtimer_bases); + if (!cpu_base->hres_active) + goto out; + td = &__get_cpu_var(tick_cpu_device); if (!td) goto out; -- cgit v1.2.3 From 1d3ba686ed706a0e8563878b2ec415e125178607 Mon Sep 17 00:00:00 2001 From: Haavard Skinnemoen Date: Mon, 13 Oct 2008 12:42:25 +0200 Subject: avr32: Kconfig: Remove pointless if around atstk1000 include The contents of the ATSTK1000 Kconfig file itself is completely conditional, so including it conditionally makes no sense and only adds clutter. Signed-off-by: Haavard Skinnemoen --- arch/avr32/Kconfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/avr32/Kconfig b/arch/avr32/Kconfig index 7c239a91627..a90d85d44fc 100644 --- a/arch/avr32/Kconfig +++ b/arch/avr32/Kconfig @@ -121,9 +121,7 @@ config BOARD_ATNGW100 select CPU_AT32AP7000 endchoice -if BOARD_ATSTK1000 source "arch/avr32/boards/atstk1000/Kconfig" -endif choice prompt "Boot loader type" -- cgit v1.2.3 From a3bee42f058c2f9fe281df942eff397924630a12 Mon Sep 17 00:00:00 2001 From: Hans-Christian Egtvedt Date: Tue, 1 Jul 2008 08:29:27 +0000 Subject: avr32: Add support for EVKLCD10X addon boards This patch lets the user enable support for EVKLCD100 and EVKLCD101 (refered to by EVKLCD10X). By enabling EVKLCD10X support the LCD controller and AC97 controller platform devices are added. The user can also choose between the EVKLCD100 (QVGA display) and the EVKLCD101 (VGA display), this is added to automagically select the correct panel timing and resolution parameters. Enabling support for EVKLCD10X addon board will cripple the MCI platform device a bit since they share two GPIO lines (detect and write-protect). These two lines are disabled when EVKLCD10X is enabled. The default configurations are based upon ATNGW100, but with added AC97C and LCDC driver. Virtual terminal is also enabled by default for EVKLCD10X boards. Verified on hardware with a NGW100 + EVKLCD100/101. Signed-off-by: Hans-Christian Egtvedt Signed-off-by: Haavard Skinnemoen --- arch/avr32/Kconfig | 1 + arch/avr32/boards/atngw100/Kconfig | 35 + arch/avr32/boards/atngw100/Makefile | 3 +- arch/avr32/boards/atngw100/evklcd10x.c | 155 +++ arch/avr32/boards/atngw100/setup.c | 5 + arch/avr32/configs/atngw100_evklcd100_defconfig | 1264 +++++++++++++++++++++++ arch/avr32/configs/atngw100_evklcd101_defconfig | 1264 +++++++++++++++++++++++ 7 files changed, 2726 insertions(+), 1 deletion(-) create mode 100644 arch/avr32/boards/atngw100/Kconfig create mode 100644 arch/avr32/boards/atngw100/evklcd10x.c create mode 100644 arch/avr32/configs/atngw100_evklcd100_defconfig create mode 100644 arch/avr32/configs/atngw100_evklcd101_defconfig diff --git a/arch/avr32/Kconfig b/arch/avr32/Kconfig index a90d85d44fc..5c6d429505e 100644 --- a/arch/avr32/Kconfig +++ b/arch/avr32/Kconfig @@ -122,6 +122,7 @@ config BOARD_ATNGW100 endchoice source "arch/avr32/boards/atstk1000/Kconfig" +source "arch/avr32/boards/atngw100/Kconfig" choice prompt "Boot loader type" diff --git a/arch/avr32/boards/atngw100/Kconfig b/arch/avr32/boards/atngw100/Kconfig new file mode 100644 index 00000000000..b3f99477bbe --- /dev/null +++ b/arch/avr32/boards/atngw100/Kconfig @@ -0,0 +1,35 @@ +# NGW100 customization + +if BOARD_ATNGW100 + +config BOARD_ATNGW100_EVKLCD10X + bool "Add support for EVKLCD10X addon board" + help + This enables support for the EVKLCD100 (QVGA) or EVKLCD101 (VGA) + addon board for the NGW100. By enabling this the LCD controller and + AC97 controller is added as platform devices. + + This choice disables the detect pin and the write-protect pin for the + MCI platform device, since it conflicts with the LCD platform device. + The MCI pins can be reenabled by editing the "add device function" but + this may break the setup for other displays that use these pins. + + Choose 'Y' here if you have a EVKLCD100/101 connected to the NGW100. + +choice + prompt "LCD panel resolution on EVKLCD10X" + depends on BOARD_ATNGW100_EVKLCD10X + default BOARD_ATNGW100_EVKLCD10X_VGA + +config BOARD_ATNGW100_EVKLCD10X_QVGA + bool "QVGA (320x240)" + +config BOARD_ATNGW100_EVKLCD10X_VGA + bool "VGA (640x480)" + +config BOARD_ATNGW100_EVKLCD10X_POW_QVGA + bool "Powertip QVGA (320x240)" + +endchoice + +endif # BOARD_ATNGW100 diff --git a/arch/avr32/boards/atngw100/Makefile b/arch/avr32/boards/atngw100/Makefile index c740aa11675..6376f5322e4 100644 --- a/arch/avr32/boards/atngw100/Makefile +++ b/arch/avr32/boards/atngw100/Makefile @@ -1 +1,2 @@ -obj-y += setup.o flash.o +obj-y += setup.o flash.o +obj-$(CONFIG_BOARD_ATNGW100_EVKLCD10X) += evklcd10x.o diff --git a/arch/avr32/boards/atngw100/evklcd10x.c b/arch/avr32/boards/atngw100/evklcd10x.c new file mode 100644 index 00000000000..8140b22b346 --- /dev/null +++ b/arch/avr32/boards/atngw100/evklcd10x.c @@ -0,0 +1,155 @@ +/* + * Board-specific setup code for the ATEVKLCD10X addon board to the ATNGW100 + * Network Gateway + * + * Copyright (C) 2008 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include + +#include