/*------------------------------------------------------------------------------
    elomaxdriver: A linux kernel driver to control http://www.elomax.nl
                  usb-io modules
    Copyright (C) 2011  programming <at> kogro org

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
------------------------------------------------------------------------------*/
/**
  @PROJECTDOC_IMPL1
  @PROJECTDOC_IMPL2

  @section req_Elomax Elomax Driver Requirements
  Elomax (http://www.elomax.nl) units are units that control io and can be
  addressed via usb. Elomax produces several types:
    - iosolution, 12 single bit digital io,
    - aninsolution, 4 differential 12 bit analog in plus 8 digital io,
    - fansolution, 4 8 bits pwm outputs plus 8 digital io

  All these 'solutions' have in common that they communicate via 8 byte blocks,
  though the content of these 8 bytes differs per 'solution'.

  That is where this package comes in. This package has to implement the bare
  usb communication, delivering the 8 byte blocks both ways, without
  interpreting their content.

  This package has to fullfill the requirements:

  - it has to be implemented as a linux 2.6 driver
  - it must be capable to deliver the last received information from the
    'solution' to the requesting application
  - it must be capable to deliver the data from the application to the
    'solution'
  - it must support at least the 'solution' types: iosolution, aninsolution and
    fansolution
  - it must be capable to tell an user application the type of 'solution'
    'behind' the device file
  - it must be capable to tell the application a unique and reproducable
    identification, representing the physical connection of the 'solution' (to
    which usb-bus, via which hub, at which port the 'solution' is connected).
  - it must support simultaneous access by multiple applications

  @section userview_Elomax Elomax Driver User View
  To install the driver in the system:
  - build the elomax.ko file from the sources in this package
  - place the elomax.ko file somewhere in the /lib/modules/`uname -r`/
    directory
  - run the command 'depmod -a'
  - place the 20-elomax.rules file in the /etc/udev/rules.d/ directory

  When an elomax unit is connected to the system's usb-bus, the kernel will
  tell udev about it. This result udev in telling the kernel to load the
  elomax.ko module, after which the usb-core of the kernel will 'probe' the unit
  through this package. The driver will accept control over the unit, which it
  tells to the usb-core, who will tell udev to create a device file for the
  unit.

  Via this device file can an user application access the driver and so the
  unit. The allowed operations on the device file are described in elomax.h.

  @dot
  graph ElomaxOverview
  {
    node [ shape = tab, fontname = Helvetica, fontsize = 10 ];
    edge [ style = solid, arrowhead = none, arrowtail = none ];

    { rank = source ; UserApp }
    { rank = same ; DeviceFile Udev }
    { rank = sink ; Elomax Kernel }

    UserApp [ shape = rectangle, label = "User\nApplication" ];
    DeviceFile [
      shape = rectangle,
      label = "device file\n/dev/elomax%d",
      URL = "\ref elomax.h" ];
    Udev [ label = "udev" ];
    Elomax [ label = "Elomax Driver\n(this package)" ];
    Kernel [ label = "kernel" ];

    UserApp -- DeviceFile [ arrowhead = normal ];
    DeviceFile -- Udev [ arrowtail = normal ];
    Udev -- Kernel [ style = dashed, arrowtail = normal ];
    DeviceFile -- Elomax [ arrowhead = normal ];
    Elomax -- Kernel [ style = dashed, arrowhead = normal, arrowtail = normal ];
  }
  @enddot

  @section impl_Elomax Elomax Driver Implementation
  Where the picture above gives an hierarchical overview, the picture below
  gives the relationship between functions and data inside this package, roughly
  ordered in time.

  The solid lines represent the usage/update of data by a function. The dashed
  lines represent one function calling another. The dotted lines represent the
  possibility to find related data (usually a 'pointer to' the destination data
  is included in the source data).

  @dot
  graph Elomax
  {
    node [ shape = rect, fontname = Helvetica, fontsize = 10 ];
    edge [ style = dashed, arrowhead = normal, arrowtail = none ];

    KernelModuleInit
      [ label = kernelmodule_init, URL = "\ref kernelmodule_init" ];
    KernelModuleExit
      [ label = kernelmodule_exit, URL = "\ref kernelmodule_exit" ];

    ElomaxUsbidTable
      [ shape = plaintext,
        label = "usb id table\n{ VendorId, ProductId }" ];
    ElomaxUsbdriverTable
      [ shape = plaintext,
        label = "usb driver table\n.probe = ...\n.disconnect = ..." ];
    ElomaxDriverConstruct
      [ label = "elomax_driver_construct",
        URL = "\ref elomax_driver_construct" ];
    ElomaxDriverDestruct
      [ label = "elomax_driver_destruct",
        URL = "\ref elomax_driver_destruct" ];
    UsbInterface [ shape = plaintext, label = "struct usb_interface" ];
    ElomaxDriverProbe
      [ label = "elomax_driver_probe", URL = "\ref elomax_driver_probe" ];
    ElomaxDriverDisconnect
      [ label = "elomax_driver_disconnect",
        URL = "\ref elomax_driver_disconnect" ];

    ElomaxInstanceFileOps
      [ shape = plaintext,
        label = "file operations table" ];
    ElomaxClassDriver
      [ shape = plaintext,
        label = "usb class driver table\nname = elomax%d" ];
    ElomaxInstance
      [ shape = plaintext,
        label = "elomax instance",
        URL = "\ref elomax_instance" ];
    ReadUrb
      [ shape = plaintext,
        label = "struct urb\n(read)" ];
    WriteUrb
      [ shape = plaintext,
        label = "struct urb\n(write)" ];
    File [ shape = plaintext, label = "struct file" ];
    ElomaxInstanceReadComplete
      [ label = "elomax_instance_read_complete",
        URL = "\ref elomax_instance_read_complete" ];
    ElomaxInstanceWriteComplete
      [ label = "elomax_instance_write_complete",
        URL = "\ref elomax_instance_write_complete" ];
    ElomaxInstanceConstruct
      [ label = "elomax_instance_construct",
        URL = "\ref elomax_instance_construct" ];
    ElomaxInstanceDestruct
      [ label = "elomax_instance_destruct",
        URL = "\ref elomax_instance_destruct" ];
    ElomaxInstanceOpen
      [ label = "elomax_instance_open", URL = "\ref elomax_instance_open" ];
    ElomaxInstanceClose
      [ label = "elomax_instance_close", URL = "\ref elomax_instance_close" ];
    ElomaxInstanceRead
      [ label = "elomax_instance_read", URL = "\ref elomax_instance_read" ];
    ElomaxInstanceWrite
      [ label = "elomax_instance_write", URL = "\ref elomax_instance_write" ];

    KernelModuleInit -- ElomaxDriverConstruct;
    KernelModuleExit -- ElomaxDriverDestruct;

    { rank = same ;
      ElomaxDriverConstruct ElomaxUsbdriverTable ElomaxDriverDestruct }
    ElomaxDriverConstruct -- ElomaxUsbdriverTable
      [ style = solid, arrowhead = none, arrowtail = normal ];
    ElomaxUsbdriverTable -- ElomaxDriverDestruct [ style = solid ];

    ElomaxUsbdriverTable -- ElomaxUsbidTable [ style = dotted ];
    ElomaxUsbdriverTable -- ElomaxDriverProbe [ style = dotted ];
    ElomaxUsbdriverTable -- ElomaxDriverDisconnect [ style = dotted ];

    { rank = same ; ElomaxDriverProbe ElomaxUsbidTable ElomaxDriverDisconnect }
    ElomaxDriverProbe -- ElomaxUsbidTable -- ElomaxDriverDisconnect
      [ style = invisible, arrowhead = none ];

    { rank = same ;
      ElomaxInstanceConstruct ElomaxClassDriver ElomaxInstanceDestruct }
    ElomaxInstanceConstruct -- ElomaxClassDriver 
      [ style = solid, arrowhead = none, arrowtail = normal ];
    ElomaxClassDriver -- ElomaxInstanceDestruct [ style = solid ];

    ElomaxDriverProbe -- ElomaxInstanceConstruct;
    ElomaxDriverProbe -- UsbInterface [ style = solid, arrowtail = normal ];
    UsbInterface -- ElomaxInstance [ style = dotted, arrowtail = normal ];

    ElomaxDriverDisconnect -- ElomaxInstanceDestruct;
    ElomaxDriverDisconnect -- UsbInterface
      [ style = solid, arrowtail = normal ];

    UsbInterface -- ElomaxInstanceConstruct [ style = solid ];
    UsbInterface -- ElomaxInstanceDestruct [ style = solid ];
    ElomaxInstanceConstruct -- ElomaxInstance [ style = solid ];
    ElomaxInstanceDestruct -- ElomaxInstance [ style = solid ];

    ElomaxInstanceFileOps -- ElomaxInstanceOpen [ style = dotted ];
    ElomaxInstanceFileOps -- ElomaxInstanceClose [ style = dotted ];
    ElomaxInstanceFileOps -- ElomaxInstanceRead [ style = dotted ];
    ElomaxInstanceFileOps -- ElomaxInstanceWrite [ style = dotted ];

    ElomaxClassDriver -- ElomaxInstanceFileOps [ style = dotted ];

    ElomaxInstance -- File
      [ style = dotted, arrowhead = none, arrowtail = normal ];
    ElomaxInstanceOpen -- File
      [ style = solid, arrowtail = normal ];
    ElomaxInstanceClose -- File
      [ style = solid, arrowhead = none, arrowtail = normal ];

    { rank = same ;
        ElomaxInstanceReadComplete ElomaxInstance ElomaxInstanceWriteComplete }
    { rank = same ; ElomaxInstanceFileOps WriteUrb }

    WriteUrb -- ElomaxInstanceWrite
      [ style = solid, arrowhead = none, arrowtail = normal ];
    ElomaxInstanceWriteComplete -- WriteUrb
      [ style = solid, arrowhead = none, arrowtail = normal ];
    ElomaxInstance -- ElomaxInstanceWriteComplete
      [ style = solid, arrowhead = none, arrowtail = normal ];

    ElomaxInstanceConstruct -- ReadUrb [ style = solid ];
    ReadUrb -- ElomaxInstanceReadComplete [ style = solid ];
    ElomaxInstanceReadComplete -- ElomaxInstance [ style = solid ];

    ElomaxInstance -- ElomaxInstanceRead [ style = solid ];
    ReadUrb -- ElomaxInstance [ style = dotted ];
    WriteUrb -- ElomaxInstance [ style = dotted ];

    ElomaxInstanceRead -- File
      [ style = solid, arrowhead = none, arrowtail = normal ];
    ElomaxInstanceWrite -- File
      [ style = solid, arrowhead = none, arrowtail = normal ];

    ElomaxInstance -- ElomaxInstanceWrite [ style = solid ];
  }
  @enddot

  First there is the stage of module loading. The modprobe/rmmod commands
  respectively trigger the execution of the kernelmodule_init() and
  kernelmodule_exit() functions. This functionality is described in more detail
  in @ref kernelmodule.h.

  The (un)loading of this package triggers the construction/destruction of the
  elomax_driver layer. The functions elomax_driver_construct() and
  elomax_driver_destruct() announce this package to the usb-core. It gives the
  @e {vendorid, @e productid} pairs for which this driver wants to be informed.
  When such a unit is connected/disconnected, the functions according the
  driver-table are called (elomax_driver_probe() and
  elomax_driver_disconnect()). This functionality is described in more detail
  in @ref elomaxdriver.h.

  The third stage is triggered when an elomax-unit is connected to the usb-bus.
  This results in the construction of an elomax_instance which is announced to
  the usb-core by telling the class-driver-table. The usb-core now (lets)
  create(s) the corresponding device-file with its minor-node. This is describe
  in more detail in @ref elomaxinstance.h.

  The fourth and last stage is user-access to the device-file. The class-driver
  told the usb-core which function to call, which when the user access the
  device-file results in elomax_instance_open(), elomax_instance_read(),
  elomax_instance_write() and/or elomax_instance_close() to be called. These
  functions are described in more detail in elomaxinstance-priv.h.

  This interaction can also be depicted as shown in the figure below.

  @msc
    width = "1000";

    user [ label = "User Space\nProgram" ],
    kernel [ label = "kernel" ],
    module [ label = "kernel\nmodule", URL = "\ref kernelmodule.h" ],
    driver [ label = "elomax\ndriver", URL = "\ref elomaxdriver.h" ],
    instance [ label = "elomax\ninstance", URL = "\ref elomax_instance" ],
    usb [ label = "kernel's\nUSB-core" ];

    |||;
    --- [ label = "load the driver in the kernel" ];
    |||;
    user => kernel [ label = "modprobe elomax" ];
    kernel => module
      [ label = "kernelmodule_init()", URL = "\ref kernelmodule_init()" ];
    module => driver
      [ label = "elomax_driver_construct()",
        URL = "\ref elomax_driver_construct()" ];
    driver => usb [ label= "usb_register()" ];
    |||;
    --- [ label = "an elomax-device is attached to the usb-bus" ];
    |||;
    usb => driver
      [ label = "elomax_driver_probe()", URL = "\ref elomax_driver_probe()" ];
    driver => instance
      [ label = "elomax_instance_construct()",
        URL = "\ref elomax_instance_construct()" ];
    instance => kernel
      [ label = "kzalloc()\nkref_init()\nsema_init()\nspin_lock_init()",
        ID = "reserve and initialize memory for instance" ];
    instance => usb
      [ label = "usb_alloc_urb()", ID = "for read- and write-buffer" ];
    instance => usb
      [ label = "usb_buffer_alloc()", ID = "for read- and write-buffer" ];
    instance => usb
      [ label = "usb_register_dev()",
        ID = "usb/udev create device-file" ];
    instance rbox instance
      [ label = "start reading device" ];
    instance => usb
      [ label = "usb_fill_int_urb( read_urb )\nusb_submit_urb( read_urb )" ];
    driver => usb [ label = "usb_set_intfdata()" ];
    |||;
    --- [ label = "device has data for us" ];
    |||;
    usb => instance
      [ label = "elomax_instance_read_complete()",
        URL = "\ref elomax_instance_read_complete()" ];
    instance => instance [ label = "spin_lock()" ];
    instance rbox instance [ label = "remember received data" ];
    instance => usb
      [ label = "usb_submit_urb( read_urb )",
        ID = "prepare next read" ];
    instance => instance [ label = "spin_unlock()" ];
    |||;
    --- [ label = "user opens the device" ];
    |||;
    user => kernel [ label = "open()" ];
    kernel => instance
      [ label = "elomax_instance_open()", URL = "\ref elomax_instance_open()" ];
    instance => kernel
      [ label = "kref_get()",
        ID = "store instance in FILE" ];
    |||;
    --- [ label = "user reads the device" ];
    |||;
    user => kernel [ label = "read()" ];
    kernel => instance
      [ label = "elomax_instance_read()", URL = "\ref elomax_instance_read()" ];
    instance => instance [ label = "spin_lock_irqsave()" ];
    instance rbox instance [ label = "copy_to_user( remembered data )" ];
    instance => instance [ label = "spin_unlock_irqrestore()" ];
    |||;
    --- [ label = "user writes the device" ];
    |||;
    user => kernel [ label = "write()" ];
    kernel => instance
      [ label = "elomax_instance_write()",
        URL = "\ref elomax_instance_write()" ];
    instance => instance [ label = "down()", ID = "only one writer" ];
    instance => instance [ label = "spin_lock_irqsave()" ];
    instance rbox instance [ label = "copy_from_user( write_urb )" ];
    instance => usb
      [ label = "usb_sndctrlpipe()\nusb_fill_control_urb( write_urb )\nusb_submit_urb( write_urb )" ];
    instance => instance [ label = "spin_unlock_irqrestore()" ];
    ...;
    ... [ label = "user now continues, while usb-core does its work" ];
    ...;
    usb => instance
      [ label = "elomax_instance_write_complete()",
        URL = "\ref elomax_instance_write_complete()" ];
    instance => instance [ label = "spin_lock()" ];
    instance rbox instance [ label = "remember written data" ];
    instance => instance [ label = "spin_unlock()" ];
    instance => instance [ label = "up()", ID = "allow next writer" ];
    |||;
    --- [ label = "user closes the device" ];
    |||;
    user => kernel [ label = "close()" ];
    kernel => instance
      [ label = "elomax_instance_close()",
        URL = "\ref elomax_instance_close()" ];
    instance => kernel
      [ label = "kref_put()",
        ID = "remove instance from FILE" ];
    |||;
    --- [ label = "an elomax-device is detached from the usb-bus" ];
    |||;
    usb => driver
      [ label = "elomax_driver_disconnect()",
        URL = "\ref elomax_driver_disconnect" ];
    driver => usb
      [ label = "usb_set_intfdata()",
        ID = "disable incoming writes" ];
    driver => instance
      [ label = "elomax_instance_destruct()",
        URL = "\ref elomax_instance_destruct" ];
    instance => usb [ label = "usb_deregister_dev()" ];
    instance => instance [ label = "spin_lock_irqsave()" ];
    instance => usb [ label = "usb_kill_urb()", ID = "kill read-urb" ];
    instance => instance [ label = "spin_lock_irqrestore()" ];
    instance => instance [ label = "elomax_instance_destroy()" ];
    instance => kernel
      [ label = "kref_put()", ID = "the last one really deletes" ];
    kernel => instance [ label = "elomax_instance_free()" ];
    instance => usb
      [ label = "usb_buffer_free()\nusb_free_urb()",
        ID = "for read- and write-urb" ];
    instance => kernel [ label = "kfree()", ID = "now we're done" ];
    |||;
    --- [ label = "unload the driver from the kernel" ];
    |||;
    user => kernel [ label = "rmmod elomax" ];
    kernel => module
      [ label = "kernelmodule_exit()", URL = "\ref kernelmodule_exit" ];
    module => driver
      [ label = "elomax_driver_destruct()",
        URL = "\ref elomax_driver_destruct" ];
    driver => usb [ label = "usb_deregister()" ];
  @endmsc
**/
