All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6
@ 2008-12-15 12:57 Inaky Perez-Gonzalez
  2008-12-15 12:57 ` [PATCH 01/29] wimax: documentation for the stack Inaky Perez-Gonzalez
                   ` (29 more replies)
  0 siblings, 30 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:57 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax


Hi All

This patchset (or the linux-wimax GIT tree at git.kernel.org) merges
the WiMAX subsystem and driver for the Intel 2400m Wireless WiMAX
connection to the tree. We'd like to get it in for the 2.6.29 merge
window. 

[read on for changelog since previous submit]

[Note linux-wimax will have netdev as upstream, but there is a
 dependency for patches in gregkh's patches tree that forbid
 submitting against netdev for now -- so the base for this is said
 tree as of 11/19. See *2]

WiMAX is a new wireless boadband technology that differs significantly
from WiFi. It is also called 4G and resembles more the model of
cellular phones, where you sign up for service with a provider.

Your WiMAX card connects to a basestation, which is operated by a
certain Network Access Provider (NAP). The NAP leases (or might be the
same) connectivity to a Network Service Provider (NSP), which is your
ISP.

When you move around, your connectivity will be handed over from
basestation to basestation in a seamless way. As in cellphones, there
is the roaming case, where you could be are connecting to your service
plan through other provider.

WiMAX's main components have been standarized (some parts still in the
process) as in a set of 802.16 standards. The WiMAX Forum's members
drive the development of WiMAX features that end up in 802.16.

This code is broken up in a WiMAX subsystem and a driver for the Intel
2400m Wireless WiMAX Link. To be able to fully utilize WiMAX services,
you need extra pieces of device-specific user space software; for now,
there is a C level API (the Common API) to use/control it, but a high
level API over DBUS that should work for any device is in the
works. See details further below.

Support for other vendor's hardware would require a driver registering
with the WiMAX stack and a user space component implementing the high
level API.

Changes in v6:

 - Fix a couple of buglets in debug and error paths.

 - Add USB IDs to the USB driver.

Changes in v5:

 - (Johannes Berg, Thomas Graf) Move away from using a generic netlink
   family per interface; address interfaces using an interface index
   attribute.

Changes in v4:

 - (David Miller) Fix patch dependencies and compact them to reduce
   the patchset size.

Changes in v3:

 - Implement feedback from the mailing list for the merge request
   (from Johaness, Thomas, Patrick and Evgeniy)

 - Moved all the sysfs controls and knobs to be debugfs based.

 - Remove kernel/user op-open and instead use the generic netlink
   controller.

 - Many improvements and optimizations on the usage of generic
   netlink.

 - Use generic netlink facilities for permission checking.
 
 - Properly use the netlink ack/nlerr instead of relying on home-grown
   system. 

 - Fix a deadlock in wimax_reset().

 - When i2400m reset completes ok, mark the device as active again
   (or the device gets confused).

 - Relicense linux/wimax.h as BSD (mistakenly didn't)

 - Add rationales on design and clarifications/fixes in the
   documentation.

Changes in v2:

 - (Stephen Hemminger) don't use bitfields in struct wimax_dev 

 - (Stephen Hemminger) don't use the interface name for the generic
   netlink family, as it might change on if rename. Change to use the
   interface index.

 - (David Miller) move the WiMAX stack to net/wimax

 - misc checkpatch fixes, kconfig cleanups. 

 - use DEFINE_SPINLOCK


Details follow:

* The WiMAX kernel stack

The (kernel) WiMAX stack is (as of now) very basic; it conforms a
"control plane", in a similar fashion to cfg80211. 

We still don't know what's going to be a good abstraction layer
because so far, we have only see one WiMAX device for which we could
implement support. 

So the current abstraction layer provides a low-level WiMAX API with
means for resetting, monitoring state changes, controlling rfkill and
sending messages to the driver (in a driver specific format) back and
forth (more on this below). The documentation in include/net/wimax.h
provides more information.

The functional API provided by the kernel stack is exported to user
space via generic netlink. For convenience, libwimaxll, a shim library
is provided in the user space package wimax-tools, but direct access
and parsing of the generic protocol is possible. All the bits and
pieces needed to operate from user space are declared in
include/linux/wimax.h, which is marked for system-wide installation
with headers_install.

Implementation of drivers for other hardware using this model should
be more or less straightforward.

* The i2400m driver

The driver for the 2400m sits below the stack, providing the back end
operations to drive control (reset, rfkill, message passing) and
feeding it state change information. On the other side, the 2400m
driver connects to netdev, where it emulates an ethernet device (*1).

The driver itself is broken in two main parts: bus-generic and
bus-specific. The bus-generic (driver) speaks the device protocol and
uses hooks provided by the bus-specific driver to talk to the device
via whichever bus it happens to be connected through.

More information about the driver can be found in
drivers/net/wimax/i2400m/i2400m.h.


* The high level WiMAX API (user space)

This part is not implemented by the code in these patches, but
available for download at http://linuxwimax.org/Download (use release
1.3 components); that implementation is specific to the Intel
2400. Other vendor's might be similar or very different.

The "high level part" (scanning, connecting, disconnecting) in WiMAX
is quite more complex than in other technologies such as WiFi. In
general, a high level API is provided to allow for:

 - scanning for networks
 - connecting and disconnecting
 - controlling the radio (turn on / off)
 - other operations

This high level API is called the Common API and is being defined by a
set of participants in the WiMAX industry. It is a cross platform API,
meant to be vendor neutral; as such, it is very alien to Linux's
system level APIs and we are planning moving the API to D-BUS and
making it look "more native", but without loosing its spirit.

The implementation of this API aims to hide the user (connection
managers, to be more exact) from the common complexities of
WiMAX. Each vendor would implement what ever they need in user space
to expose said API (could be just a passthrough if the implementation
is in the driver or in the device).

Most of these complexities are related to scanning. Scanning in WiMAX
is expensive (power consumption wise and time wise), so it has to be
done carefully, and each device will have it's most optimal way to do
it.

Most (all?) WiMAX operators run on regulated channels. To connect to
an operator, you need to know the channels where to look and scan in
there for base stations; when found, you need to know which NAPs
(Network Access Providers, the infrastructure owner) they provide and
then the NAPs you find have to be mapped to NSPs (Network Service
Providers, the ISP).

Even if you did a scan on all the channels available in a country (for
example, the US channel plan) and found different base stations, all
that information is still not discoverable from them (the 802.16g
upcoming standard aims to fix that).

So a database that helps mapping all this information (base station
IDs vs NAPs vs NSPs) is needed; we call that the "provisioning"
information, and getting that information is not trivial. The
operators, as of now, provide it to you over the OMA-DM protocol--that
requires an OMA-DM client daemon running in your machine. Another way
to provision is manually, getting a provisioning file from either the
operator or someone that has an OMA-DM client daemon.

The problem is that, as of now, there is still no OMA-DM open source
client. In http://linuxwimax.org we provide one, but Intel does not
own it and it cannot be made open source. As we said above, it is
optional if you go with the manual mode.


Once there is a network, you can connect to it. 

For networks that require authentication (most), the Intel device
requires a supplicant in user space--because of a set of issues we are
working to resolve, it cannot be made open source yet, but it will. As
of now, it is provided as a shared library that is loaded dynamically.

When a network is connected, use a DHCP client to obtain an address
(this is how most WiMAX networks are configured). 

When you connect to a network/provider with whom you don't have a
service agreement, you will probably redirected to a landing page
where you will be able to sign up for service (in exchange for your
credit card number), much similar to some WiFi hotspots. This is known
as being "activated" for said provider and you only need to do it
once, as the provider will remember your MAC address.

When you are up and running, your connection will be handed over from
tower to tower as you move around without requiring a new connection
sequence or DHCP renegotiation.

Other operations provided by the "Common API" relate to obtain
provisioning information from the network, contact information for the
network operator, manipulating roaming modes, etc ...

Caveats:

 - the 2400 goes into idle power saving when connected if there is no
   network activity. It comes out of it automatically when traffic is
   sent to the network. But sometimes, to come out of it the network
   requests a DHCP renegotiation; the user space code still has a
   piece of code missing for doing it, so you need to manually restart
   your DHCP client.

*1 Previous iterations of the driver provided a Pure IP device,
   without Ethernet wrapping but we gave up. 

   Most DHCP software in user space and in the network backends assume
   the world is Ethernet (anything that uses ISC DHCP's client and/or
   daemon). 

   We ended up registering a PureIP hw type with IANA and creating
   patches to support it to the ISC DHCP suite (at
   http://linuxwimax.org/Download) and modifying dhclient, server, and
   friends so the hardware type PureIP was recognized and thus, when
   the DHCP client sent a request, it was with hwtype ==
   purerip. 

   However, on the server end (they shall remain nameless), many
   configurations are set to take only Ethernet hwtypes and when asked
   to take the server patches for pureip, they cringed.

   It's kind of understandable -- so we assume it'll take time for
   those patches to trickle into deployment and just moved to behave
   like Ethernet. The easy path.

*2 To build in other trees, you will need the following patches
   against the USB stack.

   http://kernel.org/pub/linux/kernel/people/gregkh/gregkh-2.6/patches/usb/usb-change-interface-to-usb_lock_device_for_reset.patch
   http://kernel.org/pub/linux/kernel/people/gregkh/gregkh-2.6/patches/usb/usb-introduce-usb_queue_reset-to-do-resets-from-atomic-contexts-v7.patch

   2.6.28-rc5 has been tested.

   Or clone
   git.kernel.org/pub/scm/linux/kernel/inaky/git/linux-wimax.git; note
   that tree will be rebased when the code is accepted into mailine.

Inaky Perez-Gonzalez (29):
  wimax: documentation for the stack
  wimax: headers for kernel API and user space interaction
  wimax: internal API for the kernel space WiMAX stack
  wimax: debug macros and debug settings for the WiMAX stack
  wimax: generic device management (registration, deregistration,
    lookup)
  genetlink: export genl_unregister_mc_group()
  wimax: basic API: kernel/user messaging, rfkill and reset
  debugfs: add helpers for exporting a size_t simple value
  wimax: debugfs controls
  wimax: Makefile, Kconfig and docbook linkage for the stack
  i2400m: documentation and instructions for usage
  i2400m: host/device procotol and core driver definitions
  i2400m: Generic probe/disconnect, reset and message passing
  i2400m: linkage to the networking stack
  i2400m: firmware loading and bootrom initialization
  i2400m: RX and TX data/control paths
  i2400m: various functions for device management
  i2400m: debugfs controls
  i2400m/USB: header for the USB bus driver
  i2400m/USB: probe/disconnect, dev init/shutdown and reset backends
  i2400m/USB: firmware upload backend
  i2400m/USB: TX and RX path backends
  i2400m/SDIO: header for the SDIO subdriver
  i2400m/SDIO: probe/disconnect, dev init/shutdown and reset backends
  i2400m/SDIO: firmware upload backend
  i2400m/SDIO: TX and RX path backends
  i2400m: Makefile and Kconfig
  wimax: export linux/wimax.h and linux/wimax/i2400m.h with
    headers_install
  wimax/i2400m: add CREDITS and MAINTAINERS entries

 CREDITS                                      |   17 +
 Documentation/DocBook/networking.tmpl        |    8 +
 Documentation/wimax/README.i2400m            |  260 ++++++
 Documentation/wimax/README.wimax             |   81 ++
 MAINTAINERS                                  |   16 +
 drivers/net/Kconfig                          |    2 +
 drivers/net/Makefile                         |    1 +
 drivers/net/wimax/Kconfig                    |   17 +
 drivers/net/wimax/Makefile                   |    2 +
 drivers/net/wimax/i2400m/Kconfig             |   49 +
 drivers/net/wimax/i2400m/Makefile            |   29 +
 drivers/net/wimax/i2400m/control.c           | 1291 ++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/debug-levels.h      |   45 +
 drivers/net/wimax/i2400m/debugfs.c           |  392 ++++++++
 drivers/net/wimax/i2400m/driver.c            |  728 +++++++++++++++
 drivers/net/wimax/i2400m/fw.c                | 1095 ++++++++++++++++++++++
 drivers/net/wimax/i2400m/i2400m-sdio.h       |  132 +++
 drivers/net/wimax/i2400m/i2400m-usb.h        |  264 ++++++
 drivers/net/wimax/i2400m/i2400m.h            |  755 +++++++++++++++
 drivers/net/wimax/i2400m/netdev.c            |  524 +++++++++++
 drivers/net/wimax/i2400m/op-rfkill.c         |  207 ++++
 drivers/net/wimax/i2400m/rx.c                |  534 +++++++++++
 drivers/net/wimax/i2400m/sdio-debug-levels.h |   22 +
 drivers/net/wimax/i2400m/sdio-fw.c           |  224 +++++
 drivers/net/wimax/i2400m/sdio-rx.c           |  255 +++++
 drivers/net/wimax/i2400m/sdio-tx.c           |  153 +++
 drivers/net/wimax/i2400m/sdio.c              |  511 ++++++++++
 drivers/net/wimax/i2400m/tx.c                |  817 ++++++++++++++++
 drivers/net/wimax/i2400m/usb-debug-levels.h  |   42 +
 drivers/net/wimax/i2400m/usb-fw.c            |  340 +++++++
 drivers/net/wimax/i2400m/usb-notif.c         |  269 ++++++
 drivers/net/wimax/i2400m/usb-rx.c            |  417 +++++++++
 drivers/net/wimax/i2400m/usb-tx.c            |  229 +++++
 drivers/net/wimax/i2400m/usb.c               |  591 ++++++++++++
 fs/debugfs/file.c                            |   32 +
 include/linux/Kbuild                         |    2 +
 include/linux/debugfs.h                      |    2 +
 include/linux/wimax.h                        |  234 +++++
 include/linux/wimax/Kbuild                   |    1 +
 include/linux/wimax/debug.h                  |  453 +++++++++
 include/linux/wimax/i2400m.h                 |  512 ++++++++++
 include/net/wimax.h                          |  520 +++++++++++
 net/Kconfig                                  |    2 +
 net/Makefile                                 |    1 +
 net/netlink/genetlink.c                      |    1 +
 net/wimax/Kconfig                            |   36 +
 net/wimax/Makefile                           |   13 +
 net/wimax/debug-levels.h                     |   42 +
 net/wimax/debugfs.c                          |   90 ++
 net/wimax/id-table.c                         |  142 +++
 net/wimax/op-msg.c                           |  421 +++++++++
 net/wimax/op-reset.c                         |  143 +++
 net/wimax/op-rfkill.c                        |  532 +++++++++++
 net/wimax/stack.c                            |  599 ++++++++++++
 net/wimax/wimax-internal.h                   |   91 ++
 55 files changed, 14188 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/wimax/README.i2400m
 create mode 100644 Documentation/wimax/README.wimax
 create mode 100644 drivers/net/wimax/Kconfig
 create mode 100644 drivers/net/wimax/Makefile
 create mode 100644 drivers/net/wimax/i2400m/Kconfig
 create mode 100644 drivers/net/wimax/i2400m/Makefile
 create mode 100644 drivers/net/wimax/i2400m/control.c
 create mode 100644 drivers/net/wimax/i2400m/debug-levels.h
 create mode 100644 drivers/net/wimax/i2400m/debugfs.c
 create mode 100644 drivers/net/wimax/i2400m/driver.c
 create mode 100644 drivers/net/wimax/i2400m/fw.c
 create mode 100644 drivers/net/wimax/i2400m/i2400m-sdio.h
 create mode 100644 drivers/net/wimax/i2400m/i2400m-usb.h
 create mode 100644 drivers/net/wimax/i2400m/i2400m.h
 create mode 100644 drivers/net/wimax/i2400m/netdev.c
 create mode 100644 drivers/net/wimax/i2400m/op-rfkill.c
 create mode 100644 drivers/net/wimax/i2400m/rx.c
 create mode 100644 drivers/net/wimax/i2400m/sdio-debug-levels.h
 create mode 100644 drivers/net/wimax/i2400m/sdio-fw.c
 create mode 100644 drivers/net/wimax/i2400m/sdio-rx.c
 create mode 100644 drivers/net/wimax/i2400m/sdio-tx.c
 create mode 100644 drivers/net/wimax/i2400m/sdio.c
 create mode 100644 drivers/net/wimax/i2400m/tx.c
 create mode 100644 drivers/net/wimax/i2400m/usb-debug-levels.h
 create mode 100644 drivers/net/wimax/i2400m/usb-fw.c
 create mode 100644 drivers/net/wimax/i2400m/usb-notif.c
 create mode 100644 drivers/net/wimax/i2400m/usb-rx.c
 create mode 100644 drivers/net/wimax/i2400m/usb-tx.c
 create mode 100644 drivers/net/wimax/i2400m/usb.c
 create mode 100644 include/linux/wimax.h
 create mode 100644 include/linux/wimax/Kbuild
 create mode 100644 include/linux/wimax/debug.h
 create mode 100644 include/linux/wimax/i2400m.h
 create mode 100644 include/net/wimax.h
 create mode 100644 net/wimax/Kconfig
 create mode 100644 net/wimax/Makefile
 create mode 100644 net/wimax/debug-levels.h
 create mode 100644 net/wimax/debugfs.c
 create mode 100644 net/wimax/id-table.c
 create mode 100644 net/wimax/op-msg.c
 create mode 100644 net/wimax/op-reset.c
 create mode 100644 net/wimax/op-rfkill.c
 create mode 100644 net/wimax/stack.c
 create mode 100644 net/wimax/wimax-internal.h

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH 01/29] wimax: documentation for the stack
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
@ 2008-12-15 12:57 ` Inaky Perez-Gonzalez
  2008-12-15 12:57 ` [PATCH 02/29] wimax: headers for kernel API and user space interaction Inaky Perez-Gonzalez
                   ` (28 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:57 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 Documentation/wimax/README.wimax |   81 ++++++++++++++++++++++++++++++++++++++
 1 files changed, 81 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/wimax/README.wimax

diff --git a/Documentation/wimax/README.wimax b/Documentation/wimax/README.wimax
new file mode 100644
index 0000000..b78c437
--- /dev/null
+++ b/Documentation/wimax/README.wimax
@@ -0,0 +1,81 @@
+
+   Linux kernel WiMAX stack
+
+   (C) 2008 Intel Corporation < linux-wimax@intel.com >
+
+   This provides a basic Linux kernel WiMAX stack to provide a common
+   control API for WiMAX devices, usable from kernel and user space.
+
+1. Design
+
+   The WiMAX stack is designed to provide for common WiMAX control
+   services to current and future WiMAX devices from any vendor.
+
+   Because currently there is only one and we don't know what would be the
+   common services, the APIs it currently provides are very minimal.
+   However, it is done in such a way that it is easily extensible to
+   accommodate future requirements.
+
+   The stack works by embedding a struct wimax_dev in your device's
+   control structures. This provides a set of callbacks that the WiMAX
+   stack will call in order to implement control operations requested by
+   the user. As well, the stack provides API functions that the driver
+   calls to notify about changes of state in the device.
+
+   The stack exports the API calls needed to control the device to user
+   space using generic netlink as a marshalling mechanism. You can access
+   them using your own code or use the wrappers provided for your
+   convenience in libwimax (in the wimax-tools package).
+
+   For detailed information on the stack, please see
+   include/linux/wimax.h.
+
+2. Usage
+
+   For usage in a driver (registration, API, etc) please refer to the
+   instructions in the header file include/linux/wimax.h.
+
+   When a device is registered with the WiMAX stack, a set of debugfs
+   files will appear in /sys/kernel/debug/wimax:wmxX can tweak for
+   control.
+
+2.1. Obtaining debug information: debugfs entries
+
+   The WiMAX stack is compiled, by default, with debug messages that can
+   be used to diagnose issues. By default, said messages are disabled.
+
+   The drivers will register debugfs entries that allow the user to tweak
+   debug settings.
+
+   Each driver, when registering with the stack, will cause a debugfs
+   directory named wimax:DEVICENAME to be created; optionally, it might
+   create more subentries below it.
+
+2.1.1. Increasing debug output
+
+   The files named *dl_* indicate knobs for controlling the debug output
+   of different submodules of the WiMAX stack:
+     *
+# find /sys/kernel/debug/wimax\:wmx0 -name \*dl_\*
+/sys/kernel/debug/wimax:wmx0/wimax_dl_stack
+/sys/kernel/debug/wimax:wmx0/wimax_dl_op_rfkill
+/sys/kernel/debug/wimax:wmx0/wimax_dl_op_reset
+/sys/kernel/debug/wimax:wmx0/wimax_dl_op_msg
+/sys/kernel/debug/wimax:wmx0/wimax_dl_id_table
+/sys/kernel/debug/wimax:wmx0/wimax_dl_debugfs
+/sys/kernel/debug/wimax:wmx0/.... # other driver specific files
+
+       NOTE: Of course, if debugfs is mounted in a directory other than
+       /sys/kernel/debug, those paths will change.
+
+   By reading the file you can obtain the current value of said debug
+   level; by writing to it, you can set it.
+
+   To increase the debug level of, for example, the id-table submodule,
+   just write:
+
+$ echo 3 > /sys/kernel/debug/wimax:wmx0/wimax_dl_id_table
+
+   Increasing numbers yield increasing debug information; for details of
+   what is printed and the available levels, check the source. The code
+   uses 0 for disabled and increasing values until 8.
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 02/29] wimax: headers for kernel API and user space interaction
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
  2008-12-15 12:57 ` [PATCH 01/29] wimax: documentation for the stack Inaky Perez-Gonzalez
@ 2008-12-15 12:57 ` Inaky Perez-Gonzalez
  2008-12-16  3:43   ` Wang Chen
  2008-12-15 12:57 ` [PATCH 03/29] wimax: internal API for the kernel space WiMAX stack Inaky Perez-Gonzalez
                   ` (27 subsequent siblings)
  29 siblings, 1 reply; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:57 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Definitions for the user/kernel API protocol through generic
netlink. User space can copy it verbatim and use it.

Kernel API definition declares the main data types and calls for the
drivers to integrate into the WiMAX stack. Provides usage
documentation.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 include/linux/wimax.h |  234 ++++++++++++++++++++++
 include/net/wimax.h   |  520 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 754 insertions(+), 0 deletions(-)
 create mode 100644 include/linux/wimax.h
 create mode 100644 include/net/wimax.h

diff --git a/include/linux/wimax.h b/include/linux/wimax.h
new file mode 100644
index 0000000..c89de7f
--- /dev/null
+++ b/include/linux/wimax.h
@@ -0,0 +1,234 @@
+/*
+ * Linux WiMax
+ * API for user space
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * This file declares the user/kernel protocol that is spoken over
+ * Generic Netlink, as well as any type declaration that is to be used
+ * by kernel and user space.
+ *
+ * It is intended for user space to clone it verbatim to use it as a
+ * primary reference for definitions.
+ *
+ * Stuff intended for kernel usage as well as full protocol and stack
+ * documentation is rooted in include/net/wimax.h.
+ */
+
+#ifndef __LINUX__WIMAX_H__
+#define __LINUX__WIMAX_H__
+
+#include <linux/types.h>
+
+enum {
+	/**
+	 * Version of the interface (unsigned decimal, MMm, max 25.5)
+	 * M - Major: change if removing or modifying an existing call.
+	 * m - minor: change when adding a new call
+	 */
+	WIMAX_GNL_VERSION = 00,
+	/* Generic NetLink attributes */
+	WIMAX_GNL_ATTR_INVALID = 0x00,
+	WIMAX_GNL_ATTR_MAX = 10,
+};
+
+
+/*
+ * Generic NetLink operations
+ *
+ * Most of these map to an API call; _OP_ stands for operation, _RP_
+ * for reply and _RE_ for report (aka: signal).
+ */
+enum {
+	WIMAX_GNL_OP_MSG_FROM_USER,	/* User to kernel message */
+	WIMAX_GNL_OP_MSG_TO_USER,	/* Kernel to user message */
+	WIMAX_GNL_OP_RFKILL,	/* Run wimax_rfkill() */
+	WIMAX_GNL_OP_RESET,	/* Run wimax_rfkill() */
+	WIMAX_GNL_RE_STATE_CHANGE,	/* Report: status change */
+};
+
+
+/* Message from user / to user */
+enum {
+	WIMAX_GNL_MSG_IFIDX = 1,
+	WIMAX_GNL_MSG_PIPE_NAME,
+	WIMAX_GNL_MSG_DATA,
+};
+
+
+/*
+ * wimax_rfkill()
+ *
+ * The state of the radio (ON/OFF) is mapped to the rfkill subsystem's
+ * switch state (DISABLED/ENABLED).
+ */
+enum wimax_rf_state {
+	WIMAX_RF_OFF = 0,	/* Radio is off, rfkill on/enabled */
+	WIMAX_RF_ON = 1,	/* Radio is on, rfkill off/disabled */
+	WIMAX_RF_QUERY = 2,
+};
+
+/* Attributes */
+enum {
+	WIMAX_GNL_RFKILL_IFIDX = 1,
+	WIMAX_GNL_RFKILL_STATE,
+};
+
+
+/* Attributes for wimax_reset() */
+enum {
+	WIMAX_GNL_RESET_IFIDX = 1,
+};
+
+
+/*
+ * Attributes for the Report State Change
+ *
+ * For now we just have the old and new states; new attributes might
+ * be added later on.
+ */
+enum {
+	WIMAX_GNL_STCH_IFIDX = 1,
+	WIMAX_GNL_STCH_STATE_OLD,
+	WIMAX_GNL_STCH_STATE_NEW,
+};
+
+
+/**
+ * enum wimax_st - The different states of a WiMAX device
+ * @__WIMAX_ST_NULL: The device structure has been allocated and zeroed,
+ *     but still wimax_dev_add() hasn't been called. There is no state.
+ *
+ * @WIMAX_ST_DOWN: The device has been registered with the WiMAX and
+ *     networking stacks, but it is not initialized (normally that is
+ *     done with 'ifconfig DEV up' [or equivalent], which can upload
+ *     firmware and enable communications with the device).
+ *     In this state, the device is powered down and using as less
+ *     power as possible.
+ *     This state is the default after a call to wimax_dev_add(). It
+ *     is ok to have drivers move directly to %WIMAX_ST_UNINITIALIZED
+ *     or %WIMAX_ST_RADIO_OFF in _probe() after the call to
+ *     wimax_dev_add().
+ *     It is recommended that the driver leaves this state when
+ *     calling 'ifconfig DEV up' and enters it back on 'ifconfig DEV
+ *     down'.
+ *
+ * @__WIMAX_ST_QUIESCING: The device is being torn down, so no API
+ *     operations are allowed to proceed except the ones needed to
+ *     complete the device clean up process.
+ *
+ * @WIMAX_ST_UNINITIALIZED: [optional] Communication with the device
+ *     is setup, but the device still requires some configuration
+ *     before being operational.
+ *     Some WiMAX API calls might work.
+ *
+ * @WIMAX_ST_RADIO_OFF: The device is fully up; radio is off (wether
+ *     by hardware or software switches).
+ *     It is recommended to always leave the device in this state
+ *     after initialization.
+ *
+ * @WIMAX_ST_READY: The device is fully up and radio is on.
+ *
+ * @WIMAX_ST_SCANNING: [optional] The device has been instructed to
+ *     scan. In this state, the device cannot be actively connected to
+ *     a network.
+ *
+ * @WIMAX_ST_CONNECTING: The device is connecting to a network. This
+ *     state exists because in some devices, the connect process can
+ *     include a number of negotiations between user space, kernel
+ *     space and the device. User space needs to know what the device
+ *     is doing. If the connect sequence in a device is atomic and
+ *     fast, the device can transition directly to CONNECTED
+ *
+ * @WIMAX_ST_CONNECTED: The device is connected to a network.
+ *
+ * @__WIMAX_ST_INVALID: This is an invalid state used to mark the
+ *     maximum numeric value of states.
+ *
+ * Description:
+ *
+ * Transitions from one state to another one are atomic and can only
+ * be caused in kernel space with wimax_state_change(). To read the
+ * state, use wimax_state_get().
+ *
+ * States starting with __ are internal and shall not be used or
+ * referred to by drivers or userspace. They look ugly, but that's the
+ * point -- if any use is made non-internal to the stack, it is easier
+ * to catch on review.
+ *
+ * All API operations [with well defined exceptions] will take the
+ * device mutex before starting and then check the state. If the state
+ * is %__WIMAX_ST_NULL, %WIMAX_ST_DOWN, %WIMAX_ST_UNINITIALIZED or
+ * %__WIMAX_ST_QUIESCING, it will drop the lock and quit with
+ * -%EINVAL, -%ENOMEDIUM, -%ENOTCONN or -%ESHUTDOWN.
+ *
+ * The order of the definitions is important, so we can do numerical
+ * comparisons (eg: < %WIMAX_ST_RADIO_OFF means the device is not ready
+ * to operate).
+ */
+/*
+ * The allowed state transitions are described in the table below
+ * (states in rows can go to states in columns where there is an X):
+ *
+ *                                  UNINI   RADIO READY SCAN CONNEC CONNEC
+ *             NULL DOWN QUIESCING TIALIZED  OFF        NING  TING   TED
+ * NULL         -    x
+ * DOWN              -      x        x       x
+ * QUIESCING         x      -
+ * UNINITIALIZED            x        -       x
+ * RADIO_OFF                x                -     x
+ * READY                    x                x     -     x     x      x
+ * SCANNING                 x                x     x     -     x      x
+ * CONNECTING               x                x     x     x     -      x
+ * CONNECTED                x                x     x                  -
+ *
+ * This table not available in kernel-doc because the formatting messes it up.
+ */
+ enum wimax_st {
+	__WIMAX_ST_NULL = 0,
+	WIMAX_ST_DOWN,
+	__WIMAX_ST_QUIESCING,
+	WIMAX_ST_UNINITIALIZED,
+	WIMAX_ST_RADIO_OFF,
+	WIMAX_ST_READY,
+	WIMAX_ST_SCANNING,
+	WIMAX_ST_CONNECTING,
+	WIMAX_ST_CONNECTED,
+	__WIMAX_ST_INVALID			/* Always keep last */
+};
+
+
+#endif /* #ifndef __LINUX__WIMAX_H__ */
diff --git a/include/net/wimax.h b/include/net/wimax.h
new file mode 100644
index 0000000..d17106b
--- /dev/null
+++ b/include/net/wimax.h
@@ -0,0 +1,520 @@
+/*
+ * Linux WiMAX
+ * Kernel space API for accessing WiMAX devices
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 WiMAX stack provides an API for controlling and managing the
+ * system's WiMAX devices. This API affects the control plane; the
+ * data plane is accessed via the network stack (netdev).
+ *
+ * Parts of the WiMAX stack API and notifications are exported to
+ * user space via Generic Netlink. In user space, libwimax (part of
+ * the wimax-tools package) provides a shim layer for accessing those
+ * calls.
+ *
+ * The API is standarized for all WiMAX devices and different drivers
+ * implement the backend support for it. However, device-specific
+ * messaging pipes are provided that can be used to issue commands and
+ * receive notifications in free form.
+ *
+ * Currently the messaging pipes are the only means of control as it
+ * is not known (due to the lack of more devices in the market) what
+ * will be a good abstraction layer. Expect this to change as more
+ * devices show in the market. This API is designed to be growable in
+ * order to address this problem.
+ *
+ * USAGE
+ *
+ * Embed a `struct wimax_dev` at the beginning of the the device's
+ * private structure, initialize and register it. For details, see
+ * `struct wimax_dev`s documentation.
+ *
+ * Once this is done, wimax-tools's libwimaxll can be used to
+ * communicate with the driver from user space. You user space
+ * application does not have to forcibily use libwimaxll and can talk
+ * the generic netlink protocol directly if desired.
+ *
+ * Remember this is a very low level API that will to provide all of
+ * WiMAX features. Other daemons and services running in user space
+ * are the expected clients of it. They offer a higher level API that
+ * applications should use (an example of this is the Intel's WiMAX
+ * Network Service for the i2400m).
+ *
+ * DESIGN
+ *
+ * Although not set on final stone, this very basic interface is
+ * mostly completed. Remember this is meant to grow as new common
+ * operations are decided upon. New operations will be added to the
+ * interface, intent being on keeping backwards compatibility as much
+ * as possible.
+ *
+ * This layer implements a set of calls to control a WiMAX device,
+ * exposing a frontend to the rest of the kernel and user space (via
+ * generic netlink) and a backend implementation in the driver through
+ * function pointers.
+ *
+ * WiMAX devices have a state, and a kernel-only API allows the
+ * drivers to manipulate that state. State transitions are atomic, and
+ * only some of them are allowed (see `enum wimax_st`).
+ *
+ * Most API calls will set the state automatically; in most cases
+ * drivers have to only report state changes due to external
+ * conditions.
+ *
+ * All API operations are 'atomic', serialized thorough a mutex in the
+ * `struct wimax_dev`.
+ *
+ * EXPORTING TO USER SPACE THROUGH GENERIC NETLINK
+ *
+ * The API is exported to user space using generic netlink (other
+ * methods can be added as needed).
+ *
+ * There is a Generic Netlink Family named "WiMAX", where interfaces
+ * supporting the WiMAX interface receive commands and broadcast their
+ * signals over a multicast group named "msg".
+ *
+ * Mapping to the source/destination interface is done by an interface
+ * index attribute.
+ *
+ * For user-to-kernel traffic (commands) we use a function call
+ * marshalling mechanism, where a message X with attributes A, B, C
+ * sent from user space to kernel space means executing the WiMAX API
+ * call wimax_X(A, B, C), sending the results back as a message.
+ *
+ * Kernel-to-user (notifications or signals) communication is sent
+ * over multicast groups. This allows to have multiple applications
+ * monitoring them.
+ *
+ * Each command/signal gets assigned it's own attribute policy. This
+ * way the validator will verify that all the attributes in there are
+ * only the ones that should be for each command/signal. Thing of an
+ * attribute mapping to a type+argumentname for each command/signal.
+ *
+ * If we had a single policy for *all* commands/signals, after running
+ * the validator we'd have to check "does this attribute belong in
+ * here"?  for each one. It can be done manually, but it's just easier
+ * to have the validator do that job with multiple policies. As well,
+ * it makes it easier to later expand each command/signal signature
+ * without affecting others and keeping the namespace more or less
+ * sane. Not that it is too complicated, but it makes it even easier.
+ *
+ * No state information is maintained in the kernel for each user
+ * space connection (the connection is stateless).
+ *
+ * TESTING FOR THE INTERFACE AND VERSIONING
+ *
+ * If network interface X is a WiMAX device, there will be a Generic
+ * Netlink family named "WiMAX X" and the device will present a
+ * "wimax" directory in it's network sysfs directory
+ * (/sys/class/net/DEVICE/wimax) [used by HAL].
+ *
+ * The inexistence of any of these means the device does not support
+ * this WiMAX API.
+ *
+ * By querying the generic netlink controller, versioning information
+ * and the multicast groups available can be found. Applications using
+ * the interface can either rely on that or use the generic netlink
+ * controller to figure out which generic netlink commands/signals are
+ * supported.
+ *
+ * NOTE: this versioning is a last resort to avoid hard
+ *    incompatibilities. It is the intention of the design of this
+ *    stack not to introduce backward incompatible changes.
+ *
+ * The version code has to fit in one byte (restrictions imposed by
+ * generic netlink); we use `version / 10` for the major version and
+ * `version % 10` for the minor. This gives 9 minors for each major
+ * and 25 majors.
+ *
+ * The version change protocol is as follow:
+ *
+ * - Major versions: needs to be increased if an existing message/API
+ *   call is changed or removed. Doesn't need to be changed if a new
+ *   message is added.
+ *
+ * - Minor version: needs to be increased if new messages/API calls are
+ *   being added or some other consideration that doesn't impact the
+ *   user-kernel interface too much (like some kind of bug fix) and
+ *   that is kind of left up in the air to common sense.
+ *
+ * User space code should not try to work if the major version it was
+ * compiled for differs from what the kernel offers. As well, if the
+ * minor version of the kernel interface is lower than the one user
+ * space is expecting (the one it was compiled for), the kernel
+ * might be missing API calls; user space shall be ready to handle
+ * said condition. Use the generic netlink controller operations to
+ * find which ones are supported and which not.
+ *
+ * libwimaxll:wimaxll_open() takes care of checking versions.
+ *
+ * THE OPERATIONS:
+ *
+ * Each operation is defined in its on file (drivers/net/wimax/op-*.c)
+ * for clarity. The parts needed for an operation are:
+ *
+ *  - a function pointer in `struct wimax_dev`: optional, as the
+ *    operation might be implemented by the stack and not by the
+ *    driver.
+ *
+ *    All function pointers are named wimax_dev->op_*(), and drivers
+ *    must implement them except where noted otherwise.
+ *
+ *  - When exported to user space, a `struct nla_policy` to define the
+ *    attributes of the generic netlink command and a `struct genl_ops`
+ *    to define the operation.
+ *
+ * All the declarations for the operation codes (WIMAX_GNL_OP_<NAME>)
+ * and generic netlink attributes (WIMAX_GNL_<NAME>_*) are declared in
+ * include/linux/wimax.h; this file is intended to be cloned by user
+ * space to gain access to those declarations.
+ *
+ * A few caveats to remember:
+ *
+ *  - Need to define attribute numbers starting in 1; otherwise it
+ *    fails.
+ *
+ *  - the `struct genl_family` requires a maximum attribute id; when
+ *    defining the `struct nla_policy` for each message, it has to have
+ *    an array size of WIMAX_GNL_ATTR_MAX+1.
+ *
+ * THE PIPE INTERFACE:
+ *
+ * This interface is kept intentionally simple. The driver can send
+ * and receive free-form messages to/from user space through a
+ * pipe. See drivers/net/wimax/op-msg.c for details.
+ *
+ * The kernel-to-user messages are sent with
+ * wimax_msg(). user-to-kernel messages are delivered via
+ * wimax_dev->op_msg_from_user().
+ *
+ * RFKILL:
+ *
+ * RFKILL support is built into the wimax_dev layer; the driver just
+ * needs to call wimax_report_rfkill_{hw,sw}() to inform of changes in
+ * the hardware or software RF kill switches. When the stack wants to
+ * turn the radio off, it will call wimax_dev->op_rfkill_sw_toggle(),
+ * which the driver implements.
+ *
+ * User space can set the software RF Kill switch by calling
+ * wimax_rfkill().
+ *
+ * The code for now only supports devices that don't require polling;
+ * If the device needs to be polled, create a self-rearming delayed
+ * work struct for polling or look into adding polled support to the
+ * WiMAX stack.
+ *
+ * When initializing the hardware (_probe), after calling
+ * wimax_dev_add(), query the device for it's RF Kill switches status
+ * and feed it back to the WiMAX stack using
+ * wimax_report_rfkill_{hw,sw}(). If any switch is missing, always
+ * report it as ON.
+ *
+ * NOTE: the wimax stack uses an inverted terminology to that of the
+ * RFKILL subsystem:
+ *
+ *  - ON: radio is ON, RFKILL is DISABLED or OFF.
+ *  - OFF: radio is OFF, RFKILL is ENABLED or ON.
+ *
+ * MISCELLANEOUS OPS:
+ *
+ * wimax_reset() can be used to reset the device to power on state; by
+ * default it issues a warm reset that maintains the same device
+ * node. If that is not possible, it falls back to a cold reset
+ * (device reconnect). The driver implements the backend to this
+ * through wimax_dev->op_reset().
+ */
+
+#ifndef __NET__WIMAX_H__
+#define __NET__WIMAX_H__
+#ifdef __KERNEL__
+
+#include <linux/wimax.h>
+#include <net/genetlink.h>
+#include <linux/netdevice.h>
+
+struct net_device;
+struct genl_info;
+struct wimax_dev;
+struct input_dev;
+
+/**
+ * struct wimax_dev - Generic WiMAX device
+ *
+ * @net_dev: [fill] Pointer to the &struct net_device this WiMAX
+ *     device implements.
+ *
+ * @op_msg_from_user: [fill] Driver-specific operation to
+ *     handle a raw message from user space to the driver. The
+ *     driver can send messages to user space using with
+ *     wimax_msg_to_user().
+ *
+ * @op_rfkill_sw_toggle: [fill] Driver-specific operation to act on
+ *     userspace (or any other agent) requesting the WiMAX device to
+ *     change the RF Kill software switch (WIMAX_RF_ON or
+ *     WIMAX_RF_OFF).
+ *     If such hardware support is not present, it is assumed the
+ *     radio cannot be switched off and it is always on (and the stack
+ *     will error out when trying to switch it off). In such case,
+ *     this function pointer can be left as NULL.
+ *
+ * @op_reset: [fill] Driver specific operation to reset the
+ *     device.
+ *     This operation should always attempt first a warm reset that
+ *     does not disconnect the device from the bus and return 0.
+ *     If that fails, it should resort to some sort of cold or bus
+ *     reset (even if it implies a bus disconnection and device
+ *     dissapearance). In that case, -ENODEV should be returned to
+ *     indicate the device is gone.
+ *     This operation has to be synchronous, and return only when the
+ *     reset is complete. In case of having had to resort to bus/cold
+ *     reset implying a device disconnection, the call is allowed to
+ *     return inmediately.
+ *     NOTE: wimax_dev->mutex is NOT locked when this op is being
+ *     called; however, wimax_dev->mutex_reset IS locked to ensure
+ *     serialization of calls to wimax_reset().
+ *     See wimax_reset()'s documentation.
+ *
+ * @name: [fill] A way to identify this device. We need to register a
+ *     name with many subsystems (input for RFKILL, workqueue
+ *     creation, etc). We can't use the network device name as that
+ *     might change and in some instances we don't know it yet (until
+ *     we don't call register_netdev()). So we generate an unique one
+ *     using the driver name and device bus id, place it here and use
+ *     it across the board. Recommended naming:
+ *     DRIVERNAME-BUSNAME:BUSID (dev->bus->name, dev->bus_id).
+ *
+ * @id_table_node: [private] link to the list of wimax devices kept by
+ *     id-table.c. Protected by it's own spinlock.
+ *
+ * @mutex: [private] Serializes all concurrent access and execution of
+ *     operations.
+ *
+ * @mutex_reset: [private] Serializes reset operations. Needs to be a
+ *     different mutex because as part of the reset operation, the
+ *     driver has to call back into the stack to do things such as
+ *     state change, that require wimax_dev->mutex.
+ *
+ * @state: [private] Current state of the WiMAX device.
+ *
+ * @rfkill: [private] integration into the RF-Kill infrastructure.
+ *
+ * @rfkill_input: [private] virtual input device to process the
+ *     hardware RF Kill switches.
+ *
+ * @rf_sw: [private] State of the software radio switch (OFF/ON)
+ *
+ * @rf_hw: [private] State of the hardware radio switch (OFF/ON)
+ *
+ * Description:
+ * This structure defines a common interface to access all WiMAX
+ * devices from different vendors and provides a common API as well as
+ * a free-form device-specific messaging channel.
+ *
+ * Usage:
+ *  1. Embed a &struct wimax_dev at *the beginning* the network
+ *     device structure so that net_dev->priv points to it.
+ *
+ *  2. memset() it to zero
+ *
+ *  3. Initialize with wimax_dev_init(). This will leave the WiMAX
+ *     device in the %__WIMAX_ST_NULL state.
+ *
+ *  4. Fill all the fields marked with [fill]; once called
+ *     wimax_dev_add(), those fields CANNOT be modified.
+ *
+ *  5. Call wimax_dev_add() *after* registering the network
+ *     device. This will leave the WiMAX device in the %WIMAX_ST_DOWN
+ *     state.
+ *     Protect the driver's net_device->open() against succeeding if
+ *     the wimax device state is lower than %WIMAX_ST_DOWN.
+ *
+ *  6. Select when the device is going to be turned on/initialized;
+ *     for example, it could be initialized on 'ifconfig up' (when the
+ *     netdev op 'open()' is called on the driver).
+ *
+ * When the device is initialized (at `ifconfig up` time, or right
+ * after calling wimax_dev_add() from _probe(), make sure the
+ * following steps are taken
+ *
+ *  a. Move the device to %WIMAX_ST_UNINITIALIZED. This is needed so
+ *     some API calls that shouldn't work until the device is ready
+ *     can be blocked.
+ *
+ *  b. Initialize the device. Make sure to turn the SW radio switch
+ *     off and move the device to state %WIMAX_ST_RADIO_OFF when
+ *     done. When just initialized, a device should be left in RADIO
+ *     OFF state until user space devices to turn it on.
+ *
+ *  c. Query the device for the state of the hardware rfkill switch
+ *     and call wimax_rfkill_report_hw() and wimax_rfkill_report_sw()
+ *     as needed. See below.
+ *
+ * wimax_dev_rm() undoes before unregistering the network device. Once
+ * wimax_dev_add() is called, the driver can get called on the
+ * wimax_dev->op_* function pointers
+ *
+ * CONCURRENCY:
+ *
+ * The stack provides a mutex for each device that will disallow API
+ * calls happening concurrently; thus, op calls into the driver
+ * through the wimax_dev->op*() function pointers will always be
+ * serialized and *never* concurrent.
+ *
+ * For locking, take wimax_dev->mutex is taken; (most) operations in
+ * the API have to check for wimax_dev_is_ready() to return 0 before
+ * continuing (this is done internally).
+ *
+ * REFERENCE COUNTING:
+ *
+ * The WiMAX device is reference counted by the associated network
+ * device. The only operation that can be used to reference the device
+ * is wimax_dev_get_by_genl_info(), and the reference it acquires has
+ * to be released with dev_put(wimax_dev->net_dev).
+ *
+ * RFKILL:
+ *
+ * At startup, both HW and SW radio switchess are assumed to be off.
+ *
+ * At initialization time [after calling wimax_dev_add()], have the
+ * driver query the device for the status of the software and hardware
+ * RF kill switches and call wimax_report_rfkill_hw() and
+ * wimax_rfkill_report_sw() to indicate their state. If any is
+ * missing, just call it to indicate it is ON (radio always on).
+ *
+ * Whenever the driver detects a change in the state of the RF kill
+ * switches, it should call wimax_report_rfkill_hw() or
+ * wimax_report_rfkill_sw() to report it to the stack.
+ */
+struct wimax_dev {
+	struct net_device *net_dev;
+	struct list_head id_table_node;
+	struct mutex mutex;		/* Protects all members and API calls */
+	struct mutex mutex_reset;
+	enum wimax_st state;
+
+	int (*op_msg_from_user)(struct wimax_dev *wimax_dev,
+				const char *,
+				const void *, size_t,
+				const struct genl_info *info);
+	int (*op_rfkill_sw_toggle)(struct wimax_dev *wimax_dev,
+				   enum wimax_rf_state);
+	int (*op_reset)(struct wimax_dev *wimax_dev);
+
+	struct rfkill *rfkill;
+	struct input_dev *rfkill_input;
+	unsigned rf_hw;
+	unsigned rf_sw;
+	char name[32];
+
+	struct dentry *debugfs_dentry;
+};
+
+
+
+/*
+ * WiMAX stack public API for device drivers
+ * -----------------------------------------
+ *
+ * These functions are not exported to user space.
+ */
+extern void wimax_dev_init(struct wimax_dev *);
+extern int wimax_dev_add(struct wimax_dev *, struct net_device *);
+extern void wimax_dev_rm(struct wimax_dev *);
+
+static inline
+struct wimax_dev *net_dev_to_wimax(struct net_device *net_dev)
+{
+	return netdev_priv(net_dev);
+}
+
+static inline
+struct device *wimax_dev_to_dev(struct wimax_dev *wimax_dev)
+{
+	return wimax_dev->net_dev->dev.parent;
+}
+
+extern void wimax_state_change(struct wimax_dev *, enum wimax_st);
+extern enum wimax_st wimax_state_get(struct wimax_dev *);
+
+/*
+ * Radio Switch state reporting.
+ *
+ * enum wimax_rf_state is declared in linux/wimax.h so the exports
+ * to user space can use it.
+ */
+extern void wimax_report_rfkill_hw(struct wimax_dev *, enum wimax_rf_state);
+extern void wimax_report_rfkill_sw(struct wimax_dev *, enum wimax_rf_state);
+
+
+/*
+ * Free-form messaging to/from user space
+ *
+ * Sending a message:
+ *
+ *   wimax_msg(wimax_dev, pipe_name, buf, buf_size, GFP_KERNEL);
+ *
+ * Broken up:
+ *
+ *   skb = wimax_msg_alloc(wimax_dev, pipe_name, buf_size, GFP_KERNEL);
+ *   ...fill up skb...
+ *   wimax_msg_send(wimax_dev, pipe_name, skb);
+ *
+ * Be sure not to modify skb->data in the middle (ie: don't use
+ * skb_push()/skb_pull()/skb_reserve() on the skb).
+ *
+ * "pipe_name" is any string, than can be interpreted as the name of
+ * the pipe or destinatary; the interpretation of it is driver
+ * specific, so the recipient can multiplex it as wished. It can be
+ * NULL, it won't be used - an example is using a "diagnostics" tag to
+ * send diagnostics information that a device-specific diagnostics
+ * tool would be interested in.
+ */
+extern struct sk_buff *wimax_msg_alloc(struct wimax_dev *, const char *,
+				       const void *, size_t, gfp_t);
+extern int wimax_msg_send(struct wimax_dev *, struct sk_buff *);
+extern int wimax_msg(struct wimax_dev *, const char *,
+		     const void *, size_t, gfp_t);
+
+extern const void *wimax_msg_data_len(struct sk_buff *, size_t *);
+extern const void *wimax_msg_data(struct sk_buff *);
+extern ssize_t wimax_msg_len(struct sk_buff *);
+
+
+/*
+ * WiMAX stack user space API
+ * --------------------------
+ *
+ * This API is what gets exported to user space for general
+ * operations. As well, they can be called from within the kernel,
+ * (with a properly referenced `struct wimax_dev`).
+ *
+ * Properly referenced means: the 'struct net_device' that embeds the
+ * device's control structure and (as such) the 'struct wimax_dev' is
+ * referenced by the caller.
+ */
+extern int wimax_rfkill(struct wimax_dev *, enum wimax_rf_state);
+extern int wimax_reset(struct wimax_dev *);
+
+#else
+/* You might be looking for linux/wimax.h */
+#error This file should not be included from user space.
+#endif /* #ifdef __KERNEL__ */
+#endif /* #ifndef __NET__WIMAX_H__ */
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 03/29] wimax: internal API for the kernel space WiMAX stack
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
  2008-12-15 12:57 ` [PATCH 01/29] wimax: documentation for the stack Inaky Perez-Gonzalez
  2008-12-15 12:57 ` [PATCH 02/29] wimax: headers for kernel API and user space interaction Inaky Perez-Gonzalez
@ 2008-12-15 12:57 ` Inaky Perez-Gonzalez
  2008-12-15 12:57 ` [PATCH 04/29] wimax: debug macros and debug settings for the " Inaky Perez-Gonzalez
                   ` (26 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:57 UTC (permalink / raw)
  To: netdev; +Cc: wimax, greg

This file contains declarations and definitions used by the different
submodules of the stack.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 net/wimax/wimax-internal.h |   91 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 91 insertions(+), 0 deletions(-)
 create mode 100644 net/wimax/wimax-internal.h

diff --git a/net/wimax/wimax-internal.h b/net/wimax/wimax-internal.h
new file mode 100644
index 0000000..1e743d2
--- /dev/null
+++ b/net/wimax/wimax-internal.h
@@ -0,0 +1,91 @@
+/*
+ * Linux WiMAX
+ * Internal API for kernel space WiMAX stack
+ *
+ *
+ * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 header file is for declarations and definitions internal to
+ * the WiMAX stack. For public APIs and documentation, see
+ * include/net/wimax.h and include/linux/wimax.h.
+ */
+
+#ifndef __WIMAX_INTERNAL_H__
+#define __WIMAX_INTERNAL_H__
+#ifdef __KERNEL__
+
+#include <linux/device.h>
+#include <net/wimax.h>
+
+
+/*
+ * Decide if a (locked) device is ready for use
+ *
+ * Before using the device structure, it must be locked
+ * (wimax_dev->mutex). As well, most operations need to call this
+ * function to check if the state is the right one.
+ *
+ * An error value will be returned if the state is not the right
+ * one. In that case, the caller should not attempt to use the device
+ * and just unlock it.
+ */
+static inline __must_check
+int wimax_dev_is_ready(struct wimax_dev *wimax_dev)
+{
+	if (wimax_dev->state == __WIMAX_ST_NULL)
+		return -EINVAL;	/* Device is not even registered! */
+	if (wimax_dev->state == WIMAX_ST_DOWN)
+		return -ENOMEDIUM;
+	if (wimax_dev->state == __WIMAX_ST_QUIESCING)
+		return -ESHUTDOWN;
+	return 0;
+}
+
+
+static inline
+void __wimax_state_set(struct wimax_dev *wimax_dev, enum wimax_st state)
+{
+	wimax_dev->state = state;
+}
+extern void __wimax_state_change(struct wimax_dev *, enum wimax_st);
+
+#ifdef CONFIG_DEBUG_FS
+extern int wimax_debugfs_add(struct wimax_dev *);
+extern void wimax_debugfs_rm(struct wimax_dev *);
+#else
+static inline int wimax_debugfs_add(struct wimax_dev *wimax_dev)
+{
+	return 0;
+}
+static inline void wimax_debugfs_rm(struct wimax_dev *wimax_dev) {}
+#endif
+
+extern void wimax_id_table_add(struct wimax_dev *);
+extern struct wimax_dev *wimax_dev_get_by_genl_info(struct genl_info *, int);
+extern void wimax_id_table_rm(struct wimax_dev *);
+extern void wimax_id_table_release(void);
+
+extern int wimax_rfkill_add(struct wimax_dev *);
+extern void wimax_rfkill_rm(struct wimax_dev *);
+
+extern struct genl_family wimax_gnl_family;
+extern struct genl_multicast_group wimax_gnl_mcg;
+
+#endif /* #ifdef __KERNEL__ */
+#endif /* #ifndef __WIMAX_INTERNAL_H__ */
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 04/29] wimax: debug macros and debug settings for the WiMAX stack
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (2 preceding siblings ...)
  2008-12-15 12:57 ` [PATCH 03/29] wimax: internal API for the kernel space WiMAX stack Inaky Perez-Gonzalez
@ 2008-12-15 12:57 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 05/29] wimax: generic device management (registration, deregistration, lookup) Inaky Perez-Gonzalez
                   ` (25 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:57 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This file contains a simple debug framework that is used in the stack;
it allows the debug level to be controlled at compile-time (so the
debug code is optimized out) and at run-time (for what wasn't compiled
out).

This is eventually going to be moved to use dynamic_printk(). Just
need to find time to do it.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 include/linux/wimax/debug.h |  453 +++++++++++++++++++++++++++++++++++++++++++
 net/wimax/debug-levels.h    |   42 ++++
 2 files changed, 495 insertions(+), 0 deletions(-)
 create mode 100644 include/linux/wimax/debug.h
 create mode 100644 net/wimax/debug-levels.h

diff --git a/include/linux/wimax/debug.h b/include/linux/wimax/debug.h
new file mode 100644
index 0000000..ba0c493
--- /dev/null
+++ b/include/linux/wimax/debug.h
@@ -0,0 +1,453 @@
+/*
+ * Linux WiMAX
+ * Collection of tools to manage debug operations.
+ *
+ *
+ * Copyright (C) 2005-2007 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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.
+ *
+ *
+ * Don't #include this file directly, read on!
+ *
+ *
+ * EXECUTING DEBUGGING ACTIONS OR NOT
+ *
+ * The main thing this framework provides is decission power to take a
+ * debug action (like printing a message) if the current debug level
+ * allows it.
+ *
+ * The decission power is at two levels: at compile-time (what does
+ * not make it is compiled out) and at run-time. The run-time
+ * selection is done per-submodule (as they are declared by the user
+ * of the framework).
+ *
+ * A call to d_test(L) (L being the target debug level) returns true
+ * if the action should be taken because the current debug levels
+ * allow it (both compile and run time).
+ *
+ * It follows that a call to d_test() that can be determined to be
+ * always false at compile time will get the code depending on it
+ * compiled out by optimization.
+ *
+ *
+ * DEBUG LEVELS
+ *
+ * It is up to the caller to define how much a debugging level is.
+ *
+ * Convention sets 0 as "no debug" (so an action marked as debug level 0
+ * will always be taken). The increasing debug levels are used for
+ * increased verbosity.
+ *
+ *
+ * USAGE
+ *
+ * Group the code in modules and submodules inside each module [which
+ * in most cases maps to Linux modules and .c files that compose
+ * those].
+ *
+ *
+ * For each module, there is:
+ *
+ *  - a MODULENAME (single word, legal C identifier)
+ *
+ *  - a debug-levels.h header file that declares the list of
+ *    submodules and that is included by all .c files that use
+ *    the debugging tools. The file name can be anything.
+ *
+ *  - some (optional) .c code to manipulate the runtime debug levels
+ *    through debugfs.
+ *
+ * The debug-levels.h file would look like:
+ *
+ *     #ifndef __debug_levels__h__
+ *     #define __debug_levels__h__
+ *
+ *     #define D_MODULENAME modulename
+ *     #define D_MASTER 10
+ *
+ *     #include <linux/wimax/debug.h>
+ *
+ *     enum d_module {
+ *             D_SUBMODULE_DECLARE(submodule_1),
+ *             D_SUBMODULE_DECLARE(submodule_2),
+ *             ...
+ *             D_SUBMODULE_DECLARE(submodule_N)
+ *     };
+ *
+ *     #endif
+ *
+ * D_MASTER is the maximum compile-time debug level; any debug actions
+ * above this will be out. D_MODULENAME is the module name (legal C
+ * identifier), which has to be unique for each module (to avoid
+ * namespace collisions during linkage). Note those #defines need to
+ * be done before #including debug.h
+ *
+ * We declare N different submodules whose debug level can be
+ * independently controlled during runtime.
+ *
+ * In a .c file of the module (and only in one of them), define the
+ * following code:
+ *
+ *     struct d_level D_LEVEL[] = {
+ *             D_SUBMODULE_DEFINE(submodule_1),
+ *             D_SUBMODULE_DEFINE(submodule_2),
+ *             ...
+ *             D_SUBMODULE_DEFINE(submodule_N),
+ *     };
+ *     size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+ *
+ * Externs for d_level_MODULENAME and d_level_size_MODULENAME are used
+ * and declared in this file using the D_LEVEL and D_LEVEL_SIZE macros
+ * #defined also in this file.
+ *
+ * To manipulate from user space the levels, create a debugfs dentry
+ * and then register each submodule with:
+ *
+ *     result = d_level_register_debugfs("PREFIX_", submodule_X, parent);
+ *     if (result < 0)
+ *            goto error;
+ *
+ * Where PREFIX_ is a name of your chosing. This will create debugfs
+ * file with a single numeric value that can be use to tweak it. To
+ * remove the entires, just use debugfs_remove_recursive() on 'parent'.
+ *
+ * NOTE: remember that even if this will show attached to some
+ *     particular instance of a device, the settings are *global*.
+ *
+ *
+ * On each submodule (for example, .c files), the debug infrastructure
+ * should be included like this:
+ *
+ *     #define D_SUBMODULE submodule_x     // matches one in debug-levels.h
+ *     #include "debug-levels.h"
+ *
+ * after #including all your include files.
+ *
+ *
+ * Now you can use the d_*() macros below [d_test(), d_fnstart(),
+ * d_fnend(), d_printf(), d_dump()].
+ *
+ * If their debug level is greater than D_MASTER, they will be
+ * compiled out.
+ *
+ * If their debug level is lower or equal than D_MASTER but greater
+ * than the current debug level of their submodule, they'll be
+ * ignored.
+ *
+ * Otherwise, the action will be performed.
+ */
+#ifndef __debug__h__
+#define __debug__h__
+
+#include <linux/types.h>
+#include <linux/device.h>
+
+
+/* Backend stuff */
+
+/*
+ * Debug backend: generate a message header from a 'struct device'
+ *
+ * @head: buffer where to place the header
+ * @head_size: length of @head
+ * @dev: pointer to device used to generate a header from. If NULL,
+ *     an empty ("") header is generated.
+ */
+static inline
+void __d_head(char *head, size_t head_size,
+	      struct device *dev)
+{
+	if (dev == NULL)
+		head[0] = 0;
+	else if ((unsigned long)dev < 4096) {
+		printk(KERN_ERR "E: Corrupt dev %p\n", dev);
+		WARN_ON(1);
+	} else
+		snprintf(head, head_size, "%s %s: ",
+			 dev_driver_string(dev), dev->bus_id);
+}
+
+
+/*
+ * Debug backend: log some message if debugging is enabled
+ *
+ * @l: intended debug level
+ * @tag: tag to prefix the message with
+ * @dev: 'struct device' associated to this message
+ * @f: printf-like format and arguments
+ *
+ * Note this is optimized out if it doesn't pass the compile-time
+ * check; however, it is *always* compiled. This is useful to make
+ * sure the printf-like formats and variables are always checked and
+ * they don't get bit rot if you have all the debugging disabled.
+ */
+#define _d_printf(l, tag, dev, f, a...)					\
+do {									\
+	char head[64];							\
+	if (!d_test(l))							\
+		break;							\
+	__d_head(head, sizeof(head), dev);				\
+	printk(KERN_ERR "%s%s%s: " f, head, __func__, tag, ##a);	\
+} while (0)
+
+
+/*
+ * CPP sintatic sugar to generate A_B like symbol names when one of
+ * the arguments is a a preprocessor #define.
+ */
+#define __D_PASTE__(varname, modulename) varname##_##modulename
+#define __D_PASTE(varname, modulename) (__D_PASTE__(varname, modulename))
+#define _D_SUBMODULE_INDEX(_name) (D_SUBMODULE_DECLARE(_name))
+
+
+/*
+ * Store a submodule's runtime debug level and name
+ */
+struct d_level {
+	u8 level;
+	const char *name;
+};
+
+
+/*
+ * List of available submodules and their debug levels
+ *
+ * We call them d_level_MODULENAME and d_level_size_MODULENAME; the
+ * macros D_LEVEL and D_LEVEL_SIZE contain the name already for
+ * convenience.
+ *
+ * This array and the size are defined on some .c file that is part of
+ * the current module.
+ */
+#define D_LEVEL __D_PASTE(d_level, D_MODULENAME)
+#define D_LEVEL_SIZE __D_PASTE(d_level_size, D_MODULENAME)
+
+extern struct d_level D_LEVEL[];
+extern size_t D_LEVEL_SIZE;
+
+
+/*
+ * Frontend stuff
+ *
+ *
+ * Stuff you need to declare prior to using the actual "debug" actions
+ * (defined below).
+ */
+
+#ifndef D_MODULENAME
+#error D_MODULENAME is not defined in your debug-levels.h file
+/**
+ * D_MODULE - Name of the current module
+ *
+ * #define in your module's debug-levels.h, making sure it is
+ * unique. This has to be a legal C identifier.
+ */
+#define D_MODULENAME undefined_modulename
+#endif
+
+
+#ifndef D_MASTER
+#warning D_MASTER not defined, but debug.h included! [see docs]
+/**
+ * D_MASTER - Compile time maximum debug level
+ *
+ * #define in your debug-levels.h file to the maximum debug level the
+ * runtime code will be allowed to have. This allows you to provide a
+ * main knob.
+ *
+ * Anything above that level will be optimized out of the compile.
+ *
+ * Defaults to zero (no debug code compiled in).
+ *
+ * Maximum one definition per module (at the debug-levels.h file).
+ */
+#define D_MASTER 0
+#endif
+
+#ifndef D_SUBMODULE
+#error D_SUBMODULE not defined, but debug.h included! [see docs]
+/**
+ * D_SUBMODULE - Name of the current submodule
+ *
+ * #define in your submodule .c file before #including debug-levels.h
+ * to the name of the current submodule as previously declared and
+ * defined with D_SUBMODULE_DECLARE() (in your module's
+ * debug-levels.h) and D_SUBMODULE_DEFINE().
+ *
+ * This is used to provide runtime-control over the debug levels.
+ *
+ * Maximum one per .c file! Can be shared among different .c files
+ * (meaning they belong to the same submodule categorization).
+ */
+#define D_SUBMODULE undefined_module
+#endif
+
+
+/**
+ * D_SUBMODULE_DECLARE - Declare a submodule for runtime debug level control
+ *
+ * @_name: name of the submodule, restricted to the chars that make up a
+ *     valid C identifier ([a-zA-Z0-9_]).
+ *
+ * Declare in the module's debug-levels.h header file as:
+ *
+ * enum d_module {
+ *         D_SUBMODULE_DECLARE(submodule_1),
+ *         D_SUBMODULE_DECLARE(submodule_2),
+ *         D_SUBMODULE_DECLARE(submodule_3),
+ * };
+ *
+ * Some corresponding .c file needs to have a matching
+ * D_SUBMODULE_DEFINE().
+ */
+#define D_SUBMODULE_DECLARE(_name) __D_SUBMODULE_##_name
+
+
+/**
+ * D_SUBMODULE_DEFINE - Define a submodule for runtime debug level control
+ *
+ * @_name: name of the submodule, restricted to the chars that make up a
+ *     valid C identifier ([a-zA-Z0-9_]).
+ *
+ * Use once per module (in some .c file) as:
+ *
+ * static
+ * struct d_level d_level_SUBMODULENAME[] = {
+ *         D_SUBMODULE_DEFINE(submodule_1),
+ *         D_SUBMODULE_DEFINE(submodule_2),
+ *         D_SUBMODULE_DEFINE(submodule_3),
+ * };
+ * size_t d_level_size_SUBDMODULENAME = ARRAY_SIZE(d_level_SUBDMODULENAME);
+ *
+ * Matching D_SUBMODULE_DECLARE()s have to be present in a
+ * debug-levels.h header file.
+ */
+#define D_SUBMODULE_DEFINE(_name)		\
+[__D_SUBMODULE_##_name] = {			\
+	.level = 0,				\
+	.name = #_name				\
+}
+
+
+
+/* The actual "debug" operations */
+
+
+/**
+ * d_test - Returns true if debugging should be enabled
+ *
+ * @l: intended debug level (unsigned)
+ *
+ * If the master debug switch is enabled and the current settings are
+ * higher or equal to the requested level, then debugging
+ * output/actions should be enabled.
+ *
+ * NOTE:
+ *
+ * This needs to be coded so that it can be evaluated in compile
+ * time; this is why the ugly BUG_ON() is placed in there, so the
+ * D_MASTER evaluation compiles all out if it is compile-time false.
+ */
+#define d_test(l)							\
+({									\
+	unsigned __l = l;	/* type enforcer */			\
+	(D_MASTER) >= __l						\
+	&& ({								\
+		BUG_ON(_D_SUBMODULE_INDEX(D_SUBMODULE) >= D_LEVEL_SIZE);\
+		D_LEVEL[_D_SUBMODULE_INDEX(D_SUBMODULE)].level >= __l;	\
+	});								\
+})
+
+
+/**
+ * d_fnstart - log message at function start if debugging enabled
+ *
+ * @l: intended debug level
+ * @_dev: 'struct device' pointer, NULL if none (for context)
+ * @f: printf-like format and arguments
+ */
+#define d_fnstart(l, _dev, f, a...) _d_printf(l, " FNSTART", _dev, f, ## a)
+
+
+/**
+ * d_fnend - log message at function end if debugging enabled
+ *
+ * @l: intended debug level
+ * @_dev: 'struct device' pointer, NULL if none (for context)
+ * @f: printf-like format and arguments
+ */
+#define d_fnend(l, _dev, f, a...) _d_printf(l, " FNEND", _dev, f, ## a)
+
+
+/**
+ * d_printf - log message if debugging enabled
+ *
+ * @l: intended debug level
+ * @_dev: 'struct device' pointer, NULL if none (for context)
+ * @f: printf-like format and arguments
+ */
+#define d_printf(l, _dev, f, a...) _d_printf(l, "", _dev, f, ## a)
+
+
+/**
+ * d_dump - log buffer hex dump if debugging enabled
+ *
+ * @l: intended debug level
+ * @_dev: 'struct device' pointer, NULL if none (for context)
+ * @f: printf-like format and arguments
+ */
+#define d_dump(l, dev, ptr, size)			\
+do {							\
+	char head[64];					\
+	if (!d_test(l))					\
+		break;					\
+	__d_head(head, sizeof(head), dev);		\
+	print_hex_dump(KERN_ERR, head, 0, 16, 1,	\
+		       ((void *) ptr), (size), 0);	\
+} while (0)
+
+
+/**
+ * Export a submodule's debug level over debugfs as PREFIXSUBMODULE
+ *
+ * @prefix: string to prefix the name with
+ * @submodule: name of submodule (not a string, just the name)
+ * @dentry: debugfs parent dentry
+ *
+ * Returns: 0 if ok, < 0 errno on error.
+ *
+ * For removing, just use debugfs_remove_recursive() on the parent.
+ */
+#define d_level_register_debugfs(prefix, name, parent)			\
+({									\
+	int rc;								\
+	struct dentry *fd;						\
+	struct dentry *verify_parent_type = parent;			\
+	fd = debugfs_create_u8(						\
+		prefix #name, 0600, verify_parent_type,			\
+		&(D_LEVEL[__D_SUBMODULE_ ## name].level));		\
+	rc = PTR_ERR(fd);						\
+	if (IS_ERR(fd) && rc != -ENODEV)				\
+		printk(KERN_ERR "%s: Can't create debugfs entry %s: "	\
+		       "%d\n", __func__, prefix #name, rc);		\
+	else								\
+		rc = 0;							\
+	rc;								\
+})
+
+
+#endif /* #ifndef __debug__h__ */
diff --git a/net/wimax/debug-levels.h b/net/wimax/debug-levels.h
new file mode 100644
index 0000000..1c29123
--- /dev/null
+++ b/net/wimax/debug-levels.h
@@ -0,0 +1,42 @@
+/*
+ * Linux WiMAX Stack
+ * Debug levels control file for the wimax module
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 __debug_levels__h__
+#define __debug_levels__h__
+
+/* Maximum compile and run time debug level for all submodules */
+#define D_MODULENAME wimax
+#define D_MASTER CONFIG_WIMAX_DEBUG_LEVEL
+
+#include <linux/wimax/debug.h>
+
+/* List of all the enabled modules */
+enum d_module {
+	D_SUBMODULE_DECLARE(debugfs),
+	D_SUBMODULE_DECLARE(id_table),
+	D_SUBMODULE_DECLARE(op_msg),
+	D_SUBMODULE_DECLARE(op_reset),
+	D_SUBMODULE_DECLARE(op_rfkill),
+	D_SUBMODULE_DECLARE(stack),
+};
+
+#endif /* #ifndef __debug_levels__h__ */
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 05/29] wimax: generic device management (registration, deregistration, lookup)
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (3 preceding siblings ...)
  2008-12-15 12:57 ` [PATCH 04/29] wimax: debug macros and debug settings for the " Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 06/29] genetlink: export genl_unregister_mc_group() Inaky Perez-Gonzalez
                   ` (24 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements the basic life cycles of a 'struct wimax_dev', some common
generic netlink functionality for marshalling calls to user space,
and the device state machine.

For looking up net devices based on their generic netlink family IDs,
use a low overhead method that optimizes for the case where most
systems have a single WiMAX device, or at most, a very low number of
WiMAX adaptors.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 net/wimax/id-table.c |  142 ++++++++++++
 net/wimax/stack.c    |  599 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 741 insertions(+), 0 deletions(-)
 create mode 100644 net/wimax/id-table.c
 create mode 100644 net/wimax/stack.c

diff --git a/net/wimax/id-table.c b/net/wimax/id-table.c
new file mode 100644
index 0000000..d3b8855
--- /dev/null
+++ b/net/wimax/id-table.c
@@ -0,0 +1,142 @@
+/*
+ * Linux WiMAX
+ * Mappping of generic netlink family IDs to net devices
+ *
+ *
+ * Copyright (C) 2005-2006 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 assign a single generic netlink family ID to each device (to
+ * simplify lookup).
+ *
+ * We need a way to map family ID to a wimax_dev pointer.
+ *
+ * The idea is to use a very simple lookup. Using a netlink attribute
+ * with (for example) the interface name implies a heavier search over
+ * all the network devices; seemed kind of a waste given that we know
+ * we are looking for a WiMAX device and that most systems will have
+ * just a single WiMAX adapter.
+ *
+ * We put all the WiMAX devices in the system in a linked list and
+ * match the generic link family ID against the list.
+ *
+ * By using a linked list, the case of a single adapter in the system
+ * becomes (almost) no overhead, while still working for many more. If
+ * it ever goes beyond two, I'll be surprised.
+ */
+#include <linux/device.h>
+#include <net/genetlink.h>
+#include <linux/netdevice.h>
+#include <linux/list.h>
+#include <linux/wimax.h>
+#include "wimax-internal.h"
+
+
+#define D_SUBMODULE id_table
+#include "debug-levels.h"
+
+
+static DEFINE_SPINLOCK(wimax_id_table_lock);
+static struct list_head wimax_id_table = LIST_HEAD_INIT(wimax_id_table);
+
+
+/*
+ * wimax_id_table_add - add a gennetlink familiy ID / wimax_dev mapping
+ *
+ * @wimax_dev: WiMAX device descriptor to associate to the Generic
+ *     Netlink family ID.
+ *
+ * Look for an empty spot in the ID table; if none found, double the
+ * table's size and get the first spot.
+ */
+void wimax_id_table_add(struct wimax_dev *wimax_dev)
+{
+	d_fnstart(3, NULL, "(wimax_dev %p)\n", wimax_dev);
+	spin_lock(&wimax_id_table_lock);
+	list_add(&wimax_dev->id_table_node, &wimax_id_table);
+	spin_unlock(&wimax_id_table_lock);
+	d_fnend(3, NULL, "(wimax_dev %p)\n", wimax_dev);
+}
+
+
+/*
+ * wimax_get_netdev_by_info - lookup a wimax_dev from the gennetlink info
+ *
+ * The generic netlink family ID has been filled out in the
+ * nlmsghdr->nlmsg_type field, so we pull it from there, look it up in
+ * the mapping table and reference the wimax_dev.
+ *
+ * When done, the reference should be dropped with
+ * 'dev_put(wimax_dev->net_dev)'.
+ */
+struct wimax_dev *wimax_dev_get_by_genl_info(
+	struct genl_info *info, int ifindex)
+{
+	struct wimax_dev *wimax_dev = NULL;
+
+	d_fnstart(3, NULL, "(info %p ifindex %d)\n", info, ifindex);
+	spin_lock(&wimax_id_table_lock);
+	list_for_each_entry(wimax_dev, &wimax_id_table, id_table_node) {
+		if (wimax_dev->net_dev->ifindex == ifindex) {
+			dev_hold(wimax_dev->net_dev);
+			break;
+		}
+	}
+	if (wimax_dev == NULL)
+		d_printf(1, NULL, "wimax: no devices found with ifindex %d\n",
+			 ifindex);
+	spin_unlock(&wimax_id_table_lock);
+	d_fnend(3, NULL, "(info %p ifindex %d) = %p\n",
+		info, ifindex, wimax_dev);
+	return wimax_dev;
+}
+
+
+/*
+ * wimax_id_table_rm - Remove a gennetlink familiy ID / wimax_dev mapping
+ *
+ * @id: family ID to remove from the table
+ */
+void wimax_id_table_rm(struct wimax_dev *wimax_dev)
+{
+	spin_lock(&wimax_id_table_lock);
+	list_del_init(&wimax_dev->id_table_node);
+	spin_unlock(&wimax_id_table_lock);
+}
+
+
+/*
+ * Release the gennetlink family id / mapping table
+ *
+ * On debug, verify that the table is empty upon removal.
+ */
+void wimax_id_table_release(void)
+{
+#ifndef CONFIG_BUG
+	return;
+#endif
+	struct wimax_dev *wimax_dev;
+
+	spin_lock(&wimax_id_table_lock);
+	list_for_each_entry(wimax_dev, &wimax_id_table, id_table_node) {
+		printk(KERN_ERR "BUG: %s wimax_dev %p ifindex %d not cleared\n",
+		       __func__, wimax_dev, wimax_dev->net_dev->ifindex);
+		WARN_ON(1);
+	}
+	spin_unlock(&wimax_id_table_lock);
+}
diff --git a/net/wimax/stack.c b/net/wimax/stack.c
new file mode 100644
index 0000000..e5fd1ae
--- /dev/null
+++ b/net/wimax/stack.c
@@ -0,0 +1,599 @@
+/*
+ * Linux WiMAX
+ * Initialization, addition and removal of wimax devices
+ *
+ *
+ * Copyright (C) 2005-2006 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 implements:
+ *
+ *   - basic life cycle of 'struct wimax_dev' [wimax_dev_*()]; on
+ *     addition/registration initialize all subfields and allocate
+ *     generic netlink resources for user space communication. On
+ *     removal/unregistration, undo all that.
+ *
+ *   - device state machine [wimax_state_change()] and support to send
+ *     reports to user space when the state changes
+ *     [wimax_gnl_re_state_change*()].
+ *
+ * See include/net/wimax.h for rationales and design.
+ *
+ * ROADMAP
+ *
+ * [__]wimax_state_change()     Called by drivers to update device's state
+ *   wimax_gnl_re_state_change_alloc()
+ *   wimax_gnl_re_state_change_send()
+ *
+ * wimax_dev_init()	        Init a device
+ * wimax_dev_add()              Register
+ *   wimax_rfkill_add()
+ *   wimax_gnl_add()            Register all the generic netlink resources.
+ *   wimax_id_table_add()
+ * wimax_dev_rm()               Unregister
+ *   wimax_id_table_rm()
+ *   wimax_gnl_rm()
+ *   wimax_rfkill_rm()
+ */
+#include <linux/device.h>
+#include <net/genetlink.h>
+#include <linux/netdevice.h>
+#include <linux/wimax.h>
+#include "wimax-internal.h"
+
+
+#define D_SUBMODULE stack
+#include "debug-levels.h"
+
+/*
+ * Authoritative source for the RE_STATE_CHANGE attribute policy
+ *
+ * We don't really use it here, but /me likes to keep the definition
+ * close to where the data is generated.
+ */
+/*
+static const
+struct nla_policy wimax_gnl_re_status_change[WIMAX_GNL_ATTR_MAX + 1] = {
+	[WIMAX_GNL_STCH_STATE_OLD] = { .type = NLA_U8 },
+	[WIMAX_GNL_STCH_STATE_NEW] = { .type = NLA_U8 },
+};
+*/
+
+
+/*
+ * Allocate a Report State Change message
+ *
+ * @header: save it, you need it for _send()
+ *
+ * Creates and fills a basic state change message; different code
+ * paths can then add more attributes to the message as needed.
+ *
+ * Use wimax_gnl_re_state_change_send() to send the returned skb.
+ *
+ * Returns: skb with the genl message if ok, IS_ERR() ptr on error
+ *     with an errno code.
+ */
+static
+struct sk_buff *wimax_gnl_re_state_change_alloc(
+	struct wimax_dev *wimax_dev,
+	enum wimax_st new_state, enum wimax_st old_state,
+	void **header)
+{
+	int result;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	void *data;
+	struct sk_buff *report_skb;
+
+	d_fnstart(3, dev, "(wimax_dev %p new_state %u old_state %u)\n",
+		  wimax_dev, new_state, old_state);
+	result = -ENOMEM;
+	report_skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (report_skb == NULL) {
+		dev_err(dev, "RE_STCH: can't create message\n");
+		goto error_new;
+	}
+	data = genlmsg_put(report_skb, 0, wimax_gnl_mcg.id, &wimax_gnl_family,
+			   0, WIMAX_GNL_RE_STATE_CHANGE);
+	if (data == NULL) {
+		dev_err(dev, "RE_STCH: can't put data into message\n");
+		goto error_put;
+	}
+	*header = data;
+
+	result = nla_put_u8(report_skb, WIMAX_GNL_STCH_STATE_OLD, old_state);
+	if (result < 0) {
+		dev_err(dev, "RE_STCH: Error adding OLD attr: %d\n", result);
+		goto error_put;
+	}
+	result = nla_put_u8(report_skb, WIMAX_GNL_STCH_STATE_NEW, new_state);
+	if (result < 0) {
+		dev_err(dev, "RE_STCH: Error adding NEW attr: %d\n", result);
+		goto error_put;
+	}
+	result = nla_put_u32(report_skb, WIMAX_GNL_STCH_IFIDX,
+			     wimax_dev->net_dev->ifindex);
+	if (result < 0) {
+		dev_err(dev, "RE_STCH: Error adding IFINDEX attribute\n");
+		goto error_put;
+	}
+	d_fnend(3, dev, "(wimax_dev %p new_state %u old_state %u) = %p\n",
+		wimax_dev, new_state, old_state, report_skb);
+	return report_skb;
+
+error_put:
+	nlmsg_free(report_skb);
+error_new:
+	d_fnend(3, dev, "(wimax_dev %p new_state %u old_state %u) = %d\n",
+		wimax_dev, new_state, old_state, result);
+	return ERR_PTR(result);
+}
+
+
+/*
+ * Send a Report State Change message (as created with _alloc).
+ *
+ * @report_skb: as returned by wimax_gnl_re_state_change_alloc()
+ * @header: as returned by wimax_gnl_re_state_change_alloc()
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ *
+ * If the message is  NULL, pretend it didn't happen.
+ */
+static
+int wimax_gnl_re_state_change_send(
+	struct wimax_dev *wimax_dev, struct sk_buff *report_skb,
+	void *header)
+{
+	int result = 0;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	d_fnstart(3, dev, "(wimax_dev %p report_skb %p)\n",
+		  wimax_dev, report_skb);
+	if (report_skb == NULL)
+		goto out;
+	genlmsg_end(report_skb, header);
+	result = genlmsg_multicast(report_skb, 0, wimax_gnl_mcg.id, GFP_KERNEL);
+	if (result == -ESRCH)	/* Nobody connected, ignore it */
+		result = 0;	/* btw, the skb is freed already */
+	if (result < 0) {
+		dev_err(dev, "RE_STCH: Error sending: %d\n", result);
+		nlmsg_free(report_skb);
+	}
+out:
+	d_fnend(3, dev, "(wimax_dev %p report_skb %p) = %d\n",
+		wimax_dev, report_skb, result);
+	return result;
+}
+
+
+static
+void __check_new_state(enum wimax_st old_state, enum wimax_st new_state,
+		       unsigned allowed_states_bm)
+{
+	if (WARN_ON(((1 << new_state) & allowed_states_bm) == 0)) {
+		printk(KERN_ERR "SW BUG! Forbidden state change %u -> %u\n",
+			old_state, new_state);
+	}
+}
+
+
+/*
+ * Set the current state of a WiMAX device [unlocking version of
+ * wimax_state_change().
+ */
+void __wimax_state_change(struct wimax_dev *wimax_dev, enum wimax_st new_state)
+{
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	enum wimax_st old_state = wimax_dev->state;
+	struct sk_buff *stch_skb;
+	void *header;
+
+	d_fnstart(3, dev, "(wimax_dev %p new_state %u [old %u])\n",
+		  wimax_dev, new_state, old_state);
+
+	if (WARN_ON(new_state >= __WIMAX_ST_INVALID)) {
+		dev_err(dev, "SW BUG: requesting invalid state %u\n",
+			new_state);
+		goto out;
+	}
+	if (old_state == new_state)
+		goto out;
+	header = NULL;	/* gcc complains? can't grok why */
+	stch_skb = wimax_gnl_re_state_change_alloc(
+		wimax_dev, new_state, old_state, &header);
+
+	/* Verify the state transition and do exit-from-state actions */
+	switch (old_state) {
+	case __WIMAX_ST_NULL:
+		__check_new_state(old_state, new_state,
+				  1 << WIMAX_ST_DOWN);
+		break;
+	case WIMAX_ST_DOWN:
+		__check_new_state(old_state, new_state,
+				  1 << __WIMAX_ST_QUIESCING
+				  | 1 << WIMAX_ST_UNINITIALIZED
+				  | 1 << WIMAX_ST_RADIO_OFF);
+		break;
+	case __WIMAX_ST_QUIESCING:
+		__check_new_state(old_state, new_state, 1 << WIMAX_ST_DOWN);
+		break;
+	case WIMAX_ST_UNINITIALIZED:
+		__check_new_state(old_state, new_state,
+				  1 << __WIMAX_ST_QUIESCING
+				  | 1 << WIMAX_ST_RADIO_OFF);
+		break;
+	case WIMAX_ST_RADIO_OFF:
+		__check_new_state(old_state, new_state,
+				  1 << __WIMAX_ST_QUIESCING
+				  | 1 << WIMAX_ST_READY);
+		break;
+	case WIMAX_ST_READY:
+		__check_new_state(old_state, new_state,
+				  1 << __WIMAX_ST_QUIESCING
+				  | 1 << WIMAX_ST_RADIO_OFF
+				  | 1 << WIMAX_ST_SCANNING
+				  | 1 << WIMAX_ST_CONNECTING
+				  | 1 << WIMAX_ST_CONNECTED);
+		break;
+	case WIMAX_ST_SCANNING:
+		__check_new_state(old_state, new_state,
+				  1 << __WIMAX_ST_QUIESCING
+				  | 1 << WIMAX_ST_RADIO_OFF
+				  | 1 << WIMAX_ST_READY
+				  | 1 << WIMAX_ST_CONNECTING
+				  | 1 << WIMAX_ST_CONNECTED);
+		break;
+	case WIMAX_ST_CONNECTING:
+		__check_new_state(old_state, new_state,
+				  1 << __WIMAX_ST_QUIESCING
+				  | 1 << WIMAX_ST_RADIO_OFF
+				  | 1 << WIMAX_ST_READY
+				  | 1 << WIMAX_ST_SCANNING
+				  | 1 << WIMAX_ST_CONNECTED);
+		break;
+	case WIMAX_ST_CONNECTED:
+		__check_new_state(old_state, new_state,
+				  1 << __WIMAX_ST_QUIESCING
+				  | 1 << WIMAX_ST_RADIO_OFF
+				  | 1 << WIMAX_ST_READY);
+		netif_tx_disable(wimax_dev->net_dev);
+		netif_carrier_off(wimax_dev->net_dev);
+		break;
+	case __WIMAX_ST_INVALID:
+	default:
+		dev_err(dev, "SW BUG: wimax_dev %p is in unknown state %u\n",
+			wimax_dev, wimax_dev->state);
+		WARN_ON(1);
+		goto out;
+	}
+
+	/* Execute the actions of entry to the new state */
+	switch (new_state) {
+	case __WIMAX_ST_NULL:
+		dev_err(dev, "SW BUG: wimax_dev %p entering NULL state "
+			"from %u\n", wimax_dev, wimax_dev->state);
+		WARN_ON(1);		/* Nobody can enter this state */
+		break;
+	case WIMAX_ST_DOWN:
+		break;
+	case __WIMAX_ST_QUIESCING:
+		break;
+	case WIMAX_ST_UNINITIALIZED:
+		break;
+	case WIMAX_ST_RADIO_OFF:
+		break;
+	case WIMAX_ST_READY:
+		break;
+	case WIMAX_ST_SCANNING:
+		break;
+	case WIMAX_ST_CONNECTING:
+		break;
+	case WIMAX_ST_CONNECTED:
+		netif_carrier_on(wimax_dev->net_dev);
+		netif_wake_queue(wimax_dev->net_dev);
+		break;
+	case __WIMAX_ST_INVALID:
+	default:
+		BUG();
+	}
+	__wimax_state_set(wimax_dev, new_state);
+	if (stch_skb)
+		wimax_gnl_re_state_change_send(wimax_dev, stch_skb, header);
+out:
+	d_fnend(3, dev, "(wimax_dev %p new_state %u [old %u]) = void\n",
+		wimax_dev, new_state, old_state);
+	return;
+}
+
+
+/**
+ * wimax_state_change - Set the current state of a WiMAX device
+ *
+ * @wimax_dev: WiMAX device descriptor (properly referenced)
+ * @new_state: New state to switch to
+ *
+ * This implements the state changes for the wimax devices. It will
+ *
+ * - verify that the state transition is legal (for now it'll just
+ *   print a warning if not) according to the table in
+ *   linux/wimax.h's documentation for 'enum wimax_st'.
+ *
+ * - perform the actions needed for leaving the current state and
+ *   whichever are needed for entering the new state.
+ *
+ * - issue a report to user space indicating the new state (and an
+ *   optional payload with information about the new state).
+ *
+ * NOTE: @wimax_dev must be locked
+ */
+void wimax_state_change(struct wimax_dev *wimax_dev, enum wimax_st new_state)
+{
+	mutex_lock(&wimax_dev->mutex);
+	__wimax_state_change(wimax_dev, new_state);
+	mutex_unlock(&wimax_dev->mutex);
+	return;
+}
+EXPORT_SYMBOL_GPL(wimax_state_change);
+
+
+/**
+ * wimax_state_get() - Return the current state of a WiMAX device
+ *
+ * @wimax_dev: WiMAX device descriptor
+ *
+ * Returns: Current state of the device according to its driver.
+ */
+enum wimax_st wimax_state_get(struct wimax_dev *wimax_dev)
+{
+	enum wimax_st state;
+	mutex_lock(&wimax_dev->mutex);
+	state = wimax_dev->state;
+	mutex_unlock(&wimax_dev->mutex);
+	return state;
+}
+EXPORT_SYMBOL_GPL(wimax_state_get);
+
+
+/**
+ * wimax_dev_init - initialize a newly allocated instance
+ *
+ * @wimax_dev: WiMAX device descriptor to initialize.
+ *
+ * Initializes fields of a freshly allocated @wimax_dev instance. This
+ * function assumes that after allocation, the memory occupied by
+ * @wimax_dev was zeroed.
+ */
+void wimax_dev_init(struct wimax_dev *wimax_dev)
+{
+	INIT_LIST_HEAD(&wimax_dev->id_table_node);
+	__wimax_state_set(wimax_dev, WIMAX_ST_UNINITIALIZED);
+	mutex_init(&wimax_dev->mutex);
+	mutex_init(&wimax_dev->mutex_reset);
+}
+EXPORT_SYMBOL_GPL(wimax_dev_init);
+
+/*
+ * This extern is declared here because it's easier to keep track --
+ * both declarations are a list of the same
+ */
+extern struct genl_ops
+	wimax_gnl_msg_from_user,
+	wimax_gnl_reset,
+	wimax_gnl_rfkill;
+
+static
+struct genl_ops *wimax_gnl_ops[] = {
+	&wimax_gnl_msg_from_user,
+	&wimax_gnl_reset,
+	&wimax_gnl_rfkill,
+};
+
+
+static
+size_t wimax_addr_scnprint(char *addr_str, size_t addr_str_size,
+			   unsigned char *addr, size_t addr_len)
+{
+	unsigned cnt, total;
+	for (total = cnt = 0; cnt < addr_len; cnt++)
+		total += scnprintf(addr_str + total, addr_str_size - total,
+				   "%02x%c", addr[cnt],
+				   cnt == addr_len - 1 ? '\0' : ':');
+	return total;
+}
+
+
+/**
+ * wimax_dev_add - Register a new WiMAX device
+ *
+ * @wimax_dev: WiMAX device descriptor (as embedded in your @net_dev's
+ *     priv data). You must have called wimax_dev_init() on it before.
+ *
+ * @net_dev: net device the @wimax_dev is associated with. The
+ *     function expects SET_NETDEV_DEV() and register_netdev() were
+ *     already called on it.
+ *
+ * Registers the new WiMAX device, sets up the user-kernel control
+ * interface (generic netlink) and common WiMAX infrastructure.
+ *
+ * Note that the parts that will allow interaction with user space are
+ * setup at the very end, when the rest is in place, as once that
+ * happens, the driver might get user space control requests via
+ * netlink or from debugfs that might translate into calls into
+ * wimax_dev->op_*().
+ */
+int wimax_dev_add(struct wimax_dev *wimax_dev, struct net_device *net_dev)
+{
+	int result;
+	struct device *dev = net_dev->dev.parent;
+	char addr_str[32];
+
+	d_fnstart(3, dev, "(wimax_dev %p net_dev %p)\n", wimax_dev, net_dev);
+
+	/* Do the RFKILL setup before locking, as RFKILL will call
+	 * into our functions. */
+	wimax_dev->net_dev = net_dev;
+	result = wimax_rfkill_add(wimax_dev);
+	if (result < 0)
+		goto error_rfkill_add;
+
+	/* Set up user-space interaction */
+	mutex_lock(&wimax_dev->mutex);
+	wimax_id_table_add(wimax_dev);
+	result = wimax_debugfs_add(wimax_dev);
+	if (result < 0) {
+		dev_err(dev, "cannot initialize debugfs: %d\n",
+			result);
+		goto error_debugfs_add;
+	}
+
+	__wimax_state_set(wimax_dev, WIMAX_ST_DOWN);
+	mutex_unlock(&wimax_dev->mutex);
+
+	wimax_addr_scnprint(addr_str, sizeof(addr_str),
+			    net_dev->dev_addr, net_dev->addr_len);
+	dev_err(dev, "WiMAX interface %s (%s) ready\n",
+		net_dev->name, addr_str);
+	d_fnend(3, dev, "(wimax_dev %p net_dev %p) = 0\n", wimax_dev, net_dev);
+	return 0;
+
+error_debugfs_add:
+	wimax_id_table_rm(wimax_dev);
+	mutex_unlock(&wimax_dev->mutex);
+	wimax_rfkill_rm(wimax_dev);
+error_rfkill_add:
+	d_fnend(3, dev, "(wimax_dev %p net_dev %p) = %d\n",
+		wimax_dev, net_dev, result);
+	return result;
+}
+EXPORT_SYMBOL_GPL(wimax_dev_add);
+
+
+/**
+ * wimax_dev_rm - Unregister an existing WiMAX device
+ *
+ * @wimax_dev: WiMAX device descriptor
+ *
+ * Unregisters a WiMAX device previously registered for use with
+ * wimax_add_rm().
+ *
+ * IMPORTANT! Must call before calling unregister_netdev().
+ *
+ * After this function returns, you will not get any more user space
+ * control requests (via netlink or debugfs) and thus to wimax_dev->ops.
+ *
+ * Reentrancy control is ensured by setting the state to
+ * %__WIMAX_ST_QUIESCING. rfkill operations coming through
+ * wimax_*rfkill*() will be stopped by the quiescing state; ops coming
+ * from the rfkill subsystem will be stopped by the support being
+ * removed by wimax_rfkill_rm().
+ */
+void wimax_dev_rm(struct wimax_dev *wimax_dev)
+{
+	d_fnstart(3, NULL, "(wimax_dev %p)\n", wimax_dev);
+
+	mutex_lock(&wimax_dev->mutex);
+	__wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING);
+	wimax_debugfs_rm(wimax_dev);
+	wimax_id_table_rm(wimax_dev);
+	__wimax_state_change(wimax_dev, WIMAX_ST_DOWN);
+	mutex_unlock(&wimax_dev->mutex);
+	wimax_rfkill_rm(wimax_dev);
+	d_fnend(3, NULL, "(wimax_dev %p) = void\n", wimax_dev);
+}
+EXPORT_SYMBOL_GPL(wimax_dev_rm);
+
+struct genl_family wimax_gnl_family = {
+	.id = GENL_ID_GENERATE,
+	.name = "WiMAX",
+	.version = WIMAX_GNL_VERSION,
+	.hdrsize = 0,
+	.maxattr = WIMAX_GNL_ATTR_MAX,
+};
+
+struct genl_multicast_group wimax_gnl_mcg = {
+	.name = "msg",
+};
+
+
+
+/* Shutdown the wimax stack */
+static
+int __init wimax_subsys_init(void)
+{
+	int result, cnt;
+
+	d_fnstart(4, NULL, "()\n");
+	snprintf(wimax_gnl_family.name, sizeof(wimax_gnl_family.name),
+		 "WiMAX");
+	result = genl_register_family(&wimax_gnl_family);
+	if (unlikely(result < 0)) {
+		printk(KERN_ERR "cannot register generic netlink family: %d\n",
+		       result);
+		goto error_register_family;
+	}
+
+	for (cnt = 0; cnt < ARRAY_SIZE(wimax_gnl_ops); cnt++) {
+		result = genl_register_ops(&wimax_gnl_family,
+					   wimax_gnl_ops[cnt]);
+		d_printf(4, NULL, "registering generic netlink op code "
+			 "%u: %d\n", wimax_gnl_ops[cnt]->cmd, result);
+		if (unlikely(result < 0)) {
+			printk(KERN_ERR "cannot register generic netlink op "
+			       "code %u: %d\n",
+			       wimax_gnl_ops[cnt]->cmd, result);
+			goto error_register_ops;
+		}
+	}
+
+	result = genl_register_mc_group(&wimax_gnl_family, &wimax_gnl_mcg);
+	if (result < 0)
+		goto error_mc_group;
+	d_fnend(4, NULL, "() = 0\n");
+	return 0;
+
+error_mc_group:
+error_register_ops:
+	for (cnt--; cnt >= 0; cnt--)
+		genl_unregister_ops(&wimax_gnl_family,
+				    wimax_gnl_ops[cnt]);
+	genl_unregister_family(&wimax_gnl_family);
+error_register_family:
+	d_fnend(4, NULL, "() = %d\n", result);
+	return result;
+
+}
+module_init(wimax_subsys_init);
+
+
+/* Shutdown the wimax stack */
+static
+void __exit wimax_subsys_exit(void)
+{
+	int cnt;
+	wimax_id_table_release();
+	genl_unregister_mc_group(&wimax_gnl_family, &wimax_gnl_mcg);
+	for (cnt = ARRAY_SIZE(wimax_gnl_ops) - 1; cnt >= 0; cnt--)
+		genl_unregister_ops(&wimax_gnl_family,
+				    wimax_gnl_ops[cnt]);
+	genl_unregister_family(&wimax_gnl_family);
+}
+module_exit(wimax_subsys_exit);
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Linux WiMAX stack");
+MODULE_LICENSE("GPL");
+
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 06/29] genetlink: export genl_unregister_mc_group()
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (4 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 05/29] wimax: generic device management (registration, deregistration, lookup) Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 07/29] wimax: basic API: kernel/user messaging, rfkill and reset Inaky Perez-Gonzalez
                   ` (23 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Add an EXPORT_SYMBOL() to genl_unregister_mc_group(), to allow
unregistering groups on the run. EXPORT_SYMBOL_GPL() is not used as
the rest of the functions exported by this module (eg:
genl_register_mc_group) are also not _GPL().

Cleanup is currently done when unregistering a family, but there is
no way to unregister a single multicast group due to that function not
being exported. Seems to be a mistake as it is documented as for
external consumption.

This is needed by the WiMAX stack to be able to cleanup unused mc
groups.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
Acked-by: Johannes Berg <johannes@sipsolutions.net>
---
 net/netlink/genetlink.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c
index 3e1191c..1d3dd30 100644
--- a/net/netlink/genetlink.c
+++ b/net/netlink/genetlink.c
@@ -225,6 +225,7 @@ void genl_unregister_mc_group(struct genl_family *family,
 	__genl_unregister_mc_group(family, grp);
 	genl_unlock();
 }
+EXPORT_SYMBOL(genl_unregister_mc_group);
 
 static void genl_unregister_mc_groups(struct genl_family *family)
 {
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 07/29] wimax: basic API: kernel/user messaging, rfkill and reset
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (5 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 06/29] genetlink: export genl_unregister_mc_group() Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 08/29] debugfs: add helpers for exporting a size_t simple value Inaky Perez-Gonzalez
                   ` (22 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements the three basic operations provided by the stack's control
interface to WiMAX devices:

- Messaging channel between user space and driver/device

  This implements a direct communication channel between user space
  and the driver/device, by which free form messages can be sent back
  and forth.

  This is intended for device-specific features, vendor quirks, etc.

- RF-kill framework integration

  Provide most of the RF-Kill integration for WiMAX drivers so that
  all device drivers have to do is after wimax_dev_add() is call
  wimax_report_rfkill_{hw,sw}() to update initial state and then every
  time it changes.

  Provides wimax_rfkill() for the kernel to call to set software
  RF-Kill status and/or query current hardware and software switch
  status.

  Exports wimax_rfkill() over generic netlink to user space.

- Reset a WiMAX device

  Provides wimax_reset() for the kernel to reset a wimax device as
  needed and exports it over generic netlink to user space.

This API is clearly limited, as it still provides no way to do the
basic scan, connect and disconnect in a hardware independent way.  The
WiMAX case is more complex than WiFi due to the way networks are
discovered and provisioned.

The next developments are to add the basic operations so they can be
offerent by different drivers. However, we'd like to get more vendors
to jump in and provide feedback of how the user/kernel API/abstraction
layer should be.

The user space code for the i2400m, as of now, uses the messaging
channel, but that will change as the API evolves.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 net/wimax/op-msg.c    |  421 ++++++++++++++++++++++++++++++++++++++
 net/wimax/op-reset.c  |  143 +++++++++++++
 net/wimax/op-rfkill.c |  532 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1096 insertions(+), 0 deletions(-)
 create mode 100644 net/wimax/op-msg.c
 create mode 100644 net/wimax/op-reset.c
 create mode 100644 net/wimax/op-rfkill.c

diff --git a/net/wimax/op-msg.c b/net/wimax/op-msg.c
new file mode 100644
index 0000000..cb3b4ad
--- /dev/null
+++ b/net/wimax/op-msg.c
@@ -0,0 +1,421 @@
+/*
+ * Linux WiMAX
+ * Generic messaging interface between userspace and driver/device
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 implements a direct communication channel between user space and
+ * the driver/device, by which free form messages can be sent back and
+ * forth.
+ *
+ * This is intended for device-specific features, vendor quirks, etc.
+ *
+ * See include/net/wimax.h
+ *
+ * GENERIC NETLINK ENCODING AND CAPACITY
+ *
+ * A destination "pipe name" is added to each message; it is up to the
+ * drivers to assign or use those names (if using them at all).
+ *
+ * Messages are encoded as a binary netlink attribute using nla_put()
+ * using type NLA_UNSPEC (as some versions of libnl still in
+ * deployment don't yet understand NLA_BINARY).
+ *
+ * The maximum capacity of this transport is PAGESIZE per message (so
+ * the actual payload will be bit smaller depending on the
+ * netlink/generic netlink attributes and headers).
+ *
+ * RECEPTION OF MESSAGES
+ *
+ * When a message is received from user space, it is passed verbatim
+ * to the driver calling wimax_dev->op_msg_from_user(). The return
+ * value from this function is passed back to user space as an ack
+ * over the generic netlink protocol.
+ *
+ * The stack doesn't do any processing or interpretation of these
+ * messages.
+ *
+ * SENDING MESSAGES
+ *
+ * Messages can be sent with wimax_msg().
+ *
+ * If the message delivery needs to happen on a different context to
+ * that of its creation, wimax_msg_alloc() can be used to get a
+ * pointer to the message that can be delivered later on with
+ * wimax_msg_send().
+ *
+ * ROADMAP
+ *
+ * wimax_gnl_doit_msg_from_user()    Process a message from user space
+ *   wimax_dev_get_by_genl_info()
+ *   wimax_dev->op_msg_from_user()   Delivery of message to the driver
+ *
+ * wimax_msg()                       Send a message to user space
+ *   wimax_msg_alloc()
+ *   wimax_msg_send()
+ */
+#include <linux/device.h>
+#include <net/genetlink.h>
+#include <linux/netdevice.h>
+#include <linux/wimax.h>
+#include <linux/security.h>
+#include "wimax-internal.h"
+
+
+#define D_SUBMODULE op_msg
+#include "debug-levels.h"
+
+
+/**
+ * wimax_msg_alloc - Create a new skb for sending a message to userspace
+ *
+ * @wimax_dev: WiMAX device descriptor
+ * @pipe_name: "named pipe" the message will be sent to
+ * @msg: pointer to the message data to send
+ * @size: size of the message to send (in bytes), including the header.
+ * @gfp_flags: flags for memory allocation.
+ *
+ * Returns: %0 if ok, negative errno code on error
+ *
+ * Description:
+ *
+ * Allocates an skb that will contain the message to send to user
+ * space over the messaging pipe and initializes it, copying the
+ * payload.
+ *
+ * Once this call is done, you can deliver it with
+ * wimax_msg_send().
+ *
+ * IMPORTANT:
+ *
+ * Don't use skb_push()/skb_pull()/skb_reserve() on the skb, as
+ * wimax_msg_send() depends on skb->data being placed at the
+ * beginning of the user message.
+ */
+struct sk_buff *wimax_msg_alloc(struct wimax_dev *wimax_dev,
+				const char *pipe_name,
+				const void *msg, size_t size,
+				gfp_t gfp_flags)
+{
+	int result;
+	struct device *dev = wimax_dev->net_dev->dev.parent;
+	size_t msg_size;
+	void *genl_msg;
+	struct sk_buff *skb;
+
+	msg_size = nla_total_size(size)
+		+ nla_total_size(sizeof(u32))
+		+ (pipe_name ? nla_total_size(strlen(pipe_name)) : 0);
+	result = -ENOMEM;
+	skb = genlmsg_new(msg_size, gfp_flags);
+	if (skb == NULL)
+		goto error_new;
+	genl_msg = genlmsg_put(skb, 0, 0, &wimax_gnl_family,
+			       0, WIMAX_GNL_OP_MSG_TO_USER);
+	if (genl_msg == NULL) {
+		dev_err(dev, "no memory to create generic netlink message\n");
+		goto error_genlmsg_put;
+	}
+	result = nla_put_u32(skb, WIMAX_GNL_MSG_IFIDX,
+			     wimax_dev->net_dev->ifindex);
+	if (result < 0) {
+		dev_err(dev, "no memory to add ifindex attribute\n");
+		goto error_nla_put;
+	}
+	if (pipe_name) {
+		result = nla_put_string(skb, WIMAX_GNL_MSG_PIPE_NAME,
+					pipe_name);
+		if (result < 0) {
+			dev_err(dev, "no memory to add pipe_name attribute\n");
+			goto error_nla_put;
+		}
+	}
+	result = nla_put(skb, WIMAX_GNL_MSG_DATA, size, msg);
+	if (result < 0) {
+		dev_err(dev, "no memory to add payload in attribute\n");
+		goto error_nla_put;
+	}
+	genlmsg_end(skb, genl_msg);
+	return skb;
+
+error_nla_put:
+error_genlmsg_put:
+error_new:
+	nlmsg_free(skb);
+	return ERR_PTR(result);
+
+}
+EXPORT_SYMBOL_GPL(wimax_msg_alloc);
+
+
+/**
+ * wimax_msg_data_len - Return a pointer and size of a message's payload
+ *
+ * @msg: Pointer to a message created with wimax_msg_alloc()
+ * @size: Pointer to where to store the message's size
+ *
+ * Returns the pointer to the message data.
+ */
+const void *wimax_msg_data_len(struct sk_buff *msg, size_t *size)
+{
+	struct nlmsghdr *nlh = (void *) msg->head;
+	struct nlattr *nla;
+
+	nla = nlmsg_find_attr(nlh, sizeof(struct genlmsghdr),
+			      WIMAX_GNL_MSG_DATA);
+	if (nla == NULL) {
+		printk(KERN_ERR "Cannot find attribute WIMAX_GNL_MSG_DATA\n");
+		return NULL;
+	}
+	*size = nla_len(nla);
+	return nla_data(nla);
+}
+EXPORT_SYMBOL_GPL(wimax_msg_data_len);
+
+
+/**
+ * wimax_msg_data - Return a pointer to a message's payload
+ *
+ * @msg: Pointer to a message created with wimax_msg_alloc()
+ */
+const void *wimax_msg_data(struct sk_buff *msg)
+{
+	struct nlmsghdr *nlh = (void *) msg->head;
+	struct nlattr *nla;
+
+	nla = nlmsg_find_attr(nlh, sizeof(struct genlmsghdr),
+			      WIMAX_GNL_MSG_DATA);
+	if (nla == NULL) {
+		printk(KERN_ERR "Cannot find attribute WIMAX_GNL_MSG_DATA\n");
+		return NULL;
+	}
+	return nla_data(nla);
+}
+EXPORT_SYMBOL_GPL(wimax_msg_data);
+
+
+/**
+ * wimax_msg_len - Return a message's payload length
+ *
+ * @msg: Pointer to a message created with wimax_msg_alloc()
+ */
+ssize_t wimax_msg_len(struct sk_buff *msg)
+{
+	struct nlmsghdr *nlh = (void *) msg->head;
+	struct nlattr *nla;
+
+	nla = nlmsg_find_attr(nlh, sizeof(struct genlmsghdr),
+			      WIMAX_GNL_MSG_DATA);
+	if (nla == NULL) {
+		printk(KERN_ERR "Cannot find attribute WIMAX_GNL_MSG_DATA\n");
+		return -EINVAL;
+	}
+	return nla_len(nla);
+}
+EXPORT_SYMBOL_GPL(wimax_msg_len);
+
+
+/**
+ * wimax_msg_send - Send a pre-allocated message to user space
+ *
+ * @wimax_dev: WiMAX device descriptor
+ *
+ * @skb: &struct sk_buff returned by wimax_msg_alloc(). Note the
+ *     ownership of @skb is transferred to this function.
+ *
+ * Returns: 0 if ok, < 0 errno code on error
+ *
+ * Description:
+ *
+ * Sends a free-form message that was preallocated with
+ * wimax_msg_alloc() and filled up.
+ *
+ * Assumes that once you pass an skb to this function for sending, it
+ * owns it and will release it when done (on success).
+ *
+ * IMPORTANT:
+ *
+ * Don't use skb_push()/skb_pull()/skb_reserve() on the skb, as
+ * wimax_msg_send() depends on skb->data being placed at the
+ * beginning of the user message.
+ */
+int wimax_msg_send(struct wimax_dev *wimax_dev, struct sk_buff *skb)
+{
+	int result;
+	struct device *dev = wimax_dev->net_dev->dev.parent;
+	void *msg = skb->data;
+	size_t size = skb->len;
+	might_sleep();
+
+	d_printf(1, dev, "CTX: wimax msg, %zu bytes\n", size);
+	d_dump(2, dev, msg, size);
+	result = genlmsg_multicast(skb, 0, wimax_gnl_mcg.id, GFP_KERNEL);
+	d_printf(1, dev, "CTX: genl multicast result %d\n", result);
+	if (result == -ESRCH)	/* Nobody connected, ignore it */
+		result = 0;	/* btw, the skb is freed already */
+	return result;
+}
+EXPORT_SYMBOL_GPL(wimax_msg_send);
+
+
+/**
+ * wimax_msg - Send a message to user space
+ *
+ * @wimax_dev: WiMAX device descriptor (properly referenced)
+ * @pipe_name: "named pipe" the message will be sent to
+ * @buf: pointer to the message to send.
+ * @size: size of the buffer pointed to by @buf (in bytes).
+ * @gfp_flags: flags for memory allocation.
+ *
+ * Returns: %0 if ok, negative errno code on error.
+ *
+ * Description:
+ *
+ * Sends a free-form message to user space on the device @wimax_dev.
+ *
+ * NOTES:
+ *
+ * Once the @skb is given to this function, who will own it and will
+ * release it when done (unless it returns error).
+ */
+int wimax_msg(struct wimax_dev *wimax_dev, const char *pipe_name,
+	      const void *buf, size_t size, gfp_t gfp_flags)
+{
+	int result = -ENOMEM;
+	struct sk_buff *skb;
+
+	skb = wimax_msg_alloc(wimax_dev, pipe_name, buf, size, gfp_flags);
+	if (skb == NULL)
+		goto error_msg_new;
+	result = wimax_msg_send(wimax_dev, skb);
+error_msg_new:
+	return result;
+}
+EXPORT_SYMBOL_GPL(wimax_msg);
+
+
+static const
+struct nla_policy wimax_gnl_msg_policy[WIMAX_GNL_ATTR_MAX + 1] = {
+	[WIMAX_GNL_MSG_IFIDX] = {
+		.type = NLA_U32,
+	},
+	[WIMAX_GNL_MSG_DATA] = {
+		.type = NLA_UNSPEC,	/* libnl doesn't grok BINARY yet */
+	},
+};
+
+
+/*
+ * Relays a message from user space to the driver
+ *
+ * The skb is passed to the driver-specific function with the netlink
+ * and generic netlink headers already stripped.
+ *
+ * This call will block while handling/relaying the message.
+ */
+static
+int wimax_gnl_doit_msg_from_user(struct sk_buff *skb, struct genl_info *info)
+{
+	int result, ifindex;
+	struct wimax_dev *wimax_dev;
+	struct device *dev;
+	struct nlmsghdr *nlh = info->nlhdr;
+	char *pipe_name;
+	void *msg_buf;
+	size_t msg_len;
+
+	might_sleep();
+	d_fnstart(3, NULL, "(skb %p info %p)\n", skb, info);
+	result = -ENODEV;
+	if (info->attrs[WIMAX_GNL_MSG_IFIDX] == NULL) {
+		printk(KERN_ERR "WIMAX_GNL_MSG_FROM_USER: can't find IFIDX "
+		       "attribute\n");
+		goto error_no_wimax_dev;
+	}
+	ifindex = nla_get_u32(info->attrs[WIMAX_GNL_MSG_IFIDX]);
+	wimax_dev = wimax_dev_get_by_genl_info(info, ifindex);
+	if (wimax_dev == NULL)
+		goto error_no_wimax_dev;
+	dev = wimax_dev_to_dev(wimax_dev);
+
+	/* Unpack arguments */
+	result = -EINVAL;
+	if (info->attrs[WIMAX_GNL_MSG_DATA] == NULL) {
+		dev_err(dev, "WIMAX_GNL_MSG_FROM_USER: can't find MSG_DATA "
+			"attribute\n");
+		goto error_no_data;
+	}
+	msg_buf = nla_data(info->attrs[WIMAX_GNL_MSG_DATA]);
+	msg_len = nla_len(info->attrs[WIMAX_GNL_MSG_DATA]);
+
+	if (info->attrs[WIMAX_GNL_MSG_PIPE_NAME] == NULL)
+		pipe_name = NULL;
+	else {
+		struct nlattr *attr = info->attrs[WIMAX_GNL_MSG_PIPE_NAME];
+		size_t attr_len = nla_len(attr);
+		/* libnl-1.1 does not yet support NLA_NUL_STRING */
+		result = -ENOMEM;
+		pipe_name = kstrndup(nla_data(attr), attr_len + 1, GFP_KERNEL);
+		if (pipe_name == NULL)
+			goto error_alloc;
+		pipe_name[attr_len] = 0;
+	}
+	mutex_lock(&wimax_dev->mutex);
+	result = wimax_dev_is_ready(wimax_dev);
+	if (result < 0)
+		goto error_not_ready;
+	result = -ENOSYS;
+	if (wimax_dev->op_msg_from_user == NULL)
+		goto error_noop;
+
+	d_printf(1, dev,
+		 "CRX: nlmsghdr len %u type %u flags 0x%04x seq 0x%x pid %u\n",
+		 nlh->nlmsg_len, nlh->nlmsg_type, nlh->nlmsg_flags,
+		 nlh->nlmsg_seq, nlh->nlmsg_pid);
+	d_printf(1, dev, "CRX: wimax message %zu bytes\n", msg_len);
+	d_dump(2, dev, msg_buf, msg_len);
+
+	result = wimax_dev->op_msg_from_user(wimax_dev, pipe_name,
+					     msg_buf, msg_len, info);
+error_noop:
+error_not_ready:
+	mutex_unlock(&wimax_dev->mutex);
+error_alloc:
+	kfree(pipe_name);
+error_no_data:
+	dev_put(wimax_dev->net_dev);
+error_no_wimax_dev:
+	d_fnend(3, NULL, "(skb %p info %p) = %d\n", skb, info, result);
+	return result;
+}
+
+
+/*
+ * Generic Netlink glue
+ */
+
+struct genl_ops wimax_gnl_msg_from_user = {
+	.cmd = WIMAX_GNL_OP_MSG_FROM_USER,
+	.flags = GENL_ADMIN_PERM,
+	.policy = wimax_gnl_msg_policy,
+	.doit = wimax_gnl_doit_msg_from_user,
+	.dumpit = NULL,
+};
+
diff --git a/net/wimax/op-reset.c b/net/wimax/op-reset.c
new file mode 100644
index 0000000..ca26917
--- /dev/null
+++ b/net/wimax/op-reset.c
@@ -0,0 +1,143 @@
+/*
+ * Linux WiMAX
+ * Implement and export a method for resetting a WiMAX device
+ *
+ *
+ * Copyright (C) 2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 implements a simple synchronous call to reset a WiMAX device.
+ *
+ * Resets aim at being warm, keeping the device handles active;
+ * however, when that fails, it falls back to a cold reset (that will
+ * disconnect and reconnect the device).
+ */
+
+#include <net/wimax.h>
+#include <net/genetlink.h>
+#include <linux/wimax.h>
+#include <linux/security.h>
+#include "wimax-internal.h"
+
+#define D_SUBMODULE op_reset
+#include "debug-levels.h"
+
+
+/**
+ * wimax_reset - Reset a WiMAX device
+ *
+ * @wimax_dev: WiMAX device descriptor
+ *
+ * Returns:
+ *
+ * %0 if ok and a warm reset was done (the device still exists in
+ * the system).
+ *
+ * -%ENODEV if a cold/bus reset had to be done (device has
+ * disconnected and reconnected, so current handle is not valid
+ * any more).
+ *
+ * -%EINVAL if the device is not even registered.
+ *
+ * Any other negative error code shall be considered as
+ * non-recoverable.
+ *
+ * Description:
+ *
+ * Called when wanting to reset the device for any reason. Device is
+ * taken back to power on status.
+ *
+ * This call blocks; on succesful return, the device has completed the
+ * reset process and is ready to operate.
+ */
+int wimax_reset(struct wimax_dev *wimax_dev)
+{
+	int result = -EINVAL;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	enum wimax_st state;
+
+	might_sleep();
+	d_fnstart(3, dev, "(wimax_dev %p)\n", wimax_dev);
+	mutex_lock(&wimax_dev->mutex);
+	dev_hold(wimax_dev->net_dev);
+	state = wimax_dev->state;
+	mutex_unlock(&wimax_dev->mutex);
+
+	if (state >= WIMAX_ST_DOWN) {
+		mutex_lock(&wimax_dev->mutex_reset);
+		result = wimax_dev->op_reset(wimax_dev);
+		mutex_unlock(&wimax_dev->mutex_reset);
+	}
+	dev_put(wimax_dev->net_dev);
+
+	d_fnend(3, dev, "(wimax_dev %p) = %d\n", wimax_dev, result);
+	return result;
+}
+EXPORT_SYMBOL(wimax_reset);
+
+
+static const
+struct nla_policy wimax_gnl_reset_policy[WIMAX_GNL_ATTR_MAX + 1] = {
+	[WIMAX_GNL_RESET_IFIDX] = {
+		.type = NLA_U32,
+	},
+};
+
+
+/*
+ * Exporting to user space over generic netlink
+ *
+ * Parse the reset command from user space, return error code.
+ *
+ * No attributes.
+ */
+static
+int wimax_gnl_doit_reset(struct sk_buff *skb, struct genl_info *info)
+{
+	int result, ifindex;
+	struct wimax_dev *wimax_dev;
+	struct device *dev;
+
+	d_fnstart(3, NULL, "(skb %p info %p)\n", skb, info);
+	result = -ENODEV;
+	if (info->attrs[WIMAX_GNL_RESET_IFIDX] == NULL) {
+		printk(KERN_ERR "WIMAX_GNL_OP_RFKILL: can't find IFIDX "
+			"attribute\n");
+		goto error_no_wimax_dev;
+	}
+	ifindex = nla_get_u32(info->attrs[WIMAX_GNL_RESET_IFIDX]);
+	wimax_dev = wimax_dev_get_by_genl_info(info, ifindex);
+	if (wimax_dev == NULL)
+		goto error_no_wimax_dev;
+	dev = wimax_dev_to_dev(wimax_dev);
+	/* Execute the operation and send the result back to user space */
+	result = wimax_reset(wimax_dev);
+	dev_put(wimax_dev->net_dev);
+error_no_wimax_dev:
+	d_fnend(3, NULL, "(skb %p info %p) = %d\n", skb, info, result);
+	return result;
+}
+
+
+struct genl_ops wimax_gnl_reset = {
+	.cmd = WIMAX_GNL_OP_RESET,
+	.flags = GENL_ADMIN_PERM,
+	.policy = wimax_gnl_reset_policy,
+	.doit = wimax_gnl_doit_reset,
+	.dumpit = NULL,
+};
diff --git a/net/wimax/op-rfkill.c b/net/wimax/op-rfkill.c
new file mode 100644
index 0000000..8745bac
--- /dev/null
+++ b/net/wimax/op-rfkill.c
@@ -0,0 +1,532 @@
+/*
+ * Linux WiMAX
+ * RF-kill framework integration
+ *
+ *
+ * Copyright (C) 2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 integrates into the Linux Kernel rfkill susbystem so that the
+ * drivers just have to do the bare minimal work, which is providing a
+ * method to set the software RF-Kill switch and to report changes in
+ * the software and hardware switch status.
+ *
+ * A non-polled generic rfkill device is embedded into the WiMAX
+ * subsystem's representation of a device.
+ *
+ * FIXME: Need polled support? use a timer or add the implementation
+ *     to the stack.
+ *
+ * All device drivers have to do is after wimax_dev_init(), call
+ * wimax_report_rfkill_hw() and wimax_report_rfkill_sw() to update
+ * initial state and then every time it changes. See wimax.h:struct
+ * wimax_dev for more information.
+ *
+ * ROADMAP
+ *
+ * wimax_gnl_doit_rfkill()      User space calling wimax_rfkill()
+ *   wimax_rfkill()             Kernel calling wimax_rfkill()
+ *     __wimax_rf_toggle_radio()
+ *
+ * wimax_rfkill_toggle_radio()  RF-Kill subsytem calling
+ *   __wimax_rf_toggle_radio()
+ *
+ * __wimax_rf_toggle_radio()
+ *   wimax_dev->op_rfkill_sw_toggle() Driver backend
+ *   __wimax_state_change()
+ *
+ * wimax_report_rfkill_sw()     Driver reports state change
+ *   __wimax_state_change()
+ *
+ * wimax_report_rfkill_hw()     Driver reports state change
+ *   __wimax_state_change()
+ *
+ * wimax_rfkill_add()           Initialize/shutdown rfkill support
+ * wimax_rfkill_rm()            [called by wimax_dev_add/rm()]
+ */
+
+#include <net/wimax.h>
+#include <net/genetlink.h>
+#include <linux/wimax.h>
+#include <linux/security.h>
+#include <linux/rfkill.h>
+#include <linux/input.h>
+#include "wimax-internal.h"
+
+#define D_SUBMODULE op_rfkill
+#include "debug-levels.h"
+
+#ifdef CONFIG_RFKILL
+
+
+/**
+ * wimax_report_rfkill_hw - Reports changes in the hardware RF switch
+ *
+ * @wimax_dev: WiMAX device descriptor
+ *
+ * @state: New state of the RF Kill switch. %WIMAX_RF_ON radio on,
+ *     %WIMAX_RF_OFF radio off.
+ *
+ * When the device detects a change in the state of thehardware RF
+ * switch, it must call this function to let the WiMAX kernel stack
+ * know that the state has changed so it can be properly propagated.
+ *
+ * The WiMAX stack caches the state (the driver doesn't need to). As
+ * well, as the change is propagated it will come back as a request to
+ * change the software state to mirror the hardware state.
+ *
+ * If the device doesn't have a hardware kill switch, just report
+ * it on initialization as always on (%WIMAX_RF_ON, radio on).
+ */
+void wimax_report_rfkill_hw(struct wimax_dev *wimax_dev,
+			    enum wimax_rf_state state)
+{
+	int result;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	enum wimax_st wimax_state;
+	enum rfkill_state rfkill_state;
+
+	d_fnstart(3, dev, "(wimax_dev %p state %u)\n", wimax_dev, state);
+	BUG_ON(state == WIMAX_RF_QUERY);
+	BUG_ON(state != WIMAX_RF_ON && state != WIMAX_RF_OFF);
+
+	mutex_lock(&wimax_dev->mutex);
+	result = wimax_dev_is_ready(wimax_dev);
+	if (result < 0)
+		goto error_not_ready;
+
+	if (state != wimax_dev->rf_hw) {
+		wimax_dev->rf_hw = state;
+		rfkill_state = state == WIMAX_RF_ON ?
+			RFKILL_STATE_OFF : RFKILL_STATE_ON;
+		if (wimax_dev->rf_hw == WIMAX_RF_ON
+		    && wimax_dev->rf_sw == WIMAX_RF_ON)
+			wimax_state = WIMAX_ST_READY;
+		else
+			wimax_state = WIMAX_ST_RADIO_OFF;
+		__wimax_state_change(wimax_dev, wimax_state);
+		input_report_key(wimax_dev->rfkill_input, KEY_WIMAX,
+				 rfkill_state);
+	}
+error_not_ready:
+	mutex_unlock(&wimax_dev->mutex);
+	d_fnend(3, dev, "(wimax_dev %p state %u) = void [%d]\n",
+		wimax_dev, state, result);
+}
+EXPORT_SYMBOL_GPL(wimax_report_rfkill_hw);
+
+
+/**
+ * wimax_report_rfkill_sw - Reports changes in the software RF switch
+ *
+ * @wimax_dev: WiMAX device descriptor
+ *
+ * @state: New state of the RF kill switch. %WIMAX_RF_ON radio on,
+ *     %WIMAX_RF_OFF radio off.
+ *
+ * Reports changes in the software RF switch state to the the WiMAX
+ * stack.
+ *
+ * The main use is during initialization, so the driver can query the
+ * device for its current software radio kill switch state and feed it
+ * to the system.
+ *
+ * On the side, the device does not change the software state by
+ * itself. In practice, this can happen, as the device might decide to
+ * switch (in software) the radio off for different reasons.
+ */
+void wimax_report_rfkill_sw(struct wimax_dev *wimax_dev,
+			    enum wimax_rf_state state)
+{
+	int result;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	enum wimax_st wimax_state;
+
+	d_fnstart(3, dev, "(wimax_dev %p state %u)\n", wimax_dev, state);
+	BUG_ON(state == WIMAX_RF_QUERY);
+	BUG_ON(state != WIMAX_RF_ON && state != WIMAX_RF_OFF);
+
+	mutex_lock(&wimax_dev->mutex);
+	result = wimax_dev_is_ready(wimax_dev);
+	if (result < 0)
+		goto error_not_ready;
+
+	if (state != wimax_dev->rf_sw) {
+		wimax_dev->rf_sw = state;
+		if (wimax_dev->rf_hw == WIMAX_RF_ON
+		    && wimax_dev->rf_sw == WIMAX_RF_ON)
+			wimax_state = WIMAX_ST_READY;
+		else
+			wimax_state = WIMAX_ST_RADIO_OFF;
+		__wimax_state_change(wimax_dev, wimax_state);
+	}
+error_not_ready:
+	mutex_unlock(&wimax_dev->mutex);
+	d_fnend(3, dev, "(wimax_dev %p state %u) = void [%d]\n",
+		wimax_dev, state, result);
+}
+EXPORT_SYMBOL_GPL(wimax_report_rfkill_sw);
+
+
+/*
+ * Callback for the RF Kill toggle operation
+ *
+ * This function is called by:
+ *
+ * - The rfkill subsystem when the RF-Kill key is pressed in the
+ *   hardware and the driver notifies through
+ *   wimax_report_rfkill_hw(). The rfkill subsystem ends up calling back
+ *   here so the software RF Kill switch state is changed to reflect
+ *   the hardware switch state.
+ *
+ * - When the user sets the state through sysfs' rfkill/state file
+ *
+ * - When the user calls wimax_rfkill().
+ *
+ * This call blocks!
+ *
+ * WARNING! When we call rfkill_unregister(), this will be called with
+ * state 0!
+ *
+ * WARNING: wimax_dev must be locked
+ */
+static
+int __wimax_rf_toggle_radio(struct wimax_dev *wimax_dev,
+			    enum wimax_rf_state state)
+{
+	int result = 0;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	enum wimax_st wimax_state;
+
+	might_sleep();
+	d_fnstart(3, dev, "(wimax_dev %p state %u)\n", wimax_dev, state);
+	if (wimax_dev->rf_sw == state)
+		goto out_no_change;
+	if (wimax_dev->op_rfkill_sw_toggle != NULL)
+		result = wimax_dev->op_rfkill_sw_toggle(wimax_dev, state);
+	else if (state == WIMAX_RF_OFF)	/* No op? can't turn off */
+		result = -ENXIO;
+	else				/* No op? can turn on */
+		result = 0;		/* should never happen tho */
+	if (result >= 0) {
+		result = 0;
+		wimax_dev->rf_sw = state;
+		wimax_state = state == WIMAX_RF_ON ?
+			WIMAX_ST_READY : WIMAX_ST_RADIO_OFF;
+		__wimax_state_change(wimax_dev, wimax_state);
+	}
+out_no_change:
+	d_fnend(3, dev, "(wimax_dev %p state %u) = %d\n",
+		wimax_dev, state, result);
+	return result;
+}
+
+
+/*
+ * Translate from rfkill state to wimax state
+ *
+ * NOTE: Special state handling rules here
+ *
+ *     Just pretend the call didn't happen if we are in a state where
+ *     we know for sure it cannot be handled (WIMAX_ST_DOWN or
+ *     __WIMAX_ST_QUIESCING). rfkill() needs it to register and
+ *     unregister, as it will run this path.
+ *
+ * NOTE: This call will block until the operation is completed.
+ */
+static
+int wimax_rfkill_toggle_radio(void *data, enum rfkill_state state)
+{
+	int result;
+	struct wimax_dev *wimax_dev = data;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	enum wimax_rf_state rf_state;
+
+	d_fnstart(3, dev, "(wimax_dev %p state %u)\n", wimax_dev, state);
+	switch (state) {
+	case RFKILL_STATE_ON:
+		rf_state = WIMAX_RF_OFF;
+		break;
+	case RFKILL_STATE_OFF:
+		rf_state = WIMAX_RF_ON;
+		break;
+	default:
+		BUG();
+	}
+	mutex_lock(&wimax_dev->mutex);
+	if (wimax_dev->state <= __WIMAX_ST_QUIESCING)
+		result = 0;	/* just pretend it didn't happen */
+	else
+		result = __wimax_rf_toggle_radio(wimax_dev, rf_state);
+	mutex_unlock(&wimax_dev->mutex);
+	d_fnend(3, dev, "(wimax_dev %p state %u) = %d\n",
+		wimax_dev, state, result);
+	return result;
+}
+
+
+/**
+ * wimax_rfkill - Set the software RF switch state for a WiMAX device
+ *
+ * @wimax_dev: WiMAX device descriptor
+ *
+ * @state: New RF state.
+ *
+ * Returns:
+ *
+ * >= 0 toggle state if ok, < 0 errno code on error. The toggle state
+ * is returned as a bitmap, bit 0 being the hardware RF state, bit 1
+ * the software RF state.
+ *
+ * 0 means disabled (%WIMAX_RF_ON, radio on), 1 means enabled radio
+ * off (%WIMAX_RF_OFF).
+ *
+ * Description:
+ *
+ * Called by the user when he wants to request the WiMAX radio to be
+ * switched on (%WIMAX_RF_ON) or off (%WIMAX_RF_OFF). With
+ * %WIMAX_RF_QUERY, just the current state is returned.
+ *
+ * NOTE:
+ *
+ * This call will block until the operation is complete.
+ */
+int wimax_rfkill(struct wimax_dev *wimax_dev, enum wimax_rf_state state)
+{
+	int result;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+
+	d_fnstart(3, dev, "(wimax_dev %p state %u)\n", wimax_dev, state);
+	mutex_lock(&wimax_dev->mutex);
+	result = wimax_dev_is_ready(wimax_dev);
+	if (result < 0)
+		goto error_not_ready;
+	switch (state) {
+	case WIMAX_RF_ON:
+	case WIMAX_RF_OFF:
+		result = __wimax_rf_toggle_radio(wimax_dev, state);
+		if (result < 0)
+			goto error;
+		break;
+	case WIMAX_RF_QUERY:
+		break;
+	default:
+		result = -EINVAL;
+		goto error;
+	}
+	result = wimax_dev->rf_sw << 1 | wimax_dev->rf_hw;
+error:
+error_not_ready:
+	mutex_unlock(&wimax_dev->mutex);
+	d_fnend(3, dev, "(wimax_dev %p state %u) = %d\n",
+		wimax_dev, state, result);
+	return result;
+}
+EXPORT_SYMBOL(wimax_rfkill);
+
+
+/*
+ * Register a new WiMAX device's RF Kill support
+ *
+ * WARNING: wimax_dev->mutex must be unlocked
+ */
+int wimax_rfkill_add(struct wimax_dev *wimax_dev)
+{
+	int result;
+	struct rfkill *rfkill;
+	struct input_dev *input_dev;
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+
+	d_fnstart(3, dev, "(wimax_dev %p)\n", wimax_dev);
+	/* Initialize RF Kill */
+	result = -ENOMEM;
+	rfkill = rfkill_allocate(dev, RFKILL_TYPE_WIMAX);
+	if (rfkill == NULL)
+		goto error_rfkill_allocate;
+	wimax_dev->rfkill = rfkill;
+
+	rfkill->name = wimax_dev->name;
+	rfkill->state = RFKILL_STATE_OFF;
+	rfkill->data = wimax_dev;
+	rfkill->toggle_radio = wimax_rfkill_toggle_radio;
+	rfkill->user_claim_unsupported = 1;
+
+	/* Initialize the input device for the hw key */
+	input_dev = input_allocate_device();
+	if (input_dev == NULL)
+		goto error_input_allocate;
+	wimax_dev->rfkill_input = input_dev;
+	d_printf(1, dev, "rfkill %p input %p\n", rfkill, input_dev);
+
+	input_dev->name = wimax_dev->name;
+	/* FIXME: get a real device bus ID and stuff? do we care? */
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->id.vendor = 0xffff;
+	input_dev->evbit[0] = BIT(EV_KEY);
+	set_bit(KEY_WIMAX, input_dev->keybit);
+
+	/* Register both */
+	result = input_register_device(wimax_dev->rfkill_input);
+	if (result < 0)
+		goto error_input_register;
+	result = rfkill_register(wimax_dev->rfkill);
+	if (result < 0)
+		goto error_rfkill_register;
+
+	/* If there is no SW toggle op, SW RFKill is always on */
+	if (wimax_dev->op_rfkill_sw_toggle == NULL)
+		wimax_dev->rf_sw = WIMAX_RF_ON;
+
+	d_fnend(3, dev, "(wimax_dev %p) = 0\n", wimax_dev);
+	return 0;
+
+	/* if rfkill_register() suceeds, can't use rfkill_free() any
+	 * more, only rfkill_unregister() [it owns the refcount]; with
+	 * the input device we have the same issue--hence the if. */
+error_rfkill_register:
+	input_unregister_device(wimax_dev->rfkill_input);
+	wimax_dev->rfkill_input = NULL;
+error_input_register:
+	if (wimax_dev->rfkill_input)
+		input_free_device(wimax_dev->rfkill_input);
+error_input_allocate:
+	rfkill_free(wimax_dev->rfkill);
+error_rfkill_allocate:
+	d_fnend(3, dev, "(wimax_dev %p) = %d\n", wimax_dev, result);
+	return result;
+}
+
+
+/*
+ * Deregister a WiMAX device's RF Kill support
+ *
+ * Ick, we can't call rfkill_free() after rfkill_unregister()...oh
+ * well.
+ *
+ * WARNING: wimax_dev->mutex must be unlocked
+ */
+void wimax_rfkill_rm(struct wimax_dev *wimax_dev)
+{
+	struct device *dev = wimax_dev_to_dev(wimax_dev);
+	d_fnstart(3, dev, "(wimax_dev %p)\n", wimax_dev);
+	rfkill_unregister(wimax_dev->rfkill);	/* frees */
+	input_unregister_device(wimax_dev->rfkill_input);
+	d_fnend(3, dev, "(wimax_dev %p)\n", wimax_dev);
+}
+
+
+#else /* #ifdef CONFIG_RFKILL */
+
+void wimax_report_rfkill_hw(struct wimax_dev *wimax_dev,
+			    enum wimax_rf_state state)
+{
+}
+EXPORT_SYMBOL_GPL(wimax_report_rfkill_hw);
+
+void wimax_report_rfkill_sw(struct wimax_dev *wimax_dev,
+			    enum wimax_rf_state state)
+{
+}
+EXPORT_SYMBOL_GPL(wimax_report_rfkill_sw);
+
+int wimax_rfkill(struct wimax_dev *wimax_dev,
+		 enum wimax_rf_state state)
+{
+	return WIMAX_RF_ON << 1 | WIMAX_RF_ON;
+}
+EXPORT_SYMBOL_GPL(wimax_rfkill);
+
+int wimax_rfkill_add(struct wimax_dev *wimax_dev)
+{
+	return 0;
+}
+
+void wimax_rfkill_rm(struct wimax_dev *wimax_dev)
+{
+}
+
+#endif /* #ifdef CONFIG_RFKILL */
+
+
+/*
+ * Exporting to user space over generic netlink
+ *
+ * Parse the rfkill command from user space, return a combination
+ * value that describe the states of the different toggles.
+ *
+ * Only one attribute: the new state requested (on, off or no change,
+ * just query).
+ */
+
+static const
+struct nla_policy wimax_gnl_rfkill_policy[WIMAX_GNL_ATTR_MAX + 1] = {
+	[WIMAX_GNL_RFKILL_IFIDX] = {
+		.type = NLA_U32,
+	},
+	[WIMAX_GNL_RFKILL_STATE] = {
+		.type = NLA_U32		/* enum wimax_rf_state */
+	},
+};
+
+
+static
+int wimax_gnl_doit_rfkill(struct sk_buff *skb, struct genl_info *info)
+{
+	int result, ifindex;
+	struct wimax_dev *wimax_dev;
+	struct device *dev;
+	enum wimax_rf_state new_state;
+
+	d_fnstart(3, NULL, "(skb %p info %p)\n", skb, info);
+	result = -ENODEV;
+	if (info->attrs[WIMAX_GNL_RFKILL_IFIDX] == NULL) {
+		printk(KERN_ERR "WIMAX_GNL_OP_RFKILL: can't find IFIDX "
+			"attribute\n");
+		goto error_no_wimax_dev;
+	}
+	ifindex = nla_get_u32(info->attrs[WIMAX_GNL_RFKILL_IFIDX]);
+	wimax_dev = wimax_dev_get_by_genl_info(info, ifindex);
+	if (wimax_dev == NULL)
+		goto error_no_wimax_dev;
+	dev = wimax_dev_to_dev(wimax_dev);
+	result = -EINVAL;
+	if (info->attrs[WIMAX_GNL_RFKILL_STATE] == NULL) {
+		dev_err(dev, "WIMAX_GNL_RFKILL: can't find RFKILL_STATE "
+			"attribute\n");
+		goto error_no_pid;
+	}
+	new_state = nla_get_u32(info->attrs[WIMAX_GNL_RFKILL_STATE]);
+
+	/* Execute the operation and send the result back to user space */
+	result = wimax_rfkill(wimax_dev, new_state);
+error_no_pid:
+	dev_put(wimax_dev->net_dev);
+error_no_wimax_dev:
+	d_fnend(3, NULL, "(skb %p info %p) = %d\n", skb, info, result);
+	return result;
+}
+
+
+struct genl_ops wimax_gnl_rfkill = {
+	.cmd = WIMAX_GNL_OP_RFKILL,
+	.flags = GENL_ADMIN_PERM,
+	.policy = wimax_gnl_rfkill_policy,
+	.doit = wimax_gnl_doit_rfkill,
+	.dumpit = NULL,
+};
+
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 08/29] debugfs: add helpers for exporting a size_t simple value
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (6 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 07/29] wimax: basic API: kernel/user messaging, rfkill and reset Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 09/29] wimax: debugfs controls Inaky Perez-Gonzalez
                   ` (21 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

In the same spirit as debugfs_create_*(), introduce helpers for
exporting size_t values over debugfs.

The only trick done is that the format verifier is kept at %llu
instead of %zu; otherwise type warnings would pop up:

format ‘%zu’ expects type ‘size_t’, but argument 2 has type ‘long long unsigned int’

There is no real way to fix this one--however, we can consider %llu
and %zu to be compatible if we consider that we are using the same for
validating in debugfs_create_{x,u}{8,16,32}().

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
Cc: Greg KH <greg@kroah.com>
---
 fs/debugfs/file.c       |   32 ++++++++++++++++++++++++++++++++
 include/linux/debugfs.h |    2 ++
 2 files changed, 34 insertions(+), 0 deletions(-)

diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index 159a5ef..33a9012 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -294,6 +294,38 @@ struct dentry *debugfs_create_x32(const char *name, mode_t mode,
 }
 EXPORT_SYMBOL_GPL(debugfs_create_x32);
 
+
+static int debugfs_size_t_set(void *data, u64 val)
+{
+	*(size_t *)data = val;
+	return 0;
+}
+static int debugfs_size_t_get(void *data, u64 *val)
+{
+	*val = *(size_t *)data;
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_size_t, debugfs_size_t_get, debugfs_size_t_set,
+			"%llu\n");	/* %llu and %zu are more or less the same */
+
+/**
+ * debugfs_create_size_t - create a debugfs file that is used to read and write an size_t value
+ * @name: a pointer to a string containing the name of the file to create.
+ * @mode: the permission that the file should have
+ * @parent: a pointer to the parent dentry for this file.  This should be a
+ *          directory dentry if set.  If this parameter is %NULL, then the
+ *          file will be created in the root of the debugfs filesystem.
+ * @value: a pointer to the variable that the file should read to and write
+ *         from.
+ */
+struct dentry *debugfs_create_size_t(const char *name, mode_t mode,
+				     struct dentry *parent, size_t *value)
+{
+	return debugfs_create_file(name, mode, parent, value, &fops_size_t);
+}
+EXPORT_SYMBOL_GPL(debugfs_create_size_t);
+
+
 static ssize_t read_file_bool(struct file *file, char __user *user_buf,
 			      size_t count, loff_t *ppos)
 {
diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
index e1a6c04..23936b1 100644
--- a/include/linux/debugfs.h
+++ b/include/linux/debugfs.h
@@ -63,6 +63,8 @@ struct dentry *debugfs_create_x16(const char *name, mode_t mode,
 				  struct dentry *parent, u16 *value);
 struct dentry *debugfs_create_x32(const char *name, mode_t mode,
 				  struct dentry *parent, u32 *value);
+struct dentry *debugfs_create_size_t(const char *name, mode_t mode,
+				     struct dentry *parent, size_t *value);
 struct dentry *debugfs_create_bool(const char *name, mode_t mode,
 				  struct dentry *parent, u32 *value);
 
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 09/29] wimax: debugfs controls
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (7 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 08/29] debugfs: add helpers for exporting a size_t simple value Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 10/29] wimax: Makefile, Kconfig and docbook linkage for the stack Inaky Perez-Gonzalez
                   ` (20 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Expose knobs to control the stack's debug output.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 net/wimax/debugfs.c |   90 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 90 insertions(+), 0 deletions(-)
 create mode 100644 net/wimax/debugfs.c

diff --git a/net/wimax/debugfs.c b/net/wimax/debugfs.c
new file mode 100644
index 0000000..87cf443
--- /dev/null
+++ b/net/wimax/debugfs.c
@@ -0,0 +1,90 @@
+/*
+ * Linux WiMAX
+ * Debugfs support
+ *
+ *
+ * Copyright (C) 2005-2006 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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.
+ */
+#include <linux/debugfs.h>
+#include <linux/wimax.h>
+#include "wimax-internal.h"
+
+#define D_SUBMODULE debugfs
+#include "debug-levels.h"
+
+
+/* Debug framework control of debug levels */
+struct d_level D_LEVEL[] = {
+	D_SUBMODULE_DEFINE(debugfs),
+	D_SUBMODULE_DEFINE(id_table),
+	D_SUBMODULE_DEFINE(op_msg),
+	D_SUBMODULE_DEFINE(op_reset),
+	D_SUBMODULE_DEFINE(op_rfkill),
+	D_SUBMODULE_DEFINE(stack),
+};
+size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+
+#define __debugfs_register(prefix, name, parent)			\
+do {									\
+	result = d_level_register_debugfs(prefix, name, parent);	\
+	if (result < 0)							\
+		goto error;						\
+} while (0)
+
+
+int wimax_debugfs_add(struct wimax_dev *wimax_dev)
+{
+	int result;
+	struct net_device *net_dev = wimax_dev->net_dev;
+	struct device *dev = net_dev->dev.parent;
+	struct dentry *dentry;
+	char buf[128];
+
+	snprintf(buf, sizeof(buf), "wimax:%s", net_dev->name);
+	dentry = debugfs_create_dir(buf, NULL);
+	result = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		if (result == -ENODEV)
+			result = 0;	/* No debugfs support */
+		else
+			dev_err(dev, "Can't create debugfs dentry: %d\n",
+				result);
+		goto out;
+	}
+	wimax_dev->debugfs_dentry = dentry;
+	__debugfs_register("wimax_dl_", debugfs, dentry);
+	__debugfs_register("wimax_dl_", id_table, dentry);
+	__debugfs_register("wimax_dl_", op_msg, dentry);
+	__debugfs_register("wimax_dl_", op_reset, dentry);
+	__debugfs_register("wimax_dl_", op_rfkill, dentry);
+	__debugfs_register("wimax_dl_", stack, dentry);
+	result = 0;
+out:
+	return result;
+
+error:
+	debugfs_remove_recursive(wimax_dev->debugfs_dentry);
+	return result;
+}
+
+void wimax_debugfs_rm(struct wimax_dev *wimax_dev)
+{
+	debugfs_remove_recursive(wimax_dev->debugfs_dentry);
+}
+
+
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 10/29] wimax: Makefile, Kconfig and docbook linkage for the stack
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (8 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 09/29] wimax: debugfs controls Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 11/29] i2400m: documentation and instructions for usage Inaky Perez-Gonzalez
                   ` (19 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This patch provides Makefile and KConfig for the WiMAX stack,
integrating them into the networking stack's Makefile, Kconfig and
doc-book templates.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 Documentation/DocBook/networking.tmpl |    8 +++++++
 net/Kconfig                           |    2 +
 net/Makefile                          |    1 +
 net/wimax/Kconfig                     |   36 +++++++++++++++++++++++++++++++++
 net/wimax/Makefile                    |   13 +++++++++++
 5 files changed, 60 insertions(+), 0 deletions(-)
 create mode 100644 net/wimax/Kconfig
 create mode 100644 net/wimax/Makefile

diff --git a/Documentation/DocBook/networking.tmpl b/Documentation/DocBook/networking.tmpl
index f24f9e8..7053c1c 100644
--- a/Documentation/DocBook/networking.tmpl
+++ b/Documentation/DocBook/networking.tmpl
@@ -101,6 +101,14 @@ X!Enet/core/wireless.c
      <sect1><title>Synchronous PPP</title>
 !Edrivers/net/wan/syncppp.c
      </sect1>
+     <sect1><title>WiMAX</title>
+!Enet/wimax/op-msg.c
+!Enet/wimax/op-reset.c
+!Enet/wimax/op-rfkill.c
+!Enet/wimax/stack.c
+!Iinclude/net/wimax.h
+!Iinclude/linux/wimax.h
+     </sect1>
   </chapter>
 
 </book>
diff --git a/net/Kconfig b/net/Kconfig
index d789d79..3bff221 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -251,6 +251,8 @@ source "net/ieee80211/Kconfig"
 
 endif # WIRELESS
 
+source "net/wimax/Kconfig"
+
 source "net/rfkill/Kconfig"
 source "net/9p/Kconfig"
 
diff --git a/net/Makefile b/net/Makefile
index 27d1f10..620a505 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -61,3 +61,4 @@ obj-$(CONFIG_NET_9P)		+= 9p/
 ifeq ($(CONFIG_NET),y)
 obj-$(CONFIG_SYSCTL)		+= sysctl_net.o
 endif
+obj-$(CONFIG_WIMAX)		+= wimax/
diff --git a/net/wimax/Kconfig b/net/wimax/Kconfig
new file mode 100644
index 0000000..67d5f4d
--- /dev/null
+++ b/net/wimax/Kconfig
@@ -0,0 +1,36 @@
+#
+# WiMAX LAN device configuration
+#
+
+menuconfig WIMAX
+       tristate "WiMAX Wireless Broadband support"
+       help
+
+         Select to configure support for devices that provide wireless
+         broadband connectivity using the WiMAX protocol (IEEE 802.16).
+
+         Please note that most of these devices require signing up for
+         a service plan with a provider.
+
+         The different WiMAX drivers can be enabled in the menu entry
+
+         Device Drivers > Network device support > WiMAX Wireless Broadband devices
+
+         If unsure, it is safe to select M (module).
+
+config WIMAX_DEBUG_LEVEL
+       int "WiMAX debug level"
+       depends on WIMAX
+       default 8
+       help
+
+         Select the maximum debug verbosity level to be compiled into
+         the WiMAX stack code.
+
+         By default, debug messages are disabled at runtime and can be
+         selectively enabled for different parts of the code using the
+         sysfs debug-levels file.
+
+         If set at zero, this will compile out all the debug code.
+
+         It is recommended that it is left at 8.
diff --git a/net/wimax/Makefile b/net/wimax/Makefile
new file mode 100644
index 0000000..d548446
--- /dev/null
+++ b/net/wimax/Makefile
@@ -0,0 +1,13 @@
+
+obj-$(CONFIG_WIMAX)		+= wimax.o
+
+wimax-objs :=		\
+	id-table.o	\
+	op-msg.o	\
+	op-reset.o	\
+	op-rfkill.o	\
+	stack.o
+
+wimax-$(CONFIG_DEBUG_FS) += debugfs.o
+
+
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 11/29] i2400m: documentation and instructions for usage
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (9 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 10/29] wimax: Makefile, Kconfig and docbook linkage for the stack Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 12/29] i2400m: host/device procotol and core driver definitions Inaky Perez-Gonzalez
                   ` (18 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

The driver for the i2400m is a stacked driver. There is a core driver,
the bus-generic driver that has no knowledge or dependencies on how
the device is connected to the system; it only knows how to speak the
device protocol. Then there are the bus-specific drivers (for USB and
SDIO) that provide backends for the generic driver to communicate with
the device.

The bus generic driver connects to the network and WiMAX stacks on the
top side, and on the bottom to the bus-specific drivers.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 Documentation/wimax/README.i2400m |  260 +++++++++++++++++++++++++++++++++++++
 1 files changed, 260 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/wimax/README.i2400m

diff --git a/Documentation/wimax/README.i2400m b/Documentation/wimax/README.i2400m
new file mode 100644
index 0000000..7dffd89
--- /dev/null
+++ b/Documentation/wimax/README.i2400m
@@ -0,0 +1,260 @@
+
+   Driver for the Intel Wireless Wimax Connection 2400m
+
+   (C) 2008 Intel Corporation < linux-wimax@intel.com >
+
+   This provides a driver for the Intel Wireless WiMAX Connection 2400m
+   and a basic Linux kernel WiMAX stack.
+
+1. Requirements
+
+     * Linux installation with Linux kernel 2.6.22 or newer (if building
+       from a separate tree)
+     * Intel i2400m Echo Peak or Baxter Peak; this includes the Intel
+       Wireless WiMAX/WiFi Link 5x50 series.
+     * build tools:
+          + Linux kernel development package for the target kernel; to
+            build against your currently running kernel, you need to have
+            the kernel development package corresponding to the running
+            image installed (usually if your kernel is named
+            linux-VERSION, the development package is called
+            linux-dev-VERSION or linux-headers-VERSION).
+          + GNU C Compiler, make
+
+2. Compilation and installation
+
+2.1. Compilation of the drivers included in the kernel
+
+   Configure the kernel; to enable the WiMAX drivers select Drivers >
+   Networking Drivers > WiMAX device support. Enable all of them as
+   modules (easier).
+
+   If USB or SDIO are not enabled in the kernel configuration, the options
+   to build the i2400m USB or SDIO drivers will not show. Enable said
+   subsystems and go back to the WiMAX menu to enable the drivers.
+
+   Compile and install your kernel as usual.
+
+2.2. Compilation of the drivers distributed as an standalone module
+
+   To compile
+
+$ cd source/directory
+$ make
+
+   Once built you can load and unload using the provided load.sh script;
+   load.sh will load the modules, load.sh u will unload them.
+
+   To install in the default kernel directories (and enable auto loading
+   when the device is plugged):
+
+$ make install
+$ depmod -a
+
+   If your kernel development files are located in a non standard
+   directory or if you want to build for a kernel that is not the
+   currently running one, set KDIR to the right location:
+
+$ make KDIR=/path/to/kernel/dev/tree
+
+   For more information, please contact linux-wimax@intel.com.
+
+3. Installing the firmware
+
+   The firmware can be obtained from http://linuxwimax.org or might have
+   been supplied with your hardware.
+
+   It has to be installed in the target system:
+     *
+$ cp FIRMWAREFILE.sbcf /lib/firmware/i2400m-fw-BUSTYPE-1.3.sbcf
+
+     * NOTE: if your firmware came in an .rpm or .deb file, just install
+       it as normal, with the rpm (rpm -i FIRMWARE.rpm) or dpkg
+       (dpkg -i FIRMWARE.deb) commands. No further action is needed.
+     * BUSTYPE will be usb or sdio, depending on the hardware you have.
+       Each hardware type comes with its own firmware and will not work
+       with other types.
+
+4. Design
+
+   This package contains two major parts: a WiMAX kernel stack and a
+   driver for the Intel i2400m.
+
+   The WiMAX stack is designed to provide for common WiMAX control
+   services to current and future WiMAX devices from any vendor; please
+   see README.wimax for details.
+
+   The i2400m kernel driver is broken up in two main parts: the bus
+   generic driver and the bus-specific drivers. The bus generic driver
+   forms the drivercore and contain no knowledge of the actual method we
+   use to connect to the device. The bus specific drivers are just the
+   glue to connect the bus-generic driver and the device. Currently only
+   USB and SDIO are supported. See drivers/net/wimax/i2400m/i2400m.h for
+   more information.
+
+   The bus generic driver is logically broken up in two parts: OS-glue and
+   hardware-glue. The OS-glue interfaces with Linux. The hardware-glue
+   interfaces with the device on using an interface provided by the
+   bus-specific driver. The reason for this breakup is to be able to
+   easily reuse the hardware-glue to write drivers for other OSes; note
+   the hardware glue part is written as a native Linux driver; no
+   abstraction layers are used, so to port to another OS, the Linux kernel
+   API calls should be replaced with the target OS's.
+
+5. Usage
+
+   To load the driver, follow the instructions in the install section;
+   once the driver is loaded, plug in the device (unless it is permanently
+   plugged in). The driver will enumerate the device, upload the firmware
+   and output messages in the kernel log (dmesg, /var/log/messages or
+   /var/log/kern.log) such as:
+
+...
+i2400m_usb 5-4:1.0: firmware interface version 8.0.0
+i2400m_usb 5-4:1.0: WiMAX interface wmx0 (00:1d:e1:01:94:2c) ready
+
+   At this point the device is ready to work.
+
+   Current versions require the Intel WiMAX Network Service in userspace
+   to make things work. See the network service's README for instructions
+   on how to scan, connect and disconnect.
+
+5.1. Module parameters
+
+   Module parameters can be set at kernel or module load time or by
+   echoing values:
+
+$ echo VALUE > /sys/module/MODULENAME/parameters/PARAMETERNAME
+
+   To make changes permanent, for example, for the i2400m module, you can
+   also create a file named /etc/modprobe.d/i2400m containing:
+
+options i2400m idle_mode_disabled=1
+
+   To find which parameters are supported by a module, run:
+
+$ modinfo path/to/module.ko
+
+   During kernel bootup (if the driver is linked in the kernel), specify
+   the following to the kernel command line:
+
+i2400m.PARAMETER=VALUE
+
+5.1.1. i2400m: idle_mode_disabled
+
+   The i2400m module supports a parameter to disable idle mode. This
+   parameter, once set, will take effect only when the device is
+   reinitialized by the driver (eg: following a reset or a reconnect).
+
+5.2. Debug operations: debugfs entries
+
+   The driver will register debugfs entries that allow the user to tweak
+   debug settings. There are three main container directories where
+   entries are placed, which correspond to the three blocks a i2400m WiMAX
+   driver has:
+     * /sys/kernel/debug/wimax:DEVNAME/ for the generic WiMAX stack
+       controls
+     * /sys/kernel/debug/wimax:DEVNAME/i2400m for the i2400m generic
+       driver controls
+     * /sys/kernel/debug/wimax:DEVNAME/i2400m-usb (or -sdio) for the
+       bus-specific i2400m-usb or i2400m-sdio controls).
+
+   Of course, if debugfs is mounted in a directory other than
+   /sys/kernel/debug, those paths will change.
+
+5.2.1. Increasing debug output
+
+   The files named *dl_* indicate knobs for controlling the debug output
+   of different submodules:
+     *
+# find /sys/kernel/debug/wimax\:wmx0 -name \*dl_\*
+/sys/kernel/debug/wimax:wmx0/i2400m-usb/dl_tx
+/sys/kernel/debug/wimax:wmx0/i2400m-usb/dl_rx
+/sys/kernel/debug/wimax:wmx0/i2400m-usb/dl_notif
+/sys/kernel/debug/wimax:wmx0/i2400m-usb/dl_fw
+/sys/kernel/debug/wimax:wmx0/i2400m-usb/dl_usb
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_tx
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_rx
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_rfkill
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_netdev
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_fw
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_debugfs
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_driver
+/sys/kernel/debug/wimax:wmx0/i2400m/dl_control
+/sys/kernel/debug/wimax:wmx0/wimax_dl_stack
+/sys/kernel/debug/wimax:wmx0/wimax_dl_op_rfkill
+/sys/kernel/debug/wimax:wmx0/wimax_dl_op_reset
+/sys/kernel/debug/wimax:wmx0/wimax_dl_op_msg
+/sys/kernel/debug/wimax:wmx0/wimax_dl_id_table
+/sys/kernel/debug/wimax:wmx0/wimax_dl_debugfs
+
+   By reading the file you can obtain the current value of said debug
+   level; by writing to it, you can set it.
+
+   To increase the debug level of, for example, the i2400m's generic TX
+   engine, just write:
+
+$ echo 3 > /sys/kernel/debug/wimax:wmx0/i2400m/dl_tx
+
+   Increasing numbers yield increasing debug information; for details of
+   what is printed and the available levels, check the source. The code
+   uses 0 for disabled and increasing values until 8.
+
+5.2.2. RX and TX statistics
+
+   The i2400m/rx_stats and i2400m/tx_stats provide statistics about the
+   data reception/delivery from the device:
+
+$ cat /sys/kernel/debug/wimax:wmx0/i2400m/rx_stats
+45 1 3 34 3104 48 480
+
+   The numbers reported are
+     * packets/RX-buffer: total, min, max
+     * RX-buffers: total RX buffers received, accumulated RX buffer size
+       in bytes, min size received, max size received
+
+   Thus, to find the average buffer size received, divide accumulated
+   RX-buffer / total RX-buffers.
+
+   To clear the statistics back to 0, write anything to the rx_stats file:
+
+$ echo 1 > /sys/kernel/debug/wimax:wmx0/i2400m_rx_stats
+
+   Likewise for TX.
+
+   Note the packets this debug file refers to are not network packet, but
+   packets in the sense of the device-specific protocol for communication
+   to the host. See drivers/net/wimax/i2400m/tx.c.
+
+5.2.3. Tracing messages received from user space
+
+   To echo messages received from user space into the trace pipe that the
+   i2400m driver creates, set the debug file i2400m/trace_msg_from_user to
+   1:
+     *
+$ echo 1 > /sys/kernel/debug/wimax:wmx0/i2400m/trace_msg_from_user
+
+5.2.4. Performing a device reset
+
+   By writing a 0, a 1 or a 2 to the file
+   /sys/kernel/debug/wimax:wmx0/reset, the driver performs a warm (without
+   disconnecting from the bus), cold (disconnecting from the bus) or bus
+   (bus specific) reset on the device.
+
+5.2.5. Asking the device to enter power saving mode
+
+   By writing any value to the /sys/kernel/debug/wimax:wmx0 file, the
+   device will attempt to enter power saving mode.
+
+6. Troubleshooting
+
+6.1. Driver complains about 'i2400m-fw-usb-1.2.sbcf: request failed'
+
+   If upon connecting the device, the following is output in the kernel
+   log:
+
+i2400m_usb 5-4:1.0: fw i2400m-fw-usb-1.3.sbcf: request failed: -2
+
+   This means that the driver cannot locate the firmware file named
+   /lib/firmware/i2400m-fw-usb-1.2.sbcf. Check that the file is present in
+   the right location.
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 12/29] i2400m: host/device procotol and core driver definitions
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (10 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 11/29] i2400m: documentation and instructions for usage Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-16  3:43   ` Wang Chen
  2008-12-15 12:58 ` [PATCH 13/29] i2400m: Generic probe/disconnect, reset and message passing Inaky Perez-Gonzalez
                   ` (17 subsequent siblings)
  29 siblings, 1 reply; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

The wimax/i2400m.h defines the structures and constants for the
host-device protocols:

 - boot / firmware upload protocol

 - general data transport protocol

 - control protocol

It is done in such a way that can also be used verbatim by user space.

drivers/net/wimax/i2400m.h defines all the APIs used by the core,
bus-generic driver (i2400m) and the bus specific drivers
(i2400m-BUSNAME). It also gives a roadmap to the driver
implementation.

debug-levels.h adds the core driver's debug settings.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/debug-levels.h |   45 ++
 drivers/net/wimax/i2400m/i2400m.h       |  755 +++++++++++++++++++++++++++++++
 include/linux/wimax/i2400m.h            |  512 +++++++++++++++++++++
 3 files changed, 1312 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/debug-levels.h
 create mode 100644 drivers/net/wimax/i2400m/i2400m.h
 create mode 100644 include/linux/wimax/i2400m.h

diff --git a/drivers/net/wimax/i2400m/debug-levels.h b/drivers/net/wimax/i2400m/debug-levels.h
new file mode 100644
index 0000000..3183baa
--- /dev/null
+++ b/drivers/net/wimax/i2400m/debug-levels.h
@@ -0,0 +1,45 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Debug levels control file for the i2400m module
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 __debug_levels__h__
+#define __debug_levels__h__
+
+/* Maximum compile and run time debug level for all submodules */
+#define D_MODULENAME i2400m
+#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL
+
+#include <linux/wimax/debug.h>
+
+/* List of all the enabled modules */
+enum d_module {
+	D_SUBMODULE_DECLARE(control),
+	D_SUBMODULE_DECLARE(driver),
+	D_SUBMODULE_DECLARE(debugfs),
+	D_SUBMODULE_DECLARE(fw),
+	D_SUBMODULE_DECLARE(netdev),
+	D_SUBMODULE_DECLARE(rfkill),
+	D_SUBMODULE_DECLARE(rx),
+	D_SUBMODULE_DECLARE(tx),
+};
+
+
+#endif /* #ifndef __debug_levels__h__ */
diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h
new file mode 100644
index 0000000..06a78d3
--- /dev/null
+++ b/drivers/net/wimax/i2400m/i2400m.h
@@ -0,0 +1,755 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Declarations for bus-generic internal APIs
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * GENERAL DRIVER ARCHITECTURE
+ *
+ * The i2400m driver is split in the following two major parts:
+ *
+ *  - bus specific driver
+ *  - bus generic driver (this part)
+ *
+ * The bus specific driver sets up stuff specific to the bus the
+ * device is connected to (USB, SDIO, PCI, tam-tam...non-authoritative
+ * nor binding list) which is basically the device-model management
+ * (probe/disconnect, etc), moving data from device to kernel and
+ * back, doing the power saving details and reseting the device.
+ *
+ * For details on each bus-specific driver, see it's include file,
+ * i2400m-BUSNAME.h
+ *
+ * The bus-generic functionality break up is:
+ *
+ *  - Firmware upload: fw.c - takes care of uploading firmware to the
+ *        device. bus-specific driver just needs to provides a way to
+ *        execute boot-mode commands and to reset the device.
+ *
+ *  - RX handling: rx.c - receives data from the bus-specific code and
+ *        feeds it to the network or WiMAX stack or uses it to modify
+ *        the driver state. bus-specific driver only has to receive
+ *        frames and pass them to this module.
+ *
+ *  - TX handling: tx.c - manages the TX FIFO queue and provides means
+ *        for the bus-specific TX code to pull data from the FIFO
+ *        queue. bus-specific code just pulls frames from this module
+ *        to sends them to the device.
+ *
+ *  - netdev glue: netdev.c - interface with Linux networking
+ *        stack. Pass around data frames, and configure when the
+ *        device is up and running or shutdown (through ifconfig up /
+ *        down). Bus-generic only.
+ *
+ *  - control ops: control.c - implements various commmands for
+ *        controlling the device. bus-generic only.
+ *
+ *  - device model glue: driver.c - implements helpers for the
+ *        device-model glue done by the bus-specific layer
+ *        (setup/release the driver resources), turning the device on
+ *        and off, handling the device reboots/resets and a few simple
+ *        WiMAX stack ops.
+ *
+ * Code is also broken up in linux-glue / device-glue.
+ *
+ * Linux glue contains functions that deal mostly with gluing with the
+ * rest of the Linux kernel.
+ *
+ * Device-glue are functions that deal mostly with the way the device
+ * does things and talk the device's language.
+ *
+ * device-glue code is licensed BSD so other open source OSes can take
+ * it to implement their drivers.
+ *
+ *
+ * APIs AND HEADER FILES
+ *
+ * This bus generic code exports three APIs:
+ *
+ *  - HDI (host-device interface) definitions common to all busses
+ *    (include/linux/wimax/i2400m.h); these can be also used by user
+ *    space code.
+ *  - internal API for the bus-generic code
+ *  - external API for the bus-specific drivers
+ *
+ *
+ * LIFE CYCLE:
+ *
+ * When the bus-specific driver probes, it allocates a network device
+ * with enough space for it's data structue, that must contain a
+ * &struct i2400m at the top.
+ *
+ * On probe, it needs to fill the i2400m members marked as [fill], as
+ * well as i2400m->wimax_dev.net_dev and call i2400m_setup(). The
+ * i2400m driver will only register with the WiMAX and network stacks;
+ * the only access done to the device is to read the MAC address so we
+ * can register a network device. This calls i2400m_dev_start() to
+ * load firmware, setup communication with the device and configure it
+ * for operation.
+ *
+ * At this point, control and data communications are possible.
+ *
+ * On disconnect/driver unload, the bus-specific disconnect function
+ * calls i2400m_release() to undo i2400m_setup(). i2400m_dev_stop()
+ * shuts the firmware down and releases resources uses to communicate
+ * with the device.
+ *
+ * While the device is up, it might reset. The bus-specific driver has
+ * to catch that situation and call i2400m_dev_reset_handle() to deal
+ * with it (reset the internal driver structures and go back to square
+ * one).
+ */
+
+#ifndef __I2400M_H__
+#define __I2400M_H__
+
+#include <linux/usb.h>
+#include <linux/netdevice.h>
+#include <linux/completion.h>
+#include <linux/rwsem.h>
+#include <asm/atomic.h>
+#include <net/wimax.h>
+#include <linux/wimax/i2400m.h>
+#include <asm/byteorder.h>
+
+/* Misc constants */
+enum {
+	/* Firmware uploading */
+	I2400M_BOOT_RETRIES = 3,
+	/* Size of the Boot Mode Command buffer */
+	I2400M_BM_CMD_BUF_SIZE = 16 * 1024,
+	I2400M_BM_ACK_BUF_SIZE = 256,
+};
+
+
+/* Firmware version we request when pulling the fw image file */
+#define I2400M_FW_VERSION "1.3"
+
+
+/**
+ * i2400m_reset_type - methods to reset a device
+ *
+ * @I2400M_RT_WARM: Reset without device disconnection, device handles
+ *     are kept valid but state is back to power on, with firmware
+ *     re-uploaded.
+ * @I2400M_RT_COLD: Tell the device to disconnect itself from the bus
+ *     and reconnect. Renders all device handles invalid.
+ * @I2400M_RT_BUS: Tells the bus to reset the device; last measure
+ *     used when both types above don't work.
+ */
+enum i2400m_reset_type {
+	I2400M_RT_WARM,	/* first measure */
+	I2400M_RT_COLD,	/* second measure */
+	I2400M_RT_BUS,	/* call in artillery */
+};
+
+struct i2400m_reset_ctx;
+
+/**
+ * struct i2400m - descriptor for an Intel 2400m
+ *
+ * Members marked with [fill] must be filled out/initialized before
+ * calling i2400m_setup().
+ *
+ * @bus_tx_block_size: [fill] SDIO imposes a 256 block size, USB 16,
+ *     so we have a tx_blk_size variable that the bus layer sets to
+ *     tell the engine how much of that we need.
+ *
+ * @bus_pl_size_max: [fill] Maximum payload size.
+ *
+ * @bus_dev_start: [fill] Function called by the bus-generic code
+ *     [i2400m_dev_start()] to setup the bus-specific communications
+ *     to the the device. See LIFE CYCLE above.
+ *
+ *     NOTE: Doesn't need to upload the firmware, as that is taken
+ *     care of by the bus-generic code.
+ *
+ * @bus_dev_stop: [fill] Function called by the bus-generic code
+ *     [i2400m_dev_stop()] to shutdown the bus-specific communications
+ *     to the the device. See LIFE CYCLE above.
+ *
+ *     This function does not need to reset the device, just tear down
+ *     all the host resources created to  handle communication with
+ *     the device.
+ *
+ * @bus_tx_kick: [fill] Function called by the bus-generic code to let
+ *     the bus-specific code know that there is data available in the
+ *     TX FIFO for transmission to the device.
+ *
+ *     This function cannot sleep.
+ *
+ * @bus_reset: [fill] Function called by the bus-generic code to reset
+ *     the device in in various ways. Doesn't need to wait for the
+ *     reset to finish.
+ *
+ *     If warm or cold reset fail, this function is expected to do a
+ *     bus-specific reset (eg: USB reset) to get the device to a
+ *     working state (even if it implies device disconecction).
+ *
+ *     Note the warm reset is used by the firmware uploader to
+ *     reinitialize the device.
+ *
+ *     IMPORTANT: this is called very early in the device setup
+ *     process, so it cannot rely on common infrastructure being laid
+ *     out.
+ *
+ * @bus_bm_cmd_send: [fill] Function called to send a boot-mode
+ *     command. Flags are defined in 'enum i2400m_bm_cmd_flags'. This
+ *     is synchronous and has to return 0 if ok or < 0 errno code in
+ *     any error condition.
+ *
+ * @bus_bm_wait_for_ack: [fill] Function called to wait for a
+ *     boot-mode notification (that can be a response to a previously
+ *     issued command or an asynchronous one). Will read until all the
+ *     indicated size is read or timeout. Reading more or less data
+ *     than asked for is an error condition. Return 0 if ok, < 0 errno
+ *     code on error.
+ *
+ *     The caller to this function will check if the response is a
+ *     barker that indicates the device going into reset mode.
+ *
+ * @bus_fw_name: [fill] name of the firmware image (in most cases,
+ *     they are all the same for a single release, except that they
+ *     have the type of the bus embedded in the name (eg:
+ *     i2400m-fw-X-VERSION.sbcf, where X is the bus name).
+ *
+ * @bus_bm_mac_addr_impaired: [fill] Set to true if the device's MAC
+ *     address provided in boot mode is kind of broken and needs to
+ *     be re-read later on.
+ *
+ *
+ * @wimax_dev: WiMAX generic device for linkage into the kernel WiMAX
+ *     stack. Due to the way a net_device is allocated, we need to
+ *     force this to be the first field so that we can get from
+ *     net_dev_priv() the right pointer.
+ *
+ * @state: device's state (as reported by it)
+ *
+ * @state_wq: waitqueue that is woken up whenever the state changes
+ *
+ * @tx_lock: spinlock to protect TX members
+ *
+ * @tx_buf: FIFO buffer for TX; we queue data here
+ *
+ * @tx_in: FIFO index for incoming data. Note this doesn't wrap around
+ *     and it is always greater than @tx_out.
+ *
+ * @tx_out: FIFO index for outgoing data
+ *
+ * @tx_msg: current TX message that is active in the FIFO for
+ *     appending payloads.
+ *
+ * @tx_sequence: current sequence number for TX messages from the
+ *     device to the host.
+ *
+ * @tx_msg_size: size of the current message being transmitted by the
+ *     bus-specific code.
+ *
+ * @tx_pl_num: total number of payloads sent
+ *
+ * @tx_pl_max: maximum number of payloads sent in a TX message
+ *
+ * @tx_pl_min: minimum number of payloads sent in a TX message
+ *
+ * @tx_num: number of TX messages sent
+ *
+ * @tx_size_acc: number of bytes in all TX messages sent
+ *     (this is different to net_dev's statistics as it also counts
+ *     control messages).
+ *
+ * @tx_size_min: smallest TX message sent.
+ *
+ * @tx_size_max: biggest TX message sent.
+ *
+ * @rx_lock: spinlock to protect RX members
+ *
+ * @rx_pl_num: total number of payloads received
+ *
+ * @rx_pl_max: maximum number of payloads received in a RX message
+ *
+ * @rx_pl_min: minimum number of payloads received in a RX message
+ *
+ * @rx_num: number of RX messages received
+ *
+ * @rx_size_acc: number of bytes in all RX messages received
+ *     (this is different to net_dev's statistics as it also counts
+ *     control messages).
+ *
+ * @rx_size_min: smallest RX message received.
+ *
+ * @rx_size_max: buggest RX message received.
+ *
+ * @init_mutex: Mutex used for serializing the device bringup
+ *     sequence; this way if the device reboots in the middle, we
+ *     don't try to do a bringup again while we are tearing down the
+ *     one that failed.
+ *
+ *     Can't reuse @msg_mutex because from within the bringup sequence
+ *     we need to send messages to the device and thus use @msg_mutex.
+ *
+ * @msg_mutex: mutex used to send control commands to the device (we
+ *     only allow one at a time, per host-device interface design).
+ *
+ * @msg_completion: used to wait for an ack to a control command sent
+ *     to the device.
+ *
+ * @ack_skb: used to store the actual ack to a control command if the
+ *     reception of the command was successful. Otherwise, a ERR_PTR()
+ *     errno code that indicates what failed with the ack reception.
+ *
+ *     Only valid after @msg_completion is woken up. Only updateable
+ *     if @msg_completion is armed. Only touched by
+ *     i2400m_msg_to_dev().
+ *
+ *     Protected by @rx_lock. In theory the command execution flow is
+ *     sequential, but in case the device sends an out-of-phase or
+ *     very delayed response, we need to avoid it trampling current
+ *     execution.
+ *
+ * @bm_cmd_buf: boot mode command buffer for composing firmware upload
+ *     commands.
+ *
+ *     USB can't r/w to stack, vmalloc, etc...as well, we end up
+ *     having to alloc/free a lot to compose commands, so we use these
+ *     for stagging and not having to realloc all the time.
+ *
+ *     This assumes the code always runs serialized. Only one thread
+ *     can call i2400m_bm_cmd() at the same time.
+ *
+ * @bm_ack_buf: boot mode acknoledge buffer for staging reception of
+ *     responses to commands.
+ *
+ *     See @bm_cmd_buf.
+ *
+ * @work_queue: work queue for processing device reports. This
+ *     workqueue cannot be used for processing TX or RX to the device,
+ *     as from it we'll process device reports, which might require
+ *     further communication with the device.
+ *
+ * @debugfs_dentry: hookup for debugfs files.
+ *     These have to be in a separate directory, a child of
+ *     (wimax_dev->debugfs_dentry) so they can be removed when the
+ *     module unloads, as we don't keep each dentry.
+ */
+struct i2400m {
+	struct wimax_dev wimax_dev;	/* FIRST! See doc */
+
+	unsigned updown:1;		/* Network device is up or down */
+	unsigned boot_mode:1;		/* is the device in boot mode? */
+	unsigned sboot:1;		/* signed or unsigned fw boot */
+	unsigned ready:1;		/* all probing steps done */
+	u8 trace_msg_from_user;		/* echo rx msgs to 'trace' pipe */
+					/* typed u8 so debugfs/u8 can tweak */
+	enum i2400m_system_state state;
+	wait_queue_head_t state_wq;	/* Woken up when on state updates */
+
+	size_t bus_tx_block_size;
+	size_t bus_pl_size_max;
+	int (*bus_dev_start)(struct i2400m *);
+	void (*bus_dev_stop)(struct i2400m *);
+	void (*bus_tx_kick)(struct i2400m *);
+	int (*bus_reset)(struct i2400m *, enum i2400m_reset_type);
+	ssize_t (*bus_bm_cmd_send)(struct i2400m *,
+				   const struct i2400m_bootrom_header *,
+				   size_t, int flags);
+	ssize_t (*bus_bm_wait_for_ack)(struct i2400m *,
+				       struct i2400m_bootrom_header *, size_t);
+	const char *bus_fw_name;
+	unsigned bus_bm_mac_addr_impaired:1;
+
+	spinlock_t tx_lock;		/* protect TX state */
+	void *tx_buf;
+	size_t tx_in, tx_out;
+	struct i2400m_msg_hdr *tx_msg;
+	size_t tx_sequence, tx_msg_size;
+	/* TX stats */
+	unsigned tx_pl_num, tx_pl_max, tx_pl_min,
+		tx_num, tx_size_acc, tx_size_min, tx_size_max;
+
+	/* RX stats */
+	spinlock_t rx_lock;		/* protect RX state */
+	unsigned rx_pl_num, rx_pl_max, rx_pl_min,
+		rx_num, rx_size_acc, rx_size_min, rx_size_max;
+
+	struct mutex msg_mutex;		/* serialize command execution */
+	struct completion msg_completion;
+	struct sk_buff *ack_skb;	/* protected by rx_lock */
+
+	void *bm_ack_buf;		/* for receiving acks over USB */
+	void *bm_cmd_buf;		/* for issuing commands over USB */
+
+	struct workqueue_struct *work_queue;
+
+	struct mutex init_mutex;	/* protect bringup seq */
+	struct i2400m_reset_ctx *reset_ctx;	/* protected by init_mutex */
+
+	struct work_struct wake_tx_ws;
+	struct sk_buff *wake_tx_skb;
+
+	struct dentry *debugfs_dentry;
+};
+
+
+/*
+ * Initialize a 'struct i2400m' from all zeroes
+ *
+ * This is a bus-generic API call.
+ */
+static inline
+void i2400m_init(struct i2400m *i2400m)
+{
+	wimax_dev_init(&i2400m->wimax_dev);
+
+	i2400m->boot_mode = 1;
+	init_waitqueue_head(&i2400m->state_wq);
+
+	spin_lock_init(&i2400m->tx_lock);
+	i2400m->tx_pl_min = UINT_MAX;
+	i2400m->tx_size_min = UINT_MAX;
+
+	spin_lock_init(&i2400m->rx_lock);
+	i2400m->rx_pl_min = UINT_MAX;
+	i2400m->rx_size_min = UINT_MAX;
+
+	mutex_init(&i2400m->msg_mutex);
+	init_completion(&i2400m->msg_completion);
+
+	mutex_init(&i2400m->init_mutex);
+	/* wake_tx_ws is initialized in i2400m_tx_setup() */
+}
+
+
+/*
+ * Bus-generic internal APIs
+ * -------------------------
+ */
+
+static inline
+struct i2400m *wimax_dev_to_i2400m(struct wimax_dev *wimax_dev)
+{
+	return container_of(wimax_dev, struct i2400m, wimax_dev);
+}
+
+static inline
+struct i2400m *net_dev_to_i2400m(struct net_device *net_dev)
+{
+	return wimax_dev_to_i2400m(netdev_priv(net_dev));
+}
+
+/*
+ * Boot mode support
+ */
+
+/**
+ * i2400m_bm_cmd_flags - flags to i2400m_bm_cmd()
+ *
+ * @I2400M_BM_CMD_RAW: send the command block as-is, without doing any
+ *     extra processing for adding CRC.
+ */
+enum i2400m_bm_cmd_flags {
+	I2400M_BM_CMD_RAW	= 1 << 2,
+};
+
+/**
+ * i2400m_bri - Boot-ROM indicators
+ *
+ * Flags for i2400m_bootrom_init() and i2400m_dev_bootstrap() [which
+ * are passed from things like i2400m_setup()]. Can be combined with
+ * |.
+ *
+ * @I2400M_BRI_SOFT: The device rebooted already and a reboot
+ *     barker received, proceed directly to ack the boot sequence.
+ * @I2400M_BRI_NO_REBOOT: Do not reboot the device and proceed
+ *     directly to wait for a reboot barker from the device.
+ * @I2400M_BRI_MAC_REINIT: We need to reinitialize the boot
+ *     rom after reading the MAC adress. This is quite a dirty hack,
+ *     if you ask me -- the device requires the bootrom to be
+ *     intialized after reading the MAC address.
+ */
+enum i2400m_bri {
+	I2400M_BRI_SOFT       = 1 << 1,
+	I2400M_BRI_NO_REBOOT  = 1 << 2,
+	I2400M_BRI_MAC_REINIT = 1 << 3,
+};
+
+extern void i2400m_bm_cmd_prepare(struct i2400m_bootrom_header *);
+extern int i2400m_dev_bootstrap(struct i2400m *, enum i2400m_bri);
+extern int i2400m_read_mac_addr(struct i2400m *);
+extern int i2400m_bootrom_init(struct i2400m *, enum i2400m_bri);
+
+/* Make/grok boot-rom header commands */
+
+static inline
+__le32 i2400m_brh_command(enum i2400m_brh_opcode opcode, unsigned use_checksum,
+			  unsigned direct_access)
+{
+	return cpu_to_le32(
+		I2400M_BRH_SIGNATURE
+		| (direct_access ? I2400M_BRH_DIRECT_ACCESS : 0)
+		| I2400M_BRH_RESPONSE_REQUIRED /* response always required */
+		| (use_checksum ? I2400M_BRH_USE_CHECKSUM : 0)
+		| (opcode & I2400M_BRH_OPCODE_MASK));
+}
+
+static inline
+void i2400m_brh_set_opcode(struct i2400m_bootrom_header *hdr,
+			   enum i2400m_brh_opcode opcode)
+{
+	hdr->command = cpu_to_le32(
+		(le32_to_cpu(hdr->command) & ~I2400M_BRH_OPCODE_MASK)
+		| (opcode & I2400M_BRH_OPCODE_MASK));
+}
+
+static inline
+unsigned i2400m_brh_get_opcode(const struct i2400m_bootrom_header *hdr)
+{
+	return le32_to_cpu(hdr->command) & I2400M_BRH_OPCODE_MASK;
+}
+
+static inline
+unsigned i2400m_brh_get_response(const struct i2400m_bootrom_header *hdr)
+{
+	return (le32_to_cpu(hdr->command) & I2400M_BRH_RESPONSE_MASK)
+		>> I2400M_BRH_RESPONSE_SHIFT;
+}
+
+static inline
+unsigned i2400m_brh_get_use_checksum(const struct i2400m_bootrom_header *hdr)
+{
+	return le32_to_cpu(hdr->command) & I2400M_BRH_USE_CHECKSUM;
+}
+
+static inline
+unsigned i2400m_brh_get_response_required(
+	const struct i2400m_bootrom_header *hdr)
+{
+	return le32_to_cpu(hdr->command) & I2400M_BRH_RESPONSE_REQUIRED;
+}
+
+static inline
+unsigned i2400m_brh_get_direct_access(const struct i2400m_bootrom_header *hdr)
+{
+	return le32_to_cpu(hdr->command) & I2400M_BRH_DIRECT_ACCESS;
+}
+
+static inline
+unsigned i2400m_brh_get_signature(const struct i2400m_bootrom_header *hdr)
+{
+	return (le32_to_cpu(hdr->command) & I2400M_BRH_SIGNATURE_MASK)
+		>> I2400M_BRH_SIGNATURE_SHIFT;
+}
+
+
+/*
+ * Driver / device setup and internal functions
+ */
+extern void i2400m_netdev_setup(struct net_device *net_dev);
+extern int i2400m_tx_setup(struct i2400m *);
+extern void i2400m_wake_tx_work(struct work_struct *);
+extern void i2400m_tx_release(struct i2400m *);
+
+extern void i2400m_net_rx(struct i2400m *, struct sk_buff *, unsigned,
+			  const void *, int);
+enum i2400m_pt;
+extern int i2400m_tx(struct i2400m *, const void *, size_t, enum i2400m_pt);
+
+#ifdef CONFIG_DEBUG_FS
+extern int i2400m_debugfs_add(struct i2400m *);
+extern void i2400m_debugfs_rm(struct i2400m *);
+#else
+static inline int i2400m_debugfs_add(struct i2400m *i2400m)
+{
+	return 0;
+}
+static inline void i2400m_debugfs_rm(struct i2400m *i2400m) {}
+#endif
+
+/* Called by _dev_start()/_dev_stop() to initialize the device itself */
+extern int i2400m_dev_initialize(struct i2400m *);
+extern void i2400m_dev_shutdown(struct i2400m *);
+
+extern struct attribute_group i2400m_dev_attr_group;
+
+extern int i2400m_schedule_work(struct i2400m *,
+				void (*)(struct work_struct *), gfp_t);
+
+/* HDI message's payload description handling */
+
+static inline
+size_t i2400m_pld_size(const struct i2400m_pld *pld)
+{
+	return I2400M_PLD_SIZE_MASK & le32_to_cpu(pld->val);
+}
+
+static inline
+enum i2400m_pt i2400m_pld_type(const struct i2400m_pld *pld)
+{
+	return (I2400M_PLD_TYPE_MASK & le32_to_cpu(pld->val))
+		>> I2400M_PLD_TYPE_SHIFT;
+}
+
+static inline
+void i2400m_pld_set(struct i2400m_pld *pld, size_t size,
+		    enum i2400m_pt type)
+{
+	pld->val = cpu_to_le32(
+		((type << I2400M_PLD_TYPE_SHIFT) & I2400M_PLD_TYPE_MASK)
+		|  (size & I2400M_PLD_SIZE_MASK));
+}
+
+
+/*
+ * API for the bus-specific drivers
+ * --------------------------------
+ */
+
+static inline
+struct i2400m *i2400m_get(struct i2400m *i2400m)
+{
+	dev_hold(i2400m->wimax_dev.net_dev);
+	return i2400m;
+}
+
+static inline
+void i2400m_put(struct i2400m *i2400m)
+{
+	dev_put(i2400m->wimax_dev.net_dev);
+}
+
+extern int i2400m_dev_reset_handle(struct i2400m *);
+
+/*
+ * _setup()/_release() are called by the probe/disconnect functions of
+ * the bus-specific drivers.
+ */
+extern int i2400m_setup(struct i2400m *, enum i2400m_bri bm_flags);
+extern void i2400m_release(struct i2400m *);
+
+extern int i2400m_rx(struct i2400m *, struct sk_buff *);
+extern struct i2400m_msg_hdr *i2400m_tx_msg_get(struct i2400m *, size_t *);
+extern void i2400m_tx_msg_sent(struct i2400m *);
+
+static const __le32 i2400m_NBOOT_BARKER[4] = {
+	__constant_cpu_to_le32(I2400M_NBOOT_BARKER),
+	__constant_cpu_to_le32(I2400M_NBOOT_BARKER),
+	__constant_cpu_to_le32(I2400M_NBOOT_BARKER),
+	__constant_cpu_to_le32(I2400M_NBOOT_BARKER)
+};
+
+static const __le32 i2400m_SBOOT_BARKER[4] = {
+	__constant_cpu_to_le32(I2400M_SBOOT_BARKER),
+	__constant_cpu_to_le32(I2400M_SBOOT_BARKER),
+	__constant_cpu_to_le32(I2400M_SBOOT_BARKER),
+	__constant_cpu_to_le32(I2400M_SBOOT_BARKER)
+};
+
+
+/*
+ * Utility functions
+ */
+
+static inline
+struct device *i2400m_dev(struct i2400m *i2400m)
+{
+	return i2400m->wimax_dev.net_dev->dev.parent;
+}
+
+/*
+ * Helper for scheduling simple work functions
+ *
+ * This struct can get any kind of payload attached (normally in the
+ * form of a struct where you pack the stuff you want to pass to the
+ * _work function).
+ */
+struct i2400m_work {
+	struct work_struct ws;
+	struct i2400m *i2400m;
+	u8 pl[0];
+};
+extern int i2400m_queue_work(struct i2400m *,
+			     void (*)(struct work_struct *), gfp_t,
+				const void *, size_t);
+
+extern int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *,
+				   char *, size_t);
+extern int i2400m_msg_size_check(struct i2400m *,
+				 const struct i2400m_l3l4_hdr *, size_t);
+extern struct sk_buff *i2400m_msg_to_dev(struct i2400m *, const void *, size_t);
+extern void i2400m_msg_to_dev_cancel_wait(struct i2400m *, int);
+extern void i2400m_msg_ack_hook(struct i2400m *,
+				const struct i2400m_l3l4_hdr *, size_t);
+extern void i2400m_report_hook(struct i2400m *,
+			       const struct i2400m_l3l4_hdr *, size_t);
+extern int i2400m_cmd_enter_powersave(struct i2400m *);
+extern int i2400m_cmd_get_state(struct i2400m *);
+extern int i2400m_cmd_exit_idle(struct i2400m *);
+extern struct sk_buff *i2400m_get_device_info(struct i2400m *);
+extern int i2400m_firmware_check(struct i2400m *);
+extern int i2400m_set_init_config(struct i2400m *,
+				  const struct i2400m_tlv_hdr **, size_t);
+
+static inline
+struct usb_endpoint_descriptor *usb_get_epd(struct usb_interface *iface, int ep)
+{
+	return &iface->cur_altsetting->endpoint[ep].desc;
+}
+
+extern int i2400m_op_rfkill_sw_toggle(struct wimax_dev *,
+				      enum wimax_rf_state);
+extern void i2400m_report_tlv_rf_switches_status(
+	struct i2400m *, const struct i2400m_tlv_rf_switches_status *);
+
+
+/*
+ * Do a millisecond-sleep for allowing wireshark to dump all the data
+ * packets. Used only for debugging.
+ */
+static inline
+void __i2400m_msleep(unsigned ms)
+{
+#if 1
+#else
+	msleep(ms);
+#endif
+}
+
+/* Module parameters */
+
+extern int i2400m_idle_mode_disabled;
+
+
+#endif /* #ifndef __I2400M_H__ */
diff --git a/include/linux/wimax/i2400m.h b/include/linux/wimax/i2400m.h
new file mode 100644
index 0000000..74198f5
--- /dev/null
+++ b/include/linux/wimax/i2400m.h
@@ -0,0 +1,512 @@
+/*
+ * Intel Wireless WiMax Connection 2400m
+ * Host-Device protocol interface definitions
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * This header defines the data structures and constants used to
+ * communicate with the device.
+ *
+ * BOOTMODE/BOOTROM/FIRMWARE UPLOAD PROTOCOL
+ *
+ * The firmware upload protocol is quite simple and only requires a
+ * handful of commands. See drivers/net/wimax/i2400m/fw.c for more
+ * details.
+ *
+ * The BCF data structure is for the firmware file header.
+ *
+ *
+ * THE DATA / CONTROL PROTOCOL
+ *
+ * This is the normal protocol spoken with the device once the
+ * firmware is uploaded. It transports data payloads and control
+ * messages back and forth.
+ *
+ * It consists 'messages' that pack one or more payloads each. The
+ * format is described in detail in drivers/net/wimax/i2400m/rx.c and
+ * tx.c.
+ *
+ *
+ * THE L3L4 PROTOCOL
+ *
+ * The term L3L4 refers to Layer 3 (the device), Layer 4 (the
+ * driver/host software).
+ *
+ * This is the control protocol used by the host to control the i2400m
+ * device (scan, connect, disconnect...). This is sent to / received
+ * as control frames. These frames consist of a header and zero or
+ * more TLVs with information. We call each control frame a "message".
+ *
+ * Each message is composed of:
+ *
+ * HEADER
+ * [TLV0 + PAYLOAD0]
+ * [TLV1 + PAYLOAD1]
+ * [...]
+ * [TLVN + PAYLOADN]
+ *
+ * The HEADER is defined by 'struct i2400m_l3l4_hdr'. The payloads are
+ * defined by a TLV structure (Type Length Value) which is a 'header'
+ * (struct i2400m_tlv_hdr) and then the payload.
+ *
+ * All integers are represented as Little Endian.
+ *
+ * - REQUESTS AND EVENTS
+ *
+ * The requests can be clasified as follows:
+ *
+ *   COMMAND:  implies a request from the host to the device requesting
+ *             an action being performed. The device will reply with a
+ *             message (with the same type as the command), status and
+ *             no (TLV) payload. Execution of a command might cause
+ *             events (of different type) to be sent later on as
+ *             device's state changes.
+ *
+ *   GET/SET:  similar to COMMAND, but will not cause other
+ *             EVENTs. The reply, in the case of GET, will contain
+ *             TLVs with the requested information.
+ *
+ *   EVENT:    asynchronous messages sent from the device, maybe as a
+ *             consequence of previous COMMANDs but disassociated from
+ *             them.
+ *
+ * Only one request might be pending at the same time (ie: don't
+ * parallelize nor post another GET request before the previous
+ * COMMAND has been acknowledged with it's corresponding reply by the
+ * device).
+ *
+ * The different requests and their formats are described below:
+ *
+ *  I2400M_MT_*   Message types
+ *  I2400M_MS_*   Message status (for replies, events)
+ *  i2400m_tlv_*  TLVs
+ *
+ * data types are named 'struct i2400m_msg_OPNAME', OPNAME matching the
+ * operation.
+ */
+
+#ifndef __LINUX__WIMAX__I2400M_H__
+#define __LINUX__WIMAX__I2400M_H__
+
+#include <linux/types.h>
+
+
+/*
+ * Host Device Interface (HDI) common to all busses
+ */
+
+/* Boot-mode (firmware upload mode) commands */
+
+/* Header for the firmware file */
+struct i2400m_bcf_hdr {
+	__le32 module_type;
+	__le32 header_len;
+	__le32 header_version;
+	__le32 module_id;
+	__le32 module_vendor;
+	__le32 date;		/* BCD YYYMMDD */
+	__le32 size;
+	__le32 key_size;	/* in dwords */
+	__le32 modulus_size;	/* in dwords */
+	__le32 exponent_size;	/* in dwords */
+	__u8 reserved[88];
+} __attribute__ ((packed));
+
+/* Boot mode opcodes */
+enum i2400m_brh_opcode {
+	I2400M_BRH_READ = 1,
+	I2400M_BRH_WRITE = 2,
+	I2400M_BRH_JUMP = 3,
+	I2400M_BRH_SIGNED_JUMP = 8,
+	I2400M_BRH_HASH_PAYLOAD_ONLY = 9,
+};
+
+/* Boot mode command masks and stuff */
+enum i2400m_brh {
+	I2400M_BRH_SIGNATURE = 0xcbbc0000,
+	I2400M_BRH_SIGNATURE_MASK = 0xffff0000,
+	I2400M_BRH_SIGNATURE_SHIFT = 16,
+	I2400M_BRH_OPCODE_MASK = 0x0000000f,
+	I2400M_BRH_RESPONSE_MASK = 0x000000f0,
+	I2400M_BRH_RESPONSE_SHIFT = 4,
+	I2400M_BRH_DIRECT_ACCESS = 0x00000400,
+	I2400M_BRH_RESPONSE_REQUIRED = 0x00000200,
+	I2400M_BRH_USE_CHECKSUM = 0x00000100,
+};
+
+
+/* Constants for bcf->module_id */
+enum i2400m_bcf_mod_id {
+	/* Firmware file carries its own pokes -- pokes are a set of
+	 * magical values that have to be written in certain memory
+	 * addresses to get the device up and ready for firmware
+	 * download when it is in non-signed boot mode. */
+	I2400M_BCF_MOD_ID_POKES = 0x000000001,
+};
+
+
+/**
+ * i2400m_bootrom_header - Header for a boot-mode command
+ *
+ * @cmd: the above command descriptor
+ * @target_addr: where on the device memory should the action be performed.
+ * @data_size: for read/write, amount of data to be read/written
+ * @block_checksum: checksum value (if applicable)
+ * @payload: the beginning of data attached to this header
+ */
+struct i2400m_bootrom_header {
+	__le32 command;		/* Compose with enum i2400_brh */
+	__le32 target_addr;
+	__le32 data_size;
+	__le32 block_checksum;
+	char payload[0];
+} __attribute__ ((packed));
+
+
+/*
+ * Data / control protocol
+ */
+
+/* Packet types for the host-device interface */
+enum i2400m_pt {
+	I2400M_PT_DATA = 0,
+	I2400M_PT_CTRL,
+	I2400M_PT_TRACE,	/* For device debug */
+	I2400M_PT_RESET_WARM,	/* device reset */
+	I2400M_PT_RESET_COLD,	/* USB[transport] reset, like reconnect */
+	I2400M_PT_ILLEGAL
+};
+
+
+/*
+ * Payload for a data packet
+ *
+ * This is prefixed to each and every outgoing DATA type.
+ */
+struct i2400m_pl_data_hdr {
+	__le32 reserved;
+} __attribute__((packed));
+
+
+/* Misc constants */
+enum {
+	I2400M_PL_PAD = 16,	/* Payload data size alignment */
+	I2400M_PL_SIZE_MAX = 0x3EFF,
+	I2400M_MAX_PLS_IN_MSG = 60,
+	/* protocol barkers: sync sequences; for notifications they
+	 * are sent in groups of four. */
+	I2400M_H2D_PREVIEW_BARKER = 0xcafe900d,
+	I2400M_COLD_RESET_BARKER = 0xc01dc01d,
+	I2400M_WARM_RESET_BARKER = 0x50f750f7,
+	I2400M_NBOOT_BARKER = 0xdeadbeef,
+	I2400M_SBOOT_BARKER = 0x0ff1c1a1,
+	I2400M_ACK_BARKER = 0xfeedbabe,
+	I2400M_D2H_MSG_BARKER = 0xbeefbabe,
+};
+
+
+/*
+ * Hardware payload descriptor
+ *
+ * Bitfields encoded in a struct to enforce typing semantics.
+ *
+ * Look in rx.c and tx.c for a full description of the format.
+ */
+struct i2400m_pld {
+	__le32 val;
+} __attribute__ ((packed));
+
+#define I2400M_PLD_SIZE_MASK 0x00003fff
+#define I2400M_PLD_TYPE_SHIFT 16
+#define I2400M_PLD_TYPE_MASK 0x000f0000
+
+/*
+ * Header for a TX message or RX message
+ *
+ * @barker: preamble
+ * @size: used for management of the FIFO queue buffer; before
+ *     sending, this is converted to be a real preamble. This
+ *     indicates the real size of the TX message that starts at this
+ *     point. If the highest bit is set, then this message is to be
+ *     skipped.
+ * @sequence: sequence number of this message
+ * @offset: offset where the message itself starts -- see the comments
+ *     in the file header about message header and payload descriptor
+ *     alignment.
+ * @num_pls: number of payloads in this message
+ * @padding: amount of padding bytes at the end of the message to make
+ *           it be of block-size aligned
+ *
+ * Look in rx.c and tx.c for a full description of the format.
+ */
+struct i2400m_msg_hdr {
+	union {
+		__le32 barker;
+		__u32 size;	/* same size type as barker!! */
+	};
+	union {
+		__le32 sequence;
+		__u32 offset;	/* same size type as barker!! */
+	};
+	__le16 num_pls;
+	__le16 rsv1;
+	__le16 padding;
+	__le16 rsv2;
+	struct i2400m_pld pld[0];
+} __attribute__ ((packed));
+
+
+
+/*
+ * L3/L4 control protocol
+ */
+
+enum {
+	/* Interface version */
+	I2400M_L3L4_VERSION             = 0x0100,
+};
+
+/* Message types */
+enum i2400m_mt {
+	I2400M_MT_RESERVED              = 0x0000,
+	I2400M_MT_INVALID               = 0xffff,
+	I2400M_MT_REPORT_MASK		= 0x8000,
+
+	I2400M_MT_GET_SCAN_RESULT  	= 0x4202,
+	I2400M_MT_SET_SCAN_PARAM   	= 0x4402,
+	I2400M_MT_CMD_RF_CONTROL   	= 0x4602,
+	I2400M_MT_CMD_SCAN         	= 0x4603,
+	I2400M_MT_CMD_CONNECT      	= 0x4604,
+	I2400M_MT_CMD_DISCONNECT   	= 0x4605,
+	I2400M_MT_CMD_EXIT_IDLE   	= 0x4606,
+	I2400M_MT_GET_LM_VERSION   	= 0x5201,
+	I2400M_MT_GET_DEVICE_INFO  	= 0x5202,
+	I2400M_MT_GET_LINK_STATUS  	= 0x5203,
+	I2400M_MT_GET_STATISTICS   	= 0x5204,
+	I2400M_MT_GET_STATE        	= 0x5205,
+	I2400M_MT_GET_MEDIA_STATUS	= 0x5206,
+	I2400M_MT_SET_INIT_CONFIG	= 0x5404,
+	I2400M_MT_CMD_INIT	        = 0x5601,
+	I2400M_MT_CMD_TERMINATE		= 0x5602,
+	I2400M_MT_CMD_MODE_OF_OP	= 0x5603,
+	I2400M_MT_CMD_RESET_DEVICE	= 0x5604,
+	I2400M_MT_CMD_MONITOR_CONTROL   = 0x5605,
+	I2400M_MT_CMD_ENTER_POWERSAVE   = 0x5606,
+	I2400M_MT_GET_TLS_OPERATION_RESULT = 0x6201,
+	I2400M_MT_SET_EAP_SUCCESS       = 0x6402,
+	I2400M_MT_SET_EAP_FAIL          = 0x6403,
+	I2400M_MT_SET_EAP_KEY          	= 0x6404,
+	I2400M_MT_CMD_SEND_EAP_RESPONSE = 0x6602,
+	I2400M_MT_REPORT_SCAN_RESULT    = 0xc002,
+	I2400M_MT_REPORT_STATE		= 0xd002,
+	I2400M_MT_REPORT_POWERSAVE_READY = 0xd005,
+	I2400M_MT_REPORT_EAP_REQUEST    = 0xe002,
+	I2400M_MT_REPORT_EAP_RESTART    = 0xe003,
+	I2400M_MT_REPORT_ALT_ACCEPT    	= 0xe004,
+	I2400M_MT_REPORT_KEY_REQUEST 	= 0xe005,
+};
+
+
+/*
+ * Message Ack Status codes
+ *
+ * When a message is replied-to, this status is reported.
+ */
+enum i2400m_ms {
+	I2400M_MS_DONE_OK                  = 0,
+	I2400M_MS_DONE_IN_PROGRESS         = 1,
+	I2400M_MS_INVALID_OP               = 2,
+	I2400M_MS_BAD_STATE                = 3,
+	I2400M_MS_ILLEGAL_VALUE            = 4,
+	I2400M_MS_MISSING_PARAMS           = 5,
+	I2400M_MS_VERSION_ERROR            = 6,
+	I2400M_MS_ACCESSIBILITY_ERROR      = 7,
+	I2400M_MS_BUSY                     = 8,
+	I2400M_MS_CORRUPTED_TLV            = 9,
+	I2400M_MS_UNINITIALIZED            = 10,
+	I2400M_MS_UNKNOWN_ERROR            = 11,
+	I2400M_MS_PRODUCTION_ERROR         = 12,
+	I2400M_MS_NO_RF                    = 13,
+	I2400M_MS_NOT_READY_FOR_POWERSAVE  = 14,
+	I2400M_MS_THERMAL_CRITICAL         = 15,
+	I2400M_MS_MAX
+};
+
+
+/**
+ * i2400m_tlv - enumeration of the different types of TLVs
+ *
+ * TLVs stand for type-length-value and are the header for a payload
+ * composed of almost anything. Each payload has a type assigned
+ * and a length.
+ */
+enum i2400m_tlv {
+	I2400M_TLV_L4_MESSAGE_VERSIONS = 129,
+	I2400M_TLV_SYSTEM_STATE = 141,
+	I2400M_TLV_MEDIA_STATUS = 161,
+	I2400M_TLV_RF_OPERATION = 162,
+	I2400M_TLV_RF_STATUS = 163,
+	I2400M_TLV_DEVICE_RESET_TYPE = 132,
+	I2400M_TLV_CONFIG_IDLE_PARAMETERS = 601,
+};
+
+
+struct i2400m_tlv_hdr {
+	__le16 type;
+	__le16 length;		/* payload's */
+	__u8   pl[0];
+} __attribute__((packed));
+
+
+struct i2400m_l3l4_hdr {
+	__le16 type;
+	__le16 length;		/* payload's */
+	__le16 version;
+	__le16 resv1;
+	__le16 status;
+	__le16 resv2;
+	struct i2400m_tlv_hdr pl[0];
+} __attribute__((packed));
+
+
+/**
+ * i2400m_system_state - different states of the device
+ */
+enum i2400m_system_state {
+	I2400M_SS_UNINITIALIZED = 1,
+	I2400M_SS_INIT,
+	I2400M_SS_READY,
+	I2400M_SS_SCAN,
+	I2400M_SS_STANDBY,
+	I2400M_SS_CONNECTING,
+	I2400M_SS_WIMAX_CONNECTED,
+	I2400M_SS_DATA_PATH_CONNECTED,
+	I2400M_SS_IDLE,
+	I2400M_SS_DISCONNECTING,
+	I2400M_SS_OUT_OF_ZONE,
+	I2400M_SS_SLEEPACTIVE,
+	I2400M_SS_PRODUCTION,
+	I2400M_SS_CONFIG,
+	I2400M_SS_RF_OFF,
+	I2400M_SS_RF_SHUTDOWN,
+	I2400M_SS_DEVICE_DISCONNECT,
+	I2400M_SS_MAX,
+};
+
+
+/**
+ * i2400m_tlv_system_state - report on the state of the system
+ *
+ * @state: see enum i2400m_system_state
+ */
+struct i2400m_tlv_system_state {
+	struct i2400m_tlv_hdr hdr;
+	__le32 state;
+} __attribute__((packed));
+
+
+struct i2400m_tlv_l4_message_versions {
+	struct i2400m_tlv_hdr hdr;
+	__le16 major;
+	__le16 minor;
+	__le16 branch;
+	__le16 reserved;
+} __attribute__((packed));
+
+
+struct i2400m_tlv_detailed_device_info {
+	struct i2400m_tlv_hdr hdr;
+	__u8 reserved1[400];
+	__u8 mac_address[6];
+	__u8 reserved2[2];
+} __attribute__((packed));
+
+
+enum i2400m_rf_switch_status {
+	I2400M_RF_SWITCH_ON = 1,
+	I2400M_RF_SWITCH_OFF = 2,
+};
+
+struct i2400m_tlv_rf_switches_status {
+	struct i2400m_tlv_hdr hdr;
+	__u8 sw_rf_switch;	/* 1 ON, 2 OFF */
+	__u8 hw_rf_switch;	/* 1 ON, 2 OFF */
+	__u8 reserved[2];
+} __attribute__((packed));
+
+
+enum {
+	i2400m_rf_operation_on = 1,
+	i2400m_rf_operation_off = 2
+};
+
+struct i2400m_tlv_rf_operation {
+	struct i2400m_tlv_hdr hdr;
+	__le32 status;	/* 1 ON, 2 OFF */
+} __attribute__((packed));
+
+
+enum i2400m_tlv_reset_type {
+	I2400M_RESET_TYPE_COLD = 1,
+	I2400M_RESET_TYPE_WARM
+};
+
+struct i2400m_tlv_device_reset_type {
+	struct i2400m_tlv_hdr hdr;
+	__le32 reset_type;
+} __attribute__((packed));
+
+
+struct i2400m_tlv_config_idle_parameters {
+	struct i2400m_tlv_hdr hdr;
+	__le32 idle_timeout;	/* 100 to 300000 ms [5min], 100 increments
+				 * 0 disabled */
+	__le32 idle_paging_interval;	/* frames */
+} __attribute__((packed));
+
+
+enum i2400m_media_status {
+	I2400M_MEDIA_STATUS_LINK_UP = 1,
+	I2400M_MEDIA_STATUS_LINK_DOWN,
+	I2400M_MEDIA_STATUS_LINK_RENEW,
+};
+
+struct i2400m_tlv_media_status {
+	struct i2400m_tlv_hdr hdr;
+	__le32 media_status;
+} __attribute__((packed));
+
+#endif /* #ifndef __LINUX__WIMAX__I2400M_H__ */
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 13/29] i2400m: Generic probe/disconnect, reset and message passing
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (11 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 12/29] i2400m: host/device procotol and core driver definitions Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 14/29] i2400m: linkage to the networking stack Inaky Perez-Gonzalez
                   ` (16 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements the generic probe and disconnect functions that will be
called by the USB and SDIO driver's probe/disconnect functions.

Implements the backends for the WiMAX stack's basic operations:
message passing, rfkill control and reset.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/driver.c    |  728 ++++++++++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/op-rfkill.c |  207 ++++++++++
 2 files changed, 935 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/driver.c
 create mode 100644 drivers/net/wimax/i2400m/op-rfkill.c

diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c
new file mode 100644
index 0000000..5f98047
--- /dev/null
+++ b/drivers/net/wimax/i2400m/driver.c
@@ -0,0 +1,728 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Generic probe/disconnect, reset and message passing
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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.
+ *
+ *
+ * See i2400m.h for driver documentation. This contains helpers for
+ * the driver model glue [_setup()/_release()], handling device resets
+ * [_dev_reset_handle()], and the backends for the WiMAX stack ops
+ * reset [_op_reset()] and message from user [_op_msg_from_user()].
+ *
+ * ROADMAP:
+ *
+ * i2400m_op_msg_from_user()
+ *   i2400m_msg_to_dev()
+ *   wimax_msg_to_user_send()
+ *
+ * i2400m_op_reset()
+ *   i240m->bus_reset()
+ *
+ * i2400m_dev_reset_handle()
+ *   __i2400m_dev_reset_handle()
+ *     __i2400m_dev_stop()
+ *     __i2400m_dev_start()
+ *
+ * i2400m_setup()
+ *   i2400m_bootrom_init()
+ *   register_netdev()
+ *   i2400m_dev_start()
+ *     __i2400m_dev_start()
+ *       i2400m_dev_bootstrap()
+ *       i2400m_tx_setup()
+ *       i2400m->bus_dev_start()
+ *       i2400m_check_mac_addr()
+ *   wimax_dev_add()
+ *
+ * i2400m_release()
+ *   wimax_dev_rm()
+ *   i2400m_dev_stop()
+ *     __i2400m_dev_stop()
+ *       i2400m_dev_shutdown()
+ *       i2400m->bus_dev_stop()
+ *       i2400m_tx_release()
+ *   unregister_netdev()
+ */
+#include "i2400m.h"
+#include <linux/wimax/i2400m.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+
+#define D_SUBMODULE driver
+#include "debug-levels.h"
+
+
+int i2400m_idle_mode_disabled;	/* 0 (idle mode enabled) by default */
+module_param_named(idle_mode_disabled, i2400m_idle_mode_disabled, int, 0644);
+MODULE_PARM_DESC(idle_mode_disabled,
+		 "If true, the device will not enable idle mode negotiation "
+		 "with the base station (when connected) to save power.");
+
+/**
+ * i2400m_queue_work - schedule work on a i2400m's queue
+ *
+ * @i2400m: device descriptor
+ *
+ * @fn: function to run to execute work. It gets passed a 'struct
+ *     work_struct' that is wrapped in a 'struct i2400m_work'. Once
+ *     done, you have to (1) i2400m_put(i2400m_work->i2400m) and then
+ *     (2) kfree(i2400m_work).
+ *
+ * @gfp_flags: GFP flags for memory allocation.
+ *
+ * @pl: pointer to a payload buffer that you want to pass to the _work
+ *     function. Use this to pack (for example) a struct with extra
+ *     arguments.
+ *
+ * @pl_size: size of the payload buffer.
+ *
+ * We do this quite often, so this just saves typing; allocate a
+ * wrapper for a i2400m, get a ref to it, pack arguments and launch
+ * the work.
+ *
+ * A usual workflow is:
+ *
+ * struct my_work_args {
+ *         void *something;
+ *         int whatever;
+ * };
+ * ...
+ *
+ * struct my_work_args my_args = {
+ *         .something = FOO,
+ *         .whaetever = BLAH
+ * };
+ * i2400m_queue_work(i2400m, 1, my_work_function, GFP_KERNEL,
+ *                   &args, sizeof(args))
+ *
+ * And now the work function can unpack the arguments and call the
+ * real function (or do the job itself):
+ *
+ * static
+ * void my_work_fn((struct work_struct *ws)
+ * {
+ *         struct i2400m_work *iw =
+ *	           container_of(ws, struct i2400m_work, ws);
+ *	   struct my_work_args *my_args = (void *) iw->pl;
+ *
+ *	   my_work(iw->i2400m, my_args->something, my_args->whatevert);
+ * }
+ */
+int i2400m_queue_work(struct i2400m *i2400m,
+		      void (*fn)(struct work_struct *), gfp_t gfp_flags,
+		      const void *pl, size_t pl_size)
+{
+	int result;
+	struct i2400m_work *iw;
+
+	BUG_ON(i2400m->work_queue == NULL);
+	result = -ENOMEM;
+	iw = kzalloc(sizeof(*iw) + pl_size, gfp_flags);
+	if (iw == NULL)
+		goto error_kzalloc;
+	iw->i2400m = i2400m_get(i2400m);
+	memcpy(iw->pl, pl, pl_size);
+	INIT_WORK(&iw->ws, fn);
+	result = queue_work(i2400m->work_queue, &iw->ws);
+error_kzalloc:
+	return result;
+}
+EXPORT_SYMBOL_GPL(i2400m_queue_work);
+
+
+/*
+ * Schedule i2400m's specific work on the system's queue.
+ *
+ * Used for a few cases where we really need it; otherwise, identical
+ * to i2400m_queue_work().
+ *
+ * Returns < 0 errno code on error, 1 if ok.
+ *
+ * If it returns zero, something really bad happened, as it means the
+ * works struct was already queued, but we have just allocated it, so
+ * it should not happen.
+ */
+int i2400m_schedule_work(struct i2400m *i2400m,
+			 void (*fn)(struct work_struct *), gfp_t gfp_flags)
+{
+	int result;
+	struct i2400m_work *iw;
+
+	BUG_ON(i2400m->work_queue == NULL);
+	result = -ENOMEM;
+	iw = kzalloc(sizeof(*iw), gfp_flags);
+	if (iw == NULL)
+		goto error_kzalloc;
+	iw->i2400m = i2400m_get(i2400m);
+	INIT_WORK(&iw->ws, fn);
+	result = schedule_work(&iw->ws);
+	if (result == 0)
+		result = -ENXIO;
+error_kzalloc:
+	return result;
+}
+
+
+/*
+ * WiMAX stack operation: relay a message from user space
+ *
+ * @wimax_dev: device descriptor
+ * @pipe_name: named pipe the message is for
+ * @msg_buf: pointer to the message bytes
+ * @msg_len: length of the buffer
+ * @genl_info: passed by the generic netlink layer
+ *
+ * The WiMAX stack will call this function when a message was received
+ * from user space.
+ *
+ * For the i2400m, this is an L3L4 message, as specified in
+ * include/linux/wimax/i2400m.h, and thus prefixed with a 'struct
+ * i2400m_l3l4_hdr'. Driver (and device) expect the messages to be
+ * coded in Little Endian.
+ *
+ * This function just verifies that the header declaration and the
+ * payload are consistent and then deals with it, either forwarding it
+ * to the device or procesing it locally.
+ *
+ * In the i2400m, messages are basically commands that will carry an
+ * ack, so we use i2400m_msg_to_dev() and then deliver the ack back to
+ * user space. The rx.c code might intercept the response and use it
+ * to update the driver's state, but then it will pass it on so it can
+ * be relayed back to user space.
+ *
+ * Note that asynchronous events from the device are processed and
+ * sent to user space in rx.c.
+ */
+static
+int i2400m_op_msg_from_user(struct wimax_dev *wimax_dev,
+			    const char *pipe_name,
+			    const void *msg_buf, size_t msg_len,
+			    const struct genl_info *genl_info)
+{
+	int result;
+	struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev);
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+
+	d_fnstart(4, dev, "(wimax_dev %p [i2400m %p] msg_buf %p "
+		  "msg_len %zu genl_info %p)\n", wimax_dev, i2400m,
+		  msg_buf, msg_len, genl_info);
+	ack_skb = i2400m_msg_to_dev(i2400m, msg_buf, msg_len);
+	result = PTR_ERR(ack_skb);
+	if (IS_ERR(ack_skb))
+		goto error_msg_to_dev;
+	if (unlikely(i2400m->trace_msg_from_user))
+		wimax_msg(&i2400m->wimax_dev, "trace",
+			  msg_buf, msg_len, GFP_KERNEL);
+	result = wimax_msg_send(&i2400m->wimax_dev, ack_skb);
+error_msg_to_dev:
+	d_fnend(4, dev, "(wimax_dev %p [i2400m %p] msg_buf %p msg_len %zu "
+		"genl_info %p) = %d\n", wimax_dev, i2400m, msg_buf, msg_len,
+		genl_info, result);
+	return result;
+}
+
+
+/*
+ * Context to wait for a reset to finalize
+ */
+struct i2400m_reset_ctx {
+	struct completion completion;
+	int result;
+};
+
+
+/*
+ * WiMAX stack operation: reset a device
+ *
+ * @wimax_dev: device descriptor
+ *
+ * See the documentation for wimax_reset() and wimax_dev->op_reset for
+ * the requirements of this function. The WiMAX stack guarantees
+ * serialization on calls to this function.
+ *
+ * Do a warm reset on the device; if it fails, resort to a cold reset
+ * and return -ENODEV. On successful warm reset, we need to block
+ * until it is complete.
+ *
+ * The bus-driver implementation of reset takes care of falling back
+ * to cold reset if warm fails.
+ */
+static
+int i2400m_op_reset(struct wimax_dev *wimax_dev)
+{
+	int result;
+	struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev);
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400m_reset_ctx ctx = {
+		.completion = COMPLETION_INITIALIZER_ONSTACK(ctx.completion),
+		.result = 0,
+	};
+
+	d_fnstart(4, dev, "(wimax_dev %p)\n", wimax_dev);
+	mutex_lock(&i2400m->init_mutex);
+	i2400m->reset_ctx = &ctx;
+	mutex_unlock(&i2400m->init_mutex);
+	result = i2400m->bus_reset(i2400m, I2400M_RT_WARM);
+	if (result < 0)
+		goto out;
+	result = wait_for_completion_timeout(&ctx.completion, 4*HZ);
+	if (result == 0)
+		result = -ETIMEDOUT;
+	else if (result > 0)
+		result = ctx.result;
+	/* if result < 0, pass it on */
+	mutex_lock(&i2400m->init_mutex);
+	i2400m->reset_ctx = NULL;
+	mutex_unlock(&i2400m->init_mutex);
+out:
+	d_fnend(4, dev, "(wimax_dev %p) = %d\n", wimax_dev, result);
+	return result;
+}
+
+
+/*
+ * Check the MAC address we got from boot mode is ok
+ *
+ * @i2400m: device descriptor
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ */
+static
+int i2400m_check_mac_addr(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *skb;
+	const struct i2400m_tlv_detailed_device_info *ddi;
+	struct net_device *net_dev = i2400m->wimax_dev.net_dev;
+	const unsigned char zeromac[ETH_ALEN] = { 0 };
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	skb = i2400m_get_device_info(i2400m);
+	if (IS_ERR(skb)) {
+		result = PTR_ERR(skb);
+		dev_err(dev, "Cannot verify MAC address, error reading: %d\n",
+			result);
+		goto error;
+	}
+	/* Extract MAC addresss */
+	ddi = (void *) skb->data;
+	BUILD_BUG_ON(ETH_ALEN != sizeof(ddi->mac_address));
+	d_printf(2, dev, "GET DEVICE INFO: mac addr "
+		 "%02x:%02x:%02x:%02x:%02x:%02x\n",
+		 ddi->mac_address[0], ddi->mac_address[1],
+		 ddi->mac_address[2], ddi->mac_address[3],
+		 ddi->mac_address[4], ddi->mac_address[5]);
+	if (!memcmp(net_dev->perm_addr, ddi->mac_address,
+		   sizeof(ddi->mac_address)))
+		goto ok;
+	dev_warn(dev, "warning: device reports a different MAC address "
+		 "to that of boot mode's\n");
+	dev_warn(dev, "device reports     %02x:%02x:%02x:%02x:%02x:%02x\n",
+		 ddi->mac_address[0], ddi->mac_address[1],
+		 ddi->mac_address[2], ddi->mac_address[3],
+		 ddi->mac_address[4], ddi->mac_address[5]);
+	dev_warn(dev, "boot mode reported %02x:%02x:%02x:%02x:%02x:%02x\n",
+		 net_dev->perm_addr[0], net_dev->perm_addr[1],
+		 net_dev->perm_addr[2], net_dev->perm_addr[3],
+		 net_dev->perm_addr[4], net_dev->perm_addr[5]);
+	if (!memcmp(zeromac, ddi->mac_address, sizeof(zeromac)))
+		dev_err(dev, "device reports an invalid MAC address, "
+			"not updating\n");
+	else {
+		dev_warn(dev, "updating MAC address\n");
+		net_dev->addr_len = ETH_ALEN;
+		memcpy(net_dev->perm_addr, ddi->mac_address, ETH_ALEN);
+		memcpy(net_dev->dev_addr, ddi->mac_address, ETH_ALEN);
+	}
+ok:
+	result = 0;
+	kfree_skb(skb);
+error:
+	d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
+	return result;
+}
+
+
+/**
+ * __i2400m_dev_start - Bring up driver communication with the device
+ *
+ * @i2400m: device descriptor
+ * @flags: boot mode flags
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ *
+ * Uploads firmware and brings up all the resources needed to be able
+ * to communicate with the device.
+ *
+ * TX needs to be setup before the bus-specific code (otherwise on
+ * shutdown, the bus-tx code could try to access it).
+ */
+static
+int __i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri flags)
+{
+	int result;
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+	struct net_device *net_dev = wimax_dev->net_dev;
+	struct device *dev = i2400m_dev(i2400m);
+	int times = 3;
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+retry:
+	result = i2400m_dev_bootstrap(i2400m, flags);
+	if (result < 0) {
+		dev_err(dev, "cannot bootstrap device: %d\n", result);
+		goto error_bootstrap;
+	}
+	result = i2400m_tx_setup(i2400m);
+	if (result < 0)
+		goto error_tx_setup;
+	result = i2400m->bus_dev_start(i2400m);
+	if (result < 0)
+		goto error_bus_dev_start;
+	i2400m->work_queue = create_singlethread_workqueue(wimax_dev->name);
+	if (i2400m->work_queue == NULL) {
+		result = -ENOMEM;
+		dev_err(dev, "cannot create workqueue\n");
+		goto error_create_workqueue;
+	}
+	/* At this point is ok to send commands to the device */
+	result = i2400m_check_mac_addr(i2400m);
+	if (result < 0)
+		goto error_check_mac_addr;
+	i2400m->ready = 1;
+	wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED);
+	result = i2400m_dev_initialize(i2400m);
+	if (result < 0)
+		goto error_dev_initialize;
+	/* At this point, reports will come for the device and set it
+	 * to the right state if it is different than UNINITIALIZED */
+	d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n",
+		net_dev, i2400m, result);
+	return result;
+
+error_dev_initialize:
+error_check_mac_addr:
+	destroy_workqueue(i2400m->work_queue);
+error_create_workqueue:
+	i2400m->bus_dev_stop(i2400m);
+error_bus_dev_start:
+	i2400m_tx_release(i2400m);
+error_tx_setup:
+error_bootstrap:
+	if (result == -ERESTARTSYS && times-- > 0) {
+		flags = I2400M_BRI_SOFT;
+		goto retry;
+	}
+	d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n",
+		net_dev, i2400m, result);
+	return result;
+}
+
+
+static
+int i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri bm_flags)
+{
+	int result;
+	mutex_lock(&i2400m->init_mutex);	/* Well, start the device */
+	result = __i2400m_dev_start(i2400m, bm_flags);
+	if (result >= 0)
+		i2400m->updown = 1;
+	mutex_unlock(&i2400m->init_mutex);
+	return result;
+}
+
+
+/**
+ * i2400m_dev_stop - Tear down driver communication with the device
+ *
+ * @i2400m: device descriptor
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ *
+ * Releases all the resources allocated to communicate with the device.
+ */
+static
+void __i2400m_dev_stop(struct i2400m *i2400m)
+{
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING);
+	i2400m_dev_shutdown(i2400m);
+	i2400m->ready = 0;
+	destroy_workqueue(i2400m->work_queue);
+	i2400m->bus_dev_stop(i2400m);
+	i2400m_tx_release(i2400m);
+	wimax_state_change(wimax_dev, WIMAX_ST_DOWN);
+	d_fnend(3, dev, "(i2400m %p) = 0\n", i2400m);
+}
+
+
+/*
+ * Watch out -- we only need to stop if there is a need for it. The
+ * device could have reset itself and failed to come up again (see
+ * _i2400m_dev_reset_handle()).
+ */
+static
+void i2400m_dev_stop(struct i2400m *i2400m)
+{
+	mutex_lock(&i2400m->init_mutex);
+	if (i2400m->updown) {
+		__i2400m_dev_stop(i2400m);
+		i2400m->updown = 0;
+	}
+	mutex_unlock(&i2400m->init_mutex);
+}
+
+
+/*
+ * The device has rebooted; fix up the device and the driver
+ *
+ * Tear down the driver communication with the device, reload the
+ * firmware and reinitialize the communication with the device.
+ *
+ * If someone calls a reset when the device's firmware is down, in
+ * theory we won't see it because we are not listening. However, just
+ * in case, leave the code to handle it.
+ *
+ * If there is a reset context, use it; this means someone is waiting
+ * for us to tell him when the reset operation is complete and the
+ * device is ready to rock again.
+ *
+ * NOTE: if we are in the process of bringing up or down the
+ *       communication with the device [running i2400m_dev_start() or
+ *       _stop()], don't do anything, let it fail and handle it.
+ *
+ * This function is ran always in a thread context
+ */
+static
+void __i2400m_dev_reset_handle(struct work_struct *ws)
+{
+	int result;
+	struct i2400m_work *iw = container_of(ws, struct i2400m_work, ws);
+	struct i2400m *i2400m = iw->i2400m;
+	struct device *dev = i2400m_dev(i2400m);
+	enum wimax_st wimax_state;
+	struct i2400m_reset_ctx *ctx = i2400m->reset_ctx;
+
+	d_fnstart(3, dev, "(ws %p i2400m %p)\n", ws, i2400m);
+	result = 0;
+	if (mutex_trylock(&i2400m->init_mutex) == 0) {
+		/* We are still in i2400m_dev_start() [let it fail] or
+		 * i2400m_dev_stop() [we are shutting down anyway, so
+		 * ignore it] or we are resetting somewhere else. */
+		dev_err(dev, "device rebooted\n");
+		i2400m_msg_to_dev_cancel_wait(i2400m, -ERESTARTSYS);
+		complete(&i2400m->msg_completion);
+		goto out;
+	}
+	wimax_state = wimax_state_get(&i2400m->wimax_dev);
+	if (wimax_state < WIMAX_ST_UNINITIALIZED) {
+		dev_info(dev, "device rebooted: it is down, ignoring\n");
+		goto out_unlock;	/* ifconfig up/down wasn't called */
+	}
+	dev_err(dev, "device rebooted: reinitializing driver\n");
+	__i2400m_dev_stop(i2400m);
+	i2400m->updown = 0;
+	result = __i2400m_dev_start(i2400m,
+				    I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT);
+	if (result < 0) {
+		dev_err(dev, "device reboot: cannot start the device: %d\n",
+			result);
+		result = i2400m->bus_reset(i2400m, I2400M_RT_BUS);
+		if (result >= 0)
+			result = -ENODEV;
+	} else
+		i2400m->updown = 1;
+out_unlock:
+	if (i2400m->reset_ctx) {
+		ctx->result = result;
+		complete(&ctx->completion);
+	}
+	mutex_unlock(&i2400m->init_mutex);
+out:
+	i2400m_put(i2400m);
+	kfree(iw);
+	d_fnend(3, dev, "(ws %p i2400m %p) = void\n", ws, i2400m);
+	return;
+}
+
+
+/**
+ * i2400m_dev_reset_handle - Handle a device's reset in a thread context
+ *
+ * Schedule a device reset handling out on a thread context, so it
+ * is safe to call from atomic context. We can't use the i2400m's
+ * queue as we are going to destroy it and reinitialize it as part of
+ * the driver bringup/bringup process.
+ *
+ * See __i2400m_dev_reset_handle() for details; that takes care of
+ * reinitializing the driver to handle the reset, calling into the
+ * bus-specific functions ops as needed.
+ */
+int i2400m_dev_reset_handle(struct i2400m *i2400m)
+{
+	return i2400m_schedule_work(i2400m, __i2400m_dev_reset_handle,
+				    GFP_ATOMIC);
+}
+EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle);
+
+
+/**
+ * i2400m_setup - bus-generic setup function for the i2400m device
+ *
+ * @i2400m: device descriptor (bus-specific parts have been initialized)
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ *
+ * Initializes the bus-generic parts of the i2400m driver; the
+ * bus-specific parts have been initialized, function pointers filled
+ * out by the bus-specific probe function.
+ *
+ * As well, this registers the WiMAX and net device nodes. Once this
+ * function returns, the device is operative and has to be ready to
+ * receive and send network traffic and WiMAX control operations.
+ */
+int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags)
+{
+	int result = -ENODEV;
+	struct device *dev = i2400m_dev(i2400m);
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+	struct net_device *net_dev = i2400m->wimax_dev.net_dev;
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+
+	snprintf(wimax_dev->name, sizeof(wimax_dev->name),
+		 "i2400m-%s:%s", dev->bus->name, dev->bus_id);
+
+	i2400m->bm_cmd_buf = kzalloc(I2400M_BM_CMD_BUF_SIZE, GFP_KERNEL);
+	if (i2400m->bm_cmd_buf == NULL) {
+		dev_err(dev, "cannot allocate USB command buffer\n");
+		goto error_bm_cmd_kzalloc;
+	}
+	i2400m->bm_ack_buf = kzalloc(I2400M_BM_ACK_BUF_SIZE, GFP_KERNEL);
+	if (i2400m->bm_ack_buf == NULL) {
+		dev_err(dev, "cannot allocate USB ack buffer\n");
+		goto error_bm_ack_buf_kzalloc;
+	}
+	result = i2400m_bootrom_init(i2400m, bm_flags);
+	if (result < 0) {
+		dev_err(dev, "read mac addr: bootrom init "
+			"failed: %d\n", result);
+		goto error_bootrom_init;
+	}
+	result = i2400m_read_mac_addr(i2400m);
+	if (result < 0)
+		goto error_read_mac_addr;
+
+	result = register_netdev(net_dev);	/* Okey dokey, bring it up */
+	if (result < 0) {
+		dev_err(dev, "cannot register i2400m network device: %d\n",
+			result);
+		goto error_register_netdev;
+	}
+	netif_carrier_off(net_dev);
+
+	result = i2400m_dev_start(i2400m, bm_flags);
+	if (result < 0)
+		goto error_dev_start;
+
+	i2400m->wimax_dev.op_msg_from_user = i2400m_op_msg_from_user;
+	i2400m->wimax_dev.op_rfkill_sw_toggle = i2400m_op_rfkill_sw_toggle;
+	i2400m->wimax_dev.op_reset = i2400m_op_reset;
+	result = wimax_dev_add(&i2400m->wimax_dev, net_dev);
+	if (result < 0)
+		goto error_wimax_dev_add;
+	/* User space needs to do some init stuff */
+	wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED);
+
+	/* Now setup all that requires a registered net and wimax device. */
+	result = i2400m_debugfs_add(i2400m);
+	if (result < 0) {
+		dev_err(dev, "cannot setup i2400m's debugfs: %d\n", result);
+		goto error_debugfs_setup;
+	}
+	d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
+	return result;
+
+error_debugfs_setup:
+	wimax_dev_rm(&i2400m->wimax_dev);
+error_wimax_dev_add:
+	i2400m_dev_stop(i2400m);
+error_dev_start:
+	unregister_netdev(net_dev);
+error_register_netdev:
+error_read_mac_addr:
+error_bootrom_init:
+	kfree(i2400m->bm_ack_buf);
+error_bm_ack_buf_kzalloc:
+	kfree(i2400m->bm_cmd_buf);
+error_bm_cmd_kzalloc:
+	d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
+	return result;
+}
+EXPORT_SYMBOL_GPL(i2400m_setup);
+
+
+/**
+ * i2400m_release - release the bus-generic driver resources
+ *
+ * Sends a disconnect message and undoes any setup done by i2400m_setup()
+ */
+void i2400m_release(struct i2400m *i2400m)
+{
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	netif_stop_queue(i2400m->wimax_dev.net_dev);
+
+	i2400m_debugfs_rm(i2400m);
+	wimax_dev_rm(&i2400m->wimax_dev);
+	i2400m_dev_stop(i2400m);
+	unregister_netdev(i2400m->wimax_dev.net_dev);
+	kfree(i2400m->bm_ack_buf);
+	kfree(i2400m->bm_cmd_buf);
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+}
+EXPORT_SYMBOL_GPL(i2400m_release);
+
+
+static
+int __init i2400m_driver_init(void)
+{
+	return 0;
+}
+module_init(i2400m_driver_init);
+
+static
+void __exit i2400m_driver_exit(void)
+{
+	/* for scheds i2400m_dev_reset_handle() */
+	flush_scheduled_work();
+	return;
+}
+module_exit(i2400m_driver_exit);
+
+MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
+MODULE_DESCRIPTION("Intel 2400M WiMAX networking bus-generic driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wimax/i2400m/op-rfkill.c b/drivers/net/wimax/i2400m/op-rfkill.c
new file mode 100644
index 0000000..487ec58
--- /dev/null
+++ b/drivers/net/wimax/i2400m/op-rfkill.c
@@ -0,0 +1,207 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Implement backend for the WiMAX stack rfkill support
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 WiMAX kernel stack integrates into RF-Kill and keeps the
+ * switches's status. We just need to:
+ *
+ * - report changes in the HW RF Kill switch [with
+ *   wimax_rfkill_{sw,hw}_report(), which happens when we detect those
+ *   indications coming through hardware reports]. We also do it on
+ *   initialization to let the stack know the intial HW state.
+ *
+ * - implement indications from the stack to change the SW RF Kill
+ *   switch (coming from sysfs, the wimax stack or user space).
+ */
+#include "i2400m.h"
+#include <linux/wimax/i2400m.h>
+
+
+
+#define D_SUBMODULE rfkill
+#include "debug-levels.h"
+
+/*
+ * Return true if the i2400m radio is in the requested wimax_rf_state state
+ *
+ */
+static
+int i2400m_radio_is(struct i2400m *i2400m, enum wimax_rf_state state)
+{
+	if (state == WIMAX_RF_OFF)
+		return i2400m->state == I2400M_SS_RF_OFF
+			|| i2400m->state == I2400M_SS_RF_SHUTDOWN;
+	else if (state == WIMAX_RF_ON)
+		/* state == WIMAX_RF_ON */
+		return i2400m->state != I2400M_SS_RF_OFF
+			&& i2400m->state != I2400M_SS_RF_SHUTDOWN;
+	else
+		BUG();
+}
+
+
+/*
+ * WiMAX stack operation: implement SW RFKill toggling
+ *
+ * @wimax_dev: device descriptor
+ * @skb: skb where the message has been received; skb->data is
+ *       expected to point to the message payload.
+ * @genl_info: passed by the generic netlink layer
+ *
+ * Generic Netlink will call this function when a message is sent from
+ * userspace to change the software RF-Kill switch status.
+ *
+ * This function will set the device's sofware RF-Kill switch state to
+ * match what is requested.
+ *
+ * NOTE: the i2400m has a strict state machine; we can only set the
+ *       RF-Kill switch when it is on, the HW RF-Kill is on and the
+ *       device is initialized. So we ignore errors steaming from not
+ *       being in the right state (-EILSEQ).
+ */
+int i2400m_op_rfkill_sw_toggle(struct wimax_dev *wimax_dev,
+			       enum wimax_rf_state state)
+{
+	int result;
+	struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev);
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+	struct {
+		struct i2400m_l3l4_hdr hdr;
+		struct i2400m_tlv_rf_operation sw_rf;
+	} __attribute__((packed)) *cmd;
+	char strerr[32];
+
+	d_fnstart(4, dev, "(wimax_dev %p state %d)\n", wimax_dev, state);
+
+	result = -ENOMEM;
+	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+	if (cmd == NULL)
+		goto error_alloc;
+	cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_RF_CONTROL);
+	cmd->hdr.length = sizeof(cmd->sw_rf);
+	cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION);
+	cmd->sw_rf.hdr.type = cpu_to_le16(I2400M_TLV_RF_OPERATION);
+	cmd->sw_rf.hdr.length = cpu_to_le16(sizeof(cmd->sw_rf.status));
+	switch (state) {
+	case WIMAX_RF_OFF:	/* RFKILL ON, radio OFF */
+		cmd->sw_rf.status = cpu_to_le32(2);
+		break;
+	case WIMAX_RF_ON:	/* RFKILL OFF, radio ON */
+		cmd->sw_rf.status = cpu_to_le32(1);
+		break;
+	default:
+		BUG();
+	}
+
+	ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd));
+	result = PTR_ERR(ack_skb);
+	if (IS_ERR(ack_skb)) {
+		dev_err(dev, "Failed to issue 'RF Control' command: %d\n",
+			result);
+		goto error_msg_to_dev;
+	}
+	result = i2400m_msg_check_status(wimax_msg_data(ack_skb),
+					 strerr, sizeof(strerr));
+	if (result < 0) {
+		dev_err(dev, "'RF Control' (0x%04x) command failed: %d - %s\n",
+			I2400M_MT_CMD_RF_CONTROL, result, strerr);
+		goto error_cmd;
+	}
+
+	/* Now we wait for the state to change to RADIO_OFF or RADIO_ON */
+	result = wait_event_timeout(
+		i2400m->state_wq, i2400m_radio_is(i2400m, state),
+		5 * HZ);
+	if (result == 0)
+		result = -ETIMEDOUT;
+	if (result < 0)
+		dev_err(dev, "Error waiting for device to toggle RF state: "
+			"%d\n", result);
+	result = 0;
+error_cmd:
+	kfree_skb(ack_skb);
+error_msg_to_dev:
+error_alloc:
+	d_fnend(4, dev, "(wimax_dev %p state %d) = %d\n",
+		wimax_dev, state, result);
+	return result;
+}
+
+
+/*
+ * Inform the WiMAX stack of changes in the RF Kill switches reported
+ * by the device
+ *
+ * @i2400m: device descriptor
+ * @rfss: TLV for RF Switches status; already validated
+ *
+ * NOTE: the reports on RF switch status cannot be trusted
+ *       or used until the device is in a state of RADIO_OFF
+ *       or greater.
+ */
+void i2400m_report_tlv_rf_switches_status(
+	struct i2400m *i2400m,
+	const struct i2400m_tlv_rf_switches_status *rfss)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	enum i2400m_rf_switch_status hw, sw;
+	enum wimax_st wimax_state;
+
+	sw = le32_to_cpu(rfss->sw_rf_switch);
+	hw = le32_to_cpu(rfss->hw_rf_switch);
+
+	d_fnstart(3, dev, "(i2400m %p rfss %p [hw %u sw %u])\n",
+		  i2400m, rfss, hw, sw);
+	/* We only process rw switch evens when the device has been
+	 * fully initialized */
+	wimax_state = wimax_state_get(&i2400m->wimax_dev);
+	if (wimax_state < WIMAX_ST_RADIO_OFF) {
+		d_printf(3, dev, "ignoring RF switches report, state %u\n",
+			 wimax_state);
+		goto out;
+	}
+	switch (sw) {
+	case I2400M_RF_SWITCH_ON:	/* RF Kill disabled (radio on) */
+		wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_ON);
+		break;
+	case I2400M_RF_SWITCH_OFF:	/* RF Kill enabled (radio off) */
+		wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_OFF);
+		break;
+	default:
+		dev_err(dev, "HW BUG? Unknown RF SW state 0x%x\n", sw);
+	}
+
+	switch (hw) {
+	case I2400M_RF_SWITCH_ON:	/* RF Kill disabled (radio on) */
+		wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_ON);
+		break;
+	case I2400M_RF_SWITCH_OFF:	/* RF Kill enabled (radio off) */
+		wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_OFF);
+		break;
+	default:
+		dev_err(dev, "HW BUG? Unknown RF HW state 0x%x\n", hw);
+	}
+out:
+	d_fnend(3, dev, "(i2400m %p rfss %p [hw %u sw %u]) = void\n",
+		i2400m, rfss, hw, sw);
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 14/29] i2400m: linkage to the networking stack
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (12 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 13/29] i2400m: Generic probe/disconnect, reset and message passing Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 15/29] i2400m: firmware loading and bootrom initialization Inaky Perez-Gonzalez
                   ` (15 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: wimax, greg

Implementation of the glue to the network stack so the WiMAX device
shows up as an Ethernet device.

Initially we shot for implementing a Pure IP device -- however, the
world seems to turn around Ethernet devices. Main issues were with the
ISC DHCP client and servers (as they don't understand types other than
Ethernet and Token Ring).

We proceeded to register with IANA the PureIP hw type, so that DHCP
requests could declare such. We also created patches to the main ISC
DHCP versions to support it. However, until all that permeates into
deployments, there is going to be a long time.

So we moved back to wrap Ethernet frames around the PureIP device. At
the time being this has overhead; we need to reallocate with space for
an Ethernet header. The reason is the device-to-host protocol
coalesces many network packets into a single message, so we can't
introduce Ethernet headers without overwriting valid data from other
packets.

Coming-soon versions of the firmware have this issue solved.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/netdev.c |  524 +++++++++++++++++++++++++++++++++++++
 1 files changed, 524 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/netdev.c

diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c
new file mode 100644
index 0000000..63fe708
--- /dev/null
+++ b/drivers/net/wimax/i2400m/netdev.c
@@ -0,0 +1,524 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Glue with the networking stack
+ *
+ *
+ * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 implements an ethernet device for the i2400m.
+ *
+ * We fake being an ethernet device to simplify the support from user
+ * space and from the other side. The world is (sadly) configured to
+ * take in only Ethernet devices...
+ *
+ * Because of this, currently there is an copy-each-rxed-packet
+ * overhead on the RX path. Each IP packet has to be reallocated to
+ * add an ethernet header (as there is no space in what we get from
+ * the device). This is a known drawback and coming versions of the
+ * device's firmware are being changed to add header space that can be
+ * used to insert the ethernet header without having to reallocate and
+ * copy.
+ *
+ * TX error handling is tricky; because we have to FIFO/queue the
+ * buffers for transmission (as the hardware likes it aggregated), we
+ * just give the skb to the TX subsystem and by the time it is
+ * transmitted, we have long forgotten about it. So we just don't care
+ * too much about it.
+ *
+ * Note that when the device is in idle mode with the basestation, we
+ * need to negotiate coming back up online. That involves negotiation
+ * and possible user space interaction. Thus, we defer to a workqueue
+ * to do all that. By default, we only queue a single packet and drop
+ * the rest, as potentially the time to go back from idle to normal is
+ * long.
+ *
+ * ROADMAP
+ *
+ * i2400m_open         Called on ifconfig up
+ * i2400m_stop         Called on ifconfig down
+ *
+ * i2400m_hard_start_xmit Called by the network stack to send a packet
+ *   i2400m_net_wake_tx	  Wake up device from basestation-IDLE & TX
+ *     i2400m_wake_tx_work
+ *       i2400m_cmd_exit_idle
+ *       i2400m_tx
+ *   i2400m_net_tx        TX a data frame
+ *     i2400m_tx
+ *
+ * i2400m_change_mtu      Called on ifconfig mtu XXX
+ *
+ * i2400m_tx_timeout      Called when the device times out
+ *
+ * i2400m_net_rx          Called by the RX code when a data frame is
+ *                        available.
+ * i2400m_netdev_setup    Called to setup all the netdev stuff from
+ *                        alloc_netdev.
+ */
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE netdev
+#include "debug-levels.h"
+
+enum {
+/* netdev interface */
+	/*
+	 * Out of NWG spec (R1_v1.2.2), 3.3.3 ASN Bearer Plane MTU Size
+	 *
+	 * The MTU is 1400 or less
+	 */
+	I2400M_MAX_MTU = 1400,
+	I2400M_TX_TIMEOUT = HZ,
+	I2400M_TX_QLEN = 5,
+};
+
+
+static
+int i2400m_open(struct net_device *net_dev)
+{
+	int result;
+	struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(net_dev %p [i2400m %p])\n", net_dev, i2400m);
+	if (i2400m->ready == 0) {
+		dev_err(dev, "Device is still initializing\n");
+		result = -EBUSY;
+	} else
+		result = 0;
+	d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n",
+		net_dev, i2400m, result);
+	return result;
+}
+
+
+/*
+ *
+ * On kernel versions where cancel_work_sync() didn't return anything,
+ * we rely on wake_tx_skb() being non-NULL.
+ */
+static
+int i2400m_stop(struct net_device *net_dev)
+{
+	struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(net_dev %p [i2400m %p])\n", net_dev, i2400m);
+	/* See i2400m_hard_start_xmit(), references are taken there
+	 * and here we release them if the work was still
+	 * pending. Note we can't differentiate work not pending vs
+	 * never scheduled, so the NULL check does that. */
+	if (cancel_work_sync(&i2400m->wake_tx_ws) == 0
+	    && i2400m->wake_tx_skb != NULL) {
+		unsigned long flags;
+		struct sk_buff *wake_tx_skb;
+		spin_lock_irqsave(&i2400m->tx_lock, flags);
+		wake_tx_skb = i2400m->wake_tx_skb;	/* compat help */
+		i2400m->wake_tx_skb = NULL;	/* compat help */
+		spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+		i2400m_put(i2400m);
+		kfree_skb(wake_tx_skb);
+	}
+	d_fnend(3, dev, "(net_dev %p [i2400m %p]) = 0\n", net_dev, i2400m);
+	return 0;
+}
+
+
+/*
+ * Wake up the device and transmit a held SKB, then restart the net queue
+ *
+ * When the device goes into basestation-idle mode, we need to tell it
+ * to exit that mode; it will negotiate with the base station, user
+ * space may have to intervene to rehandshake crypto and then tell us
+ * when it is ready to transmit the packet we have "queued". Still we
+ * need to give it sometime after it reports being ok.
+ *
+ * On error, there is not much we can do. If the error was on TX, we
+ * still wake the queue up to see if the next packet will be luckier.
+ *
+ * If _cmd_exit_idle() fails...well, it could be many things; most
+ * commonly it is that something else took the device out of IDLE mode
+ * (for example, the base station). In that case we get an -EILSEQ and
+ * we are just going to ignore that one. If the device is back to
+ * connected, then fine -- if it is someother state, the packet will
+ * be dropped anyway.
+ */
+void i2400m_wake_tx_work(struct work_struct *ws)
+{
+	int result;
+	struct i2400m *i2400m = container_of(ws, struct i2400m, wake_tx_ws);
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *skb = i2400m->wake_tx_skb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	skb = i2400m->wake_tx_skb;
+	i2400m->wake_tx_skb = NULL;
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+
+	d_fnstart(3, dev, "(ws %p i2400m %p skb %p)\n", ws, i2400m, skb);
+	result = -EINVAL;
+	if (skb == NULL) {
+		dev_err(dev, "WAKE&TX: skb dissapeared!\n");
+		goto out_put;
+	}
+	result = i2400m_cmd_exit_idle(i2400m);
+	if (result == -EILSEQ)
+		result = 0;
+	if (result < 0) {
+		dev_err(dev, "WAKE&TX: device didn't get out of idle: "
+			"%d\n", result);
+			goto error;
+	}
+	result = wait_event_timeout(i2400m->state_wq,
+				    i2400m->state != I2400M_SS_IDLE, 5 * HZ);
+	if (result == 0)
+		result = -ETIMEDOUT;
+	if (result < 0) {
+		dev_err(dev, "WAKE&TX: error waiting for device to exit IDLE: "
+			"%d\n", result);
+		goto error;
+	}
+	msleep(20);	/* device still needs some time or it drops it */
+	result = i2400m_tx(i2400m, skb->data, skb->len, I2400M_PT_DATA);
+	netif_wake_queue(i2400m->wimax_dev.net_dev);
+error:
+	kfree_skb(skb);	/* refcount transferred by _hard_start_xmit() */
+out_put:
+	i2400m_put(i2400m);
+	d_fnend(3, dev, "(ws %p i2400m %p skb %p) = void [%d]\n",
+		ws, i2400m, skb, result);
+}
+
+
+/*
+ * Prepare the data payload TX header
+ *
+ * The i2400m expects a 4 byte header in front of a data packet.
+ *
+ * Because we pretend to be an ethernet device, this packet comes with
+ * an ethernet header. Pull it and push our header.
+ */
+static
+void i2400m_tx_prep_header(struct sk_buff *skb)
+{
+	struct i2400m_pl_data_hdr *pl_hdr;
+	skb_pull(skb, ETH_HLEN);
+	pl_hdr = (struct i2400m_pl_data_hdr *) skb_push(skb, sizeof(*pl_hdr));
+	pl_hdr->reserved = 0;
+}
+
+
+/*
+ * TX an skb to an idle device
+ *
+ * When the device is in basestation-idle mode, we need to wake it up
+ * and then TX. So we queue a work_struct for doing so.
+ *
+ * We need to get an extra ref for the skb (so it is not dropped), as
+ * well as be careful not to queue more than one request (won't help
+ * at all). If more than one request comes or there are errors, we
+ * just drop the packets (see i2400m_hard_start_xmit()).
+ */
+static
+int i2400m_net_wake_tx(struct i2400m *i2400m, struct net_device *net_dev,
+		       struct sk_buff *skb)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	unsigned long flags;
+
+	d_fnstart(3, dev, "(skb %p net_dev %p)\n", skb, net_dev);
+	if (net_ratelimit()) {
+		d_printf(3, dev, "WAKE&NETTX: "
+			 "skb %p sending %d bytes to radio\n",
+			 skb, skb->len);
+		d_dump(4, dev, skb->data, skb->len);
+	}
+	/* We hold a ref count for i2400m and skb, so when
+	 * stopping() the device, we need to cancel that work
+	 * and if pending, release those resources. */
+	result = 0;
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	if (!work_pending(&i2400m->wake_tx_ws)) {
+		netif_stop_queue(net_dev);
+		i2400m_get(i2400m);
+		i2400m->wake_tx_skb = skb_get(skb);	/* transfer ref count */
+		i2400m_tx_prep_header(skb);
+		result = schedule_work(&i2400m->wake_tx_ws);
+		WARN_ON(result == 0);
+	}
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	if (result == 0) {
+		/* Yes, this happens even if we stopped the
+		 * queue -- blame the queue disciplines that
+		 * queue without looking -- I guess there is a reason
+		 * for that. */
+		if (net_ratelimit())
+			d_printf(1, dev, "NETTX: device exiting idle, "
+				 "dropping skb %p, queue running %d\n",
+				 skb, netif_queue_stopped(net_dev));
+		result = -EBUSY;
+	}
+	d_fnend(3, dev, "(skb %p net_dev %p) = %d\n", skb, net_dev, result);
+	return result;
+}
+
+
+/*
+ * Transmit a packet to the base station on behalf of the network stack.
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ *
+ * We need to pull the ethernet header and add the hardware header,
+ * which is currently set to all zeroes and reserved.
+ */
+static
+int i2400m_net_tx(struct i2400m *i2400m, struct net_device *net_dev,
+		  struct sk_buff *skb)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(i2400m %p net_dev %p skb %p)\n",
+		  i2400m, net_dev, skb);
+	/* FIXME: check eth hdr, only IPv4 is routed by the device as of now */
+	net_dev->trans_start = jiffies;
+	i2400m_tx_prep_header(skb);
+	d_printf(3, dev, "NETTX: skb %p sending %d bytes to radio\n",
+		 skb, skb->len);
+	d_dump(4, dev, skb->data, skb->len);
+	result = i2400m_tx(i2400m, skb->data, skb->len, I2400M_PT_DATA);
+	d_fnend(3, dev, "(i2400m %p net_dev %p skb %p) = %d\n",
+		i2400m, net_dev, skb, result);
+	return result;
+}
+
+
+/*
+ * Transmit a packet to the base station on behalf of the network stack
+ *
+ *
+ * Returns: NETDEV_TX_OK (always, even in case of error)
+ *
+ * In case of error, we just drop it. Reasons:
+ *
+ *  - we add a hw header to each skb, and if the network stack
+ *    retries, we have no way to know if that skb has it or not.
+ *
+ *  - network protocols have their own drop-recovery mechanisms
+ *
+ *  - there is not much else we can do
+ *
+ * If the device is idle, we need to wake it up; that is an operation
+ * that will sleep. See i2400m_net_wake_tx() for details.
+ */
+static
+int i2400m_hard_start_xmit(struct sk_buff *skb,
+			   struct net_device *net_dev)
+{
+	int result;
+	struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(skb %p net_dev %p)\n", skb, net_dev);
+	if (i2400m->state == I2400M_SS_IDLE)
+		result = i2400m_net_wake_tx(i2400m, net_dev, skb);
+	else
+		result = i2400m_net_tx(i2400m, net_dev, skb);
+	if (result <  0)
+		net_dev->stats.tx_dropped++;
+	else {
+		net_dev->stats.tx_packets++;
+		net_dev->stats.tx_bytes += skb->len;
+	}
+	kfree_skb(skb);
+	result = NETDEV_TX_OK;
+	d_fnend(3, dev, "(skb %p net_dev %p) = %d\n", skb, net_dev, result);
+	return result;
+}
+
+
+static
+int i2400m_change_mtu(struct net_device *net_dev, int new_mtu)
+{
+	int result;
+	struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
+	struct device *dev = i2400m_dev(i2400m);
+
+	if (new_mtu >= I2400M_MAX_MTU) {
+		dev_err(dev, "Cannot change MTU to %d (max is %d)\n",
+			new_mtu, I2400M_MAX_MTU);
+		result = -EINVAL;
+	} else {
+		net_dev->mtu = new_mtu;
+		result = 0;
+	}
+	return result;
+}
+
+
+static
+void i2400m_tx_timeout(struct net_device *net_dev)
+{
+	/*
+	 * We might want to kick the device
+	 *
+	 * There is not much we can do though, as the device requires
+	 * that we send the data aggregated. By the time we receive
+	 * this, there might be data pending to be sent or not...
+	 */
+	net_dev->stats.tx_errors++;
+	return;
+}
+
+
+/*
+ * Create a fake ethernet header
+ *
+ * For emulating an ethernet device, every received IP header has to
+ * be prefixed with an ethernet header.
+ *
+ * What we receive has (potentially) many IP packets concatenated with
+ * no ETH_HLEN bytes prefixed. Thus there is no space for an eth
+ * header.
+ *
+ * We would have to reallocate or do ugly fragment tricks in order to
+ * add it.
+ *
+ * But what we do is use the header space of the RX transaction
+ * (*msg_hdr) as we don't need it anymore; then we'll point all the
+ * data skbs there, as they share the same backing store.
+ *
+ * We only support IPv4 for v3 firmware.
+ */
+static
+void i2400m_rx_fake_eth_header(struct net_device *net_dev,
+			       void *_eth_hdr)
+{
+	struct ethhdr *eth_hdr = _eth_hdr;
+
+	memcpy(eth_hdr->h_dest, net_dev->dev_addr, sizeof(eth_hdr->h_dest));
+	memset(eth_hdr->h_source, 0, sizeof(eth_hdr->h_dest));
+	eth_hdr->h_proto = __constant_cpu_to_be16(ETH_P_IP);
+}
+
+
+/*
+ * i2400m_net_rx - pass a network packet to the stack
+ *
+ * @i2400m: device instance
+ * @skb_rx: the skb where the buffer pointed to by @buf is
+ * @i: 1 if payload is the only one
+ * @buf: pointer to the buffer containing the data
+ * @len: buffer's length
+ *
+ * We just clone the skb and set it up so that it's skb->data pointer
+ * points to "buf" and it's length.
+ *
+ * Note that if the payload is the last (or the only one) in a
+ * multi-payload message, we don't clone the SKB but just reuse it.
+ *
+ * This function is normally run from a thread context. However, we
+ * still use netif_rx() instead of netif_receive_skb() as was
+ * recommended in the mailing list. Reason is in some stress tests
+ * when sending/receiving a lot of data we seem to hit a softlock in
+ * the kernel's TCP implementation [aroudn tcp_delay_timer()]. Using
+ * netif_rx() took care of the issue.
+ *
+ * This is, of course, still open to do more research on why running
+ * with netif_receive_skb() hits this softlock. FIXME.
+ *
+ * FIXME: currently we don't do any efforts at distinguishing if what
+ * we got was an IPv4 or IPv6 header, to setup the protocol field
+ * correctly.
+ */
+void i2400m_net_rx(struct i2400m *i2400m, struct sk_buff *skb_rx,
+		   unsigned i, const void *buf, int buf_len)
+{
+	struct net_device *net_dev = i2400m->wimax_dev.net_dev;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *skb;
+
+	d_fnstart(2, dev, "(i2400m %p buf %p buf_len %d)\n",
+		  i2400m, buf, buf_len);
+	if (i) {
+		skb = skb_get(skb_rx);
+		d_printf(2, dev, "RX: reusing first payload skb %p\n", skb);
+		skb_pull(skb, buf - (void *) skb->data);
+		skb_trim(skb, (void *) skb_end_pointer(skb) - buf);
+	} else {
+		/* Yes, this is bad -- a lot of overhead -- see
+		 * comments at the top of the file */
+		skb = __netdev_alloc_skb(net_dev, buf_len, GFP_KERNEL);
+		if (skb == NULL) {
+			dev_err(dev, "NETRX: no memory to realloc skb\n");
+			net_dev->stats.rx_dropped++;
+			goto error_skb_realloc;
+		}
+		memcpy(skb_put(skb, buf_len), buf, buf_len);
+	}
+	i2400m_rx_fake_eth_header(i2400m->wimax_dev.net_dev,
+				  skb->data - ETH_HLEN);
+	skb_set_mac_header(skb, -ETH_HLEN);
+	skb->dev = i2400m->wimax_dev.net_dev;
+	skb->protocol = htons(ETH_P_IP);
+	net_dev->stats.rx_packets++;
+	net_dev->stats.rx_bytes += buf_len;
+	d_printf(3, dev, "NETRX: receiving %d bytes to network stack\n",
+		buf_len);
+	d_dump(4, dev, buf, buf_len);
+	netif_rx_ni(skb);	/* see notes in function header */
+error_skb_realloc:
+	d_fnend(2, dev, "(i2400m %p buf %p buf_len %d) = void\n",
+		i2400m, buf, buf_len);
+}
+
+
+/**
+ * i2400m_netdev_setup - Setup setup @net_dev's i2400m private data
+ *
+ * Called by alloc_netdev()
+ */
+void i2400m_netdev_setup(struct net_device *net_dev)
+{
+	d_fnstart(3, NULL, "(net_dev %p)\n", net_dev);
+	ether_setup(net_dev);
+	net_dev->mtu = I2400M_MAX_MTU;
+	net_dev->tx_queue_len = I2400M_TX_QLEN;
+	net_dev->features =
+		  NETIF_F_VLAN_CHALLENGED
+		| NETIF_F_HIGHDMA;
+	net_dev->flags =
+		IFF_NOARP		/* i2400m is apure IP device */
+		& (~IFF_BROADCAST	/* i2400m is P2P */
+		   & ~IFF_MULTICAST);
+	net_dev->watchdog_timeo = I2400M_TX_TIMEOUT;
+	net_dev->open = i2400m_open;
+	net_dev->stop = i2400m_stop;
+	net_dev->hard_start_xmit = i2400m_hard_start_xmit;
+	net_dev->change_mtu = i2400m_change_mtu;
+	net_dev->tx_timeout = i2400m_tx_timeout;
+	d_fnend(3, NULL, "(net_dev %p) = void\n", net_dev);
+}
+EXPORT_SYMBOL_GPL(i2400m_netdev_setup);
+
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 15/29] i2400m: firmware loading and bootrom initialization
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (13 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 14/29] i2400m: linkage to the networking stack Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 16/29] i2400m: RX and TX data/control paths Inaky Perez-Gonzalez
                   ` (14 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements the firmware loader (using the bus-specific driver's
backends for the actual upload). The most critical thing in here is
the piece that puts the device in boot-mode from any other
(undetermined) state, otherwise, it is just pushing the bytes from the
firmware file to the device.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/fw.c | 1095 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1095 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/fw.c

diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c
new file mode 100644
index 0000000..1d8271f
--- /dev/null
+++ b/drivers/net/wimax/i2400m/fw.c
@@ -0,0 +1,1095 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Firmware uploader
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * THE PROCEDURE
+ *
+ * (this is decribed for USB, but for SDIO is similar)
+ *
+ * The 2400m works in two modes: boot-mode or normal mode. In boot
+ * mode we can execute only a handful of commands targeted at
+ * uploading the firmware and launching it.
+ *
+ * The 2400m enters boot mode when it is first connected to the
+ * system, when it crashes and when you ask it to reboot. There are
+ * two submodes of the boot mode: signed and non-signed. Signed takes
+ * firmwares signed with a certain private key, non-signed takes any
+ * firmware. Normal hardware takes only signed firmware.
+ *
+ * Upon entrance to boot mode, the device sends a few zero length
+ * packets (ZLPs) on the notification endpoint, then a reboot barker
+ * (4 le32 words with value I2400M_{S,N}BOOT_BARKER). We ack it by
+ * sending the same barker on the bulk out endpoint. The device acks
+ * with a reboot ack barker (4 le32 words with value 0xfeedbabe) and
+ * then the device is fully rebooted. At this point we can upload the
+ * firmware.
+ *
+ * This process is accomplished by the i2400m_bootrom_init()
+ * function. All the device interaction happens through the
+ * i2400m_bm_cmd() [boot mode command]. Special return values will
+ * indicate if the device resets.
+ *
+ * After this, we read the MAC address and then (if needed)
+ * reinitialize the device. We need to read it ahead of time because
+ * in the future, we might not upload the firmware until userspace
+ * 'ifconfig up's the device.
+ *
+ * We can then upload the firmware file. The file is composed of a BCF
+ * header (basic data, keys and signatures) and a list of write
+ * commands and payloads. We first upload the header
+ * [i2400m_dnload_init()] and then pass the commands and payloads
+ * verbatim to the i2400m_bm_cmd() function
+ * [i2400m_dnload_bcf()]. Then we tell the device to jump to the new
+ * firmware [i2400m_dnload_finalize()].
+ *
+ * Once firmware is uploaded, we are good to go :)
+ *
+ * When we don't know in which mode we are, we first try by sending a
+ * warm reset request that will take us to boot-mode. If we time out
+ * waiting for a reboot barker, that means maybe we are already in
+ * boot mode, so we send a reboot barker.
+ *
+ * COMMAND EXECUTION
+ *
+ * This code (and process) is single threaded; for executing commands,
+ * we post a URB to the notification endpoint, post the command, wait
+ * for data on the notification buffer. We don't need to worry about
+ * others as we know we are the only ones in there.
+ *
+ * BACKEND IMPLEMENTATION
+ *
+ * This code is bus-generic; the bus-specific driver provides back end
+ * implementations to send a boot mode command to the device and to
+ * read an acknolwedgement from it (or an asynchronous notification)
+ * from it.
+ *
+ * ROADMAP
+ *
+ * i2400m_dev_bootstrap               Called by __i2400m_dev_start()
+ *   request_firmware
+ *   i2400m_fw_check
+ *   i2400m_fw_dnload
+ *   release_firmware
+ *
+ * i2400m_fw_dnload
+ *   i2400m_bootrom_init
+ *     i2400m_bm_cmd
+ *     i2400m->bus_reset
+ *   i2400m_dnload_init
+ *     i2400m_dnload_init_signed
+ *     i2400m_dnload_init_nonsigned
+ *       i2400m_download_chunk
+ *         i2400m_bm_cmd
+ *   i2400m_dnload_bcf
+ *     i2400m_bm_cmd
+ *   i2400m_dnload_finalize
+ *     i2400m_bm_cmd
+ *
+ * i2400m_bm_cmd
+ *   i2400m->bus_bm_cmd_send()
+ *   i2400m->bus_bm_wait_for_ack
+ *   __i2400m_bm_ack_verify
+ *
+ * i2400m_bm_cmd_prepare              Used by bus-drivers to prep
+ *                                    commands before sending
+ */
+#include <linux/firmware.h>
+#include <linux/sched.h>
+#include <linux/usb.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE fw
+#include "debug-levels.h"
+
+
+static const __le32 i2400m_ACK_BARKER[4] = {
+	__constant_cpu_to_le32(I2400M_ACK_BARKER),
+	__constant_cpu_to_le32(I2400M_ACK_BARKER),
+	__constant_cpu_to_le32(I2400M_ACK_BARKER),
+	__constant_cpu_to_le32(I2400M_ACK_BARKER)
+};
+
+
+/**
+ * Prepare a boot-mode command for delivery
+ *
+ * @cmd: pointer to bootrom header to prepare
+ *
+ * Computes checksum if so needed. After calling this function, DO NOT
+ * modify the command or header as the checksum won't work anymore.
+ *
+ * We do it from here because some times we cannot do it in the
+ * original context the command was sent (it is a const), so when we
+ * copy it to our staging buffer, we add the checksum there.
+ */
+void i2400m_bm_cmd_prepare(struct i2400m_bootrom_header *cmd)
+{
+	if (i2400m_brh_get_use_checksum(cmd)) {
+		int i;
+		u32 checksum = 0;
+		const u32 *checksum_ptr = (void *) cmd->payload;
+		for (i = 0; i < cmd->data_size / 4; i++)
+			checksum += cpu_to_le32(*checksum_ptr++);
+		checksum += cmd->command + cmd->target_addr + cmd->data_size;
+		cmd->block_checksum = cpu_to_le32(checksum);
+	}
+}
+EXPORT_SYMBOL_GPL(i2400m_bm_cmd_prepare);
+
+
+/*
+ * Verify the ack data received
+ *
+ * Given a reply to a boot mode command, chew it and verify everything
+ * is ok.
+ *
+ * @opcode: opcode which generated this ack. For error messages.
+ * @ack: pointer to ack data we received
+ * @ack_size: size of that data buffer
+ * @flags: I2400M_BM_CMD_* flags we called the command with.
+ *
+ * Way too long function -- maybe it should be further split
+ */
+static
+ssize_t __i2400m_bm_ack_verify(struct i2400m *i2400m, int opcode,
+			       struct i2400m_bootrom_header *ack,
+			       size_t ack_size, int flags)
+{
+	ssize_t result = -ENOMEM;
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(8, dev, "(i2400m %p opcode %d ack %p size %zu)\n",
+		  i2400m, opcode, ack, ack_size);
+	if (ack_size < sizeof(*ack)) {
+		result = -EIO;
+		dev_err(dev, "boot-mode cmd %d: HW BUG? notification didn't "
+			"return enough data (%zu bytes vs %zu expected)\n",
+			opcode, ack_size, sizeof(*ack));
+		goto error_ack_short;
+	}
+	if (ack_size == sizeof(i2400m_NBOOT_BARKER)
+		 && memcmp(ack, i2400m_NBOOT_BARKER, sizeof(*ack)) == 0) {
+		result = -ERESTARTSYS;
+		i2400m->sboot = 0;
+		d_printf(6, dev, "boot-mode cmd %d: "
+			 "HW non-signed boot barker\n", opcode);
+		goto error_reboot;
+	}
+	if (ack_size == sizeof(i2400m_SBOOT_BARKER)
+		 && memcmp(ack, i2400m_SBOOT_BARKER, sizeof(*ack)) == 0) {
+		result = -ERESTARTSYS;
+		i2400m->sboot = 1;
+		d_printf(6, dev, "boot-mode cmd %d: HW signed reboot barker\n",
+			 opcode);
+		goto error_reboot;
+	}
+	if (ack_size == sizeof(i2400m_ACK_BARKER)
+		 && memcmp(ack, i2400m_ACK_BARKER, sizeof(*ack)) == 0) {
+		result = -EISCONN;
+		d_printf(3, dev, "boot-mode cmd %d: HW reboot ack barker\n",
+			 opcode);
+		goto error_reboot_ack;
+	}
+	result = 0;
+	if (flags & I2400M_BM_CMD_RAW)
+		goto out_raw;
+	ack->data_size = le32_to_cpu(ack->data_size);
+	ack->target_addr = le32_to_cpu(ack->target_addr);
+	ack->block_checksum = le32_to_cpu(ack->block_checksum);
+	d_printf(5, dev, "boot-mode cmd %d: notification for opcode %u "
+		 "response %u csum %u rr %u da %u\n",
+		 opcode, i2400m_brh_get_opcode(ack),
+		 i2400m_brh_get_response(ack),
+		 i2400m_brh_get_use_checksum(ack),
+		 i2400m_brh_get_response_required(ack),
+		 i2400m_brh_get_direct_access(ack));
+	result = -EIO;
+	if (i2400m_brh_get_signature(ack) != 0xcbbc) {
+		dev_err(dev, "boot-mode cmd %d: HW BUG? wrong signature "
+			"0x%04x\n", opcode, i2400m_brh_get_signature(ack));
+		goto error_ack_signature;
+	}
+	if (opcode != -1 && opcode != i2400m_brh_get_opcode(ack)) {
+		dev_err(dev, "boot-mode cmd %d: HW BUG? "
+			"received response for opcode %u, expected %u\n",
+			opcode, i2400m_brh_get_opcode(ack), opcode);
+		goto error_ack_opcode;
+	}
+	if (i2400m_brh_get_response(ack) != 0) {	/* failed? */
+		dev_err(dev, "boot-mode cmd %d: error; hw response %u\n",
+			opcode, i2400m_brh_get_response(ack));
+		goto error_ack_failed;
+	}
+	if (ack_size < ack->data_size + sizeof(*ack)) {
+		dev_err(dev, "boot-mode cmd %d: SW BUG "
+			"driver provided only %zu bytes for %zu bytes "
+			"of data\n", opcode, ack_size,
+			(size_t) le32_to_cpu(ack->data_size) + sizeof(*ack));
+		goto error_ack_short_buffer;
+	}
+	result = ack_size;
+	/* Don't you love this stack of empty targets? Well, I don't
+	 * either, but it helps track exactly who comes in here and
+	 * why :) */
+error_ack_short_buffer:
+error_ack_failed:
+error_ack_opcode:
+error_ack_signature:
+out_raw:
+error_reboot_ack:
+error_reboot:
+error_ack_short:
+	d_fnend(8, dev, "(i2400m %p opcode %d ack %p size %zu) = %d\n",
+		i2400m, opcode, ack, ack_size, (int) result);
+	return result;
+}
+
+
+/**
+ * i2400m_bm_cmd - Execute a boot mode command
+ *
+ * @cmd: buffer containing the command data (pointing at the header).
+ *     This data can be ANYWHERE (for USB, we will copy it to an
+ *     specific buffer). Make sure everything is in proper little
+ *     endian.
+ *
+ *     A raw buffer can be also sent, just cast it and set flags to
+ *     I2400M_BM_CMD_RAW.
+ *
+ *     This function will generate a checksum for you if the
+ *     checksum bit in the command is set (unless I2400M_BM_CMD_RAW
+ *     is set).
+ *
+ *     You can use the i2400m->bm_cmd_buf to stage your commands and
+ *     send them.
+ *
+ *     If NULL, no command is sent (we just wait for an ack).
+ *
+ * @cmd_size: size of the command. Will be auto padded to the
+ *     bus-specific drivers padding requirements.
+ *
+ * @ack: buffer where to place the acknowledgement. If it is a regular
+ *     command response, all fields will be returned with the right,
+ *     native endianess.
+ *
+ *     You *cannot* use i2400m->bm_ack_buf for this buffer.
+ *
+ * @ack_size: size of @ack, 16 aligned; you need to provide at least
+ *     sizeof(*ack) bytes and then enough to contain the return data
+ *     from the command
+ *
+ * @flags: see I2400M_BM_CMD_* above.
+ *
+ * @returns: bytes received by the notification; if < 0, an errno code
+ *     denoting an error or:
+ *
+ *     -ERESTARTSYS  The device has rebooted
+ *
+ * Executes a boot-mode command and waits for a response, doing basic
+ * validation on it; if a zero length response is received, it retries
+ * waiting for a response until a non-zero one is received (timing out
+ * after %I2400M_BOOT_RETRIES retries).
+ */
+static
+ssize_t i2400m_bm_cmd(struct i2400m *i2400m,
+		      const struct i2400m_bootrom_header *cmd, size_t cmd_size,
+		      struct i2400m_bootrom_header *ack, size_t ack_size,
+		      int flags)
+{
+	ssize_t result = -ENOMEM, rx_bytes;
+	struct device *dev = i2400m_dev(i2400m);
+	int opcode = cmd == NULL ? -1 : i2400m_brh_get_opcode(cmd);
+
+	d_fnstart(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu)\n",
+		  i2400m, cmd, cmd_size, ack, ack_size);
+	BUG_ON(ack_size < sizeof(*ack));
+	BUG_ON(i2400m->boot_mode == 0);
+
+	if (cmd != NULL) {		/* send the command */
+		memcpy(i2400m->bm_cmd_buf, cmd, cmd_size);
+		result = i2400m->bus_bm_cmd_send(i2400m, cmd, cmd_size, flags);
+		if (result < 0)
+			goto error_cmd_send;
+		if ((flags & I2400M_BM_CMD_RAW) == 0)
+			d_printf(5, dev,
+				 "boot-mode cmd %d csum %u rr %u da %u: "
+				 "addr 0x%04x size %u block csum 0x%04x\n",
+				 opcode, i2400m_brh_get_use_checksum(cmd),
+				 i2400m_brh_get_response_required(cmd),
+				 i2400m_brh_get_direct_access(cmd),
+				 cmd->target_addr, cmd->data_size,
+				 cmd->block_checksum);
+	}
+	result = i2400m->bus_bm_wait_for_ack(i2400m, ack, ack_size);
+	if (result < 0) {
+		dev_err(dev, "boot-mode cmd %d: error waiting for an ack: %d\n",
+			opcode, (int) result);	/* bah, %zd doesn't work */
+		goto error_wait_for_ack;
+	}
+	rx_bytes = result;
+	/* verify the ack and read more if neccessary [result is the
+	 * final amount of bytes we get in the ack]  */
+	result = __i2400m_bm_ack_verify(i2400m, opcode, ack, ack_size, flags);
+	if (result < 0)
+		goto error_bad_ack;
+	/* Don't you love this stack of empty targets? Well, I don't
+	 * either, but it helps track exactly who comes in here and
+	 * why :) */
+	result = rx_bytes;
+error_bad_ack:
+error_wait_for_ack:
+error_cmd_send:
+	d_fnend(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu) = %d\n",
+		i2400m, cmd, cmd_size, ack, ack_size, (int) result);
+	return result;
+}
+
+
+/**
+ * i2400m_download_chunk - write a single chunk of data to the device's memory
+ *
+ * @i2400m: device descriptor
+ * @buf: the buffer to write
+ * @buf_len: length of the buffer to write
+ * @addr: address in the device memory space
+ * @direct: bootrom write mode
+ * @do_csum: should a checksum validation be performed
+ */
+static int i2400m_download_chunk(struct i2400m *i2400m, const void *chunk,
+				 size_t __chunk_len, unsigned long addr,
+				 unsigned int direct, unsigned int do_csum)
+{
+	int ret;
+	size_t chunk_len = ALIGN(__chunk_len, I2400M_PL_PAD);
+	struct device *dev = i2400m_dev(i2400m);
+	struct {
+		struct i2400m_bootrom_header cmd;
+		u8 cmd_payload[chunk_len];
+	} __attribute__((packed)) *buf;
+	struct i2400m_bootrom_header ack;
+
+	d_fnstart(5, dev, "(i2400m %p chunk %p __chunk_len %zu addr 0x%08lx "
+		  "direct %u do_csum %u)\n", i2400m, chunk, __chunk_len,
+		  addr, direct, do_csum);
+	buf = i2400m->bm_cmd_buf;
+	memcpy(buf->cmd_payload, chunk, __chunk_len);
+	memset(buf->cmd_payload + __chunk_len, 0xad, chunk_len - __chunk_len);
+
+	buf->cmd.command = i2400m_brh_command(I2400M_BRH_WRITE,
+					      __chunk_len & 0x3 ? 0 : do_csum,
+					      __chunk_len & 0xf ? 0 : direct);
+	buf->cmd.target_addr = cpu_to_le32(addr);
+	buf->cmd.data_size = cpu_to_le32(__chunk_len);
+	ret = i2400m_bm_cmd(i2400m, &buf->cmd, sizeof(buf->cmd) + chunk_len,
+			    &ack, sizeof(ack), 0);
+	if (ret >= 0)
+		ret = 0;
+	d_fnend(5, dev, "(i2400m %p chunk %p __chunk_len %zu addr 0x%08lx "
+		"direct %u do_csum %u) = %d\n", i2400m, chunk, __chunk_len,
+		addr, direct, do_csum, ret);
+	return ret;
+}
+
+
+/*
+ * Download a BCF file's sections to the device
+ *
+ * @i2400m: device descriptor
+ * @bcf: pointer to firmware data (followed by the payloads). Assumed
+ *       verified and consistent.
+ * @bcf_len: length (in bytes) of the @bcf buffer.
+ *
+ * Returns: < 0 errno code on error or the offset to the jump instruction.
+ *
+ * Given a BCF file, downloads each section (a command and a payload)
+ * to the device's address space. Actually, it just executes each
+ * command i the BCF file.
+ *
+ * The section size has to be aligned to 4 bytes AND the padding has
+ * to be taken from the firmware file, as the signature takes it into
+ * account.
+ */
+static
+ssize_t i2400m_dnload_bcf(struct i2400m *i2400m,
+			  const struct i2400m_bcf_hdr *bcf, size_t bcf_len)
+{
+	ssize_t ret;
+	struct device *dev = i2400m_dev(i2400m);
+	size_t offset,		/* iterator offset */
+		data_size,	/* Size of the data payload */
+		section_size,	/* Size of the whole section (cmd + payload) */
+		section = 1;
+	const struct i2400m_bootrom_header *bh;
+	struct i2400m_bootrom_header ack;
+
+	d_fnstart(3, dev, "(i2400m %p bcf %p bcf_len %zu)\n",
+		  i2400m, bcf, bcf_len);
+	/* Iterate over the command blocks in the BCF file that start
+	 * after the header */
+	offset = le32_to_cpu(bcf->header_len) * sizeof(u32);
+	while (1) {	/* start sending the file */
+		bh = (void *) bcf + offset;
+		data_size = le32_to_cpu(bh->data_size);
+		section_size = ALIGN(sizeof(*bh) + data_size, 4);
+		d_printf(7, dev,
+			 "downloading section #%zu (@%zu %zu B) to 0x%08x\n",
+			 section, offset, sizeof(*bh) + data_size,
+			 le32_to_cpu(bh->target_addr));
+		if (i2400m_brh_get_opcode(bh) == I2400M_BRH_SIGNED_JUMP) {
+			/* Secure boot needs to stop here */
+			d_printf(5, dev,  "signed jump found @%zu\n", offset);
+			break;
+		}
+		if (offset + section_size == bcf_len)
+			/* Non-secure boot stops here */
+			break;
+		if (offset + section_size > bcf_len) {
+			dev_err(dev, "fw %s: bad section #%zu, "
+				"end (@%zu) beyond EOF (@%zu)\n",
+				i2400m->bus_fw_name, section,
+				offset + section_size,  bcf_len);
+			ret = -EINVAL;
+			goto error_section_beyond_eof;
+		}
+		__i2400m_msleep(20);
+		ret = i2400m_bm_cmd(i2400m, bh, section_size,
+				    &ack, sizeof(ack), I2400M_BM_CMD_RAW);
+		if (ret < 0) {
+			dev_err(dev, "fw %s: section #%zu (@%zu %zu B) "
+				"failed %d\n", i2400m->bus_fw_name, section,
+				offset, sizeof(*bh) + data_size, (int) ret);
+			goto error_send;
+		}
+		offset += section_size;
+		section++;
+	}
+	ret = offset;
+error_section_beyond_eof:
+error_send:
+	d_fnend(3, dev, "(i2400m %p bcf %p bcf_len %zu) = %d\n",
+		i2400m, bcf, bcf_len, (int) ret);
+	return ret;
+}
+
+
+/*
+ * Do the final steps of uploading firmware
+ *
+ * Depending on the boot mode (signed vs non-signed), different
+ * actions need to be taken.
+ */
+static
+int i2400m_dnload_finalize(struct i2400m *i2400m,
+			   const struct i2400m_bcf_hdr *bcf, size_t offset)
+{
+	int ret = 0;
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400m_bootrom_header *cmd, ack;
+	struct {
+		struct i2400m_bootrom_header cmd;
+		u8 cmd_pl[0];
+	} __attribute__((packed)) *cmd_buf;
+	size_t signature_block_offset, signature_block_size;
+
+	d_fnstart(3, dev, "offset %zu\n", offset);
+	cmd = (void *) bcf + offset;
+	if (i2400m->sboot == 0) {
+		struct i2400m_bootrom_header jump_ack;
+		d_printf(3, dev, "unsecure boot, jumping to 0x%08x\n",
+			le32_to_cpu(cmd->target_addr));
+		i2400m_brh_set_opcode(cmd, I2400M_BRH_JUMP);
+		cmd->data_size = 0;
+		ret = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd),
+				    &jump_ack, sizeof(jump_ack), 0);
+	} else {
+		d_printf(3, dev, "secure boot, jumping to 0x%08x\n",
+			 le32_to_cpu(cmd->target_addr));
+		cmd_buf = i2400m->bm_cmd_buf;
+		memcpy(&cmd_buf->cmd, cmd, sizeof(*cmd));
+		signature_block_offset =
+			sizeof(*bcf)
+			+ le32_to_cpu(bcf->key_size) * sizeof(u32)
+			+ le32_to_cpu(bcf->exponent_size) * sizeof(u32);
+		signature_block_size =
+			le32_to_cpu(bcf->modulus_size) * sizeof(u32);
+		memcpy(cmd_buf->cmd_pl, (void *) bcf + signature_block_offset,
+		       signature_block_size);
+		ret = i2400m_bm_cmd(i2400m, &cmd_buf->cmd,
+				    sizeof(cmd_buf->cmd) + signature_block_size,
+				    &ack, sizeof(ack), I2400M_BM_CMD_RAW);
+	}
+	d_fnend(3, dev, "returning %d\n", ret);
+	return ret;
+}
+
+
+/**
+ * i2400m_bootrom_init - Reboots a powered device into boot mode
+ *
+ * @i2400m: device descriptor
+ * @flags:
+ *      I2400M_BRI_SOFT: a reboot notification has been seen
+ *          already, so don't wait for it.
+ *
+ *      I2400M_BRI_NO_REBOOT: Don't send a reboot command, but wait
+ *          for a reboot barker notification. This is a one shot; if
+ *          the state machine needs to send a reboot command it will.
+ *
+ * Returns:
+ *
+ *     < 0 errno code on error, 0 if ok.
+ *
+ *     i2400m->sboot set to 0 for unsecure boot process, 1 for secure
+ *     boot process.
+ *
+ * Description:
+ *
+ * Tries hard enough to put the device in boot-mode. There are two
+ * main phases to this:
+ *
+ * a. (1) send a reboot command and (2) get a reboot barker
+ * b. (1) ack the reboot sending a reboot barker and (2) getting an
+ *        ack barker in return
+ *
+ * We want to skip (a) in some cases [soft]. The state machine is
+ * horrible, but it is basically: on each phase, send what has to be
+ * sent (if any), wait for the answer and act on the answer. We might
+ * have to backtrack and retry, so we keep a max tries counter for
+ * that.
+ *
+ * If we get a timeout after sending a warm reset, we do it again.
+ */
+int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400m_bootrom_header *cmd;
+	struct i2400m_bootrom_header ack;
+	int count = I2400M_BOOT_RETRIES;
+	int ack_timeout_cnt = 1;
+
+	BUILD_BUG_ON(sizeof(*cmd) != sizeof(i2400m_NBOOT_BARKER));
+	BUILD_BUG_ON(sizeof(ack) != sizeof(i2400m_ACK_BARKER));
+
+	d_fnstart(4, dev, "(i2400m %p flags 0x%08x)\n", i2400m, flags);
+	result = -ENOMEM;
+	cmd = i2400m->bm_cmd_buf;
+	if (flags & I2400M_BRI_SOFT)
+		goto do_reboot_ack;
+do_reboot:
+	if (--count < 0)
+		goto error_timeout;
+	d_printf(4, dev, "device reboot: reboot command [%d # left]\n",
+		 count);
+	if ((flags & I2400M_BRI_NO_REBOOT) == 0)
+		i2400m->bus_reset(i2400m, I2400M_RT_WARM);
+	result = i2400m_bm_cmd(i2400m, NULL, 0, &ack, sizeof(ack),
+			       I2400M_BM_CMD_RAW);
+	flags &= ~I2400M_BRI_NO_REBOOT;
+	switch (result) {
+	case -ERESTARTSYS:
+		d_printf(4, dev, "device reboot: got reboot barker\n");
+		break;
+	case -EISCONN:	/* we don't know how it got here...but we follow it */
+		d_printf(4, dev, "device reboot: got ack barker - whatever\n");
+		goto do_reboot;
+	case -ETIMEDOUT:	/* device has timed out, we might be in boot
+				 * mode already and expecting an ack, let's try
+				 * that */
+		dev_info(dev, "warm reset timed out, trying an ack\n");
+		goto do_reboot_ack;
+	case -EPROTO:
+	case -ESHUTDOWN:	/* dev is gone */
+	case -EINTR:		/* user cancelled */
+		goto error_dev_gone;
+	default:
+		dev_err(dev, "device reboot: error %d while waiting "
+			"for reboot barker - rebooting\n", result);
+		goto do_reboot;
+	}
+	/* At this point we ack back with 4 REBOOT barkers and expect
+	 * 4 ACK barkers. This is ugly, as we send a raw command --
+	 * hence the cast. _bm_cmd() will catch the reboot ack
+	 * notification and report it as -EISCONN. */
+do_reboot_ack:
+	d_printf(4, dev, "device reboot ack: sending ack [%d # left]\n", count);
+	if (i2400m->sboot == 0)
+		memcpy(cmd, i2400m_NBOOT_BARKER,
+		       sizeof(i2400m_NBOOT_BARKER));
+	else
+		memcpy(cmd, i2400m_SBOOT_BARKER,
+		       sizeof(i2400m_SBOOT_BARKER));
+	result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd),
+			       &ack, sizeof(ack), I2400M_BM_CMD_RAW);
+	switch (result) {
+	case -ERESTARTSYS:
+		d_printf(4, dev, "reboot ack: got reboot barker - retrying\n");
+		if (--count < 0)
+			goto error_timeout;
+		goto do_reboot_ack;
+	case -EISCONN:
+		d_printf(4, dev, "reboot ack: got ack barker - good\n");
+		break;
+	case -ETIMEDOUT:	/* no response, maybe it is the other type? */
+		if (ack_timeout_cnt-- >= 0) {
+			d_printf(4, dev, "reboot ack timedout: "
+				 "trying the other type?\n");
+			i2400m->sboot = !i2400m->sboot;
+			goto do_reboot_ack;
+		} else {
+			dev_err(dev, "reboot ack timedout too long: "
+				"trying reboot\n");
+			goto do_reboot;
+		}
+		break;
+	case -EPROTO:
+	case -ESHUTDOWN:	/* dev is gone */
+		goto error_dev_gone;
+	default:
+		dev_err(dev, "device reboot ack: error %d while waiting for "
+			"reboot ack barker - rebooting\n", result);
+		goto do_reboot;
+	}
+	d_printf(2, dev, "device reboot ack: got ack barker - boot done\n");
+	result = 0;
+exit_timeout:
+error_dev_gone:
+	d_fnend(4, dev, "(i2400m %p flags 0x%08x) = %d\n",
+		i2400m, flags, result);
+	return result;
+
+error_timeout:
+	dev_err(dev, "Timed out waiting for reboot ack, resetting\n");
+	i2400m->bus_reset(i2400m, I2400M_RT_BUS);
+	result = -ETIMEDOUT;
+	goto exit_timeout;
+}
+
+
+/*
+ * Read the MAC addr
+ *
+ * The position this function reads is fixed in device memory and
+ * always available, even without firmware.
+ *
+ * Note we specify we want to read only six bytes, but provide space
+ * for 16, as we always get it rounded up.
+ */
+int i2400m_read_mac_addr(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct net_device *net_dev = i2400m->wimax_dev.net_dev;
+	struct i2400m_bootrom_header *cmd;
+	struct {
+		struct i2400m_bootrom_header ack;
+		u8 ack_pl[16];
+	} __attribute__((packed)) ack_buf;
+
+	d_fnstart(5, dev, "(i2400m %p)\n", i2400m);
+	cmd = i2400m->bm_cmd_buf;
+	cmd->command = i2400m_brh_command(I2400M_BRH_READ, 0, 1);
+	cmd->target_addr = cpu_to_le32(0x00203fe8);
+	cmd->data_size = cpu_to_le32(6);
+	result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd),
+			       &ack_buf.ack, sizeof(ack_buf), 0);
+	if (result < 0) {
+		dev_err(dev, "BM: read mac addr failed: %d\n", result);
+		goto error_read_mac;
+	}
+	d_printf(2, dev,
+		 "mac addr is %02x:%02x:%02x:%02x:%02x:%02x\n",
+		 ack_buf.ack_pl[0], ack_buf.ack_pl[1],
+		 ack_buf.ack_pl[2], ack_buf.ack_pl[3],
+		 ack_buf.ack_pl[4], ack_buf.ack_pl[5]);
+	if (i2400m->bus_bm_mac_addr_impaired == 1) {
+		ack_buf.ack_pl[0] = 0x00;
+		ack_buf.ack_pl[1] = 0x16;
+		ack_buf.ack_pl[2] = 0xd3;
+		get_random_bytes(&ack_buf.ack_pl[3], 3);
+		dev_err(dev, "BM is MAC addr impaired, faking MAC addr to "
+			"mac addr is %02x:%02x:%02x:%02x:%02x:%02x\n",
+			ack_buf.ack_pl[0], ack_buf.ack_pl[1],
+			ack_buf.ack_pl[2], ack_buf.ack_pl[3],
+			ack_buf.ack_pl[4], ack_buf.ack_pl[5]);
+		result = 0;
+	}
+	net_dev->addr_len = ETH_ALEN;
+	memcpy(net_dev->perm_addr, ack_buf.ack_pl, ETH_ALEN);
+	memcpy(net_dev->dev_addr, ack_buf.ack_pl, ETH_ALEN);
+error_read_mac:
+	d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, result);
+	return result;
+}
+
+
+/*
+ * Initialize a non signed boot
+ *
+ * This implies sending some magic values to the device's memory. Note
+ * we convert the values to little endian in the same array
+ * declaration.
+ */
+static
+int i2400m_dnload_init_nonsigned(struct i2400m *i2400m)
+{
+#define POKE(a, d) {					\
+	.address = __constant_cpu_to_le32(a),		\
+	.data = __constant_cpu_to_le32(d)		\
+}
+	static const struct {
+		__le32 address;
+		__le32 data;
+	} i2400m_pokes[] = {
+		POKE(0x081A58, 0xA7810230),
+		POKE(0x080040, 0x00000000),
+		POKE(0x080048, 0x00000082),
+		POKE(0x08004C, 0x0000081F),
+		POKE(0x080054, 0x00000085),
+		POKE(0x080058, 0x00000180),
+		POKE(0x08005C, 0x00000018),
+		POKE(0x080060, 0x00000010),
+		POKE(0x080574, 0x00000001),
+		POKE(0x080550, 0x00000005),
+		POKE(0xAE0000, 0x00000000),
+	};
+#undef POKE
+	unsigned i;
+	int ret;
+	struct device *dev = i2400m_dev(i2400m);
+
+	dev_warn(dev, "WARNING!!! non-signed boot UNTESTED PATH!\n");
+
+	d_fnstart(5, dev, "(i2400m %p)\n", i2400m);
+	for (i = 0; i < ARRAY_SIZE(i2400m_pokes); i++) {
+		ret = i2400m_download_chunk(i2400m, &i2400m_pokes[i].data,
+					    sizeof(i2400m_pokes[i].data),
+					    i2400m_pokes[i].address, 1, 1);
+		if (ret < 0)
+			break;
+	}
+	d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret);
+	return ret;
+}
+
+
+/*
+ * Initialize the signed boot process
+ *
+ * @i2400m: device descriptor
+ *
+ * @bcf_hdr: pointer to the firmware header; assumes it is fully in
+ *     memory (it has gone through basic validation).
+ *
+ * Returns: 0 if ok, < 0 errno code on error, -ERESTARTSYS if the hw
+ *     rebooted.
+ *
+ * This writes the firmware BCF header to the device using the
+ * HASH_PAYLOAD_ONLY command.
+ */
+static
+int i2400m_dnload_init_signed(struct i2400m *i2400m,
+			      const struct i2400m_bcf_hdr *bcf_hdr)
+{
+	int ret;
+	struct device *dev = i2400m_dev(i2400m);
+	struct {
+		struct i2400m_bootrom_header cmd;
+		struct i2400m_bcf_hdr cmd_pl;
+	} __attribute__((packed)) *cmd_buf;
+	struct i2400m_bootrom_header ack;
+
+	d_fnstart(5, dev, "(i2400m %p bcf_hdr %p)\n", i2400m, bcf_hdr);
+	cmd_buf = i2400m->bm_cmd_buf;
+	cmd_buf->cmd.command =
+		i2400m_brh_command(I2400M_BRH_HASH_PAYLOAD_ONLY, 0, 0);
+	cmd_buf->cmd.target_addr = 0;
+	cmd_buf->cmd.data_size = cpu_to_le32(sizeof(cmd_buf->cmd_pl));
+	memcpy(&cmd_buf->cmd_pl, bcf_hdr, sizeof(*bcf_hdr));
+	ret = i2400m_bm_cmd(i2400m, &cmd_buf->cmd, sizeof(*cmd_buf),
+			    &ack, sizeof(ack), 0);
+	if (ret >= 0)
+		ret = 0;
+	d_fnend(5, dev, "(i2400m %p bcf_hdr %p) = %d\n", i2400m, bcf_hdr, ret);
+	return ret;
+}
+
+
+/*
+ * Initialize the firmware download at the device size
+ *
+ * Multiplex to the one that matters based on the device's mode
+ * (signed or non-signed).
+ */
+static
+int i2400m_dnload_init(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	u32 module_id = le32_to_cpu(bcf->module_id);
+
+	if (i2400m->sboot == 0
+	    && (module_id & I2400M_BCF_MOD_ID_POKES) == 0) {
+		/* non-signed boot process without pokes */
+		result = i2400m_dnload_init_nonsigned(i2400m);
+		if (result == -ERESTARTSYS)
+			return result;
+		if (result < 0)
+			dev_err(dev, "fw %s: non-signed download "
+				"initialization failed: %d\n",
+				i2400m->bus_fw_name, result);
+	} else if (i2400m->sboot == 0
+		 && (module_id & I2400M_BCF_MOD_ID_POKES)) {
+		/* non-signed boot process with pokes, nothing to do */
+		result = 0;
+	} else {		 /* signed boot process */
+		result = i2400m_dnload_init_signed(i2400m, bcf);
+		if (result == -ERESTARTSYS)
+			return result;
+		if (result < 0)
+			dev_err(dev, "fw %s: signed boot download "
+				"initialization failed: %d\n",
+				i2400m->bus_fw_name, result);
+	}
+	return result;
+}
+
+
+/*
+ * Run quick consistency tests on the firmware file
+ *
+ * Check for the firmware being made for the i2400m device,
+ * etc...These checks are mostly informative, as the device will make
+ * them too; but the driver's response is more informative on what
+ * went wrong.
+ */
+static
+int i2400m_fw_check(struct i2400m *i2400m,
+		    const struct i2400m_bcf_hdr *bcf,
+		    size_t bcf_size)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	unsigned module_type, header_len, major_version, minor_version,
+		module_id, module_vendor, date, size;
+
+	/* Check hard errors */
+	result = -EINVAL;
+	if (bcf_size < sizeof(*bcf)) {	/* big enough header? */
+		dev_err(dev, "firmware %s too short: "
+			"%zu B vs %zu (at least) expected\n",
+			i2400m->bus_fw_name, bcf_size, sizeof(*bcf));
+		goto error;
+	}
+
+	module_type = bcf->module_type;
+	header_len = sizeof(u32) * le32_to_cpu(bcf->header_len);
+	major_version = le32_to_cpu(bcf->header_version) & 0xffff0000 >> 16;
+	minor_version = le32_to_cpu(bcf->header_version) & 0x0000ffff;
+	module_id = le32_to_cpu(bcf->module_id);
+	module_vendor = le32_to_cpu(bcf->module_vendor);
+	date = le32_to_cpu(bcf->date);
+	size = sizeof(u32) * le32_to_cpu(bcf->size);
+
+	if (bcf_size != size) {		/* annoyingly paranoid */
+		dev_err(dev, "firmware %s: bad size, got "
+			"%zu B vs %u expected\n",
+			i2400m->bus_fw_name, bcf_size, size);
+		goto error;
+	}
+
+	d_printf(2, dev, "type 0x%x id 0x%x vendor 0x%x; header v%u.%u (%zu B) "
+		 "date %08x (%zu B)\n",
+		 module_type, module_id, module_vendor,
+		 major_version, minor_version, (size_t) header_len,
+		 date, (size_t) size);
+
+	if (module_type != 6) {		/* built for the right hardware? */
+		dev_err(dev, "bad fw %s: unexpected module type 0x%x; "
+			"aborting\n", i2400m->bus_fw_name, module_type);
+		goto error;
+	}
+
+	/* Check soft-er errors */
+	result = 0;
+	if (module_vendor != 0x8086)
+		dev_err(dev, "bad fw %s? unexpected vendor 0x%04x\n",
+			i2400m->bus_fw_name, module_vendor);
+	if (date < 0x20080300)
+		dev_err(dev, "bad fw %s? build date too old %08x\n",
+			i2400m->bus_fw_name, date);
+error:
+	return result;
+}
+
+
+/*
+ * Download the firmware to the device
+ *
+ * @i2400m: device descriptor
+ * @bcf: pointer to loaded (and minimally verified for consistency)
+ *    firmware
+ * @bcf_size: size of the @bcf buffer (header plus payloads)
+ *
+ * The process for doing this is described in this file's header.
+ *
+ * Note we only reinitialize boot-mode if the flags say so. Some hw
+ * iterations need it, some don't. In any case, if we loop, we always
+ * need to reinitialize the boot room, hence the flags modification.
+ */
+static
+int i2400m_fw_dnload(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf,
+		     size_t bcf_size, enum i2400m_bri flags)
+{
+	int ret = 0;
+	struct device *dev = i2400m_dev(i2400m);
+	int count = I2400M_BOOT_RETRIES;
+
+	d_fnstart(5, dev, "(i2400m %p bcf %p size %zu)\n",
+		  i2400m, bcf, bcf_size);
+	i2400m->boot_mode = 1;
+hw_reboot:
+	if (count-- == 0) {
+		ret = -ERESTARTSYS;
+		dev_err(dev, "device rebooted too many times, aborting\n");
+		goto error_too_many_reboots;
+	}
+	if (flags & I2400M_BRI_MAC_REINIT) {
+		ret = i2400m_bootrom_init(i2400m, flags);
+		if (ret < 0) {
+			dev_err(dev, "bootrom init failed: %d\n", ret);
+			goto error_bootrom_init;
+		}
+	}
+	flags |= I2400M_BRI_MAC_REINIT;
+
+	/*
+	 * Initialize the download, push the bytes to the device and
+	 * then jump to the new firmware. Note @ret is passed with the
+	 * offset of the jump instruction to _dnload_finalize()
+	 */
+	ret = i2400m_dnload_init(i2400m, bcf);	/* Init device's dnload */
+	if (ret == -ERESTARTSYS)
+		goto error_dev_rebooted;
+	if (ret < 0)
+		goto error_dnload_init;
+
+	ret = i2400m_dnload_bcf(i2400m, bcf, bcf_size);
+	if (ret == -ERESTARTSYS)
+		goto error_dev_rebooted;
+	if (ret < 0) {
+		dev_err(dev, "fw %s: download failed: %d\n",
+			i2400m->bus_fw_name, ret);
+		goto error_dnload_bcf;
+	}
+
+	ret = i2400m_dnload_finalize(i2400m, bcf, ret);
+	if (ret == -ERESTARTSYS)
+		goto error_dev_rebooted;
+	if (ret < 0) {
+		dev_err(dev, "fw %s: "
+			"download finalization failed: %d\n",
+			i2400m->bus_fw_name, ret);
+		goto error_dnload_finalize;
+	}
+
+	d_printf(2, dev, "fw %s successfully uploaded\n",
+		 i2400m->bus_fw_name);
+	i2400m->boot_mode = 0;
+error_dnload_finalize:
+error_dnload_bcf:
+error_dnload_init:
+error_bootrom_init:
+error_too_many_reboots:
+	d_fnend(5, dev, "(i2400m %p bcf %p size %zu) = %d\n",
+		i2400m, bcf, bcf_size, ret);
+	return ret;
+
+error_dev_rebooted:
+	dev_err(dev, "device rebooted, %d tries left\n", count);
+	/* we got the notification already, no need to wait for it again */
+	flags |= I2400M_BRI_SOFT;
+	goto hw_reboot;
+}
+
+
+/**
+ * i2400m_dev_bootstrap - Bring the device to a known state and upload firmware
+ *
+ * @i2400m: device descriptor
+ *
+ * Returns: >= 0 if ok, < 0 errno code on error.
+ *
+ * This sets up the firmware upload environment, loads the firmware
+ * file from disk, verifies and then calls the firmware upload process
+ * per se.
+ *
+ * Can be called either from probe, or after a warm reset.  Can not be
+ * called from within an interrupt.  All the flow in this code is
+ * single-threade; all I/Os are synchronous.
+ */
+int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags)
+{
+	int ret = 0;
+	struct device *dev = i2400m_dev(i2400m);
+	const struct firmware *fw;
+	const struct i2400m_bcf_hdr *bcf;	/* Firmware data */
+
+	d_fnstart(5, dev, "(i2400m %p)\n", i2400m);
+	/* Load firmware files to memory. */
+	ret = request_firmware(&fw, i2400m->bus_fw_name, dev);
+	if (ret) {
+		dev_err(dev, "fw %s: request failed: %d\n",
+			i2400m->bus_fw_name, ret);
+		goto error_fw_req;
+	}
+	bcf = (void *) fw->data;
+
+	ret = i2400m_fw_check(i2400m, bcf, fw->size);
+	if (ret < 0)
+		goto error_fw_bad;
+	ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags);
+error_fw_bad:
+	release_firmware(fw);
+error_fw_req:
+	d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap);
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 16/29] i2400m: RX and TX data/control paths
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (14 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 15/29] i2400m: firmware loading and bootrom initialization Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 17/29] i2400m: various functions for device management Inaky Perez-Gonzalez
                   ` (13 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Handling of TX/RX data to/from the i2400m device (IP packets, control
and diagnostics). On RX, this parses the received read transaction
from the device, breaks it in chunks and passes it to the
corresponding subsystems (network and control).

Transmission to the device is done through a software FIFO, as
data/control frames can be coalesced (while the device is reading the
previous tx transaction, others accumulate). A FIFO is used because at
the end it is resource-cheaper that scatter/gather over USB. As well,
most traffic is going to be download (vs upload).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/rx.c |  534 +++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/tx.c |  817 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1351 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/rx.c
 create mode 100644 drivers/net/wimax/i2400m/tx.c

diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c
new file mode 100644
index 0000000..6922022
--- /dev/null
+++ b/drivers/net/wimax/i2400m/rx.c
@@ -0,0 +1,534 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Handle incoming traffic and deliver it to the control or data planes
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *  - Initial implementation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Use skb_clone(), break up processing in chunks
+ *  - Split transport/device specific
+ *  - Make buffer size dynamic to exert less memory pressure
+ *
+ *
+ * This handles the RX path.
+ *
+ * We receive an RX message from the bus-specific driver, which
+ * contains one or more payloads that have potentially different
+ * destinataries (data or control paths).
+ *
+ * So we just take that payload from the transport specific code in
+ * the form of an skb, break it up in chunks (a cloned skb each in the
+ * case of network packets) and pass it to netdev or to the
+ * command/ack handler (and from there to the WiMAX stack).
+ *
+ * PROTOCOL FORMAT
+ *
+ * The format of the buffer is:
+ *
+ * HEADER                      (struct i2400m_msg_hdr)
+ * PAYLOAD DESCRIPTOR 0        (struct i2400m_pld)
+ * PAYLOAD DESCRIPTOR 1
+ * ...
+ * PAYLOAD DESCRIPTOR N
+ * PAYLOAD 0                   (raw bytes)
+ * PAYLOAD 1
+ * ...
+ * PAYLOAD N
+ *
+ * See tx.c for a deeper description on alignment requirements and
+ * other fun facts of it.
+ *
+ * ROADMAP
+ *
+ * i2400m_rx
+ *   i2400m_rx_msg_hdr_check
+ *   i2400m_rx_pl_descr_check
+ *   i2400m_rx_payload
+ *     i2400m_net_rx
+ *     i2400m_rx_ctl
+ *       i2400m_msg_size_check
+ *       i2400m_report_hook_work    [in a workqueue]
+ *         i2400m_report_hook
+ *       wimax_msg_to_user
+ *       i2400m_rx_ctl_ack
+ *         wimax_msg_to_user_alloc
+ *     i2400m_rx_trace
+ *       i2400m_msg_size_check
+ *       wimax_msg
+ */
+#include <linux/kernel.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/workqueue.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE rx
+#include "debug-levels.h"
+
+struct i2400m_report_hook_args {
+	struct sk_buff *skb_rx;
+	const struct i2400m_l3l4_hdr *l3l4_hdr;
+	size_t size;
+};
+
+
+/*
+ * Execute i2400m_report_hook in a workqueue
+ *
+ * Unpacks arguments from the deferred call, executes it and then
+ * drops the references.
+ *
+ * Obvious NOTE: References are needed because we are a separate
+ *     thread; otherwise the buffer changes under us because it is
+ *     released by the original caller.
+ */
+static
+void i2400m_report_hook_work(struct work_struct *ws)
+{
+	struct i2400m_work *iw =
+		container_of(ws, struct i2400m_work, ws);
+	struct i2400m_report_hook_args *args = (void *) iw->pl;
+	i2400m_report_hook(iw->i2400m, args->l3l4_hdr, args->size);
+	kfree_skb(args->skb_rx);
+	i2400m_put(iw->i2400m);
+	kfree(iw);
+}
+
+
+/*
+ * Process an ack to a command
+ *
+ * @i2400m: device descriptor
+ * @payload: pointer to message
+ * @size: size of the message
+ *
+ * Pass the acknodledgment (in an skb) to the thread that is waiting
+ * for it in i2400m->msg_completion.
+ *
+ * We need to coordinate properly with the thread waiting for the
+ * ack. Check if it is waiting or if it is gone. We loose the spinlock
+ * to avoid allocating on atomic contexts (yeah, could use GFP_ATOMIC,
+ * but this is not so speed critical).
+ */
+static
+void i2400m_rx_ctl_ack(struct i2400m *i2400m,
+		       const void *payload, size_t size)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+	unsigned long flags;
+	struct sk_buff *ack_skb;
+
+	/* Anyone waiting for an answer? */
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	if (i2400m->ack_skb != ERR_PTR(-EINPROGRESS)) {
+		dev_err(dev, "Huh? reply to command with no waiters\n");
+		goto error_no_waiter;
+	}
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+
+	ack_skb = wimax_msg_alloc(wimax_dev, NULL, payload, size, GFP_KERNEL);
+
+	/* Check waiter didn't time out waiting for the answer... */
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	if (i2400m->ack_skb != ERR_PTR(-EINPROGRESS)) {
+		d_printf(1, dev, "Huh? waiter for command reply cancelled\n");
+		goto error_waiter_cancelled;
+	}
+	if (ack_skb == NULL) {
+		dev_err(dev, "CMD/GET/SET ack: cannot allocate SKB\n");
+		i2400m->ack_skb = ERR_PTR(-ENOMEM);
+	} else
+		i2400m->ack_skb = ack_skb;
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	complete(&i2400m->msg_completion);
+	return;
+
+error_waiter_cancelled:
+	if (ack_skb)
+		kfree_skb(ack_skb);
+error_no_waiter:
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return;
+}
+
+
+/*
+ * Receive and process a control payload
+ *
+ * @i2400m: device descriptor
+ * @skb_rx: skb that contains the payload (for reference counting)
+ * @payload: pointer to message
+ * @size: size of the message
+ *
+ * There are two types of control RX messages: reports (asynchronous,
+ * like your every day interrupts) and 'acks' (reponses to a command,
+ * get or set request).
+ *
+ * If it is a report, we run hooks on it (to extract information for
+ * things we need to do in the driver) and then pass it over to the
+ * WiMAX stack to send it to user space.
+ *
+ * NOTE: report processing is done in a workqueue specific to the
+ *     generic driver, to avoid deadlocks in the system.
+ *
+ * If it is not a report, it is an ack to a previously executed
+ * command, set or get, so wake up whoever is waiting for it from
+ * i2400m_msg_to_dev(). i2400m_rx_ctl_ack() takes care of that.
+ *
+ * Note that the sizes we pass to other functions from here are the
+ * sizes of the _l3l4_hdr + payload, not full buffer sizes, as we have
+ * verified in _msg_size_check() that they are congruent.
+ *
+ * For reports: We can't clone the original skb where the data is
+ * because we need to send this up via netlink; netlink has to add
+ * headers and we can't overwrite what's preceeding the payload...as
+ * it is another message. So we just dup them.
+ */
+static
+void i2400m_rx_ctl(struct i2400m *i2400m, struct sk_buff *skb_rx,
+		   const void *payload, size_t size)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	const struct i2400m_l3l4_hdr *l3l4_hdr = payload;
+	unsigned msg_type;
+
+	result = i2400m_msg_size_check(i2400m, l3l4_hdr, size);
+	if (result < 0) {
+		dev_err(dev, "HW BUG? device sent a bad message: %d\n",
+			result);
+		goto error_check;
+	}
+	msg_type = le16_to_cpu(l3l4_hdr->type);
+	d_printf(1, dev, "%s 0x%04x: %zu bytes\n",
+		 msg_type & I2400M_MT_REPORT_MASK ? "REPORT" : "CMD/SET/GET",
+		 msg_type, size);
+	d_dump(2, dev, l3l4_hdr, size);
+	if (msg_type & I2400M_MT_REPORT_MASK) {
+		/* These hooks have to be ran serialized; as well, the
+		 * handling might force the execution of commands, and
+		 * that might cause reentrancy issues with
+		 * bus-specific subdrivers and workqueues. So we run
+		 * it in a separate workqueue. */
+		struct i2400m_report_hook_args args = {
+			.skb_rx = skb_rx,
+			.l3l4_hdr = l3l4_hdr,
+			.size = size
+		};
+		if (unlikely(i2400m->ready == 0))	/* only send if up */
+			return;
+		skb_get(skb_rx);
+		i2400m_queue_work(i2400m, i2400m_report_hook_work,
+				  GFP_KERNEL, &args, sizeof(args));
+		result = wimax_msg(&i2400m->wimax_dev, NULL, l3l4_hdr, size,
+				   GFP_KERNEL);
+		if (result < 0)
+			dev_err(dev, "error sending report to userspace: %d\n",
+				result);
+	} else		/* an ack to a CMD, GET or SET */
+		i2400m_rx_ctl_ack(i2400m, payload, size);
+error_check:
+	return;
+}
+
+
+
+
+/*
+ * Receive and send up a trace
+ *
+ * @i2400m: device descriptor
+ * @skb_rx: skb that contains the trace (for reference counting)
+ * @payload: pointer to trace message inside the skb
+ * @size: size of the message
+ *
+ * THe i2400m might produce trace information (diagnostics) and we
+ * send them through a different kernel-to-user pipe (to avoid
+ * clogging it).
+ *
+ * As in i2400m_rx_ctl(), we can't clone the original skb where the
+ * data is because we need to send this up via netlink; netlink has to
+ * add headers and we can't overwrite what's preceeding the
+ * payload...as it is another message. So we just dup them.
+ */
+static
+void i2400m_rx_trace(struct i2400m *i2400m,
+		     const void *payload, size_t size)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+	const struct i2400m_l3l4_hdr *l3l4_hdr = payload;
+	unsigned msg_type;
+
+	result = i2400m_msg_size_check(i2400m, l3l4_hdr, size);
+	if (result < 0) {
+		dev_err(dev, "HW BUG? device sent a bad trace message: %d\n",
+			result);
+		goto error_check;
+	}
+	msg_type = le16_to_cpu(l3l4_hdr->type);
+	d_printf(1, dev, "Trace %s 0x%04x: %zu bytes\n",
+		 msg_type & I2400M_MT_REPORT_MASK ? "REPORT" : "CMD/SET/GET",
+		 msg_type, size);
+	d_dump(2, dev, l3l4_hdr, size);
+	if (unlikely(i2400m->ready == 0))	/* only send if up */
+		return;
+	result = wimax_msg(wimax_dev, "trace", l3l4_hdr, size, GFP_KERNEL);
+	if (result < 0)
+		dev_err(dev, "error sending trace to userspace: %d\n",
+			result);
+error_check:
+	return;
+}
+
+
+/*
+ * Act on a received payload
+ *
+ * @i2400m: device instance
+ * @skb_rx: skb where the transaction was received
+ * @single: 1 if there is only one payload, 0 otherwise
+ * @pld: payload descriptor
+ * @payload: payload data
+ *
+ * Upon reception of a payload, look at its guts in the payload
+ * descriptor and decide what to do with it.
+ */
+static
+void i2400m_rx_payload(struct i2400m *i2400m, struct sk_buff *skb_rx,
+		       unsigned single, const struct i2400m_pld *pld,
+		       const void *payload)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	size_t pl_size = i2400m_pld_size(pld);
+	enum i2400m_pt pl_type = i2400m_pld_type(pld);
+
+	switch (pl_type) {
+	case I2400M_PT_DATA:
+		d_printf(3, dev, "RX: data payload %zu bytes\n", pl_size);
+		i2400m_net_rx(i2400m, skb_rx, single, payload, pl_size);
+		break;
+	case I2400M_PT_CTRL:
+		i2400m_rx_ctl(i2400m, skb_rx, payload, pl_size);
+		break;
+	case I2400M_PT_TRACE:
+		i2400m_rx_trace(i2400m, payload, pl_size);
+		break;
+	default:	/* Anything else shouldn't come to the host */
+		if (printk_ratelimit())
+			dev_err(dev, "RX: HW BUG? unexpected payload type %u\n",
+				pl_type);
+	}
+}
+
+
+/*
+ * Check a received transaction's message header
+ *
+ * @i2400m: device descriptor
+ * @msg_hdr: message header
+ * @buf_size: size of the received buffer
+ *
+ * Check that the declarations done by a RX buffer message header are
+ * sane and consistent with the amount of data that was received.
+ */
+static
+int i2400m_rx_msg_hdr_check(struct i2400m *i2400m,
+			    const struct i2400m_msg_hdr *msg_hdr,
+			    size_t buf_size)
+{
+	int result = -EIO;
+	struct device *dev = i2400m_dev(i2400m);
+	if (buf_size < sizeof(*msg_hdr)) {
+		dev_err(dev, "RX: HW BUG? message with short header (%zu "
+			"vs %zu bytes expected)\n", buf_size, sizeof(*msg_hdr));
+		goto error;
+	}
+	if (msg_hdr->barker != cpu_to_le32(I2400M_D2H_MSG_BARKER)) {
+		dev_err(dev, "RX: HW BUG? message received with unknown "
+			"barker 0x%08x (buf_size %zu bytes)\n",
+			le32_to_cpu(msg_hdr->barker), buf_size);
+		goto error;
+	}
+	if (msg_hdr->num_pls == 0) {
+		dev_err(dev, "RX: HW BUG? zero payload packets in message\n");
+		goto error;
+	}
+	if (le16_to_cpu(msg_hdr->num_pls) > I2400M_MAX_PLS_IN_MSG) {
+		dev_err(dev, "RX: HW BUG? message contains more payload "
+			"than maximum; ignoring.\n");
+		goto error;
+	}
+	result = 0;
+error:
+	return result;
+}
+
+
+/*
+ * Check a payload descriptor against the received data
+ *
+ * @i2400m: device descriptor
+ * @pld: payload descriptor
+ * @pl_itr: offset (in bytes) in the received buffer the payload is
+ *          located
+ * @buf_size: size of the received buffer
+ *
+ * Given a payload descriptor (part of a RX buffer), check it is sane
+ * and that the data it declares fits in the buffer.
+ */
+static
+int i2400m_rx_pl_descr_check(struct i2400m *i2400m,
+			      const struct i2400m_pld *pld,
+			      size_t pl_itr, size_t buf_size)
+{
+	int result = -EIO;
+	struct device *dev = i2400m_dev(i2400m);
+	size_t pl_size = i2400m_pld_size(pld);
+	enum i2400m_pt pl_type = i2400m_pld_type(pld);
+
+	if (pl_size > i2400m->bus_pl_size_max) {
+		dev_err(dev, "RX: HW BUG? payload @%zu: size %zu is "
+			"bigger than maximum %zu; ignoring message\n",
+			pl_itr, pl_size, i2400m->bus_pl_size_max);
+		goto error;
+	}
+	if (pl_itr + pl_size > buf_size) {	/* enough? */
+		dev_err(dev, "RX: HW BUG? payload @%zu: size %zu "
+			"goes beyond the received buffer "
+			"size (%zu bytes); ignoring message\n",
+			pl_itr, pl_size, buf_size);
+		goto error;
+	}
+	if (pl_type >= I2400M_PT_ILLEGAL) {
+		dev_err(dev, "RX: HW BUG? illegal payload type %u; "
+			"ignoring message\n", pl_type);
+		goto error;
+	}
+	result = 0;
+error:
+	return result;
+}
+
+
+/**
+ * i2400m_rx - Receive a buffer of data from the device
+ *
+ * @i2400m: device descriptor
+ * @skb: skbuff where the data has been received
+ *
+ * Parse in a buffer of data that contains an RX message sent from the
+ * device. See the file header for the format. Run all checks on the
+ * buffer header, then run over each payload's descriptors, verify
+ * their consistency and act on each payload's contents.  If
+ * everything is succesful, update the device's statistics.
+ *
+ * Note: You need to set the skb to contain only the length of the
+ * received buffer; for that, use skb_trim(skb, RECEIVED_SIZE).
+ *
+ * Returns:
+ *
+ * 0 if ok, < 0 errno on error
+ *
+ * If ok, this function owns now the skb and the caller DOESN'T have
+ * to run kfree_skb() on it. However, on error, the caller still owns
+ * the skb and it is responsible for releasing it.
+ */
+int i2400m_rx(struct i2400m *i2400m, struct sk_buff *skb)
+{
+	int i, result;
+	struct device *dev = i2400m_dev(i2400m);
+	const struct i2400m_msg_hdr *msg_hdr;
+	size_t pl_itr, pl_size, skb_len;
+	unsigned long flags;
+	unsigned num_pls;
+
+	skb_len = skb->len;
+	d_fnstart(4, dev, "(i2400m %p skb %p [size %zu])\n",
+		  i2400m, skb, skb_len);
+	result = -EIO;
+	msg_hdr = (void *) skb->data;
+	result = i2400m_rx_msg_hdr_check(i2400m, msg_hdr, skb->len);
+	if (result < 0)
+		goto error_msg_hdr_check;
+	result = -EIO;
+	num_pls = le16_to_cpu(msg_hdr->num_pls);
+	pl_itr = sizeof(*msg_hdr) +	/* Check payload descriptor(s) */
+		num_pls * sizeof(msg_hdr->pld[0]);
+	pl_itr = ALIGN(pl_itr, I2400M_PL_PAD);
+	if (pl_itr > skb->len) {	/* got all the payload descriptors? */
+		dev_err(dev, "RX: HW BUG? message too short (%u bytes) for "
+			"%u payload descriptors (%zu each, total %zu)\n",
+			skb->len, num_pls, sizeof(msg_hdr->pld[0]), pl_itr);
+		goto error_pl_descr_short;
+	}
+	/* Walk each payload payload--check we really got it */
+	for (i = 0; i < num_pls; i++) {
+		/* work around old gcc warnings */
+		pl_size = i2400m_pld_size(&msg_hdr->pld[i]);
+		result = i2400m_rx_pl_descr_check(i2400m, &msg_hdr->pld[i],
+						  pl_itr, skb->len);
+		if (result < 0)
+			goto error_pl_descr_check;
+		i2400m_rx_payload(i2400m, skb, num_pls == 1, &msg_hdr->pld[i],
+				  skb->data + pl_itr);
+		pl_itr += ALIGN(pl_size, I2400M_PL_PAD);
+		cond_resched();		/* Don't monopolize */
+	}
+	kfree_skb(skb);
+	/* Update device statistics */
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	i2400m->rx_pl_num += i;
+	if (i > i2400m->rx_pl_max)
+		i2400m->rx_pl_max = i;
+	if (i < i2400m->rx_pl_min)
+		i2400m->rx_pl_min = i;
+	i2400m->rx_num++;
+	i2400m->rx_size_acc += skb->len;
+	if (skb->len < i2400m->rx_size_min)
+		i2400m->rx_size_min = skb->len;
+	if (skb->len > i2400m->rx_size_max)
+		i2400m->rx_size_max = skb->len;
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+error_pl_descr_check:
+error_pl_descr_short:
+error_msg_hdr_check:
+	d_fnend(4, dev, "(i2400m %p skb %p [size %zu]) = %d\n",
+		i2400m, skb, skb_len, result);
+	return result;
+}
+EXPORT_SYMBOL_GPL(i2400m_rx);
diff --git a/drivers/net/wimax/i2400m/tx.c b/drivers/net/wimax/i2400m/tx.c
new file mode 100644
index 0000000..613a88f
--- /dev/null
+++ b/drivers/net/wimax/i2400m/tx.c
@@ -0,0 +1,817 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Generic (non-bus specific) TX handling
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *  - Initial implementation
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Rewritten to use a single FIFO to lower the memory allocation
+ *    pressure and optimize cache hits when copying to the queue, as
+ *    well as splitting out bus-specific code.
+ *
+ *
+ * Implements data transmission to the device; this is done through a
+ * software FIFO, as data/control frames can be coalesced (while the
+ * device is reading the previous tx transaction, others accumulate).
+ *
+ * A FIFO is used because at the end it is resource-cheaper that trying
+ * to implement scatter/gather over USB. As well, most traffic is going
+ * to be download (vs upload).
+ *
+ * The format for sending/receiving data to/from the i2400m is
+ * described in detail in rx.c:PROTOCOL FORMAT. In here we implement
+ * the transmission of that. This is split between a bus-independent
+ * part that just prepares everything and a bus-specific part that
+ * does the actual transmission over the bus to the device (in the
+ * bus-specific driver).
+ *
+ *
+ * The general format of a device-host transaction is MSG-HDR, PLD1,
+ * PLD2...PLDN, PL1, PL2,...PLN, PADDING.
+ *
+ * Because we need the send payload descriptors and then payloads and
+ * because it is kind of expensive to do scatterlists in USB (one URB
+ * per node), it becomes cheaper to append all the data to a FIFO
+ * (copying to a FIFO potentially in cache is cheaper).
+ *
+ * Then the bus-specific code takes the parts of that FIFO that are
+ * written and passes them to the device.
+ *
+ * So the concepts to keep in mind there are:
+ *
+ * We use a FIFO to queue the data in a linear buffer. We first append
+ * a MSG-HDR, space for I2400M_TX_PLD_MAX payload descriptors and then
+ * go appending payloads until we run out of space or of payload
+ * descriptors. Then we append padding to make the whole transaction a
+ * multiple of i2400m->bus_tx_block_size (as defined by the bus layer).
+ *
+ * - A TX message: a combination of a message header, payload
+ *   descriptors and payloads.
+ *
+ *     Open: it is marked as active (i2400m->tx_msg is valid) and we
+ *       can keep adding payloads to it.
+ *
+ *     Closed: we are not appending more payloads to this TX message
+ *       (exahusted space in the queue, too many payloads or
+ *       whichever).  We have appended padding so the whole message
+ *       length is aligned to i2400m->bus_tx_block_size (as set by the
+ *       bus/transport layer).
+ *
+ * - Most of the time we keep a TX message open to which we append
+ *   payloads.
+ *
+ * - If we are going to append and there is no more space (we are at
+ *   the end of the FIFO), we close the message, mark the rest of the
+ *   FIFO space unusable (skip_tail), create a new message at the
+ *   beginning of the FIFO (if there is space) and append the message
+ *   there.
+ *
+ *   This is because we need to give linear TX messages to the bus
+ *   engine. So we don't write a message to the remaining FIFO space
+ *   until the tail and continue at the head of it.
+ *
+ * - We overload one of the fields in the message header to use it as
+ *   'size' of the TX message, so we can iterate over them. It also
+ *   contains a flag that indicates if we have to skip it or not.
+ *   When we send the buffer, we update that to its real on-the-wire
+ *   value.
+ *
+ * - The MSG-HDR PLD1...PLD2 stuff has to be a size multiple of 16.
+ *
+ *   It follows that if MSG-HDR says we have N messages, the whole
+ *   header + descriptors is 16 + 4*N; for those to be a multiple of
+ *   16, it follows that N can be 4, 8, 12, ... (32, 48, 64, 80...
+ *   bytes).
+ *
+ *   So if we have only 1 payload, we have to submit a header that in
+ *   all truth has space for 4.
+ *
+ *   The implication is that we reserve space for 12 (64 bytes); but
+ *   if we fill up only (eg) 2, our header becomes 32 bytes only. So
+ *   the TX engine has to shift those 32 bytes of msg header and 2
+ *   payloads and padding so that right after it the payloads start
+ *   and the TX engine has to know about that.
+ *
+ *   It is cheaper to move the header up than the whole payloads down.
+ *
+ *   We do this in i2400m_tx_close(). See 'i2400m_msg_hdr->offset'.
+ *
+ * - Each payload has to be size-padded to 16 bytes; before appending
+ *   it, we just do it.
+ *
+ * - The whole message has to be padded to i2400m->bus_tx_block_size;
+ *   we do this at close time. Thus, when reserving space for the
+ *   payload, we always make sure there is also free space for this
+ *   padding that sooner or later will happen.
+ *
+ * When we append a message, we tell the bus specific code to kick in
+ * TXs. It will TX (in parallel) until the buffer is exhausted--hence
+ * the lockin we do. The TX code will only send a TX message at the
+ * time (which remember, might contain more than one payload). Of
+ * course, when the bus-specific driver attempts to TX a message that
+ * is still open, it gets closed first.
+ *
+ * Gee, this is messy; well a picture. In the example below we have a
+ * partially full FIFO, with a closed message ready to be delivered
+ * (with a moved message header to make sure it is size-aligned to
+ * 16), TAIL room that was unusable (and thus is marked with a message
+ * header that says 'skip this') and at the head of the buffer, an
+ * imcomplete message with a couple of payloads.
+ *
+ * N   ___________________________________________________
+ *    |                                                   |
+ *    |     TAIL room                                     |
+ *    |                                                   |
+ *    |  msg_hdr to skip (size |= 0x80000)                |
+ *    |---------------------------------------------------|-------
+ *    |                                                   |  /|\
+ *    |                                                   |   |
+ *    |  TX message padding                               |   |
+ *    |                                                   |   |
+ *    |                                                   |   |
+ *    |- - - - - - - - - - - - - - - - - - - - - - - - - -|   |
+ *    |                                                   |   |
+ *    |  payload 1                                        |   |
+ *    |                                                   | N * tx_block_size
+ *    |                                                   |   |
+ *    |- - - - - - - - - - - - - - - - - - - - - - - - - -|   |
+ *    |                                                   |   |
+ *    |  payload 1                                        |   |
+ *    |                                                   |   |
+ *    |                                                   |   |
+ *    |- - - - - - - - - - - - - - - - - - - - - - - - - -|- -|- - - -
+ *    |  padding 3                  /|\                   |   |   /|\
+ *    |  padding 2                   |                    |   |    |
+ *    |  pld 1                32 bytes (2 * 16)           |   |    |
+ *    |  pld 0                       |                    |   |    |
+ *    |  moved msg_hdr              \|/                   |  \|/   |
+ *    |- - - - - - - - - - - - - - - - - - - - - - - - - -|- - -   |
+ *    |                                                   |    _PLD_SIZE
+ *    |  unused                                           |        |
+ *    |                                                   |        |
+ *    |- - - - - - - - - - - - - - - - - - - - - - - - - -|        |
+ *    |  msg_hdr (size X)       [this message is closed]  |       \|/
+ *    |===================================================|========== <=== OUT
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |          Free rooom                               |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |===================================================|========== <=== IN
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |                                                   |
+ *    |  payload 1                                        |
+ *    |                                                   |
+ *    |                                                   |
+ *    |- - - - - - - - - - - - - - - - - - - - - - - - - -|
+ *    |                                                   |
+ *    |  payload 0                                        |
+ *    |                                                   |
+ *    |                                                   |
+ *    |- - - - - - - - - - - - - - - - - - - - - - - - - -|
+ *    |  pld 11                     /|\                   |
+ *    |  ...                         |                    |
+ *    |  pld 1                64 bytes (2 * 16)           |
+ *    |  pld 0                       |                    |
+ *    |  msg_hdr (size X)           \|/ [message is open] |
+ * 0   ---------------------------------------------------
+ *
+ *
+ * ROADMAP
+ *
+ * i2400m_tx_setup()           Called by i2400m_setup
+ * i2400m_tx_release()         Called by i2400m_release()
+ *
+ *  i2400m_tx()                 Called to send data or control frames
+ *    i2400m_tx_fifo_push()     Allocates append-space in the FIFO
+ *    i2400m_tx_new()           Opens a new message in the FIFO
+ *    i2400m_tx_fits()          Checks if a new payload fits in the message
+ *    i2400m_tx_close()         Closes an open message in the FIFO
+ *    i2400m_tx_skip_tail()     Marks unusable FIFO tail space
+ *    i2400m->bus_tx_kick()
+ *
+ * Now i2400m->bus_tx_kick() is the the bus-specific driver backend
+ * implementation; that would do:
+ *
+ * i2400m->bus_tx_kick()
+ *   i2400m_tx_msg_get()	Gets first message ready to go
+ *   ...sends it...
+ *   i2400m_tx_msg_sent()       Ack the message is sent; repeat from
+ *                              _tx_msg_get() until it returns NULL
+ *                               (FIFO empty).
+ */
+#include <linux/netdevice.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE tx
+#include "debug-levels.h"
+
+enum {
+	/**
+	 * TX Buffer size
+	 *
+	 * Doc says maximum transaction is 16KiB. If we had 16KiB en
+	 * route and 16KiB being queued, it boils down to needing
+	 * 32KiB.
+	 */
+	I2400M_TX_BUF_SIZE = 32768,
+	/**
+	 * Message header and payload descriptors have to be 16
+	 * aligned (16 + 4 * N = 16 * M). If we take that average sent
+	 * packets are MTU size (~1400-~1500) it follows that we could
+	 * fit at most 10-11 payloads in one transaction. To meet the
+	 * alignment requirement, that means we need to leave space
+	 * for 12 (64 bytes). To simplify, we leave space for that. If
+	 * at the end there are less, we pad up to the nearest
+	 * multiple of 16.
+	 */
+	I2400M_TX_PLD_MAX = 12,
+	I2400M_TX_PLD_SIZE = sizeof(struct i2400m_msg_hdr)
+	+ I2400M_TX_PLD_MAX * sizeof(struct i2400m_pld),
+	I2400M_TX_SKIP = 0x80000000,
+};
+
+#define TAIL_FULL ((void *)~(unsigned long)NULL)
+
+/*
+ * Allocate @size bytes in the TX fifo, return a pointer to it
+ *
+ * @i2400m: device descriptor
+ * @size: size of the buffer we need to allocate
+ * @padding: ensure that there is at least this many bytes of free
+ *     contiguous space in the fifo. This is needed because later on
+ *     we might need to add padding.
+ *
+ * Returns:
+ *
+ *     Pointer to the allocated space. NULL if there is no
+ *     space. TAIL_FULL if there is no space at the tail but there is at
+ *     the head (Case B below).
+ *
+ * These are the two basic cases we need to keep an eye for -- it is
+ * much better explained in linux/kernel/kfifo.c, but this code
+ * basically does the same. No rocket science here.
+ *
+ *       Case A               Case B
+ * N  ___________          ___________
+ *   | tail room |        |   data    |
+ *   |           |        |           |
+ *   |<-  IN   ->|        |<-  OUT  ->|
+ *   |           |        |           |
+ *   |   data    |        |   room    |
+ *   |           |        |           |
+ *   |<-  OUT  ->|        |<-  IN   ->|
+ *   |           |        |           |
+ *   | head room |        |   data    |
+ * 0  -----------          -----------
+ *
+ * We allocate only *contiguous* space.
+ *
+ * We can allocate only from 'room'. In Case B, it is simple; in case
+ * A, we only try from the tail room; if it is not enough, we just
+ * fail and return TAIL_FULL and let the caller figure out if we wants to
+ * skip the tail room and try to allocate from the head.
+ *
+ * Note:
+ *
+ *     Assumes i2400m->tx_lock is taken, and we use that as a barrier
+ *
+ *     The indexes keep increasing and we reset them to zero when we
+ *     pop data off the queue
+ */
+static
+void *i2400m_tx_fifo_push(struct i2400m *i2400m, size_t size, size_t padding)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	size_t room, tail_room, needed_size;
+	void *ptr;
+
+	needed_size = size + padding;
+	room = I2400M_TX_BUF_SIZE - (i2400m->tx_in - i2400m->tx_out);
+	if (room < needed_size)	{ /* this takes care of Case B */
+		d_printf(2, dev, "fifo push %zu/%zu: no space\n",
+			 size, padding);
+		return NULL;
+	}
+	/* Is there space at the tail? */
+	tail_room = I2400M_TX_BUF_SIZE - i2400m->tx_in % I2400M_TX_BUF_SIZE;
+	if (tail_room < needed_size) {
+		if (i2400m->tx_out % I2400M_TX_BUF_SIZE
+		    < i2400m->tx_in % I2400M_TX_BUF_SIZE) {
+			d_printf(2, dev, "fifo push %zu/%zu: tail full\n",
+				 size, padding);
+			return TAIL_FULL;	/* There might be head space */
+		} else {
+			d_printf(2, dev, "fifo push %zu/%zu: no head space\n",
+				 size, padding);
+			return NULL;	/* There is no space */
+		}
+	}
+	ptr = i2400m->tx_buf + i2400m->tx_in % I2400M_TX_BUF_SIZE;
+	d_printf(2, dev, "fifo push %zu/%zu: at @%zu\n", size, padding,
+		 i2400m->tx_in % I2400M_TX_BUF_SIZE);
+	i2400m->tx_in += size;
+	return ptr;
+}
+
+
+/*
+ * Mark the tail of the FIFO buffer as 'to-skip'
+ *
+ * We should never hit the BUG_ON() because all the sizes we push to
+ * the FIFO are padded to be a multiple of 16 -- the size of *msg
+ * (I2400M_PL_PAD for the payloads, I2400M_TX_PLD_SIZE for the
+ * header).
+ *
+ * Note:
+ *
+ *     Assumes i2400m->tx_lock is taken, and we use that as a barrier
+ */
+static
+void i2400m_tx_skip_tail(struct i2400m *i2400m)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	size_t tx_in = i2400m->tx_in % I2400M_TX_BUF_SIZE;
+	size_t tail_room = I2400M_TX_BUF_SIZE - tx_in;
+	struct i2400m_msg_hdr *msg = i2400m->tx_buf + tx_in;
+	BUG_ON(tail_room < sizeof(*msg));
+	msg->size = tail_room | I2400M_TX_SKIP;
+	d_printf(2, dev, "skip tail: skipping %zu bytes @%zu\n",
+		 tail_room, tx_in);
+	i2400m->tx_in += tail_room;
+}
+
+
+/*
+ * Check if a skb will fit in the TX queue's current active TX
+ * message (if there are still descriptors left unused).
+ *
+ * Returns:
+ *     0 if the message won't fit, 1 if it will.
+ *
+ * Note:
+ *
+ *     Assumes a TX message is active (i2400m->tx_msg).
+ *
+ *     Assumes i2400m->tx_lock is taken, and we use that as a barrier
+ */
+static
+unsigned i2400m_tx_fits(struct i2400m *i2400m)
+{
+	struct i2400m_msg_hdr *msg_hdr = i2400m->tx_msg;
+	return le16_to_cpu(msg_hdr->num_pls) < I2400M_TX_PLD_MAX;
+
+}
+
+
+/*
+ * Start a new TX message header in the queue.
+ *
+ * Reserve memory from the base FIFO engine and then just initialize
+ * the message header.
+ *
+ * We allocate the biggest TX message header we might need (one that'd
+ * fit I2400M_TX_PLD_MAX payloads) -- when it is closed it will be
+ * 'ironed it out' and the unneeded parts removed.
+ *
+ * NOTE:
+ *
+ *     Assumes that the previous message is CLOSED (eg: either
+ *     there was none or 'i2400m_tx_close()' was called on it).
+ *
+ *     Assumes i2400m->tx_lock is taken, and we use that as a barrier
+ */
+static
+void i2400m_tx_new(struct i2400m *i2400m)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400m_msg_hdr *tx_msg;
+	BUG_ON(i2400m->tx_msg != NULL);
+try_head:
+	tx_msg = i2400m_tx_fifo_push(i2400m, I2400M_TX_PLD_SIZE, 0);
+	if (tx_msg == NULL)
+		goto out;
+	else if (tx_msg == TAIL_FULL) {
+		i2400m_tx_skip_tail(i2400m);
+		d_printf(2, dev, "new TX message: tail full, trying head\n");
+		goto try_head;
+	}
+	memset(tx_msg, 0, I2400M_TX_PLD_SIZE);
+	tx_msg->size = I2400M_TX_PLD_SIZE;
+out:
+	i2400m->tx_msg = tx_msg;
+	d_printf(2, dev, "new TX message: %p @%zu\n",
+		 tx_msg, (void *) tx_msg - i2400m->tx_buf);
+}
+
+
+/*
+ * Finalize the current TX message header
+ *
+ * Sets the message header to be at the proper location depending on
+ * how many descriptors we have (check documentation at the file's
+ * header for more info on that).
+ *
+ * Appends padding bytes to make sure the whole TX message (counting
+ * from the 'relocated' message header) is aligned to
+ * tx_block_size. We assume the _append() code has left enough space
+ * in the FIFO for that. If there are no payloads, just pass, as it
+ * won't be transferred.
+ *
+ * The amount of padding bytes depends on how many payloads are in the
+ * TX message, as the "msg header and payload descriptors" will be
+ * shifted up in the buffer.
+ */
+static
+void i2400m_tx_close(struct i2400m *i2400m)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400m_msg_hdr *tx_msg = i2400m->tx_msg;
+	struct i2400m_msg_hdr *tx_msg_moved;
+	size_t aligned_size, padding, hdr_size;
+	void *pad_buf;
+
+	if (tx_msg->size & I2400M_TX_SKIP)	/* a skipper? nothing to do */
+		goto out;
+
+	/* Relocate the message header
+	 *
+	 * Find the current header size, align it to 16 and if we need
+	 * to move it so the tail is next to the payloads, move it and
+	 * set the offset.
+	 *
+	 * If it moved, this header is good only for transmission; the
+	 * original one (it is kept if we moved) is still used to
+	 * figure out where the next TX message starts (and where the
+	 * offset to the moved header is).
+	 */
+	hdr_size = sizeof(*tx_msg)
+		+ le16_to_cpu(tx_msg->num_pls) * sizeof(tx_msg->pld[0]);
+	hdr_size = ALIGN(hdr_size, I2400M_PL_PAD);
+	tx_msg->offset = I2400M_TX_PLD_SIZE - hdr_size;
+	tx_msg_moved = (void *) tx_msg + tx_msg->offset;
+	memmove(tx_msg_moved, tx_msg, hdr_size);
+	tx_msg_moved->size -= tx_msg->offset;
+	/*
+	 * Now figure out how much we have to add to the (moved!)
+	 * message so the size is a multiple of i2400m->bus_tx_block_size.
+	 */
+	aligned_size = ALIGN(tx_msg_moved->size, i2400m->bus_tx_block_size);
+	padding = aligned_size - tx_msg_moved->size;
+	if (padding > 0) {
+		pad_buf = i2400m_tx_fifo_push(i2400m, padding, 0);
+		if (unlikely(WARN_ON(pad_buf == NULL
+				     || pad_buf == TAIL_FULL))) {
+			/* This should not happen -- append should verify
+			 * there is always space left at least to append
+			 * tx_block_size */
+			dev_err(dev,
+				"SW BUG! Possible data leakage from memory the "
+				"device should not read for padding - "
+				"size %lu aligned_size %zu tx_buf %p in "
+				"%zu out %zu\n",
+				(unsigned long) tx_msg_moved->size,
+				aligned_size, i2400m->tx_buf, i2400m->tx_in,
+				i2400m->tx_out);
+		} else
+			memset(pad_buf, 0xad, padding);
+	}
+	tx_msg_moved->padding = cpu_to_le16(padding);
+	tx_msg_moved->size += padding;
+	if (tx_msg != tx_msg_moved)
+		tx_msg->size += padding;
+out:
+	i2400m->tx_msg = NULL;
+}
+
+
+/**
+ * i2400m_tx - send the data in a buffer to the device
+ *
+ * @buf: pointer to the buffer to transmit
+ *
+ * @buf_len: buffer size
+ *
+ * @pl_type: type of the payload we are sending.
+ *
+ * Returns:
+ *     0 if ok, < 0 errno code on error (-ENOSPC, if there is no more
+ *     room for the message in the queue).
+ *
+ * Appends the buffer to the TX FIFO and notifies the bus-specific
+ * part of the driver that there is new data ready to transmit.
+ * Once this function returns, the buffer has been copied, so it can
+ * be reused.
+ *
+ * The steps followed to append are explained in detail in the file
+ * header.
+ *
+ * Whenever we write to a message, we increase msg->size, so it
+ * reflects exactly how big the message is. This is needed so that if
+ * we concatenate two messages before they can be sent, the code that
+ * sends the messages can find the boundaries (and it will replace the
+ * size with the real barker before sending).
+ *
+ * Note:
+ *
+ *     Cold and warm reset payloads need to be sent as a single
+ *     payload, so we handle that.
+ */
+int i2400m_tx(struct i2400m *i2400m, const void *buf, size_t buf_len,
+	      enum i2400m_pt pl_type)
+{
+	int result = -ENOSPC;
+	struct device *dev = i2400m_dev(i2400m);
+	unsigned long flags;
+	size_t padded_len;
+	void *ptr;
+	unsigned is_singleton = pl_type == I2400M_PT_RESET_WARM
+		|| pl_type == I2400M_PT_RESET_COLD;
+
+	d_fnstart(3, dev, "(i2400m %p skb %p [%zu bytes] pt %u)\n",
+		  i2400m, buf, buf_len, pl_type);
+	padded_len = ALIGN(buf_len, I2400M_PL_PAD);
+	d_printf(5, dev, "padded_len %zd buf_len %zd\n", padded_len, buf_len);
+	/* If there is no current TX message, create one; if the
+	 * current one is out of payload slots or we have a singleton,
+	 * close it and start a new one */
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+try_new:
+	if (unlikely(i2400m->tx_msg == NULL))
+		i2400m_tx_new(i2400m);
+	else if (unlikely(!i2400m_tx_fits(i2400m)
+			  || (is_singleton && i2400m->tx_msg->num_pls != 0))) {
+		d_printf(2, dev, "closing TX message (fits %u singleton "
+			 "%u num_pls %u)\n", i2400m_tx_fits(i2400m),
+			 is_singleton, i2400m->tx_msg->num_pls);
+		i2400m_tx_close(i2400m);
+		i2400m_tx_new(i2400m);
+	}
+	if (i2400m->tx_msg->size + padded_len > I2400M_TX_BUF_SIZE / 2) {
+		d_printf(2, dev, "TX: message too big, going new\n");
+		i2400m_tx_close(i2400m);
+		i2400m_tx_new(i2400m);
+	}
+	if (i2400m->tx_msg == NULL)
+		goto error_tx_new;
+	/* So we have a current message header; now append space for
+	 * the message -- if there is not enough, try the head */
+	ptr = i2400m_tx_fifo_push(i2400m, padded_len,
+				  i2400m->bus_tx_block_size);
+	if (ptr == TAIL_FULL) {	/* Tail is full, try head */
+		d_printf(2, dev, "pl append: tail full\n");
+		i2400m_tx_close(i2400m);
+		i2400m_tx_skip_tail(i2400m);
+		goto try_new;
+	} else if (ptr == NULL) {	/* All full */
+		result = -ENOSPC;
+		d_printf(2, dev, "pl append: all full\n");
+	} else {			/* Got space, copy it, set padding */
+		struct i2400m_msg_hdr *tx_msg = i2400m->tx_msg;
+		unsigned num_pls = le16_to_cpu(tx_msg->num_pls);
+		memcpy(ptr, buf, buf_len);
+		memset(ptr + buf_len, 0xad, padded_len - buf_len);
+		i2400m_pld_set(&tx_msg->pld[num_pls], buf_len, pl_type);
+		d_printf(3, dev, "pld 0x%08x (type 0x%1x len 0x%04zx\n",
+			 le32_to_cpu(tx_msg->pld[num_pls].val),
+			 pl_type, buf_len);
+		tx_msg->num_pls = le16_to_cpu(num_pls+1);
+		tx_msg->size += padded_len;
+		d_printf(2, dev, "TX: appended %zu b (up to %u b) pl #%u \n",
+			padded_len, tx_msg->size, num_pls+1);
+		d_printf(2, dev,
+			 "TX: appended hdr @%zu %zu b pl #%u @%zu %zu/%zu b\n",
+			 (void *)tx_msg - i2400m->tx_buf, (size_t)tx_msg->size,
+			 num_pls+1, ptr - i2400m->tx_buf, buf_len, padded_len);
+		result = 0;
+		if (is_singleton)
+			i2400m_tx_close(i2400m);
+	}
+error_tx_new:
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	i2400m->bus_tx_kick(i2400m);	/* always kick, might free up space */
+	d_fnend(3, dev, "(i2400m %p skb %p [%zu bytes] pt %u) = %d\n",
+		i2400m, buf, buf_len, pl_type, result);
+	return result;
+}
+EXPORT_SYMBOL_GPL(i2400m_tx);
+
+
+/**
+ * i2400m_tx_msg_get - Get the first TX message in the FIFO to start sending it
+ *
+ * @i2400m: device descriptors
+ * @bus_size: where to place the size of the TX message
+ *
+ * Called by the bus-specific driver to get the first TX message at
+ * the FIF that is ready for transmission.
+ *
+ * It sets the state in @i2400m to indicate the bus-specific driver is
+ * transfering that message (i2400m->tx_msg_size).
+ *
+ * Once the transfer is completed, call i2400m_tx_msg_sent().
+ *
+ * Notes:
+ *
+ *     The size of the TX message to be transmitted might be smaller than
+ *     that of the TX message in the FIFO (in case the header was
+ *     shorter). Hence, we copy it in @bus_size, for the bus layer to
+ *     use. We keep the message's size in i2400m->tx_msg_size so that
+ *     when the bus later is done transferring we know how much to
+ *     advance the fifo.
+ *
+ *     We collect statistics here as all the data is available and we
+ *     assume it is going to work [see i2400m_tx_msg_sent()].
+ */
+struct i2400m_msg_hdr *i2400m_tx_msg_get(struct i2400m *i2400m,
+					 size_t *bus_size)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400m_msg_hdr *tx_msg, *tx_msg_moved;
+	unsigned long flags, pls;
+
+	d_fnstart(3, dev, "(i2400m %p bus_size %p)\n", i2400m, bus_size);
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+skip:
+	tx_msg_moved = NULL;
+	if (i2400m->tx_in == i2400m->tx_out) {	/* Empty FIFO? */
+		i2400m->tx_in = 0;
+		i2400m->tx_out = 0;
+		d_printf(2, dev, "TX: FIFO empty: resetting\n");
+		goto out_unlock;
+	}
+	tx_msg = i2400m->tx_buf + i2400m->tx_out % I2400M_TX_BUF_SIZE;
+	if (tx_msg->size & I2400M_TX_SKIP) {	/* skip? */
+		d_printf(2, dev, "TX: skip: msg @%zu (%zu b)\n",
+			 i2400m->tx_out % I2400M_TX_BUF_SIZE,
+			 (size_t) tx_msg->size & ~I2400M_TX_SKIP);
+		i2400m->tx_out += tx_msg->size & ~I2400M_TX_SKIP;
+		goto skip;
+	}
+
+	if (tx_msg->num_pls == 0) {		/* No payloads? */
+		if (tx_msg == i2400m->tx_msg) {	/* open, we are done */
+			d_printf(2, dev,
+				 "TX: FIFO empty: open msg w/o payloads @%zu\n",
+				 (void *) tx_msg - i2400m->tx_buf);
+			tx_msg = NULL;
+			goto out_unlock;
+		} else {			/* closed, skip it */
+			d_printf(2, dev,
+				 "TX: skip msg w/o payloads @%zu (%zu b)\n",
+				 (void *) tx_msg - i2400m->tx_buf,
+				 (size_t) tx_msg->size);
+			i2400m->tx_out += tx_msg->size & ~I2400M_TX_SKIP;
+			goto skip;
+		}
+	}
+	if (tx_msg == i2400m->tx_msg)		/* open msg? */
+		i2400m_tx_close(i2400m);
+
+	/* Now we have a valid TX message (with payloads) to TX */
+	tx_msg_moved = (void *) tx_msg + tx_msg->offset;
+	i2400m->tx_msg_size = tx_msg->size;
+	*bus_size = tx_msg_moved->size;
+	d_printf(2, dev, "TX: pid %d msg hdr at @%zu offset +@%zu "
+		 "size %zu bus_size %zu\n",
+		 current->pid, (void *) tx_msg - i2400m->tx_buf,
+		 (size_t) tx_msg->offset, (size_t) tx_msg->size,
+		 (size_t) tx_msg_moved->size);
+	tx_msg_moved->barker = le32_to_cpu(I2400M_H2D_PREVIEW_BARKER);
+	tx_msg_moved->sequence = le32_to_cpu(i2400m->tx_sequence++);
+
+	pls = le32_to_cpu(tx_msg_moved->num_pls);
+	i2400m->tx_pl_num += pls;		/* Update stats */
+	if (pls > i2400m->tx_pl_max)
+		i2400m->tx_pl_max = pls;
+	if (pls < i2400m->tx_pl_min)
+		i2400m->tx_pl_min = pls;
+	i2400m->tx_num++;
+	i2400m->tx_size_acc += *bus_size;
+	if (*bus_size < i2400m->tx_size_min)
+		i2400m->tx_size_min = *bus_size;
+	if (*bus_size > i2400m->tx_size_max)
+		i2400m->tx_size_max = *bus_size;
+out_unlock:
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	d_fnstart(3, dev, "(i2400m %p bus_size %p [%zu]) = %p\n",
+		  i2400m, bus_size, *bus_size, tx_msg_moved);
+	return tx_msg_moved;
+}
+EXPORT_SYMBOL_GPL(i2400m_tx_msg_get);
+
+
+/**
+ * i2400m_tx_msg_sent - indicate the transmission of a TX message
+ *
+ * @i2400m: device descriptor
+ *
+ * Called by the bus-specific driver when a message has been sent;
+ * this pops it from the FIFO; and as there is space, start the queue
+ * in case it was stopped.
+ *
+ * Should be called even if the message send failed and we are
+ * dropping this TX message.
+ */
+void i2400m_tx_msg_sent(struct i2400m *i2400m)
+{
+	unsigned n;
+	unsigned long flags;
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	i2400m->tx_out += i2400m->tx_msg_size;
+	d_printf(2, dev, "TX: sent %zu b\n", (size_t) i2400m->tx_msg_size);
+	i2400m->tx_msg_size = 0;
+	BUG_ON(i2400m->tx_out > i2400m->tx_in);
+	/* level them FIFO markers off */
+	n = i2400m->tx_out / I2400M_TX_BUF_SIZE;
+	i2400m->tx_out %= I2400M_TX_BUF_SIZE;
+	i2400m->tx_in -= n * I2400M_TX_BUF_SIZE;
+	netif_start_queue(i2400m->wimax_dev.net_dev);
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+}
+EXPORT_SYMBOL_GPL(i2400m_tx_msg_sent);
+
+
+/**
+ * i2400m_tx_setup - Initialize the TX queue and infrastructure
+ *
+ * Make sure we reset the TX sequence to zero, as when this function
+ * is called, the firmware has been just restarted.
+ */
+int i2400m_tx_setup(struct i2400m *i2400m)
+{
+	int result;
+
+	/* Do this here only once -- can't do on
+	 * i2400m_hard_start_xmit() as we'll cause race conditions if
+	 * the WS was scheduled on another CPU */
+	INIT_WORK(&i2400m->wake_tx_ws, i2400m_wake_tx_work);
+
+	i2400m->tx_sequence = 0;
+	i2400m->tx_buf = kmalloc(I2400M_TX_BUF_SIZE, GFP_KERNEL);
+	if (i2400m->tx_buf == NULL)
+		result = -ENOMEM;
+	else
+		result = 0;
+	/* Huh? the bus layer has to define this... */
+	BUG_ON(i2400m->bus_tx_block_size == 0);
+	return result;
+
+}
+
+
+/**
+ * i2400m_tx_release - Tear down the TX queue and infrastructure
+ */
+void i2400m_tx_release(struct i2400m *i2400m)
+{
+	kfree(i2400m->tx_buf);
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 17/29] i2400m: various functions for device management
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (15 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 16/29] i2400m: RX and TX data/control paths Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 18/29] i2400m: debugfs controls Inaky Perez-Gonzalez
                   ` (12 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This is a collection of functions used to control the device (plus a
few helpers).

There are utilities for handling TLV buffers, hooks on the device's
reports to act on device changes of state [i2400m_report_hook()], on
acks to commands [i2400m_msg_ack_hook()], a helper for sending
commands to the device and blocking until a reply arrives
[i2400m_msg_to_dev()], a few high level commands for manipulating the
device state, powersaving mode and configuration plus the routines to
setup the device once communication is established with it
[i2400m_dev_initialize()].

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/control.c | 1291 ++++++++++++++++++++++++++++++++++++
 1 files changed, 1291 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/control.c

diff --git a/drivers/net/wimax/i2400m/control.c b/drivers/net/wimax/i2400m/control.c
new file mode 100644
index 0000000..d3d37fe
--- /dev/null
+++ b/drivers/net/wimax/i2400m/control.c
@@ -0,0 +1,1291 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Miscellaneous control functions for managing the device
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Initial implementation
+ *
+ * This is a collection of functions used to control the device (plus
+ * a few helpers).
+ *
+ * There are utilities for handling TLV buffers, hooks on the device's
+ * reports to act on device changes of state [i2400m_report_hook()],
+ * on acks to commands [i2400m_msg_ack_hook()], a helper for sending
+ * commands to the device and blocking until a reply arrives
+ * [i2400m_msg_to_dev()], a few high level commands for manipulating
+ * the device state, powersving mode and configuration plus the
+ * routines to setup the device once communication is stablished with
+ * it [i2400m_dev_initialize()].
+ *
+ * ROADMAP
+ *
+ * i2400m_dev_initalize()       Called by i2400m_dev_start()
+ *   i2400m_set_init_config()
+ *   i2400m_firmware_check()
+ *   i2400m_cmd_get_state()
+ * i2400m_dev_shutdown()        Called by i2400m_dev_stop()
+ *   i2400m->bus_reset()
+ *
+ * i2400m_{cmd,get,set}_*()
+ *   i2400m_msg_to_dev()
+ *   i2400m_msg_check_status()
+ *
+ * i2400m_report_hook()         Called on reception of an event
+ *   i2400m_report_state_hook()
+ *     i2400m_tlv_buffer_walk()
+ *     i2400m_tlv_match()
+ *     i2400m_report_tlv_system_state()
+ *     i2400m_report_tlv_rf_switches_status()
+ *     i2400m_report_tlv_media_status()
+ *   i2400m_cmd_enter_powersave()
+ *
+ * i2400m_msg_ack_hook()        Called on reception of a reply to a
+ *                              command, get or set
+ */
+
+#include <stdarg.h>
+#include "i2400m.h"
+#include <linux/kernel.h>
+#include <linux/wimax/i2400m.h>
+
+
+#define D_SUBMODULE control
+#include "debug-levels.h"
+
+
+/*
+ * Return if a TLV is of a give type and size
+ *
+ * @tlv_hdr: pointer to the TLV
+ * @tlv_type: type of the TLV we are looking for
+ * @tlv_size: expected size of the TLV we are looking for (if -1,
+ *            don't check the size). This includes the header
+ * Returns: 0 if the TLV matches
+ *          < 0 if it doesn't match at all
+ *          > 0 total TLV + payload size, if the type matches, but not
+ *              the size
+ */
+static
+ssize_t i2400m_tlv_match(const struct i2400m_tlv_hdr *tlv,
+		     enum i2400m_tlv tlv_type, ssize_t tlv_size)
+{
+	if (le16_to_cpu(tlv->type) != tlv_type)	/* Not our type? skip */
+		return -1;
+	if (tlv_size != -1
+	    && le16_to_cpu(tlv->length) + sizeof(*tlv) != tlv_size) {
+		size_t size = le16_to_cpu(tlv->length) + sizeof(*tlv);
+		printk(KERN_WARNING "W: tlv type 0x%x mismatched because of "
+		       "size (got %zu vs %zu expected)\n",
+		       tlv_type, size, tlv_size);
+		return size;
+	}
+	return 0;
+}
+
+
+/*
+ * Given a buffer of TLVs, iterate over them
+ *
+ * @i2400m: device instance
+ * @tlv_buf: pointer to the beginning of the TLV buffer
+ * @buf_size: buffer size in bytes
+ * @tlv_pos: seek position; this is assumed to be a pointer returned
+ *           by i2400m_tlv_buffer_walk() [and thus, validated]. The
+ *           TLV returned will be the one following this one.
+ *
+ * Usage:
+ *
+ * tlv_itr = NULL;
+ * while (tlv_itr = i2400m_tlv_buffer_walk(i2400m, buf, size, tlv_itr))  {
+ *         ...
+ *         // Do stuff with tlv_itr, DON'T MODIFY IT
+ *         ...
+ * }
+ */
+static
+const struct i2400m_tlv_hdr *i2400m_tlv_buffer_walk(
+	struct i2400m *i2400m,
+	const void *tlv_buf, size_t buf_size,
+	const struct i2400m_tlv_hdr *tlv_pos)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	const struct i2400m_tlv_hdr *tlv_top = tlv_buf + buf_size;
+	size_t offset, length, avail_size;
+	unsigned type;
+
+	if (tlv_pos == NULL)	/* Take the first one? */
+		tlv_pos = tlv_buf;
+	else			/* Nope, the next one */
+		tlv_pos = (void *) tlv_pos
+			+ le16_to_cpu(tlv_pos->length) + sizeof(*tlv_pos);
+	if (tlv_pos == tlv_top) {	/* buffer done */
+		tlv_pos = NULL;
+		goto error_beyond_end;
+	}
+	if (tlv_pos > tlv_top) {
+		tlv_pos = NULL;
+		WARN_ON(1);
+		goto error_beyond_end;
+	}
+	offset = (void *) tlv_pos - (void *) tlv_buf;
+	avail_size = buf_size - offset;
+	if (avail_size < sizeof(*tlv_pos)) {
+		dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], tlv @%zu: "
+			"short header\n", tlv_buf, buf_size, offset);
+		goto error_short_header;
+	}
+	type = le16_to_cpu(tlv_pos->type);
+	length = le16_to_cpu(tlv_pos->length);
+	if (avail_size < sizeof(*tlv_pos) + length) {
+		dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], "
+			"tlv type 0x%04x @%zu: "
+			"short data (%zu bytes vs %zu needed)\n",
+			tlv_buf, buf_size, type, offset, avail_size,
+			sizeof(*tlv_pos) + length);
+		goto error_short_header;
+	}
+error_short_header:
+error_beyond_end:
+	return tlv_pos;
+}
+
+
+/*
+ * Find a TLV in a buffer of sequential TLVs
+ *
+ * @i2400m: device descriptor
+ * @tlv_hdr: pointer to the first TLV in the sequence
+ * @size: size of the buffer in bytes; all TLVs are assumed to fit
+ *        fully in the buffer (otherwise we'll complain).
+ * @tlv_type: type of the TLV we are looking for
+ * @tlv_size: expected size of the TLV we are looking for (if -1,
+ *            don't check the size). This includes the header
+ *
+ * Returns: NULL if the TLV is not found, otherwise a pointer to
+ *          it. If the sizes don't match, an error is printed and NULL
+ *          returned.
+ */
+static
+const struct i2400m_tlv_hdr *i2400m_tlv_find(
+	struct i2400m *i2400m,
+	const struct i2400m_tlv_hdr *tlv_hdr, size_t size,
+	enum i2400m_tlv tlv_type, ssize_t tlv_size)
+{
+	ssize_t match;
+	struct device *dev = i2400m_dev(i2400m);
+	const struct i2400m_tlv_hdr *tlv = NULL;
+	while ((tlv = i2400m_tlv_buffer_walk(i2400m, tlv_hdr, size, tlv))) {
+		match = i2400m_tlv_match(tlv, tlv_type, tlv_size);
+		if (match == 0)		/* found it :) */
+			break;
+		if (match > 0)
+			dev_warn(dev, "TLV type 0x%04x found with size "
+				 "mismatch (%zu vs %zu needed)\n",
+				 tlv_type, match, tlv_size);
+	}
+	return tlv;
+}
+
+
+static const struct
+{
+	char *msg;
+	int errno;
+} ms_to_errno[I2400M_MS_MAX] = {
+	[I2400M_MS_DONE_OK] = { "", 0 },
+	[I2400M_MS_DONE_IN_PROGRESS] = { "", 0 },
+	[I2400M_MS_INVALID_OP] = { "invalid opcode", -ENOSYS },
+	[I2400M_MS_BAD_STATE] = { "invalid state", -EILSEQ },
+	[I2400M_MS_ILLEGAL_VALUE] = { "illegal value", -EINVAL },
+	[I2400M_MS_MISSING_PARAMS] = { "missing parameters", -ENOMSG },
+	[I2400M_MS_VERSION_ERROR] = { "bad version", -EIO },
+	[I2400M_MS_ACCESSIBILITY_ERROR] = { "accesibility error", -EIO },
+	[I2400M_MS_BUSY] = { "busy", -EBUSY },
+	[I2400M_MS_CORRUPTED_TLV] = { "corrupted TLV", -EILSEQ },
+	[I2400M_MS_UNINITIALIZED] = { "not unitialized", -EILSEQ },
+	[I2400M_MS_UNKNOWN_ERROR] = { "unknown error", -EIO },
+	[I2400M_MS_PRODUCTION_ERROR] = { "production error", -EIO },
+	[I2400M_MS_NO_RF] = { "no RF", -EIO },
+	[I2400M_MS_NOT_READY_FOR_POWERSAVE] =
+		{ "not ready for powersave", -EACCES },
+	[I2400M_MS_THERMAL_CRITICAL] = { "thermal critical", -EL3HLT },
+};
+
+
+/*
+ * i2400m_msg_check_status - translate a message's status code
+ *
+ * @i2400m: device descriptor
+ * @l3l4_hdr: message header
+ * @strbuf: buffer to place a formatted error message (unless NULL).
+ * @strbuf_size: max amount of available space; larger messages will
+ * be truncated.
+ *
+ * Returns: errno code corresponding to the status code in @l3l4_hdr
+ *          and a message in @strbuf describing the error.
+ */
+int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *l3l4_hdr,
+			    char *strbuf, size_t strbuf_size)
+{
+	int result;
+	enum i2400m_ms status = le16_to_cpu(l3l4_hdr->status);
+	const char *str;
+
+	if (status == 0)
+		return 0;
+	if (status > ARRAY_SIZE(ms_to_errno)) {
+		str = "unknown status code";
+		result = -EBADR;
+	} else {
+		str = ms_to_errno[status].msg;
+		result = ms_to_errno[status].errno;
+	}
+	if (strbuf)
+		snprintf(strbuf, strbuf_size, "%s (%d)", str, status);
+	return result;
+}
+
+
+/*
+ * Act on a TLV System State reported by the device
+ *
+ * @i2400m: device descriptor
+ * @ss: validated System State TLV
+ */
+static
+void i2400m_report_tlv_system_state(struct i2400m *i2400m,
+				    const struct i2400m_tlv_system_state *ss)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+	enum i2400m_system_state i2400m_state = le32_to_cpu(ss->state);
+
+	d_fnstart(3, dev, "(i2400m %p ss %p [%u])\n", i2400m, ss, i2400m_state);
+
+	if (unlikely(i2400m->ready == 0))	/* act if up */
+		goto out;
+	if (i2400m->state != i2400m_state) {
+		i2400m->state = i2400m_state;
+		wake_up_all(&i2400m->state_wq);
+	}
+	switch (i2400m_state) {
+	case I2400M_SS_UNINITIALIZED:
+	case I2400M_SS_INIT:
+	case I2400M_SS_CONFIG:
+	case I2400M_SS_PRODUCTION:
+		wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED);
+		break;
+
+	case I2400M_SS_RF_OFF:
+	case I2400M_SS_RF_SHUTDOWN:
+		wimax_state_change(wimax_dev, WIMAX_ST_RADIO_OFF);
+		break;
+
+	case I2400M_SS_READY:
+	case I2400M_SS_STANDBY:
+	case I2400M_SS_SLEEPACTIVE:
+		wimax_state_change(wimax_dev, WIMAX_ST_READY);
+		break;
+
+	case I2400M_SS_CONNECTING:
+	case I2400M_SS_WIMAX_CONNECTED:
+		wimax_state_change(wimax_dev, WIMAX_ST_READY);
+		break;
+
+	case I2400M_SS_SCAN:
+	case I2400M_SS_OUT_OF_ZONE:
+		wimax_state_change(wimax_dev, WIMAX_ST_SCANNING);
+		break;
+
+	case I2400M_SS_IDLE:
+		d_printf(1, dev, "entering BS-negotiated idle mode\n");
+	case I2400M_SS_DISCONNECTING:
+	case I2400M_SS_DATA_PATH_CONNECTED:
+		wimax_state_change(wimax_dev, WIMAX_ST_CONNECTED);
+		break;
+
+	default:
+		/* Huh? just in case, shut it down */
+		dev_err(dev, "HW BUG? unknown state %u: shutting down\n",
+			i2400m_state);
+		i2400m->bus_reset(i2400m, I2400M_RT_WARM);
+		break;
+	};
+out:
+	d_fnend(3, dev, "(i2400m %p ss %p [%u]) = void\n",
+		i2400m, ss, i2400m_state);
+}
+
+
+/*
+ * Parse and act on a TLV Media Status sent by the device
+ *
+ * @i2400m: device descriptor
+ * @ms: validated Media Status TLV
+ *
+ * This will set the carrier up on down based on the device's link
+ * report. This is done asides of what the WiMAX stack does based on
+ * the device's state as sometimes we need to do a link-renew (the BS
+ * wants us to renew a DHCP lease, for example).
+ *
+ * In fact, doc says that everytime we get a link-up, we should do a
+ * DHCP negotiation...
+ */
+static
+void i2400m_report_tlv_media_status(struct i2400m *i2400m,
+				    const struct i2400m_tlv_media_status *ms)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+	struct net_device *net_dev = wimax_dev->net_dev;
+	enum i2400m_media_status status = le32_to_cpu(ms->media_status);
+
+	d_fnstart(3, dev, "(i2400m %p ms %p [%u])\n", i2400m, ms, status);
+
+	if (unlikely(i2400m->ready == 0))	/* act if up */
+		goto out;
+	switch (status) {
+	case I2400M_MEDIA_STATUS_LINK_UP:
+		netif_carrier_on(net_dev);
+		break;
+	case I2400M_MEDIA_STATUS_LINK_DOWN:
+		netif_carrier_off(net_dev);
+		break;
+	/*
+	 * This is the network telling us we need to retrain the DHCP
+	 * lease -- so far, we are trusting the WiMAX Network Service
+	 * in user space to pick this up and poke the DHCP client.
+	 */
+	case I2400M_MEDIA_STATUS_LINK_RENEW:
+		netif_carrier_on(net_dev);
+		break;
+	default:
+		dev_err(dev, "HW BUG? unknown media status %u\n",
+			status);
+	};
+out:
+	d_fnend(3, dev, "(i2400m %p ms %p [%u]) = void\n",
+		i2400m, ms, status);
+}
+
+
+/*
+ * Parse a 'state report' and extract carrier on/off information
+ *
+ * @i2400m: device descriptor
+ * @l3l4_hdr: pointer to message; it has been already validated for
+ *            consistent size.
+ * @size: size of the message (header + payload). The header length
+ *        declaration is assumed to be congruent with @size (as in
+ *        sizeof(*l3l4_hdr) + l3l4_hdr->length == size)
+ *
+ * Extract from the report state the system state TLV and infer from
+ * there if we have a carrier or not. Update our local state and tell
+ * netdev.
+ *
+ * When setting the carrier, it's fine to set OFF twice (for example),
+ * as netif_carrier_off() will not generate two OFF events (just on
+ * the transitions).
+ */
+static
+void i2400m_report_state_hook(struct i2400m *i2400m,
+			      const struct i2400m_l3l4_hdr *l3l4_hdr,
+			      size_t size, const char *tag)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	const struct i2400m_tlv_hdr *tlv;
+	const struct i2400m_tlv_system_state *ss;
+	const struct i2400m_tlv_rf_switches_status *rfss;
+	const struct i2400m_tlv_media_status *ms;
+	size_t tlv_size = le16_to_cpu(l3l4_hdr->length);
+
+	d_fnstart(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s)\n",
+		  i2400m, l3l4_hdr, size, tag);
+	tlv = NULL;
+
+	while ((tlv = i2400m_tlv_buffer_walk(i2400m, &l3l4_hdr->pl,
+					     tlv_size, tlv))) {
+		if (0 == i2400m_tlv_match(tlv, I2400M_TLV_SYSTEM_STATE,
+					  sizeof(*ss))) {
+			ss = container_of(tlv, typeof(*ss), hdr);
+			d_printf(2, dev, "%s: system state TLV "
+				 "found (0x%04x), state 0x%08x\n",
+				 tag, I2400M_TLV_SYSTEM_STATE,
+				 le32_to_cpu(ss->state));
+			i2400m_report_tlv_system_state(i2400m, ss);
+		}
+		if (0 == i2400m_tlv_match(tlv, I2400M_TLV_RF_STATUS,
+					  sizeof(*rfss))) {
+			rfss = container_of(tlv, typeof(*rfss), hdr);
+			d_printf(2, dev, "%s: RF status TLV "
+				 "found (0x%04x), sw 0x%02x hw 0x%02x\n",
+				 tag, I2400M_TLV_RF_STATUS,
+				 le32_to_cpu(rfss->sw_rf_switch),
+				 le32_to_cpu(rfss->hw_rf_switch));
+			i2400m_report_tlv_rf_switches_status(i2400m, rfss);
+		}
+		if (0 == i2400m_tlv_match(tlv, I2400M_TLV_MEDIA_STATUS,
+					  sizeof(*ms))) {
+			ms = container_of(tlv, typeof(*ms), hdr);
+			d_printf(2, dev, "%s: Media Status TLV: %u\n",
+				 tag, le32_to_cpu(ms->media_status));
+			i2400m_report_tlv_media_status(i2400m, ms);
+		}
+	}
+	d_fnend(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s) = void\n",
+		i2400m, l3l4_hdr, size, tag);
+}
+
+
+/*
+ * i2400m_report_hook - (maybe) act on a report
+ *
+ * @i2400m: device descriptor
+ * @l3l4_hdr: pointer to message; it has been already validated for
+ *            consistent size.
+ * @size: size of the message (header + payload). The header length
+ *        declaration is assumed to be congruent with @size (as in
+ *        sizeof(*l3l4_hdr) + l3l4_hdr->length == size)
+ *
+ * Extract information we might need (like carrien on/off) from a
+ * device report.
+ */
+void i2400m_report_hook(struct i2400m *i2400m,
+			const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size)
+{
+	struct device *dev = i2400m_dev(i2400m);
+	unsigned msg_type;
+
+	d_fnstart(3, dev, "(i2400m %p l3l4_hdr %p size %zu)\n",
+		  i2400m, l3l4_hdr, size);
+	/* Chew on the message, we might need some information from
+	 * here */
+	msg_type = le16_to_cpu(l3l4_hdr->type);
+	switch (msg_type) {
+	case I2400M_MT_REPORT_STATE:	/* carrier detection... */
+		i2400m_report_state_hook(i2400m,
+					 l3l4_hdr, size, "REPORT STATE");
+		break;
+	/* If the device is ready for power save, then ask it to do
+	 * it. */
+	case I2400M_MT_REPORT_POWERSAVE_READY:	/* zzzzz */
+		if (l3l4_hdr->status == cpu_to_le16(I2400M_MS_DONE_OK)) {
+			d_printf(1, dev, "ready for powersave, requesting\n");
+			i2400m_cmd_enter_powersave(i2400m);
+		}
+		break;
+	};
+	d_fnend(3, dev, "(i2400m %p l3l4_hdr %p size %zu) = void\n",
+		i2400m, l3l4_hdr, size);
+}
+
+
+/*
+ * i2400m_msg_ack_hook - process cmd/set/get ack for internal status
+ *
+ * @i2400m: device descriptor
+ * @l3l4_hdr: pointer to message; it has been already validated for
+ *            consistent size.
+ * @size: size of the message
+ *
+ * Extract information we might need from acks to commands and act on
+ * it. This is akin to i2400m_report_hook(). Note most of this
+ * processing should be done in the function that calls the
+ * command. This is here for some cases where it can't happen...
+ */
+void i2400m_msg_ack_hook(struct i2400m *i2400m,
+			 const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	unsigned ack_type, ack_status;
+	char strerr[32];
+
+	/* Chew on the message, we might need some information from
+	 * here */
+	ack_type = le16_to_cpu(l3l4_hdr->type);
+	ack_status = le16_to_cpu(l3l4_hdr->status);
+	switch (ack_type) {
+	case I2400M_MT_CMD_ENTER_POWERSAVE:
+		/* This is just left here for the sake of example, as
+		 * the processing is done somewhere else. */
+		if (0) {
+			result = i2400m_msg_check_status(
+				l3l4_hdr, strerr, sizeof(strerr));
+			if (result >= 0)
+				d_printf(1, dev, "ready for power save: %zd\n",
+					 size);
+		}
+		break;
+	};
+	return;
+}
+
+
+/*
+ * i2400m_msg_size_check() - verify message size and header are congruent
+ *
+ * It is ok if the total message size is larger than the expected
+ * size, as there can be padding.
+ */
+int i2400m_msg_size_check(struct i2400m *i2400m,
+			  const struct i2400m_l3l4_hdr *l3l4_hdr,
+			  size_t msg_size)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	size_t expected_size;
+	d_fnstart(4, dev, "(i2400m %p l3l4_hdr %p msg_size %zu)\n",
+		  i2400m, l3l4_hdr, msg_size);
+	if (msg_size < sizeof(*l3l4_hdr)) {
+		dev_err(dev, "bad size for message header "
+			"(expected at least %zu, got %zu)\n",
+			(size_t) sizeof(*l3l4_hdr), msg_size);
+		result = -EIO;
+		goto error_hdr_size;
+	}
+	expected_size = le16_to_cpu(l3l4_hdr->length) + sizeof(*l3l4_hdr);
+	if (msg_size < expected_size) {
+		dev_err(dev, "bad size for message code 0x%04x (expected %zu, "
+			"got %zu)\n", le16_to_cpu(l3l4_hdr->type),
+			expected_size, msg_size);
+		result = -EIO;
+	} else
+		result = 0;
+error_hdr_size:
+	d_fnend(4, dev,
+		"(i2400m %p l3l4_hdr %p msg_size %zu) = %d\n",
+		i2400m, l3l4_hdr, msg_size, result);
+	return result;
+}
+
+
+
+/*
+ * Cancel a wait for a command ACK
+ *
+ * @i2400m: device descriptor
+ * @code: [negative] errno code to cancel with (don't use
+ *     -EINPROGRESS)
+ *
+ * If there is an ack already filled out, free it.
+ */
+void i2400m_msg_to_dev_cancel_wait(struct i2400m *i2400m, int code)
+{
+	struct sk_buff *ack_skb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	ack_skb = i2400m->ack_skb;
+	if (ack_skb && !IS_ERR(ack_skb))
+		kfree(ack_skb);
+	i2400m->ack_skb = ERR_PTR(code);
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+}
+
+
+/**
+ * i2400m_msg_to_dev - Send a control message to the device and get a response
+ *
+ * @i2400m: device descriptor
+ *
+ * @msg_skb: an skb  *
+ *
+ * @buf: pointer to the buffer containing the message to be sent; it
+ *           has to start with a &struct i2400M_l3l4_hdr and then
+ *           followed by the payload. Once this function returns, the
+ *           buffer can be reused.
+ *
+ * @buf_len: buffer size
+ *
+ * Returns:
+ *
+ * Pointer to skb containing the ack message. You need to check the
+ * pointer with IS_ERR(), as it might be an error code. Error codes
+ * could happen because:
+ *
+ *  - the message wasn't formatted correctly
+ *  - couldn't send the message
+ *  - failed waiting for a response
+ *  - the ack message wasn't formatted correctly
+ *
+ * The returned skb has been allocated with wimax_msg_to_user_alloc(),
+ * it contains the reponse in a netlink attribute and is ready to be
+ * passed up to user space with wimax_msg_to_user_send(). To access
+ * the payload and its length, use wimax_msg_{data,len}() on the skb.
+ *
+ * The skb has to be freed with kfree_skb() once done.
+ *
+ * Description:
+ *
+ * This function delivers a message/command to the device and waits
+ * for an ack to be received. The format is described in
+ * linux/wimax/i2400m.h. In summary, a command/get/set is followed by an
+ * ack.
+ *
+ * This function will not check the ack status, that's left up to the
+ * caller.  Once done with the ack skb, it has to be kfree_skb()ed.
+ *
+ * The i2400m handles only one message at the same time, thus we need
+ * the mutex to exclude other players.
+ *
+ * We write the message and then wait for an answer to come back. The
+ * RX path intercepts control messages and handles them in
+ * i2400m_rx_ctl(). Reports (notifications) are (maybe) processed
+ * locally and then forwarded (as needed) to user space on the WiMAX
+ * stack message pipe. Acks are saved and passed back to us through an
+ * skb in i2400m->ack_skb which is ready to be given to generic
+ * netlink if need be.
+ */
+struct sk_buff *i2400m_msg_to_dev(struct i2400m *i2400m,
+				  const void *buf, size_t buf_len)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	const struct i2400m_l3l4_hdr *msg_l3l4_hdr;
+	struct sk_buff *ack_skb;
+	const struct i2400m_l3l4_hdr *ack_l3l4_hdr;
+	size_t ack_len;
+	int ack_timeout;
+	unsigned msg_type;
+	unsigned long flags;
+
+	d_fnstart(3, dev, "(i2400m %p buf %p len %zu)\n",
+		  i2400m, buf, buf_len);
+
+	if (i2400m->boot_mode)
+		return ERR_PTR(-ENODEV);
+
+	msg_l3l4_hdr = buf;
+	/* Check msg & payload consistency */
+	result = i2400m_msg_size_check(i2400m, msg_l3l4_hdr, buf_len);
+	if (result < 0)
+		goto error_bad_msg;
+	msg_type = le16_to_cpu(msg_l3l4_hdr->type);
+	d_printf(1, dev, "CMD/GET/SET 0x%04x %zu bytes\n",
+		 msg_type, buf_len);
+	d_dump(2, dev, buf, buf_len);
+
+	/* Setup the completion, ack_skb ("we are waiting") and send
+	 * the message to the device */
+	mutex_lock(&i2400m->msg_mutex);
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	i2400m->ack_skb = ERR_PTR(-EINPROGRESS);
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	init_completion(&i2400m->msg_completion);
+	result = i2400m_tx(i2400m, buf, buf_len, I2400M_PT_CTRL);
+	if (result < 0) {
+		dev_err(dev, "can't send message 0x%04x: %d\n",
+			le16_to_cpu(msg_l3l4_hdr->type), result);
+		goto error_tx;
+	}
+
+	/* Some commands take longer to execute because of crypto ops,
+	 * so we give them some more leeway on timeout */
+	switch (msg_type) {
+	case I2400M_MT_GET_TLS_OPERATION_RESULT:
+	case I2400M_MT_CMD_SEND_EAP_RESPONSE:
+		ack_timeout = 5 * HZ;
+		break;
+	default:
+		ack_timeout = HZ;
+	};
+
+	/* The RX path in rx.c will put any response for this message
+	 * in i2400m->ack_skb and wake us up. If we cancel the wait,
+	 * we need to change the value of i2400m->ack_skb to something
+	 * not -EINPROGRESS so RX knows there is no one waiting. */
+	result = wait_for_completion_interruptible_timeout(
+		&i2400m->msg_completion, ack_timeout);
+	if (result == 0) {
+		dev_err(dev, "timeout waiting for reply to message 0x%04x\n",
+			msg_type);
+		result = -ETIMEDOUT;
+		i2400m_msg_to_dev_cancel_wait(i2400m, result);
+		goto error_wait_for_completion;
+	} else if (result < 0) {
+		dev_err(dev, "error waiting for reply to message 0x%04x: %d\n",
+			msg_type, result);
+		i2400m_msg_to_dev_cancel_wait(i2400m, result);
+		goto error_wait_for_completion;
+	}
+
+	/* Pull out the ack data from i2400m->ack_skb -- see if it is
+	 * an error and act accordingly */
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	ack_skb = i2400m->ack_skb;
+	if (IS_ERR(ack_skb))
+		result = PTR_ERR(ack_skb);
+	else
+		result = 0;
+	i2400m->ack_skb = NULL;
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	if (result < 0)
+		goto error_ack_status;
+	ack_l3l4_hdr = wimax_msg_data_len(ack_skb, &ack_len);
+
+	/* Check the ack and deliver it if it is ok */
+	result = i2400m_msg_size_check(i2400m, ack_l3l4_hdr, ack_len);
+	if (result < 0) {
+		dev_err(dev, "HW BUG? reply to message 0x%04x: %d\n",
+			msg_type, result);
+		goto error_bad_ack_len;
+	}
+	if (msg_type != le16_to_cpu(ack_l3l4_hdr->type)) {
+		dev_err(dev, "HW BUG? bad reply 0x%04x to message 0x%04x\n",
+			le16_to_cpu(ack_l3l4_hdr->type), msg_type);
+		result = -EIO;
+		goto error_bad_ack_type;
+	}
+	i2400m_msg_ack_hook(i2400m, ack_l3l4_hdr, ack_len);
+	mutex_unlock(&i2400m->msg_mutex);
+	d_fnend(3, dev, "(i2400m %p buf %p len %zu) = %p\n",
+		i2400m, buf, buf_len, ack_skb);
+	return ack_skb;
+
+error_bad_ack_type:
+error_bad_ack_len:
+	kfree_skb(ack_skb);
+error_ack_status:
+error_wait_for_completion:
+error_tx:
+	mutex_unlock(&i2400m->msg_mutex);
+error_bad_msg:
+	d_fnend(3, dev, "(i2400m %p buf %p len %zu) = %d\n",
+		i2400m, buf, buf_len, result);
+	return ERR_PTR(result);
+}
+
+
+/*
+ * Definitions for the Enter Power Save command
+ *
+ * The Enter Power Save command requests the device to go into power
+ * saving mode. The device will ack or nak the command depending on it
+ * being ready for it. If it acks, we tell the USB subsystem to
+ *
+ * As well, the device might request to go into power saving mode by
+ * sending a report (REPORT_POWERSAVE_READY), in which case, we issue
+ * this command. The hookups in the RX coder allow
+ */
+enum {
+	I2400M_WAKEUP_ENABLED  = 0x01,
+	I2400M_WAKEUP_DISABLED = 0x02,
+	I2400M_TLV_TYPE_WAKEUP_MODE = 144,
+};
+
+struct i2400m_cmd_enter_power_save {
+	struct i2400m_l3l4_hdr hdr;
+	struct i2400m_tlv_hdr tlv;
+	__le32 val;
+} __attribute__((packed));
+
+
+/*
+ * Request entering power save
+ *
+ * This command is (mainly) executed when the device indicates that it
+ * is ready to go into powersave mode via a REPORT_POWERSAVE_READY.
+ */
+int i2400m_cmd_enter_powersave(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+	struct i2400m_cmd_enter_power_save *cmd;
+	char strerr[32];
+
+	result = -ENOMEM;
+	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+	if (cmd == NULL)
+		goto error_alloc;
+	cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_ENTER_POWERSAVE);
+	cmd->hdr.length = cpu_to_le16(sizeof(*cmd) - sizeof(cmd->hdr));
+	cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION);
+	cmd->tlv.type = cpu_to_le16(I2400M_TLV_TYPE_WAKEUP_MODE);
+	cmd->tlv.length = cpu_to_le16(sizeof(cmd->val));
+	cmd->val = cpu_to_le32(I2400M_WAKEUP_ENABLED);
+
+	ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd));
+	result = PTR_ERR(ack_skb);
+	if (IS_ERR(ack_skb)) {
+		dev_err(dev, "Failed to issue 'Enter power save' command: %d\n",
+			result);
+		goto error_msg_to_dev;
+	}
+	result = i2400m_msg_check_status(wimax_msg_data(ack_skb),
+					 strerr, sizeof(strerr));
+	if (result == -EACCES)
+		d_printf(1, dev, "Cannot enter power save mode\n");
+	else if (result < 0)
+		dev_err(dev, "'Enter power save' (0x%04x) command failed: "
+			"%d - %s\n", I2400M_MT_CMD_ENTER_POWERSAVE,
+			result, strerr);
+	else
+		d_printf(1, dev, "device ready to power save\n");
+	kfree_skb(ack_skb);
+error_msg_to_dev:
+	kfree(cmd);
+error_alloc:
+	return result;
+}
+EXPORT_SYMBOL_GPL(i2400m_cmd_enter_powersave);
+
+
+/*
+ * Definitions for getting device information
+ */
+enum {
+	I2400M_TLV_DETAILED_DEVICE_INFO = 140
+};
+
+/**
+ * i2400m_get_device_info - Query the device for detailed device information
+ *
+ * @i2400m: device descriptor
+ *
+ * Returns: an skb whose skb->data points to a 'struct
+ *    i2400m_tlv_detailed_device_info'. When done, kfree_skb() it. The
+ *    skb is *guaranteed* to contain the whole TLV data structure.
+ *
+ *    On error, IS_ERR(skb) is true and ERR_PTR(skb) is the error
+ *    code.
+ */
+struct sk_buff *i2400m_get_device_info(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+	struct i2400m_l3l4_hdr *cmd;
+	const struct i2400m_l3l4_hdr *ack;
+	size_t ack_len;
+	const struct i2400m_tlv_hdr *tlv;
+	const struct i2400m_tlv_detailed_device_info *ddi;
+	char strerr[32];
+
+	ack_skb = ERR_PTR(-ENOMEM);
+	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+	if (cmd == NULL)
+		goto error_alloc;
+	cmd->type = cpu_to_le16(I2400M_MT_GET_DEVICE_INFO);
+	cmd->length = 0;
+	cmd->version = cpu_to_le16(I2400M_L3L4_VERSION);
+
+	ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd));
+	if (IS_ERR(ack_skb)) {
+		dev_err(dev, "Failed to issue 'get device info' command: %ld\n",
+			PTR_ERR(ack_skb));
+		goto error_msg_to_dev;
+	}
+	ack = wimax_msg_data_len(ack_skb, &ack_len);
+	result = i2400m_msg_check_status(ack, strerr, sizeof(strerr));
+	if (result < 0) {
+		dev_err(dev, "'get device info' (0x%04x) command failed: "
+			"%d - %s\n", I2400M_MT_GET_DEVICE_INFO, result,
+			strerr);
+		goto error_cmd_failed;
+	}
+	tlv = i2400m_tlv_find(i2400m, ack->pl, ack_len - sizeof(*ack),
+			      I2400M_TLV_DETAILED_DEVICE_INFO, sizeof(*ddi));
+	if (tlv == NULL) {
+		dev_err(dev, "GET DEVICE INFO: "
+			"detailed device info TLV not found (0x%04x)\n",
+			I2400M_TLV_DETAILED_DEVICE_INFO);
+		result = -EIO;
+		goto error_no_tlv;
+	}
+	skb_pull(ack_skb, (void *) tlv - (void *) ack_skb->data);
+error_msg_to_dev:
+	kfree(cmd);
+error_alloc:
+	return ack_skb;
+
+error_no_tlv:
+error_cmd_failed:
+	kfree_skb(ack_skb);
+	kfree(cmd);
+	return ERR_PTR(result);
+}
+
+
+/* Firmware interface versions we support */
+enum {
+	I2400M_HDIv_MAJOR = 9,
+	I2400M_HDIv_MAJOR_2 = 8,
+	I2400M_HDIv_MINOR = 1,
+};
+
+
+/**
+ * i2400m_firmware_check - check firmware versions are compatible with
+ * the driver
+ *
+ * @i2400m: device descriptor
+ *
+ * Returns: 0 if ok, < 0 errno code an error and a message in the
+ *    kernel log.
+ *
+ * Long function, but quite simple; first chunk launches the command
+ * and double checks the reply for the right TLV. Then we process the
+ * TLV (where the meat is).
+ */
+int i2400m_firmware_check(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+	struct i2400m_l3l4_hdr *cmd;
+	const struct i2400m_l3l4_hdr *ack;
+	size_t ack_len;
+	const struct i2400m_tlv_hdr *tlv;
+	const struct i2400m_tlv_l4_message_versions *l4mv;
+	char strerr[32];
+	unsigned major, minor, branch;
+
+	result = -ENOMEM;
+	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+	if (cmd == NULL)
+		goto error_alloc;
+	cmd->type = cpu_to_le16(I2400M_MT_GET_LM_VERSION);
+	cmd->length = 0;
+	cmd->version = cpu_to_le16(I2400M_L3L4_VERSION);
+
+	ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd));
+	if (IS_ERR(ack_skb)) {
+		result = PTR_ERR(ack_skb);
+		dev_err(dev, "Failed to issue 'get lm version' command: %-d\n",
+			result);
+		goto error_msg_to_dev;
+	}
+	ack = wimax_msg_data_len(ack_skb, &ack_len);
+	result = i2400m_msg_check_status(ack, strerr, sizeof(strerr));
+	if (result < 0) {
+		dev_err(dev, "'get lm version' (0x%04x) command failed: "
+			"%d - %s\n", I2400M_MT_GET_LM_VERSION, result,
+			strerr);
+		goto error_cmd_failed;
+	}
+	tlv = i2400m_tlv_find(i2400m, ack->pl, ack_len - sizeof(*ack),
+			      I2400M_TLV_L4_MESSAGE_VERSIONS, sizeof(*l4mv));
+	if (tlv == NULL) {
+		dev_err(dev, "get lm version: TLV not found (0x%04x)\n",
+			I2400M_TLV_L4_MESSAGE_VERSIONS);
+		result = -EIO;
+		goto error_no_tlv;
+	}
+	l4mv = container_of(tlv, typeof(*l4mv), hdr);
+	major = le16_to_cpu(l4mv->major);
+	minor = le16_to_cpu(l4mv->minor);
+	branch = le16_to_cpu(l4mv->branch);
+	result = -EINVAL;
+	if (major != I2400M_HDIv_MAJOR
+	    && major != I2400M_HDIv_MAJOR_2) {
+		dev_err(dev, "unsupported major fw interface version "
+			"%u.%u.%u\n", major, minor, branch);
+		goto error_bad_major;
+	}
+	if (major == I2400M_HDIv_MAJOR_2)
+		dev_err(dev, "deprecated major fw interface version "
+			"%u.%u.%u\n", major, minor, branch);
+	result = 0;
+	if (minor != I2400M_HDIv_MINOR)
+		dev_warn(dev, "untested minor fw firmware version %u.%u.%u\n",
+			 major, minor, branch);
+error_bad_major:
+	dev_info(dev, "firmware interface version %u.%u.%u\n",
+		 major, minor, branch);
+error_no_tlv:
+error_cmd_failed:
+	kfree_skb(ack_skb);
+error_msg_to_dev:
+	kfree(cmd);
+error_alloc:
+	return result;
+}
+
+
+/*
+ * Send an DoExitIdle command to the device to ask it to go out of
+ * basestation-idle mode.
+ *
+ * @i2400m: device descriptor
+ *
+ * This starts a renegotiation with the basestation that might involve
+ * another crypto handshake with user space.
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ */
+int i2400m_cmd_exit_idle(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+	struct i2400m_l3l4_hdr *cmd;
+	char strerr[32];
+
+	result = -ENOMEM;
+	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+	if (cmd == NULL)
+		goto error_alloc;
+	cmd->type = cpu_to_le16(I2400M_MT_CMD_EXIT_IDLE);
+	cmd->length = 0;
+	cmd->version = cpu_to_le16(I2400M_L3L4_VERSION);
+
+	ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd));
+	result = PTR_ERR(ack_skb);
+	if (IS_ERR(ack_skb)) {
+		dev_err(dev, "Failed to issue 'exit idle' command: %d\n",
+			result);
+		goto error_msg_to_dev;
+	}
+	result = i2400m_msg_check_status(wimax_msg_data(ack_skb),
+					 strerr, sizeof(strerr));
+	kfree_skb(ack_skb);
+error_msg_to_dev:
+	kfree(cmd);
+error_alloc:
+	return result;
+
+}
+
+
+/*
+ * Query the device for its state, update the WiMAX stack's idea of it
+ *
+ * @i2400m: device descriptor
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ *
+ * Executes a 'Get State' command and parses the returned
+ * TLVs.
+ *
+ * Because this is almost identical to a 'Report State', we use
+ * i2400m_report_state_hook() to parse the answer. This will set the
+ * carrier state, as well as the RF Kill switches state.
+ */
+int i2400m_cmd_get_state(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+	struct i2400m_l3l4_hdr *cmd;
+	const struct i2400m_l3l4_hdr *ack;
+	size_t ack_len;
+	char strerr[32];
+
+	result = -ENOMEM;
+	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
+	if (cmd == NULL)
+		goto error_alloc;
+	cmd->type = cpu_to_le16(I2400M_MT_GET_STATE);
+	cmd->length = 0;
+	cmd->version = cpu_to_le16(I2400M_L3L4_VERSION);
+
+	ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd));
+	if (IS_ERR(ack_skb)) {
+		dev_err(dev, "Failed to issue 'get state' command: %ld\n",
+			PTR_ERR(ack_skb));
+		result = PTR_ERR(ack_skb);
+		goto error_msg_to_dev;
+	}
+	ack = wimax_msg_data_len(ack_skb, &ack_len);
+	result = i2400m_msg_check_status(ack, strerr, sizeof(strerr));
+	if (result < 0) {
+		dev_err(dev, "'get state' (0x%04x) command failed: "
+			"%d - %s\n", I2400M_MT_GET_STATE, result, strerr);
+		goto error_cmd_failed;
+	}
+	i2400m_report_state_hook(i2400m, ack, ack_len - sizeof(*ack),
+				 "GET STATE");
+	result = 0;
+	kfree_skb(ack_skb);
+error_cmd_failed:
+error_msg_to_dev:
+	kfree(cmd);
+error_alloc:
+	return result;
+}
+EXPORT_SYMBOL_GPL(i2400m_cmd_get_state);
+
+
+/**
+ * Set basic configuration settings
+ *
+ * @i2400m: device descriptor
+ * @args: array of pointers to the TLV headers to send for
+ *     configuration (each followed by its payload).
+ *     TLV headers and payloads must be properly initialized, with the
+ *     right endianess (LE).
+ * @arg_size: number of pointers in the @args array
+ */
+int i2400m_set_init_config(struct i2400m *i2400m,
+			   const struct i2400m_tlv_hdr **arg, size_t args)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct sk_buff *ack_skb;
+	struct i2400m_l3l4_hdr *cmd;
+	char strerr[32];
+	unsigned argc, argsize, tlv_size;
+	const struct i2400m_tlv_hdr *tlv_hdr;
+	void *buf, *itr;
+
+	d_fnstart(3, dev, "(i2400m %p arg %p args %zu)\n", i2400m, arg, args);
+	result = 0;
+	if (args == 0)
+		goto none;
+	/* Compute the size of all the TLVs, so we can alloc a
+	 * contiguous command block to copy them. */
+	argsize = 0;
+	for (argc = 0; argc < args; argc++) {
+		tlv_hdr = arg[argc];
+		argsize += sizeof(*tlv_hdr) + le16_to_cpu(tlv_hdr->length);
+	}
+	WARN_ON(argc >= 9);	/* As per hw spec */
+
+	/* Alloc the space for the command and TLVs*/
+	result = -ENOMEM;
+	buf = kzalloc(sizeof(*cmd) + argsize, GFP_KERNEL);
+	if (buf == NULL)
+		goto error_alloc;
+	cmd = buf;
+	cmd->type = cpu_to_le16(I2400M_MT_SET_INIT_CONFIG);
+	cmd->length = cpu_to_le16(argsize);
+	cmd->version = cpu_to_le16(I2400M_L3L4_VERSION);
+
+	/* Copy the TLVs */
+	itr = buf + sizeof(*cmd);
+	for (argc = 0; argc < args; argc++) {
+		tlv_hdr = arg[argc];
+		tlv_size = sizeof(*tlv_hdr) + le16_to_cpu(tlv_hdr->length);
+		memcpy(itr, tlv_hdr, tlv_size);
+		itr += tlv_size;
+	}
+
+	/* Send the message! */
+	ack_skb = i2400m_msg_to_dev(i2400m, buf, sizeof(*cmd) + argsize);
+	result = PTR_ERR(ack_skb);
+	if (IS_ERR(ack_skb)) {
+		dev_err(dev, "Failed to issue 'init config' command: %d\n",
+			result);
+
+		goto error_msg_to_dev;
+	}
+	result = i2400m_msg_check_status(wimax_msg_data(ack_skb),
+					 strerr, sizeof(strerr));
+	if (result < 0)
+		dev_err(dev, "'init config' (0x%04x) command failed: %d - %s\n",
+			I2400M_MT_SET_INIT_CONFIG, result, strerr);
+	kfree_skb(ack_skb);
+error_msg_to_dev:
+	kfree(buf);
+error_alloc:
+none:
+	d_fnend(3, dev, "(i2400m %p arg %p args %zu) = %d\n",
+		i2400m, arg, args, result);
+	return result;
+
+}
+EXPORT_SYMBOL_GPL(i2400m_set_init_config);
+
+
+/**
+ * i2400m_dev_initialize - Initialize the device once communications are ready
+ *
+ * @i2400m: device descriptor
+ *
+ * Returns: 0 if ok, < 0 errno code on error.
+ *
+ * Configures the device to work the way we like it.
+ *
+ * At the point of this call, the device is registered with the WiMAX
+ * and netdev stacks, firmware is uploaded and we can talk to the
+ * device normally.
+ */
+int i2400m_dev_initialize(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400m_tlv_config_idle_parameters idle_params;
+	const struct i2400m_tlv_hdr *args[9];
+	unsigned argc = 0;
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	/* Useless for now...might change */
+	if (i2400m_idle_mode_disabled) {
+		idle_params.hdr.type =
+			cpu_to_le16(I2400M_TLV_CONFIG_IDLE_PARAMETERS);
+		idle_params.hdr.length = cpu_to_le16(
+			sizeof(idle_params) - sizeof(idle_params.hdr));
+		idle_params.idle_timeout = 0;
+		idle_params.idle_paging_interval = 0;
+		args[argc++] = &idle_params.hdr;
+	}
+	result = i2400m_set_init_config(i2400m, args, argc);
+	if (result < 0)
+		goto error;
+	result = i2400m_firmware_check(i2400m);	/* fw versions ok? */
+	if (result < 0)
+		goto error;
+	/*
+	 * Update state: Here it just calls a get state; parsing the
+	 * result (System State TLV and RF Status TLV [done in the rx
+	 * path hooks]) will set the hardware and software RF-Kill
+	 * status.
+	 */
+	result = i2400m_cmd_get_state(i2400m);
+error:
+	d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
+	return result;
+}
+
+
+/**
+ * i2400m_dev_shutdown - Shutdown a running device
+ *
+ * @i2400m: device descriptor
+ *
+ * Gracefully stops the device, moving it to the lowest power
+ * consumption state possible.
+ */
+void i2400m_dev_shutdown(struct i2400m *i2400m)
+{
+	int result = -ENODEV;
+	struct device *dev = i2400m_dev(i2400m);
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	result = i2400m->bus_reset(i2400m, I2400M_RT_WARM);
+	d_fnend(3, dev, "(i2400m %p) = void [%d]\n", i2400m, result);
+	return;
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 18/29] i2400m: debugfs controls
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (16 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 17/29] i2400m: various functions for device management Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 19/29] i2400m/USB: header for the USB bus driver Inaky Perez-Gonzalez
                   ` (11 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: wimax, greg

Expose knobs to control the device (induce reset, power saving,
querying tx or rx stats, internal debug information and debug level
manipulation).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/debugfs.c |  392 ++++++++++++++++++++++++++++++++++++
 1 files changed, 392 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/debugfs.c

diff --git a/drivers/net/wimax/i2400m/debugfs.c b/drivers/net/wimax/i2400m/debugfs.c
new file mode 100644
index 0000000..6266329
--- /dev/null
+++ b/drivers/net/wimax/i2400m/debugfs.c
@@ -0,0 +1,392 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Debugfs interfaces to manipulate driver and device information
+ *
+ *
+ * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE debugfs
+#include "debug-levels.h"
+
+static
+int debugfs_netdev_queue_stopped_get(void *data, u64 *val)
+{
+	struct i2400m *i2400m = data;
+	*val = netif_queue_stopped(i2400m->wimax_dev.net_dev);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_netdev_queue_stopped,
+			debugfs_netdev_queue_stopped_get,
+			NULL, "%llu\n");
+
+
+static
+struct dentry *debugfs_create_netdev_queue_stopped(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0400, parent, i2400m,
+				   &fops_netdev_queue_stopped);
+}
+
+
+/*
+ * inode->i_private has the @data argument to debugfs_create_file()
+ */
+static
+int i2400m_stats_open(struct inode *inode, struct file *filp)
+{
+	filp->private_data = inode->i_private;
+	return 0;
+}
+
+/*
+ * We don't allow partial reads of this file, as then the reader would
+ * get weirdly confused data as it is updated.
+ *
+ * So or you read it all or nothing; if you try to read with an offset
+ * != 0, we consider you are done reading.
+ */
+static
+ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->rx_pl_num, i2400m->rx_pl_min,
+		 i2400m->rx_pl_max, i2400m->rx_num,
+		 i2400m->rx_size_acc,
+		 i2400m->rx_size_min, i2400m->rx_size_max);
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	i2400m->rx_pl_num = 0;
+	i2400m->rx_pl_max = 0;
+	i2400m->rx_pl_min = UINT_MAX;
+	i2400m->rx_num = 0;
+	i2400m->rx_size_acc = 0;
+	i2400m->rx_size_min = UINT_MAX;
+	i2400m->rx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_rx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_rx_stats_read,
+	.write =	i2400m_rx_stats_write,
+};
+
+
+/* See i2400m_rx_stats_read() */
+static
+ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->tx_pl_num, i2400m->tx_pl_min,
+		 i2400m->tx_pl_max, i2400m->tx_num,
+		 i2400m->tx_size_acc,
+		 i2400m->tx_size_min, i2400m->tx_size_max);
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	i2400m->tx_pl_num = 0;
+	i2400m->tx_pl_max = 0;
+	i2400m->tx_pl_min = UINT_MAX;
+	i2400m->tx_num = 0;
+	i2400m->tx_size_acc = 0;
+	i2400m->tx_size_min = UINT_MAX;
+	i2400m->tx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_tx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_tx_stats_read,
+	.write =	i2400m_tx_stats_write,
+};
+
+
+/* Write 1 to ask the device to go into suspend */
+static
+int debugfs_i2400m_suspend_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	result = i2400m_cmd_enter_powersave(i2400m);
+	if (result >= 0)
+		result = 0;
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_suspend,
+			NULL, debugfs_i2400m_suspend_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_suspend(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_suspend);
+}
+
+
+/*
+ * Reset the device
+ *
+ * Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus
+ * reset (as defined by enum i2400m_reset_type).
+ */
+static
+int debugfs_i2400m_reset_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	enum i2400m_reset_type rt = val;
+	switch(rt) {
+	case I2400M_RT_WARM:
+	case I2400M_RT_COLD:
+	case I2400M_RT_BUS:
+		result = i2400m->bus_reset(i2400m, rt);
+		if (result >= 0)
+			result = 0;
+	default:
+		result = -EINVAL;
+	}
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_reset,
+			NULL, debugfs_i2400m_reset_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_reset(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_reset);
+}
+
+/*
+ * Debug levels control; see debug.h
+ */
+struct d_level D_LEVEL[] = {
+	D_SUBMODULE_DEFINE(control),
+	D_SUBMODULE_DEFINE(driver),
+	D_SUBMODULE_DEFINE(debugfs),
+	D_SUBMODULE_DEFINE(fw),
+	D_SUBMODULE_DEFINE(netdev),
+	D_SUBMODULE_DEFINE(rfkill),
+	D_SUBMODULE_DEFINE(rx),
+	D_SUBMODULE_DEFINE(tx),
+};
+size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+
+#define __debugfs_register(prefix, name, parent)			\
+do {									\
+	result = d_level_register_debugfs(prefix, name, parent);	\
+	if (result < 0)							\
+		goto error;						\
+} while (0)
+
+
+int i2400m_debugfs_add(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry;
+	struct dentry *fd;
+
+	dentry = debugfs_create_dir("i2400m", dentry);
+	result = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		if (result == -ENODEV)
+			result = 0;	/* No debugfs support */
+		goto error;
+	}
+	i2400m->debugfs_dentry = dentry;
+	__debugfs_register("dl_", control, dentry);
+	__debugfs_register("dl_", driver, dentry);
+	__debugfs_register("dl_", debugfs, dentry);
+	__debugfs_register("dl_", fw, dentry);
+	__debugfs_register("dl_", netdev, dentry);
+	__debugfs_register("dl_", rfkill, dentry);
+	__debugfs_register("dl_", rx, dentry);
+	__debugfs_register("dl_", tx, dentry);
+
+	fd = debugfs_create_size_t("tx_in", 0400, dentry,
+				   &i2400m->tx_in);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_in: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_size_t("tx_out", 0400, dentry,
+				   &i2400m->tx_out);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_out: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_u32("state", 0600, dentry,
+				&i2400m->state);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"state: %d\n", result);
+		goto error;
+	}
+
+	/*
+	 * Trace received messages from user space
+	 *
+	 * In order to tap the bidirectional message stream in the
+	 * 'msg' pipe, user space can read from the 'msg' pipe;
+	 * however, due to limitations in libnl, we can't know what
+	 * the different applications are sending down to the kernel.
+	 *
+	 * So we have this hack where the driver will echo any message
+	 * received on the msg pipe from user space [through a call to
+	 * wimax_dev->op_msg_from_user() into
+	 * i2400m_op_msg_from_user()] into the 'trace' pipe that this
+	 * driver creates.
+	 *
+	 * So then, reading from both the 'trace' and 'msg' pipes in
+	 * user space will provide a full dump of the traffic.
+	 *
+	 * Write 1 to activate, 0 to clear.
+	 *
+	 * It is not really very atomic, but it is also not too
+	 * critical.
+	 */
+	fd = debugfs_create_u8("trace_msg_from_user", 0600, dentry,
+			       &i2400m->trace_msg_from_user);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"trace_msg_from_user: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_netdev_queue_stopped("netdev_queue_stopped",
+						 dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"netdev_queue_stopped: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("rx_stats", 0600, dentry, i2400m,
+				 &i2400m_rx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"rx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("tx_stats", 0600, dentry, i2400m,
+				 &i2400m_tx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_suspend("suspend", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry suspend: %d\n",
+			result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_reset("reset", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry reset: %d\n", result);
+		goto error;
+	}
+
+	result = 0;
+error:
+	return result;
+}
+
+void i2400m_debugfs_rm(struct i2400m *i2400m)
+{
+	debugfs_remove_recursive(i2400m->debugfs_dentry);
+}
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 19/29] i2400m/USB: header for the USB bus driver
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (17 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 18/29] i2400m: debugfs controls Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 20/29] i2400m/USB: probe/disconnect, dev init/shutdown and reset backends Inaky Perez-Gonzalez
                   ` (10 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This contains the common function declaration and constants for the
USB driver for the 2400m Wireless WiMAX Connection, as well as it's
debug level settings.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/i2400m-usb.h       |  264 +++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/usb-debug-levels.h |   42 +++++
 2 files changed, 306 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/i2400m-usb.h
 create mode 100644 drivers/net/wimax/i2400m/usb-debug-levels.h

diff --git a/drivers/net/wimax/i2400m/i2400m-usb.h b/drivers/net/wimax/i2400m/i2400m-usb.h
new file mode 100644
index 0000000..6f76558
--- /dev/null
+++ b/drivers/net/wimax/i2400m/i2400m-usb.h
@@ -0,0 +1,264 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * USB-specific i2400m driver definitions
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * This driver implements the bus-specific part of the i2400m for
+ * USB. Check i2400m.h for a generic driver description.
+ *
+ * ARCHITECTURE
+ *
+ * This driver listens to notifications sent from the notification
+ * endpoint (in usb-notif.c); when data is ready to read, the code in
+ * there schedules a read from the device (usb-rx.c) and then passes
+ * the data to the generic RX code (rx.c).
+ *
+ * When the generic driver needs to send data (network or control), it
+ * queues up in the TX FIFO (tx.c) and that will notify the driver
+ * through the i2400m->bus_tx_kick() callback
+ * (usb-tx.c:i2400mu_bus_tx_kick) which will send the items in the
+ * FIFO queue.
+ *
+ * This driver, as well, implements the USB-specific ops for the generic
+ * driver to be able to setup/teardown communication with the device
+ * [i2400m_bus_dev_start() and i2400m_bus_dev_stop()], reseting the
+ * device [i2400m_bus_reset()] and performing firmware upload
+ * [i2400m_bus_bm_cmd() and i2400_bus_bm_wait_for_ack()].
+ */
+
+#ifndef __I2400M_USB_H__
+#define __I2400M_USB_H__
+
+#include "i2400m.h"
+#include <linux/kthread.h>
+
+
+/*
+ * Error Density Count: cheapo error density (over time) counter
+ *
+ * Originally by Reinette Chatre <reinette.chatre@intel.com>
+ *
+ * Embed an 'struct edc' somewhere. Each time there is a soft or
+ * retryable error, call edc_inc() and check if the error top
+ * watermark has been reached.
+ */
+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;
+}
+
+/**
+ * edc_inc - report a soft error and check if we are over the watermark
+ *
+ * @edc: pointer to error density counter.
+ * @max_err: maximum number of errors we can accept over the timeframe
+ * @timeframe: lenght of the timeframe (in jiffies).
+ *
+ * Returns: !0 1 if maximum acceptable errors per timeframe has been
+ *     exceeded. 0 otherwise.
+ *
+ * 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.
+ *
+ * To use, embed a 'struct edc' somewhere, initialize it with
+ * edc_init() and when an error hits:
+ *
+ * if (do_something_fails_with_a_soft_error) {
+ *        if (edc_inc(&my->edc, MAX_ERRORS, MAX_TIMEFRAME))
+ * 	           Ops, hard error, do something about it
+ *        else
+ *                 Retry or ignore, depending on whatever
+ * }
+ */
+static inline int edc_inc(struct edc *edc, u16 max_err, u16 timeframe)
+{
+	unsigned long now;
+
+	now = jiffies;
+	if (now - edc->timestart > timeframe) {
+		edc->errorcount = 1;
+		edc->timestart = now;
+	} else if (++edc->errorcount > max_err) {
+		edc->errorcount = 0;
+		edc->timestart = now;
+		return 1;
+	}
+	return 0;
+}
+
+/* Host-Device interface for USB */
+enum {
+	I2400MU_MAX_NOTIFICATION_LEN = 256,
+	I2400MU_BLK_SIZE = 16,
+	I2400MU_PL_SIZE_MAX = 0x3EFF,
+
+	/* Endpoints */
+	I2400MU_EP_BULK_OUT = 0,
+	I2400MU_EP_NOTIFICATION,
+	I2400MU_EP_RESET_COLD,
+	I2400MU_EP_BULK_IN,
+};
+
+
+/**
+ * struct i2400mu - descriptor for a USB connected i2400m
+ *
+ * @i2400m: bus-generic i2400m implementation; has to be first (see
+ *     it's documentation in i2400m.h).
+ *
+ * @usb_dev: pointer to our USB device
+ *
+ * @usb_iface: pointer to our USB interface
+ *
+ * @urb_edc: error density counter; used to keep a density-on-time tab
+ *     on how many soft (retryable or ignorable) errors we get. If we
+ *     go over the threshold, we consider the bus transport is failing
+ *     too much and reset.
+ *
+ * @notif_urb: URB for receiving notifications from the device.
+ *
+ * @tx_kthread: thread we use for data TX. We use a thread because in
+ *     order to do deep power saving and put the device to sleep, we
+ *     need to call usb_autopm_*() [blocking functions].
+ *
+ * @tx_wq: waitqueue for the TX kthread to sleep when there is no data
+ *     to be sent; when more data is available, it is woken up by
+ *     i2400mu_bus_tx_kick().
+ *
+ * @rx_kthread: thread we use for data RX. We use a thread because in
+ *     order to do deep power saving and put the device to sleep, we
+ *     need to call usb_autopm_*() [blocking functions].
+ *
+ * @rx_wq: waitqueue for the RX kthread to sleep when there is no data
+ *     to receive. When data is available, it is woken up by
+ *     usb-notif.c:i2400mu_notification_grok().
+ *
+ * @rx_pending_count: number of rx-data-ready notifications that were
+ *     still not handled by the RX kthread.
+ *
+ * @rx_size: current RX buffer size that is being used.
+ *
+ * @rx_size_acc: accumulator of the sizes of the previous read
+ *     transactions.
+ *
+ * @rx_size_cnt: number of read transactions accumulated in
+ *     @rx_size_acc.
+ *
+ * @do_autopm: disable(0)/enable(>0) calling the
+ *     usb_autopm_get/put_interface() barriers when executing
+ *     commands. See doc in i2400mu_suspend() for more information.
+ *
+ * @rx_size_auto_shrink: if true, the rx_size is shrinked
+ *     automatically based on the average size of the received
+ *     transactions. This allows the receive code to allocate smaller
+ *     chunks of memory and thus reduce pressure on the memory
+ *     allocator by not wasting so much space. By default it is
+ *     enabled.
+ *
+ * @debugfs_dentry: hookup for debugfs files.
+ *     These have to be in a separate directory, a child of
+ *     (wimax_dev->debugfs_dentry) so they can be removed when the
+ *     module unloads, as we don't keep each dentry.
+ */
+struct i2400mu {
+	struct i2400m i2400m;		/* FIRST! See doc */
+
+	struct usb_device *usb_dev;
+	struct usb_interface *usb_iface;
+	struct edc urb_edc;		/* Error density counter */
+
+	struct urb *notif_urb;
+	struct task_struct *tx_kthread;
+	wait_queue_head_t tx_wq;
+
+	struct task_struct *rx_kthread;
+	wait_queue_head_t rx_wq;
+	atomic_t rx_pending_count;
+	size_t rx_size, rx_size_acc, rx_size_cnt;
+	atomic_t do_autopm;
+	u8 rx_size_auto_shrink;
+
+	struct dentry *debugfs_dentry;
+};
+
+
+static inline
+void i2400mu_init(struct i2400mu *i2400mu)
+{
+	i2400m_init(&i2400mu->i2400m);
+	edc_init(&i2400mu->urb_edc);
+	init_waitqueue_head(&i2400mu->tx_wq);
+	atomic_set(&i2400mu->rx_pending_count, 0);
+	init_waitqueue_head(&i2400mu->rx_wq);
+	i2400mu->rx_size = PAGE_SIZE - sizeof(struct skb_shared_info);
+	atomic_set(&i2400mu->do_autopm, 1);
+	i2400mu->rx_size_auto_shrink = 1;
+}
+
+extern int i2400mu_notification_setup(struct i2400mu *);
+extern void i2400mu_notification_release(struct i2400mu *);
+
+extern int i2400mu_rx_setup(struct i2400mu *);
+extern void i2400mu_rx_release(struct i2400mu *);
+extern void i2400mu_rx_kick(struct i2400mu *);
+
+extern int i2400mu_tx_setup(struct i2400mu *);
+extern void i2400mu_tx_release(struct i2400mu *);
+extern void i2400mu_bus_tx_kick(struct i2400m *);
+
+extern ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *,
+				       const struct i2400m_bootrom_header *,
+				       size_t, int);
+extern ssize_t i2400mu_bus_bm_wait_for_ack(struct i2400m *,
+					   struct i2400m_bootrom_header *,
+					   size_t);
+#endif /* #ifndef __I2400M_USB_H__ */
diff --git a/drivers/net/wimax/i2400m/usb-debug-levels.h b/drivers/net/wimax/i2400m/usb-debug-levels.h
new file mode 100644
index 0000000..e4358bd
--- /dev/null
+++ b/drivers/net/wimax/i2400m/usb-debug-levels.h
@@ -0,0 +1,42 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Debug levels control file for the i2400m-usb module
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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 __debug_levels__h__
+#define __debug_levels__h__
+
+/* Maximum compile and run time debug level for all submodules */
+#define D_MODULENAME i2400m_usb
+#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL
+
+#include <linux/wimax/debug.h>
+
+/* List of all the enabled modules */
+enum d_module {
+	D_SUBMODULE_DECLARE(usb),
+	D_SUBMODULE_DECLARE(fw),
+	D_SUBMODULE_DECLARE(notif),
+	D_SUBMODULE_DECLARE(rx),
+	D_SUBMODULE_DECLARE(tx),
+};
+
+
+#endif /* #ifndef __debug_levels__h__ */
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 20/29] i2400m/USB: probe/disconnect, dev init/shutdown and reset backends
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (18 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 19/29] i2400m/USB: header for the USB bus driver Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 21/29] i2400m/USB: firmware upload backend Inaky Perez-Gonzalez
                   ` (9 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements probe/disconnect for the USB device, as well as main
backends for the generic driver to control the USB device
(bus_dev_start(), bus_dev_stop() and bus_reset()).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/usb.c |  591 ++++++++++++++++++++++++++++++++++++++++
 1 files changed, 591 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/usb.c

diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c
new file mode 100644
index 0000000..6d4b65f
--- /dev/null
+++ b/drivers/net/wimax/i2400m/usb.c
@@ -0,0 +1,591 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Linux driver model glue for USB device, reset & fw upload
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *
+ * 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.
+ *
+ *
+ * See i2400m-usb.h for a general description of this driver.
+ *
+ * This file implements driver model glue, and hook ups for the
+ * generic driver to implement the bus-specific functions (device
+ * communication setup/tear down, firmware upload and resetting).
+ *
+ * ROADMAP
+ *
+ * i2400mu_probe()
+ *   alloc_netdev()...
+ *     i2400mu_netdev_setup()
+ *       i2400mu_init()
+ *       i2400m_netdev_setup()
+ *   i2400m_setup()...
+ *
+ * i2400mu_disconnect
+ *   i2400m_release()
+ *   free_netdev()
+ *
+ * i2400mu_suspend()
+ *   i2400m_cmd_enter_powersave()
+ *   i2400mu_notification_release()
+ *
+ * i2400mu_resume()
+ *   i2400mu_notification_setup()
+ *
+ * i2400mu_bus_dev_start()        Called by i2400m_dev_start() [who is
+ *   i2400mu_tx_setup()           called by i2400m_setup()]
+ *   i2400mu_rx_setup()
+ *   i2400mu_notification_setup()
+ *
+ * i2400mu_bus_dev_stop()         Called by i2400m_dev_stop() [who is
+ *   i2400mu_notification_release()  called by i2400m_release()]
+ *   i2400mu_rx_release()
+ *   i2400mu_tx_release()
+ *
+ * i2400mu_bus_reset()            Called by i2400m->bus_reset
+ *   __i2400mu_reset()
+ *     __i2400mu_send_barker()
+ *   usb_reset_device()
+ */
+#include "i2400m-usb.h"
+#include <linux/wimax/i2400m.h>
+#include <linux/debugfs.h>
+
+
+#define D_SUBMODULE usb
+#include "usb-debug-levels.h"
+
+
+/* Our firmware file name */
+#define I2400MU_FW_FILE_NAME "i2400m-fw-usb-" I2400M_FW_VERSION ".sbcf"
+
+static
+int i2400mu_bus_dev_start(struct i2400m *i2400m)
+{
+	int result;
+	struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
+	struct device *dev = &i2400mu->usb_iface->dev;
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	result = i2400mu_tx_setup(i2400mu);
+	if (result < 0)
+		goto error_usb_tx_setup;
+	result = i2400mu_rx_setup(i2400mu);
+	if (result < 0)
+		goto error_usb_rx_setup;
+	result = i2400mu_notification_setup(i2400mu);
+	if (result < 0)
+		goto error_notif_setup;
+	d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
+	return result;
+
+error_notif_setup:
+	i2400mu_rx_release(i2400mu);
+error_usb_rx_setup:
+	i2400mu_tx_release(i2400mu);
+error_usb_tx_setup:
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+	return result;
+}
+
+
+static
+void i2400mu_bus_dev_stop(struct i2400m *i2400m)
+{
+	struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
+	struct device *dev = &i2400mu->usb_iface->dev;
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	i2400mu_notification_release(i2400mu);
+	i2400mu_rx_release(i2400mu);
+	i2400mu_tx_release(i2400mu);
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+}
+
+
+/*
+ * Sends a barker buffer to the device
+ *
+ * This helper will allocate a kmalloced buffer and use it to transmit
+ * (then free it). Reason for this is that other arches cannot use
+ * stack/vmalloc/text areas for DMA transfers.
+ *
+ * Error recovery here is simpler: anything is considered a hard error
+ * and will move the reset code to use a last-resort bus-based reset.
+ */
+static
+int __i2400mu_send_barker(struct i2400mu *i2400mu,
+			  const __le32 *barker,
+			  size_t barker_size,
+			  unsigned endpoint)
+{
+	struct usb_endpoint_descriptor *epd = NULL;
+	int pipe, actual_len, ret;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	void *buffer;
+	int do_autopm = 1;
+
+	ret = usb_autopm_get_interface(i2400mu->usb_iface);
+	if (ret < 0) {
+		dev_err(dev, "RESET: can't get autopm: %d\n", ret);
+		do_autopm = 0;
+	}
+	ret = -ENOMEM;
+	buffer = kmalloc(barker_size, GFP_KERNEL);
+	if (buffer == NULL)
+		goto error_kzalloc;
+	epd = usb_get_epd(i2400mu->usb_iface, endpoint);
+	pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
+	memcpy(buffer, barker, barker_size);
+	ret = usb_bulk_msg(i2400mu->usb_dev, pipe, buffer, barker_size,
+			   &actual_len, HZ);
+	if (ret < 0) {
+		if (ret != -EINVAL)
+			dev_err(dev, "E: barker error: %d\n", ret);
+	} else if (actual_len != barker_size) {
+		dev_err(dev, "E: only %d bytes transmitted\n", actual_len);
+		ret = -EIO;
+	}
+	kfree(buffer);
+error_kzalloc:
+	if (do_autopm)
+		usb_autopm_put_interface(i2400mu->usb_iface);
+	return ret;
+}
+
+
+/*
+ * Reset a device at different levels (warm, cold or bus)
+ *
+ * @i2400m: device descriptor
+ * @reset_type: soft, warm or bus reset (I2400M_RT_WARM/SOFT/BUS)
+ *
+ * Warm and cold resets get a USB reset if they fail.
+ *
+ * Warm reset:
+ *
+ * The device will be fully reset internally, but won't be
+ * disconnected from the USB bus (so no reenumeration will
+ * happen). Firmware upload will be neccessary.
+ *
+ * The device will send a reboot barker in the notification endpoint
+ * that will trigger the driver to reinitialize the state
+ * automatically from notif.c:i2400m_notification_grok() into
+ * i2400m_dev_bootstrap_delayed().
+ *
+ * Cold and bus (USB) reset:
+ *
+ * The device will be fully reset internally, disconnected from the
+ * USB bus an a reenumeration will happen. Firmware upload will be
+ * neccessary. Thus, we don't do any locking or struct
+ * reinitialization, as we are going to be fully disconnected and
+ * reenumerated.
+ *
+ * Note we need to return -ENODEV if a warm reset was requested and we
+ * had to resort to a bus reset. See i2400m_op_reset(), wimax_reset()
+ * and wimax_dev->op_reset.
+ *
+ * WARNING: no driver state saved/fixed
+ */
+static
+int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
+{
+	int result;
+	struct i2400mu *i2400mu =
+		container_of(i2400m, struct i2400mu, i2400m);
+	struct device *dev = i2400m_dev(i2400m);
+	static const __le32 i2400m_WARM_BOOT_BARKER[4] = {
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+	};
+	static const __le32 i2400m_COLD_BOOT_BARKER[4] = {
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+	};
+
+	d_fnstart(3, dev, "(i2400m %p rt %u)\n", i2400m, rt);
+	if (rt == I2400M_RT_WARM)
+		result = __i2400mu_send_barker(i2400mu, i2400m_WARM_BOOT_BARKER,
+					       sizeof(i2400m_WARM_BOOT_BARKER),
+					       I2400MU_EP_BULK_OUT);
+	else if (rt == I2400M_RT_COLD)
+		result = __i2400mu_send_barker(i2400mu, i2400m_COLD_BOOT_BARKER,
+					       sizeof(i2400m_COLD_BOOT_BARKER),
+					       I2400MU_EP_RESET_COLD);
+	else if (rt == I2400M_RT_BUS) {
+do_bus_reset:
+		result = usb_reset_device(i2400mu->usb_dev);
+		switch (result) {
+		case 0:
+		case -EINVAL:	/* device is gone */
+		case -ENODEV:
+		case -ENOENT:
+		case -ESHUTDOWN:
+			result = rt == I2400M_RT_WARM ? -ENODEV : 0;
+			break;	/* We assume the device is disconnected */
+		default:
+			dev_err(dev, "USB reset failed (%d), giving up!\n",
+				result);
+		}
+	} else
+		BUG();
+	if (result < 0
+	    && result != -EINVAL	/* device is gone */
+	    && rt != I2400M_RT_BUS) {
+		dev_err(dev, "%s reset failed (%d); trying USB reset\n",
+			rt == I2400M_RT_WARM ? "warm" : "cold", result);
+		rt = I2400M_RT_BUS;
+		goto do_bus_reset;
+	}
+	d_fnend(3, dev, "(i2400m %p rt %u) = %d\n", i2400m, rt, result);
+	return result;
+}
+
+
+static
+void i2400mu_netdev_setup(struct net_device *net_dev)
+{
+	struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
+	struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
+	i2400mu_init(i2400mu);
+	i2400m_netdev_setup(net_dev);
+}
+
+
+/*
+ * Debug levels control; see debug.h
+ */
+struct d_level D_LEVEL[] = {
+	D_SUBMODULE_DEFINE(usb),
+	D_SUBMODULE_DEFINE(fw),
+	D_SUBMODULE_DEFINE(notif),
+	D_SUBMODULE_DEFINE(rx),
+	D_SUBMODULE_DEFINE(tx),
+};
+size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+
+
+#define __debugfs_register(prefix, name, parent)			\
+do {									\
+	result = d_level_register_debugfs(prefix, name, parent);	\
+	if (result < 0)							\
+		goto error;						\
+} while (0)
+
+
+static
+int i2400mu_debugfs_add(struct i2400mu *i2400mu)
+{
+	int result;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	struct dentry *dentry = i2400mu->i2400m.wimax_dev.debugfs_dentry;
+	struct dentry *fd;
+
+	dentry = debugfs_create_dir("i2400m-usb", dentry);
+	result = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		if (result == -ENODEV)
+			result = 0;	/* No debugfs support */
+		goto error;
+	}
+	i2400mu->debugfs_dentry = dentry;
+	__debugfs_register("dl_", usb, dentry);
+	__debugfs_register("dl_", fw, dentry);
+	__debugfs_register("dl_", notif, dentry);
+	__debugfs_register("dl_", rx, dentry);
+	__debugfs_register("dl_", tx, dentry);
+
+	/* Don't touch these if you don't know what you are doing */
+	fd = debugfs_create_u8("rx_size_auto_shrink", 0600, dentry,
+			       &i2400mu->rx_size_auto_shrink);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"rx_size_auto_shrink: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_size_t("rx_size", 0600, dentry,
+				   &i2400mu->rx_size);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"rx_size: %d\n", result);
+		goto error;
+	}
+
+	return 0;
+
+error:
+	debugfs_remove_recursive(i2400mu->debugfs_dentry);
+	return result;
+}
+
+
+/*
+ * Probe a i2400m 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.
+ *
+ * Alloc a net device, initialize the bus-specific details and then
+ * calls the bus-generic initialization routine. That will register
+ * the wimax and netdev devices, upload the firmware [using
+ * _bus_bm_*()], call _bus_dev_start() to finalize the setup of the
+ * communication with the device and then will start to talk to it to
+ * finnish setting it up.
+ */
+static
+int i2400mu_probe(struct usb_interface *iface,
+		  const struct usb_device_id *id)
+{
+	int result;
+	struct net_device *net_dev;
+	struct device *dev = &iface->dev;
+	struct i2400m *i2400m;
+	struct i2400mu *i2400mu;
+	struct usb_device *usb_dev = interface_to_usbdev(iface);
+
+	if (usb_dev->speed != USB_SPEED_HIGH)
+		dev_err(dev, "device not connected as high speed\n");
+
+	/* Allocate instance [calls i2400m_netdev_setup() on it]. */
+	result = -ENOMEM;
+	net_dev = alloc_netdev(sizeof(*i2400mu), "wmx%d",
+			       i2400mu_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);
+	i2400m = net_dev_to_i2400m(net_dev);
+	i2400mu = container_of(i2400m, struct i2400mu, i2400m);
+	i2400m->wimax_dev.net_dev = net_dev;
+	i2400mu->usb_dev = usb_get_dev(usb_dev);
+	i2400mu->usb_iface = iface;
+	usb_set_intfdata(iface, i2400mu);
+
+	i2400m->bus_tx_block_size = I2400MU_BLK_SIZE;
+	i2400m->bus_pl_size_max = I2400MU_PL_SIZE_MAX;
+	i2400m->bus_dev_start = i2400mu_bus_dev_start;
+	i2400m->bus_dev_stop = i2400mu_bus_dev_stop;
+	i2400m->bus_tx_kick = i2400mu_bus_tx_kick;
+	i2400m->bus_reset = i2400mu_bus_reset;
+	i2400m->bus_bm_cmd_send = i2400mu_bus_bm_cmd_send;
+	i2400m->bus_bm_wait_for_ack = i2400mu_bus_bm_wait_for_ack;
+	i2400m->bus_fw_name = I2400MU_FW_FILE_NAME;
+	i2400m->bus_bm_mac_addr_impaired = 0;
+
+	iface->needs_remote_wakeup = 1;		/* autosuspend (15s delay) */
+	device_init_wakeup(dev, 1);
+	usb_autopm_enable(i2400mu->usb_iface);
+	usb_dev->autosuspend_delay = 15 * HZ;
+	usb_dev->autosuspend_disabled = 0;
+
+	result = i2400m_setup(i2400m, I2400M_BRI_MAC_REINIT);
+	if (result < 0) {
+		dev_err(dev, "cannot setup device: %d\n", result);
+		goto error_setup;
+	}
+	result = i2400mu_debugfs_add(i2400mu);
+	if (result < 0) {
+		dev_err(dev, "Can't register i2400mu's debugfs: %d\n", result);
+		goto error_debugfs_add;
+	}
+	return 0;
+
+error_debugfs_add:
+	i2400m_release(i2400m);
+error_setup:
+	usb_set_intfdata(iface, NULL);
+	usb_put_dev(i2400mu->usb_dev);
+	free_netdev(net_dev);
+error_alloc_netdev:
+	return result;
+}
+
+
+/*
+ * Disconect a i2400m from the system.
+ *
+ * i2400m_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 i2400m, free and kill.
+ */
+static
+void i2400mu_disconnect(struct usb_interface *iface)
+{
+	struct i2400mu *i2400mu = usb_get_intfdata(iface);
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct net_device *net_dev = i2400m->wimax_dev.net_dev;
+	struct device *dev = &iface->dev;
+
+	d_fnstart(3, dev, "(iface %p i2400m %p)\n", iface, i2400m);
+
+	debugfs_remove_recursive(i2400mu->debugfs_dentry);
+	i2400m_release(i2400m);
+	usb_set_intfdata(iface, NULL);
+	usb_put_dev(i2400mu->usb_dev);
+	free_netdev(net_dev);
+	d_fnend(3, dev, "(iface %p i2400m %p) = void\n", iface, i2400m);
+}
+
+
+/*
+ * Get the device ready for USB port or system standby and hibernation
+ *
+ * USB port and system standby are handled the same.
+ *
+ * When the system hibernates, the USB device is powered down and then
+ * up, so we don't really have to do much here, as it will be seen as
+ * a reconnect. Still for simplicity we consider this case the same as
+ * suspend, so that the device has a chance to do notify the base
+ * station (if connected).
+ *
+ * So at the end, the three cases require common handling.
+ *
+ * If at the time of this call the device's firmware is not loaded,
+ * nothing has to be done.
+ *
+ * If the firmware is loaded, we need to:
+ *
+ *  - tell the device to go into host interface power save mode, wait
+ *    for it to ack
+ *
+ *    This is quite more interesting than it is; we need to execute a
+ *    command, but this time, we don't want the code in usb-{tx,rx}.c
+ *    to call the usb_autopm_get/put_interface() barriers as it'd
+ *    deadlock, so we need to decrement i2400mu->do_autopm, that acts
+ *    as a poor man's semaphore. Ugly, but it works.
+ *
+ *    As well, the device might refuse going to sleep for whichever
+ *    reason. In this case we just fail. For system suspend/hibernate,
+ *    we *can't* fail. We look at usb_dev->auto_pm to see if the
+ *    suspend call comes from the USB stack or from the system and act
+ *    in consequence.
+ *
+ *  - stop the notification endpoint polling
+ */
+static
+int i2400mu_suspend(struct usb_interface *iface, pm_message_t pm_msg)
+{
+	int result = 0;
+	struct device *dev = &iface->dev;
+	struct i2400mu *i2400mu = usb_get_intfdata(iface);
+	struct usb_device *usb_dev = i2400mu->usb_dev;
+	struct i2400m *i2400m = &i2400mu->i2400m;
+
+	d_fnstart(3, dev, "(iface %p pm_msg %u)\n", iface, pm_msg.event);
+	if (i2400m->updown == 0)
+		goto no_firmware;
+	d_printf(1, dev, "fw up, requesting standby\n");
+	atomic_dec(&i2400mu->do_autopm);
+	result = i2400m_cmd_enter_powersave(i2400m);
+	atomic_inc(&i2400mu->do_autopm);
+	if (result < 0 && usb_dev->auto_pm == 0) {
+		/* System suspend, can't fail */
+		dev_err(dev, "failed to suspend, will reset on resume\n");
+		result = 0;
+	}
+	if (result < 0)
+		goto error_enter_powersave;
+	i2400mu_notification_release(i2400mu);
+	d_printf(1, dev, "fw up, got standby\n");
+error_enter_powersave:
+no_firmware:
+	d_fnend(3, dev, "(iface %p pm_msg %u) = %d\n",
+		iface, pm_msg.event, result);
+	return result;
+}
+
+
+static
+int i2400mu_resume(struct usb_interface *iface)
+{
+	int ret = 0;
+	struct device *dev = &iface->dev;
+	struct i2400mu *i2400mu = usb_get_intfdata(iface);
+	struct i2400m *i2400m = &i2400mu->i2400m;
+
+	d_fnstart(3, dev, "(iface %p)\n", iface);
+	if (i2400m->updown == 0) {
+		d_printf(1, dev, "fw was down, no resume neeed\n");
+		goto out;
+	}
+	d_printf(1, dev, "fw was up, resuming\n");
+	i2400mu_notification_setup(i2400mu);
+	/* USB has flow control, so we don't need to give it time to
+	 * come back; otherwise, we'd use something like a get-state
+	 * command... */
+out:
+	d_fnend(3, dev, "(iface %p) = %d\n", iface, ret);
+	return ret;
+}
+
+
+static
+struct usb_device_id i2400mu_id_table[] = {
+	{ USB_DEVICE(0x8086, 0x0181) },
+	{ USB_DEVICE(0x8086, 0x1403) },
+	{ USB_DEVICE(0x8086, 0x1405) },
+	{ USB_DEVICE(0x8086, 0x0180) },
+	{ USB_DEVICE(0x8086, 0x0182) },
+	{ USB_DEVICE(0x8086, 0x1406) },
+	{ USB_DEVICE(0x8086, 0x1403) },
+	{ },
+};
+MODULE_DEVICE_TABLE(usb, i2400mu_id_table);
+
+
+static
+struct usb_driver i2400mu_driver = {
+	.name = KBUILD_MODNAME,
+	.suspend = i2400mu_suspend,
+	.resume = i2400mu_resume,
+	.probe = i2400mu_probe,
+	.disconnect = i2400mu_disconnect,
+	.id_table = i2400mu_id_table,
+	.supports_autosuspend = 1,
+};
+
+static
+int __init i2400mu_driver_init(void)
+{
+	return usb_register(&i2400mu_driver);
+}
+module_init(i2400mu_driver_init);
+
+
+static
+void __exit i2400mu_driver_exit(void)
+{
+	flush_scheduled_work();	/* for the stuff we schedule from sysfs.c */
+	usb_deregister(&i2400mu_driver);
+}
+module_exit(i2400mu_driver_exit);
+
+MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
+MODULE_DESCRIPTION("Intel 2400M WiMAX networking for USB");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(I2400MU_FW_FILE_NAME);
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 21/29] i2400m/USB: firmware upload backend
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (19 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 20/29] i2400m/USB: probe/disconnect, dev init/shutdown and reset backends Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 22/29] i2400m/USB: TX and RX path backends Inaky Perez-Gonzalez
                   ` (8 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This implements the backends for the generic driver (i2400m) to be
able to load firmware to the USB device.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/usb-fw.c |  340 +++++++++++++++++++++++++++++++++++++
 1 files changed, 340 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/usb-fw.c

diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c
new file mode 100644
index 0000000..5ad287c
--- /dev/null
+++ b/drivers/net/wimax/i2400m/usb-fw.c
@@ -0,0 +1,340 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Firmware uploader's USB specifics
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Initial implementation
+ *
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - bus generic/specific split
+ *
+ * THE PROCEDURE
+ *
+ * See fw.c for the generic description of this procedure.
+ *
+ * This file implements only the USB specifics. It boils down to how
+ * to send a command and waiting for an acknowledgement from the
+ * device.
+ *
+ * This code (and process) is single threaded. It assumes it is the
+ * only thread poking around (guaranteed by fw.c).
+ *
+ * COMMAND EXECUTION
+ *
+ * A write URB is posted with the buffer to the bulk output endpoint.
+ *
+ * ACK RECEPTION
+ *
+ * We just post a URB to the notification endpoint and wait for
+ * data. We repeat until we get all the data we expect (as indicated
+ * by the call from the bus generic code).
+ *
+ * The data is not read from the bulk in endpoint for boot mode.
+ *
+ * ROADMAP
+ *
+ * i2400mu_bus_bm_cmd_send
+ *   i2400m_bm_cmd_prepare...
+ *   i2400mu_tx_bulk_out
+ *
+ * i2400mu_bus_bm_wait_for_ack
+ *   i2400m_notif_submit
+ */
+#include <linux/usb.h>
+#include "i2400m-usb.h"
+
+
+#define D_SUBMODULE fw
+#include "usb-debug-levels.h"
+
+
+/*
+ * Synchronous write to the device
+ *
+ * Takes care of updating EDC counts and thus, handle device errors.
+ */
+static
+ssize_t i2400mu_tx_bulk_out(struct i2400mu *i2400mu, void *buf, size_t buf_size)
+{
+	int result;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	int len;
+	struct usb_endpoint_descriptor *epd;
+	int pipe, do_autopm = 1;
+
+	result = usb_autopm_get_interface(i2400mu->usb_iface);
+	if (result < 0) {
+		dev_err(dev, "BM-CMD: can't get autopm: %d\n", result);
+		do_autopm = 0;
+	}
+	epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_OUT);
+	pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
+retry:
+	result = usb_bulk_msg(i2400mu->usb_dev, pipe, buf, buf_size, &len, HZ);
+	switch (result) {
+	case 0:
+		if (len != buf_size) {
+			dev_err(dev, "BM-CMD: short write (%u B vs %zu "
+				"expected)\n", len, buf_size);
+			result = -EIO;
+			break;
+		}
+		result = len;
+		break;
+	case -EINVAL:			/* while removing driver */
+	case -ENODEV:			/* dev disconnect ... */
+	case -ENOENT:			/* just ignore it */
+	case -ESHUTDOWN:		/* and exit */
+	case -ECONNRESET:
+		result = -ESHUTDOWN;
+		break;
+	case -ETIMEDOUT:			/* bah... */
+		break;
+	default:				/* any other? */
+		if (edc_inc(&i2400mu->urb_edc,
+			    EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
+				dev_err(dev, "BM-CMD: maximum errors in "
+					"URB exceeded; resetting device\n");
+				usb_queue_reset_device(i2400mu->usb_iface);
+				result = -ENODEV;
+				break;
+		}
+		dev_err(dev, "BM-CMD: URB error %d, retrying\n",
+			result);
+		goto retry;
+	}
+	result = len;
+	if (do_autopm)
+		usb_autopm_put_interface(i2400mu->usb_iface);
+	return result;
+}
+
+
+/*
+ * Send a boot-mode command over the bulk-out pipe
+ *
+ * Command can be a raw command, which requires no preparation (and
+ * which might not even be following the command format). Checks that
+ * the right amount of data was transfered.
+ *
+ * To satisfy USB requirements (no onstack, vmalloc or in data segment
+ * buffers), we copy the command to i2400m->bm_cmd_buf and send it from
+ * there.
+ *
+ * @flags: pass thru from i2400m_bm_cmd()
+ * @return: cmd_size if ok, < 0 errno code on error.
+ */
+ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *i2400m,
+				const struct i2400m_bootrom_header *_cmd,
+				size_t cmd_size, int flags)
+{
+	ssize_t result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
+	int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd);
+	struct i2400m_bootrom_header *cmd;
+	size_t cmd_size_a = ALIGN(cmd_size, 16);	/* USB restriction */
+
+	d_fnstart(8, dev, "(i2400m %p cmd %p size %zu)\n",
+		  i2400m, _cmd, cmd_size);
+	result = -E2BIG;
+	if (cmd_size > I2400M_BM_CMD_BUF_SIZE)
+		goto error_too_big;
+	memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size);
+	cmd = i2400m->bm_cmd_buf;
+	if (cmd_size_a > cmd_size)			/* Zero pad space */
+		memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size);
+	if ((flags & I2400M_BM_CMD_RAW) == 0) {
+		if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0))
+			dev_warn(dev, "SW BUG: response_required == 0\n");
+		i2400m_bm_cmd_prepare(cmd);
+	}
+	result = i2400mu_tx_bulk_out(i2400mu, i2400m->bm_cmd_buf, cmd_size);
+	if (result < 0) {
+		dev_err(dev, "boot-mode cmd %d: cannot send: %zd\n",
+			opcode, result);
+		goto error_cmd_send;
+	}
+	if (result != cmd_size) {		/* all was transferred? */
+		dev_err(dev, "boot-mode cmd %d: incomplete transfer "
+			"(%zu vs %zu submitted)\n",  opcode, result, cmd_size);
+		result = -EIO;
+		goto error_cmd_size;
+	}
+error_cmd_size:
+error_cmd_send:
+error_too_big:
+	d_fnend(8, dev, "(i2400m %p cmd %p size %zu) = %zd\n",
+		i2400m, _cmd, cmd_size, result);
+	return result;
+}
+
+
+static
+void __i2400mu_bm_notif_cb(struct urb *urb)
+{
+	complete(urb->context);
+}
+
+
+/*
+ * submit a read to the notification endpoint
+ *
+ * @i2400m: device descriptor
+ * @urb: urb to use
+ * @completion: completion varible to complete when done
+ *
+ * Data is always read to i2400m->bm_ack_buf
+ */
+static
+int i2400mu_notif_submit(struct i2400mu *i2400mu, struct urb *urb,
+			 struct completion *completion)
+{
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct usb_endpoint_descriptor *epd;
+	int pipe;
+
+	epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION);
+	pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress);
+	usb_fill_int_urb(urb, i2400mu->usb_dev, pipe,
+			 i2400m->bm_ack_buf, I2400M_BM_ACK_BUF_SIZE,
+			 __i2400mu_bm_notif_cb, completion,
+			 epd->bInterval);
+	return usb_submit_urb(urb, GFP_KERNEL);
+}
+
+
+/*
+ * Read an ack from  the notification endpoint
+ *
+ * @i2400m:
+ * @_ack: pointer to where to store the read data
+ * @ack_size: how many bytes we should read
+ *
+ * Returns: < 0 errno code on error; otherwise, amount of received bytes.
+ *
+ * Submits a notification read, appends the read data to the given ack
+ * buffer and then repeats (until @ack_size bytes have been
+ * received).
+ */
+ssize_t i2400mu_bus_bm_wait_for_ack(struct i2400m *i2400m,
+				    struct i2400m_bootrom_header *_ack,
+				    size_t ack_size)
+{
+	ssize_t result = -ENOMEM;
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
+	struct urb notif_urb;
+	void *ack = _ack;
+	size_t offset, len;
+	long val;
+	int do_autopm = 1;
+	DECLARE_COMPLETION_ONSTACK(notif_completion);
+
+	d_fnstart(8, dev, "(i2400m %p ack %p size %zu)\n",
+		  i2400m, ack, ack_size);
+	BUG_ON(_ack == i2400m->bm_ack_buf);
+	result = usb_autopm_get_interface(i2400mu->usb_iface);
+	if (result < 0) {
+		dev_err(dev, "BM-ACK: can't get autopm: %d\n", (int) result);
+		do_autopm = 0;
+	}
+	usb_init_urb(&notif_urb);	/* ready notifications */
+	usb_get_urb(&notif_urb);
+	offset = 0;
+	while (offset < ack_size) {
+		init_completion(&notif_completion);
+		result = i2400mu_notif_submit(i2400mu, &notif_urb,
+					      &notif_completion);
+		if (result < 0)
+			goto error_notif_urb_submit;
+		val = wait_for_completion_interruptible_timeout(
+			&notif_completion, HZ);
+		if (val == 0) {
+			result = -ETIMEDOUT;
+			usb_kill_urb(&notif_urb);	/* Timedout */
+			goto error_notif_wait;
+		}
+		if (val == -ERESTARTSYS) {
+			result = -EINTR;		/* Interrupted */
+			usb_kill_urb(&notif_urb);
+			goto error_notif_wait;
+		}
+		result = notif_urb.status;		/* How was the ack? */
+		switch (result) {
+		case 0:
+			break;
+		case -EINVAL:			/* while removing driver */
+		case -ENODEV:			/* dev disconnect ... */
+		case -ENOENT:			/* just ignore it */
+		case -ESHUTDOWN:		/* and exit */
+		case -ECONNRESET:
+			result = -ESHUTDOWN;
+			goto error_dev_gone;
+		default:				/* any other? */
+			usb_kill_urb(&notif_urb);	/* Timedout */
+			if (edc_inc(&i2400mu->urb_edc,
+				    EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME))
+				goto error_exceeded;
+			dev_err(dev, "BM-ACK: URB error %d, "
+				"retrying\n", notif_urb.status);
+			continue;	/* retry */
+		}
+		if (notif_urb.actual_length == 0) {
+			d_printf(6, dev, "ZLP received, retrying\n");
+			continue;
+		}
+		/* Got data, append it to the buffer */
+		len = min(ack_size - offset, (size_t) notif_urb.actual_length);
+		memcpy(ack + offset, i2400m->bm_ack_buf, len);
+		offset += len;
+	}
+	result = offset;
+error_notif_urb_submit:
+error_notif_wait:
+error_dev_gone:
+out:
+	if (do_autopm)
+		usb_autopm_put_interface(i2400mu->usb_iface);
+	d_fnend(8, dev, "(i2400m %p ack %p size %zu) = %zd\n",
+		i2400m, ack, ack_size, result);
+	return result;
+
+error_exceeded:
+	dev_err(dev, "bm: maximum errors in notification URB exceeded; "
+		"resetting device\n");
+	usb_queue_reset_device(i2400mu->usb_iface);
+	goto out;
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 22/29] i2400m/USB: TX and RX path backends
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (20 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 21/29] i2400m/USB: firmware upload backend Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 23/29] i2400m/SDIO: header for the SDIO subdriver Inaky Perez-Gonzalez
                   ` (7 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements the backend so that the generic driver can TX/RX to/from
the USB device.

TX is implemented with a kthread sitting in a never-ending loop that
when kicked by the generic driver's TX code will pull data from the TX
FIFO and send it to the device until it drains it. Then it goes back
sleep, waiting for another kick.

RX is implemented in a similar fashion, but reads are kicked in by the
device notifying in the interrupt endpoint that data is ready. Device
reset notifications are also sent via the notification endpoint.

We need a thread contexts to run USB autopm functions (blocking) and
to process the received data (can get to be heavy in processing
time).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/usb-notif.c |  269 ++++++++++++++++++++++
 drivers/net/wimax/i2400m/usb-rx.c    |  417 ++++++++++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/usb-tx.c    |  229 +++++++++++++++++++
 3 files changed, 915 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/usb-notif.c
 create mode 100644 drivers/net/wimax/i2400m/usb-rx.c
 create mode 100644 drivers/net/wimax/i2400m/usb-tx.c

diff --git a/drivers/net/wimax/i2400m/usb-notif.c b/drivers/net/wimax/i2400m/usb-notif.c
new file mode 100644
index 0000000..9702c22
--- /dev/null
+++ b/drivers/net/wimax/i2400m/usb-notif.c
@@ -0,0 +1,269 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m over USB
+ * Notification handling
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * The notification endpoint is active when the device is not in boot
+ * mode; in here we just read and get notifications; based on those,
+ * we act to either reinitialize the device after a reboot or to
+ * submit a RX request.
+ *
+ * ROADMAP
+ *
+ * i2400mu_usb_notification_setup()
+ *
+ * i2400mu_usb_notification_release()
+ *
+ * i2400mu_usb_notification_cb()	Called when a URB is ready
+ *   i2400mu_notif_grok()
+ *     i2400m_dev_reset_handle()
+ *     i2400mu_rx_kick()
+ */
+#include <linux/usb.h>
+#include "i2400m-usb.h"
+
+
+#define D_SUBMODULE notif
+#include "usb-debug-levels.h"
+
+
+static const
+__le32 i2400m_ZERO_BARKER[4] = { 0, 0, 0, 0 };
+
+
+/*
+ * Process a received notification
+ *
+ * In normal operation mode, we can only receive two types of payloads
+ * on the notification endpoint:
+ *
+ *   - a reboot barker, we do a bootstrap (the device has reseted).
+ *
+ *   - a block of zeroes: there is pending data in the IN endpoint
+ */
+static
+int i2400mu_notification_grok(struct i2400mu *i2400mu, const void *buf,
+				 size_t buf_len)
+{
+	int ret;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	struct i2400m *i2400m = &i2400mu->i2400m;
+
+	d_fnstart(4, dev, "(i2400m %p buf %p buf_len %zu)\n",
+		  i2400mu, buf, buf_len);
+	ret = -EIO;
+	if (buf_len < sizeof(i2400m_NBOOT_BARKER))
+		/* Not a bug, just ignore */
+		goto error_bad_size;
+	if (!memcmp(i2400m_NBOOT_BARKER, buf, sizeof(i2400m_NBOOT_BARKER))
+	    || !memcmp(i2400m_SBOOT_BARKER, buf, sizeof(i2400m_SBOOT_BARKER)))
+		ret = i2400m_dev_reset_handle(i2400m);
+	else if (!memcmp(i2400m_ZERO_BARKER, buf, sizeof(i2400m_ZERO_BARKER))) {
+		i2400mu_rx_kick(i2400mu);
+		ret = 0;
+	} else {	/* Unknown or unexpected data in the notif message */
+		char prefix[64];
+		ret = -EIO;
+		dev_err(dev, "HW BUG? Unknown/unexpected data in notification "
+			"message (%zu bytes)\n", buf_len);
+		snprintf(prefix, sizeof(prefix), "%s %s: ",
+			 dev_driver_string(dev) , dev->bus_id);
+		if (buf_len > 64) {
+			print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET,
+				       8, 4, buf, 64, 0);
+			printk(KERN_ERR "%s... (only first 64 bytes "
+			       "dumped)\n", prefix);
+		} else
+			print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET,
+				       8, 4, buf, buf_len, 0);
+	}
+error_bad_size:
+	d_fnend(4, dev, "(i2400m %p buf %p buf_len %zu) = %d\n",
+		i2400mu, buf, buf_len, ret);
+	return ret;
+}
+
+
+/*
+ * URB callback for the notification endpoint
+ *
+ * @urb: the urb received from the notification endpoint
+ *
+ * This function will just process the USB side of the transaction,
+ * checking everything is fine, pass the processing to
+ * i2400m_notification_grok() and resubmit the URB.
+ */
+static
+void i2400mu_notification_cb(struct urb *urb)
+{
+	int ret;
+	struct i2400mu *i2400mu = urb->context;
+	struct device *dev = &i2400mu->usb_iface->dev;
+
+	d_fnstart(4, dev, "(urb %p status %d actual_length %d)\n",
+		  urb, urb->status, urb->actual_length);
+	ret = urb->status;
+	switch (ret) {
+	case 0:
+		ret = i2400mu_notification_grok(i2400mu, urb->transfer_buffer,
+						urb->actual_length);
+		if (ret == -EIO && edc_inc(&i2400mu->urb_edc, EDC_MAX_ERRORS,
+					   EDC_ERROR_TIMEFRAME))
+			goto error_exceeded;
+		if (ret == -ENOMEM)	/* uff...power cycle? shutdown? */
+			goto error_exceeded;
+		break;
+	case -EINVAL:			/* while removing driver */
+	case -ENODEV:			/* dev disconnect ... */
+	case -ENOENT:			/* ditto */
+	case -ESHUTDOWN:		/* URB killed */
+	case -ECONNRESET:		/* disconnection */
+		goto out;		/* Notify around */
+	default:			/* Some error? */
+		if (edc_inc(&i2400mu->urb_edc,
+			    EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME))
+			goto error_exceeded;
+		dev_err(dev, "notification: URB error %d, retrying\n",
+			urb->status);
+	}
+	usb_mark_last_busy(i2400mu->usb_dev);
+	ret = usb_submit_urb(i2400mu->notif_urb, GFP_ATOMIC);
+	switch (ret) {
+	case 0:
+	case -EINVAL:			/* while removing driver */
+	case -ENODEV:			/* dev disconnect ... */
+	case -ENOENT:			/* ditto */
+	case -ESHUTDOWN:		/* URB killed */
+	case -ECONNRESET:		/* disconnection */
+		break;			/* just ignore */
+	default:			/* Some error? */
+		dev_err(dev, "notification: cannot submit URB: %d\n", ret);
+		goto error_submit;
+	}
+	d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n",
+		urb, urb->status, urb->actual_length);
+	return;
+
+error_exceeded:
+	dev_err(dev, "maximum errors in notification URB exceeded; "
+		"resetting device\n");
+error_submit:
+	usb_queue_reset_device(i2400mu->usb_iface);
+out:
+	d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n",
+		urb, urb->status, urb->actual_length);
+	return;
+}
+
+
+/*
+ * setup the notification endpoint
+ *
+ * @i2400m: device descriptor
+ *
+ * This procedure prepares the notification urb and handler for receiving
+ * unsolicited barkers from the device.
+ */
+int i2400mu_notification_setup(struct i2400mu *i2400mu)
+{
+	struct device *dev = &i2400mu->usb_iface->dev;
+	int usb_pipe, ret = 0;
+	struct usb_endpoint_descriptor *epd;
+	char *buf;
+
+	d_fnstart(4, dev, "(i2400m %p)\n", i2400mu);
+	buf = kmalloc(I2400MU_MAX_NOTIFICATION_LEN, GFP_KERNEL | GFP_DMA);
+	if (buf == NULL) {
+		dev_err(dev, "notification: buffer allocation failed\n");
+		ret = -ENOMEM;
+		goto error_buf_alloc;
+	}
+
+	i2400mu->notif_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!i2400mu->notif_urb) {
+		ret = -ENOMEM;
+		dev_err(dev, "notification: cannot allocate URB\n");
+		goto error_alloc_urb;
+	}
+	epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION);
+	usb_pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress);
+	usb_fill_int_urb(i2400mu->notif_urb, i2400mu->usb_dev, usb_pipe,
+			 buf, I2400MU_MAX_NOTIFICATION_LEN,
+			 i2400mu_notification_cb, i2400mu, epd->bInterval);
+	ret = usb_submit_urb(i2400mu->notif_urb, GFP_KERNEL);
+	if (ret != 0) {
+		dev_err(dev, "notification: cannot submit URB: %d\n", ret);
+		goto error_submit;
+	}
+	d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret);
+	return ret;
+
+error_submit:
+	usb_free_urb(i2400mu->notif_urb);
+error_alloc_urb:
+	kfree(buf);
+error_buf_alloc:
+	d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret);
+	return ret;
+}
+
+
+/*
+ * Tear down of the notification mechanism
+ *
+ * @i2400m: device descriptor
+ *
+ * Kill the interrupt endpoint urb, free any allocated resources.
+ *
+ * We need to check if we have done it before as for example,
+ * _suspend() call this; if after a suspend() we get a _disconnect()
+ * (as the case is when hibernating), nothing bad happens.
+ */
+void i2400mu_notification_release(struct i2400mu *i2400mu)
+{
+	struct device *dev = &i2400mu->usb_iface->dev;
+
+	d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
+	if (i2400mu->notif_urb != NULL) {
+		usb_kill_urb(i2400mu->notif_urb);
+		kfree(i2400mu->notif_urb->transfer_buffer);
+		usb_free_urb(i2400mu->notif_urb);
+		i2400mu->notif_urb = NULL;
+	}
+	d_fnend(4, dev, "(i2400mu %p)\n", i2400mu);
+}
diff --git a/drivers/net/wimax/i2400m/usb-rx.c b/drivers/net/wimax/i2400m/usb-rx.c
new file mode 100644
index 0000000..074cc1f
--- /dev/null
+++ b/drivers/net/wimax/i2400m/usb-rx.c
@@ -0,0 +1,417 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * USB RX handling
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *  - Initial implementation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Use skb_clone(), break up processing in chunks
+ *  - Split transport/device specific
+ *  - Make buffer size dynamic to exert less memory pressure
+ *
+ *
+ * This handles the RX path on USB.
+ *
+ * When a notification is received that says 'there is RX data ready',
+ * we call i2400mu_rx_kick(); that wakes up the RX kthread, which
+ * reads a buffer from USB and passes it to i2400m_rx() in the generic
+ * handling code. The RX buffer has an specific format that is
+ * described in rx.c.
+ *
+ * We use a kernel thread in a loop because:
+ *
+ *  - we want to be able to call the USB power management get/put
+ *    functions (blocking) before each transaction.
+ *
+ *  - We might get a lot of notifications and we don't want to submit
+ *    a zillion reads; by serializing, we are throttling.
+ *
+ *  - RX data processing can get heavy enough so that it is not
+ *    appropiate for doing it in the USB callback; thus we run it in a
+ *    process context.
+ *
+ * We provide a read buffer of an arbitrary size (short of a page); if
+ * the callback reports -EOVERFLOW, it means it was too small, so we
+ * just double the size and retry (being careful to append, as
+ * sometimes the device provided some data). Every now and then we
+ * check if the average packet size is smaller than the current packet
+ * size and if so, we halve it. At the end, the size of the
+ * preallocated buffer should be following the average received
+ * transaction size, adapting dynamically to it.
+ *
+ * ROADMAP
+ *
+ * i2400mu_rx_kick()		   Called from notif.c when we get a
+ *   			           'data ready' notification
+ * i2400mu_rxd()                   Kernel RX daemon
+ *   i2400mu_rx()                  Receive USB data
+ *   i2400m_rx()                   Send data to generic i2400m RX handling
+ *
+ * i2400mu_rx_setup()              called from i2400mu_bus_dev_start()
+ *
+ * i2400mu_rx_release()            called from i2400mu_bus_dev_stop()
+ */
+#include <linux/workqueue.h>
+#include <linux/usb.h>
+#include "i2400m-usb.h"
+
+
+#define D_SUBMODULE rx
+#include "usb-debug-levels.h"
+
+/*
+ * Dynamic RX size
+ *
+ * We can't let the rx_size be a multiple of 512 bytes (the RX
+ * endpoint's max packet size). On some USB host controllers (we
+ * haven't been able to fully characterize which), if the device is
+ * about to send (for example) X bytes and we only post a buffer to
+ * receive n*512, it will fail to mark that as babble (so that
+ * i2400mu_rx() [case -EOVERFLOW] can resize the buffer and get the
+ * rest).
+ *
+ * So on growing or shrinking, if it is a multiple of the
+ * maxpacketsize, we remove some (instead of incresing some, so in a
+ * buddy allocator we try to waste less space).
+ *
+ * Note we also need a hook for this on i2400mu_rx() -- when we do the
+ * first read, we are sure we won't hit this spot because
+ * i240mm->rx_size has been set properly. However, if we have to
+ * double because of -EOVERFLOW, when we launch the read to get the
+ * rest of the data, we *have* to make sure that also is not a
+ * multiple of the max_pkt_size.
+ */
+
+static
+size_t i2400mu_rx_size_grow(struct i2400mu *i2400mu)
+{
+	struct device *dev = &i2400mu->usb_iface->dev;
+	size_t rx_size;
+	const size_t max_pkt_size = 512;
+
+	rx_size = 2 * i2400mu->rx_size;
+	if (rx_size % max_pkt_size == 0) {
+		rx_size -= 8;
+		d_printf(1, dev,
+			 "RX: expected size grew to %zu [adjusted -8] "
+			 "from %zu\n",
+			 rx_size, i2400mu->rx_size);
+	} else
+		d_printf(1, dev,
+			 "RX: expected size grew to %zu from %zu\n",
+			 rx_size, i2400mu->rx_size);
+	return rx_size;
+}
+
+
+static
+void i2400mu_rx_size_maybe_shrink(struct i2400mu *i2400mu)
+{
+	const size_t max_pkt_size = 512;
+	struct device *dev = &i2400mu->usb_iface->dev;
+
+	if (unlikely(i2400mu->rx_size_cnt >= 100
+		     && i2400mu->rx_size_auto_shrink)) {
+		size_t avg_rx_size =
+			i2400mu->rx_size_acc / i2400mu->rx_size_cnt;
+		size_t new_rx_size = i2400mu->rx_size / 2;
+		if (avg_rx_size < new_rx_size) {
+			if (new_rx_size % max_pkt_size == 0) {
+				new_rx_size -= 8;
+				d_printf(1, dev,
+					 "RX: expected size shrank to %zu "
+					 "[adjusted -8] from %zu\n",
+					 new_rx_size, i2400mu->rx_size);
+			} else
+				d_printf(1, dev,
+					 "RX: expected size shrank to %zu "
+					 "from %zu\n",
+					 new_rx_size, i2400mu->rx_size);
+			i2400mu->rx_size = new_rx_size;
+			i2400mu->rx_size_cnt = 0;
+			i2400mu->rx_size_acc = i2400mu->rx_size;
+		}
+	}
+}
+
+/*
+ * Receive a message with payloads from the USB bus into an skb
+ *
+ * @i2400mu: USB device descriptor
+ * @rx_skb: skb where to place the received message
+ *
+ * Deals with all the USB-specifics of receiving, dynamically
+ * increasing the buffer size if so needed. Returns the payload in the
+ * skb, ready to process. On a zero-length packet, we retry.
+ *
+ * On soft USB errors, we retry (until they become too frequent and
+ * then are promoted to hard); on hard USB errors, we reset the
+ * device. On other errors (skb realloacation, we just drop it and
+ * hope for the next invocation to solve it).
+ *
+ * Returns: pointer to the skb if ok, ERR_PTR on error.
+ *   NOTE: this function might realloc the skb (if it is too small),
+ *   so always update with the one returned.
+ *   ERR_PTR() is < 0 on error.
+ */
+static
+struct sk_buff *i2400mu_rx(struct i2400mu *i2400mu, struct sk_buff *rx_skb)
+{
+	int result = 0;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	int usb_pipe, read_size, rx_size, do_autopm;
+	struct usb_endpoint_descriptor *epd;
+	const size_t max_pkt_size = 512;
+
+	d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
+	do_autopm = atomic_read(&i2400mu->do_autopm);
+	result = do_autopm ?
+		usb_autopm_get_interface(i2400mu->usb_iface) : 0;
+	if (result < 0) {
+		dev_err(dev, "RX: can't get autopm: %d\n", result);
+		do_autopm = 0;
+	}
+	epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_IN);
+	usb_pipe = usb_rcvbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
+retry:
+	rx_size = skb_end_pointer(rx_skb) - rx_skb->data - rx_skb->len;
+	if (unlikely(rx_size % max_pkt_size == 0)) {
+		rx_size -= 8;
+		d_printf(1, dev, "RX: rx_size adapted to %d [-8]\n", rx_size);
+	}
+	result = usb_bulk_msg(
+		i2400mu->usb_dev, usb_pipe, rx_skb->data + rx_skb->len,
+		rx_size, &read_size, HZ);
+	usb_mark_last_busy(i2400mu->usb_dev);
+	switch (result) {
+	case 0:
+		if (read_size == 0)
+			goto retry;	/* ZLP, just resubmit */
+		skb_put(rx_skb, read_size);
+		break;
+	case -EINVAL:			/* while removing driver */
+	case -ENODEV:			/* dev disconnect ... */
+	case -ENOENT:			/* just ignore it */
+	case -ESHUTDOWN:
+	case -ECONNRESET:
+		break;
+	case -EOVERFLOW: {		/* too small, reallocate */
+		struct sk_buff *new_skb;
+		rx_size = i2400mu_rx_size_grow(i2400mu);
+		if (rx_size <= (1 << 16))	/* cap it */
+			i2400mu->rx_size = rx_size;
+		else if (printk_ratelimit()) {
+			dev_err(dev, "BUG? rx_size up to %d\n", rx_size);
+			result = -EINVAL;
+			goto out;
+		}
+		skb_put(rx_skb, read_size);
+		new_skb = skb_copy_expand(rx_skb, 0, rx_size - rx_skb->len,
+					  GFP_KERNEL);
+		if (new_skb == NULL) {
+			if (printk_ratelimit())
+				dev_err(dev, "RX: Can't reallocate skb to %d; "
+					"RX dropped\n", rx_size);
+			kfree(rx_skb);
+			result = 0;
+			goto out;	/* drop it...*/
+		}
+		kfree_skb(rx_skb);
+		rx_skb = new_skb;
+		i2400mu->rx_size_cnt = 0;
+		i2400mu->rx_size_acc = i2400mu->rx_size;
+		d_printf(1, dev, "RX: size changed to %d, received %d, "
+			 "copied %d, capacity %ld\n",
+			 rx_size, read_size, rx_skb->len,
+			 (long) (skb_end_pointer(new_skb) - new_skb->head));
+		goto retry;
+	}
+		/* In most cases, it happens due to the hardware scheduling a
+		 * read when there was no data - unfortunately, we have no way
+		 * to tell this timeout from a USB timeout. So we just ignore
+		 * it. */
+	case -ETIMEDOUT:
+		dev_err(dev, "RX: timeout: %d\n", result);
+		result = 0;
+		break;
+	default:			/* Any error */
+		if (edc_inc(&i2400mu->urb_edc,
+			    EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME))
+			goto error_reset;
+		dev_err(dev, "RX: error receiving URB: %d, retrying\n", result);
+		goto retry;
+	}
+out:
+	if (do_autopm)
+		usb_autopm_put_interface(i2400mu->usb_iface);
+	d_fnend(4, dev, "(i2400mu %p) = %p\n", i2400mu, rx_skb);
+	return rx_skb;
+
+error_reset:
+	dev_err(dev, "RX: maximum errors in URB exceeded; "
+		"resetting device\n");
+	usb_queue_reset_device(i2400mu->usb_iface);
+	rx_skb = ERR_PTR(result);
+	goto out;
+}
+
+
+/*
+ * Kernel thread for USB reception of data
+ *
+ * This thread waits for a kick; once kicked, it will allocate an skb
+ * and receive a single message to it from USB (using
+ * i2400mu_rx()). Once received, it is passed to the generic i2400m RX
+ * code for processing.
+ *
+ * When done processing, it runs some dirty statistics to verify if
+ * the last 100 messages received were smaller than half of the
+ * current RX buffer size. In that case, the RX buffer size is
+ * halved. This will helps lowering the pressure on the memory
+ * allocator.
+ *
+ * Hard errors force the thread to exit.
+ */
+static
+int i2400mu_rxd(void *_i2400mu)
+{
+	int result = 0;
+	struct i2400mu *i2400mu = _i2400mu;
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	struct net_device *net_dev = i2400m->wimax_dev.net_dev;
+	size_t pending;
+	int rx_size;
+	struct sk_buff *rx_skb;
+
+	d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
+	while (1) {
+		d_printf(2, dev, "TX: waiting for messages\n");
+		pending = 0;
+		wait_event_interruptible(
+			i2400mu->rx_wq,
+			(kthread_should_stop()	/* check this first! */
+			 || (pending = atomic_read(&i2400mu->rx_pending_count)))
+			);
+		if (kthread_should_stop())
+			break;
+		if (pending == 0)
+			continue;
+		rx_size = i2400mu->rx_size;
+		d_printf(2, dev, "RX: reading up to %d bytes\n", rx_size);
+		rx_skb = __netdev_alloc_skb(net_dev, rx_size, GFP_KERNEL);
+		if (rx_skb == NULL) {
+			dev_err(dev, "RX: can't allocate skb [%d bytes]\n",
+				rx_size);
+			msleep(50);	/* give it some time? */
+			continue;
+		}
+
+		/* Receive the message with the payloads */
+		rx_skb = i2400mu_rx(i2400mu, rx_skb);
+		result = PTR_ERR(rx_skb);
+		if (IS_ERR(rx_skb))
+			goto out;
+		atomic_dec(&i2400mu->rx_pending_count);
+		if (rx_skb->len == 0) {	/* some ignorable condition */
+			kfree_skb(rx_skb);
+			continue;
+		}
+
+		/* Deliver the message to the generic i2400m code */
+		i2400mu->rx_size_cnt++;
+		i2400mu->rx_size_acc += rx_skb->len;
+		result = i2400m_rx(i2400m, rx_skb);
+		if (result == -EIO
+		    && edc_inc(&i2400mu->urb_edc,
+			       EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
+			goto error_reset;
+		}
+
+		/* Maybe adjust RX buffer size */
+		i2400mu_rx_size_maybe_shrink(i2400mu);
+	}
+	result = 0;
+out:
+	d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result);
+	return result;
+
+error_reset:
+	dev_err(dev, "RX: maximum errors in received buffer exceeded; "
+		"resetting device\n");
+	usb_queue_reset_device(i2400mu->usb_iface);
+	goto out;
+}
+
+
+/*
+ * Start reading from the device
+ *
+ * @i2400m: device instance
+ *
+ * Notify the RX thread that there is data pending.
+ */
+void i2400mu_rx_kick(struct i2400mu *i2400mu)
+{
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct device *dev = &i2400mu->usb_iface->dev;
+
+	d_fnstart(3, dev, "(i2400mu %p)\n", i2400m);
+	atomic_inc(&i2400mu->rx_pending_count);
+	wake_up_all(&i2400mu->rx_wq);
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+}
+
+
+int i2400mu_rx_setup(struct i2400mu *i2400mu)
+{
+	int result = 0;
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+
+	i2400mu->rx_kthread = kthread_run(i2400mu_rxd, i2400mu, "%s-rx",
+					  wimax_dev->name);
+	if (IS_ERR(i2400mu->rx_kthread)) {
+		result = PTR_ERR(i2400mu->rx_kthread);
+		dev_err(dev, "RX: cannot start thread: %d\n", result);
+	}
+	return result;
+}
+
+void i2400mu_rx_release(struct i2400mu *i2400mu)
+{
+	kthread_stop(i2400mu->rx_kthread);
+}
+
diff --git a/drivers/net/wimax/i2400m/usb-tx.c b/drivers/net/wimax/i2400m/usb-tx.c
new file mode 100644
index 0000000..dfd8933
--- /dev/null
+++ b/drivers/net/wimax/i2400m/usb-tx.c
@@ -0,0 +1,229 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * USB specific TX handling
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *  - Initial implementation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Split transport/device specific
+ *
+ *
+ * Takes the TX messages in the i2400m's driver TX FIFO and sends them
+ * to the device until there are no more.
+ *
+ * If we fail sending the message, we just drop it. There isn't much
+ * we can do at this point. We could also retry, but the USB stack has
+ * already retried and still failed, so there is not much of a
+ * point. As well, most of the traffic is network, which has recovery
+ * methods for dropped packets.
+ *
+ * For sending we just obtain a FIFO buffer to send, send it to the
+ * USB bulk out, tell the TX FIFO code we have sent it; query for
+ * another one, etc... until done.
+ *
+ * We use a thread so we can call usb_autopm_enable() and
+ * usb_autopm_disable() for each transaction; this way when the device
+ * goes idle, it will suspend. It also has less overhead than a
+ * dedicated workqueue, as it is being used for a single task.
+ *
+ * ROADMAP
+ *
+ * i2400mu_tx_setup()
+ * i2400mu_tx_release()
+ *
+ * i2400mu_bus_tx_kick()	- Called by the tx.c code when there
+ *                                is new data in the FIFO.
+ * i2400mu_txd()
+ *   i2400m_tx_msg_get()
+ *   i2400m_tx_msg_sent()
+ */
+#include "i2400m-usb.h"
+
+
+#define D_SUBMODULE tx
+#include "usb-debug-levels.h"
+
+
+/*
+ * Get the next TX message in the TX FIFO and send it to the device
+ *
+ * Note that any iteration consumes a message to be sent, no matter if
+ * it succeeds or fails (we have no real way to retry or complain).
+ *
+ * Return: 0 if ok, < 0 errno code on hard error.
+ */
+static
+int i2400mu_tx(struct i2400mu *i2400mu, struct i2400m_msg_hdr *tx_msg,
+	       size_t tx_msg_size)
+{
+	int result = 0;
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	int usb_pipe, sent_size, do_autopm;
+	struct usb_endpoint_descriptor *epd;
+
+	d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
+	do_autopm = atomic_read(&i2400mu->do_autopm);
+	result = do_autopm ?
+		usb_autopm_get_interface(i2400mu->usb_iface) : 0;
+	if (result < 0) {
+		dev_err(dev, "TX: can't get autopm: %d\n", result);
+		do_autopm = 0;
+	}
+	epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_OUT);
+	usb_pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress);
+retry:
+	result = usb_bulk_msg(i2400mu->usb_dev, usb_pipe,
+			      tx_msg, tx_msg_size, &sent_size, HZ);
+	usb_mark_last_busy(i2400mu->usb_dev);
+	switch (result) {
+	case 0:
+		if (sent_size != tx_msg_size) {	/* Too short? drop it */
+			dev_err(dev, "TX: short write (%d B vs %zu "
+				"expected)\n", sent_size, tx_msg_size);
+			result = -EIO;
+		}
+		break;
+	case -EINVAL:			/* while removing driver */
+	case -ENODEV:			/* dev disconnect ... */
+	case -ENOENT:			/* just ignore it */
+	case -ESHUTDOWN:		/* and exit */
+	case -ECONNRESET:
+		result = -ESHUTDOWN;
+		break;
+	default:			/* Some error? */
+		if (edc_inc(&i2400mu->urb_edc,
+			    EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) {
+			dev_err(dev, "TX: maximum errors in URB "
+				"exceeded; resetting device\n");
+			usb_queue_reset_device(i2400mu->usb_iface);
+		} else {
+			dev_err(dev, "TX: cannot send URB; retrying. "
+				"tx_msg @%zu %zu B [%d sent]: %d\n",
+				(void *) tx_msg - i2400m->tx_buf,
+				tx_msg_size, sent_size, result);
+			goto retry;
+		}
+	}
+	if (do_autopm)
+		usb_autopm_put_interface(i2400mu->usb_iface);
+	d_fnend(4, dev, "(i2400mu %p) = result\n", i2400mu);
+	return result;
+}
+
+
+/*
+ * Get the next TX message in the TX FIFO and send it to the device
+ *
+ * Note we exit the loop if i2400mu_tx() fails; that funtion only
+ * fails on hard error (failing to tx a buffer not being one of them,
+ * see its doc).
+ *
+ * Return: 0
+ */
+static
+int i2400mu_txd(void *_i2400mu)
+{
+	int result = 0;
+	struct i2400mu *i2400mu = _i2400mu;
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	struct i2400m_msg_hdr *tx_msg;
+	size_t tx_msg_size;
+
+	d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu);
+
+	while (1) {
+		d_printf(2, dev, "TX: waiting for messages\n");
+		tx_msg = NULL;
+		wait_event_interruptible(
+			i2400mu->tx_wq,
+			(kthread_should_stop()	/* check this first! */
+			 || (tx_msg = i2400m_tx_msg_get(i2400m, &tx_msg_size)))
+			);
+		if (kthread_should_stop())
+			break;
+		WARN_ON(tx_msg == NULL);	/* should not happen...*/
+		d_printf(2, dev, "TX: submitting %zu bytes\n", tx_msg_size);
+		d_dump(5, dev, tx_msg, tx_msg_size);
+		/* Yeah, we ignore errors ... not much we can do */
+		i2400mu_tx(i2400mu, tx_msg, tx_msg_size);
+		i2400m_tx_msg_sent(i2400m);	/* ack it, advance the FIFO */
+		if (result < 0)
+			break;
+	}
+	d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result);
+	return result;
+}
+
+
+/*
+ * i2400m TX engine notifies us that there is data in the FIFO ready
+ * for TX
+ *
+ * If there is a URB in flight, don't do anything; when it finishes,
+ * it will see there is data in the FIFO and send it. Else, just
+ * submit a write.
+ */
+void i2400mu_bus_tx_kick(struct i2400m *i2400m)
+{
+	struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m);
+	struct device *dev = &i2400mu->usb_iface->dev;
+
+	d_fnstart(3, dev, "(i2400m %p) = void\n", i2400m);
+	wake_up_all(&i2400mu->tx_wq);
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+}
+
+
+int i2400mu_tx_setup(struct i2400mu *i2400mu)
+{
+	int result = 0;
+	struct i2400m *i2400m = &i2400mu->i2400m;
+	struct device *dev = &i2400mu->usb_iface->dev;
+	struct wimax_dev *wimax_dev = &i2400m->wimax_dev;
+
+	i2400mu->tx_kthread = kthread_run(i2400mu_txd, i2400mu, "%s-tx",
+					  wimax_dev->name);
+	if (IS_ERR(i2400mu->tx_kthread)) {
+		result = PTR_ERR(i2400mu->tx_kthread);
+		dev_err(dev, "TX: cannot start thread: %d\n", result);
+	}
+	return result;
+}
+
+void i2400mu_tx_release(struct i2400mu *i2400mu)
+{
+	kthread_stop(i2400mu->tx_kthread);
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 23/29] i2400m/SDIO: header for the SDIO subdriver
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (21 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 22/29] i2400m/USB: TX and RX path backends Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 24/29] i2400m/SDIO: probe/disconnect, dev init/shutdown and reset backends Inaky Perez-Gonzalez
                   ` (6 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This contains the common function declaration and constants for the
SDIO driver for the 2400m Wireless WiMAX Connection and it's debug
level settings.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/i2400m-sdio.h       |  132 ++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/sdio-debug-levels.h |   22 +++++
 2 files changed, 154 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/i2400m-sdio.h
 create mode 100644 drivers/net/wimax/i2400m/sdio-debug-levels.h

diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h
new file mode 100644
index 0000000..08c2fb7
--- /dev/null
+++ b/drivers/net/wimax/i2400m/i2400m-sdio.h
@@ -0,0 +1,132 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * SDIO-specific i2400m driver definitions
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Brian Bian <brian.bian@intel.com>
+ * Dirk Brandewie <dirk.j.brandewie@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * This driver implements the bus-specific part of the i2400m for
+ * SDIO. Check i2400m.h for a generic driver description.
+ *
+ * ARCHITECTURE
+ *
+ * This driver sits under the bus-generic i2400m driver, providing the
+ * connection to the device.
+ *
+ * When probed, all the function pointers are setup and then the
+ * bus-generic code called. The generic driver will then use the
+ * provided pointers for uploading firmware (i2400ms_bus_bm*() in
+ * sdio-fw.c) and then setting up the device (i2400ms_dev_*() in
+ * sdio.c).
+ *
+ * Once firmware is uploaded, TX functions (sdio-tx.c) are called when
+ * data is ready for transmission in the TX fifo; then the SDIO IRQ is
+ * fired and data is available (sdio-rx.c), it is sent to the generic
+ * driver for processing with i2400m_rx.
+ */
+
+#ifndef __I2400M_SDIO_H__
+#define __I2400M_SDIO_H__
+
+#include "i2400m.h"
+
+/* Host-Device interface for SDIO */
+enum {
+	I2400MS_BLK_SIZE = 256,
+	I2400MS_PL_SIZE_MAX = 0x3E00,
+
+	I2400MS_DATA_ADDR = 0x0,
+	I2400MS_INTR_STATUS_ADDR = 0x13,
+	I2400MS_INTR_CLEAR_ADDR = 0x13,
+	I2400MS_INTR_ENABLE_ADDR = 0x14,
+	I2400MS_INTR_GET_SIZE_ADDR = 0x2C,
+	/* The number of ticks to wait for the device to signal that
+	 * it is ready */
+	I2400MS_INIT_SLEEP_INTERVAL = 10,
+};
+
+
+/**
+ * struct i2400ms - descriptor for a SDIO connected i2400m
+ *
+ * @i2400m: bus-generic i2400m implementation; has to be first (see
+ *     it's documentation in i2400m.h).
+ *
+ * @func: pointer to our SDIO function
+ *
+ * @tx_worker: workqueue struct used to TX data when the bus-generic
+ *     code signals packets are pending for transmission to the device.
+ *
+ * @tx_workqueue: workqeueue used for data TX; we don't use the
+ *     system's workqueue as that might cause deadlocks with code in
+ *     the bus-generic driver.
+ */
+struct i2400ms {
+	struct i2400m i2400m;		/* FIRST! See doc */
+	struct sdio_func *func;
+
+	struct work_struct tx_worker;
+	struct workqueue_struct *tx_workqueue;
+	char tx_wq_name[32];
+
+	struct dentry *debugfs_dentry;
+};
+
+
+static inline
+void i2400ms_init(struct i2400ms *i2400ms)
+{
+	i2400m_init(&i2400ms->i2400m);
+}
+
+
+extern int i2400ms_rx_setup(struct i2400ms *);
+extern void i2400ms_rx_release(struct i2400ms *);
+extern ssize_t __i2400ms_rx_get_size(struct i2400ms *);
+
+extern int i2400ms_tx_setup(struct i2400ms *);
+extern void i2400ms_tx_release(struct i2400ms *);
+extern void i2400ms_bus_tx_kick(struct i2400m *);
+
+extern ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *,
+				       const struct i2400m_bootrom_header *,
+				       size_t, int);
+extern ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *,
+					   struct i2400m_bootrom_header *,
+					   size_t);
+#endif /* #ifndef __I2400M_SDIO_H__ */
diff --git a/drivers/net/wimax/i2400m/sdio-debug-levels.h b/drivers/net/wimax/i2400m/sdio-debug-levels.h
new file mode 100644
index 0000000..c519987
--- /dev/null
+++ b/drivers/net/wimax/i2400m/sdio-debug-levels.h
@@ -0,0 +1,22 @@
+/*
+ * debug levels control file for the i2400m module's
+ */
+#ifndef __debug_levels__h__
+#define __debug_levels__h__
+
+/* Maximum compile and run time debug level for all submodules */
+#define D_MODULENAME i2400m_sdio
+#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL
+
+#include <linux/wimax/debug.h>
+
+/* List of all the enabled modules */
+enum d_module {
+	D_SUBMODULE_DECLARE(main),
+	D_SUBMODULE_DECLARE(tx),
+	D_SUBMODULE_DECLARE(rx),
+	D_SUBMODULE_DECLARE(fw)
+};
+
+
+#endif /* #ifndef __debug_levels__h__ */
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 24/29] i2400m/SDIO: probe/disconnect, dev init/shutdown and reset backends
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (22 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 23/29] i2400m/SDIO: header for the SDIO subdriver Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 25/29] i2400m/SDIO: firmware upload backend Inaky Perez-Gonzalez
                   ` (5 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements probe/disconnect for the SDIO device, as well as main
backends for the generic driver to control the SDIO device
(bus_dev_start(), bus_dev_stop() and bus_reset()).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/sdio.c |  511 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 511 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/sdio.c

diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c
new file mode 100644
index 0000000..1bfa283
--- /dev/null
+++ b/drivers/net/wimax/i2400m/sdio.c
@@ -0,0 +1,511 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Linux driver model glue for the SDIO device, reset & fw upload
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com>
+ * Dirk Brandewie <dirk.j.brandewie@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ *
+ * 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.
+ *
+ *
+ * See i2400m-sdio.h for a general description of this driver.
+ *
+ * This file implements driver model glue, and hook ups for the
+ * generic driver to implement the bus-specific functions (device
+ * communication setup/tear down, firmware upload and resetting).
+ *
+ * ROADMAP
+ *
+ * i2400m_probe()
+ *   alloc_netdev()
+ *     i2400ms_netdev_setup()
+ *       i2400ms_init()
+ *       i2400m_netdev_setup()
+ *   i2400ms_enable_function()
+ *   i2400m_setup()
+ *
+ * i2400m_remove()
+ *     i2400m_release()
+ *     free_netdev(net_dev)
+ *
+ * i2400ms_bus_reset()            Called by i2400m->bus_reset
+ *   __i2400ms_reset()
+ *     __i2400ms_send_barker()
+ *
+ * i2400ms_bus_dev_start()        Called by i2400m_dev_start() [who is
+ *   i2400ms_tx_setup()           called by i2400m_setup()]
+ *   i2400ms_rx_setup()
+ *
+ * i2400ms_bus_dev_stop()         Called by i2400m_dev_stop() [who is
+ *   i2400ms_rx_release()         is called by i2400m_release()]
+ *   i2400ms_tx_release()
+ *
+ */
+
+#include <linux/debugfs.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include "i2400m-sdio.h"
+#include <linux/wimax/i2400m.h>
+
+#define D_SUBMODULE main
+#include "sdio-debug-levels.h"
+
+/* IOE WiMAX function timeout in seconds */
+static int ioe_timeout = 2;
+module_param(ioe_timeout, int, 0);
+
+/* Our firmware file name */
+#define I2400MS_FW_FILE_NAME "i2400m-fw-sdio-" I2400M_FW_VERSION ".sbcf"
+
+/*
+ * Enable the SDIO function
+ *
+ * Tries to enable the SDIO function; might fail if it is still not
+ * ready (in some hardware, the SDIO WiMAX function is only enabled
+ * when we ask it to explicitly doing). Tries until a timeout is
+ * reached.
+ *
+ * The reverse of this is...sdio_disable_function()
+ *
+ * Returns: 0 if the SDIO function was enabled, < 0 errno code on
+ *     error (-ENODEV when it was unable to enable the function).
+ */
+static
+int i2400ms_enable_function(struct sdio_func *func)
+{
+	u64 timeout;
+	int err;
+	struct device *dev = &func->dev;
+
+	d_fnstart(3, dev, "(func %p)\n", func);
+	/* Setup timeout (FIXME: This needs to read the CIS table to
+	 * get a real timeout) and then wait for the device to signal
+	 * it is ready */
+	timeout = get_jiffies_64() + ioe_timeout * HZ;
+	err = -ENODEV;
+	while (err != 0 && time_before64(get_jiffies_64(), timeout)) {
+		sdio_claim_host(func);
+		err = sdio_enable_func(func);
+		if (0 == err) {
+			sdio_release_host(func);
+			d_printf(2, dev, "SDIO function enabled\n");
+			goto function_enabled;
+		}
+		d_printf(2, dev, "SDIO function failed to enable: %d\n", err);
+		sdio_disable_func(func);
+		sdio_release_host(func);
+		msleep(I2400MS_INIT_SLEEP_INTERVAL);
+	}
+	/* If timed out, device is not there yet -- get -ENODEV so
+	 * the device driver core will retry later on. */
+	if (err == -ETIME) {
+		dev_err(dev, "Can't enable WiMAX function; "
+			" has the function been enabled?\n");
+		err = -ENODEV;
+	}
+function_enabled:
+	d_fnend(3, dev, "(func %p) = %d\n", func, err);
+	return err;
+}
+
+
+/*
+ * Setup driver resources needed to communicate with the device
+ *
+ * The fw needs some time to settle, and it was just uploaded,
+ * so give it a break first. I'd prefer to just wait for the device to
+ * send something, but seems the poking we do to enable SDIO stuff
+ * interferes with it, so just give it a break before starting...
+ */
+static
+int i2400ms_bus_dev_start(struct i2400m *i2400m)
+{
+	int result;
+	struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	msleep(200);
+	result = i2400ms_rx_setup(i2400ms);
+	if (result < 0)
+		goto error_rx_setup;
+	result = i2400ms_tx_setup(i2400ms);
+	if (result < 0)
+		goto error_tx_setup;
+	d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result);
+	return result;
+
+	i2400ms_tx_release(i2400ms);
+error_tx_setup:
+	i2400ms_rx_release(i2400ms);
+error_rx_setup:
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+	return result;
+}
+
+
+static
+void i2400ms_bus_dev_stop(struct i2400m *i2400m)
+{
+	struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+
+	d_fnstart(3, dev, "(i2400m %p)\n", i2400m);
+	i2400ms_rx_release(i2400ms);
+	i2400ms_tx_release(i2400ms);
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+}
+
+
+/*
+ * Sends a barker buffer to the device
+ *
+ * This helper will allocate a kmalloced buffer and use it to transmit
+ * (then free it). Reason for this is that the SDIO host controller
+ * expects alignment (unknown exactly which) which the stack won't
+ * really provide and certain arches/host-controller combinations
+ * cannot use stack/vmalloc/text areas for DMA transfers.
+ */
+static
+int __i2400ms_send_barker(struct i2400ms *i2400ms,
+			  const __le32 *barker, size_t barker_size)
+{
+	int  ret;
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+	void *buffer;
+
+	ret = -ENOMEM;
+	buffer = kmalloc(I2400MS_BLK_SIZE, GFP_KERNEL);
+	if (buffer == NULL)
+		goto error_kzalloc;
+
+	memcpy(buffer, barker, barker_size);
+	sdio_claim_host(func);
+	ret = sdio_memcpy_toio(func, 0, buffer, I2400MS_BLK_SIZE);
+	sdio_release_host(func);
+
+	if (ret < 0)
+		d_printf(0, dev, "E: barker error: %d\n", ret);
+
+	kfree(buffer);
+error_kzalloc:
+	return ret;
+}
+
+
+/*
+ * Reset a device at different levels (warm, cold or bus)
+ *
+ * @i2400ms: device descriptor
+ * @reset_type: soft, warm or bus reset (I2400M_RT_WARM/SOFT/BUS)
+ *
+ * FIXME: not tested -- need to confirm expected effects
+ *
+ * Warm and cold resets get an SDIO reset if they fail (unimplemented)
+ *
+ * Warm reset:
+ *
+ * The device will be fully reset internally, but won't be
+ * disconnected from the USB bus (so no reenumeration will
+ * happen). Firmware upload will be neccessary.
+ *
+ * The device will send a reboot barker in the notification endpoint
+ * that will trigger the driver to reinitialize the state
+ * automatically from notif.c:i2400m_notification_grok() into
+ * i2400m_dev_bootstrap_delayed().
+ *
+ * Cold and bus (USB) reset:
+ *
+ * The device will be fully reset internally, disconnected from the
+ * USB bus an a reenumeration will happen. Firmware upload will be
+ * neccessary. Thus, we don't do any locking or struct
+ * reinitialization, as we are going to be fully disconnected and
+ * reenumerated.
+ *
+ * Note we need to return -ENODEV if a warm reset was requested and we
+ * had to resort to a bus reset. See i2400m_op_reset(), wimax_reset()
+ * and wimax_dev->op_reset.
+ *
+ * WARNING: no driver state saved/fixed
+ */
+static
+int i2400ms_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt)
+{
+	int result;
+	struct i2400ms *i2400ms =
+		container_of(i2400m, struct i2400ms, i2400m);
+	struct device *dev = i2400m_dev(i2400m);
+	static const __le32 i2400m_WARM_BOOT_BARKER[4] = {
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_WARM_RESET_BARKER),
+	};
+	static const __le32 i2400m_COLD_BOOT_BARKER[4] = {
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+		__constant_cpu_to_le32(I2400M_COLD_RESET_BARKER),
+	};
+
+	if (rt == I2400M_RT_WARM)
+		result = __i2400ms_send_barker(i2400ms, i2400m_WARM_BOOT_BARKER,
+					       sizeof(i2400m_WARM_BOOT_BARKER));
+	else if (rt == I2400M_RT_COLD)
+		result = __i2400ms_send_barker(i2400ms, i2400m_COLD_BOOT_BARKER,
+					       sizeof(i2400m_COLD_BOOT_BARKER));
+	else if (rt == I2400M_RT_BUS) {
+do_bus_reset:
+		dev_err(dev, "FIXME: SDIO bus reset not implemented\n");
+		result = rt == I2400M_RT_WARM ? -ENODEV : -ENOSYS;
+	} else
+		BUG();
+	if (result < 0 && rt != I2400M_RT_BUS) {
+		dev_err(dev, "%s reset failed (%d); trying SDIO reset\n",
+			rt == I2400M_RT_WARM ? "warm" : "cold", result);
+		rt = I2400M_RT_BUS;
+		goto do_bus_reset;
+	}
+	return result;
+}
+
+
+static
+void i2400ms_netdev_setup(struct net_device *net_dev)
+{
+	struct i2400m *i2400m = net_dev_to_i2400m(net_dev);
+	struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
+	i2400ms_init(i2400ms);
+	i2400m_netdev_setup(net_dev);
+}
+
+
+/*
+ * Debug levels control; see debug.h
+ */
+struct d_level D_LEVEL[] = {
+	D_SUBMODULE_DEFINE(main),
+	D_SUBMODULE_DEFINE(tx),
+	D_SUBMODULE_DEFINE(rx),
+	D_SUBMODULE_DEFINE(fw),
+};
+size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+
+
+#define __debugfs_register(prefix, name, parent)			\
+do {									\
+	result = d_level_register_debugfs(prefix, name, parent);	\
+	if (result < 0)							\
+		goto error;						\
+} while (0)
+
+
+static
+int i2400ms_debugfs_add(struct i2400ms *i2400ms)
+{
+	int result;
+	struct dentry *dentry = i2400ms->i2400m.wimax_dev.debugfs_dentry;
+
+	dentry = debugfs_create_dir("i2400m-usb", dentry);
+	result = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		if (result == -ENODEV)
+			result = 0;	/* No debugfs support */
+		goto error;
+	}
+	i2400ms->debugfs_dentry = dentry;
+	__debugfs_register("dl_", main, dentry);
+	__debugfs_register("dl_", tx, dentry);
+	__debugfs_register("dl_", rx, dentry);
+	__debugfs_register("dl_", fw, dentry);
+
+	return 0;
+
+error:
+	debugfs_remove_recursive(i2400ms->debugfs_dentry);
+	return result;
+}
+
+
+/*
+ * Probe a i2400m interface and register it
+ *
+ * @func:    SDIO function
+ * @id:      SDIO device ID
+ * @returns: 0 if ok, < 0 errno code on error.
+ *
+ * Alloc a net device, initialize the bus-specific details and then
+ * calls the bus-generic initialization routine. That will register
+ * the wimax and netdev devices, upload the firmware [using
+ * _bus_bm_*()], call _bus_dev_start() to finalize the setup of the
+ * communication with the device and then will start to talk to it to
+ * finnish setting it up.
+ *
+ * Initialization is tricky; some instances of the hw are packed with
+ * others in a way that requires a third driver that enables the WiMAX
+ * function. In those cases, we can't enable the SDIO function and
+ * we'll return with -ENODEV. When the driver that enables the WiMAX
+ * function does its thing, it has to do a bus_rescan_devices() on the
+ * SDIO bus so this driver is called again to enumerate the WiMAX
+ * function.
+ */
+static
+int i2400ms_probe(struct sdio_func *func,
+		  const struct sdio_device_id *id)
+{
+	int result;
+	struct net_device *net_dev;
+	struct device *dev = &func->dev;
+	struct i2400m *i2400m;
+	struct i2400ms *i2400ms;
+
+	/* Allocate instance [calls i2400m_netdev_setup() on it]. */
+	result = -ENOMEM;
+	net_dev = alloc_netdev(sizeof(*i2400ms), "wmx%d",
+			       i2400ms_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);
+	i2400m = net_dev_to_i2400m(net_dev);
+	i2400ms = container_of(i2400m, struct i2400ms, i2400m);
+	i2400m->wimax_dev.net_dev = net_dev;
+	i2400ms->func = func;
+	sdio_set_drvdata(func, i2400ms);
+
+	i2400m->bus_tx_block_size = I2400MS_BLK_SIZE;
+	i2400m->bus_pl_size_max = I2400MS_PL_SIZE_MAX;
+	i2400m->bus_dev_start = i2400ms_bus_dev_start;
+	i2400m->bus_dev_stop = i2400ms_bus_dev_stop;
+	i2400m->bus_tx_kick = i2400ms_bus_tx_kick;
+	i2400m->bus_reset = i2400ms_bus_reset;
+	i2400m->bus_bm_cmd_send = i2400ms_bus_bm_cmd_send;
+	i2400m->bus_bm_wait_for_ack = i2400ms_bus_bm_wait_for_ack;
+	i2400m->bus_fw_name = I2400MS_FW_FILE_NAME;
+	i2400m->bus_bm_mac_addr_impaired = 1;
+
+	result = i2400ms_enable_function(i2400ms->func);
+	if (result < 0) {
+		dev_err(dev, "Cannot enable SDIO function: %d\n", result);
+		goto error_func_enable;
+	}
+
+	sdio_claim_host(func);
+	result = sdio_set_block_size(func, I2400MS_BLK_SIZE);
+	if (result < 0) {
+		dev_err(dev, "Failed to set block size: %d\n", result);
+		goto error_set_blk_size;
+	}
+	sdio_release_host(func);
+
+	result = i2400m_setup(i2400m, I2400M_BRI_NO_REBOOT);
+	if (result < 0) {
+		dev_err(dev, "cannot setup device: %d\n", result);
+		goto error_setup;
+	}
+
+	result = i2400ms_debugfs_add(i2400ms);
+	if (result < 0) {
+		dev_err(dev, "cannot create SDIO debugfs: %d\n",
+			result);
+		goto error_debugfs_add;
+	}
+	return 0;
+
+error_debugfs_add:
+	i2400m_release(i2400m);
+error_setup:
+	sdio_set_drvdata(func, NULL);
+	sdio_claim_host(func);
+error_set_blk_size:
+	sdio_disable_func(func);
+	sdio_release_host(func);
+error_func_enable:
+	free_netdev(net_dev);
+error_alloc_netdev:
+	return result;
+}
+
+
+static
+void i2400ms_remove(struct sdio_func *func)
+{
+	struct device *dev = &func->dev;
+	struct i2400ms *i2400ms = sdio_get_drvdata(func);
+	struct i2400m *i2400m = &i2400ms->i2400m;
+	struct net_device *net_dev = i2400m->wimax_dev.net_dev;
+
+	d_fnstart(3, dev, "SDIO func %p\n", func);
+	debugfs_remove_recursive(i2400ms->debugfs_dentry);
+	i2400m_release(i2400m);
+	sdio_set_drvdata(func, NULL);
+	sdio_claim_host(func);
+	sdio_disable_func(func);
+	sdio_release_host(func);
+	free_netdev(net_dev);
+	d_fnend(3, dev, "SDIO func %p\n", func);
+}
+
+enum {
+	I2400MS_INTEL_VID = 0x89,
+};
+
+static
+const struct sdio_device_id i2400ms_sdio_ids[] = {
+	/* Intel: i2400m WiMAX over SDIO */
+	{ SDIO_DEVICE(I2400MS_INTEL_VID, 0x1402) },
+	{ }, 			/* end: all zeroes */
+};
+MODULE_DEVICE_TABLE(sdio, i2400ms_sdio_ids);
+
+
+static
+struct sdio_driver i2400m_sdio_driver = {
+	.name		= KBUILD_MODNAME,
+	.probe		= i2400ms_probe,
+	.remove		= i2400ms_remove,
+	.id_table	= i2400ms_sdio_ids,
+};
+
+
+static
+int __init i2400ms_driver_init(void)
+{
+	return sdio_register_driver(&i2400m_sdio_driver);
+}
+module_init(i2400ms_driver_init);
+
+
+static
+void __exit i2400ms_driver_exit(void)
+{
+	flush_scheduled_work();	/* for the stuff we schedule */
+	sdio_unregister_driver(&i2400m_sdio_driver);
+}
+module_exit(i2400ms_driver_exit);
+
+
+MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>");
+MODULE_DESCRIPTION("Intel 2400M WiMAX networking for SDIO");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(I2400MS_FW_FILE_NAME);
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 25/29] i2400m/SDIO: firmware upload backend
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (23 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 24/29] i2400m/SDIO: probe/disconnect, dev init/shutdown and reset backends Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 26/29] i2400m/SDIO: TX and RX path backends Inaky Perez-Gonzalez
                   ` (4 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This implements the backends for the generic driver (i2400m) to be
able to load firmware to the SDIO device.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/sdio-fw.c |  224 ++++++++++++++++++++++++++++++++++++
 1 files changed, 224 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/sdio-fw.c

diff --git a/drivers/net/wimax/i2400m/sdio-fw.c b/drivers/net/wimax/i2400m/sdio-fw.c
new file mode 100644
index 0000000..3487205
--- /dev/null
+++ b/drivers/net/wimax/i2400m/sdio-fw.c
@@ -0,0 +1,224 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Firmware uploader's SDIO specifics
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Yanir Lubetkin <yanirx.lubetkin@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Initial implementation
+ *
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - Bus generic/specific split for USB
+ *
+ * Dirk Brandewie <dirk.j.brandewie@intel.com>
+ *  - Initial implementation for SDIO
+ *
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *  - SDIO rehash for changes in the bus-driver model
+ *
+ * THE PROCEDURE
+ *
+ * See fw.c for the generic description of this procedure.
+ *
+ * This file implements only the SDIO specifics. It boils down to how
+ * to send a command and waiting for an acknowledgement from the
+ * device. We do polled reads.
+ *
+ * COMMAND EXECUTION
+ *
+ * THe generic firmware upload code will call i2400m_bus_bm_cmd_send()
+ * to send commands.
+ *
+ * The SDIO devices expects things in 256 byte blocks, so it will pad
+ * it, compute the checksum (if needed) and pass it to SDIO.
+ *
+ * ACK RECEPTION
+ *
+ * This works in polling mode -- the fw loader says when to wait for
+ * data and for that it calls i2400ms_bus_bm_wait_for_ack().
+ *
+ * This will poll the device for data until it is received. We need to
+ * receive at least as much bytes as where asked for (although it'll
+ * always be a multiple of 256 bytes).
+ */
+#include <linux/mmc/sdio_func.h>
+#include "i2400m-sdio.h"
+
+
+#define D_SUBMODULE fw
+#include "sdio-debug-levels.h"
+
+/*
+ * Send a boot-mode command to the SDIO function
+ *
+ * We use a bounce buffer (i2400m->bm_cmd_buf) because we need to
+ * touch the header if the RAW flag is not set.
+ *
+ * @flags: pass thru from i2400m_bm_cmd()
+ * @return: cmd_size if ok, < 0 errno code on error.
+ *
+ * Note the command is padded to the SDIO block size for the device.
+ */
+ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *i2400m,
+				const struct i2400m_bootrom_header *_cmd,
+				size_t cmd_size, int flags)
+{
+	ssize_t result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
+	int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd);
+	struct i2400m_bootrom_header *cmd;
+	/* SDIO restriction */
+	size_t cmd_size_a = ALIGN(cmd_size, I2400MS_BLK_SIZE);
+
+	d_fnstart(5, dev, "(i2400m %p cmd %p size %zu)\n",
+		  i2400m, _cmd, cmd_size);
+	result = -E2BIG;
+	if (cmd_size > I2400M_BM_CMD_BUF_SIZE)
+		goto error_too_big;
+
+	memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size);	/* Prep command */
+	cmd = i2400m->bm_cmd_buf;
+	if (cmd_size_a > cmd_size)			/* Zero pad space */
+		memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size);
+	if ((flags & I2400M_BM_CMD_RAW) == 0) {
+		if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0))
+			dev_warn(dev, "SW BUG: response_required == 0\n");
+		i2400m_bm_cmd_prepare(cmd);
+	}
+	d_printf(4, dev, "BM cmd %d: %zu bytes (%zu padded)\n",
+		 opcode, cmd_size, cmd_size_a);
+	d_dump(5, dev, cmd, cmd_size);
+
+	sdio_claim_host(i2400ms->func);			/* Send & check */
+	result = sdio_memcpy_toio(i2400ms->func, I2400MS_DATA_ADDR,
+				  i2400m->bm_cmd_buf, cmd_size_a);
+	sdio_release_host(i2400ms->func);
+	if (result < 0) {
+		dev_err(dev, "BM cmd %d: cannot send: %ld\n",
+			opcode, (long) result);
+		goto error_cmd_send;
+	}
+	result = cmd_size;
+error_cmd_send:
+error_too_big:
+	d_fnend(5, dev, "(i2400m %p cmd %p size %zu) = %d\n",
+		i2400m, _cmd, cmd_size, (int) result);
+	return result;
+}
+
+
+/*
+ * Read an ack from the device's boot-mode (polling)
+ *
+ * @i2400m:
+ * @_ack: pointer to where to store the read data
+ * @ack_size: how many bytes we should read
+ *
+ * Returns: < 0 errno code on error; otherwise, amount of received bytes.
+ *
+ * The ACK for a BM command is always at least sizeof(*ack) bytes, so
+ * check for that. We don't need to check for device reboots
+ *
+ * NOTE: We do an artificial timeout of 1 sec over the SDIO timeout;
+ *     this way we have control over it...there is no way that I know
+ *     of setting an SDIO transaction timeout.
+ */
+ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *i2400m,
+				    struct i2400m_bootrom_header *ack,
+				    size_t ack_size)
+{
+	int result;
+	ssize_t rx_size;
+	u64 timeout;
+	struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+
+	BUG_ON(sizeof(*ack) > ack_size);
+
+	d_fnstart(5, dev, "(i2400m %p ack %p size %zu)\n",
+		  i2400m, ack, ack_size);
+
+	timeout = get_jiffies_64() + 2 * HZ;
+	sdio_claim_host(func);
+	while (1) {
+		if (time_after64(get_jiffies_64(), timeout)) {
+			rx_size = -ETIMEDOUT;
+			dev_err(dev, "timeout waiting for ack data\n");
+			goto error_timedout;
+		}
+
+		/* Find the RX size, check if it fits or not -- it if
+		 * doesn't fit, fail, as we have no way to dispose of
+		 * the extra data. */
+		rx_size = __i2400ms_rx_get_size(i2400ms);
+		if (rx_size < 0)
+			goto error_rx_get_size;
+		result = -ENOSPC;		/* Check it fits */
+		if (rx_size < sizeof(*ack)) {
+			rx_size = -EIO;
+			dev_err(dev, "HW BUG? received is too small (%zu vs "
+				"%zu needed)\n", sizeof(*ack), rx_size);
+			goto error_too_small;
+		}
+		if (rx_size > I2400M_BM_ACK_BUF_SIZE) {
+			dev_err(dev, "SW BUG? BM_ACK_BUF is too small (%u vs "
+				"%zu needed)\n", I2400M_BM_ACK_BUF_SIZE,
+				rx_size);
+			goto error_too_small;
+		}
+
+		/* Read it */
+		result = sdio_memcpy_fromio(func, i2400m->bm_ack_buf,
+					    I2400MS_DATA_ADDR, rx_size);
+		if (result == -ETIMEDOUT || result == -ETIME)
+			continue;
+		if (result < 0) {
+			dev_err(dev, "BM SDIO receive (%zu B) failed: %d\n",
+				rx_size, result);
+			goto error_read;
+		} else
+			break;
+	}
+	rx_size = min((ssize_t)ack_size, rx_size);
+	memcpy(ack, i2400m->bm_ack_buf, rx_size);
+error_read:
+error_too_small:
+error_rx_get_size:
+error_timedout:
+	sdio_release_host(func);
+	d_fnend(5, dev, "(i2400m %p ack %p size %zu) = %ld\n",
+		i2400m, ack, ack_size, (long) rx_size);
+	return rx_size;
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 26/29] i2400m/SDIO: TX and RX path backends
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (24 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 25/29] i2400m/SDIO: firmware upload backend Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 27/29] i2400m: Makefile and Kconfig Inaky Perez-Gonzalez
                   ` (3 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Implements the backend so that the generic driver can TX/RX to/from
the SDIO device.

For RX, when data is ready the SDIO IRQ is fired and that will
allocate an skb, put all the data there and then pass it to the
generic driver RX code for processing and delivery.

TX, when kicked by the generic driver, will schedule work on a
driver-specific workqueue that pulls data from the TX FIFO and sends
it to the device until it drains it.

Thread contexts are needed as SDIO functions are blocking.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/sdio-rx.c |  255 ++++++++++++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/sdio-tx.c |  153 +++++++++++++++++++++
 2 files changed, 408 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/sdio-rx.c
 create mode 100644 drivers/net/wimax/i2400m/sdio-tx.c

diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c
new file mode 100644
index 0000000..a3008b9
--- /dev/null
+++ b/drivers/net/wimax/i2400m/sdio-rx.c
@@ -0,0 +1,255 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * SDIO RX handling
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Dirk Brandewie <dirk.j.brandewie@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * This handles the RX path on SDIO.
+ *
+ * The SDIO bus driver calls the "irq" routine when data is available.
+ * This is not a traditional interrupt routine since the SDIO bus
+ * driver calls us from its irq thread context.  Because of this
+ * sleeping in the SDIO RX IRQ routine is okay.
+ *
+ * From there on, we obtain the size of the data that is available,
+ * allocate an skb, copy it and then pass it to the generic driver's
+ * RX routine [i2400m_rx()].
+ *
+ * ROADMAP
+ *
+ * i2400ms_irq()
+ *   i2400ms_rx()
+ *     __i2400ms_rx_get_size()
+ *     i2400m_rx()
+ *
+ * i2400ms_rx_setup()
+ *
+ * i2400ms_rx_release()
+ */
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/skbuff.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include "i2400m-sdio.h"
+
+#define D_SUBMODULE rx
+#include "sdio-debug-levels.h"
+
+
+/*
+ * Read and return the amount of bytes available for RX
+ *
+ * The RX size has to be read like this: byte reads of three
+ * sequential locations; then glue'em together.
+ *
+ * sdio_readl() doesn't work.
+ */
+ssize_t __i2400ms_rx_get_size(struct i2400ms *i2400ms)
+{
+	int ret, cnt, val;
+	ssize_t rx_size;
+	unsigned xfer_size_addr;
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &i2400ms->func->dev;
+
+	d_fnstart(7, dev, "(i2400ms %p)\n", i2400ms);
+	xfer_size_addr = I2400MS_INTR_GET_SIZE_ADDR;
+	rx_size = 0;
+	for (cnt = 0; cnt < 3; cnt++) {
+		val = sdio_readb(func, xfer_size_addr + cnt, &ret);
+		if (ret < 0) {
+			dev_err(dev, "RX: Can't read byte %d of RX size from "
+				"0x%08x: %d\n", cnt, xfer_size_addr + cnt, ret);
+			rx_size = ret;
+			goto error_read;
+		}
+		rx_size = rx_size << 8 | (val & 0xff);
+	}
+	d_printf(6, dev, "RX: rx_size is %ld\n", (long) rx_size);
+error_read:
+	d_fnend(7, dev, "(i2400ms %p) = %ld\n", i2400ms, (long) rx_size);
+	return rx_size;
+}
+
+
+/*
+ * Read data from the device (when in normal)
+ *
+ * Allocate an SKB of the right size, read the data in and then
+ * deliver it to the generic layer.
+ *
+ * We also check for a reboot barker. That means the device died and
+ * we have to reboot it.
+ */
+static
+void i2400ms_rx(struct i2400ms *i2400ms)
+{
+	int ret;
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+	struct i2400m *i2400m = &i2400ms->i2400m;
+	struct sk_buff *skb;
+	ssize_t rx_size;
+
+	d_fnstart(7, dev, "(i2400ms %p)\n", i2400ms);
+	rx_size = __i2400ms_rx_get_size(i2400ms);
+	if (rx_size < 0) {
+		ret = rx_size;
+		goto error_get_size;
+	}
+	ret = -ENOMEM;
+	skb = alloc_skb(rx_size, GFP_ATOMIC);
+	if (NULL == skb) {
+		dev_err(dev, "RX: unable to alloc skb\n");
+		goto error_alloc_skb;
+	}
+
+	ret = sdio_memcpy_fromio(func, skb->data,
+				 I2400MS_DATA_ADDR, rx_size);
+	if (ret < 0) {
+		dev_err(dev, "RX: SDIO data read failed: %d\n", ret);
+		goto error_memcpy_fromio;
+	}
+	/* Check if device has reset */
+	if (!memcmp(skb->data, i2400m_NBOOT_BARKER,
+		    sizeof(i2400m_NBOOT_BARKER))
+	    || !memcmp(skb->data, i2400m_SBOOT_BARKER,
+		       sizeof(i2400m_SBOOT_BARKER))) {
+		ret = i2400m_dev_reset_handle(i2400m);
+		kfree_skb(skb);
+	} else {
+		skb_put(skb, rx_size);
+		i2400m_rx(i2400m, skb);
+	}
+	d_fnend(7, dev, "(i2400ms %p) = void\n", i2400ms);
+	return;
+
+error_memcpy_fromio:
+	kfree_skb(skb);
+error_alloc_skb:
+error_get_size:
+	d_fnend(7, dev, "(i2400ms %p) = %d\n", i2400ms, ret);
+	return;
+}
+
+
+/*
+ * Process an interrupt from the SDIO card
+ *
+ * FIXME: need to process other events that are not just ready-to-read
+ *
+ * Checks there is data ready and then proceeds to read it.
+ */
+static
+void i2400ms_irq(struct sdio_func *func)
+{
+	int ret;
+	struct i2400ms *i2400ms = sdio_get_drvdata(func);
+	struct i2400m *i2400m = &i2400ms->i2400m;
+	struct device *dev = &func->dev;
+	int val;
+
+	d_fnstart(6, dev, "(i2400ms %p)\n", i2400ms);
+	val = sdio_readb(func, I2400MS_INTR_STATUS_ADDR, &ret);
+	if (ret < 0) {
+		dev_err(dev, "RX: Can't read interrupt status: %d\n", ret);
+		goto error_no_irq;
+	}
+	if (!val) {
+		dev_err(dev, "RX: BUG? got IRQ but no interrupt ready?\n");
+		goto error_no_irq;
+	}
+	sdio_writeb(func, 1, I2400MS_INTR_CLEAR_ADDR, &ret);
+	if (WARN_ON(i2400m->boot_mode != 0))
+		dev_err(dev, "RX: SW BUG? boot mode and IRQ is up?\n");
+	else
+		i2400ms_rx(i2400ms);
+error_no_irq:
+	d_fnend(6, dev, "(i2400ms %p) = void\n", i2400ms);
+	return;
+}
+
+
+/*
+ * Setup SDIO RX
+ *
+ * Hooks up the IRQ handler and then enables IRQs.
+ */
+int i2400ms_rx_setup(struct i2400ms *i2400ms)
+{
+	int result;
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+
+	d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms);
+	sdio_claim_host(func);
+	result = sdio_claim_irq(func, i2400ms_irq);
+	if (result < 0) {
+		dev_err(dev, "Cannot claim IRQ: %d\n", result);
+		goto error_irq_claim;
+	}
+	result = 0;
+	sdio_writeb(func, 1, I2400MS_INTR_ENABLE_ADDR, &result);
+	if (result < 0) {
+		sdio_release_irq(func);
+		dev_err(dev, "Failed to enable interrupts %d\n", result);
+	}
+error_irq_claim:
+	sdio_release_host(func);
+	d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result);
+	return result;
+}
+
+
+/*
+ * Tear down SDIO RX
+ *
+ * Disables IRQs in the device and removes the IRQ handler.
+ */
+void i2400ms_rx_release(struct i2400ms *i2400ms)
+{
+	int result;
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+
+	d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms);
+	sdio_claim_host(func);
+	sdio_writeb(func, 0, I2400MS_INTR_ENABLE_ADDR, &result);
+	sdio_release_irq(func);
+	sdio_release_host(func);
+	d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result);
+}
diff --git a/drivers/net/wimax/i2400m/sdio-tx.c b/drivers/net/wimax/i2400m/sdio-tx.c
new file mode 100644
index 0000000..5105a5e
--- /dev/null
+++ b/drivers/net/wimax/i2400m/sdio-tx.c
@@ -0,0 +1,153 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * SDIO TX transaction backends
+ *
+ *
+ * Copyright (C) 2007-2008 Intel Corporation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Intel Corporation nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *
+ * Intel Corporation <linux-wimax@intel.com>
+ * Dirk Brandewie <dirk.j.brandewie@intel.com>
+ *  - Initial implementation
+ *
+ *
+ * Takes the TX messages in the i2400m's driver TX FIFO and sends them
+ * to the device until there are no more.
+ *
+ * If we fail sending the message, we just drop it. There isn't much
+ * we can do at this point. Most of the traffic is network, which has
+ * recovery methods for dropped packets.
+ *
+ * The SDIO functions are not atomic, so we can't run from the context
+ * where i2400m->bus_tx_kick() [i2400ms_bus_tx_kick()] is being called
+ * (some times atomic). Thus, the actual TX work is deferred to a
+ * workqueue.
+ *
+ * ROADMAP
+ *
+ * i2400ms_bus_tx_kick()
+ *   i2400ms_tx_submit()     [through workqueue]
+ *
+ * i2400m_tx_setup()
+ *
+ * i2400m_tx_release()
+ */
+#include <linux/mmc/sdio_func.h>
+#include "i2400m-sdio.h"
+
+#define D_SUBMODULE tx
+#include "sdio-debug-levels.h"
+
+
+/*
+ * Pull TX transations from the TX FIFO and send them to the device
+ * until there are no more.
+ */
+static
+void i2400ms_tx_submit(struct work_struct *ws)
+{
+	int result;
+	struct i2400ms *i2400ms = container_of(ws, struct i2400ms, tx_worker);
+	struct i2400m *i2400m = &i2400ms->i2400m;
+	struct sdio_func *func = i2400ms->func;
+	struct device *dev = &func->dev;
+	struct i2400m_msg_hdr *tx_msg;
+	size_t tx_msg_size;
+
+	d_fnstart(4, dev, "(i2400ms %p, i2400m %p)\n", i2400ms, i2400ms);
+
+	while (NULL != (tx_msg = i2400m_tx_msg_get(i2400m, &tx_msg_size))) {
+		d_printf(2, dev, "TX: submitting %zu bytes\n", tx_msg_size);
+		d_dump(5, dev, tx_msg, tx_msg_size);
+
+		sdio_claim_host(func);
+		result = sdio_memcpy_toio(func, 0, tx_msg, tx_msg_size);
+		sdio_release_host(func);
+
+		i2400m_tx_msg_sent(i2400m);
+
+		if (result < 0) {
+			dev_err(dev, "TX: cannot submit TX; tx_msg @%zu %zu B:"
+				" %d\n", (void *) tx_msg - i2400m->tx_buf,
+				tx_msg_size, result);
+		}
+
+		d_printf(2, dev, "TX: %zub submitted\n", tx_msg_size);
+	}
+
+	d_fnend(4, dev, "(i2400ms %p) = void\n", i2400ms);
+}
+
+
+/*
+ * The generic driver notifies us that there is data ready for TX
+ *
+ * Schedule a run of i2400ms_tx_submit() to handle it.
+ */
+void i2400ms_bus_tx_kick(struct i2400m *i2400m)
+{
+	struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m);
+	struct device *dev = &i2400ms->func->dev;
+
+	d_fnstart(3, dev, "(i2400m %p) = void\n", i2400m);
+
+	/* schedule tx work, this is because tx may block, therefore
+	 * it has to run in a thread context.
+	 */
+	queue_work(i2400ms->tx_workqueue, &i2400ms->tx_worker);
+
+	d_fnend(3, dev, "(i2400m %p) = void\n", i2400m);
+}
+
+int i2400ms_tx_setup(struct i2400ms *i2400ms)
+{
+	int result;
+	struct device *dev = &i2400ms->func->dev;
+	struct i2400m *i2400m = &i2400ms->i2400m;
+
+	d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms);
+
+	INIT_WORK(&i2400ms->tx_worker, i2400ms_tx_submit);
+	snprintf(i2400ms->tx_wq_name, sizeof(i2400ms->tx_wq_name),
+		 "%s-tx", i2400m->wimax_dev.name);
+	i2400ms->tx_workqueue =
+		create_singlethread_workqueue(i2400ms->tx_wq_name);
+	if (NULL == i2400ms->tx_workqueue) {
+		dev_err(dev, "TX: failed to create workqueue\n");
+		result = -ENOMEM;
+	} else
+		result = 0;
+	d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result);
+	return result;
+}
+
+void i2400ms_tx_release(struct i2400ms *i2400ms)
+{
+	destroy_workqueue(i2400ms->tx_workqueue);
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 27/29] i2400m: Makefile and Kconfig
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (25 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 26/29] i2400m/SDIO: TX and RX path backends Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 28/29] wimax: export linux/wimax.h and linux/wimax/i2400m.h with headers_install Inaky Perez-Gonzalez
                   ` (2 subsequent siblings)
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Integrate the i2400m driver into the kernel's build and Kconfig.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/Kconfig               |    2 +
 drivers/net/Makefile              |    1 +
 drivers/net/wimax/Kconfig         |   17 +++++++++++++
 drivers/net/wimax/Makefile        |    2 +
 drivers/net/wimax/i2400m/Kconfig  |   49 +++++++++++++++++++++++++++++++++++++
 drivers/net/wimax/i2400m/Makefile |   29 ++++++++++++++++++++++
 6 files changed, 100 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/Kconfig
 create mode 100644 drivers/net/wimax/Makefile
 create mode 100644 drivers/net/wimax/i2400m/Kconfig
 create mode 100644 drivers/net/wimax/i2400m/Makefile

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 231eeaf..6255b79 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2585,6 +2585,8 @@ source "drivers/net/tokenring/Kconfig"
 
 source "drivers/net/wireless/Kconfig"
 
+source "drivers/net/wimax/Kconfig"
+
 source "drivers/net/usb/Kconfig"
 
 source "drivers/net/pcmcia/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index f19acf8..3ac378f 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -262,3 +262,4 @@ obj-$(CONFIG_NIU) += niu.o
 obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
 obj-$(CONFIG_SFC) += sfc/
 
+obj-y += wimax/
diff --git a/drivers/net/wimax/Kconfig b/drivers/net/wimax/Kconfig
new file mode 100644
index 0000000..30347e4
--- /dev/null
+++ b/drivers/net/wimax/Kconfig
@@ -0,0 +1,17 @@
+#
+# WiMAX LAN device drivers configuration
+#
+
+
+comment "Enable WiMAX (Networking options) to see the WiMAX drivers"
+        depends on WIMAX = n
+
+if WIMAX
+
+menu "WiMAX Wireless Broadband devices"
+
+source "drivers/net/wimax/i2400m/Kconfig"
+
+endmenu
+
+endif
diff --git a/drivers/net/wimax/Makefile b/drivers/net/wimax/Makefile
new file mode 100644
index 0000000..bda9430
--- /dev/null
+++ b/drivers/net/wimax/Makefile
@@ -0,0 +1,2 @@
+
+obj-$(CONFIG_WIMAX_I2400M)	+= i2400m/
diff --git a/drivers/net/wimax/i2400m/Kconfig b/drivers/net/wimax/i2400m/Kconfig
new file mode 100644
index 0000000..e977c46
--- /dev/null
+++ b/drivers/net/wimax/i2400m/Kconfig
@@ -0,0 +1,49 @@
+
+config WIMAX_I2400M
+        tristate
+	depends on WIMAX
+        select FW_LOADER
+
+comment "Enable USB support to see WiMAX USB drivers"
+        depends on USB = n
+
+comment "Enable MMC support to see WiMAX SDIO drivers"
+        depends on MMC = n
+
+config WIMAX_I2400M_USB
+	tristate "Intel Wireless WiMAX Connection 2400 over USB (including 5x50)"
+	depends on WIMAX && USB
+        select WIMAX_I2400M
+	help
+	  Select if you have a device based on the Intel WiMAX
+	  Connection 2400 over USB (like any of the Intel Wireless
+	  WiMAX/WiFi Link 5x50 series).
+
+          If unsure, it is safe to select M (module).
+
+config WIMAX_I2400M_SDIO
+	tristate "Intel Wireless WiMAX Connection 2400 over SDIO"
+	depends on WIMAX && MMC
+        select WIMAX_I2400M
+	help
+	  Select if you have a device based on the Intel WiMAX
+          Connection 2400 over SDIO.
+
+          If unsure, it is safe to select M (module).
+
+config WIMAX_I2400M_DEBUG_LEVEL
+       int "WiMAX i2400m debug level"
+       depends on WIMAX_I2400M
+       default 8
+       help
+
+         Select the maximum debug verbosity level to be compiled into
+         the WiMAX i2400m driver code.
+
+         By default, this is disabled at runtime and can be
+         selectively enabled at runtime for different parts of the
+         code using the sysfs debug-levels file.
+
+         If set at zero, this will compile out all the debug code.
+
+         It is recommended that it is left at 8.
diff --git a/drivers/net/wimax/i2400m/Makefile b/drivers/net/wimax/i2400m/Makefile
new file mode 100644
index 0000000..2e8c15b
--- /dev/null
+++ b/drivers/net/wimax/i2400m/Makefile
@@ -0,0 +1,29 @@
+
+obj-$(CONFIG_WIMAX_I2400M) += i2400m.o
+obj-$(CONFIG_WIMAX_I2400M_USB) += i2400m-usb.o
+obj-$(CONFIG_WIMAX_I2400M_SDIO) += i2400m-sdio.o
+
+i2400m-objs :=		\
+	control.o	\
+	driver.o	\
+	fw.o		\
+	op-rfkill.o	\
+	netdev.o	\
+	tx.o		\
+	rx.o
+
+i2400m-$(CONFIG_DEBUG_FS) += debugfs.o
+
+i2400m-usb-objs :=		\
+	usb-fw.o		\
+	usb-notif.o		\
+	usb-tx.o		\
+	usb-rx.o		\
+	usb.o
+
+
+i2400m-sdio-objs := 		\
+	sdio.o      		\
+	sdio-tx.o   		\
+	sdio-fw.o	 	\
+	sdio-rx.o
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 28/29] wimax: export linux/wimax.h and linux/wimax/i2400m.h with headers_install
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (26 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 27/29] i2400m: Makefile and Kconfig Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 12:58 ` [PATCH 29/29] wimax/i2400m: add CREDITS and MAINTAINERS entries Inaky Perez-Gonzalez
  2008-12-15 22:54 ` [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 David Miller
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, David Woodhouse, wimax

These two files are what user space can use to establish communication
with the WiMAX kernel API and to speak the Intel 2400m Wireless WiMAX
connection's control protocol.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
Cc: David Woodhouse <dwmw2@infradead.org>
---
 include/linux/Kbuild       |    2 ++
 include/linux/wimax/Kbuild |    1 +
 2 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 include/linux/wimax/Kbuild

diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index e531783..89079ff 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -375,3 +375,5 @@ unifdef-y += xattr.h
 unifdef-y += xfrm.h
 
 objhdr-y += version.h
+header-y += wimax.h
+header-y += wimax/
diff --git a/include/linux/wimax/Kbuild b/include/linux/wimax/Kbuild
new file mode 100644
index 0000000..3cb4f26
--- /dev/null
+++ b/include/linux/wimax/Kbuild
@@ -0,0 +1 @@
+header-y += i2400m.h
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 29/29] wimax/i2400m: add CREDITS and MAINTAINERS entries
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (27 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 28/29] wimax: export linux/wimax.h and linux/wimax/i2400m.h with headers_install Inaky Perez-Gonzalez
@ 2008-12-15 12:58 ` Inaky Perez-Gonzalez
  2008-12-15 22:54 ` [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 David Miller
  29 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-15 12:58 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

This patch adds entries for the original developers of the i2400m
drivers and up-to-date maintainer entries.

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 CREDITS     |   17 +++++++++++++++++
 MAINTAINERS |   16 ++++++++++++++++
 2 files changed, 33 insertions(+), 0 deletions(-)

diff --git a/CREDITS b/CREDITS
index b50db17..ae5b804 100644
--- a/CREDITS
+++ b/CREDITS
@@ -464,6 +464,11 @@ S: 1200 Goldenrod Dr.
 S: Nampa, Idaho 83686
 S: USA
 
+N: Dirk J. Brandewie
+E: dirk.j.brandewie@intel.com
+E: linux-wimax@intel.com
+D: Intel Wireless WiMAX Connection 2400 SDIO driver
+
 N: Derrick J. Brashear
 E: shadow@dementia.org
 W: http://www.dementia.org/~shadow
@@ -2119,6 +2124,11 @@ N: H.J. Lu
 E: hjl@gnu.ai.mit.edu
 D: GCC + libraries hacker
 
+N: Yanir Lubetkin
+E: yanirx.lubatkin@intel.com
+E: linux-wimax@intel.com
+D: Intel Wireless WiMAX Connection 2400 driver
+
 N: Michal Ludvig
 E: michal@logix.cz
 E: michal.ludvig@asterisk.co.nz
@@ -2693,6 +2703,13 @@ S: RR #5, 497 Pole Line Road
 S: Thunder Bay, Ontario
 S: CANADA P7C 5M9
 
+N: Inaky Perez-Gonzalez
+E: inaky.perez-gonzalez@intel.com
+E: linux-wimax@intel.com
+E: inakypg@yahoo.com
+D: WiMAX stack
+D: Intel Wireless WiMAX Connection 2400 driver
+
 N: Yuri Per
 E: yuri@pts.mipt.ru
 D: Some smbfs fixes
diff --git a/MAINTAINERS b/MAINTAINERS
index c1ea1f6..892b57a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2299,6 +2299,14 @@ W:	http://lists.sourceforge.net/mailman/listinfo/ipw2100-devel
 W:	http://ipw2200.sourceforge.net
 S:	Supported
 
+INTEL WIRELESS WIMAX CONNECTION 2400
+P:	Inaky Perez-Gonzalez
+M:	inaky.perez-gonzalez@intel.com
+M:	linux-wimax@intel.com
+L:	wimax@linuxwimax.org
+S:	Supported
+W:	http://linuxwimax.org
+
 INTEL WIRELESS WIFI LINK (iwlwifi)
 P:	Zhu Yi
 M:	yi.zhu@intel.com
@@ -4703,6 +4711,14 @@ M:	zaga@fly.cc.fer.hr
 L:	linux-scsi@vger.kernel.org
 S:	Maintained
 
+WIMAX STACK
+P:	Inaky Perez-Gonzalez
+M:	inaky.perez-gonzalez@intel.com
+M:	linux-wimax@intel.com
+L:	wimax@linuxwimax.org
+S:	Supported
+W:	http://linuxwimax.org
+
 WIMEDIA LLC PROTOCOL (WLP) SUBSYSTEM
 P:	David Vrabel
 M:	david.vrabel@csr.com
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6
  2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
                   ` (28 preceding siblings ...)
  2008-12-15 12:58 ` [PATCH 29/29] wimax/i2400m: add CREDITS and MAINTAINERS entries Inaky Perez-Gonzalez
@ 2008-12-15 22:54 ` David Miller
  2008-12-17 17:37   ` Inaky Perez-Gonzalez
  29 siblings, 1 reply; 38+ messages in thread
From: David Miller @ 2008-12-15 22:54 UTC (permalink / raw)
  To: inaky; +Cc: netdev, wimax, greg

From: Inaky Perez-Gonzalez <inaky@linux.intel.com>
Date: Mon, 15 Dec 2008 04:57:55 -0800

> This patchset (or the linux-wimax GIT tree at git.kernel.org) merges
> the WiMAX subsystem and driver for the Intel 2400m Wireless WiMAX
> connection to the tree. We'd like to get it in for the 2.6.29 merge
> window. 

Since Thomas is OK with the netlink bits now, I'm ok with
this going into the USB -next tree or wherever is appropriate.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 12/29] i2400m: host/device procotol and core driver definitions
  2008-12-15 12:58 ` [PATCH 12/29] i2400m: host/device procotol and core driver definitions Inaky Perez-Gonzalez
@ 2008-12-16  3:43   ` Wang Chen
  0 siblings, 0 replies; 38+ messages in thread
From: Wang Chen @ 2008-12-16  3:43 UTC (permalink / raw)
  To: Inaky Perez-Gonzalez; +Cc: netdev, wimax, greg

Inaky Perez-Gonzalez said the following on 2008-12-15 20:58:
> + * @wimax_dev: WiMAX generic device for linkage into the kernel WiMAX
> + *     stack. Due to the way a net_device is allocated, we need to
> + *     force this to be the first field so that we can get from
> + *     net_dev_priv() the right pointer.
> + *

net_dev_priv() -> netdev_priv()

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 02/29] wimax: headers for kernel API and user space interaction
  2008-12-15 12:57 ` [PATCH 02/29] wimax: headers for kernel API and user space interaction Inaky Perez-Gonzalez
@ 2008-12-16  3:43   ` Wang Chen
  2008-12-17 17:44     ` Inaky Perez-Gonzalez
  0 siblings, 1 reply; 38+ messages in thread
From: Wang Chen @ 2008-12-16  3:43 UTC (permalink / raw)
  To: Inaky Perez-Gonzalez; +Cc: netdev, wimax, greg

Inaky Perez-Gonzalez said the following on 2008-12-15 20:57:
> + * Usage:
> + *  1. Embed a &struct wimax_dev at *the beginning* the network
> + *     device structure so that net_dev->priv points to it.
> + *

There is no net_device->priv anymore in net-next tree.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6
  2008-12-15 22:54 ` [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 David Miller
@ 2008-12-17 17:37   ` Inaky Perez-Gonzalez
  0 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-17 17:37 UTC (permalink / raw)
  To: David Miller; +Cc: netdev, wimax, greg

On Monday 15 December 2008, David Miller wrote:
> From: Inaky Perez-Gonzalez <inaky@linux.intel.com>
> Date: Mon, 15 Dec 2008 04:57:55 -0800
>
> > This patchset (or the linux-wimax GIT tree at git.kernel.org) merges
> > the WiMAX subsystem and driver for the Intel 2400m Wireless WiMAX
> > connection to the tree. We'd like to get it in for the 2.6.29 merge
> > window.
>
> Since Thomas is OK with the netlink bits now, I'm ok with
> this going into the USB -next tree or wherever is appropriate.

Thanks -- I'll check with gregkh to make sure it goes through his tree.

-- 
Inaky

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [PATCH 02/29] wimax: headers for kernel API and user space interaction
  2008-12-16  3:43   ` Wang Chen
@ 2008-12-17 17:44     ` Inaky Perez-Gonzalez
  0 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-17 17:44 UTC (permalink / raw)
  To: Wang Chen; +Cc: netdev, wimax, greg

On Monday 15 December 2008, Wang Chen wrote:
> Inaky Perez-Gonzalez said the following on 2008-12-15 20:57:
> > + * Usage:
> > + *  1. Embed a &struct wimax_dev at *the beginning* the network
> > + *     device structure so that net_dev->priv points to it.
> > + *
>
> There is no net_device->priv anymore in net-next tree.

You are right -- need to update this documentation to refer to 
netdev_priv() [it is how it is used, anyway].

Thanks,

-- 
Inaky

^ permalink raw reply	[flat|nested] 38+ messages in thread

* [PATCH 18/29] i2400m: debugfs controls
  2008-12-21  0:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v7 Inaky Perez-Gonzalez
@ 2008-12-21  0:57 ` Inaky Perez-Gonzalez
  0 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-21  0:57 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Expose knobs to control the device (induce reset, power saving,
querying tx or rx stats, internal debug information and debug level
manipulation).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/debugfs.c |  392 ++++++++++++++++++++++++++++++++++++
 1 files changed, 392 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/debugfs.c

diff --git a/drivers/net/wimax/i2400m/debugfs.c b/drivers/net/wimax/i2400m/debugfs.c
new file mode 100644
index 0000000..6266329
--- /dev/null
+++ b/drivers/net/wimax/i2400m/debugfs.c
@@ -0,0 +1,392 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Debugfs interfaces to manipulate driver and device information
+ *
+ *
+ * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE debugfs
+#include "debug-levels.h"
+
+static
+int debugfs_netdev_queue_stopped_get(void *data, u64 *val)
+{
+	struct i2400m *i2400m = data;
+	*val = netif_queue_stopped(i2400m->wimax_dev.net_dev);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_netdev_queue_stopped,
+			debugfs_netdev_queue_stopped_get,
+			NULL, "%llu\n");
+
+
+static
+struct dentry *debugfs_create_netdev_queue_stopped(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0400, parent, i2400m,
+				   &fops_netdev_queue_stopped);
+}
+
+
+/*
+ * inode->i_private has the @data argument to debugfs_create_file()
+ */
+static
+int i2400m_stats_open(struct inode *inode, struct file *filp)
+{
+	filp->private_data = inode->i_private;
+	return 0;
+}
+
+/*
+ * We don't allow partial reads of this file, as then the reader would
+ * get weirdly confused data as it is updated.
+ *
+ * So or you read it all or nothing; if you try to read with an offset
+ * != 0, we consider you are done reading.
+ */
+static
+ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->rx_pl_num, i2400m->rx_pl_min,
+		 i2400m->rx_pl_max, i2400m->rx_num,
+		 i2400m->rx_size_acc,
+		 i2400m->rx_size_min, i2400m->rx_size_max);
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	i2400m->rx_pl_num = 0;
+	i2400m->rx_pl_max = 0;
+	i2400m->rx_pl_min = UINT_MAX;
+	i2400m->rx_num = 0;
+	i2400m->rx_size_acc = 0;
+	i2400m->rx_size_min = UINT_MAX;
+	i2400m->rx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_rx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_rx_stats_read,
+	.write =	i2400m_rx_stats_write,
+};
+
+
+/* See i2400m_rx_stats_read() */
+static
+ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->tx_pl_num, i2400m->tx_pl_min,
+		 i2400m->tx_pl_max, i2400m->tx_num,
+		 i2400m->tx_size_acc,
+		 i2400m->tx_size_min, i2400m->tx_size_max);
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	i2400m->tx_pl_num = 0;
+	i2400m->tx_pl_max = 0;
+	i2400m->tx_pl_min = UINT_MAX;
+	i2400m->tx_num = 0;
+	i2400m->tx_size_acc = 0;
+	i2400m->tx_size_min = UINT_MAX;
+	i2400m->tx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_tx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_tx_stats_read,
+	.write =	i2400m_tx_stats_write,
+};
+
+
+/* Write 1 to ask the device to go into suspend */
+static
+int debugfs_i2400m_suspend_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	result = i2400m_cmd_enter_powersave(i2400m);
+	if (result >= 0)
+		result = 0;
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_suspend,
+			NULL, debugfs_i2400m_suspend_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_suspend(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_suspend);
+}
+
+
+/*
+ * Reset the device
+ *
+ * Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus
+ * reset (as defined by enum i2400m_reset_type).
+ */
+static
+int debugfs_i2400m_reset_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	enum i2400m_reset_type rt = val;
+	switch(rt) {
+	case I2400M_RT_WARM:
+	case I2400M_RT_COLD:
+	case I2400M_RT_BUS:
+		result = i2400m->bus_reset(i2400m, rt);
+		if (result >= 0)
+			result = 0;
+	default:
+		result = -EINVAL;
+	}
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_reset,
+			NULL, debugfs_i2400m_reset_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_reset(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_reset);
+}
+
+/*
+ * Debug levels control; see debug.h
+ */
+struct d_level D_LEVEL[] = {
+	D_SUBMODULE_DEFINE(control),
+	D_SUBMODULE_DEFINE(driver),
+	D_SUBMODULE_DEFINE(debugfs),
+	D_SUBMODULE_DEFINE(fw),
+	D_SUBMODULE_DEFINE(netdev),
+	D_SUBMODULE_DEFINE(rfkill),
+	D_SUBMODULE_DEFINE(rx),
+	D_SUBMODULE_DEFINE(tx),
+};
+size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+
+#define __debugfs_register(prefix, name, parent)			\
+do {									\
+	result = d_level_register_debugfs(prefix, name, parent);	\
+	if (result < 0)							\
+		goto error;						\
+} while (0)
+
+
+int i2400m_debugfs_add(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry;
+	struct dentry *fd;
+
+	dentry = debugfs_create_dir("i2400m", dentry);
+	result = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		if (result == -ENODEV)
+			result = 0;	/* No debugfs support */
+		goto error;
+	}
+	i2400m->debugfs_dentry = dentry;
+	__debugfs_register("dl_", control, dentry);
+	__debugfs_register("dl_", driver, dentry);
+	__debugfs_register("dl_", debugfs, dentry);
+	__debugfs_register("dl_", fw, dentry);
+	__debugfs_register("dl_", netdev, dentry);
+	__debugfs_register("dl_", rfkill, dentry);
+	__debugfs_register("dl_", rx, dentry);
+	__debugfs_register("dl_", tx, dentry);
+
+	fd = debugfs_create_size_t("tx_in", 0400, dentry,
+				   &i2400m->tx_in);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_in: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_size_t("tx_out", 0400, dentry,
+				   &i2400m->tx_out);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_out: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_u32("state", 0600, dentry,
+				&i2400m->state);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"state: %d\n", result);
+		goto error;
+	}
+
+	/*
+	 * Trace received messages from user space
+	 *
+	 * In order to tap the bidirectional message stream in the
+	 * 'msg' pipe, user space can read from the 'msg' pipe;
+	 * however, due to limitations in libnl, we can't know what
+	 * the different applications are sending down to the kernel.
+	 *
+	 * So we have this hack where the driver will echo any message
+	 * received on the msg pipe from user space [through a call to
+	 * wimax_dev->op_msg_from_user() into
+	 * i2400m_op_msg_from_user()] into the 'trace' pipe that this
+	 * driver creates.
+	 *
+	 * So then, reading from both the 'trace' and 'msg' pipes in
+	 * user space will provide a full dump of the traffic.
+	 *
+	 * Write 1 to activate, 0 to clear.
+	 *
+	 * It is not really very atomic, but it is also not too
+	 * critical.
+	 */
+	fd = debugfs_create_u8("trace_msg_from_user", 0600, dentry,
+			       &i2400m->trace_msg_from_user);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"trace_msg_from_user: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_netdev_queue_stopped("netdev_queue_stopped",
+						 dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"netdev_queue_stopped: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("rx_stats", 0600, dentry, i2400m,
+				 &i2400m_rx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"rx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("tx_stats", 0600, dentry, i2400m,
+				 &i2400m_tx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_suspend("suspend", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry suspend: %d\n",
+			result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_reset("reset", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry reset: %d\n", result);
+		goto error;
+	}
+
+	result = 0;
+error:
+	return result;
+}
+
+void i2400m_debugfs_rm(struct i2400m *i2400m)
+{
+	debugfs_remove_recursive(i2400m->debugfs_dentry);
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 18/29] i2400m: debugfs controls
  2008-12-10 23:12 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v4 Inaky Perez-Gonzalez
@ 2008-12-10 23:12 ` Inaky Perez-Gonzalez
  0 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-10 23:12 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Expose knobs to control the device (induce reset, power saving,
querying tx or rx stats, internal debug information and debug level
manipulation).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/debugfs.c |  385 ++++++++++++++++++++++++++++++++++++
 1 files changed, 385 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/debugfs.c

diff --git a/drivers/net/wimax/i2400m/debugfs.c b/drivers/net/wimax/i2400m/debugfs.c
new file mode 100644
index 0000000..39de87d
--- /dev/null
+++ b/drivers/net/wimax/i2400m/debugfs.c
@@ -0,0 +1,385 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Debugfs interfaces to manipulate driver and device information
+ *
+ *
+ * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE debugfs
+#include "debug-levels.h"
+
+static
+int debugfs_netdev_queue_stopped_get(void *data, u64 *val)
+{
+	struct i2400m *i2400m = data;
+	*val = netif_queue_stopped(i2400m->wimax_dev.net_dev);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_netdev_queue_stopped,
+			debugfs_netdev_queue_stopped_get,
+			NULL, "%llu\n");
+
+
+static
+struct dentry *debugfs_create_netdev_queue_stopped(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0400, parent, i2400m,
+				   &fops_netdev_queue_stopped);
+}
+
+
+/*
+ * inode->i_private has the @data argument to debugfs_create_file()
+ */
+static
+int i2400m_stats_open(struct inode *inode, struct file *filp)
+{
+	filp->private_data = inode->i_private;
+	return 0;
+}
+
+/*
+ * We don't allow partial reads of this file, as then the reader would
+ * get weirdly confused data as it is updated.
+ *
+ * So or you read it all or nothing; if you try to read with an offset
+ * != 0, we consider you are done reading.
+ */
+static
+ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->rx_pl_num, i2400m->rx_pl_min,
+		 i2400m->rx_pl_max, i2400m->rx_num,
+		 i2400m->rx_size_acc,
+		 i2400m->rx_size_min, i2400m->rx_size_max);
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	i2400m->rx_pl_num = 0;
+	i2400m->rx_pl_max = 0;
+	i2400m->rx_pl_min = UINT_MAX;
+	i2400m->rx_num = 0;
+	i2400m->rx_size_acc = 0;
+	i2400m->rx_size_min = UINT_MAX;
+	i2400m->rx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_rx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_rx_stats_read,
+	.write =	i2400m_rx_stats_write,
+};
+
+
+/* See i2400m_rx_stats_read() */
+static
+ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->tx_pl_num, i2400m->tx_pl_min,
+		 i2400m->tx_pl_max, i2400m->tx_num,
+		 i2400m->tx_size_acc,
+		 i2400m->tx_size_min, i2400m->tx_size_max);
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	i2400m->tx_pl_num = 0;
+	i2400m->tx_pl_max = 0;
+	i2400m->tx_pl_min = UINT_MAX;
+	i2400m->tx_num = 0;
+	i2400m->tx_size_acc = 0;
+	i2400m->tx_size_min = UINT_MAX;
+	i2400m->tx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_tx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_tx_stats_read,
+	.write =	i2400m_tx_stats_write,
+};
+
+
+/* Write 1 to ask the device to go into suspend */
+static
+int debugfs_i2400m_suspend_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	result = i2400m_cmd_enter_powersave(i2400m);
+	if (result >= 0)
+		result = 0;
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_suspend,
+			NULL, debugfs_i2400m_suspend_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_suspend(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_suspend);
+}
+
+
+/*
+ * Reset the device
+ *
+ * Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus
+ * reset (as defined by enum i2400m_reset_type).
+ */
+static
+int debugfs_i2400m_reset_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	enum i2400m_reset_type rt = val;
+	result = i2400m->bus_reset(i2400m, rt);
+	if (result >= 0)
+		result = 0;
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_reset,
+			NULL, debugfs_i2400m_reset_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_reset(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_reset);
+}
+
+/*
+ * Debug levels control; see debug.h
+ */
+struct d_level D_LEVEL[] = {
+	D_SUBMODULE_DEFINE(control),
+	D_SUBMODULE_DEFINE(driver),
+	D_SUBMODULE_DEFINE(debugfs),
+	D_SUBMODULE_DEFINE(fw),
+	D_SUBMODULE_DEFINE(netdev),
+	D_SUBMODULE_DEFINE(rfkill),
+	D_SUBMODULE_DEFINE(rx),
+	D_SUBMODULE_DEFINE(tx),
+};
+size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+
+#define __debugfs_register(prefix, name, parent)			\
+do {									\
+	result = d_level_register_debugfs(prefix, name, parent);	\
+	if (result < 0)							\
+		goto error;						\
+} while (0)
+
+
+int i2400m_debugfs_add(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry;
+	struct dentry *fd;
+
+	dentry = debugfs_create_dir("i2400m", dentry);
+	result = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		if (result == -ENODEV)
+			result = 0;	/* No debugfs support */
+		goto error;
+	}
+	i2400m->debugfs_dentry = dentry;
+	__debugfs_register("dl_", control, dentry);
+	__debugfs_register("dl_", driver, dentry);
+	__debugfs_register("dl_", debugfs, dentry);
+	__debugfs_register("dl_", fw, dentry);
+	__debugfs_register("dl_", netdev, dentry);
+	__debugfs_register("dl_", rfkill, dentry);
+	__debugfs_register("dl_", rx, dentry);
+	__debugfs_register("dl_", tx, dentry);
+
+	fd = debugfs_create_size_t("tx_in", 0400, dentry,
+				   &i2400m->tx_in);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_in: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_size_t("tx_out", 0400, dentry,
+				   &i2400m->tx_out);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_out: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_u32("state", 0600, dentry,
+				&i2400m->state);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"state: %d\n", result);
+		goto error;
+	}
+
+	/*
+	 * Trace received messages from user space
+	 *
+	 * In order to tap the bidirectional message stream in the
+	 * 'msg' pipe, user space can read from the 'msg' pipe;
+	 * however, due to limitations in libnl, we can't know what
+	 * the different applications are sending down to the kernel.
+	 *
+	 * So we have this hack where the driver will echo any message
+	 * received on the msg pipe from user space [through a call to
+	 * wimax_dev->op_msg_from_user() into
+	 * i2400m_op_msg_from_user()] into the 'trace' pipe that this
+	 * driver creates.
+	 *
+	 * So then, reading from both the 'trace' and 'msg' pipes in
+	 * user space will provide a full dump of the traffic.
+	 *
+	 * Write 1 to activate, 0 to clear.
+	 *
+	 * It is not really very atomic, but it is also not too
+	 * critical.
+	 */
+	fd = debugfs_create_u8("trace_msg_from_user", 0600, dentry,
+			       &i2400m->trace_msg_from_user);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"trace_msg_from_user: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_netdev_queue_stopped("netdev_queue_stopped",
+						 dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"netdev_queue_stopped: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("rx_stats", 0600, dentry, i2400m,
+				 &i2400m_rx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"rx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("tx_stats", 0600, dentry, i2400m,
+				 &i2400m_tx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_suspend("suspend", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry suspend: %d\n",
+			result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_reset("reset", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry reset: %d\n", result);
+		goto error;
+	}
+
+	result = 0;
+error:
+	return result;
+}
+
+void i2400m_debugfs_rm(struct i2400m *i2400m)
+{
+	debugfs_remove_recursive(i2400m->debugfs_dentry);
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* [PATCH 18/29] i2400m: debugfs controls
  2008-12-08 19:09 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v3 Inaky Perez-Gonzalez
@ 2008-12-08 19:09 ` Inaky Perez-Gonzalez
  0 siblings, 0 replies; 38+ messages in thread
From: Inaky Perez-Gonzalez @ 2008-12-08 19:09 UTC (permalink / raw)
  To: netdev; +Cc: greg, wimax

Expose knobs to control the device (induce reset, power saving,
querying tx or rx stats, internal debug information and debug level
manipulation).

Signed-off-by: Inaky Perez-Gonzalez <inaky@linux.intel.com>
---
 drivers/net/wimax/i2400m/debugfs.c |  383 ++++++++++++++++++++++++++++++++++++
 1 files changed, 383 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/wimax/i2400m/debugfs.c

diff --git a/drivers/net/wimax/i2400m/debugfs.c b/drivers/net/wimax/i2400m/debugfs.c
new file mode 100644
index 0000000..9d410fe
--- /dev/null
+++ b/drivers/net/wimax/i2400m/debugfs.c
@@ -0,0 +1,383 @@
+/*
+ * Intel Wireless WiMAX Connection 2400m
+ * Debugfs interfaces to manipulate driver and device information
+ *
+ *
+ * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com>
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include "i2400m.h"
+
+
+#define D_SUBMODULE debugfs
+#include "debug-levels.h"
+
+static
+int debugfs_netdev_queue_stopped_get(void *data, u64 *val)
+{
+	struct i2400m *i2400m = data;
+	*val = netif_queue_stopped(i2400m->wimax_dev.net_dev);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_netdev_queue_stopped,
+			debugfs_netdev_queue_stopped_get,
+			NULL, "%llu\n");
+
+
+static
+struct dentry *debugfs_create_netdev_queue_stopped(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0400, parent, i2400m,
+				   &fops_netdev_queue_stopped);
+}
+
+
+/*
+ * inode->i_private has the @data argument to debugfs_create_file()
+ */
+static
+int i2400m_stats_open(struct inode *inode, struct file *filp)
+{
+	filp->private_data = inode->i_private;
+	return 0;
+}
+
+/*
+ * We don't allow partial reads of this file, as then the reader would
+ * get weirdly confused data as it is updated.
+ *
+ * So or you read it all or nothing; if you try to read with an offset
+ * != 0, we consider you are done reading.
+ */
+static
+ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->rx_pl_num, i2400m->rx_pl_min,
+		 i2400m->rx_pl_max, i2400m->rx_num,
+		 i2400m->rx_size_acc,
+		 i2400m->rx_size_min, i2400m->rx_size_max);
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->rx_lock, flags);
+	i2400m->rx_pl_num = 0;
+	i2400m->rx_pl_max = 0;
+	i2400m->rx_pl_min = UINT_MAX;
+	i2400m->rx_num = 0;
+	i2400m->rx_size_acc = 0;
+	i2400m->rx_size_min = UINT_MAX;
+	i2400m->rx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->rx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_rx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_rx_stats_read,
+	.write =	i2400m_rx_stats_write,
+};
+
+
+/* See i2400m_rx_stats_read() */
+static
+ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer,
+			     size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	char buf[128];
+	unsigned long flags;
+
+	if (*ppos != 0)
+		return 0;
+	if (count < sizeof(buf))
+		return -ENOSPC;
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n",
+		 i2400m->tx_pl_num, i2400m->tx_pl_min,
+		 i2400m->tx_pl_max, i2400m->tx_num,
+		 i2400m->tx_size_acc,
+		 i2400m->tx_size_min, i2400m->tx_size_max);
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+/* Any write clears the stats */
+static
+ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer,
+			      size_t count, loff_t *ppos)
+{
+	struct i2400m *i2400m = filp->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&i2400m->tx_lock, flags);
+	i2400m->tx_pl_num = 0;
+	i2400m->tx_pl_max = 0;
+	i2400m->tx_pl_min = UINT_MAX;
+	i2400m->tx_num = 0;
+	i2400m->tx_size_acc = 0;
+	i2400m->tx_size_min = UINT_MAX;
+	i2400m->tx_size_max = 0;
+	spin_unlock_irqrestore(&i2400m->tx_lock, flags);
+	return count;
+}
+
+static
+const struct file_operations i2400m_tx_stats_fops = {
+	.owner =	THIS_MODULE,
+	.open =		i2400m_stats_open,
+	.read =		i2400m_tx_stats_read,
+	.write =	i2400m_tx_stats_write,
+};
+
+
+/* Write 1 to ask the device to go into suspend */
+static
+int debugfs_i2400m_suspend_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	result = i2400m_cmd_enter_powersave(i2400m);
+	if (result >= 0)
+		result = 0;
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_suspend,
+			NULL, debugfs_i2400m_suspend_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_suspend(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_suspend);
+}
+
+
+/*
+ * Reset the device
+ *
+ * Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus
+ * reset (as defined by enum i2400m_reset_type).
+ */
+static
+int debugfs_i2400m_reset_set(void *data, u64 val)
+{
+	int result;
+	struct i2400m *i2400m = data;
+	enum i2400m_reset_type rt = val;
+	result = i2400m->bus_reset(i2400m, rt);
+	if (result >= 0)
+		result = 0;
+	return result;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_reset,
+			NULL, debugfs_i2400m_reset_set,
+			"%llu\n");
+
+static
+struct dentry *debugfs_create_i2400m_reset(
+	const char *name, struct dentry *parent, struct i2400m *i2400m)
+{
+	return debugfs_create_file(name, 0200, parent, i2400m,
+				   &fops_i2400m_reset);
+}
+
+/*
+ * Debug levels control; see debug.h
+ */
+struct d_level D_LEVEL[] = {
+	D_SUBMODULE_DEFINE(control),
+	D_SUBMODULE_DEFINE(driver),
+	D_SUBMODULE_DEFINE(debugfs),
+	D_SUBMODULE_DEFINE(fw),
+	D_SUBMODULE_DEFINE(netdev),
+	D_SUBMODULE_DEFINE(rfkill),
+	D_SUBMODULE_DEFINE(rx),
+	D_SUBMODULE_DEFINE(tx),
+};
+size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL);
+
+#define __debugfs_register(prefix, name, parent)		\
+	result = d_level_register_debugfs(prefix, name, parent);\
+	if (result < 0)						\
+		goto error;
+
+
+int i2400m_debugfs_add(struct i2400m *i2400m)
+{
+	int result;
+	struct device *dev = i2400m_dev(i2400m);
+	struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry;
+	struct dentry *fd;
+
+	dentry = debugfs_create_dir("i2400m", dentry);
+	result = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		if (result == -ENODEV)
+			result = 0;	/* No debugfs support */
+		goto error;
+	}
+	i2400m->debugfs_dentry = dentry;
+	__debugfs_register("dl_", control, dentry);
+	__debugfs_register("dl_", driver, dentry);
+	__debugfs_register("dl_", debugfs, dentry);
+	__debugfs_register("dl_", fw, dentry);
+	__debugfs_register("dl_", netdev, dentry);
+	__debugfs_register("dl_", rfkill, dentry);
+	__debugfs_register("dl_", rx, dentry);
+	__debugfs_register("dl_", tx, dentry);
+
+	fd = debugfs_create_size_t("tx_in", 0400, dentry,
+				   &i2400m->tx_in);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_in: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_size_t("tx_out", 0400, dentry,
+				   &i2400m->tx_out);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_out: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_u32("state", 0600, dentry,
+				&i2400m->state);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"state: %d\n", result);
+		goto error;
+	}
+
+	/*
+	 * Trace received messages from user space
+	 *
+	 * In order to tap the bidirectional message stream in the
+	 * 'msg' pipe, user space can read from the 'msg' pipe;
+	 * however, due to limitations in libnl, we can't know what
+	 * the different applications are sending down to the kernel.
+	 *
+	 * So we have this hack where the driver will echo any message
+	 * received on the msg pipe from user space [through a call to
+	 * wimax_dev->op_msg_from_user() into
+	 * i2400m_op_msg_from_user()] into the 'trace' pipe that this
+	 * driver creates.
+	 *
+	 * So then, reading from both the 'trace' and 'msg' pipes in
+	 * user space will provide a full dump of the traffic.
+	 *
+	 * Write 1 to activate, 0 to clear.
+	 *
+	 * It is not really very atomic, but it is also not too
+	 * critical.
+	 */
+	fd = debugfs_create_u8("trace_msg_from_user", 0600, dentry,
+			       &i2400m->trace_msg_from_user);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"trace_msg_from_user: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_netdev_queue_stopped("netdev_queue_stopped",
+						 dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"netdev_queue_stopped: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("rx_stats", 0600, dentry, i2400m,
+				 &i2400m_rx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"rx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_file("tx_stats", 0600, dentry, i2400m,
+				 &i2400m_tx_stats_fops);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry "
+			"tx_stats: %d\n", result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_suspend("suspend", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry suspend: %d\n",
+			result);
+		goto error;
+	}
+
+	fd = debugfs_create_i2400m_reset("reset", dentry, i2400m);
+	result = PTR_ERR(fd);
+	if (IS_ERR(fd) && result != -ENODEV) {
+		dev_err(dev, "Can't create debugfs entry reset: %d\n", result);
+		goto error;
+	}
+
+	result = 0;
+error:
+	return result;
+}
+
+void i2400m_debugfs_rm(struct i2400m *i2400m)
+{
+	debugfs_remove_recursive(i2400m->debugfs_dentry);
+}
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 38+ messages in thread

end of thread, other threads:[~2008-12-21  0:57 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-12-15 12:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 Inaky Perez-Gonzalez
2008-12-15 12:57 ` [PATCH 01/29] wimax: documentation for the stack Inaky Perez-Gonzalez
2008-12-15 12:57 ` [PATCH 02/29] wimax: headers for kernel API and user space interaction Inaky Perez-Gonzalez
2008-12-16  3:43   ` Wang Chen
2008-12-17 17:44     ` Inaky Perez-Gonzalez
2008-12-15 12:57 ` [PATCH 03/29] wimax: internal API for the kernel space WiMAX stack Inaky Perez-Gonzalez
2008-12-15 12:57 ` [PATCH 04/29] wimax: debug macros and debug settings for the " Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 05/29] wimax: generic device management (registration, deregistration, lookup) Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 06/29] genetlink: export genl_unregister_mc_group() Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 07/29] wimax: basic API: kernel/user messaging, rfkill and reset Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 08/29] debugfs: add helpers for exporting a size_t simple value Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 09/29] wimax: debugfs controls Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 10/29] wimax: Makefile, Kconfig and docbook linkage for the stack Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 11/29] i2400m: documentation and instructions for usage Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 12/29] i2400m: host/device procotol and core driver definitions Inaky Perez-Gonzalez
2008-12-16  3:43   ` Wang Chen
2008-12-15 12:58 ` [PATCH 13/29] i2400m: Generic probe/disconnect, reset and message passing Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 14/29] i2400m: linkage to the networking stack Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 15/29] i2400m: firmware loading and bootrom initialization Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 16/29] i2400m: RX and TX data/control paths Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 17/29] i2400m: various functions for device management Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 18/29] i2400m: debugfs controls Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 19/29] i2400m/USB: header for the USB bus driver Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 20/29] i2400m/USB: probe/disconnect, dev init/shutdown and reset backends Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 21/29] i2400m/USB: firmware upload backend Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 22/29] i2400m/USB: TX and RX path backends Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 23/29] i2400m/SDIO: header for the SDIO subdriver Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 24/29] i2400m/SDIO: probe/disconnect, dev init/shutdown and reset backends Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 25/29] i2400m/SDIO: firmware upload backend Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 26/29] i2400m/SDIO: TX and RX path backends Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 27/29] i2400m: Makefile and Kconfig Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 28/29] wimax: export linux/wimax.h and linux/wimax/i2400m.h with headers_install Inaky Perez-Gonzalez
2008-12-15 12:58 ` [PATCH 29/29] wimax/i2400m: add CREDITS and MAINTAINERS entries Inaky Perez-Gonzalez
2008-12-15 22:54 ` [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v6 David Miller
2008-12-17 17:37   ` Inaky Perez-Gonzalez
  -- strict thread matches above, loose matches on Subject: below --
2008-12-21  0:57 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v7 Inaky Perez-Gonzalez
2008-12-21  0:57 ` [PATCH 18/29] i2400m: debugfs controls Inaky Perez-Gonzalez
2008-12-10 23:12 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v4 Inaky Perez-Gonzalez
2008-12-10 23:12 ` [PATCH 18/29] i2400m: debugfs controls Inaky Perez-Gonzalez
2008-12-08 19:09 [PATCH 00/29] merge request for WiMAX kernel stack and i2400m driver v3 Inaky Perez-Gonzalez
2008-12-08 19:09 ` [PATCH 18/29] i2400m: debugfs controls Inaky Perez-Gonzalez

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.